#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
This commit is contained in:
Sebastian 2022-01-16 17:54:48 -03:00
parent f8ae2671c1
commit bc817a638d
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
15 changed files with 282 additions and 62 deletions

View File

@ -1,41 +1,28 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "GNU Taler Wallet (git)", "name": "GNU Taler Wallet (git)",
"description": "Privacy preserving and transparent payments", "description": "Privacy preserving and transparent payments",
"author": "GNU Taler Developers", "author": "GNU Taler Developers",
"version": "0.8.1.15", "version": "0.8.1.15",
"version_name": "0.8.1-dev.15", "version_name": "0.8.1-dev.15",
"minimum_chrome_version": "88", "minimum_chrome_version": "88",
"applications": {
"gecko": {
"id": "wallet@taler.net",
"strict_min_version": "57.0"
}
},
"icons": { "icons": {
"32": "static/img/icon.png", "32": "static/img/icon.png",
"128": "static/img/logo.png" "128": "static/img/logo.png"
}, },
"permissions": [ "permissions": [
"unlimitedStorage", "unlimitedStorage",
"activeTab" "activeTab",
"scripting"
], ],
"optional_permissions": [ "optional_permissions": [
"webRequest", "webRequest",
"webRequestBlocking" "webRequestBlocking"
], ],
"host_permissions": [ "host_permissions": [
"http://*/*", "http://*/*",
"https://*/*" "https://*/*"
], ],
"action": { "action": {
"default_icon": { "default_icon": {
"32": "static/img/icon.png" "32": "static/img/icon.png"
@ -43,9 +30,7 @@
"default_title": "Taler", "default_title": "Taler",
"default_popup": "static/popup.html" "default_popup": "static/popup.html"
}, },
"background": { "background": {
"page": "static/background.html", "service_worker": "dist/background.js"
"persistent": true
} }
} }

View File

@ -48,7 +48,7 @@
"@storybook/preact": "6.4.9", "@storybook/preact": "6.4.9",
"@testing-library/preact": "^2.0.1", "@testing-library/preact": "^2.0.1",
"@testing-library/preact-hooks": "^1.1.0", "@testing-library/preact-hooks": "^1.1.0",
"@types/chrome": "^0.0.174", "@types/chrome": "0.0.176",
"@types/history": "^4.7.8", "@types/history": "^4.7.8",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",
"@types/node": "^17.0.8", "@types/node": "^17.0.8",

View File

