/* 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 */ /** * Entry-point for the wallet under qtart, the QuckJS-based GNU Taler runtime. */ /** * 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"); /** * Sends JSON to the host application, i.e. the process that * runs the JavaScript interpreter (quickjs / qtart) to run * the embedded wallet. */ 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 wrapSuccessResponse = (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 wrapSuccessResponse({ ...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": { logger.info("resetting wallet"); const oldArgs = this.walletArgs; this.walletArgs = { ...oldArgs }; if (oldArgs && oldArgs.persistentStoragePath) { const ret = qjsOs.remove(oldArgs.persistentStoragePath); if (ret != 0) { logger.error("removing DB file failed"); } // Prevent further storage! this.walletArgs.persistentStoragePath = undefined; } const wallet = await this.wp.promise; wallet.stop(); this.wp = openPromise(); this.maybeWallet = undefined; await reinit(); logger.info("wallet re-initialized after reset"); return wrapSuccessResponse({}); } 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})`); let respMsg: CoreApiResponse; try { respMsg = await handler.handleMessage(operation, id, msg.args ?? {}); } catch (e) { respMsg = { type: "error", id, operation, error: getErrorDetailFromException(e), }; } logger.info( `native listener: sending back ${respMsg.type} message for operation ${operation} (${id})`, ); sendNativeMessage(respMsg); }; 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", bankAccessApiBaseUrl: "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", 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;