breaking login flow out into it's own provider

This commit is contained in:
Violet Caulfield
2024-10-21 07:27:46 -05:00
parent 7bbdd7dc2e
commit e4d6575ec5
8 changed files with 143 additions and 123 deletions
+2 -2
View File
@@ -5,12 +5,12 @@ import _ from "lodash";
export default function Home(): React.JSX.Element {
const { apiClient, username } = useApiClientContext();
const { apiClient } = useApiClientContext();
return (
<ScrollView paddingLeft={10}>
<YStack alignContent='flex-start'>
<H1>Hi { _.isUndefined(username) ? "there" : `, ${username}`}</H1>
<H1>Hi there</H1>
</YStack>
</ScrollView>
);
+9 -4
View File
@@ -2,24 +2,29 @@ import _ from "lodash"
import ServerAuthentication from "./helpers/server-authentication";
import ServerAddress from "./helpers/server-address";
import { createStackNavigator } from "@react-navigation/stack";
import { useApiClientContext } from "../jellyfin-api-provider";
import ServerLibrary from "./helpers/server-library";
import { useAuthenticationContext } from "./provider";
import { useEffect } from "react";
export default function Login(): React.JSX.Element {
const { server, username } = useApiClientContext();
const { serverAddress, username, triggerAuth, setTriggerAuth } = useAuthenticationContext();
const Stack = createStackNavigator();
useEffect(() => {
setTriggerAuth(false);
})
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{
(_.isUndefined(server) || _.isEmpty(server.url)) ? (
(_.isUndefined(serverAddress)) ? (
<Stack.Screen
name="ServerAddress"
options={{
headerShown: false,
animationTypeForReplace: (_.isUndefined(server) || _.isEmpty(server.url)) ? 'pop' : 'push'
animationTypeForReplace: triggerAuth ? 'push' : 'pop'
}}
component={ServerAddress}
/>
+3 -2
View File
@@ -11,16 +11,17 @@ import { Button, Input, SizableText, useTheme, View, YStack, Stack, XStack, getF
import { CheckboxWithLabel } from "../../helpers/checkbox-with-label";
import { SwitchWithLabel } from "../../helpers/switch-with-label";
import { buildApiClient } from "../../../api/client";
import { useAuthenticationContext } from "../provider";
const http = "http://"
const https = "https://"
export default function ServerAddress(): React.JSX.Element {
const { changeServer, server, setServer, setApiClient } = useApiClientContext();
const { setServer, setApiClient } = useApiClientContext();
const { serverAddress, setServerAddress } = useAuthenticationContext();
const [useHttps, setUseHttps] = useState(true)
const [serverAddress, setServerAddress] = useState(server?.url ?? undefined);
const theme = useTheme();
@@ -6,12 +6,13 @@ import * as Keychain from "react-native-keychain"
import { JellyfinCredentials } from "../../../api/types/jellyfin-credentials";
import { Button, H2, Input, View } from "tamagui";
import { client } from "../../../api/client";
import { useAuthenticationContext } from "../provider";
export default function ServerAuthentication(): React.JSX.Element {
const [username, setUsername] = React.useState('');
const { username, setUsername, setServerAddress } = useAuthenticationContext();
const [password, setPassword] = React.useState('');
const { apiClient, setApiClient, server, setServer, changeUser, setUsername: setContextUsername } = useApiClientContext();
const { apiClient, setApiClient, server } = useApiClientContext();
const useApiMutation = useMutation({
mutationFn: async (credentials: JellyfinCredentials) => {
@@ -31,7 +32,6 @@ export default function ServerAuthentication(): React.JSX.Element {
console.log(`Successfully signed in to ${server!.name}`)
setApiClient(client.createApi(server!.url, (authResult.data.AccessToken as string)))
setContextUsername(credentials.username);
return await Keychain.setInternetCredentials(server!.url, credentials.username, (authResult.data.AccessToken as string));
},
@@ -43,8 +43,8 @@ export default function ServerAuthentication(): React.JSX.Element {
const clearServer = useMutation({
mutationFn: async () => {
setContextUsername(undefined);
setServer(undefined);
setServerAddress(undefined);
return Promise.resolve();
}
});
@@ -75,9 +75,13 @@ export default function ServerAuthentication(): React.JSX.Element {
/>
<Button
disabled={_.isEmpty(username) || _.isEmpty(password)}
onPress={() => {
console.log(`Signing in to ${server!.name}`);
useApiMutation.mutate({ username, password })
if (!_.isUndefined(username)) {
console.log(`Signing in to ${server!.name}`);
useApiMutation.mutate({ username, password })
}
}}
>
Sign in
+7 -4
View File
@@ -6,21 +6,24 @@ import { JellifyLibrary } from "../../../types/JellifyLibrary";
import { useLibraries } from "../../../api/queries/libraries";
import { client } from "../../../api/client";
import { mutateServerCredentials } from "../../../api/mutators/functions/storage";
import { useAuthenticationContext } from "../provider";
export default function ServerLibrary(): React.JSX.Element {
const [musicLibrary, setMusicLibrary] = useState<JellifyLibrary | undefined>(undefined);
const [musicLibraryName, setMusicLibraryName] = useState<string>("")
const { setUsername, libraryName, setLibraryName, libraryId, setLibraryId } = useAuthenticationContext();
const { apiClient, server, setApiClient, setUsername } = useApiClientContext();
const { apiClient, server, setApiClient } = useApiClientContext();
const { data: musicLibraries, isPending: musicLibrariesPending } = useLibraries(apiClient!);
const clearUser = useMutation({
mutationFn: async () => {
setUsername(undefined)
setUsername(undefined);
// Reset API client so that we don't attempt to auth as a user
setApiClient(client.createApi(server!.url))
return Promise.resolve();
@@ -43,7 +46,7 @@ export default function ServerLibrary(): React.JSX.Element {
}}
>Switch User</Button>
<Select value={musicLibraryName}></Select>
<Select value={libraryName}></Select>
</View>
)
}
+90
View File
@@ -0,0 +1,90 @@
import React, { createContext, ReactNode, SetStateAction, useContext, useState } from "react";
interface JellyfinAuthenticationContext {
username: string | undefined;
setUsername: React.Dispatch<SetStateAction<string | undefined>>;
serverAddress: string | undefined;
setServerAddress: React.Dispatch<SetStateAction<string | undefined>>;
libraryName: string | undefined;
setLibraryName: React.Dispatch<React.SetStateAction<string | undefined>>;
libraryId: string | undefined;
setLibraryId: React.Dispatch<React.SetStateAction<string | undefined>>;
triggerAuth: boolean;
setTriggerAuth: React.Dispatch<React.SetStateAction<boolean>>;
}
const JellyfinAuthenticationContextInitializer = () => {
const [username, setUsername] = useState<string | undefined>(undefined);
const [serverAddress, setServerAddress] = useState<string | undefined>(undefined);
const [libraryName, setLibraryName] = useState<string | undefined>(undefined);
const [libraryId, setLibraryId] = useState<string | undefined>(undefined);
const [triggerAuth, setTriggerAuth] = useState<boolean>(true);
return {
username,
setUsername,
serverAddress,
setServerAddress,
libraryName,
setLibraryName,
libraryId,
setLibraryId,
triggerAuth,
setTriggerAuth
};
}
const JellyfinAuthenticationContext =
createContext<JellyfinAuthenticationContext>({
username: undefined,
setUsername: () => {},
serverAddress: undefined,
setServerAddress: () => {},
libraryName: undefined,
setLibraryName: () => {},
libraryId: undefined,
setLibraryId: () => {},
triggerAuth: true,
setTriggerAuth: () => {},
});
export const JellyfinAuthenticationProvider: ({ children }: {
children: ReactNode;
}) => React.JSX.Element = ({ children }: { children: ReactNode }) => {
const {
username,
setUsername,
serverAddress,
setServerAddress,
libraryName,
setLibraryName,
libraryId,
setLibraryId,
triggerAuth,
setTriggerAuth,
} = JellyfinAuthenticationContextInitializer();
return (
<JellyfinAuthenticationContext.Provider value={{
username,
setUsername,
serverAddress,
setServerAddress,
libraryName,
setLibraryName,
libraryId,
setLibraryId,
triggerAuth,
setTriggerAuth,
}}>
{ children }
</JellyfinAuthenticationContext.Provider>
);
};
export const useAuthenticationContext = () => useContext(JellyfinAuthenticationContext)
+7 -3
View File
@@ -7,6 +7,7 @@ import React, { } from "react";
import { DarkTheme, DefaultTheme, NavigationContainer } from "@react-navigation/native";
import Navigation from "./navigation";
import Login from "./Login/component";
import { JellyfinAuthenticationProvider } from "./Login/provider";
export default function Jellify(): React.JSX.Element {
@@ -30,14 +31,17 @@ function conditionalHomeRender(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const { libraryId } = useApiClientContext();
// If library ID hasn't been set, we haven't completed the auth flow
const { library } = useApiClientContext();
return (
<NavigationContainer theme={isDarkMode ? DarkTheme : DefaultTheme}>
{ !_.isUndefined(libraryId) ? (
{ !_.isUndefined(library) ? (
<Navigation />
) : (
<Login />
<JellyfinAuthenticationProvider>
<Login />
</JellyfinAuthenticationProvider>
)}
</NavigationContainer>
);
+14 -101
View File
@@ -4,46 +4,21 @@ import { useApi } from '../api/queries';
import _ from 'lodash';
import { JellifyServer } from '../types/JellifyServer';
import { useCredentials, useServer } from '../api/queries/keychain';
import { JellifyLibrary } from '../types/JellifyLibrary';
interface JellyfinApiClientContext {
apiClient: Api | undefined;
apiClientPending: boolean;
setApiClient: React.Dispatch<React.SetStateAction<Api | undefined>>;
server: JellifyServer | undefined;
serverPending: boolean;
setServer: React.Dispatch<React.SetStateAction<JellifyServer | undefined>>;
changeServer: boolean;
setChangeServer: React.Dispatch<React.SetStateAction<boolean>>;
username: string | undefined;
setUsername: React.Dispatch<React.SetStateAction<string | undefined>>;
isUsernamePending: boolean;
setIsUsernamePending: React.Dispatch<React.SetStateAction<boolean>>;
changeUser: boolean;
setChangeUser: React.Dispatch<React.SetStateAction<boolean>>;
libraryName: string | undefined;
setLibraryName: React.Dispatch<React.SetStateAction<string | undefined>>;
libraryId: string | undefined;
setLibraryId: React.Dispatch<React.SetStateAction<string | undefined>>;
changeLibrary: boolean;
setChangeLibrary: React.Dispatch<React.SetStateAction<boolean>>;
library: JellifyLibrary | undefined;
setLibrary: React.Dispatch<React.SetStateAction<JellifyLibrary | undefined>>;
}
const JellyfinApiClientContextInitializer = () => {
const [apiClient, setApiClient] = useState<Api | undefined>(undefined);
const [server, setServer] = useState<JellifyServer | undefined>(undefined);
const [isServerPending, setIsServerPending] = useState<boolean>(true);
const [isApiPending, setIsApiPending] = useState<boolean>(true);
const [changeServerRequested, setChangeServerRequested] = useState<boolean>(false);
const [changeUserRequested, setChangeUserRequested] = useState<boolean>(false);
const [changeLibraryRequested, setChangeLibraryRequested] = useState<boolean>(false);
const [userName, setUserName] = useState<string | undefined>(undefined);
const [isUsernamePending, setIsUsernamePending] = useState<boolean>(false)
const [libraryName, setLibraryName] = useState<string | undefined>(undefined);
const [libraryId, setLibraryId] = useState<string | undefined>(undefined);
const [library, setLibrary] = useState<JellifyLibrary | undefined>(undefined);
const { data: api, isPending: apiPending } = useApi();
const { data: jellyfinServer, isPending: serverPending } = useServer();
@@ -52,10 +27,6 @@ const JellyfinApiClientContextInitializer = () => {
useEffect(() => {
setApiClient(api);
setServer(jellyfinServer);
setIsApiPending(apiPending);
setIsServerPending(serverPending);
setUserName(credentials?.username ?? undefined)
setIsUsernamePending(credentialsPending);
}, [
api,
apiPending,
@@ -63,56 +34,26 @@ const JellyfinApiClientContextInitializer = () => {
credentialsPending,
jellyfinServer,
serverPending,
userName,
isUsernamePending,
]);
return {
apiClient,
apiClient,
setApiClient,
isApiPending,
server,
server,
setServer,
isServerPending,
changeServerRequested,
setChangeServerRequested,
changeUserRequested,
setChangeUserRequested,
changeLibraryRequested,
setChangeLibraryRequested,
userName,
setUserName,
isUsernamePending,
setIsUsernamePending,
libraryName,
setLibraryName,
libraryId,
setLibraryId
library,
setLibrary,
};
}
export const JellyfinApiClientContext =
createContext<JellyfinApiClientContext>({
apiClient: undefined,
apiClientPending: true,
setApiClient: () => {},
server: undefined,
serverPending: true,
setServer: () => {},
changeServer: false,
setChangeServer: () => {},
username: undefined,
setUsername: () => {},
isUsernamePending: false,
setIsUsernamePending: () => {},
changeUser: false,
setChangeUser: () => {},
libraryName: undefined,
setLibraryName: () => {},
libraryId: undefined,
setLibraryId: () => {},
changeLibrary: false,
setChangeLibrary: () => {},
library: undefined,
setLibrary: () => {},
});
export const JellyfinApiClientProvider: ({ children }: {
@@ -121,24 +62,10 @@ export const JellyfinApiClientProvider: ({ children }: {
const {
apiClient,
setApiClient,
isApiPending,
server,
setServer,
isServerPending,
changeServerRequested,
setChangeServerRequested,
userName,
setUserName,
isUsernamePending,
setIsUsernamePending,
changeUserRequested,
setChangeUserRequested,
libraryName,
setLibraryName,
libraryId,
setLibraryId,
changeLibraryRequested,
setChangeLibraryRequested
library,
setLibrary,
} = JellyfinApiClientContextInitializer();
// Add your logic to check if credentials are stored and initialize the API client here.
@@ -147,24 +74,10 @@ export const JellyfinApiClientProvider: ({ children }: {
<JellyfinApiClientContext.Provider value={{
apiClient,
setApiClient,
apiClientPending: isApiPending,
server,
setServer,
serverPending: isServerPending,
changeServer: changeServerRequested,
setChangeServer: setChangeServerRequested,
username: userName,
setUsername: setUserName,
isUsernamePending,
setIsUsernamePending,
changeUser: changeUserRequested,
setChangeUser: setChangeUserRequested,
libraryName: libraryName,
setLibraryName: setLibraryName,
libraryId: libraryId,
setLibraryId: setLibraryId,
changeLibrary: changeLibraryRequested,
setChangeLibrary: setChangeLibraryRequested
library,
setLibrary
}}>
{children}
</JellyfinApiClientContext.Provider>