@ -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<string | undefined> { 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) => { return new Promise((resolve, reject) => {
//manifest v2
chrome.tabs.executeScript( chrome.tabs.executeScript(
{ {
code: ` code: `

View File

@ -25,6 +25,12 @@
*/ */
import { wxMain } from "./wxBackend"; import { wxMain } from "./wxBackend";
const loadedFromWebpage = typeof window !== "undefined"
if (chrome.runtime.getManifest().manifest_version === 3) {
wxMain();
} else {
window.addEventListener("load", () => { window.addEventListener("load", () => {
wxMain(); wxMain();
}); });
}

View File

@ -198,7 +198,7 @@ export class ChromeBadge {
this.canvas.width, this.canvas.width,
this.canvas.height, this.canvas.height,
); );
chrome.browserAction.setIcon({ imageData }); chrome.action.setIcon({ imageData });
} catch (e) { } catch (e) {
// Might fail if browser has over-eager canvas fingerprinting countermeasures. // Might fail if browser has over-eager canvas fingerprinting countermeasures.
// There's nothing we can do then ... // There's nothing we can do then ...

View File

@ -15,6 +15,6 @@
*/ */
export const extendedPermissions = { export const extendedPermissions = {
permissions: ["webRequest", "webRequestBlocking"], permissions: ["webRequest"],
origins: ["http://*/*", "https://*/*"], origins: ["http://*/*", "https://*/*"],
}; };

View File

@ -194,7 +194,7 @@ export function openExtensionPage(page: string) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
chrome.tabs.create({ chrome.tabs.create({
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
url: chrome.extension.getURL(page), url: chrome.runtime.getURL(page),
}); });
}; };
} }

View File

@ -60,9 +60,9 @@ export function SettingsView({
style={{ color: "darkgreen", textDecoration: "none" }} style={{ color: "darkgreen", textDecoration: "none" }}
href={ href={
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
chrome.extension chrome.runtime
? // eslint-disable-next-line no-undef ? // eslint-disable-next-line no-undef
chrome.extension.getURL(`/static/wallet.html#/settings`) chrome.runtime.getURL(`/static/wallet.html#/settings`)
: "#" : "#"
} }
> >

View File

@ -173,7 +173,7 @@ function goToWalletPage(page: Pages | string): null {
chrome.tabs.create({ chrome.tabs.create({
active: true, active: true,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
url: chrome.extension.getURL(`/static/wallet.html#${page}`), url: chrome.runtime.getURL(`/static/wallet.html#${page}`),
}); });
return null; return null;
} }

View File

@ -167,7 +167,7 @@ export function PageLink(props: {
typeof chrome === "undefined" typeof chrome === "undefined"
? undefined ? undefined
: // eslint-disable-next-line no-undef : // eslint-disable-next-line no-undef
chrome.extension?.getURL(`/static/wallet.html#/${props.pageName}`); chrome.runtime?.getURL(`/static/wallet.html#/${props.pageName}`);
return ( return (
<a class="actionLink" href={url} target="_blank" rel="noopener noreferrer"> <a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
{props.children} {props.children}

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* 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<HttpResponse> {
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<HttpResponse> {
return this.fetch(url, {
method: "GET",
...opt,
});
}
postJson(
url: string,
body: any,
opt?: HttpRequestOptions,
): Promise<HttpResponse> {
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<any> {
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<any> {
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);
}
}

View File

@ -225,7 +225,7 @@ function makeExtensionUrlWithParams(
params?: { [name: string]: string | undefined }, params?: { [name: string]: string | undefined },
): string { ): string {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const innerUrl = new URL(chrome.extension.getURL("/" + url)); const innerUrl = new URL(chrome.runtime.getURL("/" + url));
if (params) { if (params) {
const hParams = Object.keys(params) const hParams = Object.keys(params)
.map((k) => `${k}=${params[k]}`) .map((k) => `${k}=${params[k]}`)

View File

@ -23,18 +23,6 @@
/** /**
* Imports. * 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 { import {
classifyTalerUri, classifyTalerUri,
CoreApiResponse, CoreApiResponse,
@ -42,10 +30,19 @@ import {
NotificationType, NotificationType,
TalerErrorCode, TalerErrorCode,
TalerUriType, TalerUriType,
WalletDiagnostics, WalletDiagnostics
} from "@gnu-taler/taler-util"; } 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 { 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 * Currently active wallet instance. Might be unloaded and
@ -188,10 +185,10 @@ function getTab(tabId: number): Promise<chrome.tabs.Tab> {
}); });
} }
function setBadgeText(options: chrome.browserAction.BadgeTextDetails): void { function setBadgeText(options: chrome.action.BadgeTextDetails): void {
// not supported by all browsers ... // not supported by all browsers ...
if (chrome && chrome.browserAction && chrome.browserAction.setBadgeText) { if (chrome && chrome.action && chrome.action.setBadgeText) {
chrome.browserAction.setBadgeText(options); chrome.action.setBadgeText(options);
} else { } else {
console.warn("can't set badge text, not supported", options); console.warn("can't set badge text, not supported", options);
} }
@ -214,7 +211,7 @@ function makeSyncWalletRedirect(
oldUrl: string, oldUrl: string,
params?: { [name: string]: string | undefined }, params?: { [name: string]: string | undefined },
): Record<string, unknown> { ): Record<string, unknown> {
const innerUrl = new URL(chrome.extension.getURL(url)); const innerUrl = new URL(chrome.runtime.getURL(url));
if (params) { if (params) {
const hParams = Object.keys(params) const hParams = Object.keys(params)
.map((k) => `${k}=${params[k]}`) .map((k) => `${k}=${params[k]}`)
@ -256,12 +253,22 @@ async function reinitWallet(): Promise<void> {
walletInit.reject(e); walletInit.reject(e);
return; 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"); console.log("setting wallet");
const wallet = await Wallet.create( const wallet = await Wallet.create(
currentDatabase, currentDatabase,
http, httpLib,
new BrowserCryptoWorkerFactory(), cryptoWorker,
); );
try { try {
await wallet.handleCoreApiRequest("initWallet", "native-init", {}); await wallet.handleCoreApiRequest("initWallet", "native-init", {});
@ -284,7 +291,9 @@ async function reinitWallet(): Promise<void> {
console.log("error during wallet task loop", e); console.log("error during wallet task loop", e);
}); });
// Useful for debugging in the background page. // Useful for debugging in the background page.
if (typeof window !== "undefined") {
(window as any).talerWallet = wallet; (window as any).talerWallet = wallet;
}
currentWallet = wallet; currentWallet = wallet;
walletInit.resolve(); walletInit.resolve();
} }
@ -295,8 +304,8 @@ try {
chrome.runtime.onInstalled.addListener((details) => { chrome.runtime.onInstalled.addListener((details) => {
console.log("onInstalled with reason", details.reason); console.log("onInstalled with reason", details.reason);
if (details.reason === "install") { if (details.reason === "install") {
const url = chrome.extension.getURL("/static/wallet.html#/welcome"); const url = chrome.runtime.getURL("/static/wallet.html#/welcome");
chrome.tabs.create({ active: true, url: url }); chrome.tabs.create({ active: true, url });
} }
}); });
} catch (e) { } catch (e) {
@ -387,6 +396,10 @@ function headerListener(
} }
function setupHeaderListener(): void { 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"); console.log("setting up header listener");
// Handlers for catching HTTP requests // Handlers for catching HTTP requests
getPermissionsApi().contains(extendedPermissions, (result: boolean) => { getPermissionsApi().contains(extendedPermissions, (result: boolean) => {
@ -427,12 +440,14 @@ export async function wxMain(): Promise<void> {
console.log("update available:", details); console.log("update available:", details);
chrome.runtime.reload(); chrome.runtime.reload();
}); });
reinitWallet(); const afterWalletIsInitialized = reinitWallet();
// Handlers for messages coming directly from the content // Handlers for messages coming directly from the content
// script on the page // script on the page
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
afterWalletIsInitialized.then(() => {
dispatch(req, sender, sendResponse); dispatch(req, sender, sendResponse);
})
return true; return true;
}); });
@ -447,7 +462,9 @@ export async function wxMain(): Promise<void> {
}); });
try { try {
if (chrome.runtime.getManifest().manifest_version === 2) {
setupHeaderListener(); setupHeaderListener();
}
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -338,7 +338,7 @@ importers:
'@storybook/preact': 6.4.9 '@storybook/preact': 6.4.9
'@testing-library/preact': ^2.0.1 '@testing-library/preact': ^2.0.1
'@testing-library/preact-hooks': ^1.1.0 '@testing-library/preact-hooks': ^1.1.0
'@types/chrome': ^0.0.174 '@types/chrome': 0.0.176
'@types/history': ^4.7.8 '@types/history': ^4.7.8
'@types/mocha': ^9.0.0 '@types/mocha': ^9.0.0
'@types/node': ^17.0.8 '@types/node': ^17.0.8
@ -393,7 +393,7 @@ importers:
'@storybook/preact': 6.4.9_7ac135b2eab8a45315147b85be8cb430 '@storybook/preact': 6.4.9_7ac135b2eab8a45315147b85be8cb430
'@testing-library/preact': 2.0.1_preact@10.5.14 '@testing-library/preact': 2.0.1_preact@10.5.14
'@testing-library/preact-hooks': 1.1.0_6273b572ba1ed58b8bbb2ee93959f9e4 '@testing-library/preact-hooks': 1.1.0_6273b572ba1ed58b8bbb2ee93959f9e4
'@types/chrome': 0.0.174 '@types/chrome': 0.0.176
'@types/history': 4.7.9 '@types/history': 4.7.9
'@types/mocha': 9.0.0 '@types/mocha': 9.0.0
'@types/node': 17.0.8 '@types/node': 17.0.8
@ -9654,8 +9654,8 @@ packages:
'@types/node': 17.0.8 '@types/node': 17.0.8
dev: true dev: true
/@types/chrome/0.0.174: /@types/chrome/0.0.176:
resolution: {integrity: sha512-x5kjvNdwDtOnT+vbnksj69pDl0u9P/WH9LbQWJawLqGgkBRO3AN/xzTxTPgLpp3IqCbuwfp7bRCHqkkaZguzWw==} resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
dependencies: dependencies:
'@types/filesystem': 0.0.32 '@types/filesystem': 0.0.32
'@types/har-format': 1.2.8 '@types/har-format': 1.2.8