From af67c53dd63d2fa7c227bb9c5717b8afe4bdd375 Mon Sep 17 00:00:00 2001 From: "Autumn (Bee)" Date: Sat, 6 Jun 2026 19:52:16 +0100 Subject: [PATCH] [codex] Restart Jellyfin remote session after setup login (#112) --- changes/jellyfin-remote-session-restart.md | 5 +++ .../jellyfin-runtime-composer.test.ts | 33 ++++++++++++++++++- .../composers/jellyfin-runtime-composer.ts | 17 ++++++++++ .../jellyfin-setup-window-main-deps.test.ts | 8 +++++ .../jellyfin-setup-window-main-deps.ts | 4 +++ .../runtime/jellyfin-setup-window.test.ts | 14 +++++++- src/main/runtime/jellyfin-setup-window.ts | 10 ++++++ 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 changes/jellyfin-remote-session-restart.md diff --git a/changes/jellyfin-remote-session-restart.md b/changes/jellyfin-remote-session-restart.md new file mode 100644 index 00000000..41f345ac --- /dev/null +++ b/changes/jellyfin-remote-session-restart.md @@ -0,0 +1,5 @@ +type: fixed +area: jellyfin + +- Restarted the Jellyfin remote session after successful setup login so websocket reconnects use the freshly saved credentials. +- Stopped the Jellyfin remote session on setup logout. diff --git a/src/main/runtime/composers/jellyfin-runtime-composer.test.ts b/src/main/runtime/composers/jellyfin-runtime-composer.test.ts index f2d67816..ec2308c0 100644 --- a/src/main/runtime/composers/jellyfin-runtime-composer.test.ts +++ b/src/main/runtime/composers/jellyfin-runtime-composer.test.ts @@ -1,6 +1,37 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { composeJellyfinRuntimeHandlers } from './jellyfin-runtime-composer'; +import { + composeJellyfinRuntimeHandlers, + createRestartJellyfinRemoteSessionAfterSetupLoginHandler, +} from './jellyfin-runtime-composer'; + +test('setup login restart uses auto-connect path without an active remote session', async () => { + const startOptions: Array<{ explicit?: boolean } | undefined> = []; + const restart = createRestartJellyfinRemoteSessionAfterSetupLoginHandler({ + getCurrentSession: () => null, + startJellyfinRemoteSession: async (options) => { + startOptions.push(options); + }, + }); + + await restart(); + + assert.deepEqual(startOptions, [undefined]); +}); + +test('setup login restart explicitly refreshes an active remote session', async () => { + const startOptions: Array<{ explicit?: boolean } | undefined> = []; + const restart = createRestartJellyfinRemoteSessionAfterSetupLoginHandler({ + getCurrentSession: () => ({ stop: () => {} }), + startJellyfinRemoteSession: async (options) => { + startOptions.push(options); + }, + }); + + await restart(); + + assert.deepEqual(startOptions, [{ explicit: true }]); +}); test('composeJellyfinRuntimeHandlers returns callable jellyfin runtime handlers', () => { let activePlayback: unknown = null; diff --git a/src/main/runtime/composers/jellyfin-runtime-composer.ts b/src/main/runtime/composers/jellyfin-runtime-composer.ts index 30892744..7433ec3f 100644 --- a/src/main/runtime/composers/jellyfin-runtime-composer.ts +++ b/src/main/runtime/composers/jellyfin-runtime-composer.ts @@ -153,6 +153,16 @@ export type JellyfinRuntimeComposerResult = ComposerOutputs<{ openJellyfinSetupWindow: ReturnType; }>; +export function createRestartJellyfinRemoteSessionAfterSetupLoginHandler(deps: { + getCurrentSession: () => unknown | null; + startJellyfinRemoteSession: (options?: { explicit?: boolean }) => Promise; +}) { + return async (): Promise => { + const hasActiveSession = deps.getCurrentSession() !== null; + await deps.startJellyfinRemoteSession(hasActiveSession ? { explicit: true } : undefined); + }; +} + export function composeJellyfinRuntimeHandlers( options: JellyfinRuntimeComposerOptions, ): JellyfinRuntimeComposerResult { @@ -268,12 +278,19 @@ export function composeJellyfinRuntimeHandlers( const maybeFocusExistingJellyfinSetupWindow = createMaybeFocusExistingJellyfinSetupWindowHandler( options.maybeFocusExistingJellyfinSetupWindowMainDeps, ); + const restartJellyfinRemoteSessionAfterSetupLogin = + createRestartJellyfinRemoteSessionAfterSetupLoginHandler({ + getCurrentSession: () => options.startJellyfinRemoteSessionMainDeps.getCurrentSession(), + startJellyfinRemoteSession: (startOptions) => startJellyfinRemoteSession(startOptions), + }); const openJellyfinSetupWindow = createOpenJellyfinSetupWindowHandler( createBuildOpenJellyfinSetupWindowMainDepsHandler({ ...options.openJellyfinSetupWindowMainDeps, maybeFocusExistingSetupWindow: maybeFocusExistingJellyfinSetupWindow, getResolvedJellyfinConfig: () => getResolvedJellyfinConfig(), getJellyfinClientInfo: () => getJellyfinClientInfo(), + restartRemoteSession: () => restartJellyfinRemoteSessionAfterSetupLogin(), + stopRemoteSession: () => stopJellyfinRemoteSession(), })(), ); diff --git a/src/main/runtime/jellyfin-setup-window-main-deps.test.ts b/src/main/runtime/jellyfin-setup-window-main-deps.test.ts index 4d812a5d..80070095 100644 --- a/src/main/runtime/jellyfin-setup-window-main-deps.test.ts +++ b/src/main/runtime/jellyfin-setup-window-main-deps.test.ts @@ -40,6 +40,10 @@ test('open jellyfin setup window main deps builder maps callbacks', async () => clearStoredSession: () => calls.push('clear-session'), patchJellyfinConfig: () => calls.push('patch'), persistAuthenticatedSession: () => calls.push('persist'), + restartRemoteSession: () => { + calls.push('restart-remote'); + }, + stopRemoteSession: () => calls.push('stop-remote'), logInfo: (message) => calls.push(`info:${message}`), logError: (message) => calls.push(`error:${message}`), showMpvOsd: (message) => calls.push(`osd:${message}`), @@ -95,6 +99,8 @@ test('open jellyfin setup window main deps builder maps callbacks', async () => }, deps.getJellyfinClientInfo(), ); + await deps.restartRemoteSession?.(); + deps.stopRemoteSession?.(); deps.logInfo('ok'); deps.logError('bad', null); deps.showMpvOsd('toast'); @@ -110,6 +116,8 @@ test('open jellyfin setup window main deps builder maps callbacks', async () => 'clear-session', 'patch', 'persist', + 'restart-remote', + 'stop-remote', 'info:ok', 'error:bad', 'osd:toast', diff --git a/src/main/runtime/jellyfin-setup-window-main-deps.ts b/src/main/runtime/jellyfin-setup-window-main-deps.ts index 416eddbd..66ef9480 100644 --- a/src/main/runtime/jellyfin-setup-window-main-deps.ts +++ b/src/main/runtime/jellyfin-setup-window-main-deps.ts @@ -20,6 +20,10 @@ export function createBuildOpenJellyfinSetupWindowMainDepsHandler( persistAuthenticatedSession: deps.persistAuthenticatedSession ? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo) : undefined, + restartRemoteSession: deps.restartRemoteSession + ? () => deps.restartRemoteSession?.() + : undefined, + stopRemoteSession: deps.stopRemoteSession ? () => deps.stopRemoteSession?.() : undefined, logInfo: (message: string) => deps.logInfo(message), logError: (message: string, error: unknown) => deps.logError(message, error), showMpvOsd: (message: string) => deps.showMpvOsd(message), diff --git a/src/main/runtime/jellyfin-setup-window.test.ts b/src/main/runtime/jellyfin-setup-window.test.ts index 8abac80b..427a6588 100644 --- a/src/main/runtime/jellyfin-setup-window.test.ts +++ b/src/main/runtime/jellyfin-setup-window.test.ts @@ -160,6 +160,9 @@ test('createHandleJellyfinSetupSubmissionHandler applies successful login', asyn patchPayload = session; calls.push('patch'); }, + restartRemoteSession: async () => { + calls.push('restart-remote'); + }, logInfo: () => calls.push('info'), logError: () => calls.push('error'), showMpvOsd: (message) => calls.push(`osd:${message}`), @@ -172,7 +175,14 @@ test('createHandleJellyfinSetupSubmissionHandler applies successful login', asyn 'b', ); assert.equal(handled, true); - assert.deepEqual(calls, ['save', 'patch', 'info', 'osd:Jellyfin login success', 'reload']); + assert.deepEqual(calls, [ + 'save', + 'patch', + 'restart-remote', + 'info', + 'osd:Jellyfin login success', + 'reload', + ]); assert.equal(authPassword, 'b'); assert.deepEqual(savedSession, { accessToken: 'token', userId: 'uid' }); assert.deepEqual(patchPayload, { @@ -329,6 +339,7 @@ test('createHandleJellyfinSetupSubmissionHandler handles logout and done', async saveStoredSession: () => calls.push('save'), clearStoredSession: () => calls.push('clear'), patchJellyfinConfig: () => calls.push('patch'), + stopRemoteSession: () => calls.push('stop-remote'), logInfo: (message) => calls.push(message), logError: () => calls.push('error'), showMpvOsd: (message) => calls.push(`osd:${message}`), @@ -340,6 +351,7 @@ test('createHandleJellyfinSetupSubmissionHandler handles logout and done', async assert.equal(await handler('subminer://jellyfin-setup?action=done'), true); assert.deepEqual(calls, [ 'clear', + 'stop-remote', 'Cleared stored Jellyfin auth session.', 'osd:Jellyfin logged out', 'reload', diff --git a/src/main/runtime/jellyfin-setup-window.ts b/src/main/runtime/jellyfin-setup-window.ts index 3a26ac0b..82e6de83 100644 --- a/src/main/runtime/jellyfin-setup-window.ts +++ b/src/main/runtime/jellyfin-setup-window.ts @@ -425,6 +425,8 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: { clearStoredSession: () => void; patchJellyfinConfig: (session: JellyfinSession) => void; persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void; + restartRemoteSession?: () => Promise | void; + stopRemoteSession?: () => void; logInfo: (message: string) => void; logError: (message: string, error: unknown) => void; showMpvOsd: (message: string) => void; @@ -447,6 +449,7 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: { if (submission.action === 'logout') { try { deps.clearStoredSession(); + deps.stopRemoteSession?.(); deps.logInfo('Cleared stored Jellyfin auth session.'); deps.showMpvOsd('Jellyfin logged out'); deps.reloadSetupWindow({ @@ -491,6 +494,7 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: { deps.saveStoredSession({ accessToken: session.accessToken, userId: session.userId }); deps.patchJellyfinConfig(session); } + await deps.restartRemoteSession?.(); deps.logInfo(`Jellyfin setup saved for ${session.username}.`); deps.showMpvOsd('Jellyfin login success'); deps.reloadSetupWindow({ @@ -593,6 +597,8 @@ export function createOpenJellyfinSetupWindowHandler< clearStoredSession: () => void; patchJellyfinConfig: (session: JellyfinSession) => void; persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void; + restartRemoteSession?: () => Promise | void; + stopRemoteSession?: () => void; logInfo: (message: string) => void; logError: (message: string, error: unknown) => void; showMpvOsd: (message: string) => void; @@ -633,6 +639,10 @@ export function createOpenJellyfinSetupWindowHandler< persistAuthenticatedSession: deps.persistAuthenticatedSession ? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo) : undefined, + restartRemoteSession: deps.restartRemoteSession + ? () => deps.restartRemoteSession?.() + : undefined, + stopRemoteSession: deps.stopRemoteSession ? () => deps.stopRemoteSession?.() : undefined, logInfo: (message) => deps.logInfo(message), logError: (message, error) => deps.logError(message, error), showMpvOsd: (message) => deps.showMpvOsd(message),