mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-17 09:53:02 +01:00
Compare commits
6 Commits
develop
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a377317710 | ||
|
|
85929c2854 | ||
|
|
214832f81c | ||
|
|
17f7b42728 | ||
|
|
b8c586139f | ||
|
|
4dd5e97971 |
@@ -7,6 +7,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
|
import { AddNewServer } from "@/components/settings/AddNewServer";
|
||||||
import { AppLanguageSelector } from "@/components/settings/AppLanguageSelector";
|
import { AppLanguageSelector } from "@/components/settings/AppLanguageSelector";
|
||||||
import { AudioToggles } from "@/components/settings/AudioToggles";
|
import { AudioToggles } from "@/components/settings/AudioToggles";
|
||||||
import { ChromecastSettings } from "@/components/settings/ChromecastSettings";
|
import { ChromecastSettings } from "@/components/settings/ChromecastSettings";
|
||||||
@@ -17,6 +18,7 @@ import { MediaToggles } from "@/components/settings/MediaToggles";
|
|||||||
import { OtherSettings } from "@/components/settings/OtherSettings";
|
import { OtherSettings } from "@/components/settings/OtherSettings";
|
||||||
import { PluginSettings } from "@/components/settings/PluginSettings";
|
import { PluginSettings } from "@/components/settings/PluginSettings";
|
||||||
import { QuickConnect } from "@/components/settings/QuickConnect";
|
import { QuickConnect } from "@/components/settings/QuickConnect";
|
||||||
|
import { ServerSwitcher } from "@/components/settings/ServerSwitcher";
|
||||||
import { StorageSettings } from "@/components/settings/StorageSettings";
|
import { StorageSettings } from "@/components/settings/StorageSettings";
|
||||||
import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
|
import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
|
||||||
import { UserInfo } from "@/components/settings/UserInfo";
|
import { UserInfo } from "@/components/settings/UserInfo";
|
||||||
@@ -64,6 +66,10 @@ export default function settings() {
|
|||||||
<View className='p-4 flex flex-col gap-y-4'>
|
<View className='p-4 flex flex-col gap-y-4'>
|
||||||
<UserInfo />
|
<UserInfo />
|
||||||
|
|
||||||
|
<ServerSwitcher className='mb-4' />
|
||||||
|
|
||||||
|
<AddNewServer className='mb-4' />
|
||||||
|
|
||||||
<QuickConnect className='mb-4' />
|
<QuickConnect className='mb-4' />
|
||||||
|
|
||||||
<MediaProvider>
|
<MediaProvider>
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import { ListItem } from "./list/ListItem";
|
|||||||
|
|
||||||
interface Server {
|
interface Server {
|
||||||
address: string;
|
address: string;
|
||||||
|
serverName?: string;
|
||||||
|
serverId?: string;
|
||||||
|
lastUsername?: string;
|
||||||
|
savedToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PreviousServersListProps {
|
interface PreviousServersListProps {
|
||||||
@@ -26,6 +30,20 @@ export const PreviousServersList: React.FC<PreviousServersListProps> = ({
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
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;
|
if (!previousServers.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -35,7 +53,9 @@ export const PreviousServersList: React.FC<PreviousServersListProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
key={s.address}
|
key={s.address}
|
||||||
onPress={() => onServerSelect(s)}
|
onPress={() => onServerSelect(s)}
|
||||||
title={s.address}
|
title={getServerDisplayName(s)}
|
||||||
|
subtitle={getServerSubtitle(s)}
|
||||||
|
icon={s.savedToken ? "key" : "server"}
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
124
components/settings/AddNewServer.tsx
Normal file
124
components/settings/AddNewServer.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Alert, View, type ViewProps } from "react-native";
|
||||||
|
import { useJellyfin } from "@/providers/JellyfinProvider";
|
||||||
|
import { ListGroup } from "../list/ListGroup";
|
||||||
|
import { ListItem } from "../list/ListItem";
|
||||||
|
import { Input } from "../common/Input";
|
||||||
|
import { Button } from "../Button";
|
||||||
|
|
||||||
|
interface Props extends ViewProps {}
|
||||||
|
|
||||||
|
export const AddNewServer: React.FC<Props> = ({ ...props }) => {
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [serverUrl, setServerUrl] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { addNewServer } = useJellyfin();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleAddServer = async () => {
|
||||||
|
if (!serverUrl.trim()) {
|
||||||
|
Alert.alert(t("login.error_title"), "Please enter a server URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Validate URL format
|
||||||
|
const cleanUrl = serverUrl.trim().replace(/\/$/, "");
|
||||||
|
|
||||||
|
// Test connection to the server
|
||||||
|
const baseUrl = cleanUrl.replace(/^https?:\/\//i, "");
|
||||||
|
const protocols = ["https", "http"];
|
||||||
|
let validUrl: string | null = null;
|
||||||
|
|
||||||
|
for (const protocol of protocols) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${protocol}://${baseUrl}/System/Info/Public`,
|
||||||
|
{ mode: "cors" }
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
validUrl = `${protocol}://${baseUrl}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Continue to next protocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validUrl) {
|
||||||
|
Alert.alert(
|
||||||
|
t("login.connection_failed"),
|
||||||
|
t("login.could_not_connect_to_server")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the server to the list
|
||||||
|
await addNewServer({ address: validUrl });
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
"Success",
|
||||||
|
`Server ${validUrl} has been added to your server list. You can now switch to it from Quick Switch Servers.`
|
||||||
|
);
|
||||||
|
|
||||||
|
setServerUrl("");
|
||||||
|
setShowForm(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to add server:", error);
|
||||||
|
Alert.alert(
|
||||||
|
t("login.error_title"),
|
||||||
|
"Failed to add server. Please try again."
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...props}>
|
||||||
|
<ListGroup title={t("server.add_new_server")}>
|
||||||
|
{!showForm ? (
|
||||||
|
<ListItem
|
||||||
|
onPress={() => setShowForm(true)}
|
||||||
|
title="Add Server"
|
||||||
|
icon="add"
|
||||||
|
showArrow
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View className="p-4 space-y-4">
|
||||||
|
<Input
|
||||||
|
placeholder={t("server.server_url_placeholder")}
|
||||||
|
value={serverUrl}
|
||||||
|
onChangeText={setServerUrl}
|
||||||
|
keyboardType="url"
|
||||||
|
autoCapitalize="none"
|
||||||
|
textContentType="URL"
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
<View className="flex-row space-x-2">
|
||||||
|
<Button
|
||||||
|
onPress={handleAddServer}
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading || !serverUrl.trim()}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Add Server
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
setShowForm(false);
|
||||||
|
setServerUrl("");
|
||||||
|
}}
|
||||||
|
className="flex-1 bg-neutral-800 border border-neutral-700"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ListGroup>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
89
components/settings/ServerSwitcher.tsx
Normal file
89
components/settings/ServerSwitcher.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import type React from "react";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { View, type ViewProps } from "react-native";
|
||||||
|
import { useMMKVString } from "react-native-mmkv";
|
||||||
|
import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider";
|
||||||
|
import { ListGroup } from "../list/ListGroup";
|
||||||
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|
||||||
|
interface Server {
|
||||||
|
address: string;
|
||||||
|
serverName?: string;
|
||||||
|
serverId?: string;
|
||||||
|
lastUsername?: string;
|
||||||
|
savedToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends ViewProps {}
|
||||||
|
|
||||||
|
export const ServerSwitcher: React.FC<Props> = ({ ...props }) => {
|
||||||
|
const [_previousServers] = useMMKVString("previousServers");
|
||||||
|
const [api] = useAtom(apiAtom);
|
||||||
|
const [switchingServer, setSwitchingServer] = useState<string | null>(null);
|
||||||
|
const { switchServer } = useJellyfin();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const previousServers = useMemo(() => {
|
||||||
|
const servers = JSON.parse(_previousServers || "[]") as Server[];
|
||||||
|
// Filter out the current server since we don't need to "switch" to it
|
||||||
|
const currentServer = api?.basePath;
|
||||||
|
return servers.filter((server) => server.address !== currentServer);
|
||||||
|
}, [_previousServers, api?.basePath]);
|
||||||
|
|
||||||
|
const handleServerSwitch = async (server: Server) => {
|
||||||
|
try {
|
||||||
|
setSwitchingServer(server.address);
|
||||||
|
await switchServer(server);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to switch server:", error);
|
||||||
|
setSwitchingServer(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServerDisplayName = (server: Server) => {
|
||||||
|
if (server.serverName) {
|
||||||
|
return `${server.serverName} (${server.address})`;
|
||||||
|
}
|
||||||
|
return server.address;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServerSubtitle = (server: Server) => {
|
||||||
|
if (server.lastUsername) {
|
||||||
|
const hasToken = !!server.savedToken;
|
||||||
|
return hasToken
|
||||||
|
? `${server.lastUsername} • Auto-login available`
|
||||||
|
: `Last user: ${server.lastUsername}`;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!previousServers.length) {
|
||||||
|
return (
|
||||||
|
<View {...props}>
|
||||||
|
<ListGroup title={t("server.quick_switch")}>
|
||||||
|
<ListItem title={t("server.no_previous_servers")} disabled />
|
||||||
|
</ListGroup>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...props}>
|
||||||
|
<ListGroup title={t("server.quick_switch")}>
|
||||||
|
{previousServers.map((server) => (
|
||||||
|
<ListItem
|
||||||
|
key={server.address}
|
||||||
|
onPress={() => handleServerSwitch(server)}
|
||||||
|
title={getServerDisplayName(server)}
|
||||||
|
subtitle={getServerSubtitle(server)}
|
||||||
|
icon={server.savedToken ? "key" : "server"}
|
||||||
|
showArrow
|
||||||
|
disabled={switchingServer === server.address}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ListGroup>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -30,6 +30,10 @@ import { store } from "@/utils/store";
|
|||||||
|
|
||||||
interface Server {
|
interface Server {
|
||||||
address: string;
|
address: string;
|
||||||
|
serverName?: string;
|
||||||
|
serverId?: string;
|
||||||
|
lastUsername?: string;
|
||||||
|
savedToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiAtom = atom<Api | null>(null);
|
export const apiAtom = atom<Api | null>(null);
|
||||||
@@ -40,7 +44,9 @@ interface JellyfinContextValue {
|
|||||||
discoverServers: (url: string) => Promise<Server[]>;
|
discoverServers: (url: string) => Promise<Server[]>;
|
||||||
setServer: (server: Server) => Promise<void>;
|
setServer: (server: Server) => Promise<void>;
|
||||||
removeServer: () => void;
|
removeServer: () => void;
|
||||||
login: (username: string, password: string) => Promise<void>;
|
switchServer: (server: Server) => Promise<void>;
|
||||||
|
addNewServer: (server: Server) => Promise<void>;
|
||||||
|
login: (username: string, password: string, saveCredentials?: boolean) => Promise<void>;
|
||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
initiateQuickConnect: () => Promise<string | undefined>;
|
initiateQuickConnect: () => Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
@@ -180,6 +186,21 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
|
|
||||||
if (!apiInstance?.basePath) throw new Error("Failed to connect");
|
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);
|
setApi(apiInstance);
|
||||||
storage.set("serverUrl", server.address);
|
storage.set("serverUrl", server.address);
|
||||||
},
|
},
|
||||||
@@ -215,9 +236,11 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
saveCredentials = true,
|
||||||
}: {
|
}: {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
saveCredentials?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!api || !jellyfin) throw new Error("API not initialized");
|
if (!api || !jellyfin) throw new Error("API not initialized");
|
||||||
|
|
||||||
@@ -230,6 +253,26 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken));
|
setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken));
|
||||||
storage.set("token", auth.data?.AccessToken);
|
storage.set("token", auth.data?.AccessToken);
|
||||||
|
|
||||||
|
// Save token 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,
|
||||||
|
savedToken: auth.data.AccessToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
});
|
||||||
|
|
||||||
|
storage.set("previousServers", JSON.stringify(updatedServers));
|
||||||
|
}
|
||||||
|
|
||||||
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
||||||
if (recentPluginSettings?.jellyseerrServerUrl?.value) {
|
if (recentPluginSettings?.jellyseerrServerUrl?.value) {
|
||||||
const jellyseerrApi = new JellyseerrApi(
|
const jellyseerrApi = new JellyseerrApi(
|
||||||
@@ -297,6 +340,112 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const switchServerMutation = useMutation({
|
||||||
|
mutationFn: async (server: Server) => {
|
||||||
|
// Get current server info for comparison
|
||||||
|
const currentServerId = await getCurrentServerId();
|
||||||
|
|
||||||
|
// If switching to same server (different URL), try auto-login with saved token
|
||||||
|
if (server.serverId && server.serverId === currentServerId && server.savedToken) {
|
||||||
|
try {
|
||||||
|
// Create API instance with saved token
|
||||||
|
const apiInstance = jellyfin?.createApi(server.address, server.savedToken);
|
||||||
|
if (!apiInstance) throw new Error("Failed to create API instance");
|
||||||
|
|
||||||
|
// Validate the token by making an authenticated request
|
||||||
|
const userApi = getUserApi(apiInstance);
|
||||||
|
const userResponse = await userApi.getCurrentUser();
|
||||||
|
|
||||||
|
if (userResponse.data) {
|
||||||
|
// Token is valid, update the API and user
|
||||||
|
setApi(apiInstance);
|
||||||
|
setUser(userResponse.data);
|
||||||
|
storage.set("serverUrl", server.address);
|
||||||
|
storage.set("token", server.savedToken);
|
||||||
|
storage.set("user", JSON.stringify(userResponse.data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Saved token is invalid, falling back to manual login:", error);
|
||||||
|
// Remove invalid token from server
|
||||||
|
const previousServers = JSON.parse(
|
||||||
|
storage.getString("previousServers") || "[]",
|
||||||
|
) as Server[];
|
||||||
|
|
||||||
|
const updatedServers = previousServers.map((s) => {
|
||||||
|
if (s.address === server.address) {
|
||||||
|
const { savedToken, ...serverWithoutToken } = s;
|
||||||
|
return serverWithoutToken;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
|
||||||
|
storage.set("previousServers", JSON.stringify(updatedServers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For different servers or if auto-login fails, do the normal logout → set server flow
|
||||||
|
await logoutMutation.mutateAsync();
|
||||||
|
await setServerMutation.mutateAsync(server);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to switch server:", error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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 [loaded, setLoaded] = useState(false);
|
||||||
const [initialLoaded, setInitialLoaded] = useState(false);
|
const [initialLoaded, setInitialLoaded] = useState(false);
|
||||||
|
|
||||||
@@ -311,6 +460,23 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (!jellyfin) return;
|
if (!jellyfin) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Migrate any existing savedCredentials to remove them
|
||||||
|
const previousServers = JSON.parse(
|
||||||
|
storage.getString("previousServers") || "[]",
|
||||||
|
) as any[];
|
||||||
|
|
||||||
|
if (previousServers.length > 0) {
|
||||||
|
const migratedServers = previousServers.map((server) => {
|
||||||
|
if (server.savedCredentials) {
|
||||||
|
// Remove savedCredentials field for security
|
||||||
|
const { savedCredentials, ...serverWithoutCredentials } = server;
|
||||||
|
return serverWithoutCredentials;
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
});
|
||||||
|
storage.set("previousServers", JSON.stringify(migratedServers));
|
||||||
|
}
|
||||||
|
|
||||||
const token = getTokenFromStorage();
|
const token = getTokenFromStorage();
|
||||||
const serverUrl = getServerUrlFromStorage();
|
const serverUrl = getServerUrlFromStorage();
|
||||||
const storedUser = getUserFromStorage();
|
const storedUser = getUserFromStorage();
|
||||||
@@ -340,8 +506,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
discoverServers,
|
discoverServers,
|
||||||
setServer: (server) => setServerMutation.mutateAsync(server),
|
setServer: (server) => setServerMutation.mutateAsync(server),
|
||||||
removeServer: () => removeServerMutation.mutateAsync(),
|
removeServer: () => removeServerMutation.mutateAsync(),
|
||||||
login: (username, password) =>
|
switchServer: (server) => switchServerMutation.mutateAsync(server),
|
||||||
loginMutation.mutateAsync({ username, password }),
|
addNewServer: (server) => addNewServerMutation.mutateAsync(server),
|
||||||
|
login: (username, password, saveCredentials = true) =>
|
||||||
|
loginMutation.mutateAsync({ username, password, saveCredentials }),
|
||||||
logout: () => logoutMutation.mutateAsync(),
|
logout: () => logoutMutation.mutateAsync(),
|
||||||
initiateQuickConnect,
|
initiateQuickConnect,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,12 @@
|
|||||||
"clear_button": "Clear",
|
"clear_button": "Clear",
|
||||||
"search_for_local_servers": "Search for local servers",
|
"search_for_local_servers": "Search for local servers",
|
||||||
"searching": "Searching...",
|
"searching": "Searching...",
|
||||||
"servers": "Servers"
|
"servers": "Servers",
|
||||||
|
"quick_switch": "Quick Switch Servers",
|
||||||
|
"switch_server": "Switch Server",
|
||||||
|
"no_previous_servers": "No previous servers available",
|
||||||
|
"add_new_server": "Add New Server",
|
||||||
|
"auto_login_available": "Auto-login available"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"no_internet": "No Internet",
|
"no_internet": "No Internet",
|
||||||
|
|||||||
Reference in New Issue
Block a user