mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 12:55:16 -07:00
Fix Jellyfin Login (#76)
This commit is contained in:
@@ -654,6 +654,40 @@ test('authenticateWithPassword surfaces invalid credentials and server status fa
|
||||
}
|
||||
});
|
||||
|
||||
test('authenticateWithPassword surfaces unreachable server failures', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () => {
|
||||
throw new TypeError('fetch failed');
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => authenticateWithPassword('http://jellyfin.local:8096/', 'kyle', 'pw', clientInfo),
|
||||
/Could not reach Jellyfin server \(fetch failed\)\./,
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('authenticateWithPassword surfaces login timeouts', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () => {
|
||||
const error = new Error('aborted') as Error & { name: string };
|
||||
error.name = 'AbortError';
|
||||
throw error;
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => authenticateWithPassword('http://jellyfin.local:8096/', 'kyle', 'pw', clientInfo),
|
||||
/Jellyfin login timed out\./,
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('listLibraries surfaces token-expiry auth errors', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { JellyfinConfig } from '../../types';
|
||||
|
||||
const JELLYFIN_TICKS_PER_SECOND = 10_000_000;
|
||||
const JELLYFIN_LOGIN_TIMEOUT_MS = 15_000;
|
||||
|
||||
export interface JellyfinAuthSession {
|
||||
serverUrl: string;
|
||||
@@ -116,6 +117,21 @@ function asIntegerOrNull(value: unknown): number | null {
|
||||
return typeof value === 'number' && Number.isInteger(value) ? value : null;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
function isAbortError(error: unknown): boolean {
|
||||
return isRecord(error) && error.name === 'AbortError';
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error || 'unknown error');
|
||||
}
|
||||
|
||||
function resolveDeliveryUrl(
|
||||
session: JellyfinAuthSession,
|
||||
stream: JellyfinMediaStream,
|
||||
@@ -309,17 +325,30 @@ export async function authenticateWithPassword(
|
||||
if (!username.trim()) throw new Error('Missing Jellyfin username.');
|
||||
if (!password) throw new Error('Missing Jellyfin password.');
|
||||
|
||||
const response = await fetch(`${normalizedUrl}/Users/AuthenticateByName`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: createAuthorizationHeader(client),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
Username: username,
|
||||
Pw: password,
|
||||
}),
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), JELLYFIN_LOGIN_TIMEOUT_MS);
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${normalizedUrl}/Users/AuthenticateByName`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: createAuthorizationHeader(client),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
Username: username,
|
||||
Pw: password,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
if (isAbortError(error)) {
|
||||
throw new Error('Jellyfin login timed out. Check the server URL and network connection.');
|
||||
}
|
||||
throw new Error(`Could not reach Jellyfin server (${getErrorMessage(error)}).`);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
throw new Error('Invalid Jellyfin username or password.');
|
||||
|
||||
Reference in New Issue
Block a user