#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,
"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"
}
}

View File

@ -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",

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> {
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: `

View File

@ -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();
});
}

View File

@ -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 ...

View File

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

View File

@ -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),
});
};
}

View File

@ -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`)
: "#"
}
>

View File

@ -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;
}

View File

@ -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 (
<a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
{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 },
): 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]}`)

View File

@ -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<chrome.tabs.Tab> {
});
}
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<string, unknown> {
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<void> {
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<void> {
console.log("error during wallet task loop", e);
});
// Useful for debugging in the background page.
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<void> {
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) => {
afterWalletIsInitialized.then(() => {
dispatch(req, sender, sendResponse);
})
return true;
});
@ -447,7 +462,9 @@ export async function wxMain(): Promise<void> {
});
try {
if (chrome.runtime.getManifest().manifest_version === 2) {
setupHeaderListener();
}
} catch (e) {
console.log(e);
}

View File

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