Can I switch to a singleton for the api client?

This commit is contained in:
Violet Caulfield
2025-01-19 09:52:26 -06:00
parent aa59c09dae
commit 678f946e28
6 changed files with 152 additions and 52 deletions

View File

@@ -1,36 +1,118 @@
import { Api, Jellyfin } from "@jellyfin/sdk";
import { getDeviceNameSync, getUniqueIdSync } from "react-native-device-info";
import { name, version } from "../package.json"
import { capitalize } from "lodash";
import { Api } from "@jellyfin/sdk/lib/api";
import { JellyfinInfo } from "./info";
import { JellifyServer } from "@/types/JellifyServer";
import { JellifyUser } from "@/types/JellifyUser";
import { storage } from '../constants/storage';
import { MMKVStorageKeys } from "@/enums/mmkv-storage-keys";
import uuid from 'react-native-uuid';
import { JellifyLibrary } from "@/types/JellifyLibrary";
/**
* Client object that represents Jellify on the Jellyfin server.
*/
export const jellifyClient: Jellyfin = new Jellyfin({
clientInfo: {
name: capitalize(name),
version: version
},
deviceInfo: {
name: getDeviceNameSync(),
id: getUniqueIdSync()
export default class Client {
static #instance: Client;
public api : Api | undefined;
public user : JellifyUser | undefined;
public server : JellifyServer | undefined;
public library : JellifyLibrary | undefined;
public sessionId : string = uuid.v4();
private constructor(
api?: Api | undefined,
user?: JellifyUser | undefined,
server?: JellifyServer | undefined,
library?: JellifyLibrary | undefined
) {
const userJson = storage.getString(MMKVStorageKeys.User)
const serverJson = storage.getString(MMKVStorageKeys.Server);
const libraryJson = storage.getString(MMKVStorageKeys.Library);
if (api)
this.api = api
if (user)
this.setAndPersistUser
else if (userJson)
this.user = JSON.parse(userJson)
if (server)
this.setAndPersistServer(server)
else if (serverJson)
this.server = JSON.parse(serverJson);
if (library)
this.setAndPersistLibrary(library)
else if (libraryJson)
this.library = JSON.parse(libraryJson)
}
});
/**
* Uses the jellifyClient to create a public Jellyfin API instance.
* @param serverUrl The URL of the Jellyfin server
* @returns
*/
export function buildPublicApiClient(serverUrl : string) : Api {
return jellifyClient.createApi(serverUrl);
}
public static get instance(): Client {
if (!Client.#instance) {
Client.#instance = new Client();
}
/**
*
* @param serverUrl The URL of the Jellyfin server
* @param accessToken The assigned accessToken for the Jellyfin user
*/
export function buildAuthenticatedApiClient(serverUrl: string, accessToken: string) : Api {
return jellifyClient.createApi(serverUrl, accessToken);
return Client.#instance;
}
public static signOut(): void {
if (!Client.#instance) {
Client.instance;
}
Client.instance.removeCredentials()
}
private setAndPersistUser(user: JellifyUser) {
this.user = user;
// persist user details
storage.set(MMKVStorageKeys.User, JSON.stringify(user));
}
private setAndPersistServer(server : JellifyServer) {
this.server = server;
storage.set(MMKVStorageKeys.Server, JSON.stringify(server));
}
private setAndPersistLibrary(library : JellifyLibrary) {
this.library = library;
storage.set(MMKVStorageKeys.Library, JSON.stringify(library))
}
private removeCredentials() {
this.library = undefined;
this.library = undefined;
this.server = undefined;
this.user = undefined;
storage.delete(MMKVStorageKeys.Server)
storage.delete(MMKVStorageKeys.Library)
storage.delete(MMKVStorageKeys.User)
}
/**
* Uses the jellifyClient to create a public Jellyfin API instance.
* @param serverUrl The URL of the Jellyfin server
* @returns
*/
public static setPublicApiClient(server : JellifyServer) : void {
const api = JellyfinInfo.createApi(server.url);
Client.#instance = new Client(api, undefined, server, undefined)
}
/**
*
* @param serverUrl The URL of the Jellyfin server
* @param accessToken The assigned accessToken for the Jellyfin user
*/
public static setPrivateApiClient(server : JellifyServer, user : JellifyUser) : void {
const api = JellyfinInfo.createApi(server.url, user.accessToken);
Client.#instance = new Client(api, user, server, undefined)
}
}

18
api/info.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Api, Jellyfin } from "@jellyfin/sdk";
import { getDeviceNameSync, getUniqueIdSync } from "react-native-device-info";
import { name, version } from "../package.json"
import { capitalize } from "lodash";
/**
* Client object that represents Jellify on the Jellyfin server.
*/
export const JellyfinInfo: Jellyfin = new Jellyfin({
clientInfo: {
name: capitalize(name),
version: version
},
deviceInfo: {
name: getDeviceNameSync(),
id: getUniqueIdSync()
}
});

View File

@@ -1,5 +1,5 @@
import { Api } from "@jellyfin/sdk";
import { jellifyClient } from "../../client";
import { JellyfinInfo } from "../../info";
import _ from "lodash";
export function createApi(serverUrl?: string, username?: string, password?: string, accessToken?: string): Promise<Api> {
@@ -12,22 +12,22 @@ export function createApi(serverUrl?: string, username?: string, password?: stri
if (!_.isUndefined(accessToken)) {
console.info("Creating API with accessToken")
return resolve(jellifyClient.createApi(serverUrl, accessToken));
return resolve(JellyfinInfo.createApi(serverUrl, accessToken));
}
if (_.isUndefined(username) && _.isUndefined(password)) {
console.info("Creating public API for server url")
return resolve(jellifyClient.createApi(serverUrl));
return resolve(JellyfinInfo.createApi(serverUrl));
}
console.log("Signing into Jellyfin")
let authResult = await jellifyClient.createApi(serverUrl).authenticateUserByName(username!, password);
let authResult = await JellyfinInfo.createApi(serverUrl).authenticateUserByName(username!, password);
if (authResult.data.AccessToken) {
console.info("Signed into Jellyfin successfully")
return resolve(jellifyClient.createApi(serverUrl, authResult.data.AccessToken));
return resolve(JellyfinInfo.createApi(serverUrl, authResult.data.AccessToken));
}
return reject("Unable to sign in");

View File

@@ -11,7 +11,7 @@ import Input from "../../Global/helpers/input";
import Button from "../../Global/helpers/button";
import { http, https } from "../utils/constants";
import { storage } from "../../../constants/storage";
import { jellifyClient } from "../../../api/client";
import { JellyfinInfo } from "../../../api/info";
import { Jellyfin } from "@jellyfin/sdk/lib/jellyfin";
import { getSystemApi } from "@jellyfin/sdk/lib/utils/api/system-api";
import { SafeAreaView } from "react-native-safe-area-context";
@@ -25,7 +25,7 @@ export default function ServerAddress(): React.JSX.Element {
const useServerMutation = useMutation({
mutationFn: async () => {
let jellyfin = new Jellyfin(jellifyClient);
let jellyfin = new JellyfinInfo(JellyfinInfo);
if (!!!serverAddress)
throw new Error("Server address was empty");

View File

@@ -8,7 +8,7 @@ import { JellifyServer } from '../types/JellifyServer';
import { JellifyLibrary } from '../types/JellifyLibrary';
import { JellifyUser } from '../types/JellifyUser';
import uuid from 'react-native-uuid';
import { buildAuthenticatedApiClient, buildPublicApiClient } from '@/api/client';
import Client from '@/api/client';
interface JellyfinApiClientContext {
apiClient: Api | undefined;
@@ -24,19 +24,17 @@ interface JellyfinApiClientContext {
const JellyfinApiClientContextInitializer = () => {
const userJson = storage.getString(MMKVStorageKeys.User)
const serverJson = storage.getString(MMKVStorageKeys.Server);
const libraryJson = storage.getString(MMKVStorageKeys.Library);
const [sessionId, setSessionId] = useState<string>(uuid.v4())
const [user, setUser] = useState<JellifyUser | undefined>(userJson ? (JSON.parse(userJson) as JellifyUser) : undefined);
const [server, setServer] = useState<JellifyServer | undefined>(serverJson ? (JSON.parse(serverJson) as JellifyServer) : undefined);
const [library, setLibrary] = useState<JellifyLibrary | undefined>(libraryJson ? (JSON.parse(libraryJson) as JellifyLibrary) : undefined);
const [apiClient, setApiClient] = useState<Api | undefined>(!isUndefined(server) && !isUndefined(user) ? buildAuthenticatedApiClient(server!.url, user!.accessToken) : undefined);
const [apiClient, setApiClient] = useState<Api | undefined>(Client.instance.api);
const [sessionId, setSessionId] = useState<string>(Client.instance.sessionId);
const [user, setUser] = useState<JellifyUser | undefined>(Client.instance.user);
const [server, setServer] = useState<JellifyServer | undefined>(Client.instance.server);
const [library, setLibrary] = useState<JellifyLibrary | undefined>(Client.instance.library);
const signOut = () => {
console.debug("Signing out of Jellify");
Client.signOut();
setUser(undefined);
setServer(undefined);
setLibrary(undefined);
@@ -44,11 +42,11 @@ const JellyfinApiClientContextInitializer = () => {
useEffect(() => {
if (server && user)
setApiClient(buildAuthenticatedApiClient(server.url, user.accessToken));
Client.setPrivateApiClient(server, user)
else if (server)
setApiClient(buildPublicApiClient(server.url));
Client.setPublicApiClient(server)
else
setApiClient(undefined);
Client.signOut();
}, [
server,
user

View File

@@ -4,6 +4,8 @@ import App from './App';
import {name as appName} from './app.json';
import { PlaybackService } from './player/service'
import TrackPlayer from 'react-native-track-player';
import { Client } from './api/client';
Client.instance;
AppRegistry.registerComponent(appName, () => App);
TrackPlayer.registerPlaybackService(() => PlaybackService);