gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 01/04: make wallet-cli runnable under qtart


From: gnunet
Subject: [taler-wallet-core] 01/04: make wallet-cli runnable under qtart
Date: Thu, 16 Feb 2023 03:24:23 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit 825d2c4352022e7397854b2bd9ba7d3589873c07
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Feb 15 23:32:42 2023 +0100

    make wallet-cli runnable under qtart
---
 packages/anastasis-core/src/cli.ts                 |   2 +-
 .../merchant-backoffice-ui/src/utils/regex.test.ts |   2 +-
 packages/taler-harness/src/bench1.ts               |  17 +-
 packages/taler-harness/src/bench2.ts               |  11 +-
 packages/taler-harness/src/bench3.ts               |  15 +-
 packages/taler-harness/src/harness/harness.ts      |   9 +-
 packages/taler-harness/src/index.ts                |  13 +-
 .../integrationtests/test-exchange-timetravel.ts   |  10 +-
 .../taler-harness/src/integrationtests/test-kyc.ts |  14 +-
 .../test-merchant-spec-public-orders.ts            |   5 +-
 .../src/integrationtests/test-payment-on-demo.ts   |   4 +-
 .../integrationtests/test-wallet-cryptoworker.ts   |  13 --
 .../src/integrationtests/test-wallet-dbless.ts     |  11 +-
 packages/taler-harness/src/lint.ts                 |  10 +-
 packages/taler-util/package.json                   |  30 ++-
 packages/taler-util/src/clk.ts                     |  53 ++---
 .../src/{twrpc-impl.missing.ts => compat.d.ts}     |   7 +-
 packages/taler-util/src/compat.node.ts             |  59 +++++
 packages/taler-util/src/compat.qtart.ts            |  53 +++++
 .../src/errors.ts                                  |   0
 .../src/http-common.ts}                            |  39 ++--
 packages/taler-util/src/http-impl.node.d.ts        |  17 ++
 packages/taler-util/src/http-impl.node.ts          | 175 +++++++++++++++
 packages/taler-util/src/http-impl.qtart.ts         | 127 +++++++++++
 .../src/util => taler-util/src}/http.ts            |   8 +-
 packages/taler-util/src/index.browser.ts           |   4 +
 packages/taler-util/src/index.node.ts              |   1 -
 packages/taler-util/src/index.ts                   |   1 +
 packages/taler-util/src/qtart.ts                   |  36 ++++
 packages/taler-util/src/twrpc-impl.missing.ts      |   9 +
 packages/taler-wallet-cli/build.mjs                |   6 +-
 packages/taler-wallet-cli/src/index.ts             | 131 ++++++------
 packages/taler-wallet-core/package.json            |   8 +-
 packages/taler-wallet-core/src/bank-api-client.ts  |   7 +-
 .../src/crypto/workers/crypto-dispatcher.test.ts   |   2 -
 .../src/crypto/workers/crypto-dispatcher.ts        |   2 +-
 .../src/crypto/workers/rpcClient.ts                |  92 --------
 .../src/crypto/workers/synchronousWorkerNode.ts    | 174 ---------------
 .../src/crypto/workers/worker-common.ts            |   2 +-
 packages/taler-wallet-core/src/db.ts               |   3 +
 packages/taler-wallet-core/src/dbless.ts           |   2 +-
 packages/taler-wallet-core/src/dev-experiments.ts  |   2 +-
 .../taler-wallet-core/src/headless/NodeHttpLib.ts  | 183 ----------------
 packages/taler-wallet-core/src/host-common.ts      |  60 ++++++
 .../src/{headless/helpers.ts => host-impl.node.ts} |  78 ++-----
 packages/taler-wallet-core/src/host-impl.qtart.ts  | 120 +++++++++++
 packages/taler-wallet-core/src/host.ts             |  47 ++++
 packages/taler-wallet-core/src/index.node.ts       |  11 +-
 packages/taler-wallet-core/src/index.ts            |   6 +-
 .../taler-wallet-core/src/internal-wallet-state.ts |  13 +-
 .../src/operations/backup/index.ts                 |   4 +-
 .../taler-wallet-core/src/operations/common.ts     |   2 +-
 .../taler-wallet-core/src/operations/deposits.ts   |   6 +-
 .../taler-wallet-core/src/operations/exchanges.ts  |   9 +-
 .../taler-wallet-core/src/operations/merchants.ts  |   2 +-
 .../src/operations/pay-merchant.ts                 |   7 +-
 .../taler-wallet-core/src/operations/pay-peer.ts   |   5 +-
 .../taler-wallet-core/src/operations/recoup.ts     |   2 +-
 .../taler-wallet-core/src/operations/refresh.ts    |   4 +-
 .../taler-wallet-core/src/operations/testing.ts    |   2 +-
 packages/taler-wallet-core/src/operations/tip.ts   |   4 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |   4 +-
 packages/taler-wallet-core/src/remote.ts           |   2 +-
 packages/taler-wallet-core/src/util/retries.ts     |   2 +-
 packages/taler-wallet-core/src/wallet.ts           |   4 +-
 packages/taler-wallet-embedded/build.mjs           |   1 +
 packages/taler-wallet-embedded/src/index.ts        |  28 ++-
 packages/taler-wallet-embedded/src/wallet-qjs.ts   | 237 ++-------------------
 .../src/browserHttpLib.ts                          |  10 +-
 .../src/browserWorkerEntry.ts                      |   7 +-
 .../src/cta/InvoiceCreate/state.ts                 |   2 +-
 .../src/cta/InvoicePay/state.ts                    |   6 +-
 .../src/cta/TransferCreate/state.ts                |   6 +-
 .../src/cta/TransferPickup/state.ts                |   7 +-
 .../src/cta/Withdraw/state.ts                      |   3 +-
 .../src/hooks/useAsyncAsHook.ts                    |   3 +-
 .../src/platform/chrome.ts                         |   3 +-
 .../src/serviceWorkerCryptoWorkerFactory.ts        |   4 +-
 .../src/serviceWorkerHttpLib.ts                    |   4 +-
 packages/taler-wallet-webextension/src/wxApi.ts    |   8 +-
 .../taler-wallet-webextension/src/wxBackend.ts     |   4 +-
 81 files changed, 1051 insertions(+), 1045 deletions(-)

