/* 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 */ /** * Interface to the wallet through WebExtension messaging. */ /** * Imports. */ import { AbsoluteTime, CoreApiResponse, DetailsMap, Logger, LogLevel, NotificationType, TalerError, TalerErrorCode, TalerErrorDetail, WalletNotification, } from "@gnu-taler/taler-util"; import { WalletCoreApiClient, WalletCoreOpKeys, WalletCoreRequestType, WalletCoreResponseType, } from "@gnu-taler/taler-wallet-core"; import { ExtensionNotification, MessageFromBackend, MessageFromFrontendBackground, MessageFromFrontendWallet, } from "./platform/api.js"; import { platform } from "./platform/foreground.js"; import { WalletActivityTrack } from "./wxBackend.js"; /** * * @author sebasjm */ const logger = new Logger("wxApi"); export const WALLET_CORE_SUPPORTED_VERSION = "7:0:0"; export interface ExtendedPermissionsResponse { newValue: boolean; } export interface BackgroundOperations { resetDb: { request: void; response: void; }; runGarbageCollector: { request: void; response: void; }; reinitWallet: { request: void; response: void; }; getNotifications: { request: { filter: string; operationsFrom?: string; }; response: WalletActivityTrack[]; }; clearNotifications: { request: void; response: void; }; setLoggingLevel: { request: { tag?: string; level: LogLevel; }; response: void; }; } export type WalletEvent = { notification: WalletNotification; when: AbsoluteTime; }; export interface BackgroundApiClient { call( operation: Op, payload: BackgroundOperations[Op]["request"], ): Promise; } export class BackgroundError extends Error { public readonly errorDetail: TalerErrorDetail & T; public readonly cause: Error; constructor(title: string, e: TalerErrorDetail & T, cause: Error) { super(title); this.errorDetail = e; this.cause = cause; } hasErrorCode( code: C, ): this is BackgroundError { return this.errorDetail.code === code; } } /** * BackgroundApiClient integration with browser platform */ class BackgroundApiClientImpl implements BackgroundApiClient { async call( operation: Op, payload: BackgroundOperations[Op]["request"], ): Promise { let response: CoreApiResponse; const message: MessageFromFrontendBackground = { channel: "background", operation, payload, }; try { response = await platform.sendMessageToBackground(message); } catch (error) { if (error instanceof Error) { throw new BackgroundError( operation, { code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, when: AbsoluteTime.now(), }, error, ); } throw error; } if (response.type === "error") { throw new BackgroundError( `Background operation "${operation}" failed`, response.error, TalerError.fromUncheckedDetail(response.error), ); } logger.trace("response", response); return response.result as any; } } /** * WalletCoreApiClient integration with browser platform */ class WalletApiClientImpl implements WalletCoreApiClient { async call( operation: Op, payload: WalletCoreRequestType, ): Promise> { let response: CoreApiResponse; try { // FIXME: This type must be fixed and needs documentation! // @ts-ignore const message: MessageFromFrontendWallet = { channel: "wallet", operation, payload, }; response = await platform.sendMessageToBackground(message); } catch (error) { if (error instanceof Error) { throw new BackgroundError( operation, { code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, when: AbsoluteTime.now(), }, error, ); } throw error; } if (response.type === "error") { throw new BackgroundError( `Wallet operation "${operation}" failed`, response.error, TalerError.fromUncheckedDetail(response.error), ); } logger.trace("got response", response); return response.result as any; } } function onUpdateNotification( messageTypes: Array, doCallback: undefined | ((n: WalletNotification) => void), ): () => void { //if no callback, then ignore if (!doCallback) return () => { return; }; const onNewMessage = (message: MessageFromBackend): void => { const shouldNotify = message.type === "wallet" && messageTypes.includes(message.notification.type); if (shouldNotify) { doCallback(message.notification); } }; return platform.listenToWalletBackground(onNewMessage); } export type WxApiType = { wallet: WalletCoreApiClient; background: BackgroundApiClient; listener: { trigger: (d: ExtensionNotification) => void; onUpdateNotification: typeof onUpdateNotification; }; }; function trigger(w: ExtensionNotification) { platform.triggerWalletEvent({ type: "web-extension", notification: w, }); } export const wxApi = { wallet: new WalletApiClientImpl(), background: new BackgroundApiClientImpl(), listener: { trigger, onUpdateNotification, }, };