gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: all the browser related code


From: gnunet
Subject: [taler-wallet-core] branch master updated: all the browser related code move into one place, making it easy for specific platform code or mocking for testing
Date: Wed, 23 Mar 2022 14:59:06 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 32f6409a all the browser related code move into one place, making it 
easy for specific platform code or mocking for testing
32f6409a is described below

commit 32f6409ac312f31821f791c3a376168289f0e4f4
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Mar 23 10:50:12 2022 -0300

    all the browser related code move into one place, making it easy for 
specific platform code or mocking for testing
---
 .../taler-wallet-webextension/src/api/browser.ts   |  54 ---
 .../taler-wallet-webextension/src/background.ts    |  29 +-
 .../taler-wallet-webextension/src/chromeBadge.ts   |   4 +-
 packages/taler-wallet-webextension/src/compat.ts   | 101 ------
 .../src/components/Diagnostics.tsx                 |   6 +-
 .../src/context/iocContext.ts                      |   4 +-
 .../taler-wallet-webextension/src/cta/Refund.tsx   |  32 +-
 packages/taler-wallet-webextension/src/cta/Tip.tsx |  31 +-
 .../src/hooks/useExtendedPermissions.ts            |  53 ++-
 .../taler-wallet-webextension/src/platform/api.ts  |  90 +++++
 .../src/platform/chrome.ts                         | 371 +++++++++++++++++++++
 .../src/platform/firefox.ts                        |  74 ++++
 .../src/popup/DeveloperPage.tsx                    |  20 --
 .../src/popup/TalerActionFound.tsx                 |  53 +--
 .../src/popupEntryPoint.tsx                        |  31 +-
 .../taler-wallet-webextension/src/renderHtml.tsx   | 176 ----------
 .../taler-wallet-webextension/src/utils/index.ts   |  49 ---
 .../src/wallet/AddNewActionView.tsx                |  13 +-
 .../src/walletEntryPoint.tsx                       |  23 +-
 packages/taler-wallet-webextension/src/wxApi.ts    |  47 +--
 .../taler-wallet-webextension/src/wxBackend.ts     | 308 ++++-------------
 21 files changed, 779 insertions(+), 790 deletions(-)

diff --git a/packages/taler-wallet-webextension/src/api/browser.ts 
b/packages/taler-wallet-webextension/src/api/browser.ts
deleted file mode 100644
index b69a4968..00000000
--- a/packages/taler-wallet-webextension/src/api/browser.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-function searchForTalerLinks(): string | undefined {
-  let found;
-  found = document.querySelector("a[href^='taler://'")
-  if (found) return found.toString()
-  found = document.querySelector("a[href^='taler+http://'")
-  if (found) return found.toString()
-  return undefined
-}
-
-async function getCurrentTab() {
-  let queryOptions = { active: true, currentWindow: true };
-  let [tab] = await chrome.tabs.query(queryOptions);
-  return tab;
-}
-
-
-
-export async function findTalerUriInActiveTab(): Promise<string | undefined> {
-  if (chrome.runtime.getManifest().manifest_version === 3) {
-    // manifest v3
-    const tab = await getCurrentTab();
-    const res = await chrome.scripting.executeScript({
-      target: {
-        tabId: tab.id!,
-        allFrames: true,
-      } as any,
-      func: searchForTalerLinks,
-      args: []
-    })
-    return res[0].result
-  }
-  return new Promise((resolve, reject) => {
-    //manifest v2
-    chrome.tabs.executeScript(
-      {
-        code: `
-        (() => {
-          let x = document.querySelector("a[href^='taler://'") || 
document.querySelector("a[href^='taler+http://'");
-          return x ? x.href.toString() : null;
-        })();
-        `,
-        allFrames: false,
-      },
-      (result) => {
-        if (chrome.runtime.lastError) {
-          console.error(chrome.runtime.lastError);
-          resolve(undefined);
-          return;
-        }
-        resolve(result[0]);
-      },
-    );
-  });
-}
diff --git a/packages/taler-wallet-webextension/src/background.ts 
b/packages/taler-wallet-webextension/src/background.ts
index d6aeddc1..9c572c17 100644
--- a/packages/taler-wallet-webextension/src/background.ts
+++ b/packages/taler-wallet-webextension/src/background.ts
@@ -23,14 +23,31 @@
 /**
  * Imports.
  */
+import { platform, setupPlatform } from "./platform/api";
+import firefoxAPI from "./platform/firefox"
+import chromeAPI from "./platform/chrome"
 import { wxMain } from "./wxBackend";
 
-const loadedFromWebpage = typeof window !== "undefined"
+const isFirefox = typeof (window as any)['InstallTrigger'] !== 'undefined'
 
-if (chrome.runtime.getManifest().manifest_version === 3) {
-  wxMain();
+//FIXME: create different entry point for any platform instead of 
+//switching in runtime
+if (isFirefox) {
+  console.log("Wallet setup for Firefox API")
+  setupPlatform(firefoxAPI)
 } else {
-  window.addEventListener("load", () => {
-    wxMain();
-  });
+  console.log("Wallet setup for Chrome API")
+  setupPlatform(chromeAPI)
+}
+
+try {
+  platform.registerOnInstalled(() => {
+    platform.openWalletPage("/welcome")
+  })
+} catch (e) {
+  console.error(e);
 }
+
+platform.notifyWhenAppIsReady(() => {
+  wxMain();
+})
diff --git a/packages/taler-wallet-webextension/src/chromeBadge.ts 
b/packages/taler-wallet-webextension/src/chromeBadge.ts
index 60585793..74c2fcd2 100644
--- a/packages/taler-wallet-webextension/src/chromeBadge.ts
+++ b/packages/taler-wallet-webextension/src/chromeBadge.ts
@@ -14,7 +14,7 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { isFirefox } from "./compat";
+import { platform } from "./platform/api";
 
 /**
  * Polyfill for requestAnimationFrame, which
@@ -210,7 +210,7 @@ export class ChromeBadge {
     if (this.animationRunning) {
       return;
     }
-    if (isFirefox()) {
+    if (platform.isFirefox()) {
       // Firefox does not support badge animations properly
       return;
     }
diff --git a/packages/taler-wallet-webextension/src/compat.ts 
b/packages/taler-wallet-webextension/src/compat.ts
deleted file mode 100644
index b17d0fb8..00000000
--- a/packages/taler-wallet-webextension/src/compat.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 INRIA
-
- 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.
-
- 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
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Compatibility helpers needed for browsers that don't implement
- * WebExtension APIs consistently.
- */
-
-// globalThis polyfill, see https://mathiasbynens.be/notes/globalthis
-(function () {
-  if (typeof globalThis === "object") return;
-  Object.defineProperty(Object.prototype, "__magic__", {
-    get: function () {
-      return this;
-    },
-    configurable: true, // This makes it possible to `delete` the getter later.
-  });
-  // @ts-ignore: polyfill magic
-  __magic__.globalThis = __magic__; // lolwat
-  // @ts-ignore: polyfill magic
-  delete Object.prototype.__magic__;
-})();
-
-export function isFirefox(): boolean {
-  const rt = chrome.runtime as any;
-  if (typeof rt.getBrowserInfo === "function") {
-    return true;
-  }
-  return false;
-}
-
-/**
- * Check if we are running under nodejs.
- */
-export function isNode(): boolean {
-  return typeof process !== "undefined" && process.release.name === "node";
-}
-
-/**
- * Compatibility API that works on multiple browsers.
- */
-export interface CrossBrowserPermissionsApi {
-  contains(
-    permissions: chrome.permissions.Permissions,
-    callback: (result: boolean) => void,
-  ): void;
-
-  addPermissionsListener(
-    callback: (permissions: chrome.permissions.Permissions) => void,
-  ): void;
-
-  request(
-    permissions: chrome.permissions.Permissions,
-    callback?: (granted: boolean) => void,
-  ): void;
-
-  remove(
-    permissions: chrome.permissions.Permissions,
-    callback?: (removed: boolean) => void,
-  ): void;
-}
-
-export function getPermissionsApi(): CrossBrowserPermissionsApi {
-  const myBrowser = (globalThis as any).browser;
-  if (
-    typeof myBrowser === "object" &&
-    typeof myBrowser.permissions === "object"
-  ) {
-    return {
-      addPermissionsListener: () => {
-        console.log("not supported for firefox")
-        // Not supported yet.
-      },
-      contains: myBrowser.permissions.contains,
-      request: myBrowser.permissions.request,
-      remove: myBrowser.permissions.remove,
-    };
-  } else {
-    return {
-      addPermissionsListener: chrome.permissions.onAdded.addListener.bind(
-        chrome.permissions.onAdded,
-      ),
-      contains: chrome.permissions.contains,
-      request: chrome.permissions.request,
-      remove: chrome.permissions.remove,
-    };
-  }
-}
diff --git a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx 
b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
index 0998cab7..0cffff69 100644
--- a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
+++ b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
@@ -17,7 +17,6 @@
 import { WalletDiagnostics } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useTranslationContext } from "../context/translation";
