[codex] Restart Jellyfin remote session after setup login (#112)

This commit is contained in:
Autumn (Bee)
2026-06-06 19:52:16 +01:00
committed by GitHub
parent ea79e331fa
commit af67c53dd6
7 changed files with 89 additions and 2 deletions
@@ -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.
@@ -1,6 +1,37 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import test from 'node:test'; 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', () => { test('composeJellyfinRuntimeHandlers returns callable jellyfin runtime handlers', () => {
let activePlayback: unknown = null; let activePlayback: unknown = null;
@@ -153,6 +153,16 @@ export type JellyfinRuntimeComposerResult = ComposerOutputs<{
openJellyfinSetupWindow: ReturnType<typeof createOpenJellyfinSetupWindowHandler>; openJellyfinSetupWindow: ReturnType<typeof createOpenJellyfinSetupWindowHandler>;
}>; }>;
export function createRestartJellyfinRemoteSessionAfterSetupLoginHandler(deps: {
getCurrentSession: () => unknown | null;
startJellyfinRemoteSession: (options?: { explicit?: boolean }) => Promise<void>;
}) {
return async (): Promise<void> => {
const hasActiveSession = deps.getCurrentSession() !== null;
await deps.startJellyfinRemoteSession(hasActiveSession ? { explicit: true } : undefined);
};
}
export function composeJellyfinRuntimeHandlers( export function composeJellyfinRuntimeHandlers(
options: JellyfinRuntimeComposerOptions, options: JellyfinRuntimeComposerOptions,
): JellyfinRuntimeComposerResult { ): JellyfinRuntimeComposerResult {
@@ -268,12 +278,19 @@ export function composeJellyfinRuntimeHandlers(
const maybeFocusExistingJellyfinSetupWindow = createMaybeFocusExistingJellyfinSetupWindowHandler( const maybeFocusExistingJellyfinSetupWindow = createMaybeFocusExistingJellyfinSetupWindowHandler(
options.maybeFocusExistingJellyfinSetupWindowMainDeps, options.maybeFocusExistingJellyfinSetupWindowMainDeps,
); );
const restartJellyfinRemoteSessionAfterSetupLogin =
createRestartJellyfinRemoteSessionAfterSetupLoginHandler({
getCurrentSession: () => options.startJellyfinRemoteSessionMainDeps.getCurrentSession(),
startJellyfinRemoteSession: (startOptions) => startJellyfinRemoteSession(startOptions),
});
const openJellyfinSetupWindow = createOpenJellyfinSetupWindowHandler( const openJellyfinSetupWindow = createOpenJellyfinSetupWindowHandler(
createBuildOpenJellyfinSetupWindowMainDepsHandler({ createBuildOpenJellyfinSetupWindowMainDepsHandler({
...options.openJellyfinSetupWindowMainDeps, ...options.openJellyfinSetupWindowMainDeps,
maybeFocusExistingSetupWindow: maybeFocusExistingJellyfinSetupWindow, maybeFocusExistingSetupWindow: maybeFocusExistingJellyfinSetupWindow,
getResolvedJellyfinConfig: () => getResolvedJellyfinConfig(), getResolvedJellyfinConfig: () => getResolvedJellyfinConfig(),
getJellyfinClientInfo: () => getJellyfinClientInfo(), getJellyfinClientInfo: () => getJellyfinClientInfo(),
restartRemoteSession: () => restartJellyfinRemoteSessionAfterSetupLogin(),
stopRemoteSession: () => stopJellyfinRemoteSession(),
})(), })(),
); );
@@ -40,6 +40,10 @@ test('open jellyfin setup window main deps builder maps callbacks', async () =>
clearStoredSession: () => calls.push('clear-session'), clearStoredSession: () => calls.push('clear-session'),
patchJellyfinConfig: () => calls.push('patch'), patchJellyfinConfig: () => calls.push('patch'),
persistAuthenticatedSession: () => calls.push('persist'), persistAuthenticatedSession: () => calls.push('persist'),
restartRemoteSession: () => {
calls.push('restart-remote');
},
stopRemoteSession: () => calls.push('stop-remote'),
logInfo: (message) => calls.push(`info:${message}`), logInfo: (message) => calls.push(`info:${message}`),
logError: (message) => calls.push(`error:${message}`), logError: (message) => calls.push(`error:${message}`),
showMpvOsd: (message) => calls.push(`osd:${message}`), showMpvOsd: (message) => calls.push(`osd:${message}`),
@@ -95,6 +99,8 @@ test('open jellyfin setup window main deps builder maps callbacks', async () =>
}, },
deps.getJellyfinClientInfo(), deps.getJellyfinClientInfo(),
); );
await deps.restartRemoteSession?.();
deps.stopRemoteSession?.();
deps.logInfo('ok'); deps.logInfo('ok');
deps.logError('bad', null); deps.logError('bad', null);
deps.showMpvOsd('toast'); deps.showMpvOsd('toast');
@@ -110,6 +116,8 @@ test('open jellyfin setup window main deps builder maps callbacks', async () =>
'clear-session', 'clear-session',
'patch', 'patch',
'persist', 'persist',
'restart-remote',
'stop-remote',
'info:ok', 'info:ok',
'error:bad', 'error:bad',
'osd:toast', 'osd:toast',
@@ -20,6 +20,10 @@ export function createBuildOpenJellyfinSetupWindowMainDepsHandler(
persistAuthenticatedSession: deps.persistAuthenticatedSession persistAuthenticatedSession: deps.persistAuthenticatedSession
? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo) ? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo)
: undefined, : undefined,
restartRemoteSession: deps.restartRemoteSession
? () => deps.restartRemoteSession?.()
: undefined,
stopRemoteSession: deps.stopRemoteSession ? () => deps.stopRemoteSession?.() : undefined,
logInfo: (message: string) => deps.logInfo(message), logInfo: (message: string) => deps.logInfo(message),
logError: (message: string, error: unknown) => deps.logError(message, error), logError: (message: string, error: unknown) => deps.logError(message, error),
showMpvOsd: (message: string) => deps.showMpvOsd(message), showMpvOsd: (message: string) => deps.showMpvOsd(message),
+13 -1
View File
@@ -160,6 +160,9 @@ test('createHandleJellyfinSetupSubmissionHandler applies successful login', asyn
patchPayload = session; patchPayload = session;
calls.push('patch'); calls.push('patch');
}, },
restartRemoteSession: async () => {
calls.push('restart-remote');
},
logInfo: () => calls.push('info'), logInfo: () => calls.push('info'),
logError: () => calls.push('error'), logError: () => calls.push('error'),
showMpvOsd: (message) => calls.push(`osd:${message}`), showMpvOsd: (message) => calls.push(`osd:${message}`),
@@ -172,7 +175,14 @@ test('createHandleJellyfinSetupSubmissionHandler applies successful login', asyn
'b', 'b',
); );
assert.equal(handled, true); 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.equal(authPassword, 'b');
assert.deepEqual(savedSession, { accessToken: 'token', userId: 'uid' }); assert.deepEqual(savedSession, { accessToken: 'token', userId: 'uid' });
assert.deepEqual(patchPayload, { assert.deepEqual(patchPayload, {
@@ -329,6 +339,7 @@ test('createHandleJellyfinSetupSubmissionHandler handles logout and done', async
saveStoredSession: () => calls.push('save'), saveStoredSession: () => calls.push('save'),
clearStoredSession: () => calls.push('clear'), clearStoredSession: () => calls.push('clear'),
patchJellyfinConfig: () => calls.push('patch'), patchJellyfinConfig: () => calls.push('patch'),
stopRemoteSession: () => calls.push('stop-remote'),
logInfo: (message) => calls.push(message), logInfo: (message) => calls.push(message),
logError: () => calls.push('error'), logError: () => calls.push('error'),
showMpvOsd: (message) => calls.push(`osd:${message}`), 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.equal(await handler('subminer://jellyfin-setup?action=done'), true);
assert.deepEqual(calls, [ assert.deepEqual(calls, [
'clear', 'clear',
'stop-remote',
'Cleared stored Jellyfin auth session.', 'Cleared stored Jellyfin auth session.',
'osd:Jellyfin logged out', 'osd:Jellyfin logged out',
'reload', 'reload',
+10
View File
@@ -425,6 +425,8 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: {
clearStoredSession: () => void; clearStoredSession: () => void;
patchJellyfinConfig: (session: JellyfinSession) => void; patchJellyfinConfig: (session: JellyfinSession) => void;
persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void; persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void;
restartRemoteSession?: () => Promise<void> | void;
stopRemoteSession?: () => void;
logInfo: (message: string) => void; logInfo: (message: string) => void;
logError: (message: string, error: unknown) => void; logError: (message: string, error: unknown) => void;
showMpvOsd: (message: string) => void; showMpvOsd: (message: string) => void;
@@ -447,6 +449,7 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: {
if (submission.action === 'logout') { if (submission.action === 'logout') {
try { try {
deps.clearStoredSession(); deps.clearStoredSession();
deps.stopRemoteSession?.();
deps.logInfo('Cleared stored Jellyfin auth session.'); deps.logInfo('Cleared stored Jellyfin auth session.');
deps.showMpvOsd('Jellyfin logged out'); deps.showMpvOsd('Jellyfin logged out');
deps.reloadSetupWindow({ deps.reloadSetupWindow({
@@ -491,6 +494,7 @@ export function createHandleJellyfinSetupSubmissionHandler(deps: {
deps.saveStoredSession({ accessToken: session.accessToken, userId: session.userId }); deps.saveStoredSession({ accessToken: session.accessToken, userId: session.userId });
deps.patchJellyfinConfig(session); deps.patchJellyfinConfig(session);
} }
await deps.restartRemoteSession?.();
deps.logInfo(`Jellyfin setup saved for ${session.username}.`); deps.logInfo(`Jellyfin setup saved for ${session.username}.`);
deps.showMpvOsd('Jellyfin login success'); deps.showMpvOsd('Jellyfin login success');
deps.reloadSetupWindow({ deps.reloadSetupWindow({
@@ -593,6 +597,8 @@ export function createOpenJellyfinSetupWindowHandler<
clearStoredSession: () => void; clearStoredSession: () => void;
patchJellyfinConfig: (session: JellyfinSession) => void; patchJellyfinConfig: (session: JellyfinSession) => void;
persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void; persistAuthenticatedSession?: (session: JellyfinSession, clientInfo: JellyfinClientInfo) => void;
restartRemoteSession?: () => Promise<void> | void;
stopRemoteSession?: () => void;
logInfo: (message: string) => void; logInfo: (message: string) => void;
logError: (message: string, error: unknown) => void; logError: (message: string, error: unknown) => void;
showMpvOsd: (message: string) => void; showMpvOsd: (message: string) => void;
@@ -633,6 +639,10 @@ export function createOpenJellyfinSetupWindowHandler<
persistAuthenticatedSession: deps.persistAuthenticatedSession persistAuthenticatedSession: deps.persistAuthenticatedSession
? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo) ? (session, clientInfo) => deps.persistAuthenticatedSession?.(session, clientInfo)
: undefined, : undefined,
restartRemoteSession: deps.restartRemoteSession
? () => deps.restartRemoteSession?.()
: undefined,
stopRemoteSession: deps.stopRemoteSession ? () => deps.stopRemoteSession?.() : undefined,
logInfo: (message) => deps.logInfo(message), logInfo: (message) => deps.logInfo(message),
logError: (message, error) => deps.logError(message, error), logError: (message, error) => deps.logError(message, error),
showMpvOsd: (message) => deps.showMpvOsd(message), showMpvOsd: (message) => deps.showMpvOsd(message),