/*
This file is part of GNU Taler
(C) 2022 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
*/
/**
* This will modify all the pages that the user load when navigating with Web Extension enabled
*
* Can't do useful integration since it run in ISOLATED (or equivalent) mode.
*
* If taler support is expected, it will inject a script which will complete the integration.
*/
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#content_script_environment
// ISOLATED mode in chromium browsers
// https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#world
// X-Ray vision in Firefox
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#xray_vision_in_firefox
// *** IMPORTANT ***
// Content script lifecycle during navigation
// In Firefox: Content scripts remain injected in a web page after the user has navigated away,
// however, window object properties are destroyed.
// In Chrome: Content scripts are destroyed when the user navigates away from a web page.
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]",
);
// safe check, if one of this is true then taler handler is not useful
// or not expected
const shouldNotInject =
!documentDocTypeIsHTML ||
!suffixIsNotXMLorPDF ||
// !pageAcceptsTalerSupport || FIXME: removing this before release for testing
!rootElementIsHTML;
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),
};
function start() {
if (shouldNotInject) {
return;
}
const debugEnabled =
pageAcceptsTalerSupport?.getAttribute("debug") === "true";
if (debugEnabled) {
logger.debug = logger.info;
}
createBridgeWithExtension();
logger.debug("bridged created");
injectTalerSupportScript(debugEnabled);
logger.debug("done");
}
/**
* Create a element that load the support in the page context.
* The interaction support script will create the API to send message
* that will be received by this loader and be redirected to the extension
* using the bridge.
*/
function injectTalerSupportScript(debugEnabled: boolean) {
const container = document.head || document.documentElement;
const scriptTag = document.createElement("script");
scriptTag.setAttribute("async", "false");
const url = new URL(
chrome.runtime.getURL("/dist/taler-wallet-interaction-support.js"),
);
url.searchParams.set("id", chrome.runtime.id);
if (debugEnabled) {
url.searchParams.set("debug", "true");
}
scriptTag.src = url.href;
try {
container.insertBefore(scriptTag, container.children[0]);
} catch (e) {
logger.info("inserting link handler failed!");
logger.error(e);
}
}
/**
* Create a bridge connection between the page and the extension.
*
* Useful for API calls and replies. Not yet supported.
*/
function createBridgeWithExtension() {
const port = chrome.runtime.connect();
window.addEventListener(
"message",
(event) => {
logger.debug("message received", event);
if (event.source !== window) {
return;
}
if (event.origin !== window.origin) {
return;
}
if (event.data.type && event.data.type === "FROM_PAGE") {
logger.debug("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
},
false,
);
}
start();