/*
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,
},
};