mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add auth guard and connection check on mobile
This commit is contained in:
parent
14319a5c89
commit
4c955e4115
@ -21,7 +21,7 @@
|
|||||||
import { PortalProvider } from "@gorhom/portal";
|
import { PortalProvider } from "@gorhom/portal";
|
||||||
import { ThemeSelector } from "@kyoo/primitives";
|
import { ThemeSelector } from "@kyoo/primitives";
|
||||||
import { NavbarRight, NavbarTitle } from "@kyoo/ui";
|
import { NavbarRight, NavbarTitle } from "@kyoo/ui";
|
||||||
import { AccountProvider, createQueryClient, useAccounts } from "@kyoo/models";
|
import { AccountProvider, createQueryClient, useAccount, useAccounts } from "@kyoo/models";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
@ -92,37 +92,34 @@ const ThemedStack = ({ onLayout }: { onLayout?: () => void }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthGuard = ({ selected }: { selected: number | null }) => {
|
const AuthGuard = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
// TODO: support guest accounts on mobile too.
|
||||||
|
const account = useAccount();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selected === null)
|
if (account === null)
|
||||||
router.replace("/login", undefined, {
|
router.replace("/login", undefined, {
|
||||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||||
});
|
});
|
||||||
}, [selected, router]);
|
}, [account, router]);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
let rendered: boolean = false;
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
const [queryClient] = useState(() => createQueryClient());
|
const [queryClient] = useState(() => createQueryClient());
|
||||||
const theme = useColorScheme();
|
const theme = useColorScheme();
|
||||||
const [fontsLoaded] = useFonts({ Poppins_300Light, Poppins_400Regular, Poppins_900Black });
|
const [fontsLoaded] = useFonts({ Poppins_300Light, Poppins_400Regular, Poppins_900Black });
|
||||||
const info = useAccounts();
|
|
||||||
const isReady = fontsLoaded && (rendered || info.type !== "loading");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isReady) SplashScreen.hideAsync();
|
if (fontsLoaded) SplashScreen.hideAsync();
|
||||||
}, [isReady]);
|
}, [fontsLoaded]);
|
||||||
|
|
||||||
if (!isReady) return null;
|
if (!fontsLoaded) return null;
|
||||||
rendered = true;
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AccountProvider>
|
|
||||||
<ThemeSelector
|
<ThemeSelector
|
||||||
theme={theme ?? "light"}
|
theme={theme ?? "light"}
|
||||||
font={{
|
font={{
|
||||||
@ -133,12 +130,15 @@ export default function Root() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PortalProvider>
|
<PortalProvider>
|
||||||
{info.type === "loading" ? <CircularProgress /> : <ThemedStack />}
|
<AccountProvider>
|
||||||
{info.type === "error" && <ConnectionError />}
|
<>
|
||||||
{info.type === "ok" && <AuthGuard selected={info.selected} />}
|
<ThemedStack />
|
||||||
|
<ConnectionError />
|
||||||
|
<AuthGuard />
|
||||||
|
</>
|
||||||
|
</AccountProvider>
|
||||||
</PortalProvider>
|
</PortalProvider>
|
||||||
</ThemeSelector>
|
</ThemeSelector>
|
||||||
</AccountProvider>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AccountContext } from "@kyoo/models";
|
import { ConnectionErrorContext } from "@kyoo/models";
|
||||||
import { Button, H1, P, ts } from "@kyoo/primitives";
|
import { Button, H1, P, ts } from "@kyoo/primitives";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
@ -30,12 +30,12 @@ const ConnectionError = () => {
|
|||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { error, retry } = useContext(AccountContext);
|
const { error, retry } = useContext(ConnectionErrorContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...css({ padding: ts(2) })}>
|
<View {...css({ padding: ts(2) })}>
|
||||||
<H1 {...css({ textAlign: "center" })}>{t("errors.connection")}</H1>
|
<H1 {...css({ textAlign: "center" })}>{t("errors.connection")}</H1>
|
||||||
<P>{error ?? t("error.unknown")}</P>
|
<P>{error?.errors[0] ?? t("error.unknown")}</P>
|
||||||
<P>{t("errors.connection-tips")}</P>
|
<P>{t("errors.connection-tips")}</P>
|
||||||
<Button onPress={retry} text={t("errors.try-again")} {...css({ m: ts(1) })} />
|
<Button onPress={retry} text={t("errors.try-again")} {...css({ m: ts(1) })} />
|
||||||
<Button
|
<Button
|
||||||
|
@ -27,6 +27,7 @@ import { useMMKVString } from "react-native-mmkv";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { queryFn, useFetch } from "./query";
|
import { queryFn, useFetch } from "./query";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { KyooErrors } from "./kyoo-errors";
|
||||||
|
|
||||||
export const TokenP = z.object({
|
export const TokenP = z.object({
|
||||||
token_type: z.literal("Bearer"),
|
token_type: z.literal("Bearer"),
|
||||||
@ -47,6 +48,10 @@ export const AccountP = UserP.and(
|
|||||||
export type Account = z.infer<typeof AccountP>;
|
export type Account = z.infer<typeof AccountP>;
|
||||||
|
|
||||||
const AccountContext = createContext<(Account & { select: () => void; remove: () => void })[]>([]);
|
const AccountContext = createContext<(Account & { select: () => void; remove: () => void })[]>([]);
|
||||||
|
export const ConnectionErrorContext = createContext<{
|
||||||
|
error: KyooErrors | null;
|
||||||
|
retry?: () => void;
|
||||||
|
}>({ error: null });
|
||||||
|
|
||||||
export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [accStr] = useMMKVString("accounts");
|
const [accStr] = useMMKVString("accounts");
|
||||||
@ -80,15 +85,27 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const oldSelectedId = useRef<string | undefined>(selected?.id);
|
const oldSelectedId = useRef<string | undefined>(selected?.id);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if the user change account (or connect/disconnect), reset query cache.
|
// if the user change account (or connect/disconnect), reset query cache.
|
||||||
if (selected?.id !== oldSelectedId.current)
|
if (selected?.id !== oldSelectedId.current) queryClient.invalidateQueries();
|
||||||
queryClient.invalidateQueries();
|
|
||||||
oldSelectedId.current = selected?.id;
|
oldSelectedId.current = selected?.id;
|
||||||
|
|
||||||
// update cookies for ssr (needs to contains token, theme, language...)
|
// update cookies for ssr (needs to contains token, theme, language...)
|
||||||
if (Platform.OS === "web") setAccountCookie(selected);
|
if (Platform.OS === "web") setAccountCookie(selected);
|
||||||
}, [selected, queryClient]);
|
}, [selected, queryClient]);
|
||||||
|
|
||||||
return <AccountContext.Provider value={accounts}>{children}</AccountContext.Provider>;
|
return (
|
||||||
|
<AccountContext.Provider value={accounts}>
|
||||||
|
<ConnectionErrorContext.Provider
|
||||||
|
value={{
|
||||||
|
error: user.error,
|
||||||
|
retry: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["auth", "me"] });
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ConnectionErrorContext.Provider>
|
||||||
|
</AccountContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccount = () => {
|
export const useAccount = () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user