diff --git a/packages/anastasis-core/src/cli.ts 
b/packages/anastasis-core/src/cli.ts
index 517f2876d..df53d6bd0 100644
--- a/packages/anastasis-core/src/cli.ts
+++ b/packages/anastasis-core/src/cli.ts
@@ -1,4 +1,4 @@
-import { clk } from "@gnu-taler/taler-util";
+import { clk } from "@gnu-taler/taler-util/clk";
 import {
   getBackupStartState,
   getRecoveryStartState,
diff --git a/packages/merchant-backoffice-ui/src/utils/regex.test.ts 
b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
index 341a19fb1..d473bd08c 100644
--- a/packages/merchant-backoffice-ui/src/utils/regex.test.ts
+++ b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
@@ -20,7 +20,7 @@
  */
 
 import { expect } from "chai";
-import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js";
+import { AMOUNT_REGEX, PAYTO_REGEX } from "./constants.js";
 
 describe("payto uri format", () => {
   const valids = [
diff --git a/packages/taler-harness/src/bench1.ts 
b/packages/taler-harness/src/bench1.ts
index 84786d25a..0a4118ec1 100644
--- a/packages/taler-harness/src/bench1.ts
+++ b/packages/taler-harness/src/bench1.ts
@@ -19,19 +19,19 @@
  */
 import {
   buildCodecForObject,
+  codecForBoolean,
   codecForNumber,
   codecForString,
-  codecForBoolean,
   codecOptional,
   j2s,
   Logger,
 } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 import {
-  getDefaultNodeWallet2,
-  NodeHttpLib,
-  WalletApiOperation,
-  Wallet,
   AccessStats,
+  createNativeWalletHost2,
+  Wallet,
+  WalletApiOperation,
 } from "@gnu-taler/taler-wallet-core";
 
 /**
@@ -46,8 +46,9 @@ export async function runBench1(configJson: any): 
Promise<void> {
   // Validate the configuration file for this benchmark.
   const b1conf = codecForBench1Config().decode(configJson);
 
-  const myHttpLib = new NodeHttpLib();
-  myHttpLib.setThrottling(false);
+  const myHttpLib = createPlatformHttpLib({
+    enableThrottling: false,
+  });
 
   const numIter = b1conf.iterations ?? 1;
   const numDeposits = b1conf.deposits ?? 5;
@@ -81,7 +82,7 @@ export async function runBench1(configJson: any): 
Promise<void> {
         console.log("wallet DB stats", j2s(getDbStats!()));
       }
 
-      const res = await getDefaultNodeWallet2({
+      const res = await createNativeWalletHost2({
         // No persistent DB storage.
         persistentStoragePath: undefined,
         httpLib: myHttpLib,
diff --git a/packages/taler-harness/src/bench2.ts 
b/packages/taler-harness/src/bench2.ts
index 9fa5d7caf..ff87da52a 100644
--- a/packages/taler-harness/src/bench2.ts
+++ b/packages/taler-harness/src/bench2.ts
@@ -24,6 +24,7 @@ import {
   codecOptional,
   Logger,
 } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 import {
   checkReserve,
   createFakebankReserve,
@@ -31,9 +32,8 @@ import {
   depositCoin,
   downloadExchangeInfo,
   findDenomOrThrow,
-  NodeHttpLib,
   refreshCoin,
-  SynchronousCryptoWorkerFactoryNode,
+  SynchronousCryptoWorkerFactoryPlain,
   withdrawCoin,
 } from "@gnu-taler/taler-wallet-core";
 
@@ -50,12 +50,13 @@ export async function runBench2(configJson: any): 
Promise<void> {
   const benchConf = codecForBench2Config().decode(configJson);
   const curr = benchConf.currency;
   const cryptoDisp = new CryptoDispatcher(
-    new SynchronousCryptoWorkerFactoryNode(),
+    new SynchronousCryptoWorkerFactoryPlain(),
   );
   const cryptoApi = cryptoDisp.cryptoApi;
 
-  const http = new NodeHttpLib();
-  http.setThrottling(false);
+  const http = createPlatformHttpLib({
+    enableThrottling: false,
+  });
 
   const numIter = benchConf.iterations ?? 1;
   const numDeposits = benchConf.deposits ?? 5;
diff --git a/packages/taler-harness/src/bench3.ts 
b/packages/taler-harness/src/bench3.ts
index 9679f05a6..5e8fac0e9 100644
--- a/packages/taler-harness/src/bench3.ts
+++ b/packages/taler-harness/src/bench3.ts
@@ -25,12 +25,12 @@ import {
   j2s,
   Logger,
 } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 import {
-  getDefaultNodeWallet2,
-  NodeHttpLib,
-  WalletApiOperation,
-  Wallet,
   AccessStats,
+  createNativeWalletHost2,
+  Wallet,
+  WalletApiOperation,
 } from "@gnu-taler/taler-wallet-core";
 import benchMerchantIDGenerator from "./benchMerchantIDGenerator.js";
 
@@ -50,8 +50,9 @@ export async function runBench3(configJson: any): 
Promise<void> {
     throw new Error("Payto template url must contain '${id}' placeholder");
   }
 
-  const myHttpLib = new NodeHttpLib();
-  myHttpLib.setThrottling(false);
+  const myHttpLib = createPlatformHttpLib({
+    enableThrottling: false,
+  });
 
   const numIter = b3conf.iterations ?? 1;
   const numDeposits = b3conf.deposits ?? 5;
@@ -89,7 +90,7 @@ export async function runBench3(configJson: any): 
Promise<void> {
         console.log("wallet DB stats", j2s(getDbStats!()));
       }
 
-      const res = await getDefaultNodeWallet2({
+      const res = await createNativeWalletHost2({
         // No persistent DB storage.
         persistentStoragePath: undefined,
         httpLib: myHttpLib,
diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index 0b7ba14cf..518b98d82 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -41,17 +41,15 @@ import {
   MerchantTemplateAddDetails,
   parsePaytoUri,
   stringToBytes,
+  TalerError,
   TalerProtocolDuration,
   WalletNotification,
 } from "@gnu-taler/taler-util";
 import {
-  BankAccessApi,
   BankApi,
   BankServiceHandle,
   HarnessExchangeBankAccount,
-  NodeHttpLib,
   openPromise,
-  TalerError,
   WalletCoreApiClient,
 } from "@gnu-taler/taler-wallet-core";
 import { deepStrictEqual } from "assert";
@@ -83,6 +81,7 @@ import {
   RemoteWallet,
   WalletNotificationWaiter,
 } from "@gnu-taler/taler-wallet-core/remote";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 
 const logger = new Logger("harness.ts");
 
@@ -507,7 +506,7 @@ class LibEuFinBankService extends BankServiceBase 
implements BankServiceHandle {
   sandboxProc: ProcessWrapper | undefined;
   nexusProc: ProcessWrapper | undefined;
 
-  http = new NodeHttpLib();
+  http = createPlatformHttpLib();
 
   static async create(
     gc: GlobalTestState,
@@ -794,7 +793,7 @@ export class FakebankService
 {
   proc: ProcessWrapper | undefined;
 
-  http = new NodeHttpLib();
+  http = createPlatformHttpLib();
 
   // We store "created" accounts during setup and
   // register them after startup.
diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index e4ee25dae..14b8a4302 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -23,7 +23,6 @@ import os from "os";
 import path from "path";
 import {
   Amounts,
-  clk,
   Configuration,
   decodeCrock,
   Logger,
@@ -38,6 +37,7 @@ import { GlobalTestState, runTestWithState } from 
"./harness/harness.js";
 import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
 import { lintExchangeDeployment } from "./lint.js";
 import { runEnvFull } from "./env-full.js";
+import { clk } from "@gnu-taler/taler-util/clk";
 
 const logger = new Logger("taler-harness:index.ts");
 
@@ -74,6 +74,15 @@ const advancedCli = testingCli.subcommand("advancedArgs", 
"advanced", {
   help: "Subcommands for advanced operations (only use if you know what you're 
doing!).",
 });
 
+advancedCli
+  .subcommand("decode", "decode", {
+    help: "Decode base32-crockford.",
+  })
+  .action((args) => {
+    const enc = fs.readFileSync(0, "utf8");
+    console.log(decodeCrock(enc.trim()));
+  });
+
 advancedCli
   .subcommand("bench1", "bench1", {
     help: "Run the 'bench1' benchmark",
@@ -272,7 +281,7 @@ testingCli
     help: "Produce less output.",
   })
   .flag("noTimeout", ["--no-timeout"], {
-    help: "Do not time out tests."
+    help: "Do not time out tests.",
   })
   .action(async (args) => {
     await runTests({
diff --git 
a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts 
b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
index 074126e9f..20285cb6a 100644
--- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
+++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
@@ -24,22 +24,18 @@ import {
   Duration,
   durationFromSpec,
 } from "@gnu-taler/taler-util";
-import {
-  NodeHttpLib,
-  readSuccessResponseJsonOrThrow,
-} from "@gnu-taler/taler-wallet-core";
+import { createPlatformHttpLib, readSuccessResponseJsonOrThrow } from 
"@gnu-taler/taler-util/http";
 import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
 import {
   BankService,
   ExchangeService,
   GlobalTestState,
-  MerchantPrivateApi,
   MerchantService,
   setupDb,
   WalletCli,
   getPayto,
 } from "../harness/harness.js";
-import { startWithdrawViaBank, withdrawViaBank } from "../harness/helpers.js";
+import { withdrawViaBank } from "../harness/helpers.js";
 
 async function applyTimeTravel(
   timetravelDuration: Duration,
@@ -69,7 +65,7 @@ async function applyTimeTravel(
   }
 }
 
-const http = new NodeHttpLib();
+const http = createPlatformHttpLib();
 
 /**
  * Basic time travel test.
diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts 
b/packages/taler-harness/src/integrationtests/test-kyc.ts
index 490673cee..915c3d470 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc.ts
@@ -17,11 +17,14 @@
 /**
  * Imports.
  */
-import { Duration, j2s, NotificationType } from "@gnu-taler/taler-util";
+import {
+  Duration,
+  j2s,
+  NotificationType,
+} from "@gnu-taler/taler-util";
 import {
   BankAccessApi,
   BankApi,
-  NodeHttpLib,
   WalletApiOperation,
 } from "@gnu-taler/taler-wallet-core";
 import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -37,6 +40,7 @@ import {
 } from "../harness/harness.js";
 import { EnvOptions, SimpleTestEnvironmentNg } from "../harness/helpers.js";
 import * as http from "node:http";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 
 export async function createKycTestkudosEnvironment(
   t: GlobalTestState,
@@ -336,12 +340,12 @@ export async function runKycTest(t: GlobalTestState) {
   // We now simulate the user interacting with the KYC service,
   // which would usually done in the browser.
 
-  const httpClient = new NodeHttpLib();
-  const kycServerResp = await httpClient.get(kycNotif.kycUrl);
+  const httpLib = createPlatformHttpLib();
+  const kycServerResp = await httpLib.get(kycNotif.kycUrl);
   const kycLoginResp = await kycServerResp.json();
   console.log("kyc server resp:", j2s(kycLoginResp));
   const kycProofUrl = kycLoginResp.redirect_uri;
-  const proofHttpResp = await httpClient.get(kycProofUrl);
+  const proofHttpResp = await httpLib.get(kycProofUrl);
   console.log("proof resp status", proofHttpResp.status);
   console.log("resp headers", proofHttpResp.headers.toJSON());
 
diff --git 
a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts
 
b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts
index 70edaaf0c..2fafe7584 100644
--- 
a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts
+++ 
b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts
@@ -24,7 +24,8 @@ import {
   encodeCrock,
   getRandomBytes,
 } from "@gnu-taler/taler-util";
-import { NodeHttpLib, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import {
   BankService,
   ExchangeService,
@@ -38,7 +39,7 @@ import {
   withdrawViaBank,
 } from "../harness/helpers.js";
 
-const httpLib = new NodeHttpLib();
+const httpLib = createPlatformHttpLib();
 
 interface Context {
   merchant: MerchantService;
diff --git 
a/packages/taler-harness/src/integrationtests/test-payment-on-demo.ts 
b/packages/taler-harness/src/integrationtests/test-payment-on-demo.ts
index 737620ce7..22e88c8a0 100644
--- a/packages/taler-harness/src/integrationtests/test-payment-on-demo.ts
+++ b/packages/taler-harness/src/integrationtests/test-payment-on-demo.ts
@@ -24,8 +24,8 @@ import {
   BankApi,
   BankAccessApi,
   BankServiceHandle,
-  NodeHttpLib,
 } from "@gnu-taler/taler-wallet-core";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 
 /**
  * Run test for basic, bank-integrated withdrawal and payment.
@@ -35,7 +35,7 @@ export async function runPaymentDemoTest(t: GlobalTestState) {
   let bankInterface: BankServiceHandle = {
     baseUrl: "https://bank.demo.taler.net/";,
     bankAccessApiBaseUrl: "https://bank.demo.taler.net/";,
-    http: new NodeHttpLib(),
+    http: createPlatformHttpLib(),
   };
   let user = await BankApi.createRandomBankUser(bankInterface);
   let wop = await BankAccessApi.createWithdrawalOperation(
diff --git 
a/packages/taler-harness/src/integrationtests/test-wallet-cryptoworker.ts 
b/packages/taler-harness/src/integrationtests/test-wallet-cryptoworker.ts
index a9f1c4d80..6c2006636 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-cryptoworker.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-cryptoworker.ts
@@ -17,23 +17,10 @@
 /**
  * Imports.
  */
-import { j2s } from "@gnu-taler/taler-util";
 import {
-  checkReserve,
-  CryptoDispatcher,
-  depositCoin,
-  downloadExchangeInfo,
-  findDenomOrThrow,
-  NodeHttpLib,
-  refreshCoin,
-  SynchronousCryptoWorkerFactoryNode,
-  TalerError,
-  topupReserveWithDemobank,
   WalletApiOperation,
-  withdrawCoin,
 } from "@gnu-taler/taler-wallet-core";
 import { GlobalTestState, WalletCli } from "../harness/harness.js";
-import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
 
 /**
  * Run test for the different crypto workers.
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts 
b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
index 7692f12b2..08c10fd91 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
@@ -17,17 +17,16 @@
 /**
  * Imports.
  */
-import { j2s } from "@gnu-taler/taler-util";
+import { j2s, TalerError } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 import {
   checkReserve,
   CryptoDispatcher,
   depositCoin,
   downloadExchangeInfo,
   findDenomOrThrow,
-  NodeHttpLib,
   refreshCoin,
-  SynchronousCryptoWorkerFactoryNode,
-  TalerError,
+  SynchronousCryptoWorkerFactoryPlain,
   topupReserveWithDemobank,
   withdrawCoin,
 } from "@gnu-taler/taler-wallet-core";
@@ -42,9 +41,9 @@ export async function runWalletDblessTest(t: GlobalTestState) 
{
 
   const { bank, exchange } = await createSimpleTestkudosEnvironment(t);
 
-  const http = new NodeHttpLib();
+  const http = createPlatformHttpLib();
   const cryptiDisp = new CryptoDispatcher(
-    new SynchronousCryptoWorkerFactoryNode(),
+    new SynchronousCryptoWorkerFactoryPlain(),
   );
   const cryptoApi = cryptiDisp.cryptoApi;
 
diff --git a/packages/taler-harness/src/lint.ts 
b/packages/taler-harness/src/lint.ts
index 49fb9dc86..3d3805e79 100644
--- a/packages/taler-harness/src/lint.ts
+++ b/packages/taler-harness/src/lint.ts
@@ -37,13 +37,13 @@ import {
   Configuration,
   decodeCrock,
 } from "@gnu-taler/taler-util";
-import {
-  NodeHttpLib,
-  readSuccessResponseJsonOrThrow,
-} from "@gnu-taler/taler-wallet-core";
 import { URL } from "url";
 import { spawn } from "child_process";
 import { delayMs } from "./harness/harness.js";
+import {
+  createPlatformHttpLib,
+  readSuccessResponseJsonOrThrow,
+} from "@gnu-taler/taler-util/http";
 
 interface BasicConf {
   mainCurrency: string;
@@ -53,7 +53,7 @@ interface PubkeyConf {
   masterPublicKey: string;
 }
 
-const httpLib = new NodeHttpLib();
+const httpLib = createPlatformHttpLib();
 
 interface ShellResult {
   stdout: string;
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json
index 23ff5dcfa..9cf47d256 100644
--- a/packages/taler-util/package.json
+++ b/packages/taler-util/package.json
@@ -15,12 +15,38 @@
     },
     "./twrpc": {
       "default": "./lib/twrpc.js"
+    },
+    "./compat": {
+      "types": "./lib/compat.node.js",
+      "node": "./lib/compat.node.js",
+      "qtart": "./lib/compat.qtart.js",
+      "default": "./lib/not-implemented.js"
+    },
+    "./clk": {
+      "default": "./lib/clk.js"
+    },
+    "./http": {
+      "default": "./lib/http.js"
+    },
+    "./qtart": {
+      "types": "./lib/qtart.js",
+      "qtart": "./lib/qtart.js",
+      "default": "./lib/not-implemented.js"
     }
   },
   "imports": {
     "#twrpc-impl": {
-      "node": "./lib/twrpc-impl.node.js",
-      "default": "./lib/twrpc-impl.missing.js"
+      "node": "./lib/twrpc-impl.node.js"
+    },
+    "#compat-impl": {
+      "node": "./lib/compat.node.js",
+      "qtart": "./lib/compat.qtart.js",
+      "type": "./lib/compat.d.ts"
+    },
+    "#http-impl": {
+      "type": "./lib/http-impl.node.js",
+      "node": "./lib/http-impl.node.js",
+      "qtart": "./lib/http-impl.qtart.js"
     }
   },
   "scripts": {
diff --git a/packages/taler-util/src/clk.ts b/packages/taler-util/src/clk.ts
index e99ebf733..7bcd19b04 100644
--- a/packages/taler-util/src/clk.ts
+++ b/packages/taler-util/src/clk.ts
@@ -17,10 +17,12 @@
 /**
  * Imports.
  */
-import process from "process";
-import path from "path";
-import readline from "readline";
-import { devNull } from "os";
+import {
+  processExit,
+  processArgv,
+  readlinePrompt,
+  pathBasename,
+} from "#compat-impl";
 
 export namespace clk {
   class Converter<T> {}
@@ -359,13 +361,13 @@ export namespace clk {
               console.error(
                 `error: unknown option '--${r.key}' for ${currentName}`,
               );
-              process.exit(-1);
+              processExit(-1);
               throw Error("not reached");
             }
             if (d.isFlag) {
               if (r.value !== undefined) {
                 console.error(`error: flag '--${r.key}' does not take a 
value`);
-                process.exit(-1);
+                processExit(-1);
                 throw Error("not reached");
               }
               storeFlag(d, true);
@@ -373,7 +375,7 @@ export namespace clk {
               if (r.value === undefined) {
                 if (i === unparsedArgs.length - 1) {
                   console.error(`error: option '--${r.key}' needs an 
argument`);
-                  process.exit(-1);
+                  processExit(-1);
                   throw Error("not reached");
                 }
                 storeOption(d, unparsedArgs[i + 1]);
@@ -391,7 +393,7 @@ export namespace clk {
               const opt = this.shortOptions[chr];
               if (!opt) {
                 console.error(`error: option '-${chr}' not known`);
-                process.exit(-1);
+                processExit(-1);
               }
               if (opt.isFlag) {
                 storeFlag(opt, true);
@@ -399,7 +401,7 @@ export namespace clk {
                 if (si == optShort.length - 1) {
                   if (i === unparsedArgs.length - 1) {
                     console.error(`error: option '-${chr}' needs an argument`);
-                    process.exit(-1);
+                    processExit(-1);
                     throw Error("not reached");
                   } else {
                     storeOption(opt, unparsedArgs[i + 1]);
@@ -418,7 +420,7 @@ export namespace clk {
           const subcmd = this.subcommandMap[argVal];
           if (!subcmd) {
             console.error(`error: unknown command '${argVal}'`);
-            process.exit(-1);
+            processExit(-1);
             throw Error("not reached");
           }
           foundSubcommand = subcmd.commandGroup;
@@ -427,7 +429,7 @@ export namespace clk {
           const d = this.arguments[posArgIndex];
           if (!d) {
             console.error(`error: too many arguments for ${currentName}`);
-            process.exit(-1);
+            processExit(-1);
             throw Error("not reached");
           }
           myArgs[d.name] = unparsedArgs[i];
@@ -437,7 +439,7 @@ export namespace clk {
 
       if (parsedArgs[this.argKey].help) {
         this.printHelp(progname, parents);
-        process.exit(0);
+        processExit(0);
         throw Error("not reached");
       }
 
@@ -450,7 +452,7 @@ export namespace clk {
             console.error(
               `error: missing positional argument '${d.name}' for 
${currentName}`,
             );
-            process.exit(-1);
+            processExit(-1);
             throw Error("not reached");
           }
         }
@@ -464,7 +466,7 @@ export namespace clk {
             } else {
               const name = option.flagspec.join(",");
               console.error(`error: missing option '${name}'`);
-              process.exit(-1);
+              processExit(-1);
               throw Error("not reached");
             }
           }
@@ -492,16 +494,16 @@ export namespace clk {
         } catch (e) {
           console.error(`An error occurred while running ${currentName}`);
           console.error(e);
-          process.exit(1);
+          processExit(1);
         }
         Promise.resolve(r).catch((e) => {
           console.error(`An error occurred while running ${currentName}`);
           console.error(e);
-          process.exit(1);
+          processExit(1);
         });
       } else {
         this.printHelp(progname, parents);
-        process.exit(-1);
+        processExit(-1);
         throw Error("not reached");
       }
     }
@@ -524,15 +526,15 @@ export namespace clk {
       if (cmdlineArgs) {
         args = cmdlineArgs;
       } else {
-        args = process.argv.slice(1);
+        args = processArgv().slice(1);
       }
       if (args.length < 1) {
         console.error(
           "Error while parsing command line arguments: not enough arguments",
         );
-        process.exit(-1);
+        processExit(-1);
       }
-      const progname = path.basename(args[0]);
+      const progname = pathBasename(args[0]);
       const rest = args.slice(1);
 
       this.mainCommand.run(progname, [], rest, {});
@@ -622,15 +624,6 @@ export namespace clk {
   }
 
   export function prompt(question: string): Promise<string> {
-    const stdinReadline = readline.createInterface({
-      input: process.stdin,
-      output: process.stdout,
-    });
-    return new Promise<string>((resolve, reject) => {
-      stdinReadline.question(question, (res) => {
-        resolve(res);
-        stdinReadline.close();
-      });
-    });
+    return readlinePrompt(question);
   }
 }
diff --git a/packages/taler-util/src/twrpc-impl.missing.ts 
b/packages/taler-util/src/compat.d.ts
similarity index 67%
copy from packages/taler-util/src/twrpc-impl.missing.ts
copy to packages/taler-util/src/compat.d.ts
index d9ed37815..12ba31124 100644
--- a/packages/taler-util/src/twrpc-impl.missing.ts
+++ b/packages/taler-util/src/compat.d.ts
@@ -14,4 +14,9 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-// Not implemented.
+export function processExit(status: number): never;
+export function processArgv(): string[];
+export function readlinePrompt(prompt: string): Promise<string>;
+export function pathBasename(s: string): string;
+export function setUnhandledRejectionHandler(h: (e: any) => void): void;
+export function getenv(name: string): string | undefined;
\ No newline at end of file
diff --git a/packages/taler-util/src/compat.node.ts 
b/packages/taler-util/src/compat.node.ts
new file mode 100644
index 000000000..ed27a7acd
--- /dev/null
+++ b/packages/taler-util/src/compat.node.ts
@@ -0,0 +1,59 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 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/>
+ */
+
+import process from "node:process";
+import readline from "node:readline";
+import path from "node:path";
+import os from "node:os";
+
+export function processExit(status: number): never {
+  process.exit(1);
+}
+
+export function processArgv(): string[] {
+  return [...process.argv];
+}
+
+export function readlinePrompt(prompt: string): Promise<string> {
+  const stdinReadline = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+  });
+  return new Promise<string>((resolve, reject) => {
+    stdinReadline.question(prompt, (res) => {
+      resolve(res);
+      stdinReadline.close();
+    });
+  });
+}
+
+export function pathBasename(p: string): string {
+  return path.basename(p);
+}
+
+export function pathHomedir(): string {
+  return os.homedir();
+}
+
+export function setUnhandledRejectionHandler(h: (e: any) => void): void {
+  process.on("unhandledRejection", (e) => {
+    h(e);
+  });
+}
+
+export function getenv(name: string): string | undefined {
+  return process.env[name];
+}
diff --git a/packages/taler-util/src/compat.qtart.ts 
b/packages/taler-util/src/compat.qtart.ts
new file mode 100644
index 000000000..f8b336b11
--- /dev/null
+++ b/packages/taler-util/src/compat.qtart.ts
@@ -0,0 +1,53 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 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/>
+ */
+
+// qtart "std" library
+// @ts-ignore
+import * as std from "std";
+
+export function processExit(status: number): never {
+  std.exit(status);
+  throw Error("not reached");
+}
+
+export function processArgv(): string[] {
+  // @ts-ignore
+  return ["qtart", ...globalThis.scriptArgs];
+}
+
+export function readlinePrompt(prompt: string): Promise<string> {
+  throw new Error("not supported");
+}
+
+export function pathBasename(p: string): string {
+  const slashIndex = p.lastIndexOf("/");
+  if (slashIndex < 0) {
+    return p;
+  }
+  return p.substring(0, slashIndex);
+}
+
+export function pathHomedir(): string {
+  return std.getenv("HOME");
+}
+
+export function setUnhandledRejectionHandler(h: (e: any) => void): void {
+  // not supported
+}
+
+export function getenv(name: string): string | undefined {
+  return std.getenv(name);
+}
diff --git a/packages/taler-wallet-core/src/errors.ts 
b/packages/taler-util/src/errors.ts
similarity index 100%
rename from packages/taler-wallet-core/src/errors.ts
rename to packages/taler-util/src/errors.ts
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts 
b/packages/taler-util/src/http-common.ts
similarity index 50%
rename from 
packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts
rename to packages/taler-util/src/http-common.ts
index 90f9a43fa..eeb335ba7 100644
--- 
a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2023 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
@@ -12,25 +12,28 @@
 
  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/>
- */
 
-/**
- * Imports.
- */
-import { CryptoWorkerFactory } from "./crypto-dispatcher.js";
-import { CryptoWorker } from "./cryptoWorkerInterface.js";
-import { SynchronousCryptoWorkerNode } from "./synchronousWorkerNode.js";
+ SPDX-License-Identifier: AGPL3.0-or-later
+*/
 
-/**
- * The synchronous crypto worker produced by this factory doesn't run in the
- * background, but actually blocks the caller until the operation is done.
- */
-export class SynchronousCryptoWorkerFactoryNode implements CryptoWorkerFactory 
{
-  startWorker(): CryptoWorker {
-    return new SynchronousCryptoWorkerNode();
-  }
+const textEncoder = new TextEncoder();
+
+export interface HttpLibArgs {
+  enableThrottling?: boolean,
+}
 
-  getConcurrency(): number {
-    return 1;
+export function encodeBody(body: any): ArrayBuffer {
+  if (body == null) {
+    return new ArrayBuffer(0);
+  }
+  if (typeof body === "string") {
+    return textEncoder.encode(body).buffer;
+  } else if (ArrayBuffer.isView(body)) {
+    return body.buffer;
+  } else if (body instanceof ArrayBuffer) {
+    return body;
+  } else if (typeof body === "object") {
+    return textEncoder.encode(JSON.stringify(body)).buffer;
   }
+  throw new TypeError("unsupported request body type");
 }
diff --git a/packages/taler-util/src/http-impl.node.d.ts 
b/packages/taler-util/src/http-impl.node.d.ts
new file mode 100644
index 000000000..b0fba9b30
--- /dev/null
+++ b/packages/taler-util/src/http-impl.node.d.ts
@@ -0,0 +1,17 @@
+import { HttpLibArgs } from "./http-common.js";
+import { HttpRequestLibrary, HttpRequestOptions, HttpResponse } from 
"./http.js";
+/**
+ * Implementation of the HTTP request library interface for node.
+ */
+export declare class HttpLibImpl implements HttpRequestLibrary {
+    private throttle;
+    private throttlingEnabled;
+    constructor(args?: HttpLibArgs);
+    /**
+     * Set whether requests should be throttled.
+     */
+    setThrottling(enabled: boolean): void;
+    fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse>;
+    get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse>;
+    postJson(url: string, body: any, opt?: HttpRequestOptions): 
Promise<HttpResponse>;
+}
diff --git a/packages/taler-util/src/http-impl.node.ts 
b/packages/taler-util/src/http-impl.node.ts
new file mode 100644
index 000000000..5f2b3ac8a
--- /dev/null
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -0,0 +1,175 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+*/
+
+/**
+ * Imports.
+ */
+import * as http from "node:http";
+import { RequestOptions } from "node:http";
+import { TalerError } from "./errors.js";
+import { encodeBody, HttpLibArgs } from "./http-common.js";
+import {
+  DEFAULT_REQUEST_TIMEOUT_MS,
+  Headers,
+  HttpRequestLibrary,
+  HttpRequestOptions,
+  HttpResponse,
+} from "./http.js";
+import {
+  Logger,
+  RequestThrottler,
+  TalerErrorCode,
+  typedArrayConcat,
+  URL,
+} from "./index.js";
+
+const logger = new Logger("http-impl.node.ts");
+
+const textDecoder = new TextDecoder();
+
+/**
+ * Implementation of the HTTP request library interface for node.
+ */
+export class HttpLibImpl implements HttpRequestLibrary {
+  private throttle = new RequestThrottler();
+  private throttlingEnabled = true;
+
+  constructor(args?: HttpLibArgs) {
+    this.throttlingEnabled = args?.enableThrottling ?? false;
+  }
+
+  /**
+   * Set whether requests should be throttled.
+   */
+  setThrottling(enabled: boolean): void {
+    this.throttlingEnabled = enabled;
+  }
+
+  async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
+    const method = opt?.method?.toUpperCase() ?? "GET";
+    let body = opt?.body;
+
+    logger.trace(`Requesting ${method} ${url}`);
+
+    const parsedUrl = new URL(url);
+    if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
+        {
+          requestMethod: method,
+          requestUrl: url,
+          throttleStats: this.throttle.getThrottleStats(url),
+        },
+        `request to origin ${parsedUrl.origin} was throttled`,
+      );
+    }
+    let timeoutMs: number | undefined;
+    if (typeof opt?.timeout?.d_ms === "number") {
+      timeoutMs = opt.timeout.d_ms;
+    } else {
+      timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
+    }
+
+    const headers = { ...opt?.headers };
+    headers["Content-Type"] = "application/json";
+
+    let reqBody: ArrayBuffer | undefined;
+
+    if (opt?.method == "POST") {
+      reqBody = encodeBody(opt.body);
+    }
+
+    const options: RequestOptions = {
+      protocol: parsedUrl.protocol,
+      port: parsedUrl.port,
+      host: parsedUrl.host,
+      method: method,
+      path: parsedUrl.pathname,
+      headers: opt?.headers,
+    };
+
+    const chunks: Uint8Array[] = [];
+
+    return new Promise((resolve, reject) => {
+      const req = http.request(options, (res) => {
+        res.on("data", (d) => {
+          chunks.push(d);
+        });
+        res.on("end", () => {
+          const headers: Headers = new Headers();
+          for (const [k, v] of Object.entries(res.headers)) {
+            if (!v) {
+              continue;
+            }
+            if (typeof v === "string") {
+              headers.set(k, v);
+            } else {
+              headers.set(k, v.join(", "));
+            }
+          }
+          const data = typedArrayConcat(chunks);
+          const resp: HttpResponse = {
+            requestMethod: method,
+            requestUrl: parsedUrl.href,
+            status: res.statusCode || 0,
+            headers,
+            async bytes() {
+              return data;
+            },
+            json() {
+              const text = textDecoder.decode(data);
+              return JSON.parse(text);
+            },
+            async text() {
+              const text = textDecoder.decode(data);
+              return text;
+            },
+          };
+          resolve(resp);
+        });
+        res.on("error", (e) => {
+          reject(e);
+        });
+      });
+
+      if (reqBody) {
+        req.write(reqBody);
+      }
+      req.end();
+    });
+  }
+
+  async get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
+    return this.fetch(url, {
+      method: "GET",
+      ...opt,
+    });
+  }
+
+  async postJson(
+    url: string,
+    body: any,
+    opt?: HttpRequestOptions,
+  ): Promise<HttpResponse> {
+    return this.fetch(url, {
+      method: "POST",
+      body,
+      ...opt,
+    });
+  }
+}
diff --git a/packages/taler-util/src/http-impl.qtart.ts 
b/packages/taler-util/src/http-impl.qtart.ts
new file mode 100644
index 000000000..954b41802
--- /dev/null
+++ b/packages/taler-util/src/http-impl.qtart.ts
@@ -0,0 +1,127 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+*/
+
+/**
+ * Imports.
+ */
+import { Logger } from "@gnu-taler/taler-util";
+import { TalerError } from "./errors.js";
+import { encodeBody, HttpLibArgs } from "./http-common.js";
+import {
+  Headers,
+  HttpRequestLibrary,
+  HttpRequestOptions,
+  HttpResponse,
+} from "./http.js";
+import { RequestThrottler, TalerErrorCode, URL } from "./index.js";
+import { qjsOs } from "./qtart.js";
+
+const logger = new Logger("http-impl.qtart.ts");
+
+const textDecoder = new TextDecoder();
+
+/**
+ * Implementation of the HTTP request library interface for node.
+ */
+export class HttpLibImpl implements HttpRequestLibrary {
+  private throttle = new RequestThrottler();
+  private throttlingEnabled = true;
+
+  constructor(args?: HttpLibArgs) {
+    this.throttlingEnabled = args?.enableThrottling ?? false;
+  }
+
+  /**
+   * Set whether requests should be throttled.
+   */
+  setThrottling(enabled: boolean): void {
+    this.throttlingEnabled = enabled;
+  }
+
+  async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
+    const method = opt?.method ?? "GET";
+
+    logger.trace(`Requesting ${method} ${url}`);
+
+    const parsedUrl = new URL(url);
+    if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
+        {
+          requestMethod: method,
+          requestUrl: url,
+          throttleStats: this.throttle.getThrottleStats(url),
+        },
+        `request to origin ${parsedUrl.origin} was throttled`,
+      );
+    }
+
+    let data: ArrayBuffer | undefined = undefined;
+    let headers: string[] = [];
+    if (opt?.headers) {
+      for (let headerName of Object.keys(opt.headers)) {
+        headers.push(`${headerName}: ${opt.headers[headerName]}`);
+      }
+    }
+    if (method.toUpperCase() === "POST") {
+      data = encodeBody(opt?.body);
+    }
+    const res = await qjsOs.fetchHttp(url, {
+      method,
+      data,
+      headers,
+    });
+    return {
+      requestMethod: method,
+      // FIXME: We don't return headers!
+      headers: new Headers(),
+      async bytes() {
+        return res.data;
+      },
+      json() {
+        const text = textDecoder.decode(res.data);
+        return JSON.parse(text);
+      },
+      async text() {
+        const text = textDecoder.decode(res.data);
+        return text;
+      },
+      requestUrl: url,
+      status: res.status,
+    };
+  }
+
+  async get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
+    return this.fetch(url, {
+      method: "GET",
+      ...opt,
+    });
+  }
+
+  async postJson(
+    url: string,
+    body: any,
+    opt?: HttpRequestOptions,
+  ): Promise<HttpResponse> {
+    return this.fetch(url, {
+      method: "POST",
+      body,
+      ...opt,
+    });
+  }
+}
diff --git a/packages/taler-wallet-core/src/util/http.ts 
b/packages/taler-util/src/http.ts
similarity index 97%
rename from packages/taler-wallet-core/src/util/http.ts
rename to packages/taler-util/src/http.ts
index 1da31a315..fd594b655 100644
--- a/packages/taler-wallet-core/src/util/http.ts
+++ b/packages/taler-util/src/http.ts
@@ -34,7 +34,9 @@ import {
   CancellationToken,
 } from "@gnu-taler/taler-util";
 import { TalerErrorCode } from "@gnu-taler/taler-util";
-import { makeErrorDetail, TalerError } from "../errors.js";
+import { makeErrorDetail, TalerError } from "./errors.js";
+import * as impl from "#http-impl";
+import { HttpLibArgs } from "./http-common.js";
 
 const logger = new Logger("http.ts");
 
@@ -352,3 +354,7 @@ export function getExpiry(
   }
   return t;
 }
+
+export function createPlatformHttpLib(args?: HttpLibArgs): HttpRequestLibrary {
+  return new impl.HttpLibImpl(args);
+}
diff --git a/packages/taler-util/src/index.browser.ts 
b/packages/taler-util/src/index.browser.ts
index 3b8e194b3..2a600644d 100644
--- a/packages/taler-util/src/index.browser.ts
+++ b/packages/taler-util/src/index.browser.ts
@@ -19,3 +19,7 @@
 import { loadBrowserPrng } from "./prng-browser.js";
 loadBrowserPrng();
 export * from "./index.js";
+
+// The web stuff doesn't support package.json export declarations yet,
+// so we export more stuff here than we should.
+export * from "./http.js";
diff --git a/packages/taler-util/src/index.node.ts 
b/packages/taler-util/src/index.node.ts
index bd59f320a..018b4767f 100644
--- a/packages/taler-util/src/index.node.ts
+++ b/packages/taler-util/src/index.node.ts
@@ -21,4 +21,3 @@ initNodePrng();
 export * from "./index.js";
 export * from "./talerconfig.js";
 export * from "./globbing/minimatch.js";
-export { clk } from "./clk.js";
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 661b0332f..cf4f545a4 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -36,3 +36,4 @@ export * from "./CancellationToken.js";
 export * from "./contract-terms.js";
 export * from "./base64.js";
 export * from "./merchant-api-types.js";
+export * from "./errors.js";
diff --git a/packages/taler-util/src/qtart.ts b/packages/taler-util/src/qtart.ts
new file mode 100644
index 000000000..f8edf234e
--- /dev/null
+++ b/packages/taler-util/src/qtart.ts
@@ -0,0 +1,36 @@
+
+// @ts-ignore
+import * as _qjsOsImp from "os";
+// @ts-ignore
+import * as _qjsStdImp from "std";
+
+
+export interface QjsHttpResp {
+  status: number;
+  data: ArrayBuffer;
+}
+
+export interface QjsHttpOptions {
+  method: string;
+  debug?: boolean;
+  data?: ArrayBuffer;
+  headers?: string[];
+}
+
+
+export interface QjsOsLib {
+  fetchHttp(url: string, options?: QjsHttpOptions): Promise<QjsHttpResp>;
+  postMessageToHost(s: string): void;
+  setMessageFromHostHandler(h: (s: string) => void): void;
+  rename(oldPath: string, newPath: string): number;
+}
+
+export interface QjsStdLib {
+  writeFile(filename: string, contents: string): void;
+  loadFile(filename: string): string;
+}
+
+// This is not the nodejs "os" module, but the qjs "os" module.
+export const qjsOs: QjsOsLib = _qjsOsImp as any;
+
+export const qjsStd: QjsStdLib = _qjsStdImp as any;
\ No newline at end of file
diff --git a/packages/taler-util/src/twrpc-impl.missing.ts 
b/packages/taler-util/src/twrpc-impl.missing.ts
index d9ed37815..7d7fa84ae 100644
--- a/packages/taler-util/src/twrpc-impl.missing.ts
+++ b/packages/taler-util/src/twrpc-impl.missing.ts
@@ -14,4 +14,13 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+import type { RpcConnectArgs, RpcServerArgs } from "./twrpc.js";
+
 // Not implemented.
+export async function connectRpc<T>(args: RpcConnectArgs<T>): Promise<T> {
+  throw Error("not implemented");
+}
+
+export async function runRpcServer(args: RpcServerArgs): Promise<void> {
+  throw Error("not implemented");
+}
diff --git a/packages/taler-wallet-cli/build.mjs 
b/packages/taler-wallet-cli/build.mjs
index 14b626815..b2ed2c937 100755
--- a/packages/taler-wallet-cli/build.mjs
+++ b/packages/taler-wallet-cli/build.mjs
@@ -53,7 +53,7 @@ function git_hash() {
 
 export const buildConfig = {
   entryPoints: ["src/index.ts"],
-  outfile: "dist/taler-wallet-cli.mjs",
+  outfile: "dist/taler-wallet-cli.qtart.mjs",
   bundle: true,
   minify: false,
   target: [
@@ -61,7 +61,11 @@ export const buildConfig = {
   ],
   format: 'esm',
   platform: 'neutral',
+  mainFields: ["module", "main"],
+  conditions: ["qtart"],
   sourcemap: true,
+  // quickjs standard library
+  external: ["std", "os"],
   define: {
     '__VERSION__': `"${_package.version}"`,
     '__GIT_HASH__': `"${GIT_HASH}"`,
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index 228395991..aed9a24c0 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -21,12 +21,11 @@ import {
   addPaytoQueryParams,
   AgeRestriction,
   classifyTalerUri,
-  clk,
   codecForList,
   codecForString,
   CoreApiResponse,
-  decodeCrock,
   encodeCrock,
+  getErrorDetailFromException,
   getRandomBytes,
   j2s,
   Logger,
@@ -35,20 +34,26 @@ import {
   RecoveryMergeStrategy,
   setDangerousTimetravel,
   setGlobalLogLevelFromString,
+  summarizeTalerErrorDetail,
   TalerUriType,
   WalletNotification,
 } from "@gnu-taler/taler-util";
+import { clk } from "@gnu-taler/taler-util/clk";
+import {
+  getenv,
+  pathHomedir,
+  processExit,
+  setUnhandledRejectionHandler,
+} from "@gnu-taler/taler-util/compat";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc";
 import {
+  createNativeWalletHost,
+  createNativeWalletHost2,
   CryptoDispatcher,
-  getDefaultNodeWallet,
-  getDefaultNodeWallet2,
-  getErrorDetailFromException,
   nativeCrypto,
-  NodeHttpLib,
-  NodeThreadCryptoWorkerFactory,
-  summarizeTalerErrorDetail,
-  SynchronousCryptoWorkerFactoryNode,
+  //NodeThreadCryptoWorkerFactory,
+  //SynchronousCryptoWorkerFactoryPlain,
   TalerCryptoInterface,
   Wallet,
   WalletApiOperation,
@@ -60,8 +65,6 @@ import {
   getClientFromRemoteWallet,
   makeNotificationWaiter,
 } from "@gnu-taler/taler-wallet-core/remote";
-import fs from "fs";
-import os from "os";
 
 // This module also serves as the entry point for the crypto
 // thread worker, and thus must expose these two handlers.
@@ -76,13 +79,13 @@ const EXIT_EXCEPTION = 4;
 const EXIT_API_ERROR = 5;
 const EXIT_RETRIES_EXCEEDED = 6;
 
-process.on("unhandledRejection", (error: any) => {
+setUnhandledRejectionHandler((error: any) => {
   logger.error("unhandledRejection", error.message);
   logger.error("stack", error.stack);
-  process.exit(2);
+  processExit(1);
 });
 
-const defaultWalletDbPath = os.homedir + "/" + ".talerwalletdb.json";
+const defaultWalletDbPath = pathHomedir() + "/" + ".talerwalletdb.json";
 
 function assertUnreachable(x: never): never {
   throw new Error("Didn't expect to get here");
@@ -99,7 +102,7 @@ async function doPay(
   if (result.status === PreparePayResultType.InsufficientBalance) {
     console.log("contract", result.contractTerms);
     console.error("insufficient balance");
-    process.exit(1);
+    processExit(1);
     return;
   }
   if (result.status === PreparePayResultType.AlreadyConfirmed) {
@@ -108,8 +111,7 @@ async function doPay(
     } else {
       console.log("payment already in progress");
     }
-
-    process.exit(0);
+    processExit(0);
     return;
   }
   if (result.status === "payment-possible") {
@@ -154,7 +156,7 @@ function applyVerbose(verbose: boolean): void {
 declare const __VERSION__: string;
 function printVersion(): void {
   console.log(__VERSION__);
-  process.exit(0);
+  processExit(0);
 }
 
 export const walletCli = clk
@@ -203,7 +205,7 @@ export const walletCli = clk
 type WalletCliArgsType = clk.GetArgType<typeof walletCli>;
 
 function checkEnvFlag(name: string): boolean {
-  const val = process.env[name];
+  const val = getenv(name);
   if (val == "1") {
     return true;
   }
@@ -238,11 +240,10 @@ async function createLocalWallet(
   notificationHandler?: (n: WalletNotification) => void,
 ): Promise<Wallet> {
   const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath;
-  const myHttpLib = new NodeHttpLib();
-  if (walletCliArgs.wallet.noThrottle) {
-    myHttpLib.setThrottling(false);
-  }
-  const wallet = await getDefaultNodeWallet({
+  const myHttpLib = createPlatformHttpLib({
+    enableThrottling: walletCliArgs.wallet.noThrottle ? false : true,
+  });
+  const wallet = await createNativeWalletHost({
     persistentStoragePath: dbPath !== ":memory:" ? dbPath : undefined,
     httpLib: myHttpLib,
     notifyHandler: (n) => {
@@ -268,7 +269,7 @@ async function createLocalWallet(
     const ed = getErrorDetailFromException(e);
     console.error("Operation failed: " + summarizeTalerErrorDetail(ed));
     console.error("Error details:", JSON.stringify(ed, undefined, 2));
-    process.exit(1);
+    processExit(1);
   } finally {
     logger.trace("operation with wallet finished, stopping");
     logger.trace("stopped wallet");
@@ -357,7 +358,7 @@ walletCli
         requestJson = JSON.parse(args.api.request);
       } catch (e) {
         console.error("Invalid JSON");
-        process.exit(1);
+        processExit(1);
       }
       try {
         const resp = await wallet.makeCoreApiRequest(
@@ -367,12 +368,12 @@ walletCli
         console.log(JSON.stringify(resp, undefined, 2));
         if (resp.type === "error") {
           if (args.api.expectSuccess) {
-            process.exit(EXIT_API_ERROR);
+            processExit(EXIT_API_ERROR);
           }
         }
       } catch (e) {
         logger.error(`Got exception while handling API request ${e}`);
-        process.exit(EXIT_EXCEPTION);
+        processExit(EXIT_EXCEPTION);
       }
     });
     logger.info("finished handling API request");
@@ -475,7 +476,7 @@ walletCli
       });
       wallet.ws.stop();
       if (resp.retriesExceeded && args.finishPendingOpt.failOnMaxRetries) {
-        process.exit(EXIT_RETRIES_EXCEEDED);
+        processExit(EXIT_RETRIES_EXCEEDED);
       }
     });
   });
@@ -594,7 +595,7 @@ walletCli
             const selectedExchange = withdrawInfo.defaultExchangeBaseUrl;
             if (!selectedExchange) {
               console.error("no suggested exchange!");
-              process.exit(1);
+              processExit(1);
               return;
             }
             const res = await wallet.client.call(
@@ -1014,9 +1015,10 @@ advancedCli
     help: "Run the 'bench-internal' benchmark",
   })
   .action(async (args) => {
-    const myHttpLib = new NodeHttpLib();
-    myHttpLib.setThrottling(false);
-    const res = await getDefaultNodeWallet2({
+    const myHttpLib = createPlatformHttpLib({
+      enableThrottling: false,
+    });
+    const res = await createNativeWalletHost2({
       // No persistent DB storage.
       persistentStoragePath: undefined,
       httpLib: myHttpLib,
@@ -1060,15 +1062,6 @@ advancedCli
     });
   });
 
-advancedCli
-  .subcommand("decode", "decode", {
-    help: "Decode base32-crockford.",
-  })
-  .action((args) => {
-    const enc = fs.readFileSync(0, "utf8");
-    console.log(decodeCrock(enc.trim()));
-  });
-
 advancedCli
   .subcommand("genSegwit", "gen-segwit")
   .requiredArgument("paytoUri", clk.STRING)
@@ -1229,7 +1222,7 @@ advancedCli
         );
       } catch (e: any) {
         console.log("could not parse coin list:", e.message);
-        process.exit(1);
+        processExit(1);
       }
       for (const c of coinPubList) {
         await wallet.client.call(WalletApiOperation.SetCoinSuspended, {
@@ -1254,7 +1247,7 @@ advancedCli
         );
       } catch (e: any) {
         console.log("could not parse coin list:", e.message);
-        process.exit(1);
+        processExit(1);
       }
       for (const c of coinPubList) {
         await wallet.client.call(WalletApiOperation.SetCoinSuspended, {
@@ -1420,33 +1413,33 @@ async function read(stream: NodeJS.ReadStream) {
   return Buffer.concat(chunks).toString("utf8");
 }
 
-testCli
-  .subcommand("cryptoworker", "cryptoworker")
-  .maybeOption("impl", ["--impl"], clk.STRING)
-  .action(async (args) => {
-    let cryptoApi: TalerCryptoInterface;
-    if (!args.cryptoworker.impl || args.cryptoworker.impl === "node") {
-      const workerFactory = new NodeThreadCryptoWorkerFactory();
-      const cryptoDisp = new CryptoDispatcher(workerFactory);
-      cryptoApi = cryptoDisp.cryptoApi;
-    } else if (args.cryptoworker.impl === "sync") {
-      const workerFactory = new SynchronousCryptoWorkerFactoryNode();
-      const cryptoDisp = new CryptoDispatcher(workerFactory);
-      cryptoApi = cryptoDisp.cryptoApi;
-    } else if (args.cryptoworker.impl === "none") {
-      cryptoApi = nativeCrypto;
-    } else {
-      throw Error(`invalid crypto worker type ${args.cryptoworker.impl}`);
-    }
-
-    const input = "foo";
-    console.log(`testing crypto worker by hashing string '${input}'`);
-    const res = await cryptoApi.hashString({ str: input });
-    console.log(res);
-  });
+// testCli
+//   .subcommand("cryptoworker", "cryptoworker")
+//   .maybeOption("impl", ["--impl"], clk.STRING)
+//   .action(async (args) => {
+//     let cryptoApi: TalerCryptoInterface;
+//     if (!args.cryptoworker.impl || args.cryptoworker.impl === "node") {
+//       const workerFactory = new NodeThreadCryptoWorkerFactory();
+//       const cryptoDisp = new CryptoDispatcher(workerFactory);
+//       cryptoApi = cryptoDisp.cryptoApi;
+//     } else if (args.cryptoworker.impl === "sync") {
+//       const workerFactory = new SynchronousCryptoWorkerFactoryPlain();
+//       const cryptoDisp = new CryptoDispatcher(workerFactory);
+//       cryptoApi = cryptoDisp.cryptoApi;
+//     } else if (args.cryptoworker.impl === "none") {
+//       cryptoApi = nativeCrypto;
+//     } else {
+//       throw Error(`invalid crypto worker type ${args.cryptoworker.impl}`);
+//     }
+
+//     const input = "foo";
+//     console.log(`testing crypto worker by hashing string '${input}'`);
+//     const res = await cryptoApi.hashString({ str: input });
+//     console.log(res);
+//   });
 
 export function main() {
-  if (process.env["TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE"]) {
+  if (getenv("TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE")) {
     logger.warn("Allowing withdrawal of late denominations for debugging");
     walletCoreDebugFlags.denomselAllowLate = true;
   }
diff --git a/packages/taler-wallet-core/package.json 
b/packages/taler-wallet-core/package.json
index 4f1692872..72b4eb410 100644
--- a/packages/taler-wallet-core/package.json
+++ b/packages/taler-wallet-core/package.json
@@ -38,7 +38,13 @@
       "default": "./lib/index.js"
     },
     "./remote": {
-      "node": "./lib/remote.js"
+      "default": "./lib/remote.js"
+    }
+  },
+  "imports": {
+    "#host-impl": {
+      "node": "./lib/host-impl.node.js",
+      "qtart": "./lib/host-impl.qtart.js"
     }
   },
   "devDependencies": {
diff --git a/packages/taler-wallet-core/src/bank-api-client.ts 
b/packages/taler-wallet-core/src/bank-api-client.ts
index dc7845150..addec709f 100644
--- a/packages/taler-wallet-core/src/bank-api-client.ts
+++ b/packages/taler-wallet-core/src/bank-api-client.ts
@@ -33,13 +33,10 @@ import {
   j2s,
   Logger,
   stringToBytes,
+  TalerError,
   TalerErrorCode,
 } from "@gnu-taler/taler-util";
-import { TalerError } from "./errors.js";
-import {
-  HttpRequestLibrary,
-  readSuccessResponseJsonOrThrow,
-} from "./util/http.js";
+import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from 
"@gnu-taler/taler-util/http";
 
 const logger = new Logger("bank-api-client.ts");
 
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts 
b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts
index d8d53a839..1e9d82f66 100644
--- a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts
@@ -21,8 +21,6 @@ import {
   CryptoWorker,
   CryptoWorkerResponseMessage,
 } from "./cryptoWorkerInterface.js";
-import { SynchronousCryptoWorkerFactoryNode } from 
"./synchronousWorkerFactoryNode.js";
-import { processRequestWithImpl } from "./worker-common.js";
 
 export class MyCryptoWorker implements CryptoWorker {
   /**
diff --git a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts 
b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
index f086691e5..192e9cda1 100644
--- a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
@@ -24,7 +24,7 @@
  * Imports.
  */
 import { j2s, Logger, TalerErrorCode } from "@gnu-taler/taler-util";
-import { TalerError } from "../../errors.js";
+import { TalerError } from "@gnu-taler/taler-util";
 import { openPromise } from "../../util/promiseUtils.js";
 import { timer, performanceNow, TimerHandle } from "../../util/timer.js";
 import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js";
diff --git a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts 
b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts
deleted file mode 100644
index 21d88fffa..000000000
--- a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- 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/>
- */
-
-/**
- * Imports.
- */
-import { Logger } from "@gnu-taler/taler-util";
-import child_process from "child_process";
-import type internal from "stream";
-import { OpenedPromise, openPromise } from "../../util/promiseUtils.js";
-
-const logger = new Logger("synchronousWorkerFactory.ts");
-
-/**
- * Client for the crypto helper process (taler-crypto-worker from 
exchange.git).
- */
-export class CryptoRpcClient {
-  proc: child_process.ChildProcessByStdio<
-    internal.Writable,
-    internal.Readable,
-    null
-  >;
-  requests: Array<{
-    p: OpenedPromise<any>;
-    req: any;
-  }> = [];
-
-  constructor() {
-    const stdoutChunks: Buffer[] = [];
-    this.proc = child_process.spawn("taler-crypto-worker", {
-      //stdio: ["pipe", "pipe", "inherit"],
-      stdio: ["pipe", "pipe", "inherit"],
-      detached: true,
-    });
-    this.proc.on("close", (): void => {
-      logger.error("child process exited");
-    });
-    (this.proc.stdout as any).unref();
-    (this.proc.stdin as any).unref();
-    this.proc.unref();
-
-    this.proc.stdout.on("data", (x) => {
-      if (x instanceof Buffer) {
-        const nlIndex = x.indexOf("\n");
-        if (nlIndex >= 0) {
-          const before = x.slice(0, nlIndex);
-          const after = x.slice(nlIndex + 1);
-          stdoutChunks.push(after);
-          const str = Buffer.concat([...stdoutChunks, before]).toString(
-            "utf-8",
-          );
-          const req = this.requests.shift();
-          if (!req) {
-            throw Error("request was undefined");
-          }
-          if (this.requests.length === 0) {
-            this.proc.unref();
-          }
-          //logger.info(`got response: ${str}`);
-          req.p.resolve(JSON.parse(str));
-        } else {
-          stdoutChunks.push(x);
-        }
-      } else {
-        throw Error(`unexpected data chunk type (${typeof x})`);
-      }
-    });
-  }
-
-  async queueRequest(req: any): Promise<any> {
-    const p = openPromise<any>();
-    if (this.requests.length === 0) {
-      this.proc.ref();
-    }
-    this.requests.push({ req, p });
-    this.proc.stdin.write(`${JSON.stringify(req)}\n`);
-    return p.promise;
-  }
-}
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts 
b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts
deleted file mode 100644
index b2653158c..000000000
--- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-import { j2s, Logger } from "@gnu-taler/taler-util";
-import {
-  nativeCryptoR,
-  TalerCryptoInterfaceR,
-} from "../cryptoImplementation.js";
-import { CryptoWorker } from "./cryptoWorkerInterface.js";
-import { CryptoRpcClient } from "./rpcClient.js";
-import { processRequestWithImpl } from "./worker-common.js";
-
-const logger = new Logger("synchronousWorker.ts");
-
-/**
- * Worker implementation that uses node subprocesses.
- *
- * The node crypto worker can also use IPC to offload cryptographic
- * operations to a helper process (usually written in C / part of 
taler-exchange).
- */
-export class SynchronousCryptoWorkerNode implements CryptoWorker {
-  /**
-   * Function to be called when we receive a message from the worker thread.
-   */
-  onmessage: undefined | ((m: any) => void);
-
-  /**
-   * Function to be called when we receive an error from the worker thread.
-   */
-  onerror: undefined | ((m: any) => void);
-
-  cryptoImplR: TalerCryptoInterfaceR;
-
-  rpcClient: CryptoRpcClient | undefined;
-
-  constructor() {
-    this.onerror = undefined;
-    this.onmessage = undefined;
-
-    this.cryptoImplR = { ...nativeCryptoR };
-
-    if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) {
-      logger.info("using RPC for some crypto operations");
-      const rpc = (this.rpcClient = new CryptoRpcClient());
-      this.cryptoImplR.eddsaSign = async (_, req) => {
-        return await rpc.queueRequest({
-          op: "eddsa_sign",
-          args: {
-            msg: req.msg,
-            priv: req.priv,
-          },
-        });
-      };
-      this.cryptoImplR.setupRefreshPlanchet = async (_, req) => {
-        const res = await rpc.queueRequest({
-          op: "setup_refresh_planchet",
-          args: {
-            coin_index: req.coinNumber,
-            transfer_secret: req.transferSecret,
-          },
-        });
-        return {
-          bks: res.blinding_key,
-          coinPriv: res.coin_priv,
-          coinPub: res.coin_pub,
-        };
-      };
-      this.cryptoImplR.rsaBlind = async (_, req) => {
-        const res = await rpc.queueRequest({
-          op: "rsa_blind",
-          args: {
-            bks: req.bks,
-            hm: req.hm,
-            pub: req.pub,
-          },
-        });
-        return {
-          blinded: res.blinded,
-        };
-      };
-      this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => {
-        const res = await rpc.queueRequest({
-          op: "kx_ecdhe_eddsa",
-          args: {
-            ecdhe_priv: req.ecdhePriv,
-            eddsa_pub: req.eddsaPub,
-          },
-        });
-        return {
-          h: res.h,
-        };
-      };
-      this.cryptoImplR.eddsaGetPublic = async (_, req) => {
-        const res = await rpc.queueRequest({
-          op: "eddsa_get_public",
-          args: {
-            eddsa_priv: req.priv,
-          },
-        });
-        return {
-          pub: res.eddsa_pub,
-        };
-      };
-      this.cryptoImplR.ecdheGetPublic = async (_, req) => {
-        const res = await rpc.queueRequest({
-          op: "ecdhe_get_public",
-          args: {
-            ecdhe_priv: req.priv,
-          },
-        });
-        return {
-          pub: res.ecdhe_pub,
-        };
-      };
-    }
-  }
-
-  /**
-   * Add an event listener for either an "error" or "message" event.
-   */
-  addEventListener(event: "message" | "error", fn: (x: any) => void): void {
-    switch (event) {
-      case "message":
-        this.onmessage = fn;
-        break;
-      case "error":
-        this.onerror = fn;
-        break;
-    }
-  }
-
-  private dispatchMessage(msg: any): void {
-    if (this.onmessage) {
-      this.onmessage(msg);
-    }
-  }
-
-  /**
-   * Send a message to the worker thread.
-   */
-  postMessage(msg: any): void {
-    const handleRequest = async () => {
-      const responseMsg = await processRequestWithImpl(msg, this.cryptoImplR);
-      try {
-        setTimeout(() => this.dispatchMessage(responseMsg), 0);
-      } catch (e) {
-        logger.error("got error during dispatch", e);
-      }
-    };
-    handleRequest().catch((e) => {
-      logger.error("Error while handling crypto request:", e);
-    });
-  }
-
-  /**
-   * Forcibly terminate the worker thread.
-   */
-  terminate(): void {
-    // This is a no-op.
-  }
-}
diff --git a/packages/taler-wallet-core/src/crypto/workers/worker-common.ts 
b/packages/taler-wallet-core/src/crypto/workers/worker-common.ts
index 8a74a5231..9f23cf685 100644
--- a/packages/taler-wallet-core/src/crypto/workers/worker-common.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/worker-common.ts
@@ -23,7 +23,7 @@ import {
   stringifyError as safeStringifyError,
   TalerErrorCode,
 } from "@gnu-taler/taler-util";
-import { getErrorDetailFromException, makeErrorDetail } from "../../errors.js";
+import { getErrorDetailFromException, makeErrorDetail } from 
"@gnu-taler/taler-util";
 import { TalerCryptoInterfaceR } from "../cryptoImplementation.js";
 import {
   CryptoWorkerRequestMessage,
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 8bb8d519f..75e6408f7 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -2659,6 +2659,9 @@ function onMetaDbUpgradeNeeded(
 /**
  * Return a promise that resolves
  * to the taler wallet db.
+ *
+ * @param onVersionChange Called when another client concurrenctly connects to 
the database
+ * with a higher version.
  */
 export async function openTalerDatabase(
   idbFactory: IDBFactory,
diff --git a/packages/taler-wallet-core/src/dbless.ts 
b/packages/taler-wallet-core/src/dbless.ts
index 544e2d458..99596edd8 100644
--- a/packages/taler-wallet-core/src/dbless.ts
+++ b/packages/taler-wallet-core/src/dbless.ts
@@ -58,7 +58,7 @@ import {
 import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
-} from "./util/http.js";
+} from "@gnu-taler/taler-util/http";
 import {
   getBankStatusUrl,
   getBankWithdrawalInfo,
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts 
b/packages/taler-wallet-core/src/dev-experiments.ts
index 6c36d6f6c..3e6194ccd 100644
--- a/packages/taler-wallet-core/src/dev-experiments.ts
+++ b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -32,7 +32,7 @@ import {
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
-} from "./util/http.js";
+} from "@gnu-taler/taler-util/http";
 
 const logger = new Logger("dev-experiments.ts");
 
diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts 
b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
deleted file mode 100644
index c1d42796d..000000000
--- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 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/>
-
- SPDX-License-Identifier: AGPL3.0-or-later
-*/
-
-/**
- * Imports.
- */
-import {
-  DEFAULT_REQUEST_TIMEOUT_MS,
-  Headers,
-  HttpRequestLibrary,
-  HttpRequestOptions,
-  HttpResponse,
-} from "../util/http.js";
-import { RequestThrottler } from "@gnu-taler/taler-util";
-import axios, { AxiosResponse } from "axios";
-import { TalerError } from "../errors.js";
-import { Logger, bytesToString } from "@gnu-taler/taler-util";
-import { TalerErrorCode, URL } from "@gnu-taler/taler-util";
-
-const logger = new Logger("NodeHttpLib.ts");
-
-/**
- * Implementation of the HTTP request library interface for node.
- */
-export class NodeHttpLib implements HttpRequestLibrary {
-  private throttle = new RequestThrottler();
-  private throttlingEnabled = true;
-
-  /**
-   * Set whether requests should be throttled.
-   */
-  setThrottling(enabled: boolean): void {
-    this.throttlingEnabled = enabled;
-  }
-
-  async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
-    const method = opt?.method ?? "GET";
-    let body = opt?.body;
-
-    logger.trace(`Requesting ${method} ${url}`);
-
-    const parsedUrl = new URL(url);
-    if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
-      throw TalerError.fromDetail(
-        TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
-        {
-          requestMethod: method,
-          requestUrl: url,
-          throttleStats: this.throttle.getThrottleStats(url),
-        },
-        `request to origin ${parsedUrl.origin} was throttled`,
-      );
-    }
-    let timeoutMs: number | undefined;
-    if (typeof opt?.timeout?.d_ms === "number") {
-      timeoutMs = opt.timeout.d_ms;
-    } else {
-      timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
-    }
-    // FIXME: Use AbortController / etc. to handle cancellation
-    let resp: AxiosResponse;
-    try {
-      let respPromise = axios.default({
-        method,
-        url: url,
-        responseType: "arraybuffer",
-        headers: opt?.headers,
-        validateStatus: () => true,
-        transformResponse: (x) => x,
-        data: body,
-        timeout: timeoutMs,
-        maxRedirects: 0,
-      });
-      if (opt?.cancellationToken) {
-        respPromise = opt.cancellationToken.racePromise(respPromise);
-      }
-      resp = await respPromise;
-    } catch (e: any) {
-      throw TalerError.fromDetail(
-        TalerErrorCode.WALLET_NETWORK_ERROR,
-        {
-          requestUrl: url,
-          requestMethod: method,
-        },
-        `${e.message}`,
-      );
-    }
-
-    const makeText = async (): Promise<string> => {
-      opt?.cancellationToken?.throwIfCancelled();
-      const respText = new Uint8Array(resp.data);
-      return bytesToString(respText);
-    };
-
-    const makeJson = async (): Promise<any> => {
-      opt?.cancellationToken?.throwIfCancelled();
-      let responseJson;
-      const respText = await makeText();
-      try {
-        responseJson = JSON.parse(respText);
-      } catch (e) {
-        logger.trace(`invalid json: '${resp.data}'`);
-        throw TalerError.fromDetail(
-          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-          {
-            httpStatusCode: resp.status,
-            requestUrl: url,
-            requestMethod: method,
-          },
-          "Could not parse response body as JSON",
-        );
-      }
-      if (responseJson === null || typeof responseJson !== "object") {
-        logger.trace(`invalid json (not an object): '${respText}'`);
-        throw TalerError.fromDetail(
-          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-          {
-            httpStatusCode: resp.status,
-            requestUrl: url,
-            requestMethod: method,
-          },
-          `invalid JSON`,
-        );
-      }
-      return responseJson;
-    };
-    const makeBytes = async () => {
-      opt?.cancellationToken?.throwIfCancelled();
-      if (typeof resp.data.byteLength !== "number") {
-        throw Error("expected array buffer");
-      }
-      const buf = resp.data;
-      return buf;
-    };
-    const headers = new Headers();
-    for (const hn of Object.keys(resp.headers)) {
-      headers.set(hn, resp.headers[hn]);
-    }
-    return {
-      requestUrl: url,
-      requestMethod: method,
-      headers,
-      status: resp.status,
-      text: makeText,
-      json: makeJson,
-      bytes: makeBytes,
-    };
-  }
-
-  async get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
-    return this.fetch(url, {
-      method: "GET",
-      ...opt,
-    });
-  }
-
-  async postJson(
-    url: string,
-    body: any,
-    opt?: HttpRequestOptions,
-  ): Promise<HttpResponse> {
-    return this.fetch(url, {
-      method: "POST",
-      body,
-      ...opt,
-    });
-  }
-}
diff --git a/packages/taler-wallet-core/src/host-common.ts 
b/packages/taler-wallet-core/src/host-common.ts
new file mode 100644
index 000000000..7651e5a12
--- /dev/null
+++ b/packages/taler-wallet-core/src/host-common.ts
@@ -0,0 +1,60 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+ */
+
+import { WalletNotification } from "@gnu-taler/taler-util";
+import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
+
+/**
+ * Helpers to initiate a wallet in a host environment.
+ */
+
+/**
+ */
+export interface DefaultNodeWalletArgs {
+  /**
+   * Location of the wallet database.
+   *
+   * If not specified, the wallet starts out with an empty database and
+   * the wallet database is stored only in memory.
+   */
+  persistentStoragePath?: string;
+
+  /**
+   * Handler for asynchronous notifications from the wallet.
+   */
+  notifyHandler?: (n: WalletNotification) => void;
+
+  /**
+   * If specified, use this as HTTP request library instead
+   * of the default one.
+   */
+  httpLib?: HttpRequestLibrary;
+
+  cryptoWorkerType?: "sync" | "node-worker-thread";
+}
+
+/**
+ * Generate a random alphanumeric ID.  Does *not* use cryptographically
+ * secure randomness.
+ */
+export function makeTempfileId(length: number): string {
+  let result = "";
+  const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
+  for (let i = 0; i < length; i++) {
+    result += characters.charAt(Math.floor(Math.random() * characters.length));
+  }
+  return result;
+}
diff --git a/packages/taler-wallet-core/src/headless/helpers.ts 
b/packages/taler-wallet-core/src/host-impl.node.ts
similarity index 67%
rename from packages/taler-wallet-core/src/headless/helpers.ts
rename to packages/taler-wallet-core/src/host-impl.node.ts
index fbeb84c67..ec57e0ebe 100644
--- a/packages/taler-wallet-core/src/headless/helpers.ts
+++ b/packages/taler-wallet-core/src/host-impl.node.ts
@@ -30,70 +30,27 @@ import {
   shimIndexedDB,
 } from "@gnu-taler/idb-bridge";
 import { AccessStats } from "@gnu-taler/idb-bridge";
-import { Logger, WalletNotification } from "@gnu-taler/taler-util";
+import { Logger } from "@gnu-taler/taler-util";
 import * as fs from "fs";
-import { NodeThreadCryptoWorkerFactory } from 
"../crypto/workers/nodeThreadWorker.js";
-import { SynchronousCryptoWorkerFactoryNode } from 
"../crypto/workers/synchronousWorkerFactoryNode.js";
-import { openTalerDatabase } from "../index.js";
-import { HttpRequestLibrary } from "../util/http.js";
-import { SetTimeoutTimerAPI } from "../util/timer.js";
-import { Wallet } from "../wallet.js";
-import { NodeHttpLib } from "./NodeHttpLib.js";
-
-const logger = new Logger("headless/helpers.ts");
-
-export interface DefaultNodeWalletArgs {
-  /**
-   * Location of the wallet database.
-   *
-   * If not specified, the wallet starts out with an empty database and
-   * the wallet database is stored only in memory.
-   */
-  persistentStoragePath?: string;
-
-  /**
-   * Handler for asynchronous notifications from the wallet.
-   */
-  notifyHandler?: (n: WalletNotification) => void;
-
-  /**
-   * If specified, use this as HTTP request library instead
-   * of the default one.
-   */
-  httpLib?: HttpRequestLibrary;
-
-  cryptoWorkerType?: "sync" | "node-worker-thread";
-}
+import { NodeThreadCryptoWorkerFactory } from 
"./crypto/workers/nodeThreadWorker.js";
+import { SynchronousCryptoWorkerFactoryPlain } from 
"./crypto/workers/synchronousWorkerFactoryPlain.js";
+import { openTalerDatabase } from "./index.js";
+import {
+  createPlatformHttpLib,
+} from "@gnu-taler/taler-util/http";
+import { SetTimeoutTimerAPI } from "./util/timer.js";
+import { Wallet } from "./wallet.js";
+import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js";
 
-/**
- * Generate a random alphanumeric ID.  Does *not* use cryptographically
- * secure randomness.
- */
-function makeId(length: number): string {
-  let result = "";
-  const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
-  for (let i = 0; i < length; i++) {
-    result += characters.charAt(Math.floor(Math.random() * characters.length));
-  }
-  return result;
-}
+const logger = new Logger("host-impl.node.ts");
 
-/**
- * Get a wallet instance with default settings for node.
- */
-export async function getDefaultNodeWallet(
-  args: DefaultNodeWalletArgs = {},
-): Promise<Wallet> {
-  const res = await getDefaultNodeWallet2(args);
-  return res.wallet;
-}
 
 /**
  * Get a wallet instance with default settings for node.
  *
  * Extended version that allows getting DB stats.
  */
-export async function getDefaultNodeWallet2(
+export async function createNativeWalletHost2(
   args: DefaultNodeWalletArgs = {},
 ): Promise<{
   wallet: Wallet;
@@ -127,7 +84,8 @@ export async function getDefaultNodeWallet2(
       if (args.persistentStoragePath === undefined) {
         return;
       }
-      const tmpPath = `${args.persistentStoragePath}-${makeId(5)}.tmp`;
+      const tmpPath = `${args.persistentStoragePath}-${makeTempfileId(5)}.tmp`;
+      logger.trace("exported DB dump");
       const dbContent = myBackend.exportDump();
       fs.writeFileSync(tmpPath, JSON.stringify(dbContent, undefined, 2), {
         encoding: "utf-8",
@@ -147,7 +105,9 @@ export async function getDefaultNodeWallet2(
   if (args.httpLib) {
     myHttpLib = args.httpLib;
   } else {
-    myHttpLib = new NodeHttpLib();
+    myHttpLib = createPlatformHttpLib({
+      enableThrottling: true,
+    });
   }
 
   const myVersionChange = (): Promise<void> => {
@@ -165,7 +125,7 @@ export async function getDefaultNodeWallet2(
   const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread";
   if (cryptoWorkerType === "sync") {
     logger.info("using synchronous crypto worker");
-    workerFactory = new SynchronousCryptoWorkerFactoryNode();
+    workerFactory = new SynchronousCryptoWorkerFactoryPlain();
   } else if (cryptoWorkerType === "node-worker-thread") {
     try {
       // Try if we have worker threads available, fails in older node versions.
@@ -179,7 +139,7 @@ export async function getDefaultNodeWallet2(
       logger.warn(
         "worker threads not available, falling back to synchronous workers",
       );
-      workerFactory = new SynchronousCryptoWorkerFactoryNode();
+      workerFactory = new SynchronousCryptoWorkerFactoryPlain();
     }
   } else {
     throw Error(`unsupported crypto worker type '${cryptoWorkerType}'`);
diff --git a/packages/taler-wallet-core/src/host-impl.qtart.ts 
b/packages/taler-wallet-core/src/host-impl.qtart.ts
new file mode 100644
index 000000000..337914292
--- /dev/null
+++ b/packages/taler-wallet-core/src/host-impl.qtart.ts
@@ -0,0 +1,120 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+ */
+
+/**
+ * Helpers to create headless wallets.
+ * @author Florian Dold <dold@taler.net>
+ */
+
+/**
+ * Imports.
+ */
+import type { IDBFactory } from "@gnu-taler/idb-bridge";
+// eslint-disable-next-line no-duplicate-imports
+import {
+  BridgeIDBFactory,
+  MemoryBackend,
+  shimIndexedDB,
+} from "@gnu-taler/idb-bridge";
+import { AccessStats } from "@gnu-taler/idb-bridge";
+import { SynchronousCryptoWorkerFactoryPlain } from 
"./crypto/workers/synchronousWorkerFactoryPlain.js";
+import { openTalerDatabase } from "./index.js";
+import { Logger } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { SetTimeoutTimerAPI } from "./util/timer.js";
+import { Wallet } from "./wallet.js";
+import { qjsOs, qjsStd } from "@gnu-taler/taler-util/qtart";
+import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js";
+
+const logger = new Logger("host-impl.qtart.ts");
+
+export async function createNativeWalletHost2(
+  args: DefaultNodeWalletArgs = {},
+): Promise<{
+  wallet: Wallet;
+  getDbStats: () => AccessStats;
+}> {
+  BridgeIDBFactory.enableTracing = false;
+  const myBackend = new MemoryBackend();
+  myBackend.enableTracing = false;
+
+  const storagePath = args.persistentStoragePath;
+  if (storagePath) {
+    const dbContentStr = qjsStd.loadFile(storagePath);
+    if (dbContentStr != null) {
+      const dbContent = JSON.parse(dbContentStr);
+      myBackend.importDump(dbContent);
+    }
+
+    myBackend.afterCommitCallback = async () => {
+      logger.trace("committing database");
+      // Allow caller to stop persisting the wallet.
+      if (args.persistentStoragePath === undefined) {
+        return;
+      }
+      const tmpPath = `${args.persistentStoragePath}-${makeTempfileId(5)}.tmp`;
+      const dbContent = myBackend.exportDump();
+      logger.trace("exported DB dump");
+      qjsStd.writeFile(tmpPath, JSON.stringify(dbContent, undefined, 2));
+      // Atomically move the temporary file onto the DB path.
+      const res = qjsOs.rename(tmpPath, args.persistentStoragePath);
+      if (res != 0) {
+        throw Error("db commit failed at rename");
+      }
+      logger.trace("committing database done");
+    };
+  }
+
+  logger.info("done processing storage path");
+
+  BridgeIDBFactory.enableTracing = false;
+
+  const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
+  const myIdbFactory: IDBFactory = myBridgeIdbFactory as any as IDBFactory;
+
+  let myHttpLib;
+  if (args.httpLib) {
+    myHttpLib = args.httpLib;
+  } else {
+    myHttpLib = createPlatformHttpLib();
+  }
+
+  const myVersionChange = (): Promise<void> => {
+    logger.error("version change requested, should not happen");
+    throw Error(
+      "BUG: wallet DB version change event can't happen with memory IDB",
+    );
+  };
+
+  shimIndexedDB(myBridgeIdbFactory);
+
+  const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
+
+  let workerFactory;
+  workerFactory = new SynchronousCryptoWorkerFactoryPlain();
+
+  const timer = new SetTimeoutTimerAPI();
+
+  const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory);
+
+  if (args.notifyHandler) {
+    w.addNotificationListener(args.notifyHandler);
+  }
+  return {
+    wallet: w,
+    getDbStats: () => myBackend.accessStats,
+  };
+}
diff --git a/packages/taler-wallet-core/src/host.ts 
b/packages/taler-wallet-core/src/host.ts
new file mode 100644
index 000000000..4b319f081
--- /dev/null
+++ b/packages/taler-wallet-core/src/host.ts
@@ -0,0 +1,47 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+ */
+
+import { DefaultNodeWalletArgs } from "./host-common.js";
+import { Wallet } from "./index.js";
+
+import * as hostImpl from "#host-impl";
+import { AccessStats } from "@gnu-taler/idb-bridge";
+
+/**
+ * Helpers to initiate a wallet in a host environment.
+ */
+
+/**
+ * Get a wallet instance.
+ */
+export async function createNativeWalletHost2(
+  args: DefaultNodeWalletArgs = {},
+): Promise<{
+  wallet: Wallet;
+  getDbStats: () => AccessStats;
+}> {
+  return hostImpl.createNativeWalletHost2(args);
+}
+
+/**
+ * Get a wallet instance.
+ */
+export async function createNativeWalletHost(
+  args: DefaultNodeWalletArgs = {},
+): Promise<Wallet> {
+  const res = await hostImpl.createNativeWalletHost2(args);
+  return res.wallet;
+}
diff --git a/packages/taler-wallet-core/src/index.node.ts 
b/packages/taler-wallet-core/src/index.node.ts
index 8567d13ac..13392d39c 100644
--- a/packages/taler-wallet-core/src/index.node.ts
+++ b/packages/taler-wallet-core/src/index.node.ts
@@ -16,15 +16,8 @@
 
 export * from "./index.js";
 
-// Utils for using the wallet under node
-export { NodeHttpLib } from "./headless/NodeHttpLib.js";
-export {
-  getDefaultNodeWallet,
-  getDefaultNodeWallet2,
-  DefaultNodeWalletArgs,
-} from "./headless/helpers.js";
 export * from "./crypto/workers/nodeThreadWorker.js";
-export { SynchronousCryptoWorkerNode as SynchronousCryptoWorker } from 
"./crypto/workers/synchronousWorkerNode.js";
+export { SynchronousCryptoWorkerPlain } from 
"./crypto/workers/synchronousWorkerPlain.js";
 
 export type { AccessStats } from "@gnu-taler/idb-bridge";
-export * from "./crypto/workers/synchronousWorkerFactoryNode.js";
+export * from "./crypto/workers/synchronousWorkerFactoryPlain.js";
diff --git a/packages/taler-wallet-core/src/index.ts 
b/packages/taler-wallet-core/src/index.ts
index 031656a6c..7b21d8f91 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -18,13 +18,9 @@
  * Module entry point for the wallet when used as a node module.
  */
 
-// Errors
-export * from "./errors.js";
-
 // Util functionality
 export * from "./util/promiseUtils.js";
 export * from "./util/query.js";
-export * from "./util/http.js";
 
 export * from "./versions.js";
 
@@ -67,3 +63,5 @@ export * from "./util/timer.js";
 export * from "./util/denominations.js";
 
 export { SynchronousCryptoWorkerFactoryPlain } from 
"./crypto/workers/synchronousWorkerFactoryPlain.js";
+export * from "./host-common.js";
+export * from "./host.js";
diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts 
b/packages/taler-wallet-core/src/internal-wallet-state.ts
index d180861f8..8434c3b8f 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -30,18 +30,14 @@
  * Imports.
  */
 import {
-  WalletNotification,
-  BalancesResponse,
-  AmountJson,
-  DenominationPubKey,
-  TalerProtocolTimestamp,
   CancellationToken,
+  CoinRefreshRequest,
   DenominationInfo,
   RefreshGroupId,
-  CoinRefreshRequest,
   RefreshReason,
+  WalletNotification,
 } from "@gnu-taler/taler-util";
-import { CryptoDispatcher } from "./crypto/workers/crypto-dispatcher.js";
+import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
 import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
 import {
   ExchangeDetailsRecord,
@@ -49,9 +45,6 @@ import {
   RefreshReasonDetails,
   WalletStoresV1,
 } from "./db.js";
-import { PendingOperationsResponse } from "./pending-types.js";
-import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
-import { HttpRequestLibrary } from "./util/http.js";
 import { AsyncCondition } from "./util/promiseUtils.js";
 import {
   DbAccess,
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts 
b/packages/taler-wallet-core/src/operations/backup/index.ts
index 7d3953ebb..3dae26087 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -85,13 +85,13 @@ import {
   ConfigRecordKey,
   WalletBackupConfState,
 } from "../../db.js";
-import { TalerError } from "../../errors.js";
+import { TalerError } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../../internal-wallet-state.js";
 import { assertUnreachable } from "../../util/assertUnreachable.js";
 import {
   readSuccessResponseJsonOrThrow,
   readTalerErrorResponse,
-} from "../../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import {
   checkDbInvariant,
   checkLogicInvariant,
diff --git a/packages/taler-wallet-core/src/operations/common.ts 
b/packages/taler-wallet-core/src/operations/common.ts
index 3ea02012b..e61a6fe95 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -42,7 +42,7 @@ import {
   ExchangeDetailsRecord,
   ExchangeRecord,
 } from "../db.js";
-import { makeErrorDetail, TalerError } from "../errors.js";
+import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 4ff6a65cd..9d71f020f 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -62,10 +62,10 @@ import {
   OperationStatus,
   TransactionStatus,
 } from "../db.js";
-import { TalerError } from "../errors.js";
-import { checkWithdrawalKycStatus, KycPendingInfo, KycUserType } from 
"../index.js";
+import { TalerError } from "@gnu-taler/taler-util";
+import { KycPendingInfo, KycUserType } from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
 import { OperationAttemptResult } from "../util/retries.js";
 import { makeTransactionId, spendCoins } from "./common.js";
 import { getExchangeDetails } from "./exchanges.js";
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 67f77de77..2b6a881dd 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -41,6 +41,7 @@ import {
   NotificationType,
   parsePaytoUri,
   Recoup,
+  TalerError,
   TalerErrorCode,
   TalerProtocolDuration,
   TalerProtocolTimestamp,
@@ -49,6 +50,7 @@ import {
   WireFeeMap,
   WireInfo,
 } from "@gnu-taler/taler-util";
+import { HttpRequestLibrary, readSuccessResponseTextOrThrow, 
readSuccessResponseJsonOrThrow, getExpiry } from "@gnu-taler/taler-util/http";
 import {
   DenominationRecord,
   DenominationVerificationStatus,
@@ -56,14 +58,7 @@ import {
   ExchangeRecord,
   WalletStoresV1,
 } from "../db.js";
-import { TalerError } from "../errors.js";
 import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
-import {
-  getExpiry,
-  HttpRequestLibrary,
-  readSuccessResponseJsonOrThrow,
-  readSuccessResponseTextOrThrow,
-} from "../util/http.js";
 import { checkDbInvariant } from "../util/invariants.js";
 import {
   DbAccess,
diff --git a/packages/taler-wallet-core/src/operations/merchants.ts 
b/packages/taler-wallet-core/src/operations/merchants.ts
index eeefc0f79..c47ec4a0a 100644
--- a/packages/taler-wallet-core/src/operations/merchants.ts
+++ b/packages/taler-wallet-core/src/operations/merchants.ts
@@ -25,7 +25,7 @@ import {
   LibtoolVersion,
 } from "@gnu-taler/taler-util";
 import { InternalWalletState, MerchantInfo } from 
"../internal-wallet-state.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
 
 const logger = new Logger("taler-wallet-core:merchants.ts");
 
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 2a89c59ed..f84ac2567 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -94,13 +94,12 @@ import {
   makePendingOperationFailedError,
   TalerError,
   TalerProtocolViolationError,
-} from "../errors.js";
-import { GetReadWriteAccess } from "../index.browser.js";
+} from "@gnu-taler/taler-util";
+import { GetReadWriteAccess } from "../index.js";
 import {
   EXCHANGE_COINS_LOCK,
   InternalWalletState,
 } from "../internal-wallet-state.js";
-import { PendingTaskType } from "../pending-types.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import {
   CoinSelectionTally,
@@ -114,7 +113,7 @@ import {
   readTalerErrorResponse,
   readUnexpectedResponseDetails,
   throwUnexpectedRequestError,
-} from "../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import {
   OperationAttemptResult,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts 
b/packages/taler-wallet-core/src/operations/pay-peer.ts
index 7dc7b67fe..022a824de 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -81,16 +81,15 @@ import {
   WithdrawalGroupStatus,
   WithdrawalRecordType,
 } from "../db.js";
-import { TalerError } from "../errors.js";
+import { TalerError } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import {
   makeTransactionId,
   runOperationWithErrorReporting,
   spendCoins,
 } from "../operations/common.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
 import { checkDbInvariant } from "../util/invariants.js";
-import { GetReadOnlyAccess } from "../util/query.js";
 import {
   OperationAttemptResult,
   OperationAttemptResultType,
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index 00dd0e1c6..3b423474b 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -49,7 +49,7 @@ import {
   WithdrawCoinSource,
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import {
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 5b7bf8d83..773689635 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -63,7 +63,7 @@ import {
   RefreshReasonDetails,
   WalletStoresV1,
 } from "../db.js";
-import { TalerError } from "../errors.js";
+import { TalerError } from "@gnu-taler/taler-util";
 import {
   EXCHANGE_COINS_LOCK,
   InternalWalletState,
@@ -72,7 +72,7 @@ import { assertUnreachable } from 
"../util/assertUnreachable.js";
 import {
   readSuccessResponseJsonOrThrow,
   readUnexpectedResponseDetails,
-} from "../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import {
diff --git a/packages/taler-wallet-core/src/operations/testing.ts 
b/packages/taler-wallet-core/src/operations/testing.ts
index 50454a920..873fac021 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -29,7 +29,7 @@ import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
   checkSuccessResponseOrThrow,
-} from "../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import {
   AmountString,
   codecForAny,
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index 2bf216102..ec7546992 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -45,12 +45,12 @@ import {
   DenominationRecord,
   TipRecord,
 } from "../db.js";
-import { makeErrorDetail } from "../errors.js";
+import { makeErrorDetail } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import {
   getHttpResponseErrorDetails,
   readSuccessResponseJsonOrThrow,
-} from "../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import {
   OperationAttemptResult,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index bcc8600c7..f6d79b229 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -85,7 +85,7 @@ import {
   getErrorDetailFromException,
   makeErrorDetail,
   TalerError,
-} from "../errors.js";
+} from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import {
   makeCoinAvailable,
@@ -99,7 +99,7 @@ import {
   readSuccessResponseJsonOrErrorCode,
   readSuccessResponseJsonOrThrow,
   throwUnexpectedRequestError,
-} from "../util/http.js";
+} from "@gnu-taler/taler-util/http";
 import {
   checkDbInvariant,
   checkLogicInvariant,
diff --git a/packages/taler-wallet-core/src/remote.ts 
b/packages/taler-wallet-core/src/remote.ts
index bc0be9d30..89348698e 100644
--- a/packages/taler-wallet-core/src/remote.ts
+++ b/packages/taler-wallet-core/src/remote.ts
@@ -19,10 +19,10 @@ import {
   CoreApiResponse,
   j2s,
   Logger,
+  TalerError,
   WalletNotification,
 } from "@gnu-taler/taler-util";
 import { connectRpc, JsonMessage } from "@gnu-taler/taler-util/twrpc";
-import { TalerError } from "./errors.js";
 import { OpenedPromise, openPromise } from "./index.js";
 import { WalletCoreApiClient } from "./wallet-api-types.js";
 
diff --git a/packages/taler-wallet-core/src/util/retries.ts 
b/packages/taler-wallet-core/src/util/retries.ts
index fcb63ecd1..742381f7b 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -40,7 +40,7 @@ import {
   WalletStoresV1,
   WithdrawalGroupRecord,
 } from "../db.js";
-import { TalerError } from "../errors.js";
+import { TalerError } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { PendingTaskType } from "../pending-types.js";
 import { GetReadWriteAccess } from "./query.js";
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index a57c71bcf..0d02b667b 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -129,7 +129,7 @@ import {
   maybeInitDevMode,
   setDevMode,
 } from "./dev-experiments.js";
-import { getErrorDetailFromException, TalerError } from "./errors.js";
+import { getErrorDetailFromException, TalerError } from 
"@gnu-taler/taler-util";
 import {
   ActiveLongpollInfo,
   ExchangeOperations,
@@ -247,7 +247,7 @@ import {
 import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
-} from "./util/http.js";
+} from "@gnu-taler/taler-util/http";
 import { checkDbInvariant } from "./util/invariants.js";
 import {
   AsyncCondition,
diff --git a/packages/taler-wallet-embedded/build.mjs 
b/packages/taler-wallet-embedded/build.mjs
index 537a4fbc0..28351e6e5 100755
--- a/packages/taler-wallet-embedded/build.mjs
+++ b/packages/taler-wallet-embedded/build.mjs
@@ -55,6 +55,7 @@ export const buildConfig = {
   format: 'esm',
   platform: 'neutral',
   mainFields: ["module", "main"],
+  conditions: ["qtart"],
   sourcemap: true,
   define: {
     '__VERSION__': `"${_package.version}"`,
diff --git a/packages/taler-wallet-embedded/src/index.ts 
b/packages/taler-wallet-embedded/src/index.ts
index b505a2d9d..e0a13390d 100644
--- a/packages/taler-wallet-embedded/src/index.ts
+++ b/packages/taler-wallet-embedded/src/index.ts
@@ -18,27 +18,25 @@
  * Imports.
  */
 import {
+  createNativeWalletHost,
   DefaultNodeWalletArgs,
-  getDefaultNodeWallet,
-  getErrorDetailFromException,
   handleWorkerError,
   handleWorkerMessage,
-  Headers,
-  HttpRequestLibrary,
-  HttpRequestOptions,
-  HttpResponse,
-  NodeHttpLib,
   OpenedPromise,
   openPromise,
   Wallet,
-  WALLET_EXCHANGE_PROTOCOL_VERSION,
-  WALLET_MERCHANT_PROTOCOL_VERSION,
 } from "@gnu-taler/taler-wallet-core";
 
 import {
   CoreApiMessageEnvelope,
   CoreApiResponse,
   CoreApiResponseSuccess,
+  createPlatformHttpLib,
+  getErrorDetailFromException,
+  Headers,
+  HttpRequestLibrary,
+  HttpRequestOptions,
+  HttpResponse,
   Logger,
   WalletNotification,
 } from "@gnu-taler/taler-util";
@@ -51,7 +49,7 @@ const logger = new Logger("taler-wallet-embedded/index.ts");
 export class NativeHttpLib implements HttpRequestLibrary {
   useNfcTunnel = false;
 
-  private nodeHttpLib: HttpRequestLibrary = new NodeHttpLib();
+  private httpLib: HttpRequestLibrary = createPlatformHttpLib();
 
   private requestId = 1;
 
@@ -62,7 +60,7 @@ export class NativeHttpLib implements HttpRequestLibrary {
   constructor(private sendMessage: (m: string) => void) {}
 
   fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
-    return this.nodeHttpLib.fetch(url, opt);
+    return this.httpLib.fetch(url, opt);
   }
 
   get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
@@ -83,7 +81,7 @@ export class NativeHttpLib implements HttpRequestLibrary {
       );
       return p.promise;
     } else {
-      return this.nodeHttpLib.get(url, opt);
+      return this.httpLib.get(url, opt);
     }
   }
 
@@ -106,7 +104,7 @@ export class NativeHttpLib implements HttpRequestLibrary {
       );
       return p.promise;
     } else {
-      return this.nodeHttpLib.postJson(url, body, opt);
+      return this.httpLib.postJson(url, body, opt);
     }
   }
 
@@ -158,7 +156,7 @@ class NativeWalletMessageHandler {
   walletArgs: DefaultNodeWalletArgs | undefined;
   maybeWallet: Wallet | undefined;
   wp = openPromise<Wallet>();
-  httpLib = new NodeHttpLib();
+  httpLib = createPlatformHttpLib();
 
   /**
    * Handle a request from the native wallet.
@@ -181,7 +179,7 @@ class NativeWalletMessageHandler {
 
     const reinit = async () => {
       logger.info("in reinit");
-      const w = await getDefaultNodeWallet(this.walletArgs);
+      const w = await createNativeWalletHost(this.walletArgs);
       this.maybeWallet = w;
       const resp = await w.handleCoreApiRequest(
         "initWallet",
diff --git a/packages/taler-wallet-embedded/src/wallet-qjs.ts 
b/packages/taler-wallet-embedded/src/wallet-qjs.ts
index f7b73711c..77d166095 100644
--- a/packages/taler-wallet-embedded/src/wallet-qjs.ts
+++ b/packages/taler-wallet-embedded/src/wallet-qjs.ts
@@ -17,44 +17,26 @@
 /**
  * Imports.
  */
-import {
-  AccessStats,
-  DefaultNodeWalletArgs,
-  getErrorDetailFromException,
-  Headers,
-  HttpRequestLibrary,
-  HttpRequestOptions,
-  HttpResponse,
-  openPromise,
-  openTalerDatabase,
-  SetTimeoutTimerAPI,
-  SynchronousCryptoWorkerFactoryPlain,
-  Wallet,
-  WalletApiOperation,
-} from "@gnu-taler/taler-wallet-core";
-
 import {
   CoreApiMessageEnvelope,
   CoreApiResponse,
   CoreApiResponseSuccess,
+  createPlatformHttpLib,
+  getErrorDetailFromException,
   InitRequest,
-  j2s,
   Logger,
   setGlobalLogLevelFromString,
   setPRNG,
   WalletNotification,
 } from "@gnu-taler/taler-util";
-import { BridgeIDBFactory } from "@gnu-taler/idb-bridge";
-import { MemoryBackend } from "@gnu-taler/idb-bridge";
-import { shimIndexedDB } from "@gnu-taler/idb-bridge";
-import { IDBFactory } from "@gnu-taler/idb-bridge";
-
-import * as _qjsOsImp from "os";
-// @ts-ignore
-import * as _qjsStdImp from "std";
-
-const textDecoder = new TextDecoder();
-const textEncoder = new TextEncoder();
+import { qjsOs } from "@gnu-taler/taler-util/qtart";
+import {
+  createNativeWalletHost2,
+  DefaultNodeWalletArgs,
+  openPromise,
+  Wallet,
+  WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
 
 setGlobalLogLevelFromString("trace");
 
@@ -66,210 +48,19 @@ setPRNG(function (x: Uint8Array, n: number) {
   for (let i = 0; i < v.length; i++) v[i] = 0;
 });
 
-export interface QjsHttpResp {
-  status: number;
-  data: ArrayBuffer;
-}
-
-export interface QjsHttpOptions {
-  method: string;
-  debug?: boolean;
-  data?: ArrayBuffer;
-  headers?: string[];
-}
-
-export interface QjsOsLib {
-  fetchHttp(url: string, options?: QjsHttpOptions): Promise<QjsHttpResp>;
-  postMessageToHost(s: string): void;
-  setMessageFromHostHandler(h: (s: string) => void): void;
-  rename(oldPath: string, newPath: string): number;
-}
-
-export interface QjsStdLib {
-  writeFile(filename: string, contents: string): void;
-  loadFile(filename: string): string;
-}
-
-// This is not the nodejs "os" module, but the qjs "os" module.
-const qjsOs: QjsOsLib = _qjsOsImp as any;
-
-const qjsStd: QjsStdLib = _qjsStdImp as any;
-
 const logger = new Logger("taler-wallet-embedded/index.ts");
 
-export class NativeHttpLib implements HttpRequestLibrary {
-  get(
-    url: string,
-    opt?: HttpRequestOptions | undefined,
-  ): Promise<HttpResponse> {
-    return this.fetch(url, {
-      method: "GET",
-      ...opt,
-    });
-  }
-  postJson(
-    url: string,
-    body: any,
-    opt?: HttpRequestOptions | undefined,
-  ): Promise<HttpResponse> {
-    return this.fetch(url, {
-      method: "POST",
-      body,
-      ...opt,
-    });
-  }
-  async fetch(
-    url: string,
-    opt?: HttpRequestOptions | undefined,
-  ): Promise<HttpResponse> {
-    const method = opt?.method ?? "GET";
-    let data: ArrayBuffer | undefined = undefined;
-    let headers: string[] = [];
-    if (opt?.headers) {
-      for (let headerName of Object.keys(opt.headers)) {
-        headers.push(`${headerName}: ${opt.headers[headerName]}`);
-      }
-    }
-    if (method.toUpperCase() === "POST") {
-      if (opt?.body) {
-        if (typeof opt.body === "string") {
-          data = textEncoder.encode(opt.body).buffer;
-        } else if (ArrayBuffer.isView(opt.body)) {
-          data = opt.body.buffer;
-        } else if (opt.body instanceof ArrayBuffer) {
-          data = opt.body;
-        } else if (typeof opt.body === "object") {
-          data = textEncoder.encode(JSON.stringify(opt.body)).buffer;
-        }
-      } else {
-        data = new ArrayBuffer(0);
-      }
-    }
-    const res = await qjsOs.fetchHttp(url, {
-      method,
-      data,
-      headers,
-    });
-    return {
-      requestMethod: method,
-      headers: new Headers(),
-      async bytes() {
-        return res.data;
-      },
-      json() {
-        const text = textDecoder.decode(res.data);
-        return JSON.parse(text);
-      },
-      async text() {
-        const text = textDecoder.decode(res.data);
-        return text;
-      },
-      requestUrl: url,
-      status: res.status,
-    };
-  }
-}
-
 function sendNativeMessage(ev: CoreApiMessageEnvelope): void {
   const m = JSON.stringify(ev);
   qjsOs.postMessageToHost(m);
 }
 
-/**
- * Generate a random alphanumeric ID.  Does *not* use cryptographically
- * secure randomness.
- */
-function makeId(length: number): string {
-  let result = "";
-  const characters =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-  for (let i = 0; i < length; i++) {
-    result += characters.charAt(Math.floor(Math.random() * characters.length));
-  }
-  return result;
-}
-
-export async function getWallet(args: DefaultNodeWalletArgs = {}): Promise<{
-  wallet: Wallet;
-  getDbStats: () => AccessStats;
-}> {
-  BridgeIDBFactory.enableTracing = false;
-  const myBackend = new MemoryBackend();
-  myBackend.enableTracing = false;
-
-  const storagePath = args.persistentStoragePath;
-  if (storagePath) {
-    const dbContentStr = qjsStd.loadFile(storagePath);
-    if (dbContentStr != null) {
-      const dbContent = JSON.parse(dbContentStr);
-      myBackend.importDump(dbContent);
-    }
-
-    myBackend.afterCommitCallback = async () => {
-      logger.trace("committing database");
-      // Allow caller to stop persisting the wallet.
-      if (args.persistentStoragePath === undefined) {
-        return;
-      }
-      const tmpPath = `${args.persistentStoragePath}-${makeId(5)}.tmp`;
-      const dbContent = myBackend.exportDump();
-      qjsStd.writeFile(tmpPath, JSON.stringify(dbContent, undefined, 2));
-      // Atomically move the temporary file onto the DB path.
-      const res = qjsOs.rename(tmpPath, args.persistentStoragePath);
-      if (res != 0) {
-        throw Error("db commit failed at rename");
-      }
-      logger.trace("committing database done");
-    };
-  }
-
-  console.log("done processing storage path");
-
-  BridgeIDBFactory.enableTracing = false;
-
-  const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
-  const myIdbFactory: IDBFactory = myBridgeIdbFactory as any as IDBFactory;
-
-  let myHttpLib;
-  if (args.httpLib) {
-    myHttpLib = args.httpLib;
-  } else {
-    myHttpLib = new NativeHttpLib();
-  }
-
-  const myVersionChange = (): Promise<void> => {
-    logger.error("version change requested, should not happen");
-    throw Error(
-      "BUG: wallet DB version change event can't happen with memory IDB",
-    );
-  };
-
-  shimIndexedDB(myBridgeIdbFactory);
-
-  const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
-
-  let workerFactory;
-  workerFactory = new SynchronousCryptoWorkerFactoryPlain();
-
-  const timer = new SetTimeoutTimerAPI();
-
-  const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory);
-
-  if (args.notifyHandler) {
-    w.addNotificationListener(args.notifyHandler);
-  }
-  return {
-    wallet: w,
-    getDbStats: () => myBackend.accessStats,
-  };
-}
-
 class NativeWalletMessageHandler {
   walletArgs: DefaultNodeWalletArgs | undefined;
   initRequest: InitRequest = {};
   maybeWallet: Wallet | undefined;
   wp = openPromise<Wallet>();
-  httpLib = new NativeHttpLib();
+  httpLib = createPlatformHttpLib();
 
   /**
    * Handle a request from the native wallet.
@@ -292,7 +83,7 @@ class NativeWalletMessageHandler {
 
     const reinit = async () => {
       logger.info("in reinit");
-      const wR = await getWallet(this.walletArgs);
+      const wR = await createNativeWalletHost2(this.walletArgs);
       const w = wR.wallet;
       this.maybeWallet = w;
       const resp = await w.handleCoreApiRequest("initWallet", "native-init", {
@@ -422,7 +213,7 @@ globalThis.installNativeWalletListener = 
installNativeWalletListener;
 globalThis.makeWallet = getWallet;
 
 export async function testWithGv() {
-  const w = await getWallet();
+  const w = await createNativeWalletHost2();
   await w.wallet.client.call(WalletApiOperation.InitWallet, {});
   await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, {
     amountToSpend: "KUDOS:1",
@@ -438,7 +229,7 @@ export async function testWithGv() {
 
 export async function testWithLocal() {
   console.log("running local test");
-  const w = await getWallet({
+  const w = await createNativeWalletHost2({
     persistentStoragePath: "walletdb.json",
   });
   console.log("created wallet");
diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts 
b/packages/taler-wallet-webextension/src/browserHttpLib.ts
index 165a0037c..3b8bb1881 100644
--- a/packages/taler-wallet-webextension/src/browserHttpLib.ts
+++ b/packages/taler-wallet-webextension/src/browserHttpLib.ts
@@ -18,19 +18,17 @@
  * Imports.
  */
 import {
+  Logger,
+  RequestThrottler,
+  TalerErrorCode,
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
   Headers,
   TalerError,
-} from "@gnu-taler/taler-wallet-core";
-import {
-  Logger,
-  RequestThrottler,
-  stringToBytes,
-  TalerErrorCode,
 } from "@gnu-taler/taler-util";
 
+
 const logger = new Logger("browserHttpLib");
 
 /**
diff --git a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts 
b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
index 2f1a26e36..bb1794e56 100644
--- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
+++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
@@ -22,11 +22,12 @@
  * Imports.
  */
 
-import { j2s, Logger } from "@gnu-taler/taler-util";
 import {
+  j2s,
+  Logger,
   getErrorDetailFromException,
-  nativeCrypto,
-} from "@gnu-taler/taler-wallet-core";
+} from "@gnu-taler/taler-util";
+import { nativeCrypto } from "@gnu-taler/taler-wallet-core";
 
 const logger = new Logger("browserWorkerEntry.ts");
 
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
index ee5375859..670a67599 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -16,7 +16,7 @@
 
 /* eslint-disable react-hooks/rules-of-hooks */
 import { Amounts, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
-import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { isFuture, parse } from "date-fns";
 import { useState } from "preact/hooks";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts 
b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
index 66c018ddf..8459d5ca2 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
@@ -20,16 +20,14 @@ import {
   NotificationType,
   PreparePayResult,
   PreparePayResultType,
-  TalerErrorDetail,
   TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
-import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useEffect, useState } from "preact/hooks";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { useEffect } from "preact/hooks";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
 import { useTranslationContext } from "../../context/translation.js";
 import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
-import { withSafe } from "../../mui/handlers.js";
 import { Props, State } from "./index.js";
 
 export function useComponentState({
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
index 6574d6ba1..b306ca122 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
@@ -15,11 +15,9 @@
  */
 
 import {
-  Amounts,
-  TalerErrorDetail,
-  TalerProtocolTimestamp,
+  Amounts, TalerProtocolTimestamp
 } from "@gnu-taler/taler-util";
-import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { isFuture, parse } from "date-fns";
 import { useState } from "preact/hooks";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts 
b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts
index 12643b893..6b50faf10 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts
@@ -16,12 +16,9 @@
 
 import {
   AbsoluteTime,
-  Amounts,
-  TalerErrorDetail,
-  TalerProtocolTimestamp,
+  Amounts, TalerProtocolTimestamp
 } from "@gnu-taler/taler-util";
-import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useState } from "preact/hooks";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
 import { useTranslationContext } from "../../context/translation.js";
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 5f149064c..9522c2bfb 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -20,8 +20,9 @@ import {
   Amounts,
   ExchangeListItem,
   ExchangeTosStatus,
+  TalerError,
 } from "@gnu-taler/taler-util";
-import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { useState } from "preact/hooks";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts 
b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
index cf9409bad..a5e357f7d 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -13,8 +13,7 @@
  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/>
  */
-import { TalerErrorDetail } from "@gnu-taler/taler-util";
-import { TalerError } from "@gnu-taler/taler-wallet-core";
+import { TalerErrorDetail, TalerError } from "@gnu-taler/taler-util";
 import { useEffect, useMemo, useState } from "preact/hooks";
 import { BackgroundError } from "../wxApi.js";
 
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts 
b/packages/taler-wallet-webextension/src/platform/chrome.ts
index 07829641e..beb65b2d0 100644
--- a/packages/taler-wallet-webextension/src/platform/chrome.ts
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -19,8 +19,9 @@ import {
   Logger,
   TalerErrorCode,
   TalerUriType,
+  TalerError,
 } from "@gnu-taler/taler-util";
-import { TalerError, WalletOperations } from "@gnu-taler/taler-wallet-core";
+import { WalletOperations } from "@gnu-taler/taler-wallet-core";
 import { BackgroundOperations } from "../wxApi.js";
 import {
   BackgroundPlatformAPI,
diff --git 
a/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts 
b/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
index 0742d5ccd..4ee572435 100644
--- a/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
+++ b/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
@@ -22,12 +22,12 @@
 import {
   CryptoWorker,
   CryptoWorkerFactory,
-  SynchronousCryptoWorker,
+  SynchronousCryptoWorkerPlain,
 } from "@gnu-taler/taler-wallet-core";
 
 export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
   startWorker(): CryptoWorker {
-    return new SynchronousCryptoWorker();
+    return new SynchronousCryptoWorkerPlain();
   }
 
   getConcurrency(): number {
diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts 
b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
index 4b47e89d5..00c0085ef 100644
--- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
+++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
@@ -21,14 +21,12 @@ import {
   Logger,
   RequestThrottler,
   TalerErrorCode,
-} from "@gnu-taler/taler-util";
-import {
   Headers,
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
   TalerError,
-} from "@gnu-taler/taler-wallet-core";
+} from "@gnu-taler/taler-util";
 
 /**
  * An implementation of the [[HttpRequestLibrary]] using the
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index c064d7111..5f3d09619 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -29,20 +29,18 @@ import {
   NotificationType,
   TalerErrorCode,
   TalerErrorDetail,
-  WalletDiagnostics,
+  WalletDiagnostics
 } from "@gnu-taler/taler-util";
 import {
-  TalerError,
   WalletCoreApiClient,
   WalletCoreOpKeys,
   WalletCoreRequestType,
-  WalletCoreResponseType,
-  WalletOperations,
+  WalletCoreResponseType
 } from "@gnu-taler/taler-wallet-core";
 import {
   MessageFromBackend,
   MessageFromFrontendBackground,
-  MessageFromFrontendWallet,
+  MessageFromFrontendWallet
 } from "./platform/api.js";
 import { platform } from "./platform/foreground.js";
 
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 99602445d..cca07941a 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -32,14 +32,14 @@ import {
   TalerErrorCode,
   TalerUriType,
   WalletDiagnostics,
+  makeErrorDetail,
+  getErrorDetailFromException,
 } from "@gnu-taler/taler-util";
 import {
   DbAccess,
   deleteTalerDatabase,
   exportDb,
-  getErrorDetailFromException,
   importDb,
-  makeErrorDetail,
   OpenedPromise,
   openPromise,
   openTalerDatabase,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]