mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-02-02 00:18:08 +00:00
fix(auth): distinguish session expiry from network errors
This commit is contained in:
@@ -2,7 +2,7 @@ import { SubtitlePlaybackMode } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useAtom } from "jotai";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { Alert, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVPasswordEntryModal } from "@/components/login/TVPasswordEntryModal";
|
||||
@@ -80,12 +80,26 @@ export default function SettingsTV() {
|
||||
const hasOtherAccounts = otherAccounts.length > 0;
|
||||
|
||||
// Handle account selection from modal
|
||||
const handleAccountSelect = (account: SavedServerAccount) => {
|
||||
const handleAccountSelect = async (account: SavedServerAccount) => {
|
||||
if (!currentServer) return;
|
||||
|
||||
if (account.securityType === "none") {
|
||||
// Direct login with saved credential
|
||||
loginWithSavedCredential(currentServer.address, account.userId);
|
||||
try {
|
||||
await loginWithSavedCredential(currentServer.address, account.userId);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
);
|
||||
}
|
||||
} else if (account.securityType === "pin") {
|
||||
// Show PIN modal
|
||||
setSelectedServer(currentServer);
|
||||
@@ -103,10 +117,24 @@ export default function SettingsTV() {
|
||||
const handlePinSuccess = async () => {
|
||||
setPinModalVisible(false);
|
||||
if (selectedServer && selectedAccount) {
|
||||
await loginWithSavedCredential(
|
||||
selectedServer.address,
|
||||
selectedAccount.userId,
|
||||
);
|
||||
try {
|
||||
await loginWithSavedCredential(
|
||||
selectedServer.address,
|
||||
selectedAccount.userId,
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
setSelectedServer(null);
|
||||
setSelectedAccount(null);
|
||||
|
||||
@@ -73,10 +73,19 @@ export const PreviousServersList: React.FC<PreviousServersListProps> = ({
|
||||
setLoadingServer(server.address);
|
||||
try {
|
||||
await onQuickLogin(server.address, account.userId);
|
||||
} catch {
|
||||
Alert.alert(
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
t("server.please_login_again"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
[{ text: t("common.ok"), onPress: () => onServerSelect(server) }],
|
||||
);
|
||||
} finally {
|
||||
@@ -122,10 +131,17 @@ export const PreviousServersList: React.FC<PreviousServersListProps> = ({
|
||||
setLoadingServer(selectedServer.address);
|
||||
try {
|
||||
await onQuickLogin(selectedServer.address, selectedAccount.userId);
|
||||
} catch {
|
||||
Alert.alert(
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
t("server.please_login_again"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
[
|
||||
{
|
||||
text: t("common.ok"),
|
||||
|
||||
@@ -72,22 +72,24 @@ export const Login: React.FC = () => {
|
||||
password: string;
|
||||
} | null>(null);
|
||||
|
||||
// Handle URL params for server connection
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (_apiUrl) {
|
||||
await setServer({
|
||||
address: _apiUrl,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (_username && _password) {
|
||||
setCredentials({ username: _username, password: _password });
|
||||
login(_username, _password);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
})();
|
||||
}, [_apiUrl, _username, _password]);
|
||||
}, [_apiUrl]);
|
||||
|
||||
// Handle auto-login when api is ready and credentials are provided via URL params
|
||||
useEffect(() => {
|
||||
if (api?.basePath && _apiUrl && _username && _password) {
|
||||
setCredentials({ username: _username, password: _password });
|
||||
login(_username, _password);
|
||||
}
|
||||
}, [api?.basePath, _apiUrl, _username, _password]);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
|
||||
@@ -122,19 +122,21 @@ export const TVLogin: React.FC = () => {
|
||||
};
|
||||
}, [stopQuickConnectPolling]);
|
||||
|
||||
// Auto login from URL params
|
||||
// Handle URL params for server connection
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (_apiUrl) {
|
||||
await setServer({ address: _apiUrl });
|
||||
setTimeout(() => {
|
||||
if (_username && _password) {
|
||||
login(_username, _password);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
})();
|
||||
}, [_apiUrl, _username, _password]);
|
||||
}, [_apiUrl]);
|
||||
|
||||
// Handle auto-login when api is ready and credentials are provided via URL params
|
||||
useEffect(() => {
|
||||
if (api?.basePath && _apiUrl && _username && _password) {
|
||||
login(_username, _password);
|
||||
}
|
||||
}, [api?.basePath, _apiUrl, _username, _password]);
|
||||
|
||||
// Update header
|
||||
useEffect(() => {
|
||||
@@ -263,10 +265,19 @@ export const TVLogin: React.FC = () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await loginWithSavedCredential(currentServer.address, account.userId);
|
||||
} catch {
|
||||
Alert.alert(
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
t("server.please_login_again"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
[
|
||||
{
|
||||
text: t("common.ok"),
|
||||
@@ -301,10 +312,17 @@ export const TVLogin: React.FC = () => {
|
||||
currentServer.address,
|
||||
selectedAccount.userId,
|
||||
);
|
||||
} catch {
|
||||
Alert.alert(
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : t("server.session_expired");
|
||||
const isSessionExpired = errorMessage.includes(
|
||||
t("server.session_expired"),
|
||||
t("server.please_login_again"),
|
||||
);
|
||||
Alert.alert(
|
||||
isSessionExpired
|
||||
? t("server.session_expired")
|
||||
: t("login.connection_failed"),
|
||||
isSessionExpired ? t("server.please_login_again") : errorMessage,
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -427,14 +427,33 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
||||
// Refresh plugin settings
|
||||
await refreshStreamyfinPluginSettings();
|
||||
} catch (error) {
|
||||
// Token is invalid/expired - remove it
|
||||
if (
|
||||
axios.isAxiosError(error) &&
|
||||
(error.response?.status === 401 || error.response?.status === 403)
|
||||
) {
|
||||
await deleteAccountCredential(serverUrl, userId);
|
||||
throw new Error(t("server.session_expired"));
|
||||
// Check for axios error
|
||||
if (axios.isAxiosError(error)) {
|
||||
// Token is invalid/expired - remove it
|
||||
if (
|
||||
error.response?.status === 401 ||
|
||||
error.response?.status === 403
|
||||
) {
|
||||
await deleteAccountCredential(serverUrl, userId);
|
||||
throw new Error(t("server.session_expired"));
|
||||
}
|
||||
|
||||
// Network error - server not reachable (no response means server didn't respond)
|
||||
if (!error.response) {
|
||||
throw new Error(t("home.server_unreachable"));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for network error by message pattern (fallback detection)
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.toLowerCase().includes("network") ||
|
||||
error.message.toLowerCase().includes("econnrefused") ||
|
||||
error.message.toLowerCase().includes("timeout"))
|
||||
) {
|
||||
throw new Error(t("home.server_unreachable"));
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user