aboutsummaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/context/session.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/context/session.ts')
-rw-r--r--packages/merchant-backoffice-ui/src/context/session.ts282
1 files changed, 139 insertions, 143 deletions
diff --git a/packages/merchant-backoffice-ui/src/context/session.ts b/packages/merchant-backoffice-ui/src/context/session.ts
index 6d01464e0..fb1b7b374 100644
--- a/packages/merchant-backoffice-ui/src/context/session.ts
+++ b/packages/merchant-backoffice-ui/src/context/session.ts
@@ -17,11 +17,10 @@
import {
AccessToken,
Codec,
+ TalerMerchantApi,
buildCodecForObject,
- buildCodecForUnion,
- codecForBoolean,
- codecForConstString,
codecForString,
+ codecForURL,
codecOptional,
} from "@gnu-taler/taler-util";
import {
@@ -29,99 +28,79 @@ import {
useLocalStorage,
useMerchantApiContext,
} from "@gnu-taler/web-util/browser";
+import { ComponentChildren, VNode, createContext, h } from "preact";
+import { useContext, useEffect, useState } from "preact/hooks";
import { mutate } from "swr";
+import { MerchantLib } from "../../../web-util/lib/context/activity.js";
/**
* Has the information to reach and
* authenticate at the bank's backend.
*/
-export type SessionState = LoggedIn | LoggedOut | Expired;
+export type SessionState = LoggedIn | LoggedOut;
interface LoggedIn {
status: "loggedIn";
+
+ // is this instance admin? usually "default" name
isAdmin: boolean;
+
+ // url where all the request will be made
+ // usually this is from where the SPA was loaded
+ // unless it's using impersonate feature
+ backendUrl: URL;
+
+ // instance name
instance: string;
+
+ // session is not the same from where it was loaded
+ impersonated: boolean;
+
+ //instane access token
token: AccessToken | undefined;
- impersonate: Impersonate | undefined;
-}
-interface Impersonate {
- originalInstance: string;
- originalToken: AccessToken | undefined;
- originalBackendUrl: string;
-}
-interface Expired {
- status: "expired";
- isAdmin: boolean;
- instance: string;
- token?: undefined;
- impersonate: Impersonate | undefined;
}
+
interface LoggedOut {
status: "loggedOut";
+ backendUrl: URL;
instance: string;
isAdmin: boolean;
- token?: undefined;
+ token: AccessToken | undefined;
}
-export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> =>
- buildCodecForObject<LoggedIn>()
- .property("status", codecForConstString("loggedIn"))
- .property("instance", codecForString())
- .property("impersonate", codecOptional(codecForImpresonate()))
+interface SavedSession {
+ backendUrl: URL;
+ token: AccessToken | undefined;
+ prevToken: AccessToken | undefined;
+}
+
+export const codecForSessionState = (): Codec<SavedSession> =>
+ buildCodecForObject<SavedSession>()
+ .property("backendUrl", codecForURL())
.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("impersonate", codecOptional(codecForImpresonate()))
- .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 codecForImpresonate = (): Codec<Impersonate> =>
- buildCodecForObject<Impersonate>()
- .property("originalInstance", codecForString())
.property(
- "originalToken",
+ "prevToken",
codecOptional(codecForString() as Codec<AccessToken>),
)
- .property("originalBackendUrl", codecForString())
- .build("SessionState.Impersonate");
-
-export const codecForSessionState = (): Codec<SessionState> =>
- buildCodecForUnion<SessionState>()
- .discriminateOn("status")
- .alternative("loggedIn", codecForSessionStateLoggedIn())
- .alternative("loggedOut", codecForSessionStateLoggedOut())
- .alternative("expired", codecForSessionStateExpired())
- .build("SessionState");
+ .build("SavedSession");
function inferInstanceName(url: URL) {
const match = INSTANCE_ID_LOOKUP.exec(url.href);
return !match || !match[1] ? DEFAULT_ADMIN_USERNAME : match[1];
}
-export const defaultState = (url: URL): SessionState => {
- const instance = inferInstanceName(url);
+export const defaultState = (url: URL): SavedSession => {
return {
- status: "loggedIn",
- instance,
- isAdmin: instance === DEFAULT_ADMIN_USERNAME,
+ backendUrl: url,
token: undefined,
- impersonate: undefined,
+ prevToken: undefined,
};
};
export interface SessionStateHandler {
+ lib: MerchantLib;
+ config: TalerMerchantApi.VersionResponse;
+
state: SessionState;
/**
* from every state to logout state
@@ -132,19 +111,15 @@ export interface SessionStateHandler {
*/
deImpersonate(): void;
/**
- * from non-loggedOut state to expired
- */
- expired(): void;
- /**
* from any to loggedIn
* @param info
*/
- logIn(info: { token?: AccessToken }): void;
+ logIn(token: AccessToken | undefined): void;
/**
* from loggedIn to impersonate
* @param info
*/
- impersonate(info: { instance: string; baseUrl: URL, token?: AccessToken }): void;
+ impersonate(baseUrl: URL): void;
}
const SESSION_STATE_KEY = buildStorageKey(
@@ -156,95 +131,116 @@ 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 useSessionContext(): SessionStateHandler {
- const { url: merchantUrl, changeBackend } = useMerchantApiContext();
+export function cleanAllCache(): void {
+ mutate(() => true, undefined, { revalidate: false });
+}
+const Context = createContext<SessionStateHandler>(undefined!);
+
+export const useSessionContext = (): SessionStateHandler => useContext(Context);
+
+export const SessionContextProvider = ({
+ children,
+ // value,
+}: {
+ // value: MerchantUiSettings;
+ children: ComponentChildren;
+}): VNode => {
+ const {
+ lib: rootLib,
+ config: rootConfig,
+ url: merchantUrl,
+ } = useMerchantApiContext();
+ const [status, setStatus] = useState<"loggedIn" | "loggedOut">("loggedIn");
+ const [currentConfig, setCurrentConfig] =
+ useState<TalerMerchantApi.VersionResponse>();
const { value: state, update } = useLocalStorage(
SESSION_STATE_KEY,
defaultState(merchantUrl),
);
- return {
- state,
+ const currentInstance = inferInstanceName(state.backendUrl);
+
+ let lib: MerchantLib;
+ let config: TalerMerchantApi.VersionResponse;
+ const doingImpersonation = state.backendUrl.href !== merchantUrl.href;
+ if (doingImpersonation) {
+ /**
+ * FIXME: can't impersonate other than local instances
+ */
+ lib = rootLib.subInstanceApi(inferInstanceName(state.backendUrl));
+
+ config = currentConfig ?? rootConfig;
+ } else {
+ lib = rootLib;
+ config = rootConfig;
+ }
+
+ useEffect(() => {
+ // FIXME: handle what happen if the subinstance /config
+ // fails
+ if (!doingImpersonation) return;
+ lib.instance.getConfig().then((resp) => {
+ if (resp.type === "ok") {
+ setCurrentConfig(resp.body);
+ }
+ });
+ }, [state.backendUrl.href]);
+
+ const value: SessionStateHandler = {
+ state: {
+ backendUrl: state.backendUrl,
+ token: state.token,
+ impersonated: doingImpersonation,
+ instance: currentInstance,
+ isAdmin: currentInstance === DEFAULT_ADMIN_USERNAME,
+ status: status,
+ },
+ lib,
+ config,
logOut() {
- const instance = inferInstanceName(merchantUrl);
- const nextState: SessionState = {
- status: "loggedOut",
- instance,
- isAdmin: instance === DEFAULT_ADMIN_USERNAME,
- };
- update(nextState);
+ setStatus("loggedOut");
+ update({
+ backendUrl: merchantUrl,
+ token: undefined,
+ prevToken: undefined,
+ });
+ cleanAllCache();
},
deImpersonate() {
- if (state.status === "loggedOut" || state.status === "expired") {
- // can't impersonate if not loggedin
- return;
- }
- if (state.impersonate === undefined) {
- return;
- }
- const newURL = new URL(`./`, state.impersonate.originalBackendUrl);
- changeBackend(newURL);
- const nextState: SessionState = {
- status: "loggedIn",
- isAdmin: state.impersonate.originalInstance === DEFAULT_ADMIN_USERNAME,
- instance: state.impersonate.originalInstance,
- token: state.impersonate.originalToken,
- impersonate: undefined,
- };
- update(nextState);
- },
- impersonate(info) {
- if (state.status === "loggedOut" || state.status === "expired") {
- // can't impersonate if not loggedin
- return;
- }
- changeBackend(info.baseUrl);
- const nextState: SessionState = {
- status: "loggedIn",
- isAdmin: info.instance === DEFAULT_ADMIN_USERNAME,
- instance: info.instance,
- // FIXME: bank and merchant should have consistent behavior
- token: info.token?.substring("secret-token:".length) as AccessToken,
- impersonate: {
- originalBackendUrl: merchantUrl.href,
- originalToken: state.token,
- originalInstance: state.instance,
- },
- };
- update(nextState);
+ cleanAllCache();
+ update({
+ backendUrl: merchantUrl,
+ token: state.prevToken,
+ prevToken: undefined,
+ });
+ setStatus("loggedIn");
},
- expired() {
- if (state.status === "loggedOut") return;
-
- const nextState: SessionState = {
- ...state,
- status: "expired",
+ impersonate(baseUrl) {
+ /**
+ * FIXME: can't impersonate other than local instances
+ */
+ update({
+ backendUrl: baseUrl,
token: undefined,
- };
- update(nextState);
+ prevToken: state.token,
+ });
+ setStatus("loggedIn");
+ cleanAllCache();
},
- logIn(info) {
- // admin is defined by the username
- const nextState: SessionState = {
- impersonate: undefined,
- ...state,
- status: "loggedIn",
- // FIXME: bank and merchant should have consistent behavior
- token: info.token?.substring("secret-token:".length) as AccessToken,
- // token: info.token,
- };
- update(nextState);
+ logIn(token) {
cleanAllCache();
+ setStatus("loggedIn");
+ update({
+ backendUrl: state.backendUrl,
+ token: token,
+ prevToken: state.prevToken,
+ });
},
};
-}
-export function cleanAllCache(): void {
- mutate(() => true, undefined, { revalidate: false });
-}
+ return h(Context.Provider, {
+ value,
+ children,
+ });
+};