From bc817a638d4ddfff0d5f05b51453c6ca790b24ec Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 16 Jan 2022 17:54:48 -0300 Subject: [PATCH] #7120 manifest v3: first iteration working new permission needed: scripting chrome.browserAction -> chrome.action webRequestBlocking is not possible anymore chrome.extension.getUrl -> chrome.runtime.getUrl new serviceWorkerHttpLib: using fetch new serviceWorkerCryptoWorkerFactory: using syncCryptoImpl few other minor changes still missing some other changes like migrating setTimeout to chrome.alarms api --- .../manifest-v3.json | 25 +-- .../taler-wallet-webextension/package.json | 2 +- .../src/api/browser.ts | 30 ++++ .../src/background.ts | 10 +- .../src/chromeBadge.ts | 2 +- .../src/permissions.ts | 2 +- .../src/popup/DeveloperPage.tsx | 2 +- .../src/popup/Settings.tsx | 4 +- .../src/popupEntryPoint.tsx | 2 +- .../src/renderHtml.tsx | 2 +- .../src/serviceWorkerCryptoWorkerFactory.ts | 36 +++++ .../src/serviceWorkerHttpLib.ts | 146 ++++++++++++++++++ .../src/utils/index.ts | 2 +- .../src/wxBackend.ts | 71 +++++---- pnpm-lock.yaml | 8 +- 15 files changed, 282 insertions(+), 62 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts create mode 100644 packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json index b7f7c9026..6024b7505 100644 --- a/packages/taler-wallet-webextension/manifest-v3.json +++ b/packages/taler-wallet-webextension/manifest-v3.json @@ -1,41 +1,28 @@ { "manifest_version": 3, - "name": "GNU Taler Wallet (git)", "description": "Privacy preserving and transparent payments", "author": "GNU Taler Developers", "version": "0.8.1.15", "version_name": "0.8.1-dev.15", - "minimum_chrome_version": "88", - - "applications": { - "gecko": { - "id": "wallet@taler.net", - "strict_min_version": "57.0" - } - }, - "icons": { "32": "static/img/icon.png", "128": "static/img/logo.png" }, - "permissions": [ "unlimitedStorage", - "activeTab" + "activeTab", + "scripting" ], - "optional_permissions": [ "webRequest", "webRequestBlocking" ], - - "host_permissions":[ + "host_permissions": [ "http://*/*", "https://*/*" ], - "action": { "default_icon": { "32": "static/img/icon.png" @@ -43,9 +30,7 @@ "default_title": "Taler", "default_popup": "static/popup.html" }, - "background": { - "page": "static/background.html", - "persistent": true + "service_worker": "dist/background.js" } -} +} \ No newline at end of file diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index fd4b0fb45..9b02eee24 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -48,7 +48,7 @@ "@storybook/preact": "6.4.9", "@testing-library/preact": "^2.0.1", "@testing-library/preact-hooks": "^1.1.0", - "@types/chrome": "^0.0.174", + "@types/chrome": "0.0.176", "@types/history": "^4.7.8", "@types/mocha": "^9.0.0", "@types/node": "^17.0.8", diff --git a/packages/taler-wallet-webextension/src/api/browser.ts b/packages/taler-wallet-webextension/src/api/browser.ts index bc50853fb..b69a49680 100644 --- a/packages/taler-wallet-webextension/src/api/browser.ts +++ b/packages/taler-wallet-webextension/src/api/browser.ts @@ -1,6 +1,36 @@ +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 { + 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: ` diff --git a/packages/taler-wallet-webextension/src/background.ts b/packages/taler-wallet-webextension/src/background.ts index dcbf96139..428cd86f5 100644 --- a/packages/taler-wallet-webextension/src/background.ts +++ b/packages/taler-wallet-webextension/src/background.ts @@ -25,6 +25,12 @@ */ import { wxMain } from "./wxBackend"; -window.addEventListener("load", () => { +const loadedFromWebpage = typeof window !== "undefined" + +if (chrome.runtime.getManifest().manifest_version === 3) { wxMain(); -}); +} else { + window.addEventListener("load", () => { + wxMain(); + }); +} diff --git a/packages/taler-wallet-webextension/src/chromeBadge.ts b/packages/taler-wallet-webextension/src/chromeBadge.ts index 7bc5d368d..60585793e 100644 --- a/packages/taler-wallet-webextension/src/chromeBadge.ts +++ b/packages/taler-wallet-webextension/src/chromeBadge.ts @@ -198,7 +198,7 @@ export class ChromeBadge { this.canvas.width, this.canvas.height, ); - chrome.browserAction.setIcon({ imageData }); + chrome.action.setIcon({ imageData }); } catch (e) { // Might fail if browser has over-eager canvas fingerprinting countermeasures. // There's nothing we can do then ... diff --git a/packages/taler-wallet-webextension/src/permissions.ts b/packages/taler-wallet-webextension/src/permissions.ts index bcd357fd6..909433bb8 100644 --- a/packages/taler-wallet-webextension/src/permissions.ts +++ b/packages/taler-wallet-webextension/src/permissions.ts @@ -15,6 +15,6 @@ */ export const extendedPermissions = { - permissions: ["webRequest", "webRequestBlocking"], + permissions: ["webRequest"], origins: ["http://*/*", "https://*/*"], }; diff --git a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx index ea87ba01f..f4b49c230 100644 --- a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx @@ -194,7 +194,7 @@ export function openExtensionPage(page: string) { // eslint-disable-next-line no-undef chrome.tabs.create({ // eslint-disable-next-line no-undef - url: chrome.extension.getURL(page), + url: chrome.runtime.getURL(page), }); }; } diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx index 3732cf7b5..a7cdf9cc0 100644 --- a/packages/taler-wallet-webextension/src/popup/Settings.tsx +++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx @@ -60,9 +60,9 @@ export function SettingsView({ style={{ color: "darkgreen", textDecoration: "none" }} href={ // eslint-disable-next-line no-undef - chrome.extension + chrome.runtime ? // eslint-disable-next-line no-undef - chrome.extension.getURL(`/static/wallet.html#/settings`) + chrome.runtime.getURL(`/static/wallet.html#/settings`) : "#" } > diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index 908349e89..5cd68b9b4 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -173,7 +173,7 @@ function goToWalletPage(page: Pages | string): null { chrome.tabs.create({ active: true, // eslint-disable-next-line no-undef - url: chrome.extension.getURL(`/static/wallet.html#${page}`), + url: chrome.runtime.getURL(`/static/wallet.html#${page}`), }); return null; } diff --git a/packages/taler-wallet-webextension/src/renderHtml.tsx b/packages/taler-wallet-webextension/src/renderHtml.tsx index ba98ae237..1e482ccea 100644 --- a/packages/taler-wallet-webextension/src/renderHtml.tsx +++ b/packages/taler-wallet-webextension/src/renderHtml.tsx @@ -167,7 +167,7 @@ export function PageLink(props: { typeof chrome === "undefined" ? undefined : // eslint-disable-next-line no-undef - chrome.extension?.getURL(`/static/wallet.html#/${props.pageName}`); + chrome.runtime?.getURL(`/static/wallet.html#/${props.pageName}`); return ( {props.children} diff --git a/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts b/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts new file mode 100644 index 000000000..6084ebae1 --- /dev/null +++ b/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts @@ -0,0 +1,36 @@ +/* + This file is part of TALER + (C) 2016 GNUnet e.V. + + 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 + */ + +/** + * API to access the Taler crypto worker thread. + * @author Florian Dold + */ + +import { + CryptoWorker, + CryptoWorkerFactory, + SynchronousCryptoWorker, +} from "@gnu-taler/taler-wallet-core"; + +export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory { + startWorker(): CryptoWorker { + return new SynchronousCryptoWorker(); + } + + getConcurrency(): number { + return 1; + } +} diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts new file mode 100644 index 000000000..f8953f73f --- /dev/null +++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts @@ -0,0 +1,146 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * Imports. + */ +import { Logger, TalerErrorCode } from "@gnu-taler/taler-util"; +import { + Headers, HttpRequestLibrary, + HttpRequestOptions, + HttpResponse, + OperationFailedError +} from "@gnu-taler/taler-wallet-core"; + +const logger = new Logger("browserHttpLib"); + +/** + * An implementation of the [[HttpRequestLibrary]] using the + * browser's XMLHttpRequest. + */ +export class ServiceWorkerHttpLib implements HttpRequestLibrary { + async fetch(requestUrl: string, options?: HttpRequestOptions): Promise { + const requestMethod = options?.method ?? "GET"; + const requestBody = options?.body; + const requestHeader = options?.headers; + + const response = await fetch(requestUrl, { + headers: requestHeader, + body: requestBody, + method: requestMethod, + // timeout: options?.timeout + }) + + const headerMap = new Headers(); + response.headers.forEach(addLineToMap(headerMap)); + + return { + headers: headerMap, + status: response.status, + requestMethod, + requestUrl, + json: makeJsonHandler(response, requestUrl), + text: makeTextHandler(response, requestUrl), + bytes: async () => (await response.blob()).arrayBuffer(), + } + + } + + + get(url: string, opt?: HttpRequestOptions): Promise { + return this.fetch(url, { + method: "GET", + ...opt, + }); + } + + postJson( + url: string, + body: any, + opt?: HttpRequestOptions, + ): Promise { + return this.fetch(url, { + method: "POST", + body: JSON.stringify(body), + ...opt, + }); + } + + stop(): void { + // Nothing to do + } +} + +function makeTextHandler(response: Response, requestUrl: string) { + return async function getJsonFromResponse(): Promise { + let respText; + try { + respText = await response.text() + } catch (e) { + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + "Invalid JSON from HTTP response", + { + requestUrl, + httpStatusCode: response.status, + }, + ); + } + return respText + } +} + +function makeJsonHandler(response: Response, requestUrl: string) { + return async function getJsonFromResponse(): Promise { + let responseJson; + try { + responseJson = await response.json() + } catch (e) { + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + "Invalid JSON from HTTP response", + { + requestUrl, + httpStatusCode: response.status, + }, + ); + } + if (responseJson === null || typeof responseJson !== "object") { + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + "Invalid JSON from HTTP response", + { + requestUrl, + httpStatusCode: response.status, + }, + ); + } + return responseJson + } +} + +function addLineToMap(map: { set(k: string, v: string): void }) { + return (line: string) => { + const parts = line.split(": "); + const headerName = parts.shift(); + if (!headerName) { + logger.warn("skipping invalid header"); + return; + } + const value = parts.join(": "); + map.set(headerName, value); + } +} \ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts index 88f9bc4b3..55898d181 100644 --- a/packages/taler-wallet-webextension/src/utils/index.ts +++ b/packages/taler-wallet-webextension/src/utils/index.ts @@ -225,7 +225,7 @@ function makeExtensionUrlWithParams( params?: { [name: string]: string | undefined }, ): string { // eslint-disable-next-line no-undef - const innerUrl = new URL(chrome.extension.getURL("/" + url)); + const innerUrl = new URL(chrome.runtime.getURL("/" + url)); if (params) { const hParams = Object.keys(params) .map((k) => `${k}=${params[k]}`) diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 10889f781..412f33f12 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -23,18 +23,6 @@ /** * Imports. */ -import { isFirefox, getPermissionsApi } from "./compat"; -import { extendedPermissions } from "./permissions"; -import { - OpenedPromise, - openPromise, - openTalerDatabase, - makeErrorDetails, - deleteTalerDatabase, - DbAccess, - WalletStoresV1, - Wallet, -} from "@gnu-taler/taler-wallet-core"; import { classifyTalerUri, CoreApiResponse, @@ -42,10 +30,19 @@ import { NotificationType, TalerErrorCode, TalerUriType, - WalletDiagnostics, + WalletDiagnostics } from "@gnu-taler/taler-util"; -import { BrowserHttpLib } from "./browserHttpLib"; +import { + DbAccess, deleteTalerDatabase, makeErrorDetails, OpenedPromise, + openPromise, + openTalerDatabase, Wallet, WalletStoresV1 +} from "@gnu-taler/taler-wallet-core"; import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory"; +import { BrowserHttpLib } from "./browserHttpLib"; +import { getPermissionsApi, isFirefox } from "./compat"; +import { extendedPermissions } from "./permissions"; +import { SynchronousCryptoWorkerFactory } from "./serviceWorkerCryptoWorkerFactory.js"; +import { ServiceWorkerHttpLib } from "./serviceWorkerHttpLib"; /** * Currently active wallet instance. Might be unloaded and @@ -188,10 +185,10 @@ function getTab(tabId: number): Promise { }); } -function setBadgeText(options: chrome.browserAction.BadgeTextDetails): void { +function setBadgeText(options: chrome.action.BadgeTextDetails): void { // not supported by all browsers ... - if (chrome && chrome.browserAction && chrome.browserAction.setBadgeText) { - chrome.browserAction.setBadgeText(options); + if (chrome && chrome.action && chrome.action.setBadgeText) { + chrome.action.setBadgeText(options); } else { console.warn("can't set badge text, not supported", options); } @@ -214,7 +211,7 @@ function makeSyncWalletRedirect( oldUrl: string, params?: { [name: string]: string | undefined }, ): Record { - const innerUrl = new URL(chrome.extension.getURL(url)); + const innerUrl = new URL(chrome.runtime.getURL(url)); if (params) { const hParams = Object.keys(params) .map((k) => `${k}=${params[k]}`) @@ -256,12 +253,22 @@ async function reinitWallet(): Promise { walletInit.reject(e); return; } - const http = new BrowserHttpLib(); + let httpLib; + let cryptoWorker; + + if (chrome.runtime.getManifest().manifest_version === 3) { + httpLib = new ServiceWorkerHttpLib() + cryptoWorker = new SynchronousCryptoWorkerFactory(); + } else { + httpLib = new BrowserHttpLib() + cryptoWorker = new BrowserCryptoWorkerFactory() + } + console.log("setting wallet"); const wallet = await Wallet.create( currentDatabase, - http, - new BrowserCryptoWorkerFactory(), + httpLib, + cryptoWorker, ); try { await wallet.handleCoreApiRequest("initWallet", "native-init", {}); @@ -284,7 +291,9 @@ async function reinitWallet(): Promise { console.log("error during wallet task loop", e); }); // Useful for debugging in the background page. - (window as any).talerWallet = wallet; + if (typeof window !== "undefined") { + (window as any).talerWallet = wallet; + } currentWallet = wallet; walletInit.resolve(); } @@ -295,8 +304,8 @@ try { chrome.runtime.onInstalled.addListener((details) => { console.log("onInstalled with reason", details.reason); if (details.reason === "install") { - const url = chrome.extension.getURL("/static/wallet.html#/welcome"); - chrome.tabs.create({ active: true, url: url }); + const url = chrome.runtime.getURL("/static/wallet.html#/welcome"); + chrome.tabs.create({ active: true, url }); } }); } catch (e) { @@ -387,6 +396,10 @@ function headerListener( } 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(extendedPermissions, (result: boolean) => { @@ -427,12 +440,14 @@ export async function wxMain(): Promise { console.log("update available:", details); chrome.runtime.reload(); }); - reinitWallet(); + const afterWalletIsInitialized = reinitWallet(); // Handlers for messages coming directly from the content // script on the page chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { - dispatch(req, sender, sendResponse); + afterWalletIsInitialized.then(() => { + dispatch(req, sender, sendResponse); + }) return true; }); @@ -447,7 +462,9 @@ export async function wxMain(): Promise { }); try { - setupHeaderListener(); + if (chrome.runtime.getManifest().manifest_version === 2) { + setupHeaderListener(); + } } catch (e) { console.log(e); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33b57d498..c39313437 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -338,7 +338,7 @@ importers: '@storybook/preact': 6.4.9 '@testing-library/preact': ^2.0.1 '@testing-library/preact-hooks': ^1.1.0 - '@types/chrome': ^0.0.174 + '@types/chrome': 0.0.176 '@types/history': ^4.7.8 '@types/mocha': ^9.0.0 '@types/node': ^17.0.8 @@ -393,7 +393,7 @@ importers: '@storybook/preact': 6.4.9_7ac135b2eab8a45315147b85be8cb430 '@testing-library/preact': 2.0.1_preact@10.5.14 '@testing-library/preact-hooks': 1.1.0_6273b572ba1ed58b8bbb2ee93959f9e4 - '@types/chrome': 0.0.174 + '@types/chrome': 0.0.176 '@types/history': 4.7.9 '@types/mocha': 9.0.0 '@types/node': 17.0.8 @@ -9654,8 +9654,8 @@ packages: '@types/node': 17.0.8 dev: true - /@types/chrome/0.0.174: - resolution: {integrity: sha512-x5kjvNdwDtOnT+vbnksj69pDl0u9P/WH9LbQWJawLqGgkBRO3AN/xzTxTPgLpp3IqCbuwfp7bRCHqkkaZguzWw==} + /@types/chrome/0.0.176: + resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==} dependencies: '@types/filesystem': 0.0.32 '@types/har-format': 1.2.8