diff options
Diffstat (limited to 'packages/taler-wallet-webextension')
13 files changed, 264 insertions, 561 deletions
diff --git a/packages/taler-wallet-webextension/manifest-v2.json b/packages/taler-wallet-webextension/manifest-v2.json index 3475cd8aa..6f2096b05 100644 --- a/packages/taler-wallet-webextension/manifest-v2.json +++ b/packages/taler-wallet-webextension/manifest-v2.json @@ -18,7 +18,6 @@ "permissions": [ "unlimitedStorage", "storage", - "webRequest", "<all_urls>", "activeTab" ], diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json index d6a303ed6..65a75824b 100644 --- a/packages/taler-wallet-webextension/manifest-v3.json +++ b/packages/taler-wallet-webextension/manifest-v3.json @@ -17,7 +17,6 @@ "storage", "activeTab", "scripting", - "webRequest", "declarativeContent", "alarms" ], diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index a2b26441b..c7d297db9 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -46,20 +46,32 @@ export interface Permissions { * Compatibility API that works on multiple browsers. */ export interface CrossBrowserPermissionsApi { - containsHostPermissions(): Promise<boolean>; - requestHostPermissions(): Promise<boolean>; - removeHostPermissions(): Promise<boolean>; containsClipboardPermissions(): Promise<boolean>; requestClipboardPermissions(): Promise<boolean>; removeClipboardPermissions(): Promise<boolean>; - addPermissionsListener( - callback: (p: Permissions, lastError?: string) => void, - ): void; } -export type MessageFromBackend = WalletNotification; +export enum ExtensionNotificationType { + SettingsChange = "settings-change", +} + +export interface SettingsChangeNotification { + type: ExtensionNotificationType.SettingsChange; + + currentValue: Settings; +} + +export type ExtensionNotification = SettingsChangeNotification + +export type MessageFromBackend = { + type: "wallet", + notification: WalletNotification +} | { + type: "web-extension", + notification: ExtensionNotification +}; export type MessageFromFrontend< Op extends BackgroundOperations | WalletOperations | ExtensionOperations, @@ -110,7 +122,7 @@ export interface Settings extends WebexWalletConfig { } export const defaultSettings: Settings = { - injectTalerSupport: true, + injectTalerSupport: false, autoOpen: true, advanceMode: false, backup: false, @@ -207,13 +219,6 @@ export interface BackgroundPlatformAPI { ) => Promise<MessageResponse>, ): void; - /** - * Use by the wallet backend to activate the listener of HTTP request - */ - registerTalerHeaderListener(): void; - - containsTalerHeaderListener(): boolean; - } export interface ForegroundPlatformAPI { /** diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index 18d282342..d791a560f 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -28,6 +28,7 @@ import { BackgroundOperations } from "../wxApi.js"; import { BackgroundPlatformAPI, CrossBrowserPermissionsApi, + ExtensionNotificationType, ForegroundPlatformAPI, MessageFromBackend, MessageFromFrontend, @@ -60,27 +61,31 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { useServiceWorkerAsBackgroundProcess, keepAlive, listenNetworkConnectionState, - registerTalerHeaderListener, - containsTalerHeaderListener, }; export default api; const logger = new Logger("chrome.ts"); -async function getSettingsFromStorage(): Promise<Settings> { - const data = await chrome.storage.local.get("wallet-settings"); - if (!data) return defaultSettings; - const settings = data["wallet-settings"]; - if (!settings) return defaultSettings; + +const WALLET_STORAGE_KEY = "wallet-settings"; + +function jsonParseOrDefault(unparsed: any, def: any) { + if (!unparsed) return def try { - const parsed = JSON.parse(settings); - return parsed; + return JSON.parse(unparsed); } catch (e) { - return defaultSettings; + return def; } } +async function getSettingsFromStorage(): Promise<Settings> { + const data = await chrome.storage.local.get(WALLET_STORAGE_KEY); + if (!data) return defaultSettings; + const settings = data[WALLET_STORAGE_KEY]; + return jsonParseOrDefault(settings, defaultSettings) +} + function keepAlive(callback: any): void { if (extensionIsManifestV3()) { chrome.alarms.create("wallet-worker", { periodInMinutes: 1 }); @@ -140,21 +145,8 @@ export function removeClipboardPermissions(): Promise<boolean> { }); } -function addPermissionsListener( - callback: (p: Permissions, lastError?: string) => void, -): void { - chrome.permissions.onAdded.addListener((perm: Permissions) => { - const lastError = chrome.runtime.lastError?.message; - callback(perm, lastError); - }); -} - function getPermissionsApi(): CrossBrowserPermissionsApi { return { - containsHostPermissions, - requestHostPermissions, - removeHostPermissions, - addPermissionsListener, requestClipboardPermissions, removeClipboardPermissions, containsClipboardPermissions, @@ -363,6 +355,18 @@ function registerAllIncomingConnections(): void { logger.error("error trying to save incoming connection", e); } }); + chrome.storage.onChanged.addListener((event) => { + if (event[WALLET_STORAGE_KEY]) { + sendMessageToAllChannels({ + type: "web-extension", + notification: { + type: ExtensionNotificationType.SettingsChange, + currentValue: jsonParseOrDefault(event[WALLET_STORAGE_KEY], defaultSettings) + } + }) + } + }) + } function listenToAllChannels( @@ -723,253 +727,3 @@ function listenNetworkConnectionState( }; } -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; - - -function containsTalerHeaderListener(): boolean { - return ( - currentHeaderListener !== undefined - // || currentTabListener !== undefined - ); -} - -function headerListener( - details: chrome.webRequest.WebResponseHeadersDetails, -): chrome.webRequest.BlockingResponse | undefined { - logger.trace("header listener run", details.statusCode, chrome.runtime.lastError) - 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); - - const talerUri = values.length > 0 ? values[0] : undefined - if (talerUri) { - logger.info( - `Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}: ${talerUri}`, - ); - parseTalerUriAndRedirect(details.tabId, talerUri); - return; - } - } - return details; -} -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; - } - redirectTabToWalletPage( - tabId, - `/taler-uri/${encodeURIComponent(talerUri)}`, - ); -} - -/** - * Not needed anymore since SPA use taler support - */ - -// async function tabListener( -// tabId: number, -// info: chrome.tabs.TabChangeInfo, -// ): Promise<void> { -// 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}`); -// parseTalerUriAndRedirect(tabId, uri); -// } -// } - -/** - * unused, declarative redirect is not good enough - * - */ -// async function registerDeclarativeRedirect() { -// await chrome.declarativeNetRequest.updateDynamicRules({ -// removeRuleIds: [1], -// addRules: [ -// { -// id: 1, -// priority: 1, -// condition: { -// urlFilter: "https://developer.chrome.com/docs/extensions/mv2/", -// regexFilter: ".*taler_uri=([^&]*).*", -// // isUrlFilterCaseSensitive: false, -// // requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET] -// // resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME], -// }, -// action: { -// type: chrome.declarativeNetRequest.RuleActionType.REDIRECT, -// redirect: { -// regexSubstitution: `chrome-extension://${chrome.runtime.id}/static/wallet.html?action=\\1`, -// }, -// }, -// }, -// ], -// }); -// } - -function registerTalerHeaderListener(): void { - logger.info("setting up header listener"); - - const prevHeaderListener = currentHeaderListener; - // const prevTabListener = currentTabListener; - - if ( - prevHeaderListener && - chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener) - ) { - return; - // console.log("removming on header listener") - // chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener); - // chrome.webRequest.onCompleted.removeListener(prevHeaderListener); - // chrome.webRequest.onResponseStarted.removeListener(prevHeaderListener); - // chrome.webRequest.onErrorOccurred.removeListener(prevHeaderListener); - } - - // if ( - // prevTabListener && - // chrome?.tabs?.onUpdated?.hasListener(prevTabListener) - // ) { - // console.log("removming on tab listener") - // chrome.tabs.onUpdated.removeListener(prevTabListener); - // } - - console.log("headers on, disabled:", chrome?.webRequest?.onHeadersReceived === undefined) - if (chrome?.webRequest) { - if (extensionIsManifestV3()) { - chrome.webRequest.onHeadersReceived.addListener(headerListener, - { urls: ["<all_urls>"] }, - ["responseHeaders"] - ); - } else { - chrome.webRequest.onHeadersReceived.addListener(headerListener, - { urls: ["<all_urls>"] }, - ["responseHeaders"] - ); - } - // chrome.webRequest.onCompleted.addListener(headerListener, - // { urls: ["<all_urls>"] }, - // ["responseHeaders", "extraHeaders"] - // ); - // chrome.webRequest.onResponseStarted.addListener(headerListener, - // { urls: ["<all_urls>"] }, - // ["responseHeaders", "extraHeaders"] - // ); - // chrome.webRequest.onErrorOccurred.addListener(headerListener, - // { urls: ["<all_urls>"] }, - // ["extraHeaders"] - // ); - 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 hostPermissions = { - permissions: ["webRequest"], - origins: ["http://*/*", "https://*/*"], -}; - -export function containsHostPermissions(): Promise<boolean> { - 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 requestHostPermissions(): Promise<boolean> { - 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 removeHostPermissions(): Promise<boolean> { - //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; - - //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); - }); - }); -}
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/platform/dev.ts b/packages/taler-wallet-webextension/src/platform/dev.ts index 51744e318..2993c88bc 100644 --- a/packages/taler-wallet-webextension/src/platform/dev.ts +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -37,18 +37,11 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { listenNetworkConnectionState, openNewURLFromPopup: () => undefined, getPermissionsApi: () => ({ - addPermissionsListener: () => undefined, - containsHostPermissions: async () => true, - removeHostPermissions: async () => false, - requestHostPermissions: async () => false, containsClipboardPermissions: async () => true, removeClipboardPermissions: async () => false, requestClipboardPermissions: async () => false, }), - // registerDeclarativeRedirect: () => false, - registerTalerHeaderListener: () => false, - containsTalerHeaderListener: () => false, getWalletWebExVersion: () => ({ version: "none", }), diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts b/packages/taler-wallet-webextension/src/platform/firefox.ts index 0bbe805cf..3d67423fd 100644 --- a/packages/taler-wallet-webextension/src/platform/firefox.ts +++ b/packages/taler-wallet-webextension/src/platform/firefox.ts @@ -26,9 +26,6 @@ import chromePlatform, { containsClipboardPermissions as chromeClipContains, removeClipboardPermissions as chromeClipRemove, requestClipboardPermissions as chromeClipRequest, - containsHostPermissions as chromeHostContains, - requestHostPermissions as chromeHostRequest, - removeHostPermissions as chromeHostRemove, } from "./chrome.js"; const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { @@ -47,16 +44,8 @@ function isFirefox(): boolean { return true; } -function addPermissionsListener(callback: (p: Permissions) => void): void { - // throw Error("addPermissionListener is not supported for Firefox"); -} - function getPermissionsApi(): CrossBrowserPermissionsApi { return { - addPermissionsListener, - containsHostPermissions: chromeHostContains, - requestHostPermissions: chromeHostRequest, - removeHostPermissions: chromeHostRemove, containsClipboardPermissions: chromeClipContains, removeClipboardPermissions: chromeClipRemove, requestClipboardPermissions: chromeClipRequest, 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 d1b1dc374..6cc4eb2b4 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts @@ -15,6 +15,7 @@ */ import { CoreApiResponse, TalerError, TalerErrorCode } from "@gnu-taler/taler-util"; +import type { MessageFromBackend } from "./platform/api.js"; /** * This will modify all the pages that the user load when navigating with Web Extension enabled @@ -46,6 +47,9 @@ const suffixIsNotXMLorPDF = const rootElementIsHTML = document.documentElement.nodeName && document.documentElement.nodeName.toLowerCase() === "html"; +// const pageAcceptsTalerSupport = document.head.querySelector( +// "meta[name=taler-support]", +// ); @@ -67,6 +71,7 @@ function convertURIToWebExtensionPath(uri: string) { const shouldNotInject = !documentDocTypeIsHTML || !suffixIsNotXMLorPDF || + // !pageAcceptsTalerSupport || !rootElementIsHTML; const logger = { @@ -93,16 +98,22 @@ function redirectToTalerActionHandler(element: HTMLMetaElement) { return; } - location.href = convertURIToWebExtensionPath(uri) + const walletPage = convertURIToWebExtensionPath(uri) + window.location.replace(walletPage) } -function injectTalerSupportScript(head: HTMLHeadElement) { +function injectTalerSupportScript(head: HTMLHeadElement, trusted: boolean) { const meta = head.querySelector("meta[name=taler-support]") + if (!meta) return; + const content = meta.getAttribute("content"); + if (!content) return; + const features = content.split(",") - const debugEnabled = meta?.getAttribute("debug") === "true"; + const debugEnabled = meta.getAttribute("debug") === "true"; + const hijackEnabled = features.indexOf("uri") !== -1 + const talerApiEnabled = features.indexOf("api") !== -1 && trusted const scriptTag = document.createElement("script"); - scriptTag.setAttribute("async", "false"); const url = new URL( chrome.runtime.getURL("/dist/taler-wallet-interaction-support.js"), @@ -111,6 +122,12 @@ function injectTalerSupportScript(head: HTMLHeadElement) { if (debugEnabled) { url.searchParams.set("debug", "true"); } + if (talerApiEnabled) { + url.searchParams.set("api", "true"); + } + if (hijackEnabled) { + url.searchParams.set("hijack", "true"); + } scriptTag.src = url.href; try { @@ -123,12 +140,14 @@ function injectTalerSupportScript(head: HTMLHeadElement) { export interface ExtensionOperations { - isInjectionEnabled: { + isAutoOpenEnabled: { request: void; response: boolean; }; - isAutoOpenEnabled: { - request: void; + isDomainTrusted: { + request: { + domain: string; + }; response: boolean; }; } @@ -200,48 +219,82 @@ async function sendMessageToBackground<Op extends keyof ExtensionOperations>( }); } +let notificationPort: chrome.runtime.Port | undefined; +function listenToWalletBackground(listener: (m: any) => void): () => void { + if (notificationPort === undefined) { + notificationPort = chrome.runtime.connect({ name: "notifications" }); + } + notificationPort.onMessage.addListener(listener); + function removeListener(): void { + if (notificationPort !== undefined) { + notificationPort.onMessage.removeListener(listener); + } + } + return removeListener; +} + +const loaderSettings = { + isAutoOpenEnabled: false, +} + function start( - onTalerMetaTagFound: (listener:(el: HTMLMetaElement)=>void) => void, - onHeadReady: (listener:(el: HTMLHeadElement)=>void) => void + onTalerMetaTagFound: (listener: (el: HTMLMetaElement) => void) => void, + onHeadReady: (listener: (el: HTMLHeadElement) => void) => void ) { - // do not run everywhere, this is just expected to run on html - // sites + // do not run everywhere, this is just expected to run on site + // that are aware of taler if (shouldNotInject) return; - const isAutoOpenEnabled_promise = callBackground("isAutoOpenEnabled", undefined) - const isInjectionEnabled_promise = callBackground("isInjectionEnabled", undefined) + callBackground("isAutoOpenEnabled", undefined).then(result => { + loaderSettings.isAutoOpenEnabled = result + }) + const isDomainTrusted_promise = callBackground("isDomainTrusted", { + domain: window.location.origin + }) - onTalerMetaTagFound(async (el)=> { - const enabled = await isAutoOpenEnabled_promise; - if (!enabled) return; + onTalerMetaTagFound(async (el) => { + if (!loaderSettings.isAutoOpenEnabled) return; redirectToTalerActionHandler(el) }) onHeadReady(async (el) => { - const enabled = await isInjectionEnabled_promise; - if (!enabled) return; - injectTalerSupportScript(el) + const trusted = await isDomainTrusted_promise + injectTalerSupportScript(el, trusted) + }) + + listenToWalletBackground((e: MessageFromBackend) => { + if (e.type === "web-extension" && e.notification.type === "settings-change") { + const settings = e.notification.currentValue + loaderSettings.isAutoOpenEnabled = settings.autoOpen + } + console.log("loader ->", e) }) } +function isCorrectMetaElement(el: HTMLMetaElement): boolean { + const name = el.getAttribute("name") + if (!name) return false; + if (name !== "taler-uri") return false; + const uri = el.getAttribute("content"); + if (!uri) return false; + return true +} + /** * Tries to find taler meta tag ASAP and report * @param notify * @returns */ -function onTalerMetaTag(notify: (el: HTMLMetaElement) => void) { +function notifyWhenTalerUriIsFound(notify: (el: HTMLMetaElement) => void) { if (document.head) { const element = document.head.querySelector("meta[name=taler-uri]") if (!element) return; if (!(element instanceof HTMLMetaElement)) return; - const name = element.getAttribute("name") - if (!name) return; - if (name !== "taler-uri") return; - const uri = element.getAttribute("content"); - if (!uri) return; - notify(element) + if (isCorrectMetaElement(element)) { + notify(element) + } return; } const obs = new MutationObserver(async function (mutations) { @@ -250,13 +303,10 @@ function onTalerMetaTag(notify: (el: HTMLMetaElement) => void) { if (mut.type === "childList") { mut.addedNodes.forEach((added) => { if (added instanceof HTMLMetaElement) { - const name = added.getAttribute("name") - if (!name) return; - if (name !== "taler-uri") return; - const uri = added.getAttribute("content"); - if (!uri) return; - notify(added) - obs.disconnect() + if (isCorrectMetaElement(added)) { + notify(added) + obs.disconnect() + } } }); } @@ -279,7 +329,7 @@ function onTalerMetaTag(notify: (el: HTMLMetaElement) => void) { * @param notify * @returns */ -function onHeaderReady(notify: (el: HTMLHeadElement) => void) { +function notifyWhenHeadIsFound(notify: (el: HTMLHeadElement) => void) { if (document.head) { notify(document.head) return; @@ -290,7 +340,6 @@ function onHeaderReady(notify: (el: HTMLHeadElement) => void) { if (mut.type === "childList") { mut.addedNodes.forEach((added) => { if (added instanceof HTMLHeadElement) { - notify(added) obs.disconnect() } @@ -309,4 +358,4 @@ function onHeaderReady(notify: (el: HTMLHeadElement) => void) { }) } -start(onTalerMetaTag, onHeaderReady); +start(notifyWhenTalerUriIsFound, notifyWhenHeadIsFound); 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 993c12703..8b15380f9 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts @@ -47,7 +47,7 @@ const shouldNotRun = !documentDocTypeIsHTML || !suffixIsNotXMLorPDF || - // !pageAcceptsTalerSupport || FIXME: removing this before release for testing + !pageAcceptsTalerSupport || !rootElementIsHTML; interface Info { @@ -154,32 +154,38 @@ function start() { if (shouldNotRun) return; - // FIXME: we can remove this if the script caller send information we need if (!(document.currentScript instanceof HTMLScriptElement)) return; const url = new URL(document.currentScript.src); const { protocol, searchParams, hostname } = url; const extensionId = searchParams.get("id") ?? ""; const debugEnabled = searchParams.get("debug") === "true"; - if (debugEnabled) { - logger.debug = logger.info; - } + const apiEnabled = searchParams.get("api") === "true"; + const hijackEnabled = searchParams.get("hijack") === "true"; const info: Info = Object.freeze({ extensionId, protocol, hostname, }); + + if (debugEnabled) { + logger.debug = logger.info; + } + const taler: TalerSupport = { info, __internal: buildApi(info), }; - //@ts-ignore - window.taler = taler; + if (apiEnabled) { + //@ts-ignore + window.taler = taler; + } - //default behavior: register on install - taler.__internal.registerProtocolHandler(); + if (hijackEnabled) { + taler.__internal.registerProtocolHandler(); + } } // utils functions @@ -189,6 +195,6 @@ ); } - return start + start(); })() diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index faa64e07d..d12ae864b 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -40,6 +40,9 @@ import { Button } from "../mui/Button.js"; import { Grid } from "../mui/Grid.js"; import { Paper } from "../mui/Paper.js"; import { TextField } from "../mui/TextField.js"; +import { Checkbox } from "../components/Checkbox.js"; +import { useSettings } from "../hooks/useSettings.js"; +import { useAlertContext } from "../context/alert.js"; export function DeveloperPage(): VNode { const listenAllEvents = Array.from<NotificationType>({ length: 1 }); @@ -132,6 +135,8 @@ export function View({ operations, coins, onDownloadDatabase }: Props): VNode { dump: JSON.parse(str), }); } + const [settings, updateSettings] = useSettings(); + const { safely } = useAlertContext(); const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), @@ -256,26 +261,6 @@ export function View({ operations, coins, onDownloadDatabase }: Props): VNode { <Button variant="contained" onClick={async () => { - api.background.call("toggleHeaderListener", true); - }} - > - <i18n.Translate>enable header listener</i18n.Translate> - </Button> - </Grid> - <Grid item> - <Button - variant="contained" - onClick={async () => { - api.background.call("toggleHeaderListener", false); - }} - > - <i18n.Translate>disable header listener</i18n.Translate> - </Button> - </Grid> - <Grid item> - <Button - variant="contained" - onClick={async () => { navigator.registerProtocolHandler( "taler", `${window.location.origin}/static/wallet.html#/cta/withdraw?talerWithdrawUri=%s`, @@ -360,6 +345,22 @@ export function View({ operations, coins, onDownloadDatabase }: Props): VNode { </Button> </Grid>{" "} </Grid> + <Checkbox + label={i18n.str`Inject Taler support in all pages`} + name="inject" + description={ + <i18n.Translate> + Enabling this option will make `window.taler` be available + in all sites + </i18n.Translate> + } + enabled={settings.injectTalerSupport!} + onToggle={safely("update support injection", async () => { + updateSettings("injectTalerSupport", !settings.injectTalerSupport); + })} + /> + + <Paper style={{ padding: 10, margin: 10 }}> <h3>Logging</h3> <div> @@ -396,92 +397,98 @@ export function View({ operations, coins, onDownloadDatabase }: Props): VNode { Set log level </Button> </Paper> - {downloadedDatabase && ( - <div> - <i18n.Translate> - Database exported at{" "} - <Time - timestamp={AbsoluteTime.fromMilliseconds( - downloadedDatabase.time.getTime(), - )} - format="yyyy/MM/dd HH:mm:ss" - />{" "} - <a - href={`data:text/plain;charset=utf-8;base64,${toBase64( - downloadedDatabase.content, - )}`} - download={`taler-wallet-database-${format( - downloadedDatabase.time, - "yyyy/MM/dd_HH:mm", - )}.json`} - > - <i18n.Translate>click here</i18n.Translate> - </a>{" "} - to download - </i18n.Translate> - </div> - )} + { + downloadedDatabase && ( + <div> + <i18n.Translate> + Database exported at{" "} + <Time + timestamp={AbsoluteTime.fromMilliseconds( + downloadedDatabase.time.getTime(), + )} + format="yyyy/MM/dd HH:mm:ss" + />{" "} + <a + href={`data:text/plain;charset=utf-8;base64,${toBase64( + downloadedDatabase.content, + )}`} + download={`taler-wallet-database-${format( + downloadedDatabase.time, + "yyyy/MM/dd_HH:mm", + )}.json`} + > + <i18n.Translate>click here</i18n.Translate> + </a>{" "} + to download + </i18n.Translate> + </div> + ) + } <br /> <p> <i18n.Translate>Coins</i18n.Translate>: </p> - {Object.keys(money_by_exchange).map((ex, idx) => { - const allcoins = money_by_exchange[ex]; - allcoins.sort((a, b) => { - if (b.denom_value !== a.denom_value) { - return b.denom_value - a.denom_value; - } - return b.denom_fraction - a.denom_fraction; - }); + { + Object.keys(money_by_exchange).map((ex, idx) => { + const allcoins = money_by_exchange[ex]; + allcoins.sort((a, b) => { + if (b.denom_value !== a.denom_value) { + return b.denom_value - a.denom_value; + } + return b.denom_fraction - a.denom_fraction; + }); - const coins = allcoins.reduce( - (prev, cur) => { - if (cur.status === CoinStatus.Fresh) prev.usable.push(cur); - if (cur.status === CoinStatus.Dormant) prev.spent.push(cur); - return prev; - }, - { - spent: [], - usable: [], - } as SplitedCoinInfo, - ); + const coins = allcoins.reduce( + (prev, cur) => { + if (cur.status === CoinStatus.Fresh) prev.usable.push(cur); + if (cur.status === CoinStatus.Dormant) prev.spent.push(cur); + return prev; + }, + { + spent: [], + usable: [], + } as SplitedCoinInfo, + ); - return ( - <ShowAllCoins - key={idx} - coins={coins} - ex={ex} - currencies={currencies} - /> - ); - })} + return ( + <ShowAllCoins + key={idx} + coins={coins} + ex={ex} + currencies={currencies} + /> + ); + }) + } <br /> - {operations && operations.length > 0 && ( - <Fragment> - <p> - <i18n.Translate>Pending operations</i18n.Translate> - </p> - <dl> - {operations.reverse().map((o) => { - return ( - <NotifyUpdateFadeOut key={hashObjectId(o)}> - <dt> - {o.type}{" "} - <Time - timestamp={o.timestampDue} - format="yy/MM/dd HH:mm:ss" - /> - </dt> - <dd> - <pre>{JSON.stringify(o, undefined, 2)}</pre> - </dd> - </NotifyUpdateFadeOut> - ); - })} - </dl> - </Fragment> - )} - </div> + { + operations && operations.length > 0 && ( + <Fragment> + <p> + <i18n.Translate>Pending operations</i18n.Translate> + </p> + <dl> + {operations.reverse().map((o) => { + return ( + <NotifyUpdateFadeOut key={hashObjectId(o)}> + <dt> + {o.type}{" "} + <Time + timestamp={o.timestampDue} + format="yy/MM/dd HH:mm:ss" + /> + </dt> + <dd> + <pre>{JSON.stringify(o, undefined, 2)}</pre> + </dd> + </NotifyUpdateFadeOut> + ); + })} + </dl> + </Fragment> + ) + } + </div > ); } diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx index 86c420b91..a5d6972de 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx @@ -55,7 +55,6 @@ export const AllOff = tests.createExample(TestedComponent, { deviceName: "this-is-the-device-name", advanceToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} }, - injectTalerToggle: { value: false, button: {} }, langToggle: { value: false, button: {} }, setDeviceName: () => Promise.resolve(), ...version, @@ -65,7 +64,6 @@ export const OneChecked = tests.createExample(TestedComponent, { deviceName: "this-is-the-device-name", advanceToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} }, - injectTalerToggle: { value: false, button: {} }, langToggle: { value: false, button: {} }, setDeviceName: () => Promise.resolve(), ...version, @@ -75,7 +73,6 @@ export const WithOneExchange = tests.createExample(TestedComponent, { deviceName: "this-is-the-device-name", advanceToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} }, - injectTalerToggle: { value: false, button: {} }, langToggle: { value: false, button: {} }, setDeviceName: () => Promise.resolve(), knownExchanges: [ @@ -100,7 +97,6 @@ export const WithExchangeInDifferentState = tests.createExample( deviceName: "this-is-the-device-name", advanceToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} }, - injectTalerToggle: { value: false, button: {} }, langToggle: { value: false, button: {} }, setDeviceName: () => Promise.resolve(), knownExchanges: [ diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index b27413a96..e25629148 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -80,14 +80,6 @@ export function SettingsPage(): VNode { }), }, }} - injectTalerToggle={{ - value: settings.injectTalerSupport, - button: { - onClick: safely("update support injection", async () => { - updateSettings("injectTalerSupport", !settings.injectTalerSupport); - }), - }, - }} advanceToggle={{ value: settings.advanceMode, button: { @@ -117,7 +109,6 @@ export interface ViewProps { deviceName: string; setDeviceName: (s: string) => Promise<void>; autoOpenToggle: ToggleHandler; - injectTalerToggle: ToggleHandler; advanceToggle: ToggleHandler; langToggle: ToggleHandler; knownExchanges: Array<ExchangeListItem>; @@ -131,7 +122,6 @@ export interface ViewProps { export function SettingsView({ knownExchanges, autoOpenToggle, - injectTalerToggle, advanceToggle, langToggle, coreVersion, @@ -276,19 +266,6 @@ export function SettingsView({ <i18n.Translate>Navigator</i18n.Translate> </SubTitle> <Checkbox - label={i18n.str`Inject Taler support in all pages`} - name="inject" - description={ - <i18n.Translate> - Disabling this option will make some web application not able to - trigger the wallet when clicking links but you will be able to - open the wallet using the keyboard shortcut - </i18n.Translate> - } - enabled={injectTalerToggle.value!} - onToggle={injectTalerToggle.button.onClick!} - /> - <Checkbox label={i18n.str`Automatically open wallet`} name="autoOpen" description={ diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 8fb8211ae..d989c9662 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -83,14 +83,6 @@ export interface BackgroundOperations { }; response: void; }; - containsHeaderListener: { - request: void; - response: ExtendedPermissionsResponse; - }; - toggleHeaderListener: { - request: boolean; - response: ExtendedPermissionsResponse; - }; } export interface BackgroundApiClient { @@ -194,7 +186,7 @@ function onUpdateNotification( return; }; const onNewMessage = (message: MessageFromBackend): void => { - const shouldNotify = messageTypes.includes(message.type); + const shouldNotify = message.type === "wallet" && messageTypes.includes(message.notification.type); if (shouldNotify) { doCallback(); } diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 1ecd66f05..95d31c519 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -122,18 +122,18 @@ async function sum(ns: Array<number>): Promise<number> { } const extensionHandlers: ExtensionHandlerType = { - isInjectionEnabled, isAutoOpenEnabled, + isDomainTrusted, }; -async function isInjectionEnabled(): Promise<boolean> { +async function isAutoOpenEnabled(): Promise<boolean> { const settings = await platform.getSettingsFromStorage(); - return settings.injectTalerSupport === true; + return settings.autoOpen === true; } -async function isAutoOpenEnabled(): Promise<boolean> { +async function isDomainTrusted(): Promise<boolean> { const settings = await platform.getSettingsFromStorage(); - return settings.autoOpen === true; + return settings.injectTalerSupport === true; } const backendHandlers: BackendHandlerType = { @@ -142,14 +142,8 @@ const backendHandlers: BackendHandlerType = { resetDb, runGarbageCollector, setLoggingLevel, - containsHeaderListener, - toggleHeaderListener, }; -async function containsHeaderListener(): Promise<ExtendedPermissionsResponse> { - const result = platform.containsTalerHeaderListener(); - return { newValue: result }; -} async function setLoggingLevel({ tag, @@ -309,8 +303,10 @@ async function reinitWallet(): Promise<void> { return; } wallet.addNotificationListener((message) => { - logger.info("wallet -> ui", message); - platform.sendMessageToAllChannels(message); + platform.sendMessageToAllChannels({ + type: "wallet", + notification: message + }); }); platform.keepAlive(() => { @@ -360,65 +356,6 @@ export async function wxMain(): Promise<void> { console.error(e); } - // platform.registerDeclarativeRedirect(); - // if (false) { - /** - * this is not working reliable on chrome, just - * intercepts queries after the user clicks the popups - * which doesn't make sense, keeping it to make more tests - */ - - logger.trace("check taler header listener"); - const enabled = platform.containsTalerHeaderListener() - if (!enabled) { - logger.info("header listener on") - const perm = await platform.getPermissionsApi().containsHostPermissions() - if (perm) { - logger.info("header listener allowed") - try { - platform.registerTalerHeaderListener(); - } catch (e) { - logger.error("could not register header listener", e); - } - } else { - logger.info("header listener requested") - await platform.getPermissionsApi().requestHostPermissions() - } - } - - // On platforms that support it, also listen to external - // modification of permissions. - platform.getPermissionsApi().addPermissionsListener((perm, lastError) => { - logger.info(`permission added: ${perm}`,) - if (lastError) { - logger.error( - `there was a problem trying to get permission ${perm}`, - lastError, - ); - return; - } - platform.registerTalerHeaderListener(); - }); - - // } } -async function toggleHeaderListener( - newVal: boolean, -): Promise<ExtendedPermissionsResponse> { - logger.trace("new extended permissions value", newVal); - if (newVal) { - try { - platform.registerTalerHeaderListener(); - return { newValue: true }; - } catch (e) { - logger.error("FAIL to toggle", e) - } - return { newValue: false } - } - - const rem = await platform.getPermissionsApi().removeHostPermissions(); - logger.trace("permissions removed:", rem); - return { newValue: false }; -} |