From f25f719e5f77cee7586e9870d632220d71810919 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 13 Sep 2026 20:17:14 +0200 Subject: [PATCH] Fix the real crash: partyPage.html was missing data-controller Traced the exact "Cannot read properties of undefined (reading lifeCycleAbortSignal)" error by fetching the target server's actual viewmanager.js and pluginmanager.js directly (both are unauthenticated static JS) instead of guessing further from docs. Root cause: Emby's modern ViewManager (v4.10.0.40) only creates a view.controller - required before it calls any lifecycle method on the view - when the view's root element has a data-controller attribute resolving to an AMD module via pluginManager.getConfigurationResourceUrl(). partyPage.html never had one (only the legacy data-role="page" attribute), so newView.controller stayed undefined and getResumeOptions crashed reading a property off it. configPage.html (the admin settings page) apparently survives without this because dashboard pages go through a different/older loading path. Fix: added data-controller="__plugin/WatchPartyRoomController" to partyPage.html, and a third PluginPageInfo/embedded resource (partyPageController.js) - a minimal AMD module for it to resolve to. ViewManager mixes in baseview.js's onResume/onPause/lifeCycleAbortSignal automatically since this controller doesn't define its own, so it only needs to exist. Also checked (live JS probe, not a guess): pluginmanager.js has a hardcoded plugin-id allowlist (allowPluginPages), but it's bypassed whenever appHost.supports('restrictedplugins') is falsy - confirmed falsy here, so it isn't blocking anything on this server's web client. docs/architecture.md updated with the full trace for posterity. Co-Authored-By: Claude Sonnet 5 --- docs/architecture.md | 37 +++++++++++++++++-- src/WatchParty/Configuration/partyPage.html | 2 +- .../Configuration/partyPageController.js | 24 ++++++++++++ src/WatchParty/Plugin.cs | 11 ++++++ src/WatchParty/WatchParty.csproj | 1 + 5 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/WatchParty/Configuration/partyPageController.js diff --git a/docs/architecture.md b/docs/architecture.md index 8f057f0..49fd4d9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -241,10 +241,39 @@ can't check): - **`partyPage.html`**: whether `ApiClient.ajax({...})` really behaves like the jQuery-style options object it's called with here (`type`, `url`, `dataType`) — that's the long-standing Emby/Jellyfin web client convention, - not confirmed against this specific build. Whether `EnableInMainMenu`/ - `MenuIcon = "group"` place the page where you'd expect (main nav vs. a - user submenu) is also cosmetic-only until seen live — easy to adjust once - visible. + not confirmed against this specific build. +- Whether an automatic menu link ever actually appears anywhere for + `EnableInUserMenu`/`MenuIcon = "group"`. As of this writing the page is + reachable and crash-free (see below) but no menu entry has been spotted + yet — worth checking `MenuSection` next if it still doesn't show up + (left unset so far; the one confirmed real-world example didn't set it + either, but that example also didn't use `EnableInMainMenu`, which turned + out to matter for something else - see below). + +**Real bug found and fixed by reading the target server's actual client code** +(not from any doc — see git history for the two commits this produced): +navigating to the party page threw inside Emby's own `viewmanager.js` +(v4.10.0.40): `TypeError: Cannot read properties of undefined (reading +'lifeCycleAbortSignal')`. Fetching that file directly from the running +server (it's unauthenticated static JS) showed why: a view only gets a +`view.controller` — required before ViewManager will call any lifecycle +method on it — if its root element has a `data-controller` attribute +resolving to an AMD module. `configPage.html` never needed this because +admin dashboard pages apparently go through an older/separate loading path; +a page reached through `EnableInUserMenu`/`EnableInMainMenu` does not get +that same leniency. Fixed by adding +`data-controller="__plugin/WatchPartyRoomController"` to `partyPage.html` +and shipping a third, minimal `PluginPageInfo` (`partyPageController.js`) for +it to resolve to — see the comments in `Plugin.cs` and +`Configuration/partyPageController.js`. + +Also checked directly (via a JS probe against the live server, not a guess): +`pluginmanager.js` has a hardcoded GUID allowlist gating which plugins get a +page at all (`allowPluginPages`), but it's skipped entirely whenever +`appHost.supports('restrictedplugins')` is falsy — confirmed falsy on this +server's web client, so the allowlist is not what's blocking anything here. +It's presumably what actually blocks arbitrary plugins on official mobile +apps (matches the forum thread cited above). The remaining way to close these out: install the plugin DLL on a real Emby Server 4.9.x and exercise it with two accounts. diff --git a/src/WatchParty/Configuration/partyPage.html b/src/WatchParty/Configuration/partyPage.html index ee68ba1..3eb3cc0 100644 --- a/src/WatchParty/Configuration/partyPage.html +++ b/src/WatchParty/Configuration/partyPage.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/WatchParty/Configuration/partyPageController.js b/src/WatchParty/Configuration/partyPageController.js new file mode 100644 index 0000000..e19a3c5 --- /dev/null +++ b/src/WatchParty/Configuration/partyPageController.js @@ -0,0 +1,24 @@ +define([], function () { + 'use strict'; + + // Minimal controller so Emby's modern ViewManager (v4.10+) has a + // `view.controller` instance to call lifecycle hooks on. Without this, + // ViewManager crashes with "Cannot read properties of undefined (reading + // 'lifeCycleAbortSignal')" in getResumeOptions - confirmed by reading the + // actual viewmanager.js served by the target server. A view only gets a + // controller if its root element has a data-controller attribute + // resolving to a module like this one (see partyPage.html). + // + // We deliberately don't implement onResume/onPause ourselves: + // ViewManager's addMethodsToPluginControllerFactory automatically mixes + // in modules/viewmanager/baseview.js's prototype (which supplies both, + // including lifeCycleAbortSignal) whenever a controller doesn't already + // define both. The actual page logic stays in partyPage.html's inline + //