From 3ecd05c20fc5d09e85694e7d6a167f1519c2b381 Mon Sep 17 00:00:00 2001 From: Violet Caulfield Date: Tue, 22 Oct 2024 10:52:31 -0500 Subject: [PATCH] lots of backend changes to slim up the app provider and to beef up the login provider --- api/mutators/functions/storage.ts | 8 +- api/queries.ts | 12 +-- api/queries/functions/api.ts | 31 +++++--- api/queries/functions/libraries.ts | 29 +++---- components/Login/component.tsx | 8 +- components/Login/helpers/server-address.tsx | 16 ++-- .../Login/helpers/server-authentication.tsx | 25 +++--- components/Login/helpers/server-library.tsx | 7 +- components/Login/provider.tsx | 79 +++++++++++++++++-- components/Login/utils/constants.ts | 2 + components/jellify.tsx | 4 +- components/jellyfin-api-provider.tsx | 77 +++--------------- 12 files changed, 156 insertions(+), 142 deletions(-) create mode 100644 components/Login/utils/constants.ts diff --git a/api/mutators/functions/storage.ts b/api/mutators/functions/storage.ts index 33848da4..0d2b73a4 100644 --- a/api/mutators/functions/storage.ts +++ b/api/mutators/functions/storage.ts @@ -25,8 +25,12 @@ export const serverMutation = async (serverUrl: string) => { return await getSystemApi(api).getPublicSystemInfo(); } -export const mutateServer = async (server: JellifyServer | undefined) => { - return await AsyncStorage.setItem(AsyncStorageKeys.ServerUrl, JSON.stringify(server)); +export const mutateServer = async (server?: JellifyServer) => { + + if (!_.isUndefined(server)) + return await AsyncStorage.setItem(AsyncStorageKeys.ServerUrl, JSON.stringify(server)); + + return await AsyncStorage.removeItem(AsyncStorageKeys.ServerUrl); } export const mutateServerCredentials = async (credentials?: JellyfinCredentials) => { diff --git a/api/queries.ts b/api/queries.ts index 439f85f9..a90214be 100644 --- a/api/queries.ts +++ b/api/queries.ts @@ -3,15 +3,11 @@ import { QueryKeys } from "../enums/query-keys"; import { createApi, createPublicApi } from "./queries/functions/api"; export const usePublicApi = (serverUrl: string) => useQuery({ - queryKey: [QueryKeys.PublicApi, serverUrl], - queryFn: ({ queryKey }) => { - return createPublicApi(queryKey[1]) - } -}) + queryKey: [QueryKeys.PublicApi, { serverUrl }], + queryFn: createPublicApi +}); export const useApi = () => useQuery({ queryKey: [QueryKeys.Api], - queryFn: () => { - return createApi() - } + queryFn: createApi }) \ No newline at end of file diff --git a/api/queries/functions/api.ts b/api/queries/functions/api.ts index ec2fe76e..2f8f9b8d 100644 --- a/api/queries/functions/api.ts +++ b/api/queries/functions/api.ts @@ -2,21 +2,32 @@ import { Api } from "@jellyfin/sdk"; import { fetchCredentials } from "./storage"; import { client } from "../../client"; import _ from "lodash"; +import { QueryFunctionContext, QueryKey } from "@tanstack/react-query"; /** * A promise to build an authenticated Jellyfin API client * @returns A Promise of the authenticated Jellyfin API client or a rejection */ -export const createApi: () => Promise = () => new Promise(async (resolve, reject) => { - let credentials = await fetchCredentials(); +export function createApi(): Promise { + return new Promise(async (resolve, reject) => { + let credentials = await fetchCredentials(); - if (_.isUndefined(credentials)) - reject("No credentials exist for the current user") - - console.log("Signing into Jellyfin") - resolve(client.createApi(credentials!.server, credentials!.password)); -}); + if (_.isUndefined(credentials)) { + console.warn("No credentials exist for user, launching login flow"); + return reject("No credentials exist for the current user"); + } + + console.log("Signing into Jellyfin") + return resolve(client.createApi(credentials!.server, credentials!.password)); + }); +} -export const createPublicApi: (serverUrl: string) => Api = (serverUrl) => { - return client.createApi(serverUrl); +export function createPublicApi({ queryKey }: QueryFunctionContext): Promise { + return new Promise((resolve) => { + + ///@ts-ignore + const [_key, { serverUrl } ] = queryKey; + + resolve(client.createApi(serverUrl)); + }); } \ No newline at end of file diff --git a/api/queries/functions/libraries.ts b/api/queries/functions/libraries.ts index 67ae23e4..1d1a0190 100644 --- a/api/queries/functions/libraries.ts +++ b/api/queries/functions/libraries.ts @@ -4,20 +4,21 @@ import { getItemsApi } from "@jellyfin/sdk/lib/utils/api/items-api"; import _ from "lodash"; -export const fetchMusicLibraries : (api: Api) => Promise = (api: Api) => new Promise( async (resolve) => { +export function fetchMusicLibraries(api: Api): Promise { + return new Promise( async (resolve) => { + console.log("Fetching music libraries from Jellyfin"); + + let libraries = await getItemsApi(api).getItems(); - console.log("Fetching music libraries from Jellyfin"); - - let libraries = await getItemsApi(api).getItems(); + if (_.isUndefined(libraries.data.Items)) { + console.log("No libraries found on Jellyfin"); + return Promise.reject("No libraries found on Jellyfin"); + } - if (_.isUndefined(libraries.data.Items)) { - console.log("No libraries found on Jellyfin"); - return Promise.reject("No libraries found on Jellyfin"); - } + let musicLibraries = libraries.data.Items!.filter(library => library.CollectionType == 'music'); - let musicLibraries = libraries.data.Items!.filter(library => library.CollectionType == 'music'); - - console.log(`Found ${musicLibraries.length} music libraries`); - - resolve(musicLibraries); -}); \ No newline at end of file + console.log(`Found ${musicLibraries.length} music libraries`); + + resolve(musicLibraries); + }); +} \ No newline at end of file diff --git a/components/Login/component.tsx b/components/Login/component.tsx index 436c28a8..12c98034 100644 --- a/components/Login/component.tsx +++ b/components/Login/component.tsx @@ -9,9 +9,9 @@ import { useApiClientContext } from "../jellyfin-api-provider"; export default function Login(): React.JSX.Element { - const { serverAddress, username, triggerAuth, setTriggerAuth } = useAuthenticationContext(); + const { serverAddress, storedServer, changeServer, username, changeUsername, triggerAuth, setTriggerAuth } = useAuthenticationContext(); - const { apiClient, username: clientUsername } = useApiClientContext(); + const { apiClient } = useApiClientContext(); const Stack = createStackNavigator(); @@ -22,7 +22,7 @@ export default function Login(): React.JSX.Element { return ( { - (_.isUndefined(serverAddress) || _.isUndefined(apiClient)) ? ( + (_.isUndefined(storedServer) || changeServer) ? ( ) : ( - (_.isUndefined(username) || _.isUndefined(clientUsername)) ? ( + (_.isUndefined(username) || changeUsername) ? ( { console.error("An error occurred connecting to the Jellyfin instance", error); diff --git a/components/Login/helpers/server-authentication.tsx b/components/Login/helpers/server-authentication.tsx index c56d317e..e229bbad 100644 --- a/components/Login/helpers/server-authentication.tsx +++ b/components/Login/helpers/server-authentication.tsx @@ -1,21 +1,20 @@ -import React, { useEffect } from "react"; +import React from "react"; import { useMutation } from "@tanstack/react-query"; import { useApiClientContext } from "../../jellyfin-api-provider"; import _ from "lodash"; import * as Keychain from "react-native-keychain" import { JellyfinCredentials } from "../../../api/types/jellyfin-credentials"; import { View } from "tamagui"; -import { client } from "../../../api/client"; import { useAuthenticationContext } from "../provider"; import { Heading } from "../../helpers/text"; import Button from "../../helpers/button"; import Input from "../../helpers/input"; export default function ServerAuthentication(): React.JSX.Element { - const { username, setUsername, setServerAddress } = useAuthenticationContext(); + const { username, setUsername, setChangeUsername, setServerAddress, storedServer } = useAuthenticationContext(); const [password, setPassword] = React.useState(''); - const { apiClient, server, setUsername: setClientUsername } = useApiClientContext(); + const { apiClient, refetchApi } = useApiClientContext(); const useApiMutation = useMutation({ mutationFn: async (credentials: JellyfinCredentials) => { @@ -23,7 +22,7 @@ export default function ServerAuthentication(): React.JSX.Element { }, onSuccess: async (authResult, credentials) => { - console.log(`Received auth response from ${server!.name}`) + console.log(`Received auth response from ${storedServer!.name}`) if (_.isUndefined(authResult)) return Promise.reject(new Error("Authentication result was empty")) @@ -33,15 +32,15 @@ export default function ServerAuthentication(): React.JSX.Element { if (_.isUndefined(authResult.data.User)) return Promise.reject(new Error("Unable to login")); - console.log(`Successfully signed in to ${server!.name}`) + console.log(`Successfully signed in to ${storedServer!.name}`) setUsername(credentials.username); - setClientUsername(credentials.username); - return await Keychain.setInternetCredentials(server!.url, credentials.username, (authResult.data.AccessToken as string)); - + setChangeUsername(false); + await Keychain.setInternetCredentials(storedServer!.url, credentials.username, (authResult.data.AccessToken as string)); + return await refetchApi(); }, onError: async (error: Error) => { console.error("An error occurred connecting to the Jellyfin instance", error); - return Promise.reject(`An error occured signing into ${server!.name}`); + return Promise.reject(`An error occured signing into ${storedServer!.name}`); } }); @@ -55,7 +54,7 @@ export default function ServerAuthentication(): React.JSX.Element { return ( - { `Sign in to ${server?.name ?? "Jellyfin"}`} + { `Sign in to ${storedServer?.name ?? "Jellyfin"}`}