diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/hooks')
6 files changed, 213 insertions, 334 deletions
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts index 4305a9309..37dfd8fd6 100644 --- a/packages/merchant-backoffice-ui/src/hooks/backend.ts +++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts @@ -19,8 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AbsoluteTime, HttpStatusCode } from "@gnu-taler/taler-util"; import { + AbsoluteTime, + AccessToken, + HttpStatusCode, +} from "@gnu-taler/taler-util"; +import { + EmptyObject, ErrorType, HttpError, HttpResponse, @@ -31,10 +36,8 @@ import { } from "@gnu-taler/web-util/browser"; import { useCallback, useEffect, useState } from "preact/hooks"; import { useSWRConfig } from "swr"; -import { useBackendContext } from "../context/backend.js"; -import { useInstanceContext } from "../context/instance.js"; -import { AccessToken, LoginToken, MerchantBackend, Timestamp } from "../declaration.js"; - +import { useSessionContext } from "../context/session.js"; +import { LoginToken, MerchantBackend, Timestamp } from "../declaration.js"; export function useMatchMutate(): ( re?: RegExp, @@ -49,18 +52,22 @@ export function useMatchMutate(): ( } return function matchRegexMutate(re?: RegExp) { - return mutate((key) => { - // evict if no key or regex === all - if (!key || !re) return true - // match string - if (typeof key === 'string' && re.test(key)) return true - // record or object have the path at [0] - if (typeof key === 'object' && re.test(key[0])) return true - //key didn't match regex - return false - }, undefined, { - revalidate: true, - }); + return mutate( + (key) => { + // evict if no key or regex === all + if (!key || !re) return true; + // match string + if (typeof key === "string" && re.test(key)) return true; + // record or object have the path at [0] + if (typeof key === "object" && re.test(key[0])) return true; + //key didn't match regex + return false; + }, + undefined, + { + revalidate: true, + }, + ); }; } @@ -97,30 +104,36 @@ export function useBackendConfig(): HttpResponse< const { request } = useBackendBaseRequest(); type Type = MerchantBackend.VersionResponse; - type State = { data: HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>>, timer: number } - const [result, setResult] = useState<State>({ data: { loading: true }, timer: 0 }); + type State = { + data: HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>>; + timer: number; + }; + const [result, setResult] = useState<State>({ + data: { loading: true }, + timer: 0, + }); useEffect(() => { if (result.timer) { - clearTimeout(result.timer) + clearTimeout(result.timer); } function tryConfig(): void { request<Type>(`/config`) .then((data) => { const timer: any = setTimeout(() => { - tryConfig() - }, CHECK_CONFIG_INTERVAL_OK) - setResult({ data, timer }) + tryConfig(); + }, CHECK_CONFIG_INTERVAL_OK); + setResult({ data, timer }); }) .catch((error) => { const timer: any = setTimeout(() => { - tryConfig() - }, CHECK_CONFIG_INTERVAL_FAIL) - const data = error.cause - setResult({ data, timer }) + tryConfig(); + }, CHECK_CONFIG_INTERVAL_FAIL); + const data = error.cause; + setResult({ data, timer }); }); } - tryConfig() + tryConfig(); }, [request]); return result.data; @@ -134,29 +147,29 @@ interface useBackendInstanceRequestType { fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>; multiFetcher: <T>(params: [url: string[]]) => Promise<HttpResponseOk<T>[]>; orderFetcher: <T>( - params: [endpoint: string, + params: [ + endpoint: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, - delta?: number,] + delta?: number, + ], ) => Promise<HttpResponseOk<T>>; transferFetcher: <T>( - params: [endpoint: string, + params: [ + endpoint: string, payto_uri?: string, verified?: string, position?: string, - delta?: number,] + delta?: number, + ], ) => Promise<HttpResponseOk<T>>; templateFetcher: <T>( - params: [endpoint: string, - position?: string, - delta?: number] + params: [endpoint: string, position?: string, delta?: number], ) => Promise<HttpResponseOk<T>>; webhookFetcher: <T>( - params: [endpoint: string, - position?: string, - delta?: number] + params: [endpoint: string, position?: string, delta?: number], ) => Promise<HttpResponseOk<T>>; } interface useBackendBaseRequestType { @@ -167,14 +180,16 @@ interface useBackendBaseRequestType { } type YesOrNo = "yes" | "no"; -type LoginResult = { - valid: true; - token: string; - expiration: Timestamp; -} | { - valid: false; - cause: HttpError<{}>; -} +type LoginResult = + | { + valid: true; + token: string; + expiration: Timestamp; + } + | { + valid: false; + cause: HttpError<EmptyObject>; + }; export function useCredentialsChecker() { const { request } = useApiContext(); @@ -187,24 +202,34 @@ export function useCredentialsChecker() { const data: MerchantBackend.Instances.LoginTokenRequest = { scope: "write", duration: { - d_us: "forever" + d_us: "forever", }, refreshable: true, - } + }; try { - const response = await request<MerchantBackend.Instances.LoginTokenSuccessResponse>(baseUrl, `/private/token`, { - method: "POST", - token, - data - }); - return { valid: true, token: response.data.token, expiration: response.data.expiration }; + const response = + await request<MerchantBackend.Instances.LoginTokenSuccessResponse>( + baseUrl, + `/private/token`, + { + method: "POST", + token, + data, + }, + ); + return { + valid: true, + token: response.data.token, + expiration: response.data.expiration, + }; } catch (error) { if (error instanceof RequestError) { return { valid: false, cause: error.cause }; } return { - valid: false, cause: { + valid: false, + cause: { type: ErrorType.UNEXPECTED, loading: false, info: { @@ -212,23 +237,28 @@ export function useCredentialsChecker() { status: 0, options: {}, url: `/private/token`, - payload: {} + payload: {}, }, exception: error, - message: (error instanceof Error ? error.message : "unpexepected error") - } + message: + error instanceof Error ? error.message : "unpexepected error", + }, }; } - }; + } async function refreshLoginToken( baseUrl: string, - token: LoginToken + token: LoginToken, ): Promise<LoginResult> { - - if (AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(token.expiration))) { + if ( + AbsoluteTime.isExpired( + AbsoluteTime.fromProtocolTimestamp(token.expiration), + ) + ) { return { - valid: false, cause: { + valid: false, + cause: { type: ErrorType.CLIENT, status: HttpStatusCode.Unauthorized, message: "login token expired, login again.", @@ -237,16 +267,16 @@ export function useCredentialsChecker() { status: 401, options: {}, url: `/private/token`, - payload: {} + payload: {}, }, - payload: {} + payload: {}, }, - } + }; } - return requestNewLoginToken(baseUrl, token.token as AccessToken) + return requestNewLoginToken(baseUrl, token.token as AccessToken); } - return { requestNewLoginToken, refreshLoginToken } + return { requestNewLoginToken, refreshLoginToken }; } /** @@ -255,37 +285,36 @@ export function useCredentialsChecker() { * @returns request handler to */ export function useBackendBaseRequest(): useBackendBaseRequestType { - const { url: backend, token: loginToken } = useBackendContext(); const { request: requestHandler } = useApiContext(); - const token = loginToken?.token; + const { state } = useSessionContext(); + const token = state.status === "loggedIn" ? state.token : undefined; + const baseUrl = state.backendUrl; const request = useCallback( function requestImpl<T>( endpoint: string, options: RequestOptions = {}, ): Promise<HttpResponseOk<T>> { - return requestHandler<T>(backend, endpoint, { ...options, token }).then(res => { - return res - }).catch(err => { - throw err - }); + return requestHandler<T>(baseUrl, endpoint, { ...options, token }) + .then((res) => { + return res; + }) + .catch((err) => { + throw err; + }); }, - [backend, token], + [baseUrl, token], ); return { request }; } export function useBackendInstanceRequest(): useBackendInstanceRequestType { - const { url: rootBackendUrl, token: rootToken } = useBackendContext(); - const { token: instanceToken, admin } = useInstanceContext(); const { request: requestHandler } = useApiContext(); - const { baseUrl, token: loginToken } = !admin - ? { baseUrl: rootBackendUrl, token: rootToken } - : { baseUrl: rootBackendUrl, token: instanceToken }; - - const token = loginToken?.token; + const { state } = useSessionContext(); + const token = state.status === "loggedIn" ? state.token : undefined; + const baseUrl = state.backendUrl; const request = useCallback( function requestImpl<T>( @@ -301,7 +330,7 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { function multiFetcherImpl<T>( args: [endpoints: string[]], ): Promise<HttpResponseOk<T>[]> { - const [endpoints] = args + const [endpoints] = args; return Promise.all( endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint, { token }), @@ -320,18 +349,22 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { const orderFetcher = useCallback( function orderFetcherImpl<T>( - args: [endpoint: string, + args: [ + endpoint: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, - delta?: number,] + delta?: number, + ], ): Promise<HttpResponseOk<T>> { - const [endpoint, paid, refunded, wired, searchDate, delta] = args + const [endpoint, paid, refunded, wired, searchDate, delta] = args; const date_s = delta && delta < 0 && searchDate ? Math.floor(searchDate.getTime() / 1000) + 1 - : searchDate !== undefined ? Math.floor(searchDate.getTime() / 1000) : undefined; + : searchDate !== undefined + ? Math.floor(searchDate.getTime() / 1000) + : undefined; const params: any = {}; if (paid !== undefined) params.paid = paid; if (delta !== undefined) params.delta = delta; @@ -339,12 +372,12 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { if (wired !== undefined) params.wired = wired; if (date_s !== undefined) params.date_s = date_s; if (delta === 0) { - //in this case we can already assume the response + //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { orders: [] } as T, - }) + }); } return requestHandler<T>(baseUrl, endpoint, { params, token }); }, @@ -353,23 +386,25 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { const transferFetcher = useCallback( function transferFetcherImpl<T>( - args: [endpoint: string, + args: [ + endpoint: string, payto_uri?: string, verified?: string, position?: string, - delta?: number,] + delta?: number, + ], ): Promise<HttpResponseOk<T>> { - const [endpoint, payto_uri, verified, position, delta] = args + const [endpoint, payto_uri, verified, position, delta] = args; const params: any = {}; if (payto_uri !== undefined) params.payto_uri = payto_uri; if (verified !== undefined) params.verified = verified; if (delta === 0) { - //in this case we can already assume the response + //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { transfers: [] } as T, - }) + }); } if (delta !== undefined) { params.limit = delta; @@ -383,19 +418,17 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { const templateFetcher = useCallback( function templateFetcherImpl<T>( - args: [endpoint: string, - position?: string, - delta?: number,] + args: [endpoint: string, position?: string, delta?: number], ): Promise<HttpResponseOk<T>> { - const [endpoint, position, delta] = args + const [endpoint, position, delta] = args; const params: any = {}; if (delta === 0) { - //in this case we can already assume the response + //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { templates: [] } as T, - }) + }); } if (delta !== undefined) { params.limit = delta; @@ -409,19 +442,17 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { const webhookFetcher = useCallback( function webhookFetcherImpl<T>( - args: [endpoint: string, - position?: string, - delta?: number,] + args: [endpoint: string, position?: string, delta?: number], ): Promise<HttpResponseOk<T>> { - const [endpoint, position, delta] = args + const [endpoint, position, delta] = args; const params: any = {}; if (delta === 0) { - //in this case we can already assume the response + //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { webhooks: [] } as T, - }) + }); } if (delta !== undefined) { params.limit = delta; diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts index 4f6cabc9e..a1bb3d5d4 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts @@ -21,7 +21,7 @@ import * as tests from "@gnu-taler/web-util/testing"; import { expect } from "chai"; -import { AccessToken, MerchantBackend } from "../declaration.js"; +import { MerchantBackend } from "../declaration.js"; import { useAdminAPI, useBackendInstances, @@ -40,6 +40,7 @@ import { API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_BY_ID, } from "./urls.js"; +import { AccessToken } from "@gnu-taler/taler-util"; describe("instance api interaction with details", () => { it("should evict cache when updating an instance", async () => { diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts index 352f54982..dfe97fd61 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -17,9 +17,9 @@ import { HttpResponse, HttpResponseOk, RequestError, + useMerchantApiContext, } from "@gnu-taler/web-util/browser"; -import { useBackendContext } from "../context/backend.js"; -import { AccessToken, MerchantBackend } from "../declaration.js"; +import { MerchantBackend } from "../declaration.js"; import { useBackendBaseRequest, useBackendInstanceRequest, @@ -29,6 +29,8 @@ import { // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import _useSWR, { SWRHook, useSWRConfig } from "swr"; +import { useSessionContext } from "../context/session.js"; +import { AccessToken } from "@gnu-taler/taler-util"; const useSWR = _useSWR as unknown as SWRHook; interface InstanceAPI { @@ -37,7 +39,10 @@ interface InstanceAPI { ) => Promise<void>; deleteInstance: () => Promise<void>; clearAccessToken: (currentToken: AccessToken | undefined) => Promise<void>; - setNewAccessToken: (currentToken: AccessToken | undefined, token: AccessToken) => Promise<void>; + setNewAccessToken: ( + currentToken: AccessToken | undefined, + token: AccessToken, + ) => Promise<void>; } export function useAdminAPI(): AdminAPI { @@ -87,10 +92,13 @@ export interface AdminAPI { export function useManagementAPI(instanceId: string): InstanceAPI { const mutateAll = useMatchMutate(); - const { url: backendURL } = useBackendContext() - const { updateToken } = useBackendContext(); + const { + state: { backendUrl }, + logIn, + logOut, + } = useSessionContext(); const { request } = useBackendBaseRequest(); - const { requestNewLoginToken } = useCredentialsChecker() + const { requestNewLoginToken } = useCredentialsChecker(); const updateInstance = async ( instance: MerchantBackend.Instances.InstanceReconfigurationMessage, @@ -111,7 +119,9 @@ export function useManagementAPI(instanceId: string): InstanceAPI { mutateAll(/\/management\/instances/); }; - const clearAccessToken = async (currentToken: AccessToken | undefined): Promise<void> => { + const clearAccessToken = async ( + currentToken: AccessToken | undefined, + ): Promise<void> => { await request(`/management/instances/${instanceId}/auth`, { method: "POST", token: currentToken, @@ -121,36 +131,46 @@ export function useManagementAPI(instanceId: string): InstanceAPI { mutateAll(/\/management\/instances/); }; - const setNewAccessToken = async (currentToken: AccessToken | undefined, newToken: AccessToken): Promise<void> => { + const setNewAccessToken = async ( + currentToken: AccessToken | undefined, + newToken: AccessToken, + ): Promise<void> => { await request(`/management/instances/${instanceId}/auth`, { method: "POST", token: currentToken, data: { method: "token", token: newToken }, }); - const resp = await requestNewLoginToken(backendURL, newToken) + const resp = await requestNewLoginToken(backendUrl, newToken); if (resp.valid) { - const { token, expiration } = resp - updateToken({ token, expiration }); + logIn({ token: resp.token as AccessToken }); } else { - updateToken(undefined) + logOut(); } mutateAll(/\/management\/instances/); }; - return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken }; + return { + updateInstance, + deleteInstance, + setNewAccessToken, + clearAccessToken, + }; } export function useInstanceAPI(): InstanceAPI { const { mutate } = useSWRConfig(); - const { url: backendURL, updateToken } = useBackendContext() - const { - token: adminToken, - } = useBackendContext(); + state: { backendUrl }, + } = useSessionContext(); + const { request } = useBackendInstanceRequest(); - const { requestNewLoginToken } = useCredentialsChecker() + const { requestNewLoginToken } = useCredentialsChecker(); + const { state, logIn, logOut } = useSessionContext(); + + const adminToken = + state.status === "loggedIn" && state.isAdmin ? state.token : undefined; const updateInstance = async ( instance: MerchantBackend.Instances.InstanceReconfigurationMessage, @@ -160,7 +180,9 @@ export function useInstanceAPI(): InstanceAPI { data: instance, }); - if (adminToken) mutate(["/private/instances", adminToken, backendURL], null); + if (adminToken) { + mutate(["/private/instances", adminToken, backendUrl], null); + } mutate([`/private/`], null); }; @@ -170,11 +192,15 @@ export function useInstanceAPI(): InstanceAPI { // token: adminToken, }); - if (adminToken) mutate(["/private/instances", adminToken, backendURL], null); + if (adminToken) { + mutate(["/private/instances", adminToken, backendUrl], null); + } mutate([`/private/`], null); }; - const clearAccessToken = async (currentToken: AccessToken | undefined): Promise<void> => { + const clearAccessToken = async ( + currentToken: AccessToken | undefined, + ): Promise<void> => { await request(`/private/auth`, { method: "POST", token: currentToken, @@ -184,25 +210,32 @@ export function useInstanceAPI(): InstanceAPI { mutate([`/private/`], null); }; - const setNewAccessToken = async (currentToken: AccessToken | undefined, newToken: AccessToken): Promise<void> => { + const setNewAccessToken = async ( + currentToken: AccessToken | undefined, + newToken: AccessToken, + ): Promise<void> => { await request(`/private/auth`, { method: "POST", token: currentToken, data: { method: "token", token: newToken }, }); - const resp = await requestNewLoginToken(backendURL, newToken) + const resp = await requestNewLoginToken(backendUrl, newToken); if (resp.valid) { - const { token, expiration } = resp - updateToken({ token, expiration }); + logIn({ token: resp.token as AccessToken }); } else { - updateToken(undefined) + logOut(); } mutate([`/private/`], null); }; - return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken }; + return { + updateInstance, + deleteInstance, + setNewAccessToken, + clearAccessToken, + }; } export function useInstanceDetails(): HttpResponse< diff --git a/packages/merchant-backoffice-ui/src/hooks/preference.ts b/packages/merchant-backoffice-ui/src/hooks/preference.ts index 4570ff679..5a50eb378 100644 --- a/packages/merchant-backoffice-ui/src/hooks/preference.ts +++ b/packages/merchant-backoffice-ui/src/hooks/preference.ts @@ -59,6 +59,7 @@ const PREFERENCES_KEY = buildStorageKey( export function usePreference(): [ Readonly<Preferences>, <T extends keyof Preferences>(key: T, value: Preferences[T]) => void, + (s: Preferences) => void, ] { const { value, update } = useLocalStorage(PREFERENCES_KEY, defaultSettings); function updateField<T extends keyof Preferences>(k: T, v: Preferences[T]) { @@ -66,7 +67,7 @@ export function usePreference(): [ update(newValue); } - return [value, updateField]; + return [value, updateField, update]; } export function dateFormatForSettings(s: Preferences): string { diff --git a/packages/merchant-backoffice-ui/src/hooks/session.ts b/packages/merchant-backoffice-ui/src/hooks/session.ts deleted file mode 100644 index 8bf075e94..000000000 --- a/packages/merchant-backoffice-ui/src/hooks/session.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import { - AccessToken, - Codec, - buildCodecForObject, - buildCodecForUnion, - codecForBoolean, - codecForConstString, - codecForConstTrue, - codecForString, - codecOptional, -} from "@gnu-taler/taler-util"; -import { buildStorageKey, useLocalStorage, useMerchantApiContext } from "@gnu-taler/web-util/browser"; -import { mutate } from "swr"; - -/** - * Has the information to reach and - * authenticate at the bank's backend. - */ -export type SessionState = LoggedIn | LoggedOut | Expired | Impersonate; - -interface LoggedIn { - status: "loggedIn"; - instance: string; - isAdmin: boolean; - token: AccessToken | undefined; -} -interface Expired { - status: "expired"; - instance: string; - isAdmin: boolean; -} -interface Impersonate { - status: "impersonate"; - instance: string; - isAdmin: true; - token: AccessToken | undefined; - originalInstance: string; - originalToken: AccessToken | undefined; -} -interface LoggedOut { - status: "loggedOut"; - instance: string; - isAdmin: boolean; -} - -export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> => - buildCodecForObject<LoggedIn>() - .property("status", codecForConstString("loggedIn")) - .property("instance", codecForString()) - .property("token", codecOptional(codecForString() as Codec<AccessToken>)) - .property("isAdmin", codecForBoolean()) - .build("SessionState.LoggedIn"); - -export const codecForSessionStateExpired = (): Codec<Expired> => - buildCodecForObject<Expired>() - .property("status", codecForConstString("expired")) - .property("instance", codecForString()) - .property("isAdmin", codecForBoolean()) - .build("SessionState.Expired"); - -export const codecForSessionStateLoggedOut = (): Codec<LoggedOut> => - buildCodecForObject<LoggedOut>() - .property("status", codecForConstString("loggedOut")) - .property("instance", codecForString()) - .property("isAdmin", codecForBoolean()) - .build("SessionState.LoggedOut"); - -export const codecForSessionStateImpresonate = (): Codec<Impersonate> => - buildCodecForObject<Impersonate>() - .property("status", codecForConstString("impersonate")) - .property("instance", codecForString()) - .property("isAdmin", codecForConstTrue()) - .property("token", codecOptional(codecForString() as Codec<AccessToken>)) - .property("originalInstance", codecForString()) - .property("originalToken", codecOptional(codecForString() as Codec<AccessToken>)) - .build("SessionState.Impersonate"); - -export const codecForSessionState = (): Codec<SessionState> => - buildCodecForUnion<SessionState>() - .discriminateOn("status") - .alternative("loggedIn", codecForSessionStateLoggedIn()) - .alternative("impersonate", codecForSessionStateImpresonate()) - .alternative("loggedOut", codecForSessionStateLoggedOut()) - .alternative("expired", codecForSessionStateExpired()) - .build("SessionState"); - -export const defaultState = (instance: string): SessionState => ({ - status: "loggedIn", - instance, - isAdmin: instance === DEFAULT_ADMIN_USERNAME, - token: undefined, -}); - -export interface SessionStateHandler { - state: SessionState; - logOut(): void; - expired(): void; - logIn(info: { instance: string; token?: AccessToken }): void; - impersonate(info: { instance: string; token?: AccessToken }): void; -} - -const SESSION_STATE_KEY = buildStorageKey("merchant-session", codecForSessionState()); - -export const DEFAULT_ADMIN_USERNAME = "default" - -export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/; - -/** - * Return getters and setters for - * login credentials and backend's - * base URL. - */ -export function useSessionState(): SessionStateHandler { - const { url } = useMerchantApiContext(); - - const match = INSTANCE_ID_LOOKUP.exec(url.href); - const instanceName = !match || !match[1] ? DEFAULT_ADMIN_USERNAME : match[1]; - - const { value: state, update } = useLocalStorage( - SESSION_STATE_KEY, - defaultState(instanceName), - ); - - return { - state, - logOut() { - update(defaultState(instanceName)); - }, - impersonate(info) { - if (state.status === "loggedOut" || state.status === "expired") { - // can't impersonate if not loggedin - return; - } - const nextState: SessionState = { - status: "impersonate", - originalToken: state.token, - originalInstance: state.instance, - isAdmin: true, - instance: info.instance, - token: info.token, - }; - update(nextState); - }, - expired() { - if (state.status === "loggedOut") return; - const nextState: SessionState = { - status: "expired", - instance: state.instance, - isAdmin: state.instance === DEFAULT_ADMIN_USERNAME, - }; - update(nextState); - }, - logIn(info) { - // admin is defined by the username - const nextState: SessionState = { - status: "loggedIn", - instance: info.instance, - token: info.token, - isAdmin: state.instance === DEFAULT_ADMIN_USERNAME, - }; - update(nextState); - cleanAllCache(); - }, - }; -} - -function cleanAllCache(): void { - mutate(() => true, undefined, { revalidate: false }); -} diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx b/packages/merchant-backoffice-ui/src/hooks/testing.tsx index d9a70e794..bebf7716b 100644 --- a/packages/merchant-backoffice-ui/src/hooks/testing.tsx +++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx @@ -24,8 +24,6 @@ import { ComponentChildren, FunctionalComponent, h, VNode } from "preact"; import { HttpRequestLibrary, HttpRequestOptions, HttpResponse } from "@gnu-taler/taler-util/http"; import { SWRConfig } from "swr"; import { ApiContextProvider } from "@gnu-taler/web-util/browser"; -import { BackendContextProvider } from "../context/backend.js"; -import { InstanceContextProvider } from "../context/instance.js"; import { HttpResponseOk, RequestOptions } from "@gnu-taler/web-util/browser"; import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient } from "@gnu-taler/taler-util"; @@ -149,15 +147,15 @@ export class ApiMockEnvironment extends MockEnvironment { const bankWire = new TalerWireGatewayHttpClient(bankCore.getWireGatewayAPI("b").href, "b", mockHttpClient) return ( - <BackendContextProvider defaultUrl="http://backend"> - <InstanceContextProvider - value={{ - token: undefined, - id: "default", - admin: true, - changeToken: () => null, - }} - > + // <BackendContextProvider defaultUrl="http://backend"> + // <InstanceContextProvider + // value={{ + // token: undefined, + // id: "default", + // admin: true, + // changeToken: () => null, + // }} + // > <ApiContextProvider value={{ request, bankCore, bankIntegration, bankRevenue, bankWire }}> <SC value={{ @@ -172,8 +170,8 @@ export class ApiMockEnvironment extends MockEnvironment { {children} </SC> </ApiContextProvider> - </InstanceContextProvider> - </BackendContextProvider> + // </InstanceContextProvider> + // </BackendContextProvider> ); }; } |