summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src')
-rw-r--r--packages/taler-wallet-webextension/src/platform/api.ts35
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts300
-rw-r--r--packages/taler-wallet-webextension/src/platform/dev.ts7
-rw-r--r--packages/taler-wallet-webextension/src/platform/firefox.ts11
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts121
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts26
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx205
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.tsx23
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts10
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts81
11 files changed, 264 insertions, 559 deletions
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 };
-}