diff options
Diffstat (limited to 'src/webex/wxBackend.ts')
-rw-r--r-- | src/webex/wxBackend.ts | 575 |
1 files changed, 0 insertions, 575 deletions
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts deleted file mode 100644 index 39fcf899e..000000000 --- a/src/webex/wxBackend.ts +++ /dev/null @@ -1,575 +0,0 @@ -/* - 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/> - */ - -/** - * Messaging for the WebExtensions wallet. Should contain - * parts that are specific for WebExtensions, but as little business - * logic as possible. - */ - -/** - * Imports. - */ -import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi"; -import { - deleteTalerDatabase, - openTalerDatabase, - WALLET_DB_MINOR_VERSION, -} from "../db"; -import { ReturnCoinsRequest, WalletDiagnostics } from "../types/walletTypes"; -import { BrowserHttpLib } from "../util/http"; -import { OpenedPromise, openPromise } from "../util/promiseUtils"; -import { classifyTalerUri, TalerUriType } from "../util/taleruri"; -import { Wallet } from "../wallet"; -import { isFirefox, getPermissionsApi } from "./compat"; -import * as wxApi from "./wxApi"; -import MessageSender = chrome.runtime.MessageSender; -import { Database } from "../util/query"; -import { extendedPermissions } from "./permissions"; - -const NeedsWallet = Symbol("NeedsWallet"); - -/** - * Currently active wallet instance. Might be unloaded and - * re-instantiated when the database is reset. - */ -let currentWallet: Wallet | undefined; - -let currentDatabase: IDBDatabase | undefined; - -/** - * Last version if an outdated DB, if applicable. - */ -let outdatedDbVersion: number | undefined; - -const walletInit: OpenedPromise<void> = openPromise<void>(); - -const notificationPorts: chrome.runtime.Port[] = []; - -async function handleMessage( - sender: MessageSender, - type: string, - detail: any, -): Promise<any> { - function needsWallet(): Wallet { - if (!currentWallet) { - throw NeedsWallet; - } - return currentWallet; - } - switch (type) { - case "balances": { - return needsWallet().getBalances(); - } - case "dump-db": { - const db = needsWallet().db; - return db.exportDatabase(); - } - case "import-db": { - const db = needsWallet().db; - return db.importDatabase(detail.dump); - } - case "ping": { - return Promise.resolve(); - } - case "reset-db": { - deleteTalerDatabase(indexedDB); - setBadgeText({ text: "" }); - console.log("reset done"); - if (!currentWallet) { - reinitWallet(); - } - return Promise.resolve({}); - } - case "confirm-pay": { - if (typeof detail.proposalId !== "string") { - throw Error("proposalId must be string"); - } - return needsWallet().confirmPay(detail.proposalId, detail.sessionId); - } - case "exchange-info": { - if (!detail.baseUrl) { - return Promise.resolve({ error: "bad url" }); - } - return needsWallet().updateExchangeFromUrl(detail.baseUrl); - } - case "get-exchanges": { - return needsWallet().getExchangeRecords(); - } - case "get-currencies": { - return needsWallet().getCurrencies(); - } - case "update-currency": { - return needsWallet().updateCurrency(detail.currencyRecord); - } - case "get-reserves": { - if (typeof detail.exchangeBaseUrl !== "string") { - return Promise.reject(Error("exchangeBaseUrl missing")); - } - return needsWallet().getReserves(detail.exchangeBaseUrl); - } - case "get-coins": { - if (typeof detail.exchangeBaseUrl !== "string") { - return Promise.reject(Error("exchangBaseUrl missing")); - } - return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl); - } - case "get-denoms": { - if (typeof detail.exchangeBaseUrl !== "string") { - return Promise.reject(Error("exchangBaseUrl missing")); - } - return needsWallet().getDenoms(detail.exchangeBaseUrl); - } - case "refresh-coin": { - if (typeof detail.coinPub !== "string") { - return Promise.reject(Error("coinPub missing")); - } - return needsWallet().refresh(detail.coinPub); - } - case "get-sender-wire-infos": { - return needsWallet().getSenderWireInfos(); - } - case "return-coins": { - const d = { - amount: detail.amount, - exchange: detail.exchange, - senderWire: detail.senderWire, - }; - const req = ReturnCoinsRequest.checked(d); - return needsWallet().returnCoins(req); - } - case "check-upgrade": { - let dbResetRequired = false; - if (!currentWallet) { - dbResetRequired = true; - } - const resp: wxApi.UpgradeResponse = { - currentDbVersion: WALLET_DB_MINOR_VERSION.toString(), - dbResetRequired, - oldDbVersion: (outdatedDbVersion || "unknown").toString(), - }; - return resp; - } - case "get-purchase-details": { - const proposalId = detail.proposalId; - if (!proposalId) { - throw Error("proposalId missing"); - } - if (typeof proposalId !== "string") { - throw Error("proposalId must be a string"); - } - return needsWallet().getPurchaseDetails(proposalId); - } - case "accept-refund": - return needsWallet().applyRefund(detail.refundUrl); - case "get-tip-status": { - return needsWallet().getTipStatus(detail.talerTipUri); - } - case "accept-tip": { - return needsWallet().acceptTip(detail.talerTipUri); - } - case "abort-failed-payment": { - if (!detail.contractTermsHash) { - throw Error("contracTermsHash not given"); - } - return needsWallet().abortFailedPayment(detail.contractTermsHash); - } - case "benchmark-crypto": { - if (!detail.repetitions) { - throw Error("repetitions not given"); - } - return needsWallet().benchmarkCrypto(detail.repetitions); - } - case "accept-withdrawal": { - return needsWallet().acceptWithdrawal( - detail.talerWithdrawUri, - detail.selectedExchange, - ); - } - case "get-diagnostics": { - const manifestData = chrome.runtime.getManifest(); - const errors: string[] = []; - let firefoxIdbProblem = false; - let dbOutdated = false; - try { - await walletInit.promise; - } catch (e) { - errors.push("Error during wallet initialization: " + e); - if ( - currentDatabase === undefined && - outdatedDbVersion === undefined && - isFirefox() - ) { - firefoxIdbProblem = true; - } - } - if (!currentWallet) { - errors.push("Could not create wallet backend."); - } - if (!currentDatabase) { - errors.push("Could not open database"); - } - if (outdatedDbVersion !== undefined) { - errors.push(`Outdated DB version: ${outdatedDbVersion}`); - dbOutdated = true; - } - const diagnostics: WalletDiagnostics = { - walletManifestDisplayVersion: - manifestData.version_name || "(undefined)", - walletManifestVersion: manifestData.version, - errors, - firefoxIdbProblem, - dbOutdated, - }; - return diagnostics; - } - case "prepare-pay": - return needsWallet().preparePayForUri(detail.talerPayUri); - case "set-extended-permissions": { - const newVal = detail.value; - console.log("new extended permissions value", newVal); - if (newVal) { - setupHeaderListener(); - return { newValue: true }; - } else { - await new Promise((resolve, reject) => { - getPermissionsApi().remove(extendedPermissions, (rem) => { - console.log("permissions removed:", rem); - resolve(); - }); - }); - return { newVal: false }; - } - } - case "get-extended-permissions": { - const res = await new Promise((resolve, reject) => { - getPermissionsApi().contains(extendedPermissions, (result: boolean) => { - resolve(result); - }); - }); - return { newValue: res }; - } - default: - console.error(`Request type ${type} unknown`); - console.error(`Request detail was ${detail}`); - return { - error: { - message: `request type ${type} unknown`, - requestType: type, - }, - }; - } -} - -async function dispatch( - req: any, - sender: any, - sendResponse: any, -): Promise<void> { - try { - const p = handleMessage(sender, req.type, req.detail); - const r = await p; - try { - sendResponse(r); - } catch (e) { - // might fail if tab disconnected - } - } catch (e) { - console.log(`exception during wallet handler for '${req.type}'`); - console.log("request", req); - console.error(e); - let stack; - try { - stack = e.stack.toString(); - } catch (e) { - // might fail - } - try { - sendResponse({ - error: { - message: e.message, - stack, - }, - }); - } catch (e) { - console.log(e); - // might fail if tab disconnected - } - } -} - -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.browserAction.BadgeTextDetails): void { - // not supported by all browsers ... - if (chrome && chrome.browserAction && chrome.browserAction.setBadgeText) { - chrome.browserAction.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.extension.getURL("/" + url)); - if (params) { - for (const key in params) { - const p = params[key]; - if (p) { - innerUrl.searchParams.set(key, p); - } - } - } - if (isFirefox()) { - // 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) { - chrome.tabs.update(tabId, { url: innerUrl.href }); - } - }; - doit(); - } - console.log("redirecting to", innerUrl.href); - chrome.tabs.update(tabId, { url: innerUrl.href }); - return { redirectUrl: innerUrl.href }; -} - -async function reinitWallet(): Promise<void> { - if (currentWallet) { - currentWallet.stop(); - currentWallet = undefined; - } - currentDatabase = undefined; - setBadgeText({ text: "" }); - try { - currentDatabase = await openTalerDatabase(indexedDB, reinitWallet); - } catch (e) { - console.error("could not open database", e); - walletInit.reject(e); - return; - } - const http = new BrowserHttpLib(); - console.log("setting wallet"); - const wallet = new Wallet( - new Database(currentDatabase), - http, - new BrowserCryptoWorkerFactory(), - ); - wallet.addNotificationListener((x) => { - for (const x of notificationPorts) { - try { - x.postMessage({ type: "notification" }); - } catch (e) { - console.error(e); - } - } - }); - wallet.runRetryLoop().catch((e) => { - console.log("error during wallet retry loop", e); - }); - // Useful for debugging in the background page. - (window as any).talerWallet = wallet; - currentWallet = wallet; - 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.extension.getURL("/welcome.html"); - chrome.tabs.create({ active: true, url: url }); - } - }); -} catch (e) { - console.error(e); -} - -function headerListener( - details: chrome.webRequest.WebResponseHeadersDetails, -): chrome.webRequest.BlockingResponse | undefined { - console.log("header listener"); - if (chrome.runtime.lastError) { - console.error(chrome.runtime.lastError); - return; - } - const wallet = currentWallet; - if (!wallet) { - console.warn("wallet not available while handling header"); - return; - } - console.log("in header listener"); - if (details.statusCode === 402 || details.statusCode === 202) { - console.log(`got 402/202 from ${details.url}`); - 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( - "withdraw.html", - details.tabId, - details.url, - { - talerWithdrawUri: talerUri, - }, - ); - case TalerUriType.TalerPay: - return makeSyncWalletRedirect( - "pay.html", - details.tabId, - details.url, - { - talerPayUri: talerUri, - }, - ); - case TalerUriType.TalerTip: - return makeSyncWalletRedirect( - "tip.html", - details.tabId, - details.url, - { - talerTipUri: talerUri, - }, - ); - case TalerUriType.TalerRefund: - return makeSyncWalletRedirect( - "refund.html", - details.tabId, - details.url, - { - talerRefundUri: talerUri, - }, - ); - case TalerUriType.TalerNotifyReserve: - Promise.resolve().then(() => { - const w = currentWallet; - if (!w) { - return; - } - w.handleNotifyReserve(); - }); - break; - default: - console.warn( - "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.", - ); - break; - } - } - } - } - return; -} - -function setupHeaderListener(): void { - console.log("setting up header listener"); - // Handlers for catching HTTP requests - getPermissionsApi().contains(extendedPermissions, (result: boolean) => { - if ( - chrome.webRequest.onHeadersReceived && - 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", "blocking"], - ); - } - chrome.webRequest.handlerBehaviorChanged(() => { - if (chrome.runtime.lastError) { - console.error(chrome.runtime.lastError); - } - }); - }); -} - -/** - * Main function to run for the WebExtension backend. - * - * 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(); - }); - reinitWallet(); - - // Handlers for messages coming directly from the content - // script on the page - chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { - dispatch(req, sender, sendResponse); - 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); - } - }); - }); - - try { - setupHeaderListener(); - } 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); - return; - } - setupHeaderListener(); - }); -} |