ginjaninja 636 Posted 4 hours ago Posted 4 hours ago (edited) I noticed separate users were seeing the same page instance on my user facing plugin even without using UIViewInfoChanged. Does any one have an example repository with good patterns for user pages with generic ui please? Hopefully not ai slop but claudes suggestion was below, basically suggested the need of "abandoning the GenericEdit/generic-UI-page framework for those [userfacing] pages" Summary: GenericUI command routing limitation (Emby 4.10.0.22) Component: Emby.Web.GenericUI (server-side C#) + modules/genericui/genericui.js (dashboard client) The limitation: Two separate but compounding issues make it impossible for a plugin using IPluginUIView/IPluginPageView (the GenericEdit-driven page framework) to reliably identify the calling user on a per-command basis. Server-side: no per-command user identity reaches the view. IPluginUIView.RunCommand(string itemId, string commandId, string data) — the interface every plugin page implements — has no user parameter at all. The host class that dispatches to it, PageControllerHostBase.RunCommand(requestItemId, requestCommandId, requestData, UserDto user), does receive the real authenticated caller from the API layer, but never assigns it to the view's User property before invoking RunCommand. IPluginUIView.User is only ever refreshed on GetUIView (initial page load / tab switch) — never on a postback. So inside RunCommand, a plugin has no reliable way to know who actually issued the command. Client-side: no way to route around it. genericui.js's runUiCommand() hardcodes every command from every plugin's generic UI page to a single fixed endpoint, POST UI/Command, regardless of which button or command fired. There is no per-button/per-command hook, override, or extension point for a plugin to direct specific actions to its own authenticated API endpoint instead. The command dispatch is entirely generic and not plugin-customizable at the transport level. Combined effect: Because UI/Command is a single shared endpoint whose server-side handler (PageControllerHostBase) also has one controller instance shared across all users of the server (keyed only by pageId, not by user/session — UIPagesManager.controllers is ConcurrentDictionary<string, PageControllerHostBase>), a plugin author has no supported way to build a multi-user-facing page (as opposed to a single shared admin config page) where each command execution can be confidently attributed to the correct calling user. The only available workaround is having the view privately track identity through data it stamps and asks the client to echo back on the next request — which works for legitimate use, but isn't something the SDK provides or guarantees, and could be spoofed by a malicious client since it isn't real authentication. Suggested fix for the SDK maintainers: either (a) have PageControllerHostBase.RunCommand assign CurrentUIView.User = user before calling RunCommand, using the already-available authenticated caller, or (b) add a UserDto parameter to IPluginUIView.RunCommand itself so plugin authors don't have to reconstruct identity by hand. https://github.com/ginjaninja1/recommendme Claude suggested a hot fix which seems to work outlined below applied to recommendme ## Emby generic UI pages are singleton, server-wide, per pageId — not per-user/session `UIPagesManager.controllers` is `ConcurrentDictionary<string, PageControllerHostBase>`, keyed ONLY by pageId. One instance of your `IPluginUIView` serves every user on the server. Anything stored directly on that view's `ContentData`/fields (search results, form state, etc.) is visible/overwritten by every other user — confirmed root cause of "user sees another user's search results" bugs. Worse: `IPluginUIView.User` (and `IUIView.User`) is ONLY refreshed by the framework on `GetUIView` (page load / tab switch) — `PageControllerHostBase.RunCommand` receives the real authenticated `UserDto` from Emby's API layer but never assigns it to `CurrentUIView.User` before invoking `RunCommand`. So `this.User` inside a page's `RunCommand` is not "who clicked this" — it's "whoever most recently loaded this page anywhere on the server." Do not use `this.User` for identity/authorization inside `RunCommand`. **Working fix pattern** (no adversarial/security requirement — trusted single-tenant use only): - Add an `OwnerUserId` field to the ContentData/view-model class, `[Browsable(false)]`. - Stamp it with `value.Id` in the view's `User` property setter (this only fires on `GetUIView`, which is trustworthy). - Because `PageControllerHostBase.RunCommand` deserializes the client's posted `data` string fresh on every call (this string is per-request, not shared), and the client always round-trips the full ContentData object back on postback, `OwnerUserId` survives the trip. - In `RunCommand`, deserialize `data` FIRST, pull `OwnerUserId` from it, and resolve the calling user (`IUserManager.GetUserById`) from that — never from `this.User`. - Keep per-user state in a `ConcurrentDictionary<string, TViewModel>` keyed by that same id, looked up in both the `User` setter and `RunCommand`. - `UserDto.Id` is a `string` (not Guid/long) — matches the `GetUserById(string)` overload. - No eviction is built into this pattern — the dictionary grows for the process lifetime. Fine for low user counts; needs `ISessionManager.SessionEnded`-driven cleanup at scale. My other plugin doesnt have the suggested fix, i mention it in case an example of a default case is needed. (to help explain what i might be dsoing wrong please) I asked the ai to consume the sdk and use the patterns in sdk demo to build the pages. Maybe there is a proper approach to user pages my ai isnt seeing? https://github.com/ginjaninja1/ManageComingSoon Edited 4 hours ago by ginjaninja
softworkz 5345 Posted 4 hours ago Posted 4 hours ago Yes - when GenericUI was created there didn't even exist any possibility for a plugin to provide "per-user settings pages". Everything is designed in a way to make operations and UI single-instance, so that long-running operations - like a TV tuner channel scan - do not get lost when you accidentally close the dialog - or the browser tab. Or when another admin would log in, they are supposed to see those operations, so they won't get to perform any conflicting operations. To serve user settings, a number of changes are required. The AI suggestion is not suitable - each user's view needs to be backed by a separate server-side instance.
softworkz 5345 Posted 2 hours ago Posted 2 hours ago Afer some deeper assessment, the answer has become more clear: For the time being, html/js is the only option for user settings. 1
ginjaninja 636 Posted 45 minutes ago Author Posted 45 minutes ago Hi @softworkz Thanks for confirming. I feel like i am on "the bleeding edge" on plugin development, exciting times...but good to know where i need to look now. 1
softworkz 5345 Posted 43 minutes ago Posted 43 minutes ago 1 minute ago, ginjaninja said: Hi @softworkz Thanks for confirming. I feel like i am on "the bleeding edge" on plugin development, exciting times...but good to know where i need to look now. The good news is that the other issues you ran into are fixed internally now - plus some new (small) features coming...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now