Implement enhanced server switching with credential persistence and auto-login

Co-authored-by: retardgerman <78982850+retardgerman@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-05 18:14:44 +00:00
parent 17f7b42728
commit 214832f81c
4 changed files with 177 additions and 8 deletions

View File

@@ -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<PreviousServersListProps> = ({
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<PreviousServersListProps> = ({
<ListItem
key={s.address}
onPress={() => onServerSelect(s)}
title={s.address}
title={getServerDisplayName(s)}
subtitle={getServerSubtitle(s)}
icon={s.savedCredentials ? "key" : "server"}
showArrow
/>
))}

View File

@@ -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> = ({ ...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 (
<View {...props}>
@@ -55,7 +79,9 @@ export const ServerSwitcher: React.FC<Props> = ({ ...props }) => {
<ListItem
key={server.address}
onPress={() => handleServerSwitch(server)}
title={server.address}
title={getServerDisplayName(server)}
subtitle={getServerSubtitle(server)}
icon={server.savedCredentials ? "key" : "server"}
showArrow
disabled={switchingServer === server.address}
/>

View File

@@ -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<Api | null>(null);
@@ -41,7 +48,8 @@ interface JellyfinContextValue {
setServer: (server: Server) => Promise<void>;
removeServer: () => void;
switchServer: (server: Server) => Promise<void>;
login: (username: string, password: string) => Promise<void>;
addNewServer: (server: Server) => Promise<void>;
login: (username: string, password: string, saveCredentials?: boolean) => Promise<void>;
logout: () => Promise<void>;
initiateQuickConnect: () => Promise<string | undefined>;
}
@@ -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<string | null> => {
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,
};

View File

@@ -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",