diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts')
-rw-r--r-- | packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts | 314 |
1 files changed, 161 insertions, 153 deletions
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 b70ca2899..8b15380f9 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts @@ -20,173 +20,181 @@ * This script will be loaded and run in every page while the * user us navigating. It must be short, simple and safe. */ +(() => { + const logger = { + debug: (...msg: any[]) => { }, + info: (...msg: any[]) => + console.log(`${new Date().toISOString()} TALER`, ...msg), + error: (...msg: any[]) => + console.error(`${new Date().toISOString()} TALER`, ...msg), + }; -const logger = { - debug: (...msg: any[]) => { }, - info: (...msg: any[]) => - console.log(`${new Date().toISOString()} TALER`, ...msg), - error: (...msg: any[]) => - console.error(`${new Date().toISOString()} TALER`, ...msg), -}; - -const documentDocTypeIsHTML = - window.document.doctype && window.document.doctype.name === "html"; -const suffixIsNotXMLorPDF = - !window.location.pathname.endsWith(".xml") && - !window.location.pathname.endsWith(".pdf"); -const rootElementIsHTML = - document.documentElement.nodeName && - document.documentElement.nodeName.toLowerCase() === "html"; -const pageAcceptsTalerSupport = document.head.querySelector( - "meta[name=taler-support]", -); - -// this is also checked by the loader -// but a double check will prevent running and breaking user navigation -// if loaded from other location -const shouldNotRun = - !documentDocTypeIsHTML || - !suffixIsNotXMLorPDF || - // !pageAcceptsTalerSupport || FIXME: removing this before release for testing - !rootElementIsHTML; - -interface Info { - extensionId: string; - protocol: string; - hostname: string; -} -interface API { - convertURIToWebExtensionPath: (uri: string) => string | undefined; - anchorOnClick: (ev: MouseEvent) => void; - registerProtocolHandler: () => void; -} -interface TalerSupport { - info: Readonly<Info>; - __internal: API; -} - -function buildApi(config: Readonly<Info>): API { - /** - * Takes an anchor href that starts with taler:// and - * returns the path to the web-extension page - */ - function convertURIToWebExtensionPath(uri: string): string | undefined { - if (!validateTalerUri(uri)) { - logger.error(`taler:// URI is invalid: ${uri}`); - return undefined; - } - const host = `${config.protocol}//${config.hostname}`; - const path = `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`; - return `${host}/${path}`; + const documentDocTypeIsHTML = + window.document.doctype && window.document.doctype.name === "html"; + const suffixIsNotXMLorPDF = + !window.location.pathname.endsWith(".xml") && + !window.location.pathname.endsWith(".pdf"); + const rootElementIsHTML = + document.documentElement.nodeName && + document.documentElement.nodeName.toLowerCase() === "html"; + const pageAcceptsTalerSupport = document.head.querySelector( + "meta[name=taler-support]", + ); + + // this is also checked by the loader + // but a double check will prevent running and breaking user navigation + // if loaded from other location + const shouldNotRun = + !documentDocTypeIsHTML || + !suffixIsNotXMLorPDF || + !pageAcceptsTalerSupport || + !rootElementIsHTML; + + interface Info { + extensionId: string; + protocol: string; + hostname: string; + } + interface API { + convertURIToWebExtensionPath: (uri: string) => string | undefined; + anchorOnClick: (ev: MouseEvent) => void; + registerProtocolHandler: () => void; + } + interface TalerSupport { + info: Readonly<Info>; + __internal: API; } - function anchorOnClick(ev: MouseEvent) { - if (!(ev.currentTarget instanceof Element)) { - logger.debug(`onclick: registered in a link that is not an HTML element`); - return; + function buildApi(config: Readonly<Info>): API { + /** + * Takes an anchor href that starts with taler:// and + * returns the path to the web-extension page + */ + function convertURIToWebExtensionPath(uri: string): string | undefined { + if (!validateTalerUri(uri)) { + logger.error(`taler:// URI is invalid: ${uri}`); + return undefined; + } + const host = `${config.protocol}//${config.hostname}`; + const path = `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`; + return `${host}/${path}`; } - const hrefAttr = ev.currentTarget.attributes.getNamedItem("href"); - if (!hrefAttr) { - logger.debug(`onclick: link didn't have href with taler:// uri`); - return; + + function anchorOnClick(ev: MouseEvent) { + if (!(ev.currentTarget instanceof Element)) { + logger.debug(`onclick: registered in a link that is not an HTML element`); + return; + } + const hrefAttr = ev.currentTarget.attributes.getNamedItem("href"); + if (!hrefAttr) { + logger.debug(`onclick: link didn't have href with taler:// uri`); + return; + } + const targetAttr = ev.currentTarget.attributes.getNamedItem("target"); + const windowTarget = + targetAttr && targetAttr.value ? targetAttr.value : "_self"; + const page = convertURIToWebExtensionPath(hrefAttr.value); + if (!page) { + logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`); + return; + } + // we can use window.open, but maybe some browser will block it? + window.open(page, windowTarget); + ev.preventDefault(); + ev.stopPropagation(); + ev.stopImmediatePropagation(); + return false; } - const targetAttr = ev.currentTarget.attributes.getNamedItem("target"); - const windowTarget = - targetAttr && targetAttr.value ? targetAttr.value : "_self"; - const page = convertURIToWebExtensionPath(hrefAttr.value); - if (!page) { - logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`); - return; + + function overrideAllAnchor(root: HTMLElement) { + const allAnchors = root.querySelectorAll("a[href^=taler]"); + logger.debug(`registering taler protocol in ${allAnchors.length} links`); + allAnchors.forEach((link) => { + if (link instanceof HTMLElement) { + link.addEventListener("click", anchorOnClick); + } + }); } - // we can use window.open, but maybe some browser will block it? - window.open(page, windowTarget); - ev.preventDefault(); - ev.stopPropagation(); - ev.stopImmediatePropagation(); - return false; - } - function overrideAllAnchor(root: HTMLElement) { - const allAnchors = root.querySelectorAll("a[href^=taler]"); - logger.debug(`registering taler protocol in ${allAnchors.length} links`); - allAnchors.forEach((link) => { - if (link instanceof HTMLElement) { - link.addEventListener("click", anchorOnClick); - } - }); - } + function checkForNewAnchors( + mutations: MutationRecord[], + observer: MutationObserver, + ) { + mutations.forEach((mut) => { + if (mut.type === "childList") { + mut.addedNodes.forEach((added) => { + if (added instanceof HTMLElement) { + logger.debug(`new element`, added); + overrideAllAnchor(added); + } + }); + } + }); + } - function checkForNewAnchors( - mutations: MutationRecord[], - observer: MutationObserver, - ) { - mutations.forEach((mut) => { - if (mut.type === "childList") { - mut.addedNodes.forEach((added) => { - if (added instanceof HTMLElement) { - logger.debug(`new element`, added); - overrideAllAnchor(added); - } - }); - } - }); + /** + * Check of every anchor and observes for new one. + * Register the anchor handler when found + */ + function registerProtocolHandler() { + if (document.body) overrideAllAnchor(document.body) + new MutationObserver(checkForNewAnchors).observe(document, { + childList: true, + subtree: true, + attributes: false, + }); + } + + return { + convertURIToWebExtensionPath, + anchorOnClick, + registerProtocolHandler, + }; } - /** - * Check of every anchor and observes for new one. - * Register the anchor handler when found - */ - function registerProtocolHandler() { - overrideAllAnchor(document.body) - new MutationObserver(checkForNewAnchors).observe(document, { - childList: true, - subtree: true, - attributes: false, + function start() { + if (shouldNotRun) return; + 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"; + const apiEnabled = searchParams.get("api") === "true"; + const hijackEnabled = searchParams.get("hijack") === "true"; + + const info: Info = Object.freeze({ + extensionId, + protocol, + hostname, }); - } - return { - convertURIToWebExtensionPath, - anchorOnClick, - registerProtocolHandler, - }; -} - -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; - } + if (debugEnabled) { + logger.debug = logger.info; + } - const info: Info = Object.freeze({ - extensionId, - protocol, - hostname, - }); - const taler: TalerSupport = { - info, - __internal: buildApi(info), - }; + const taler: TalerSupport = { + info, + __internal: buildApi(info), + }; + + if (apiEnabled) { + //@ts-ignore + window.taler = taler; + } - //@ts-ignore - window.taler = taler; + if (hijackEnabled) { + taler.__internal.registerProtocolHandler(); + } + } - //default behavior: register on install - taler.__internal.registerProtocolHandler(); -} + // utils functions + function validateTalerUri(uri: string): boolean { + return ( + !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://")) + ); + } -// utils functions -function validateTalerUri(uri: string): boolean { - return ( - !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://")) - ); -} + start(); +})() -start(); |