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:
parent
d4a365ae5c
commit
f25f719e5f
5 changed files with 70 additions and 5 deletions
|
|
@ -241,10 +241,39 @@ can't check):
|
||||||
- **`partyPage.html`**: whether `ApiClient.ajax({...})` really behaves like
|
- **`partyPage.html`**: whether `ApiClient.ajax({...})` really behaves like
|
||||||
the jQuery-style options object it's called with here (`type`, `url`,
|
the jQuery-style options object it's called with here (`type`, `url`,
|
||||||
`dataType`) — that's the long-standing Emby/Jellyfin web client convention,
|
`dataType`) — that's the long-standing Emby/Jellyfin web client convention,
|
||||||
not confirmed against this specific build. Whether `EnableInMainMenu`/
|
not confirmed against this specific build.
|
||||||
`MenuIcon = "group"` place the page where you'd expect (main nav vs. a
|
- Whether an automatic menu link ever actually appears anywhere for
|
||||||
user submenu) is also cosmetic-only until seen live — easy to adjust once
|
`EnableInUserMenu`/`MenuIcon = "group"`. As of this writing the page is
|
||||||
visible.
|
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
|
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.
|
Server 4.9.x and exercise it with two accounts.
|
||||||
|
|
|
||||||
|
|
@ -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 data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<div class="verticalSection">
|
<div class="verticalSection">
|
||||||
|
|
|
||||||
24
src/WatchParty/Configuration/partyPageController.js
Normal file
24
src/WatchParty/Configuration/partyPageController.js
Normal 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;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -74,6 +74,17 @@ namespace WatchParty
|
||||||
MenuIcon = "group",
|
MenuIcon = "group",
|
||||||
IsMainConfigPage = false
|
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"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Configuration\configPage.html" />
|
<EmbeddedResource Include="Configuration\configPage.html" />
|
||||||
<EmbeddedResource Include="Configuration\partyPage.html" />
|
<EmbeddedResource Include="Configuration\partyPage.html" />
|
||||||
|
<EmbeddedResource Include="Configuration\partyPageController.js" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Dev convenience: after building, also drop the DLL into a local Emby
|
<!-- Dev convenience: after building, also drop the DLL into a local Emby
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue