summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src')
-rw-r--r--packages/taler-wallet-webextension/src/api/browser.ts54
-rw-r--r--packages/taler-wallet-webextension/src/background.ts29
-rw-r--r--packages/taler-wallet-webextension/src/chromeBadge.ts4
-rw-r--r--packages/taler-wallet-webextension/src/compat.ts101
-rw-r--r--packages/taler-wallet-webextension/src/components/Diagnostics.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/context/iocContext.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Refund.tsx32
-rw-r--r--packages/taler-wallet-webextension/src/cta/Tip.tsx31
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts53
-rw-r--r--packages/taler-wallet-webextension/src/platform/api.ts90
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts371
-rw-r--r--packages/taler-wallet-webextension/src/platform/firefox.ts74
-rw-r--r--packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx20
-rw-r--r--packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx53
-rw-r--r--packages/taler-wallet-webextension/src/popupEntryPoint.tsx31
-rw-r--r--packages/taler-wallet-webextension/src/renderHtml.tsx176
-rw-r--r--packages/taler-wallet-webextension/src/utils/index.ts49
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/walletEntryPoint.tsx23
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts47
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts308
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 b69a49680..000000000
--- 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 d6aeddc1d..9c572c176 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 60585793e..74c2fcd2f 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 b17d0fb80..000000000
--- 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 0998cab7b..0cffff693 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 688e7b488..a24b0c1ca 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 efc436bc8..790e8d9fa 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 71aa04a2b..5767b5008 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 6bf6a7bdf..66d710705 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 000000000..9b4e02ffb
--- /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 000000000..dada23c57
--- /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 000000000..dad90626b
--- /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 3deea032d..d47b8ce7b 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 9ac83a578..a1082ad92 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 7ee6c8e4b..dfb12666b 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 1e482ccea..000000000
--- 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 cef0595d3..e5447f9cb 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 3516bfbf1..bebf036c9 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 2f53917e4..e7cb1f5e6 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 2071f85e5..ee2a81062 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 b7a0cdc54..048c8fc79 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);
});
}