/* This file is part of GNU Taler (C) 2019 GNUnet e.V. 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 */ /** * Imports. */ import { CoreApiMessageEnvelope, CoreApiResponse, CoreApiResponseSuccess, getErrorDetailFromException, InitRequest, Logger, setGlobalLogLevelFromString, setPRNG, WalletNotification, } from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { qjsOs } from "@gnu-taler/taler-util/qtart"; import { createNativeWalletHost2, DefaultNodeWalletArgs, openPromise, Wallet, WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; setGlobalLogLevelFromString("trace"); setPRNG(function (x: Uint8Array, n: number) { // @ts-ignore const va = globalThis._tart.randomBytes(n); const v = new Uint8Array(va); for (let i = 0; i < n; i++) x[i] = v[i]; for (let i = 0; i < v.length; i++) v[i] = 0; }); const logger = new Logger("taler-wallet-embedded/index.ts"); function sendNativeMessage(ev: CoreApiMessageEnvelope): void { const m = JSON.stringify(ev); qjsOs.postMessageToHost(m); } class NativeWalletMessageHandler { walletArgs: DefaultNodeWalletArgs | undefined; initRequest: InitRequest = {}; maybeWallet: Wallet | undefined; wp = openPromise(); httpLib = createPlatformHttpLib(); /** * Handle a request from the native wallet. */ async handleMessage( operation: string, id: string, args: any, ): Promise { const wrapResponse = (result: unknown): CoreApiResponseSuccess => { return { type: "response", id, operation, result, }; }; let initResponse: any = {}; const reinit = async () => { logger.info("in reinit"); const wR = await createNativeWalletHost2(this.walletArgs); const w = wR.wallet; this.maybeWallet = w; const resp = await w.handleCoreApiRequest("initWallet", "native-init", { ...this.initRequest, }); initResponse = resp.type == "response" ? resp.result : resp.error; w.runTaskLoop().catch((e) => { logger.error( `Error during wallet retry loop: ${e.stack ?? e.toString()}`, ); }); this.wp.resolve(w); }; switch (operation) { case "init": { this.initRequest = { ...args, }; this.walletArgs = { notifyHandler: async (notification: WalletNotification) => { sendNativeMessage({ type: "notification", payload: notification }); }, persistentStoragePath: args.persistentStoragePath, httpLib: this.httpLib, cryptoWorkerType: args.cryptoWorkerType, }; const logLevel = args.logLevel; if (logLevel) { setGlobalLogLevelFromString(logLevel); } await reinit(); return wrapResponse({ ...initResponse, }); } case "startTunnel": { // this.httpLib.useNfcTunnel = true; throw Error("not implemented"); } case "stopTunnel": { // this.httpLib.useNfcTunnel = false; throw Error("not implemented"); } case "tunnelResponse": { // httpLib.handleTunnelResponse(msg.args); throw Error("not implemented"); } case "reset": { const oldArgs = this.walletArgs; this.walletArgs = { ...oldArgs }; if (oldArgs && oldArgs.persistentStoragePath) { try { logger.error("FIXME: reset not implemented"); // fs.unlinkSync(oldArgs.persistentStoragePath); } catch (e) { logger.error("Error while deleting the wallet db:", e); } // Prevent further storage! this.walletArgs.persistentStoragePath = undefined; } const wallet = await this.wp.promise; wallet.stop(); this.wp = openPromise(); this.maybeWallet = undefined; await reinit(); return wrapResponse({}); } default: { const wallet = await this.wp.promise; return await wallet.handleCoreApiRequest(operation, id, args); } } } } export function installNativeWalletListener(): void { setGlobalLogLevelFromString("trace"); const handler = new NativeWalletMessageHandler(); const onMessage = async (msgStr: any): Promise => { if (typeof msgStr !== "string") { logger.error("expected string as message"); return; } const msg = JSON.parse(msgStr); const operation = msg.operation; if (typeof operation !== "string") { logger.error( "message to native wallet helper must contain operation of type string", ); return; } const id = msg.id; logger.info(`native listener: got request for ${operation} (${id})`); try { const respMsg = await handler.handleMessage( operation, id, msg.args ?? {}, ); logger.info( `native listener: sending success response for ${operation} (${id})`, ); sendNativeMessage(respMsg); } catch (e) { const respMsg: CoreApiResponse = { type: "error", id, operation, error: getErrorDetailFromException(e), }; sendNativeMessage(respMsg); return; } }; qjsOs.setMessageFromHostHandler((m) => onMessage(m)); logger.info("native wallet listener installed"); } // @ts-ignore globalThis.installNativeWalletListener = installNativeWalletListener; export async function testWithGv() { const w = await createNativeWalletHost2(); await w.wallet.client.call(WalletApiOperation.InitWallet, {}); await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "KUDOS:1", amountToWithdraw: "KUDOS:3", bankBaseUrl: "https://bank.demo.taler.net/demobanks/default/access-api/", exchangeBaseUrl: "https://exchange.demo.taler.net/", merchantBaseUrl: "https://backend.demo.taler.net/", }); await w.wallet.runTaskLoop({ stopWhenDone: true, }); } export async function testWithLocal() { console.log("running local test"); const w = await createNativeWalletHost2({ persistentStoragePath: "walletdb.json", }); console.log("created wallet"); await w.wallet.client.call(WalletApiOperation.InitWallet, { skipDefaults: true, }); console.log("initialized wallet"); await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "TESTKUDOS:1", amountToWithdraw: "TESTKUDOS:3", bankBaseUrl: "http://localhost:8082/", bankAccessApiBaseUrl: "http://localhost:8082/taler-bank-access/", exchangeBaseUrl: "http://localhost:8081/", merchantBaseUrl: "http://localhost:8083/", }); console.log("started integration test"); await w.wallet.runTaskLoop({ stopWhenDone: true, }); console.log("done with task loop"); w.wallet.stop(); } // @ts-ignore globalThis.testWithGv = testWithGv; // @ts-ignore globalThis.testWithLocal = testWithLocal;