Files
streamyfin/hooks/useServerUrlResolver.ts
Gauvain 7fc74df0aa refactor(jellyseerr): keep the server version out of the field UI; enforce it at login
The resolver field only needs to find the working URL — the Jellyseerr version requirement is irrelevant there and only polluted the UI.

- jellyseerrProbe: validate reachability + that it's a jellyseerr (no version gate, no version-too-low outcome).

- Drop the version-too-low reason from the whole resolver stack (types, resolve, hook, status text, i18n).

- Min version 2.0.0 stays enforced in JellyseerrApi.test() at login: now writes an error log + toast, and uses numeric isVersionBelow (fixes the "2.10.0" < "2.0.0" string-compare bug).
2026-06-04 21:24:40 +02:00

66 lines
1.9 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import {
type ResolveFailureReason,
type ResolveOptions,
type ResolveResult,
resolveServerUrl,
} from "@/utils/serverUrl/resolve";
import type { ServerProbe } from "@/utils/serverUrl/types";
export type ServerUrlResolverState =
| { status: "idle" }
| { status: "resolving" }
| { status: "ok"; resolvedUrl: string; meta?: Record<string, unknown> }
| { status: "error"; reason: ResolveFailureReason };
/**
* Stateful wrapper around `resolveServerUrl` for screens.
*
* `resolve(input)` cancels any in-flight resolution, drives the state machine
* (idle → resolving → ok | error) and returns the raw result. Pass a stable
* (module-level) probe; memoize `options` if you supply one.
*/
export function useServerUrlResolver(
probe: ServerProbe,
options?: ResolveOptions,
) {
const [state, setState] = useState<ServerUrlResolverState>({
status: "idle",
});
const abortRef = useRef<AbortController | null>(null);
const resolve = useCallback(
async (input: string): Promise<ResolveResult> => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setState({ status: "resolving" });
const result = await resolveServerUrl(input, probe, {
...options,
signal: controller.signal,
});
// Ignore results from a resolution that was superseded/cancelled.
if (!controller.signal.aborted) {
setState(
result.ok
? { status: "ok", resolvedUrl: result.url, meta: result.meta }
: { status: "error", reason: result.reason },
);
}
return result;
},
[probe, options],
);
const reset = useCallback(() => {
abortRef.current?.abort();
setState({ status: "idle" });
}, []);
useEffect(() => () => abortRef.current?.abort(), []);
return { ...state, resolve, reset };
}