diff --git a/packages/taler-wallet-webextension/manifest-v2.json b/packages/taler-wallet-webextension/manifest-v2.json index bcce56e71..e83a16f56 100644 --- a/packages/taler-wallet-webextension/manifest-v2.json +++ b/packages/taler-wallet-webextension/manifest-v2.json @@ -17,8 +17,7 @@ }, "permissions": [ "unlimitedStorage", - "http://*/*", - "https://*/*", + "storage", "activeTab" ], "web_accessible_resources": [ @@ -28,21 +27,31 @@ "dist/taler-wallet-interaction-support.js.map", "dist/taler-wallet-interaction-support.js" ], - "optional_permissions": [ - "http://*/*", - "https://*/*", - "webRequest" - ], "content_scripts": [{ "id": "taler-wallet-interaction-support", "matches": ["file://*/*", "http://*/*", "https://*/*"], "js": ["dist/taler-wallet-interaction-loader.js"] }], "protocol_handlers": [ + { + "protocol": "ext+taler+http", + "name": "Taler Wallet WebExtension", + "uriTemplate": "/static/wallet.html#/cta/taler-uri/%s" + }, + { + "protocol": "web+taler+http", + "name": "Taler Wallet WebExtension", + "uriTemplate": "/static/wallet.html#/cta/taler-uri/%s" + }, { "protocol": "ext+taler", "name": "Taler Wallet WebExtension", - "uriTemplate": "/static/wallet.html#/cta/withdraw?d=1&talerWithdrawUri=%s" + "uriTemplate": "/static/wallet.html#/cta/taler-uri/%s" + }, + { + "protocol": "web+taler", + "name": "Taler Wallet WebExtension", + "uriTemplate": "/static/wallet.html#/cta/taler-uri/%s" } ], "browser_action": { diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json index 72c8d8cd8..93a1a7fc8 100644 --- a/packages/taler-wallet-webextension/manifest-v3.json +++ b/packages/taler-wallet-webextension/manifest-v3.json @@ -14,6 +14,7 @@ }, "permissions": [ "unlimitedStorage", + "storage", "activeTab", "scripting", "declarativeContent", @@ -26,9 +27,6 @@ } } }, - "optional_permissions": [ - "webRequest" - ], "content_scripts": [{ "id": "taler-wallet-interaction", "matches": ["file://*/*", "http://*/*", "https://*/*"], @@ -50,10 +48,6 @@ ] } ], - "host_permissions": [ - "http://*/*", - "https://*/*" - ], "action": { "default_icon": { "16": "static/img/taler-logo-16.png", diff --git a/packages/taler-wallet-webextension/src/background.dev.ts b/packages/taler-wallet-webextension/src/background.dev.ts index cac62c44d..9ed0e1b8f 100644 --- a/packages/taler-wallet-webextension/src/background.dev.ts +++ b/packages/taler-wallet-webextension/src/background.dev.ts @@ -30,14 +30,8 @@ import { wxMain } from "./wxBackend.js"; console.log("Wallet setup for Dev API"); setupPlatform(devAPI); -try { - platform.registerOnInstalled(() => { - platform.openWalletPage("/welcome"); - }); -} catch (e) { - console.error(e); +async function start() { + await platform.notifyWhenAppIsReady(); + await wxMain(); } - -platform.notifyWhenAppIsReady(() => { - wxMain(); -}); +start(); diff --git a/packages/taler-wallet-webextension/src/background.ts b/packages/taler-wallet-webextension/src/background.ts index 7382120aa..0d5a186d2 100644 --- a/packages/taler-wallet-webextension/src/background.ts +++ b/packages/taler-wallet-webextension/src/background.ts @@ -43,6 +43,9 @@ if (isFirefox) { } // setGlobalLogLevelFromString("trace") -platform.notifyWhenAppIsReady(() => { - wxMain(); -}); + +async function start() { + await platform.notifyWhenAppIsReady(); + await wxMain(); +} +start(); diff --git a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts index 25757f473..3255c90e5 100644 --- a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts @@ -20,6 +20,12 @@ import { useBackendContext } from "../context/backend.js"; import { ToggleHandler } from "../mui/handlers.js"; import { platform } from "../platform/foreground.js"; +/** + * This is not implemented. + * Clipboard permission need to get ask the permission to the user + * based on user-intention + * @returns + */ export function useClipboardPermissions(): ToggleHandler { const [enabled, setEnabled] = useState(false); const api = useBackendContext(); @@ -40,27 +46,27 @@ export function useClipboardPermissions(): ToggleHandler { } setEnabled(granted); } else { - try { - await api.background - .call("toggleHeaderListener", false) - .then((r) => setEnabled(r.newValue)); - } catch (e) { - console.log(e); - } + // try { + // await api.background + // .call("toggleHeaderListener", false) + // .then((r) => setEnabled(r.newValue)); + // } catch (e) { + // console.log(e); + // } } return; } - useEffect(() => { - async function getValue(): Promise { - const res = await api.background.call( - "containsHeaderListener", - undefined, - ); - setEnabled(res.newValue); - } - getValue(); - }, []); + // useEffect(() => { + // async function getValue(): Promise { + // const res = await api.background.call( + // "containsHeaderListener", + // undefined, + // ); + // setEnabled(res.newValue); + // } + // getValue(); + // }, []); return { value: enabled, diff --git a/packages/taler-wallet-webextension/src/hooks/useSettings.ts b/packages/taler-wallet-webextension/src/hooks/useSettings.ts index 04bce236a..040fee424 100644 --- a/packages/taler-wallet-webextension/src/hooks/useSettings.ts +++ b/packages/taler-wallet-webextension/src/hooks/useSettings.ts @@ -15,14 +15,7 @@ */ import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser"; - -interface Settings { - injectTalerSupport: boolean; -} - -const defaultSettings: Settings = { - injectTalerSupport: false, -}; +import { Settings, defaultSettings } from "../platform/api.js"; function parse_json_or_undefined(str: string | undefined): T | undefined { if (str === undefined) return undefined; @@ -42,7 +35,6 @@ export function useSettings(): [ const parsed: Settings = parse_json_or_undefined(value) ?? defaultSettings; function updateField(k: T, v: Settings[T]) { const newValue = { ...parsed, [k]: v }; - console.log("should update", k, v, parsed, newValue); const json = JSON.stringify(newValue); console.log(json); update(json); diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index 40993477b..f87500c4f 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -17,6 +17,10 @@ import { CoreApiResponse, NotificationType } from "@gnu-taler/taler-util"; import { WalletOperations } from "@gnu-taler/taler-wallet-core"; import { BackgroundOperations } from "../wxApi.js"; +import { + ExtensionOperations, + MessageFromExtension, +} from "../taler-wallet-interaction-loader.js"; export interface Permissions { /** @@ -35,9 +39,9 @@ export interface Permissions { * Compatibility API that works on multiple browsers. */ export interface CrossBrowserPermissionsApi { - containsHostPermissions(): Promise; - requestHostPermissions(): Promise; - removeHostPermissions(): Promise; + // containsHostPermissions(): Promise; + // requestHostPermissions(): Promise; + // removeHostPermissions(): Promise; containsClipboardPermissions(): Promise; requestClipboardPermissions(): Promise; @@ -53,9 +57,11 @@ export type MessageFromBackend = { }; export type MessageFromFrontend< - Op extends BackgroundOperations | WalletOperations, + Op extends BackgroundOperations | WalletOperations | ExtensionOperations, > = Op extends BackgroundOperations ? MessageFromFrontendBackground + : Op extends ExtensionOperations + ? MessageFromExtension : Op extends WalletOperations ? MessageFromFrontendWallet : never; @@ -81,11 +87,23 @@ export interface WalletWebExVersion { version: string; } +export interface Settings { + injectTalerSupport: boolean; +} + +export const defaultSettings: Settings = { + injectTalerSupport: false, +}; + /** * Compatibility helpers needed for browsers that don't implement * WebExtension APIs consistently. */ export interface BackgroundPlatformAPI { + /** + * + */ + getSettingsFromStorage(): Promise; /** * Guarantee that the service workers don't die */ @@ -116,16 +134,12 @@ export interface BackgroundPlatformAPI { * Register a callback to be called when the wallet is ready to start * @param callback */ - notifyWhenAppIsReady(callback: () => void): void; + notifyWhenAppIsReady(): Promise; /** * Get the wallet version from manifest */ getWalletWebExVersion(): WalletWebExVersion; - /** - * Frontend API - */ - containsTalerHeaderListener(): boolean; /** * Backend API */ @@ -134,12 +148,6 @@ export interface BackgroundPlatformAPI { * Backend API */ registerReloadOnNewVersion(): void; - /** - * Backend API - */ - registerTalerHeaderListener( - onHeader: (tabId: number, url: string) => void, - ): void; /** * Permission API for checking and add a listener diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index 03259314e..51cf2f64e 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -31,10 +31,13 @@ import { MessageFromFrontend, MessageResponse, Permissions, + Settings, + defaultSettings, } from "./api.js"; const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { isFirefox, + getSettingsFromStorage, findTalerUriInActiveTab, findTalerUriInClipboard, getPermissionsApi, @@ -49,11 +52,9 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { registerOnInstalled, listenToAllChannels: listenToAllChannels as any, registerReloadOnNewVersion, - registerTalerHeaderListener, sendMessageToAllChannels, sendMessageToBackground, useServiceWorkerAsBackgroundProcess, - containsTalerHeaderListener, keepAlive, }; @@ -61,6 +62,19 @@ export default api; const logger = new Logger("chrome.ts"); +async function getSettingsFromStorage(): Promise { + const data = await chrome.storage.local.get("wallet-settings"); + if (!data) return defaultSettings; + const settings = data["wallet-settings"]; + if (!settings) return defaultSettings; + try { + const parsed = JSON.parse(settings); + return parsed; + } catch (e) { + return defaultSettings; + } +} + function keepAlive(callback: any): void { if (extensionIsManifestV3()) { chrome.alarms.create("wallet-worker", { periodInMinutes: 1 }); @@ -78,10 +92,10 @@ function isFirefox(): boolean { return false; } -const hostPermissions = { - permissions: ["webRequest"], - origins: ["http://*/*", "https://*/*"], -}; +// const hostPermissions = { +// permissions: ["webRequest"], +// origins: ["http://*/*", "https://*/*"], +// }; export function containsClipboardPermissions(): Promise { return new Promise((res, rej) => { @@ -96,17 +110,17 @@ export function containsClipboardPermissions(): Promise { }); } -export function containsHostPermissions(): Promise { - return new Promise((res, rej) => { - chrome.permissions.contains(hostPermissions, (resp) => { - const le = chrome.runtime.lastError?.message; - if (le) { - rej(le); - } - res(resp); - }); - }); -} +// export function containsHostPermissions(): Promise { +// return new Promise((res, rej) => { +// chrome.permissions.contains(hostPermissions, (resp) => { +// const le = chrome.runtime.lastError?.message; +// if (le) { +// rej(le); +// } +// res(resp); +// }); +// }); +// } export async function requestClipboardPermissions(): Promise { return new Promise((res, rej) => { @@ -121,73 +135,67 @@ export async function requestClipboardPermissions(): Promise { }); } -export async function requestHostPermissions(): Promise { - return new Promise((res, rej) => { - chrome.permissions.request(hostPermissions, (resp) => { - const le = chrome.runtime.lastError?.message; - if (le) { - rej(le); - } - res(resp); - }); - }); -} +// export async function requestHostPermissions(): Promise { +// return new Promise((res, rej) => { +// chrome.permissions.request(hostPermissions, (resp) => { +// const le = chrome.runtime.lastError?.message; +// if (le) { +// rej(le); +// } +// res(resp); +// }); +// }); +// } -type HeaderListenerFunc = ( - details: chrome.webRequest.WebResponseHeadersDetails, -) => void; -let currentHeaderListener: HeaderListenerFunc | undefined = undefined; +// type HeaderListenerFunc = ( +// details: chrome.webRequest.WebResponseHeadersDetails, +// ) => void; +// let currentHeaderListener: HeaderListenerFunc | undefined = undefined; -type TabListenerFunc = (tabId: number, info: chrome.tabs.TabChangeInfo) => void; -let currentTabListener: TabListenerFunc | undefined = undefined; +// type TabListenerFunc = (tabId: number, info: chrome.tabs.TabChangeInfo) => void; +// let currentTabListener: TabListenerFunc | undefined = undefined; -export function containsTalerHeaderListener(): boolean { - return ( - currentHeaderListener !== undefined || currentTabListener !== undefined - ); -} +// export async function removeHostPermissions(): Promise { +// //if there is a handler already, remove it +// if ( +// currentHeaderListener && +// chrome?.webRequest?.onHeadersReceived?.hasListener(currentHeaderListener) +// ) { +// chrome.webRequest.onHeadersReceived.removeListener(currentHeaderListener); +// } +// if ( +// currentTabListener && +// chrome?.tabs?.onUpdated?.hasListener(currentTabListener) +// ) { +// chrome.tabs.onUpdated.removeListener(currentTabListener); +// } -export async function removeHostPermissions(): Promise { - //if there is a handler already, remove it - if ( - currentHeaderListener && - chrome?.webRequest?.onHeadersReceived?.hasListener(currentHeaderListener) - ) { - chrome.webRequest.onHeadersReceived.removeListener(currentHeaderListener); - } - if ( - currentTabListener && - chrome?.tabs?.onUpdated?.hasListener(currentTabListener) - ) { - chrome.tabs.onUpdated.removeListener(currentTabListener); - } +// currentHeaderListener = undefined; +// currentTabListener = undefined; - currentHeaderListener = undefined; - currentTabListener = undefined; +// //notify the browser about this change, this operation is expensive +// if ("webRequest" in chrome) { +// chrome.webRequest.handlerBehaviorChanged(() => { +// if (chrome.runtime.lastError) { +// logger.error(JSON.stringify(chrome.runtime.lastError)); +// } +// }); +// } - //notify the browser about this change, this operation is expensive - if ("webRequest" in chrome) { - chrome.webRequest.handlerBehaviorChanged(() => { - if (chrome.runtime.lastError) { - logger.error(JSON.stringify(chrome.runtime.lastError)); - } - }); - } - - if (extensionIsManifestV3()) { - // Trying to remove host permissions with manifest >= v3 throws an error - return true; - } - return new Promise((res, rej) => { - chrome.permissions.remove(hostPermissions, (resp) => { - const le = chrome.runtime.lastError?.message; - if (le) { - rej(le); - } - res(resp); - }); - }); -} +// if (extensionIsManifestV3()) { +// // Trying to remove host permissions with manifest >= v3 throws an error +// return true; +// } +// return new Promise((res, rej) => { +// chrome.permissions.remove(hostPermissions, (resp) => { +// const le = chrome.runtime.lastError?.message; +// if (le) { +// rej(le); +// } +// res(resp); +// }); +// }); +// } export function removeClipboardPermissions(): Promise { return new Promise((res, rej) => { @@ -214,9 +222,9 @@ function addPermissionsListener( function getPermissionsApi(): CrossBrowserPermissionsApi { return { addPermissionsListener, - containsHostPermissions, - requestHostPermissions, - removeHostPermissions, + // containsHostPermissions, + // requestHostPermissions, + // removeHostPermissions, requestClipboardPermissions, removeClipboardPermissions, containsClipboardPermissions, @@ -227,12 +235,16 @@ function getPermissionsApi(): CrossBrowserPermissionsApi { * * @param callback function to be called */ -function notifyWhenAppIsReady(callback: () => void): void { - if (extensionIsManifestV3()) { - callback(); - } else { - window.addEventListener("load", callback); - } +function notifyWhenAppIsReady(): Promise { + return new Promise((resolve, reject) => { + if (extensionIsManifestV3()) { + resolve(); + } else { + window.addEventListener("load", () => { + resolve(); + }); + } + }); } function openWalletURIFromPopup(maybeTalerUri: string): void { @@ -478,101 +490,6 @@ function getWalletWebExVersion(): WalletVersion { return manifestData; } -function registerTalerHeaderListener( - callback: (tabId: number, url: string) => void, -): void { - logger.trace("setting up header listener"); - - function headerListener( - details: chrome.webRequest.WebResponseHeadersDetails, - ): void { - if (chrome.runtime.lastError) { - logger.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) { - logger.info( - `Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`, - ); - callback(details.tabId, values[0]); - } - } - return; - } - - async function tabListener( - tabId: number, - info: chrome.tabs.TabChangeInfo, - ): Promise { - if (tabId < 0) return; - const tabLocationHasBeenUpdated = info.status === "complete"; - const tabTitleHasBeenUpdated = info.title !== undefined; - if (tabLocationHasBeenUpdated || tabTitleHasBeenUpdated) { - const uri = await findTalerUriInTab(tabId); - if (!uri) return; - logger.info(`Found a Taler URI in the tab ${tabId}`); - callback(tabId, uri); - } - } - - const prevHeaderListener = currentHeaderListener; - const prevTabListener = currentTabListener; - - getPermissionsApi() - .containsHostPermissions() - .then((result) => { - //if there is a handler already, remove it - if ( - prevHeaderListener && - chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener) - ) { - chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener); - } - if ( - prevTabListener && - chrome?.tabs?.onUpdated?.hasListener(prevTabListener) - ) { - chrome.tabs.onUpdated.removeListener(prevTabListener); - } - - //if the result was positive, add the headerListener - if (result) { - const headersEvent: - | chrome.webRequest.WebResponseHeadersEvent - | undefined = chrome?.webRequest?.onHeadersReceived; - if (headersEvent) { - headersEvent.addListener(headerListener, { urls: [""] }, [ - "responseHeaders", - ]); - currentHeaderListener = headerListener; - } - - const tabsEvent: chrome.tabs.TabUpdatedEvent | undefined = - chrome?.tabs?.onUpdated; - if (tabsEvent) { - tabsEvent.addListener(tabListener); - currentTabListener = tabListener; - } - } - - //notify the browser about this change, this operation is expensive - chrome?.webRequest?.handlerBehaviorChanged(() => { - if (chrome.runtime.lastError) { - logger.error(JSON.stringify(chrome.runtime.lastError)); - } - }); - }); -} - const alertIcons = { "16": "/static/img/taler-alert-16.png", "19": "/static/img/taler-alert-19.png", @@ -750,7 +667,7 @@ function registerOnInstalled(callback: () => void): void { if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) { callback(); } - registerIconChangeOnTalerContent(); + await registerIconChangeOnTalerContent(); }); } diff --git a/packages/taler-wallet-webextension/src/platform/dev.ts b/packages/taler-wallet-webextension/src/platform/dev.ts index d57072c80..1a4183bec 100644 --- a/packages/taler-wallet-webextension/src/platform/dev.ts +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -23,18 +23,17 @@ import { MessageFromBackend, MessageFromFrontend, MessageResponse, + defaultSettings, } from "./api.js"; const frames = ["popup", "wallet"]; const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { isFirefox: () => false, + getSettingsFromStorage: () => Promise.resolve(defaultSettings), keepAlive: (cb: VoidFunction) => cb(), findTalerUriInActiveTab: async () => undefined, findTalerUriInClipboard: async () => undefined, - containsTalerHeaderListener: () => { - return true; - }, getPermissionsApi: () => ({ addPermissionsListener: () => undefined, containsHostPermissions: async () => true, @@ -47,21 +46,23 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { getWalletWebExVersion: () => ({ version: "none", }), - notifyWhenAppIsReady: (fn: () => void) => { + notifyWhenAppIsReady: () => { let total = frames.length; - function waitAndNotify(): void { - total--; - if (total < 1) { - fn(); - } - } - frames.forEach((f) => { - const theFrame = window.frames[f as any]; - if (theFrame.location.href === "about:blank") { - waitAndNotify(); - } else { - theFrame.addEventListener("load", waitAndNotify); + return new Promise((fn) => { + function waitAndNotify(): void { + total--; + if (total < 1) { + fn(); + } } + frames.forEach((f) => { + const theFrame = window.frames[f as any]; + if (theFrame.location.href === "about:blank") { + waitAndNotify(); + } else { + theFrame.addEventListener("load", waitAndNotify); + } + }); }); }, @@ -80,9 +81,8 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { }, registerAllIncomingConnections: () => undefined, - registerOnInstalled: (fn: () => void) => undefined, + registerOnInstalled: () => Promise.resolve(), registerReloadOnNewVersion: () => undefined, - registerTalerHeaderListener: () => undefined, useServiceWorkerAsBackgroundProcess: () => false, diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts b/packages/taler-wallet-webextension/src/platform/firefox.ts index 7f6980be7..01848e1ab 100644 --- a/packages/taler-wallet-webextension/src/platform/firefox.ts +++ b/packages/taler-wallet-webextension/src/platform/firefox.ts @@ -19,11 +19,10 @@ import { CrossBrowserPermissionsApi, ForegroundPlatformAPI, Permissions, + Settings, + defaultSettings, } from "./api.js"; import chromePlatform, { - containsHostPermissions as chromeHostContains, - removeHostPermissions as chromeHostRemove, - requestHostPermissions as chromeHostRequest, containsClipboardPermissions as chromeClipContains, removeClipboardPermissions as chromeClipRemove, requestClipboardPermissions as chromeClipRequest, @@ -32,6 +31,7 @@ import chromePlatform, { const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { ...chromePlatform, isFirefox, + getSettingsFromStorage, getPermissionsApi, notifyWhenAppIsReady, redirectTabToWalletPage, @@ -51,25 +51,43 @@ function addPermissionsListener(callback: (p: Permissions) => void): void { function getPermissionsApi(): CrossBrowserPermissionsApi { return { addPermissionsListener, - containsHostPermissions: chromeHostContains, - requestHostPermissions: chromeHostRequest, - removeHostPermissions: chromeHostRemove, + // containsHostPermissions: chromeHostContains, + // requestHostPermissions: chromeHostRequest, + // removeHostPermissions: chromeHostRemove, containsClipboardPermissions: chromeClipContains, removeClipboardPermissions: chromeClipRemove, requestClipboardPermissions: chromeClipRequest, }; } +async function getSettingsFromStorage(): Promise { + //@ts-ignore + const data = await browser.storage.local.get("wallet-settings"); + if (!data) return defaultSettings; + const settings = data["wallet-settings"]; + if (!settings) return defaultSettings; + try { + const parsed = JSON.parse(settings); + return parsed; + } catch (e) { + return defaultSettings; + } +} + /** * * @param callback function to be called */ -function notifyWhenAppIsReady(callback: () => void): void { - if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) { - callback(); - } else { - window.addEventListener("load", callback); - } +function notifyWhenAppIsReady(): Promise { + return new Promise((resolve) => { + if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) { + resolve(); + } else { + window.addEventListener("load", () => { + resolve(); + }); + } + }); } function redirectTabToWalletPage(tabId: number, page: string): void { diff --git a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts index 838b47397..cc5a02260 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts @@ -14,6 +14,8 @@ GNU Taler; see the file COPYING. If not, see */ +import { CoreApiResponse } from "@gnu-taler/taler-util"; + /** * This will modify all the pages that the user load when navigating with Web Extension enabled * @@ -62,7 +64,7 @@ const logger = { console.error(`${new Date().toISOString()} TALER`, ...msg), }; -function start() { +async function start() { if (shouldNotInject) { return; } @@ -73,8 +75,15 @@ function start() { } createBridgeWithExtension(); logger.debug("bridged created"); - injectTalerSupportScript(debugEnabled); - logger.debug("done"); + + const shouldInject = await callBackground("isInjectionEnabled", undefined); + + if (shouldInject) { + injectTalerSupportScript(debugEnabled); + logger.debug("injection completed"); + } else { + logger.debug("injection is not enabled"); + } } /** @@ -132,4 +141,65 @@ function createBridgeWithExtension() { ); } +export interface ExtensionOperations { + isInjectionEnabled: { + request: void; + response: boolean; + }; +} + +export type MessageFromExtension = { + channel: "extension"; + operation: Op; + payload: ExtensionOperations[Op]["request"]; +}; + +export type MessageResponse = CoreApiResponse; + +async function callBackground( + operation: Op, + payload: ExtensionOperations[Op]["request"], +): Promise { + const message: MessageFromExtension = { + channel: "extension", + operation, + payload, + }; + + const response = await sendMessageToBackground(message); + if (response.type === "error") { + throw new Error(`Background operation "${operation}" failed`); + } + return response.result as any; +} +let nextMessageIndex = 0; +async function sendMessageToBackground( + message: MessageFromExtension, +): Promise { + const messageWithId = { ...message, id: `id_${nextMessageIndex++ % 1000}` }; + + return new Promise((resolve, reject) => { + // logger.trace("send operation to the wallet background", message); + let timedout = false; + const timerId = setTimeout(() => { + timedout = true; + throw new Error("timeout"); + // throw TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, {}); + }, 5 * 1000); //five seconds + chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { + if (timedout) { + return false; //already rejected + } + clearTimeout(timerId); + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError.message); + } else { + resolve(backgroundResponse); + } + // return true to keep the channel open + return true; + }); + }); +} + start(); diff --git a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts index 00f774cc6..34687306b 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts @@ -92,7 +92,7 @@ function buildApi(config: Readonly): API { } const targetAttr = ev.currentTarget.attributes.getNamedItem("target"); const windowTarget = - targetAttr && targetAttr.value ? targetAttr.value : "taler-wallet"; + targetAttr && targetAttr.value ? targetAttr.value : "_self"; const page = convertURIToWebExtensionPath(hrefAttr.value); if (!page) { logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`); diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index eea1ffb49..ae3a6e688 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -271,7 +271,7 @@ export function SettingsView({ Navigator diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index 1893c4161..0ddcdb0ac 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -100,7 +100,7 @@ export function View({ Navigator diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 98ed72f50..d15528699 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -68,18 +68,10 @@ export interface BackgroundOperations { request: void; response: void; }; - containsHeaderListener: { - request: void; - response: ExtendedPermissionsResponse; - }; getDiagnostics: { request: void; response: WalletDiagnostics; }; - toggleHeaderListener: { - request: boolean; - response: ExtendedPermissionsResponse; - }; runGarbageCollector: { request: void; response: void; diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index c50412053..bf91e8521 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -24,42 +24,39 @@ * Imports. */ import { - classifyTalerUri, - Logger, LogLevel, + Logger, + TalerErrorCode, + WalletDiagnostics, + getErrorDetailFromException, + makeErrorDetail, setGlobalLogLevelFromString, setLogLevelFromString, - TalerErrorCode, - TalerUriType, - WalletDiagnostics, - makeErrorDetail, - getErrorDetailFromException, - parseTalerUri, - TalerUriAction, } from "@gnu-taler/taler-util"; import { DbAccess, - deleteTalerDatabase, - exportDb, - importDb, OpenedPromise, - openPromise, - openTalerDatabase, SetTimeoutTimerAPI, Wallet, WalletOperations, WalletStoresV1, + deleteTalerDatabase, + exportDb, + importDb, + openPromise, + openTalerDatabase, } from "@gnu-taler/taler-wallet-core"; import { BrowserHttpLib } from "./browserHttpLib.js"; -import { platform } from "./platform/background.js"; import { MessageFromBackend, MessageFromFrontend, MessageResponse, } from "./platform/api.js"; +import { platform } from "./platform/background.js"; import { SynchronousCryptoWorkerFactory } from "./serviceWorkerCryptoWorkerFactory.js"; import { ServiceWorkerHttpLib } from "./serviceWorkerHttpLib.js"; -import { BackgroundOperations, ExtendedPermissionsResponse } from "./wxApi.js"; +import { ExtensionOperations } from "./taler-wallet-interaction-loader.js"; +import { BackgroundOperations } from "./wxApi.js"; /** * Currently active wallet instance. Might be unloaded and @@ -123,10 +120,11 @@ type BackendHandlerType = { ) => Promise; }; -async function containsHeaderListener(): Promise { - const result = await platform.containsTalerHeaderListener(); - return { newValue: result }; -} +type ExtensionHandlerType = { + [Op in keyof ExtensionOperations]: ( + req: ExtensionOperations[Op]["request"], + ) => Promise; +}; async function resetDb(): Promise { await deleteTalerDatabase(indexedDB as any); @@ -153,20 +151,6 @@ async function runGarbageCollector(): Promise { logger.info("imported"); } -async function toggleHeaderListener( - newVal: boolean, -): Promise { - logger.trace("new extended permissions value", newVal); - if (newVal) { - platform.registerTalerHeaderListener(parseTalerUriAndRedirect); - return { newValue: true }; - } - - const rem = await platform.getPermissionsApi().removeHostPermissions(); - logger.trace("permissions removed:", rem); - return { newValue: false }; -} - function freeze(time: number): Promise { return new Promise((res, rej) => { setTimeout(res, time); @@ -177,14 +161,21 @@ async function sum(ns: Array): Promise { return ns.reduce((prev, cur) => prev + cur, 0); } +const extensionHandlers: ExtensionHandlerType = { + isInjectionEnabled, +}; + +async function isInjectionEnabled(): Promise { + const settings = await platform.getSettingsFromStorage(); + return settings.injectTalerSupport === true; +} + const backendHandlers: BackendHandlerType = { freeze, sum, - containsHeaderListener, getDiagnostics, resetDb, runGarbageCollector, - toggleHeaderListener, setLoggingLevel, }; @@ -203,55 +194,85 @@ async function setLoggingLevel({ } } -async function dispatch( - req: MessageFromFrontend & { id: string }, -): Promise { - if (req.channel === "background") { - const handler = backendHandlers[req.operation] as (req: any) => any; - if (!handler) { - return { - type: "error", - id: req.id, - operation: String(req.operation), - error: getErrorDetailFromException( - Error(`unknown background operation`), - ), - }; +async function dispatch< + Op extends WalletOperations | BackgroundOperations | ExtensionOperations, +>(req: MessageFromFrontend & { id: string }): Promise { + switch (req.channel) { + case "background": { + const handler = backendHandlers[req.operation] as (req: any) => any; + if (!handler) { + return { + type: "error", + id: req.id, + operation: String(req.operation), + error: getErrorDetailFromException( + Error(`unknown background operation`), + ), + }; + } + try { + const result = await handler(req.payload); + return { + type: "response", + id: req.id, + operation: String(req.operation), + result, + }; + } catch (er) { + return { + type: "error", + id: req.id, + error: getErrorDetailFromException(er), + operation: String(req.operation), + }; + } } - try { - const result = await handler(req.payload); - return { - type: "response", - id: req.id, - operation: String(req.operation), - result, - }; - } catch (er) { - return { - type: "error", - id: req.id, - error: getErrorDetailFromException(er), - operation: String(req.operation), - }; + case "extension": { + const handler = extensionHandlers[req.operation] as (req: any) => any; + if (!handler) { + return { + type: "error", + id: req.id, + operation: String(req.operation), + error: getErrorDetailFromException( + Error(`unknown extension operation`), + ), + }; + } + try { + const result = await handler(req.payload); + return { + type: "response", + id: req.id, + operation: String(req.operation), + result, + }; + } catch (er) { + return { + type: "error", + id: req.id, + error: getErrorDetailFromException(er), + operation: String(req.operation), + }; + } } - } + case "wallet": { + const w = currentWallet; + if (!w) { + return { + type: "error", + id: req.id, + operation: req.operation, + error: makeErrorDetail( + TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, + {}, + "wallet core not available", + ), + }; + } - if (req.channel === "wallet") { - const w = currentWallet; - if (!w) { - return { - type: "error", - id: req.id, - operation: req.operation, - error: makeErrorDetail( - TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, - {}, - "wallet core not available", - ), - }; + return await w.handleCoreApiRequest(req.operation, req.id, req.payload); } - - return await w.handleCoreApiRequest(req.operation, req.id, req.payload); } const anyReq = req as any; @@ -261,7 +282,7 @@ async function dispatch( operation: String(anyReq.operation), error: getErrorDetailFromException( Error( - `unknown channel ${anyReq.channel}, should be "background" or "wallet"`, + `unknown channel ${anyReq.channel}, should be "background", "extension" or "wallet"`, ), ), }; @@ -330,23 +351,6 @@ async function reinitWallet(): Promise { return walletInit.resolve(); } -function parseTalerUriAndRedirect(tabId: number, maybeTalerUri: string): void { - const talerUri = maybeTalerUri.startsWith("ext+") - ? maybeTalerUri.substring(4) - : maybeTalerUri; - const uri = parseTalerUri(talerUri); - if (!uri) { - logger.warn( - `Response with HTTP 402 the Taler header but could not classify ${talerUri}`, - ); - return; - } - return platform.redirectTabToWalletPage( - tabId, - `/taler-uri/${encodeURIComponent(talerUri)}`, - ); -} - /** * Main function to run for the WebExtension backend. * @@ -370,30 +374,10 @@ export async function wxMain(): Promise { platform.registerAllIncomingConnections(); try { - platform.registerOnInstalled(() => { + await platform.registerOnInstalled(() => { platform.openWalletPage("/welcome"); - - // - try { - platform.registerTalerHeaderListener(parseTalerUriAndRedirect); - } catch (e) { - logger.error("could not register header listener", e); - } }); } catch (e) { console.error(e); } - - // On platforms that support it, also listen to external - // modification of permissions. - platform.getPermissionsApi().addPermissionsListener((perm, lastError) => { - if (lastError) { - logger.error( - `there was a problem trying to get permission ${perm}`, - lastError, - ); - return; - } - platform.registerTalerHeaderListener(parseTalerUriAndRedirect); - }); }