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 <noreply@anthropic.com>
This commit is contained in:
Nicolas 2026-09-13 20:17:14 +02:00
parent d4a365ae5c
commit f25f719e5f
5 changed files with 70 additions and 5 deletions

View file

@ -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.

View file

@ -1,4 +1,4 @@
<div id="watchPartyPage" data-role="page" class="page type-interior" data-require="emby-input,emby-button">
<div id="watchPartyPage" data-role="page" class="page type-interior" data-require="emby-input,emby-button" data-controller="__plugin/WatchPartyRoomController">
<div data-role="content">
<div class="content-primary">
<div class="verticalSection">

View file

@ -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
// <script>, driven by the pageshow/pagehide events ViewManager still
// dispatches on views that carry data-role="page" (its "needsPageEvents"
// handling) - so this file only needs to exist, not do anything.
return function (view, params) {
this.view = view;
this.params = params;
};
});

View file

@ -74,6 +74,17 @@ namespace WatchParty
MenuIcon = "group",
IsMainConfigPage = false
};
// Companion AMD controller module partyPage.html points to via
// data-controller="__plugin/WatchPartyRoomController" - required
// by Emby's modern ViewManager (v4.10+) or it crashes trying to
// build lifecycle options for a nonexistent view.controller. Not
// a page anyone navigates to directly, so no menu flags.
yield return new PluginPageInfo
{
Name = "WatchPartyRoomController",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.partyPageController.js"
};
}
}
}

View file

@ -20,6 +20,7 @@
<ItemGroup>
<EmbeddedResource Include="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\partyPage.html" />
<EmbeddedResource Include="Configuration\partyPageController.js" />
</ItemGroup>
<!-- Dev convenience: after building, also drop the DLL into a local Emby