/*
This file is part of GNU Taler
(C) 2022 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,
useLocalStorage
} from "@gnu-taler/web-util/browser";
import { useMemo } from "preact/hooks";
import { useMaybeExchangeApiContext } from "../context/config.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 = useMaybeExchangeApiContext();
// dev account, is save when 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) => {
if (!exchangeContext) return;
const req = await fetch(new URL("seed", exchangeContext.api.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();
},
};
}