mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-25 00:04:20 +00:00
164 lines
4.9 KiB
TypeScript
164 lines
4.9 KiB
TypeScript
|
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<SophonContextContents>()
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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<string>(localStorage, "instanceUrl", process.env.REACT_APP_DEFAULT_INSTANCE_URL ?? "https://prod.sophon.steffo.eu")
|
||
|
|
||
|
const [loginData, setLoginData]
|
||
|
= useStorageState<LoginData | null>(localStorage, "loginData", null)
|
||
|
|
||
|
const [loginError, setLoginError]
|
||
|
= React.useState<Error | null>(null)
|
||
|
|
||
|
const [loginRunning, setLoginRunning]
|
||
|
= React.useState<boolean>(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 (
|
||
|
<SophonContext.Provider value={{api, loginData, loginRunning, loginError, login, logout, changeSophon}}>
|
||
|
{children}
|
||
|
</SophonContext.Provider>
|
||
|
)
|
||
|
}
|