wallet-core/packages/taler-wallet-webextension/src/wxApi.ts
2023-09-08 11:15:13 -03:00

210 lines
5.0 KiB
TypeScript

/*
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 <http://www.gnu.org/licenses/>
*/
/**
* Interface to the wallet through WebExtension messaging.
*/
/**
* Imports.
*/
import {
AbsoluteTime,
CoreApiResponse,
DetailsMap,
Logger,
LogLevel,
NotificationType,
TalerError,
TalerErrorCode,
TalerErrorDetail,
WalletDiagnostics,
} from "@gnu-taler/taler-util";
import {
WalletCoreApiClient,
WalletCoreOpKeys,
WalletCoreRequestType,
WalletCoreResponseType,
} from "@gnu-taler/taler-wallet-core";
import {
MessageFromBackend,
MessageFromFrontendBackground,
MessageFromFrontendWallet,
} from "./platform/api.js";
import { platform } from "./platform/foreground.js";
/**
*
* @author sebasjm
*/
const logger = new Logger("wxApi");
export interface ExtendedPermissionsResponse {
newValue: boolean;
}
export interface BackgroundOperations {
freeze: {
request: number;
response: void;
};
sum: {
request: number[];
response: number;
};
resetDb: {
request: void;
response: void;
};
runGarbageCollector: {
request: void;
response: void;
};
setLoggingLevel: {
request: {
tag?: string;
level: LogLevel;
};
response: void;
};
}
export interface BackgroundApiClient {
call<Op extends keyof BackgroundOperations>(
operation: Op,
payload: BackgroundOperations[Op]["request"],
): Promise<BackgroundOperations[Op]["response"]>;
}
export class BackgroundError<T = any> extends Error {
public errorDetail: TalerErrorDetail & T;
constructor(title: string, e: TalerErrorDetail & T) {
super(title);
this.errorDetail = e;
}
hasErrorCode<C extends keyof DetailsMap>(
code: C,
): this is BackgroundError<DetailsMap[C]> {
return this.errorDetail.code === code;
}
}
/**
* BackgroundApiClient integration with browser platform
*/
class BackgroundApiClientImpl implements BackgroundApiClient {
async call<Op extends keyof BackgroundOperations>(
operation: Op,
payload: BackgroundOperations[Op]["request"],
): Promise<BackgroundOperations[Op]["response"]> {
let response: CoreApiResponse;
const message: MessageFromFrontendBackground<Op> = {
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(),
});
}
throw error;
}
if (response.type === "error") {
throw new BackgroundError(
`Background operation "${operation}" failed`,
response.error,
);
}
logger.trace("response", response);
return response.result as any;
}
}
/**
* WalletCoreApiClient integration with browser platform
*/
class WalletApiClientImpl implements WalletCoreApiClient {
async call<Op extends WalletCoreOpKeys>(
operation: Op,
payload: WalletCoreRequestType<Op>,
): Promise<WalletCoreResponseType<Op>> {
let response: CoreApiResponse;
try {
const message: MessageFromFrontendWallet<Op> = {
channel: "wallet",
operation,
payload,
};
response = await platform.sendMessageToBackground(message);
} catch (e) {
logger.error("Error calling backend", e);
throw new Error(`Error contacting backend: ${e}`);
}
if (response.type === "error") {
throw new BackgroundError(
`Wallet operation "${operation}" failed`,
response.error,
);
}
logger.trace("got response", response);
return response.result as any;
}
}
function onUpdateNotification(
messageTypes: Array<NotificationType>,
doCallback: undefined | (() => void),
): () => void {
//if no callback, then ignore
if (!doCallback)
return () => {
return;
};
const onNewMessage = (message: MessageFromBackend): void => {
const shouldNotify = messageTypes.includes(message.type);
if (shouldNotify) {
doCallback();
}
};
return platform.listenToWalletBackground(onNewMessage);
}
export type WxApiType = {
wallet: WalletCoreApiClient;
background: BackgroundApiClient;
listener: {
onUpdateNotification: typeof onUpdateNotification;
};
};
export const wxApi = {
wallet: new WalletApiClientImpl(),
background: new BackgroundApiClientImpl(),
listener: {
onUpdateNotification,
},
};