gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 02/04: taler wallet interaction support, first versi


From: gnunet
Subject: [taler-wallet-core] 02/04: taler wallet interaction support, first version
Date: Thu, 13 Apr 2023 17:58:04 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit ebd004195673c58718c7c9d8b8270df28b35b539
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Apr 13 12:19:00 2023 -0300

    taler wallet interaction support, first version
---
 .../build-fast-with-linaria.mjs                    |   2 +
 .../taler-wallet-webextension/manifest-v2.json     |   5 +
 .../taler-wallet-webextension/manifest-v3.json     |   8 +-
 .../src/taler-wallet-interaction-loader.ts         | 135 +++++++++++++++
 .../src/taler-wallet-interaction-support.ts        | 192 +++++++++++++++++++++
 5 files changed, 341 insertions(+), 1 deletion(-)

diff --git a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs 
b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
index 1232eac98..44e502b6a 100755
--- a/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
+++ b/packages/taler-wallet-webextension/build-fast-with-linaria.mjs
@@ -60,6 +60,8 @@ const entryPoints = [
   'src/background.ts',
   'src/stories.tsx',
   'src/background.dev.ts',
+  'src/taler-wallet-interaction-loader.ts',
+  'src/taler-wallet-interaction-support.ts',
   'src/browserWorkerEntry.ts'
 ]
 
diff --git a/packages/taler-wallet-webextension/manifest-v2.json 
b/packages/taler-wallet-webextension/manifest-v2.json
index 6adadad98..a5b77168c 100644
--- a/packages/taler-wallet-webextension/manifest-v2.json
+++ b/packages/taler-wallet-webextension/manifest-v2.json
@@ -26,6 +26,11 @@
     "https://*/*";,
     "webRequest"
   ],
+  "content_scripts": [{
+    "id": "taler-wallet-interaction-support",
+    "matches": ["file://*/*", "http://*/*";, "https://*/*";],
+    "js": ["dist/taler-wallet-interaction-loader.js"]
+  }],
   "protocol_handlers": [
     {
       "protocol": "ext+taler",
diff --git a/packages/taler-wallet-webextension/manifest-v3.json 
b/packages/taler-wallet-webextension/manifest-v3.json
index 4e18125b3..68b3e23ee 100644
--- a/packages/taler-wallet-webextension/manifest-v3.json
+++ b/packages/taler-wallet-webextension/manifest-v3.json
@@ -29,10 +29,16 @@
   "optional_permissions": [
     "webRequest"
   ],
+  "content_scripts": [{
+    "id": "taler-wallet-interaction",
+    "matches": ["file://*/*", "http://*/*";, "https://*/*";],
+    "js": ["dist/taler-wallet-interaction-loader.js"]
+  }],
   "web_accessible_resources": [
     {
       "resources": [
-        "static/wallet.html"
+        "static/wallet.html",
+        "dist/taler-wallet-interaction-support.js"
       ],
       "matches": [
         "https://*/*";,
diff --git 
a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts 
b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts
new file mode 100644
index 000000000..838b47397
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts
@@ -0,0 +1,135 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * 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 <script /> 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();
diff --git 
a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts 
b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts
new file mode 100644
index 000000000..a0ddc40f1
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts
@@ -0,0 +1,192 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * WARNING
+ *
+ * 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 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>;
+  api: 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}`;
+  }
+
+  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 : "taler-wallet";
+    const page = convertURIToWebExtensionPath(hrefAttr.value);
+    if (!page) {
+      logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`);
+      return;
+    }
+    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);
+          }
+        });
+      }
+    });
+  }
+
+  /**
+   * Check of every anchor and observes for new one.
+   * Register the anchor handler when found
+   */
+  function registerProtocolHandler() {
+    const observer = new MutationObserver(checkForNewAnchors);
+    observer.observe(document.body, {
+      childList: true,
+      subtree: true,
+      attributes: false,
+    });
+
+    overrideAllAnchor(document.body);
+  }
+
+  return {
+    convertURIToWebExtensionPath,
+    anchorOnClick,
+    registerProtocolHandler,
+  };
+}
+
+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";
+  if (debugEnabled) {
+    logger.debug = logger.info;
+  }
+
+  const info: Info = Object.freeze({
+    extensionId,
+    protocol,
+    hostname,
+  });
+  const taler: TalerSupport = {
+    info,
+    api: buildApi(info),
+  };
+
+  //@ts-ignore
+  window.taler = taler;
+
+  //default behavior: register on install
+  taler.api.registerProtocolHandler();
+}
+
+// utils functions
+function validateTalerUri(uri: string): boolean {
+  return (
+    !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://";))
+  );
+}
+
+start();

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]