/* This file is part of GNU Taler (C) 2022-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 */ import { AbsoluteTime, Codec, LockedAccount, OfficerAccount, OfficerId, SigningKey, buildCodecForObject, codecForAbsoluteTime, codecForString, createNewOfficerAccount, decodeCrock, encodeCrock, unlockOfficerAccount, } from "@gnu-taler/taler-util"; import { buildStorageKey, useExchangeApiContext, useLocalStorage } from "@gnu-taler/web-util/browser"; import { useMemo } from "preact/hooks"; import { usePreferences } from "./preferences.js"; export interface Officer { account: LockedAccount; when: AbsoluteTime; } const codecForLockedAccount = codecForString() as Codec; type OfficerAccountString = { id: string; strKey: string; }; export const codecForOfficerAccount = (): Codec => buildCodecForObject() .property("id", codecForString()) // FIXME .property("strKey", codecForString()) // FIXME .build("OfficerAccount"); export const codecForOfficer = (): Codec => buildCodecForObject() .property("account", codecForLockedAccount) // FIXME .property("when", codecForAbsoluteTime) // FIXME .build("Officer"); export type OfficerState = OfficerNotReady | OfficerReady; export type OfficerNotReady = OfficerNotFound | OfficerLocked; interface OfficerNotFound { state: "not-found"; create: (password: string) => Promise; } interface OfficerLocked { state: "locked"; forget: () => void; tryUnlock: (password: string) => Promise; } interface OfficerReady { state: "ready"; account: OfficerAccount; forget: () => void; lock: () => void; } const OFFICER_KEY = buildStorageKey("officer", codecForOfficer()); const DEV_ACCOUNT_KEY = buildStorageKey( "account-dev", codecForOfficerAccount(), ); export function useOfficer(): OfficerState { const exchangeContext = useExchangeApiContext(); const [pref] = usePreferences(); pref.keepSessionAfterReload; // dev account, is kept on reloaded. const accountStorage = useLocalStorage(DEV_ACCOUNT_KEY); const account = useMemo(() => { if (!accountStorage.value) return undefined; return { id: accountStorage.value.id as OfficerId, signingKey: decodeCrock(accountStorage.value.strKey) as SigningKey, }; }, [accountStorage.value?.id, accountStorage.value?.strKey]); const officerStorage = useLocalStorage(OFFICER_KEY); const officer = useMemo(() => { if (!officerStorage.value) return undefined; return officerStorage.value; }, [officerStorage.value?.account, officerStorage.value?.when.t_ms]); if (officer === undefined) { return { state: "not-found", create: async (pwd: string) => { const req = await fetch( new URL("seed", exchangeContext.lib.exchange.baseUrl).href, ); const b = await req.blob(); const ar = await b.arrayBuffer(); const uintar = new Uint8Array(ar); const { id, safe, signingKey } = await createNewOfficerAccount( pwd, uintar, ); officerStorage.update({ account: safe, when: AbsoluteTime.now(), }); // accountStorage.update({ id, signingKey }); const strKey = encodeCrock(signingKey); accountStorage.update({ id, strKey }); }, }; } if (account === undefined) { return { state: "locked", forget: () => { officerStorage.reset(); }, tryUnlock: async (pwd: string) => { const ac = await unlockOfficerAccount(officer.account, pwd); // accountStorage.update(ac); accountStorage.update({ id: ac.id, strKey: encodeCrock(ac.signingKey), }); }, }; } return { state: "ready", account, lock: () => { accountStorage.reset(); }, forget: () => { officerStorage.reset(); accountStorage.reset(); }, }; }