mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 11:38:26 +01:00
feat(network): add local network auto-switch feature (#1334)
This commit is contained in:
committed by
GitHub
parent
ac9ac5d423
commit
467bea7192
@@ -74,6 +74,7 @@ interface JellyfinContextValue {
|
||||
password: string,
|
||||
) => Promise<void>;
|
||||
removeSavedCredential: (serverUrl: string, userId: string) => Promise<void>;
|
||||
switchServerUrl: (newUrl: string) => void;
|
||||
}
|
||||
|
||||
const JellyfinContext = createContext<JellyfinContextValue | undefined>(
|
||||
@@ -466,6 +467,18 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
||||
},
|
||||
});
|
||||
|
||||
const switchServerUrl = useCallback(
|
||||
(newUrl: string) => {
|
||||
if (!jellyfin || !api?.accessToken) return;
|
||||
|
||||
const newApi = jellyfin.createApi(newUrl, api.accessToken);
|
||||
setApi(newApi);
|
||||
// Note: We don't update storage.set("serverUrl") here
|
||||
// because we want to keep the original remote URL as the "primary" URL
|
||||
},
|
||||
[jellyfin, api?.accessToken],
|
||||
);
|
||||
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [initialLoaded, setInitialLoaded] = useState(false);
|
||||
|
||||
@@ -541,6 +554,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
||||
loginWithPasswordMutation.mutateAsync({ serverUrl, username, password }),
|
||||
removeSavedCredential: (serverUrl, userId) =>
|
||||
removeSavedCredentialMutation.mutateAsync({ serverUrl, userId }),
|
||||
switchServerUrl,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
139
providers/ServerUrlProvider.tsx
Normal file
139
providers/ServerUrlProvider.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useAtomValue } from "jotai";
|
||||
import type React from "react";
|
||||
import {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useWifiSSID } from "@/hooks/useWifiSSID";
|
||||
import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { getServerLocalConfig } from "@/utils/secureCredentials";
|
||||
|
||||
interface ServerUrlContextValue {
|
||||
effectiveServerUrl: string | null;
|
||||
isUsingLocalUrl: boolean;
|
||||
currentSSID: string | null;
|
||||
refreshUrlState: () => void;
|
||||
}
|
||||
|
||||
const ServerUrlContext = createContext<ServerUrlContextValue | null>(null);
|
||||
|
||||
const DEBOUNCE_MS = 500;
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function ServerUrlProvider({ children }: Props): React.ReactElement {
|
||||
const api = useAtomValue(apiAtom);
|
||||
const { switchServerUrl } = useJellyfin();
|
||||
const { ssid, permissionStatus } = useWifiSSID();
|
||||
|
||||
console.log(
|
||||
"[ServerUrlProvider] ssid:",
|
||||
ssid,
|
||||
"permissionStatus:",
|
||||
permissionStatus,
|
||||
);
|
||||
|
||||
const [isUsingLocalUrl, setIsUsingLocalUrl] = useState(false);
|
||||
const [effectiveServerUrl, setEffectiveServerUrl] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const remoteUrlRef = useRef<string | null>(null);
|
||||
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const lastSSIDRef = useRef<string | null>(null);
|
||||
|
||||
// Sync remoteUrl from storage when api changes
|
||||
useEffect(() => {
|
||||
const storedUrl = storage.getString("serverUrl");
|
||||
if (storedUrl) {
|
||||
remoteUrlRef.current = storedUrl;
|
||||
}
|
||||
if (api?.basePath && !effectiveServerUrl) {
|
||||
setEffectiveServerUrl(api.basePath);
|
||||
}
|
||||
}, [api?.basePath, effectiveServerUrl]);
|
||||
|
||||
// Function to evaluate and switch URL based on current config and SSID
|
||||
const evaluateAndSwitchUrl = useCallback(() => {
|
||||
const remoteUrl = remoteUrlRef.current;
|
||||
if (!remoteUrl || !switchServerUrl) return;
|
||||
|
||||
const config = getServerLocalConfig(remoteUrl);
|
||||
const shouldUseLocal = Boolean(
|
||||
config?.enabled &&
|
||||
config.localUrl &&
|
||||
ssid !== null &&
|
||||
config.homeWifiSSIDs.includes(ssid),
|
||||
);
|
||||
|
||||
const targetUrl = shouldUseLocal ? config!.localUrl : remoteUrl;
|
||||
|
||||
console.log("[ServerUrlProvider] evaluateAndSwitchUrl:", {
|
||||
ssid,
|
||||
shouldUseLocal,
|
||||
targetUrl,
|
||||
config,
|
||||
});
|
||||
|
||||
switchServerUrl(targetUrl);
|
||||
setIsUsingLocalUrl(shouldUseLocal);
|
||||
setEffectiveServerUrl(targetUrl);
|
||||
}, [ssid, switchServerUrl]);
|
||||
|
||||
// Manual refresh function for when config changes
|
||||
const refreshUrlState = useCallback(() => {
|
||||
console.log("[ServerUrlProvider] refreshUrlState called");
|
||||
evaluateAndSwitchUrl();
|
||||
}, [evaluateAndSwitchUrl]);
|
||||
|
||||
// Debounced SSID change handler
|
||||
useEffect(() => {
|
||||
if (permissionStatus !== "granted") return;
|
||||
if (ssid === lastSSIDRef.current) return;
|
||||
|
||||
lastSSIDRef.current = ssid;
|
||||
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
evaluateAndSwitchUrl();
|
||||
}, DEBOUNCE_MS);
|
||||
|
||||
return () => {
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, [ssid, permissionStatus, evaluateAndSwitchUrl]);
|
||||
|
||||
return (
|
||||
<ServerUrlContext.Provider
|
||||
value={{
|
||||
effectiveServerUrl,
|
||||
isUsingLocalUrl,
|
||||
currentSSID: ssid,
|
||||
refreshUrlState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ServerUrlContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useServerUrl(): ServerUrlContextValue {
|
||||
const context = useContext(ServerUrlContext);
|
||||
if (!context) {
|
||||
throw new Error("useServerUrl must be used within ServerUrlProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user