From 214832f81c4f8fa8954c9e14bcab3d5faf80d94a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 18:14:44 +0000 Subject: [PATCH] Implement enhanced server switching with credential persistence and auto-login Co-authored-by: retardgerman <78982850+retardgerman@users.noreply.github.com> --- components/PreviousServersList.tsx | 25 ++++- components/settings/ServerSwitcher.tsx | 28 +++++- providers/JellyfinProvider.tsx | 128 ++++++++++++++++++++++++- translations/en.json | 4 +- 4 files changed, 177 insertions(+), 8 deletions(-) diff --git a/components/PreviousServersList.tsx b/components/PreviousServersList.tsx index ffa310d3..2a115fb8 100644 --- a/components/PreviousServersList.tsx +++ b/components/PreviousServersList.tsx @@ -8,6 +8,13 @@ import { ListItem } from "./list/ListItem"; interface Server { address: string; + serverName?: string; + serverId?: string; + lastUsername?: string; + savedCredentials?: { + username: string; + password: string; + }; } interface PreviousServersListProps { @@ -26,6 +33,20 @@ export const PreviousServersList: React.FC = ({ const { t } = useTranslation(); + const getServerDisplayName = (server: Server) => { + if (server.serverName) { + return `${server.serverName}`; + } + return server.address; + }; + + const getServerSubtitle = (server: Server) => { + if (server.lastUsername) { + return `${server.address} • ${server.lastUsername}`; + } + return server.address; + }; + if (!previousServers.length) return null; return ( @@ -35,7 +56,9 @@ export const PreviousServersList: React.FC = ({ onServerSelect(s)} - title={s.address} + title={getServerDisplayName(s)} + subtitle={getServerSubtitle(s)} + icon={s.savedCredentials ? "key" : "server"} showArrow /> ))} diff --git a/components/settings/ServerSwitcher.tsx b/components/settings/ServerSwitcher.tsx index b3447b01..18feac82 100644 --- a/components/settings/ServerSwitcher.tsx +++ b/components/settings/ServerSwitcher.tsx @@ -10,6 +10,13 @@ import { ListItem } from "../list/ListItem"; interface Server { address: string; + serverName?: string; + serverId?: string; + lastUsername?: string; + savedCredentials?: { + username: string; + password: string; + }; } interface Props extends ViewProps {} @@ -38,6 +45,23 @@ export const ServerSwitcher: React.FC = ({ ...props }) => { } }; + const getServerDisplayName = (server: Server) => { + if (server.serverName) { + return `${server.serverName} (${server.address})`; + } + return server.address; + }; + + const getServerSubtitle = (server: Server) => { + if (server.lastUsername) { + const hasCredentials = !!server.savedCredentials; + return hasCredentials + ? `${server.lastUsername} • Auto-login available` + : `Last user: ${server.lastUsername}`; + } + return undefined; + }; + if (!previousServers.length) { return ( @@ -55,7 +79,9 @@ export const ServerSwitcher: React.FC = ({ ...props }) => { handleServerSwitch(server)} - title={server.address} + title={getServerDisplayName(server)} + subtitle={getServerSubtitle(server)} + icon={server.savedCredentials ? "key" : "server"} showArrow disabled={switchingServer === server.address} /> diff --git a/providers/JellyfinProvider.tsx b/providers/JellyfinProvider.tsx index 0a2c5105..0b42eb7e 100644 --- a/providers/JellyfinProvider.tsx +++ b/providers/JellyfinProvider.tsx @@ -30,6 +30,13 @@ import { store } from "@/utils/store"; interface Server { address: string; + serverName?: string; + serverId?: string; + lastUsername?: string; + savedCredentials?: { + username: string; + password: string; + }; } export const apiAtom = atom(null); @@ -41,7 +48,8 @@ interface JellyfinContextValue { setServer: (server: Server) => Promise; removeServer: () => void; switchServer: (server: Server) => Promise; - login: (username: string, password: string) => Promise; + addNewServer: (server: Server) => Promise; + login: (username: string, password: string, saveCredentials?: boolean) => Promise; logout: () => Promise; initiateQuickConnect: () => Promise; } @@ -181,6 +189,21 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ if (!apiInstance?.basePath) throw new Error("Failed to connect"); + // Get server info to obtain serverId and serverName + try { + const response = await fetch( + `${server.address}/System/Info/Public`, + { mode: "cors" } + ); + if (response.ok) { + const data = await response.json(); + server.serverId = data.Id; + server.serverName = data.ServerName; + } + } catch (error) { + console.warn("Could not get server info:", error); + } + setApi(apiInstance); storage.set("serverUrl", server.address); }, @@ -216,9 +239,11 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ mutationFn: async ({ username, password, + saveCredentials = true, }: { username: string; password: string; + saveCredentials?: boolean; }) => { if (!api || !jellyfin) throw new Error("API not initialized"); @@ -231,6 +256,26 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken)); storage.set("token", auth.data?.AccessToken); + // Save credentials to the current server if requested + if (saveCredentials && api.basePath) { + const previousServers = JSON.parse( + storage.getString("previousServers") || "[]", + ) as Server[]; + + const updatedServers = previousServers.map((server) => { + if (server.address === api.basePath) { + return { + ...server, + lastUsername: username, + savedCredentials: { username, password } + }; + } + return server; + }); + + storage.set("previousServers", JSON.stringify(updatedServers)); + } + const recentPluginSettings = await refreshStreamyfinPluginSettings(); if (recentPluginSettings?.jellyseerrServerUrl?.value) { const jellyseerrApi = new JellyseerrApi( @@ -300,9 +345,28 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ const switchServerMutation = useMutation({ mutationFn: async (server: Server) => { - // First logout the current user + // Get current server info for comparison + const currentServerId = await getCurrentServerId(); + + // If switching to same server (different URL), try auto-login with saved credentials + if (server.serverId && server.serverId === currentServerId && server.savedCredentials) { + try { + // Just set the new server without logging out + await setServerMutation.mutateAsync(server); + // Auto-login with saved credentials + await loginMutation.mutateAsync({ + username: server.savedCredentials.username, + password: server.savedCredentials.password, + saveCredentials: false, // Don't save again + }); + return; + } catch (error) { + console.warn("Auto-login failed, falling back to manual login:", error); + } + } + + // For different servers or if auto-login fails, do the normal logout → set server flow await logoutMutation.mutateAsync(); - // Then set the new server await setServerMutation.mutateAsync(server); }, onError: (error) => { @@ -310,6 +374,59 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ }, }); + const addNewServerMutation = useMutation({ + mutationFn: async (server: Server) => { + // Add a new server to the list without switching to it + const previousServers = JSON.parse( + storage.getString("previousServers") || "[]", + ) as Server[]; + + // Get server info first + try { + const response = await fetch( + `${server.address}/System/Info/Public`, + { mode: "cors" } + ); + if (response.ok) { + const data = await response.json(); + server.serverId = data.Id; + server.serverName = data.ServerName; + } + } catch (error) { + console.warn("Could not get server info:", error); + } + + const updatedServers = [ + server, + ...previousServers.filter((s: Server) => s.address !== server.address), + ]; + storage.set( + "previousServers", + JSON.stringify(updatedServers.slice(0, 5)), + ); + }, + onError: (error) => { + console.error("Failed to add new server:", error); + }, + }); + + const getCurrentServerId = async (): Promise => { + if (!api?.basePath) return null; + try { + const response = await fetch( + `${api.basePath}/System/Info/Public`, + { mode: "cors" } + ); + if (response.ok) { + const data = await response.json(); + return data.Id; + } + } catch (error) { + console.warn("Could not get current server ID:", error); + } + return null; + }; + const [loaded, setLoaded] = useState(false); const [initialLoaded, setInitialLoaded] = useState(false); @@ -354,8 +471,9 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ setServer: (server) => setServerMutation.mutateAsync(server), removeServer: () => removeServerMutation.mutateAsync(), switchServer: (server) => switchServerMutation.mutateAsync(server), - login: (username, password) => - loginMutation.mutateAsync({ username, password }), + addNewServer: (server) => addNewServerMutation.mutateAsync(server), + login: (username, password, saveCredentials = true) => + loginMutation.mutateAsync({ username, password, saveCredentials }), logout: () => logoutMutation.mutateAsync(), initiateQuickConnect, }; diff --git a/translations/en.json b/translations/en.json index 65a11b3a..f95e23b5 100644 --- a/translations/en.json +++ b/translations/en.json @@ -35,7 +35,9 @@ "servers": "Servers", "quick_switch": "Quick Switch Servers", "switch_server": "Switch Server", - "no_previous_servers": "No previous servers available" + "no_previous_servers": "No previous servers available", + "add_new_server": "Add New Server", + "auto_login_available": "Auto-login available" }, "home": { "no_internet": "No Internet",