-import { PageLink } from "../renderHtml";
 
 interface Props {
   timedOut: boolean;
@@ -70,10 +69,7 @@ export function Diagnostics({ timedOut, diagnostics }: 
Props): VNode {
           <p>
             <i18n.Translate>
               Your wallet database is outdated. Currently automatic migration 
is
-              not supported. Please go{" "}
-              <PageLink pageName="/reset-required">
-                <i18n.Translate>here</i18n.Translate>
-              </PageLink>{" "}
+              not supported. Please go <i18n.Translate>here</i18n.Translate>
               to reset the wallet database.
             </i18n.Translate>
           </p>
diff --git a/packages/taler-wallet-webextension/src/context/iocContext.ts 
b/packages/taler-wallet-webextension/src/context/iocContext.ts
index 688e7b48..a24b0c1c 100644
--- a/packages/taler-wallet-webextension/src/context/iocContext.ts
+++ b/packages/taler-wallet-webextension/src/context/iocContext.ts
@@ -21,7 +21,7 @@
 
 import { createContext, h, VNode } from "preact";
 import { useContext } from "preact/hooks";
-import { findTalerUriInActiveTab } from "../api/browser";
+import { platform } from "../platform/api";
 
 interface Type {
   findTalerUriInActiveTab: () => Promise<string | undefined>;
@@ -45,5 +45,5 @@ export const IoCProviderForTesting = ({ value, children }: { 
value: Type, childr
 };
 
 export const IoCProviderForRuntime = ({ children }: { children: any }): VNode 
=> {
-  return h(Context.Provider, { value: { findTalerUriInActiveTab }, children });
+  return h(Context.Provider, { value: { findTalerUriInActiveTab: 
platform.findTalerUriInActiveTab }, children });
 };
diff --git a/packages/taler-wallet-webextension/src/cta/Refund.tsx 
b/packages/taler-wallet-webextension/src/cta/Refund.tsx
index efc436bc..790e8d9f 100644
--- a/packages/taler-wallet-webextension/src/cta/Refund.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Refund.tsx
@@ -20,11 +20,15 @@
  * @author sebasjm
  */
 
-import { Amounts, ApplyRefundResponse } from "@gnu-taler/taler-util";
+import {
+  amountFractionalBase,
+  AmountJson,
+  Amounts,
+  ApplyRefundResponse,
+} from "@gnu-taler/taler-util";
 import { h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { useTranslationContext } from "../context/translation";
-import { AmountView } from "../renderHtml";
 import * as wxApi from "../wxApi";
 
 interface Props {
@@ -120,3 +124,27 @@ export function RefundPage({ talerRefundUri }: Props): 
VNode {
 
   return <View applyResult={applyResult} />;
 }
+
+export function renderAmount(amount: AmountJson | string): VNode {
+  let a;
+  if (typeof amount === "string") {
+    a = Amounts.parse(amount);
+  } else {
+    a = amount;
+  }
+  if (!a) {
+    return <span>(invalid amount)</span>;
+  }
+  const x = a.value + a.fraction / amountFractionalBase;
+  return (
+    <span>
+      {x}&nbsp;{a.currency}
+    </span>
+  );
+}
+
+export const AmountView = ({
+  amount,
+}: {
+  amount: AmountJson | string;
+}): VNode => renderAmount(amount);
diff --git a/packages/taler-wallet-webextension/src/cta/Tip.tsx 
b/packages/taler-wallet-webextension/src/cta/Tip.tsx
index 71aa04a2..5767b500 100644
--- a/packages/taler-wallet-webextension/src/cta/Tip.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Tip.tsx
@@ -17,15 +17,19 @@
 /**
  * Page shown to the user to accept or ignore a tip from a merchant.
  *
- * @author sebasjm <dold@taler.net>
+ * @author sebasjm
  */
 
-import { PrepareTipResult } from "@gnu-taler/taler-util";
+import {
+  amountFractionalBase,
+  AmountJson,
+  Amounts,
+  PrepareTipResult,
+} from "@gnu-taler/taler-util";
 import { h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { Loading } from "../components/Loading";
 import { useTranslationContext } from "../context/translation";
-import { AmountView } from "../renderHtml";
 import * as wxApi from "../wxApi";
 
 interface Props {
@@ -136,3 +140,24 @@ export function TipPage({ talerTipUri }: Props): VNode {
     />
   );
 }
+
+function renderAmount(amount: AmountJson | string): VNode {
+  let a;
+  if (typeof amount === "string") {
+    a = Amounts.parse(amount);
+  } else {
+    a = amount;
+  }
+  if (!a) {
+    return <span>(invalid amount)</span>;
+  }
+  const x = a.value + a.fraction / amountFractionalBase;
+  return (
+    <span>
+      {x}&nbsp;{a.currency}
+    </span>
+  );
+}
+
+const AmountView = ({ amount }: { amount: AmountJson | string }): VNode =>
+  renderAmount(amount);
diff --git 
a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts 
b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
index 6bf6a7bd..66d71070 100644
--- a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
@@ -16,7 +16,7 @@
 
 import { useState, useEffect } from "preact/hooks";
 import * as wxApi from "../wxApi";
-import { getPermissionsApi } from "../compat";
+import { platform } from "../platform/api";
 import { getReadRequestPermissions } from "../permissions";
 
 export function useExtendedPermissions(): [boolean, () => Promise<void>] {
@@ -40,24 +40,41 @@ async function handleExtendedPerm(isEnabled: boolean, 
onChange: (value: boolean)
   if (!isEnabled) {
     // We set permissions here, since apparently FF wants this to be done
     // as the result of an input event ...
-    return new Promise<void>((res) => {
-      getPermissionsApi().request(getReadRequestPermissions(), async (granted: 
boolean) => {
-        console.log("permissions granted:", granted);
-        if (chrome.runtime.lastError) {
-          console.error("error requesting permissions");
-          console.error(chrome.runtime.lastError);
-          onChange(false);
-          return;
-        }
-        try {
-          const res = await wxApi.setExtendedPermissions(granted);
-          onChange(res.newValue);
-        } finally {
-          res()
-        }
+    const granted = await 
platform.getPermissionsApi().request(getReadRequestPermissions());
+    console.log("permissions granted:", granted);
+    const lastError = platform.getLastError();
+    if (lastError) {
+      console.error("error requesting permissions");
+      console.error(lastError);
+      onChange(false);
+      return;
+    }
+    // try {
+    const res = await wxApi.setExtendedPermissions(granted);
+    onChange(res.newValue);
+    // } finally {
+    //   return
+    // }
+
+    // return new Promise<void>((res) => {
+    //   platform.getPermissionsApi().request(getReadRequestPermissions(), 
async (granted: boolean) => {
+    //     console.log("permissions granted:", granted);
+    //     const lastError = getLastError()
+    //     if (lastError) {
+    //       console.error("error requesting permissions");
+    //       console.error(lastError);
+    //       onChange(false);
+    //       return;
+    //     }
+    //     try {
+    //       const res = await wxApi.setExtendedPermissions(granted);
+    //       onChange(res.newValue);
+    //     } finally {
+    //       res()
+    //     }
 
-      });
-    })
+    //   });
+    // })
   }
   await wxApi.setExtendedPermissions(false).then(r => onChange(r.newValue));
   return
diff --git a/packages/taler-wallet-webextension/src/platform/api.ts 
b/packages/taler-wallet-webextension/src/platform/api.ts
new file mode 100644
index 00000000..9b4e02ff
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/platform/api.ts
@@ -0,0 +1,90 @@
+/*
+ This file is part of TALER
+ (C) 2017 INRIA
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { CoreApiResponse, NotificationType, TalerUriType } from 
"@gnu-taler/taler-util";
+
+export interface Permissions {
+  /**
+   * List of named permissions.
+   */
+  permissions?: string[] | undefined;
+  /**
+   * List of origin permissions. Anything listed here must be a subset of a 
+   * host that appears in the optional_permissions list in the manifest. 
+   * 
+   */
+  origins?: string[] | undefined;
+
+}
+
+/**
+ * Compatibility API that works on multiple browsers.
+ */
+export interface CrossBrowserPermissionsApi {
+  contains(p: Permissions): Promise<boolean>;
+  request(p: Permissions): Promise<boolean>;
+  remove(p: Permissions): Promise<boolean>;
+
+  addPermissionsListener(callback: (p: Permissions) => void): void;
+
+}
+
+export type MessageFromBackend = {
+  type: NotificationType;
+};
+
+export interface WalletVersion {
+  version_name?: string | undefined;
+  version: string;
+}
+
+/**
+ * Compatibility helpers needed for browsers that don't implement
+ * WebExtension APIs consistently.
+ */
+export interface PlatformAPI {
+  /**
+   * check if the platform is firefox
+   */
+  isFirefox(): boolean;
+  /**
+   * 
+   */
+  getPermissionsApi(): CrossBrowserPermissionsApi;
+  notifyWhenAppIsReady(callback: () => void): void;
+  openWalletURIFromPopup(uriType: TalerUriType, talerUri: string): void;
+  openWalletPage(page: string): void;
+  openWalletPageFromPopup(page: string): void;
+  setMessageToWalletBackground(operation: string, payload: any): 
Promise<CoreApiResponse>;
+  listenToWalletNotifications(listener: (m: any) => void): () => void;
+  sendMessageToAllChannels(message: MessageFromBackend): void;
+  registerAllIncomingConnections(): void;
+  registerOnNewMessage(onNewMessage: (message: any, sender: any, callback: 
any) => void): void;
+  registerReloadOnNewVersion(): void;
+  redirectTabToWalletPage(tabId: number, page: string): void;
+  getWalletVersion(): WalletVersion;
+  registerTalerHeaderListener(onHeader: (tabId: number, url: string) => void): 
void;
+  registerOnInstalled(callback: () => void): void;
+  useServiceWorkerAsBackgroundProcess(): boolean;
+  getLastError(): string | undefined;
+  searchForTalerLinks(): string | undefined;
+  findTalerUriInActiveTab(): Promise<string | undefined>;
+}
+
+export let platform: PlatformAPI = undefined as any;
+export function setupPlatform(impl: PlatformAPI) {
+  platform = impl;
+}
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts 
b/packages/taler-wallet-webextension/src/platform/chrome.ts
new file mode 100644
index 00000000..dada23c5
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -0,0 +1,371 @@
+/*
+ This file is part of TALER
+ (C) 2017 INRIA
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { TalerUriType } from "@gnu-taler/taler-util";
+import { getReadRequestPermissions } from "../permissions";
+import { CrossBrowserPermissionsApi, MessageFromBackend, Permissions, 
PlatformAPI } from "./api.js";
+
+const api: PlatformAPI = {
+  isFirefox,
+  findTalerUriInActiveTab,
+  getLastError,
+  getPermissionsApi,
+  getWalletVersion,
+  listenToWalletNotifications,
+  notifyWhenAppIsReady,
+  openWalletPage,
+  openWalletPageFromPopup,
+  openWalletURIFromPopup,
+  redirectTabToWalletPage,
+  registerAllIncomingConnections,
+  registerOnInstalled,
+  registerOnNewMessage,
+  registerReloadOnNewVersion,
+  registerTalerHeaderListener,
+  searchForTalerLinks,
+  sendMessageToAllChannels,
+  setMessageToWalletBackground,
+  useServiceWorkerAsBackgroundProcess
+}
+
+export default api;
+
+function isFirefox(): boolean {
+  return false;
+}
+
+export function contains(p: Permissions): Promise<boolean> {
+  return new Promise((res, rej) => {
+    chrome.permissions.contains(p, (resp) => {
+      const le = getLastError()
+      if (le) {
+        rej(le)
+      }
+      res(resp)
+    })
+  })
+}
+
+export async function request(p: Permissions): Promise<boolean> {
+  return new Promise((res, rej) => {
+    chrome.permissions.request(p, (resp) => {
+      const le = getLastError()
+      if (le) {
+        rej(le)
+      }
+      res(resp)
+    })
+  })
+}
+
+export async function remove(p: Permissions): Promise<boolean> {
+  return new Promise((res, rej) => {
+    chrome.permissions.remove(p, (resp) => {
+      const le = getLastError()
+      if (le) {
+        rej(le)
+      }
+      res(resp)
+    })
+  })
+}
+
+function addPermissionsListener(callback: (p: Permissions) => void): void {
+  console.log("addPermissionListener is not supported for Firefox");
+  chrome.permissions.onAdded.addListener(callback)
+}
+
+function getPermissionsApi(): CrossBrowserPermissionsApi {
+  return {
+    addPermissionsListener, contains, request, remove
+  }
+}
+
+/**
+ * 
+ * @param callback function to be called
+ */
+function notifyWhenAppIsReady(callback: () => void) {
+  if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
+    callback()
+  } else {
+    window.addEventListener("load", callback);
+  }
+}
+
+
+function openWalletURIFromPopup(uriType: TalerUriType, talerUri: string) {
+  let url: string | undefined = undefined;
+  switch (uriType) {
+    case TalerUriType.TalerWithdraw:
+      url = 
chrome.runtime.getURL(`static/wallet.html#/cta/withdraw?talerWithdrawUri=${talerUri}`);
+      break;
+    case TalerUriType.TalerPay:
+      url = 
chrome.runtime.getURL(`static/wallet.html#/cta/pay?talerPayUri=${talerUri}`);
+      break;
+    case TalerUriType.TalerTip:
+      url = 
chrome.runtime.getURL(`static/wallet.html#/cta/tip?talerTipUri=${talerUri}`);
+      break;
+    case TalerUriType.TalerRefund:
+      url = 
chrome.runtime.getURL(`static/wallet.html#/cta/refund?talerRefundUri=${talerUri}`);
+      break;
+    default:
+      console.warn(
+        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
+      );
+      return;
+  }
+
+  chrome.tabs.create(
+    { active: true, url, },
+    () => { window.close(); },
+  );
+}
+
+function openWalletPage(page: string) {
+  const url = chrome.runtime.getURL(`/static/wallet.html#${page}`)
+  chrome.tabs.create(
+    { active: true, url, },
+  );
+}
+
+function openWalletPageFromPopup(page: string) {
+  const url = chrome.runtime.getURL(`/static/wallet.html#${page}`)
+  chrome.tabs.create(
+    { active: true, url, },
+    () => { window.close(); },
+  );
+}
+
+async function setMessageToWalletBackground(operation: string, payload: any): 
Promise<any> {
+  return new Promise<any>((resolve, reject) => {
+    chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => 
{
+      if (chrome.runtime.lastError) {
+        reject(chrome.runtime.lastError.message)
+      }
+      resolve(resp)
+      // return true to keep the channel open
+      return true;
+    })
+  })
+}
+
+let notificationPort: chrome.runtime.Port | undefined;
+function listenToWalletNotifications(listener: (m: any) => void) {
+  if (notificationPort === undefined) {
+    notificationPort = chrome.runtime.connect({ name: "notifications" })
+  }
+  notificationPort.onMessage.addListener(listener)
+  function removeListener() {
+    if (notificationPort !== undefined) {
+      notificationPort.onMessage.removeListener(listener)
+    }
+  }
+  return removeListener
+}
+
+
+const allPorts: chrome.runtime.Port[] = [];
+
+function sendMessageToAllChannels(message: MessageFromBackend) {
+  for (const notif of allPorts) {
+    // const message: MessageFromBackend = { type: msg.type };
+    try {
+      notif.postMessage(message);
+    } catch (e) {
+      console.error(e);
+    }
+  }
+}
+
+function registerAllIncomingConnections() {
+  chrome.runtime.onConnect.addListener((port) => {
+    allPorts.push(port);
+    port.onDisconnect.addListener((discoPort) => {
+      const idx = allPorts.indexOf(discoPort);
+      if (idx >= 0) {
+        allPorts.splice(idx, 1);
+      }
+    });
+  });
+}
+
+function registerOnNewMessage(cb: (message: any, sender: any, callback: any) 
=> void) {
+  chrome.runtime.onMessage.addListener((m, s, c) => {
+    cb(m, s, c)
+
+    // keep the connection open
+    return true;
+  });
+}
+
+function registerReloadOnNewVersion() {
+  // Explicitly unload the extension page as soon as an update is available,
+  // so the update gets installed as soon as possible.
+  chrome.runtime.onUpdateAvailable.addListener((details) => {
+    console.log("update available:", details);
+    chrome.runtime.reload();
+  });
+
+}
+
+function redirectTabToWalletPage(
+  tabId: number,
+  page: string,
+) {
+  const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
+  console.log("redirecting tabId: ", tabId, " to: ", url);
+  chrome.tabs.update(tabId, { url });
+}
+
+interface WalletVersion {
+  version_name?: string | undefined;
+  version: string;
+}
+
+function getWalletVersion(): WalletVersion {
+  const manifestData = chrome.runtime.getManifest();
+  return manifestData;
+}
+
+
+function registerTalerHeaderListener(callback: (tabId: number, url: string) => 
void): void {
+  console.log("setting up header listener");
+
+  function headerListener(
+    details: chrome.webRequest.WebResponseHeadersDetails,
+  ) {
+    if (chrome.runtime.lastError) {
+      console.error(JSON.stringify(chrome.runtime.lastError));
+      return;
+    }
+    if (
+      details.statusCode === 402 ||
+      details.statusCode === 202 ||
+      details.statusCode === 200
+    ) {
+      const values = (details.responseHeaders || [])
+        .filter(h => h.name.toLowerCase() === 'taler')
+        .map(h => h.value)
+        .filter((value): value is string => !!value)
+      if (values.length > 0) {
+        callback(details.tabId, values[0])
+      }
+    }
+    return;
+  }
+
+  getPermissionsApi().contains(getReadRequestPermissions()).then(result => {
+    //if there is a handler already, remove it
+    if (
+      "webRequest" in chrome &&
+      "onHeadersReceived" in chrome.webRequest &&
+      chrome.webRequest.onHeadersReceived.hasListener(headerListener)
+    ) {
+      chrome.webRequest.onHeadersReceived.removeListener(headerListener);
+    }
+    //if the result was positive, add the headerListener
+    if (result) {
+      chrome.webRequest.onHeadersReceived.addListener(
+        headerListener,
+        { urls: ["<all_urls>"] },
+        ["responseHeaders"],
+      );
+    }
+    //notify the browser about this change, this operation is expensive
+    if ("webRequest" in chrome) {
+      chrome.webRequest.handlerBehaviorChanged(() => {
+        if (chrome.runtime.lastError) {
+          console.error(JSON.stringify(chrome.runtime.lastError));
+        }
+      });
+    }
+  });
+}
+
+function registerOnInstalled(callback: () => void) {
+  // This needs to be outside of main, as Firefox won't fire the event if
+  // the listener isn't created synchronously on loading the backend.
+  chrome.runtime.onInstalled.addListener((details) => {
+    console.log("onInstalled with reason", details.reason);
+    if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
+      callback()
+    }
+  });
+}
+
+function useServiceWorkerAsBackgroundProcess() {
+  return chrome.runtime.getManifest().manifest_version === 3
+}
+
+function getLastError() {
+  return chrome.runtime.lastError?.message;
+}
+
+
+function searchForTalerLinks(): string | undefined {
+  let found;
+  found = document.querySelector("a[href^='taler://'")
+  if (found) return found.toString()
+  found = document.querySelector("a[href^='taler+http://'")
+  if (found) return found.toString()
+  return undefined
+}
+
+async function getCurrentTab() {
+  let queryOptions = { active: true, currentWindow: true };
+  let [tab] = await chrome.tabs.query(queryOptions);
+  return tab;
+}
+
+
+async function findTalerUriInActiveTab(): Promise<string | undefined> {
+  if (chrome.runtime.getManifest().manifest_version === 3) {
+    // manifest v3
+    const tab = await getCurrentTab();
+    const res = await chrome.scripting.executeScript({
+      target: {
+        tabId: tab.id!,
+        allFrames: true,
+      } as any,
+      func: searchForTalerLinks,
+      args: []
+    })
+    return res[0].result
+  }
+  return new Promise((resolve, reject) => {
+    //manifest v2
+    chrome.tabs.executeScript(
+      {
+        code: `
+        (() => {
+          let x = document.querySelector("a[href^='taler://'") || 
document.querySelector("a[href^='taler+http://'");
+          return x ? x.href.toString() : null;
+        })();
+        `,
+        allFrames: false,
+      },
+      (result) => {
+        if (chrome.runtime.lastError) {
+          console.error(JSON.stringify(chrome.runtime.lastError));
+          resolve(undefined);
+          return;
+        }
+        resolve(result[0]);
+      },
+    );
+  });
+}
diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts 
b/packages/taler-wallet-webextension/src/platform/firefox.ts
new file mode 100644
index 00000000..dad90626
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/platform/firefox.ts
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ (C) 2017 INRIA
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { CrossBrowserPermissionsApi, Permissions, PlatformAPI } from 
"./api.js";
+import chromePlatform, { contains as chromeContains, remove as chromeRemove, 
request as chromeRequest } from "./chrome";
+
+const api: PlatformAPI = {
+  ...chromePlatform,
+  isFirefox,
+  getPermissionsApi,
+  notifyWhenAppIsReady,
+  redirectTabToWalletPage,
+  useServiceWorkerAsBackgroundProcess
+};
+
+export default api;
+
+function isFirefox(): boolean {
+  return true
+}
+
+
+function addPermissionsListener(callback: (p: Permissions) => void): void {
+  console.log("addPermissionListener is not supported for Firefox")
+}
+
+function getPermissionsApi(): CrossBrowserPermissionsApi {
+  return {
+    addPermissionsListener,
+    contains: chromeContains,
+    request: chromeRequest,
+    remove: chromeRemove
+  }
+}
+
+/**
+ * 
+ * @param callback function to be called
+ */
+function notifyWhenAppIsReady(callback: () => void) {
+  if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
+    callback()
+  } else {
+    window.addEventListener("load", callback);
+  }
+}
+
+
+function redirectTabToWalletPage(
+  tabId: number,
+  page: string,
+) {
+  const url = chrome.runtime.getURL(`/static/wallet.html#${page}`);
+  console.log("redirecting tabId: ", tabId, " to: ", url);
+  chrome.tabs.update(tabId, { url, loadReplace: true } as any);
+}
+
+
+function useServiceWorkerAsBackgroundProcess() {
+  return false
+}
diff --git a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx 
b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
index 3deea032..d47b8ce7 100644
--- a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
@@ -375,16 +375,6 @@ function toBase64(str: string): string {
   );
 }
 
-export function reload(): void {
-  try {
-    // eslint-disable-next-line no-undef
-    chrome.runtime.reload();
-    window.close();
-  } catch (e) {
-    // Functionality missing in firefox, ignore!
-  }
-}
-
 function runIntegrationTest() {}
 
 export async function confirmReset(
@@ -395,13 +385,3 @@ export async function confirmReset(
     window.close();
   }
 }
-
-export function openExtensionPage(page: string) {
-  return () => {
-    // eslint-disable-next-line no-undef
-    chrome.tabs.create({
-      // eslint-disable-next-line no-undef
-      url: chrome.runtime.getURL(page),
-    });
-  };
-}
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
index 9ac83a57..a1082ad9 100644
--- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -21,36 +21,21 @@
 
 import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
 import { Fragment, h } from "preact";
+import { platform } from "../platform/api";
 import { ButtonPrimary, ButtonSuccess } from "../components/styled";
 import { useTranslationContext } from "../context/translation";
-import { actionForTalerUri } from "../utils/index";
 
 export interface Props {
   url: string;
   onDismiss: () => void;
 }
 
-async function getCurrentTab(): Promise<chrome.tabs.Tab> {
-  let queryOptions = { active: true, currentWindow: true };
-  const tab = await new Promise<chrome.tabs.Tab>((res, rej) => {
-    chrome.tabs.query(queryOptions, (tabs) => {
-      res(tabs[0]);
-    });
-  });
-  return tab;
-}
-
-async function navigateTo(url?: string) {
-  if (!url) return;
-  const tab = await getCurrentTab();
-  if (!tab.id) return;
-  await chrome.tabs.update(tab.id, { url });
-  window.close();
-}
-
 export function TalerActionFound({ url, onDismiss }: Props) {
   const uriType = classifyTalerUri(url);
   const { i18n } = useTranslationContext();
+  function redirectToWallet() {
+    platform.openWalletURIFromPopup(uriType, url);
+  }
   return (
     <Fragment>
       <section>
@@ -62,11 +47,7 @@ export function TalerActionFound({ url, onDismiss }: Props) {
             <p>
               <i18n.Translate>This page has pay action.</i18n.Translate>
             </p>
-            <ButtonSuccess
-              onClick={() => {
-                navigateTo(actionForTalerUri(uriType, url));
-              }}
-            >
+            <ButtonSuccess onClick={redirectToWallet}>
               <i18n.Translate>Open pay page</i18n.Translate>
             </ButtonSuccess>
           </div>
@@ -78,11 +59,7 @@ export function TalerActionFound({ url, onDismiss }: Props) {
                 This page has a withdrawal action.
               </i18n.Translate>
             </p>
-            <ButtonSuccess
-              onClick={() => {
-                navigateTo(actionForTalerUri(uriType, url));
-              }}
-            >
+            <ButtonSuccess onClick={redirectToWallet}>
               <i18n.Translate>Open withdraw page</i18n.Translate>
             </ButtonSuccess>
           </div>
@@ -92,11 +69,7 @@ export function TalerActionFound({ url, onDismiss }: Props) {
             <p>
               <i18n.Translate>This page has a tip action.</i18n.Translate>
             </p>
-            <ButtonSuccess
-              onClick={() => {
-                navigateTo(actionForTalerUri(uriType, url));
-              }}
-            >
+            <ButtonSuccess onClick={redirectToWallet}>
               <i18n.Translate>Open tip page</i18n.Translate>
             </ButtonSuccess>
           </div>
@@ -108,11 +81,7 @@ export function TalerActionFound({ url, onDismiss }: Props) 
{
                 This page has a notify reserve action.
               </i18n.Translate>
             </p>
-            <ButtonSuccess
-              onClick={() => {
-                navigateTo(actionForTalerUri(uriType, url));
-              }}
-            >
+            <ButtonSuccess onClick={redirectToWallet}>
               <i18n.Translate>Notify</i18n.Translate>
             </ButtonSuccess>
           </div>
@@ -122,11 +91,7 @@ export function TalerActionFound({ url, onDismiss }: Props) 
{
             <p>
               <i18n.Translate>This page has a refund action.</i18n.Translate>
             </p>
-            <ButtonSuccess
-              onClick={() => {
-                navigateTo(actionForTalerUri(uriType, url));
-              }}
-            >
+            <ButtonSuccess onClick={redirectToWallet}>
               <i18n.Translate>Open refund page</i18n.Translate>
             </ButtonSuccess>
           </div>
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 7ee6c8e4..dfb12666 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -17,7 +17,7 @@
 /**
  * Main entry point for extension pages.
  *
- * @author sebasjm <dold@taler.net>
+ * @author sebasjm
  */
 
 import { setupI18n } from "@gnu-taler/taler-util";
@@ -37,6 +37,9 @@ import {
 import { useTalerActionURL } from "./hooks/useTalerActionURL";
 import { strings } from "./i18n/strings";
 import { Pages, PopupNavBar } from "./NavigationBar";
+import { platform, setupPlatform } from "./platform/api";
+import chromeAPI from "./platform/chrome";
+import firefoxAPI from "./platform/firefox";
 import { BalancePage } from "./popup/BalancePage";
 import { TalerActionFound } from "./popup/TalerActionFound";
 import { BackupPage } from "./wallet/BackupPage";
@@ -59,6 +62,17 @@ function main(): void {
 
 setupI18n("en", strings);
 
+//FIXME: create different entry point for any platform instead of
+//switching in runtime
+const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined";
+if (isFirefox) {
+  console.log("Wallet setup for Firefox API");
+  setupPlatform(firefoxAPI);
+} else {
+  console.log("Wallet setup for Chrome API");
+  setupPlatform(chromeAPI);
+}
+
 if (document.readyState === "loading") {
   document.addEventListener("DOMContentLoaded", main);
 } else {
@@ -106,7 +120,7 @@ function Application(): VNode {
                     route(Pages.balance_deposit.replace(":currency", currency))
                   }
                   goToWalletHistory={(currency: string) =>
-                    route(Pages.balance_history.replace(":currency", currency))
+                    route(Pages.balance_history.replace(":currency?", 
currency))
                   }
                 />
 
@@ -180,19 +194,10 @@ function Application(): VNode {
 }
 
 function RedirectToWalletPage(): VNode {
-  const page = document.location.hash || "#/";
+  const page = (document.location.hash || "#/").replace("#", "");
   const [showText, setShowText] = useState(false);
   useEffect(() => {
-    chrome.tabs.create(
-      {
-        active: true,
-        // eslint-disable-next-line no-undef
-        url: chrome.runtime.getURL(`/static/wallet.html${page}`),
-      },
-      () => {
-        window.close();
-      },
-    );
+    platform.openWalletPageFromPopup(page);
     setTimeout(() => {
       setShowText(true);
     }, 250);
diff --git a/packages/taler-wallet-webextension/src/renderHtml.tsx 
b/packages/taler-wallet-webextension/src/renderHtml.tsx
deleted file mode 100644
index 1e482cce..00000000
--- a/packages/taler-wallet-webextension/src/renderHtml.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 INRIA
-
- 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.
-
- 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
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Helpers functions to render Taler-related data structures to HTML.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import {
-  AmountJson,
-  Amounts,
-  amountFractionalBase,
-} from "@gnu-taler/taler-util";
-import { Component, ComponentChildren, h, VNode } from "preact";
-
-/**
- * Render amount as HTML, which non-breaking space between
- * decimal value and currency.
- */
-export function renderAmount(amount: AmountJson | string): VNode {
-  let a;
-  if (typeof amount === "string") {
-    a = Amounts.parse(amount);
-  } else {
-    a = amount;
-  }
-  if (!a) {
-    return <span>(invalid amount)</span>;
-  }
-  const x = a.value + a.fraction / amountFractionalBase;
-  return (
-    <span>
-      {x}&nbsp;{a.currency}
-    </span>
-  );
-}
-
-export const AmountView = ({
-  amount,
-}: {
-  amount: AmountJson | string;
-}): VNode => renderAmount(amount);
-
-/**
- * Abbreviate a string to a given length, and show the full
- * string on hover as a tooltip.
- */
-export function abbrev(s: string, n = 5): VNode {
-  let sAbbrev = s;
-  if (s.length > n) {
-    sAbbrev = s.slice(0, n) + "..";
-  }
-  return (
-    <span class="abbrev" title={s}>
-      {sAbbrev}
-    </span>
-  );
-}
-
-interface CollapsibleState {
-  collapsed: boolean;
-}
-
-interface CollapsibleProps {
-  initiallyCollapsed: boolean;
-  title: string;
-}
-
-/**
- * Component that shows/hides its children when clicking
- * a heading.
- */
-export class Collapsible extends Component<CollapsibleProps, CollapsibleState> 
{
-  constructor(props: CollapsibleProps) {
-    super(props);
-    this.state = { collapsed: props.initiallyCollapsed };
-  }
-  render(): VNode {
-    const doOpen = (e: any): void => {
-      this.setState({ collapsed: false });
-      e.preventDefault();
-    };
-    const doClose = (e: any): void => {
-      this.setState({ collapsed: true });
-      e.preventDefault();
-    };
-    if (this.state.collapsed) {
-      return (
-        <h2>
-          <a class="opener opener-collapsed" href="#" onClick={doOpen}>
-            {" "}
-            {this.props.title}
-          </a>
-        </h2>
-      );
-    }
-    return (
-      <div>
-        <h2>
-          <a class="opener opener-open" href="#" onClick={doClose}>
-            {" "}
-            {this.props.title}
-          </a>
-        </h2>
-        {this.props.children}
-      </div>
-    );
-  }
-}
-
-interface ExpanderTextProps {
-  text: string;
-}
-
-/**
- * Show a heading with a toggle to show/hide the expandable content.
- */
-export function ExpanderText({ text }: ExpanderTextProps): VNode {
-  return <span>{text}</span>;
-}
-
-export interface LoadingButtonProps
-  extends h.JSX.HTMLAttributes<HTMLButtonElement> {
-  isLoading: boolean;
-}
-
-export function ProgressButton({
-  isLoading,
-  ...rest
-}: LoadingButtonProps): VNode {
-  return (
-    <button class="pure-button pure-button-primary" type="button" {...rest}>
-      {isLoading ? (
-        <span>
-          <object class="svg-icon svg-baseline" data="/img/spinner-bars.svg" />
-        </span>
-      ) : null}{" "}
-      {rest.children}
-    </button>
-  );
-}
-
-export function PageLink(props: {
-  pageName: string;
-  children?: ComponentChildren;
-}): VNode {
-  // eslint-disable-next-line no-undef
-
-  const url =
-    typeof chrome === "undefined"
-      ? undefined
-      : // eslint-disable-next-line no-undef
-        chrome.runtime?.getURL(`/static/wallet.html#/${props.pageName}`);
-  return (
-    <a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
-      {props.children}
-    </a>
-  );
-}
diff --git a/packages/taler-wallet-webextension/src/utils/index.ts 
b/packages/taler-wallet-webextension/src/utils/index.ts
index cef0595d..e5447f9c 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -187,52 +187,3 @@ export function amountToString(text: AmountJson): string {
   return `${amount} ${aj.currency}`;
 }
 
-export function actionForTalerUri(
-  uriType: TalerUriType,
-  talerUri: string,
-): string | undefined {
-  switch (uriType) {
-    case TalerUriType.TalerWithdraw:
-      return makeExtensionUrlWithParams("static/wallet.html#/cta/withdraw", {
-        talerWithdrawUri: talerUri,
-      });
-    case TalerUriType.TalerPay:
-      return makeExtensionUrlWithParams("static/wallet.html#/cta/pay", {
-        talerPayUri: talerUri,
-      });
-    case TalerUriType.TalerTip:
-      return makeExtensionUrlWithParams("static/wallet.html#/cta/tip", {
-        talerTipUri: talerUri,
-      });
-    case TalerUriType.TalerRefund:
-      return makeExtensionUrlWithParams("static/wallet.html#/cta/refund", {
-        talerRefundUri: talerUri,
-      });
-    case TalerUriType.TalerNotifyReserve:
-      // FIXME: implement
-      break;
-    default:
-      console.warn(
-        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
-      );
-      break;
-  }
-  return undefined;
-}
-
-function makeExtensionUrlWithParams(
-  url: string,
-  params?: { [name: string]: string | undefined },
-): string {
-  // eslint-disable-next-line no-undef
-  const innerUrl = new URL(chrome.runtime.getURL("/" + url));
-  if (params) {
-    const hParams = Object.keys(params)
-      .map((k) => `${k}=${params[k]}`)
-      .join("&");
-    innerUrl.hash = innerUrl.hash + "?" + hParams;
-  }
-  return innerUrl.href;
-}
-
-
diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
index 3516bfbf..bebf036c 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx
@@ -1,9 +1,9 @@
 import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
+import { platform } from "../platform/api";
 import { Button, ButtonSuccess, InputWithLabel } from "../components/styled";
 import { useTranslationContext } from "../context/translation";
-import { actionForTalerUri } from "../utils/index";
 
 export interface Props {
   onCancel: () => void;
@@ -14,6 +14,10 @@ export function AddNewActionView({ onCancel }: Props): VNode 
{
   const uriType = classifyTalerUri(url);
   const { i18n } = useTranslationContext();
 
+  function redirectToWallet() {
+    platform.openWalletURIFromPopup(uriType, url);
+  }
+
   return (
     <Fragment>
       <section>
@@ -37,12 +41,7 @@ export function AddNewActionView({ onCancel }: Props): VNode 
{
           <i18n.Translate>Cancel</i18n.Translate>
         </Button>
         {uriType !== TalerUriType.Unknown && (
-          <ButtonSuccess
-            onClick={() => {
-              // eslint-disable-next-line no-undef
-              chrome.tabs.create({ url: actionForTalerUri(uriType, url) });
-            }}
-          >
+          <ButtonSuccess onClick={redirectToWallet}>
             {(() => {
               switch (uriType) {
                 case TalerUriType.TalerNotifyReserve:
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index 2f53917e..e7cb1f5e 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -17,7 +17,7 @@
 /**
  * Main entry point for extension pages.
  *
- * @author sebasjm <dold@taler.net>
+ * @author sebasjm
  */
 
 import { setupI18n } from "@gnu-taler/taler-util";
@@ -28,12 +28,7 @@ import Match from "preact-router/match";
 import { useEffect, useState } from "preact/hooks";
 import { LogoHeader } from "./components/LogoHeader";
 import PendingTransactions from "./components/PendingTransactions";
-import {
-  NavigationHeader,
-  NavigationHeaderHolder,
-  SuccessBox,
-  WalletBox,
-} from "./components/styled";
+import { SuccessBox, WalletBox } from "./components/styled";
 import { DevContextProvider } from "./context/devContext";
 import { IoCProviderForRuntime } from "./context/iocContext";
 import {
@@ -46,6 +41,9 @@ import { TipPage } from "./cta/Tip";
 import { WithdrawPage } from "./cta/Withdraw";
 import { strings } from "./i18n/strings";
 import { Pages, WalletNavBar } from "./NavigationBar";
+import { setupPlatform } from "./platform/api";
+import chromeAPI from "./platform/chrome";
+import firefoxAPI from "./platform/firefox";
 import { DeveloperPage } from "./popup/DeveloperPage";
 import { BackupPage } from "./wallet/BackupPage";
 import { DepositPage } from "./wallet/DepositPage";
@@ -75,6 +73,17 @@ function main(): void {
 
 setupI18n("en", strings);
 
+const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined";
+//FIXME: create different entry point for any platform instead of
+//switching in runtime
+if (isFirefox) {
+  console.log("Wallet setup for Firefox API");
+  setupPlatform(firefoxAPI);
+} else {
+  console.log("Wallet setup for Chrome API");
+  setupPlatform(chromeAPI);
+}
+
 if (document.readyState === "loading") {
   document.addEventListener("DOMContentLoaded", main);
 } else {
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index 2071f85e..ee2a8106 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -61,7 +61,7 @@ import {
 } from "@gnu-taler/taler-wallet-core";
 import { DepositFee } from 
"@gnu-taler/taler-wallet-core/src/operations/deposits";
 import type { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
-import { MessageFromBackend } from "./wxBackend";
+import { platform, MessageFromBackend } from "./platform/api";
 
 /**
  *
@@ -95,27 +95,18 @@ export interface UpgradeResponse {
 }
 
 async function callBackend(operation: string, payload: any): Promise<any> {
-  return new Promise<any>((resolve, reject) => {
-    // eslint-disable-next-line no-undef
-    chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => 
{
-      // eslint-disable-next-line no-undef
-      if (chrome.runtime.lastError) {
-        console.log("Error calling backend");
-        reject(
-          new Error(
-            `Error contacting backend: ${chrome.runtime.lastError.message}`,
-          ),
-        );
-      }
-      console.log("got response", resp);
-      const r = resp as CoreApiResponse;
-      if (r.type === "error") {
-        reject(TalerError.fromUncheckedDetail(r.error));
-        return;
-      }
-      resolve(r.result);
-    });
-  });
+  let response: CoreApiResponse;
+  try {
+    response = await platform.setMessageToWalletBackground(operation, payload)
+  } catch (e) {
+    console.log("Error calling backend");
+    throw new Error(`Error contacting backend: ${e}`)
+  }
+  console.log("got response", response);
+  if (response.type === "error") {
+    throw new TalerError.fromUncheckedDetail(response.error);
+  }
+  return response.result;
 }
 
 /**
@@ -422,20 +413,12 @@ export function importDB(dump: any): Promise<void> {
   return callBackend("importDb", { dump });
 }
 
-export function onUpdateNotification(
-  messageTypes: Array<NotificationType>,
-  doCallback: () => void,
-): () => void {
-  // eslint-disable-next-line no-undef
-  const port = chrome.runtime.connect({ name: "notifications" });
+export function onUpdateNotification(messageTypes: Array<NotificationType>, 
doCallback: () => void): () => void {
   const listener = (message: MessageFromBackend): void => {
     const shouldNotify = messageTypes.includes(message.type);
     if (shouldNotify) {
       doCallback();
     }
   };
-  port.onMessage.addListener(listener);
-  return () => {
-    port.onMessage.removeListener(listener);
-  };
+  return platform.listenToWalletNotifications(listener)
 }
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index b7a0cdc5..048c8fc7 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -26,11 +26,9 @@
 import {
   classifyTalerUri,
   CoreApiResponse,
-  CoreApiResponseSuccess,
-  NotificationType,
-  TalerErrorCode,
+  CoreApiResponseSuccess, TalerErrorCode,
   TalerUriType,
-  WalletDiagnostics,
+  WalletDiagnostics
 } from "@gnu-taler/taler-util";
 import {
   DbAccess,
@@ -40,13 +38,13 @@ import {
   openPromise,
   openTalerDatabase,
   Wallet,
-  WalletStoresV1,
+  WalletStoresV1
 } from "@gnu-taler/taler-wallet-core";
 import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
 import { BrowserHttpLib } from "./browserHttpLib";
-import { getPermissionsApi, isFirefox } from "./compat";
 import { getReadRequestPermissions } from "./permissions";
-import { SynchronousCryptoWorkerFactory } from 
"./serviceWorkerCryptoWorkerFactory.js";
+import { MessageFromBackend, platform } from "./platform/api";
+import { SynchronousCryptoWorkerFactory } from 
"./serviceWorkerCryptoWorkerFactory";
 import { ServiceWorkerHttpLib } from "./serviceWorkerHttpLib";
 
 /**
@@ -66,10 +64,8 @@ let outdatedDbVersion: number | undefined;
 
 const walletInit: OpenedPromise<void> = openPromise<void>();
 
-const notificationPorts: chrome.runtime.Port[] = [];
-
 async function getDiagnostics(): Promise<WalletDiagnostics> {
-  const manifestData = chrome.runtime.getManifest();
+  const manifestData = platform.getWalletVersion();
   const errors: string[] = [];
   let firefoxIdbProblem = false;
   let dbOutdated = false;
@@ -80,7 +76,7 @@ async function getDiagnostics(): Promise<WalletDiagnostics> {
     if (
       currentDatabase === undefined &&
       outdatedDbVersion === undefined &&
-      isFirefox()
+      platform.isFirefox()
     ) {
       firefoxIdbProblem = true;
     }
@@ -132,14 +128,7 @@ async function dispatch(
       break;
     }
     case "wxGetExtendedPermissions": {
-      const res = await new Promise((resolve, reject) => {
-        getPermissionsApi().contains(
-          getReadRequestPermissions(),
-          (result: boolean) => {
-            resolve(result);
-          },
-        );
-      });
+      const res = await 
platform.getPermissionsApi().contains(getReadRequestPermissions());
       r = wrapResponse({ newValue: res });
       break;
     }
@@ -147,15 +136,11 @@ async function dispatch(
       const newVal = req.payload.value;
       console.log("new extended permissions value", newVal);
       if (newVal) {
-        setupHeaderListener();
+        platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
         r = wrapResponse({ newValue: true });
       } else {
-        await new Promise<void>((resolve, reject) => {
-          getPermissionsApi().remove(getReadRequestPermissions(), (rem) => {
-            console.log("permissions removed:", rem);
-            resolve();
-          });
-        });
+        const rem = await 
platform.getPermissionsApi().remove(getReadRequestPermissions());
+        console.log("permissions removed:", rem);
         r = wrapResponse({ newVal: false });
       }
       break;
@@ -187,74 +172,13 @@ async function dispatch(
   }
 }
 
-function getTab(tabId: number): Promise<chrome.tabs.Tab> {
-  return new Promise((resolve, reject) => {
-    chrome.tabs.get(tabId, (tab: chrome.tabs.Tab) => resolve(tab));
-  });
-}
-
-function setBadgeText(options: chrome.action.BadgeTextDetails): void {
-  // not supported by all browsers ...
-  if (chrome && chrome.action && chrome.action.setBadgeText) {
-    chrome.action.setBadgeText(options);
-  } else {
-    console.warn("can't set badge text, not supported", options);
-  }
-}
-
-function waitMs(timeoutMs: number): Promise<void> {
-  return new Promise((resolve, reject) => {
-    const bgPage = chrome.extension.getBackgroundPage();
-    if (!bgPage) {
-      reject("fatal: no background page");
-      return;
-    }
-    bgPage.setTimeout(() => resolve(), timeoutMs);
-  });
-}
-
-function makeSyncWalletRedirect(
-  url: string,
-  tabId: number,
-  oldUrl: string,
-  params?: { [name: string]: string | undefined },
-): Record<string, unknown> {
-  const innerUrl = new URL(chrome.runtime.getURL(url));
-  if (params) {
-    const hParams = Object.keys(params)
-      .map((k) => `${k}=${params[k]}`)
-      .join("&");
-    innerUrl.hash = innerUrl.hash + "?" + hParams;
-  }
-  // Some platforms don't support the sync redirect (yet), so fall back to
-  // async redirect after a timeout.
-  const doit = async (): Promise<void> => {
-    await waitMs(150);
-    const tab = await getTab(tabId);
-    if (tab.url === oldUrl) {
-      console.log("redirecting to", innerUrl.href);
-      chrome.tabs.update(tabId, {
-        url: innerUrl.href,
-        loadReplace: true,
-      } as any);
-    }
-  };
-  doit();
-
-  return { redirectUrl: innerUrl.href };
-}
-
-export type MessageFromBackend = {
-  type: NotificationType;
-};
-
 async function reinitWallet(): Promise<void> {
   if (currentWallet) {
     currentWallet.stop();
     currentWallet = undefined;
   }
   currentDatabase = undefined;
-  setBadgeText({ text: "" });
+  // setBadgeText({ text: "" });
   try {
     currentDatabase = await openTalerDatabase(indexedDB as any, reinitWallet);
   } catch (e) {
@@ -265,7 +189,7 @@ async function reinitWallet(): Promise<void> {
   let httpLib;
   let cryptoWorker;
 
-  if (chrome.runtime.getManifest().manifest_version === 3) {
+  if (platform.useServiceWorkerAsBackgroundProcess()) {
     httpLib = new ServiceWorkerHttpLib();
     cryptoWorker = new SynchronousCryptoWorkerFactory();
   } else {
@@ -283,14 +207,8 @@ async function reinitWallet(): Promise<void> {
     return;
   }
   wallet.addNotificationListener((x) => {
-    for (const notif of notificationPorts) {
-      const message: MessageFromBackend = { type: x.type };
-      try {
-        notif.postMessage(message);
-      } catch (e) {
-        console.error(e);
-      }
-    }
+    const message: MessageFromBackend = { type: x.type };
+    platform.sendMessageToAllChannels(message)
   });
   wallet.runTaskLoop().catch((e) => {
     console.log("error during wallet task loop", e);
@@ -303,135 +221,41 @@ async function reinitWallet(): Promise<void> {
   walletInit.resolve();
 }
 
-try {
-  // This needs to be outside of main, as Firefox won't fire the event if
-  // the listener isn't created synchronously on loading the backend.
-  chrome.runtime.onInstalled.addListener((details) => {
-    console.log("onInstalled with reason", details.reason);
-    if (details.reason === "install") {
-      const url = chrome.runtime.getURL("/static/wallet.html#/welcome");
-      chrome.tabs.create({ active: true, url });
-    }
-  });
-} catch (e) {
-  console.error(e);
-}
-
-function headerListener(
-  details: chrome.webRequest.WebResponseHeadersDetails,
-): chrome.webRequest.BlockingResponse | undefined {
-  if (chrome.runtime.lastError) {
-    console.error(chrome.runtime.lastError);
-    return;
-  }
-  const wallet = currentWallet;
-  if (!wallet) {
-    console.warn("wallet not available while handling header");
-    return;
-  }
-  if (
-    details.statusCode === 402 ||
-    details.statusCode === 202 ||
-    details.statusCode === 200
-  ) {
-    for (const header of details.responseHeaders || []) {
-      if (header.name.toLowerCase() === "taler") {
-        const talerUri = header.value || "";
-        const uriType = classifyTalerUri(talerUri);
-        switch (uriType) {
-          case TalerUriType.TalerWithdraw:
-            return makeSyncWalletRedirect(
-              "/static/wallet.html#/cta/withdraw",
-              details.tabId,
-              details.url,
-              {
-                talerWithdrawUri: talerUri,
-              },
-            );
-          case TalerUriType.TalerPay:
-            return makeSyncWalletRedirect(
-              "/static/wallet.html#/cta/pay",
-              details.tabId,
-              details.url,
-              {
-                talerPayUri: talerUri,
-              },
-            );
-          case TalerUriType.TalerTip:
-            return makeSyncWalletRedirect(
-              "/static/wallet.html#/cta/tip",
-              details.tabId,
-              details.url,
-              {
-                talerTipUri: talerUri,
-              },
-            );
-          case TalerUriType.TalerRefund:
-            return makeSyncWalletRedirect(
-              "/static/wallet.html#/cta/refund",
-              details.tabId,
-              details.url,
-              {
-                talerRefundUri: talerUri,
-              },
-            );
-          case TalerUriType.TalerNotifyReserve:
-            Promise.resolve().then(() => {
-              const w = currentWallet;
-              if (!w) {
-                return;
-              }
-              // FIXME:  Is this still useful?
-              // handleNotifyReserve(w);
-            });
-            break;
-          default:
-            console.warn(
-              "Response with HTTP 402 has Taler header, but header value is 
not a taler:// URI.",
-            );
-            break;
-        }
-      }
-    }
+function parseTalerUriAndRedirect(tabId: number, talerUri: string) {
+  const uriType = classifyTalerUri(talerUri);
+  switch (uriType) {
+    case TalerUriType.TalerWithdraw:
+      return platform.redirectTabToWalletPage(
+        tabId,
+        `/cta/withdraw?talerWithdrawUri=${talerUri}`,
+      );
+    case TalerUriType.TalerPay:
+      return platform.redirectTabToWalletPage(
+        tabId,
+        `/cta/pay?talerPayUri=${talerUri}`,
+      );
+    case TalerUriType.TalerTip:
+      return platform.redirectTabToWalletPage(
+        tabId,
+        `/cta/tip?talerTipUri=${talerUri}`,
+      );
+    case TalerUriType.TalerRefund:
+      return platform.redirectTabToWalletPage(
+        tabId,
+        `/cta/refund?talerRefundUri=${talerUri}`,
+      );
+    case TalerUriType.TalerNotifyReserve:
+      // FIXME:  Is this still useful?
+      // handleNotifyReserve(w);
+      break;
+    default:
+      console.warn(
+        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
+      );
+      break;
   }
-  return;
 }
 
-function setupHeaderListener(): void {
-  // if (chrome.runtime.getManifest().manifest_version === 3) {
-  //   console.error("cannot block request on manfest v3")
-  //   return
-  // }
-  console.log("setting up header listener");
-  // Handlers for catching HTTP requests
-  getPermissionsApi().contains(
-    getReadRequestPermissions(),
-    (result: boolean) => {
-      if (
-        "webRequest" in chrome &&
-        "onHeadersReceived" in chrome.webRequest &&
-        chrome.webRequest.onHeadersReceived.hasListener(headerListener)
-      ) {
-        chrome.webRequest.onHeadersReceived.removeListener(headerListener);
-      }
-      if (result) {
-        console.log("actually adding listener");
-        chrome.webRequest.onHeadersReceived.addListener(
-          headerListener,
-          { urls: ["<all_urls>"] },
-          ["responseHeaders"],
-        );
-      }
-      if ("webRequest" in chrome) {
-        chrome.webRequest.handlerBehaviorChanged(() => {
-          if (chrome.runtime.lastError) {
-            console.error(chrome.runtime.lastError);
-          }
-        });
-      }
-    },
-  );
-}
 
 /**
  * Main function to run for the WebExtension backend.
@@ -439,48 +263,34 @@ function setupHeaderListener(): void {
  * Sets up all event handlers and other machinery.
  */
 export async function wxMain(): Promise<void> {
-  // Explicitly unload the extension page as soon as an update is available,
-  // so the update gets installed as soon as possible.
-  chrome.runtime.onUpdateAvailable.addListener((details) => {
-    console.log("update available:", details);
-    chrome.runtime.reload();
-  });
   const afterWalletIsInitialized = reinitWallet();
 
+  platform.registerReloadOnNewVersion();
+
   // Handlers for messages coming directly from the content
   // script on the page
-  chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
+  platform.registerOnNewMessage((message, sender, callback) => {
     afterWalletIsInitialized.then(() => {
-      dispatch(req, sender, sendResponse);
+      dispatch(message, sender, callback);
     });
-    return true;
-  });
+  })
 
-  chrome.runtime.onConnect.addListener((port) => {
-    notificationPorts.push(port);
-    port.onDisconnect.addListener((discoPort) => {
-      const idx = notificationPorts.indexOf(discoPort);
-      if (idx >= 0) {
-        notificationPorts.splice(idx, 1);
-      }
-    });
-  });
+  platform.registerAllIncomingConnections()
 
   try {
-    if (chrome.runtime.getManifest().manifest_version === 2) {
-      setupHeaderListener();
-    }
+    platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
   } catch (e) {
     console.log(e);
   }
 
   // On platforms that support it, also listen to external
   // modification of permissions.
-  getPermissionsApi().addPermissionsListener((perm) => {
-    if (chrome.runtime.lastError) {
-      console.error(chrome.runtime.lastError);
+  platform.getPermissionsApi().addPermissionsListener((perm) => {
+    const lastError = platform.getLastError()
+    if (lastError) {
+      console.error(lastError);
       return;
     }
-    setupHeaderListener();
+    platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
   });
 }

-- 
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]