import * as React from "react" import Axios, {AxiosInstance} from "axios" import {LoginData, makeAuthorizationHeader, requestLoginData} from "./LoginData"; import {createNullContext, useNotNullContext} from "../hooks/useNotNullContext"; import {useStorageState} from "../hooks/useStorageState"; /** * The type of the `changeSophon` function in {@link SophonContextContents}. */ export type ChangeSophonFunction = (url: string) => void /** * The type of the `login` function in {@link SophonContextContents}. */ export type LoginFunction = (username: string, password: string) => void /** * The type of the `logout` function in {@link SophonContextContents}. */ export type LogoutFunction = () => void /** * The contents of the global app context {@link SophonContext}. */ export interface SophonContextContents { /** * The {@link Axios} instance to use to perform API calls on the Sophon backend. */ api: AxiosInstance, /** * The {@link LoginData} of the currently logged in user, or `null` if the user is anonymous. */ loginData: LoginData | null, /** * Whether a login is running or not. */ loginRunning: boolean, /** * An error that occoured during the login if it happened, `null` otherwise. */ loginError: Error | null, /** * Login to the Sophon backend with the given `username` and `password`, consequently updating the {@link api} instance. */ login: LoginFunction, /** * Logout from the Sophon backend, consequently updating the {@link api} instance. */ logout: LogoutFunction, /** * Change Sophon instance to the one with the given `url`. */ changeSophon: ChangeSophonFunction, } /** * The global app context, containing {@link SophonContextContents}. */ export const SophonContext = createNullContext() /** * Shortcut hook for using the {@link useNotNullContext} hook on {@link SophonContext}. */ export function useSophonContext(): SophonContextContents { return useNotNullContext(SophonContext) } /** * The props that can be passed to the {@link SophonContextProvider}. */ export interface SophonContextProviderProps { children?: React.ReactNode, } /** * Automatic provider for the global app context {@link SophonContext}. * * No need to do anything with it, except to use it to wrap the whole app. */ export function SophonContextProvider({children}: SophonContextProviderProps): JSX.Element { const [instanceUrl, setInstanceUrl] = useStorageState(localStorage, "instanceUrl", process.env.REACT_APP_DEFAULT_INSTANCE_URL ?? "https://prod.sophon.steffo.eu") const [loginData, setLoginData] = useStorageState(localStorage, "loginData", null) const [loginError, setLoginError] = React.useState(null) const [loginRunning, setLoginRunning] = React.useState(false) const api: AxiosInstance = React.useMemo( () => { console.debug("Creating new AxiosInstance...") return Axios.create({ baseURL: instanceUrl, timeout: 3000, headers: { ...makeAuthorizationHeader(loginData) } }) }, [instanceUrl, loginData] ) const login: LoginFunction = React.useCallback( (username, password) => { console.info("Trying to login as", username, "...") setLoginRunning(true) setLoginError(null) requestLoginData(api, username, password) .then(loginData => setLoginData(loginData)) .catch(error => setLoginError(error)) .finally(() => setLoginRunning(false)) }, [api, setLoginData, setLoginRunning, setLoginError] ) const logout: LogoutFunction = React.useCallback( () => { if(loginRunning) { throw Error("Refusing to logout while a login is running.") } console.info("Logging out...") setLoginData(null) }, [setLoginData] ) const changeSophon: ChangeSophonFunction = React.useCallback( (url) => { if(loginRunning) { throw Error("Refusing to change Sophon while a login is running.") } if(loginData) { console.debug("Logging out user before changing Sophon...") logout() } console.info("Changing Sophon to ", url, "...") setInstanceUrl(url) }, [logout, setInstanceUrl, loginRunning, loginData] ) return ( {children} ) }