diff options
author | Sebastian <sebasjm@gmail.com> | 2023-10-30 18:42:39 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-10-30 18:44:41 -0300 |
commit | f1967ab0baf825cdfa767d36bb7cce78521e4e4b (patch) | |
tree | 43650091b8542dfa750a69524efa3e5ff16b7201 /packages/taler-wallet-webextension | |
parent | fb4c172bb430936268bcebe34142d4206a0d422e (diff) |
testing auto open feature
Diffstat (limited to 'packages/taler-wallet-webextension')
11 files changed, 448 insertions, 375 deletions
diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json index bdee05539..ae1be5181 100644 --- a/packages/taler-wallet-webextension/manifest-v3.json +++ b/packages/taler-wallet-webextension/manifest-v3.json @@ -15,7 +15,6 @@ "permissions": [ "unlimitedStorage", "storage", - "webRequest", "activeTab", "scripting", "declarativeContent", @@ -28,11 +27,19 @@ } } }, - "content_scripts": [{ - "id": "taler-wallet-interaction", - "matches": ["file://*/*", "http://*/*", "https://*/*"], - "js": ["dist/taler-wallet-interaction-loader.js"] - }], + "content_scripts": [ + { + "id": "taler-wallet-interaction", + "matches": [ + "http://*/*", + "https://*/*" + ], + "js": [ + "dist/taler-wallet-interaction-loader.js" + ], + "run_at": "document_start" + } + ], "web_accessible_resources": [ { "resources": [ @@ -44,8 +51,7 @@ ], "matches": [ "https://*/*", - "http://*/*", - "file://*/*" + "http://*/*" ] } ], diff --git a/packages/taler-wallet-webextension/src/hooks/useSettings.ts b/packages/taler-wallet-webextension/src/hooks/useSettings.ts index 563f3628a..dd3822c1a 100644 --- a/packages/taler-wallet-webextension/src/hooks/useSettings.ts +++ b/packages/taler-wallet-webextension/src/hooks/useSettings.ts @@ -35,7 +35,7 @@ export const codecForSettings = (): Codec<Settings> => buildCodecForObject<Settings>() .property("walletAllowHttp", codecForBoolean()) .property("injectTalerSupport", codecForBoolean()) - .property("autoOpenByHeader", codecForBoolean()) + .property("autoOpen", codecForBoolean()) .property("advanceMode", codecForBoolean()) .property("backup", codecForBoolean()) .property("langSelector", codecForBoolean()) diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index 820711ea9..76add93d1 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -46,9 +46,9 @@ export interface Permissions { * Compatibility API that works on multiple browsers. */ export interface CrossBrowserPermissionsApi { - containsHostPermissions(): Promise<boolean>; - requestHostPermissions(): Promise<boolean>; - removeHostPermissions(): Promise<boolean>; + // containsHostPermissions(): Promise<boolean>; + // requestHostPermissions(): Promise<boolean>; + // removeHostPermissions(): Promise<boolean>; containsClipboardPermissions(): Promise<boolean>; requestClipboardPermissions(): Promise<boolean>; @@ -100,7 +100,7 @@ type WebexWalletConfig = { export interface Settings extends WebexWalletConfig { injectTalerSupport: boolean; - autoOpenByHeader: boolean; + autoOpen: boolean; advanceMode: boolean; backup: boolean; langSelector: boolean; @@ -111,7 +111,7 @@ export interface Settings extends WebexWalletConfig { export const defaultSettings: Settings = { injectTalerSupport: true, - autoOpenByHeader: true, + autoOpen: true, advanceMode: false, backup: false, langSelector: false, @@ -207,14 +207,6 @@ export interface BackgroundPlatformAPI { ) => Promise<MessageResponse>, ): void; - /** - * Backend API - */ - registerTalerHeaderListener( - // onHeader: (tabId: number, url: string) => void, - ): 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 fa9ad0522..4fb4bddfd 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -59,8 +59,6 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { useServiceWorkerAsBackgroundProcess, keepAlive, listenNetworkConnectionState, - registerTalerHeaderListener, - containsTalerHeaderListener, }; export default api; @@ -151,9 +149,6 @@ function addPermissionsListener( function getPermissionsApi(): CrossBrowserPermissionsApi { return { addPermissionsListener, - containsHostPermissions, - requestHostPermissions, - removeHostPermissions, requestClipboardPermissions, removeClipboardPermissions, containsClipboardPermissions, @@ -395,10 +390,17 @@ function registerReloadOnNewVersion(): void { }); } -function redirectTabToWalletPage(tabId: number, page: string): void { +async function redirectCurrentTabToWalletPage(page: string): Promise<void> { + let queryOptions = { active: true, lastFocusedWindow: true }; + let [tab] = await chrome.tabs.query(queryOptions); + + return redirectTabToWalletPage(tab.id!, page); +} + +async function redirectTabToWalletPage(tabId: number, page: string): Promise<void> { const url = chrome.runtime.getURL(`/static/wallet.html#${page}`); logger.trace("redirecting tabId: ", tabId, " to: ", url); - chrome.tabs.update(tabId, { url }); + await chrome.tabs.update(tabId, { url }); } interface WalletVersion { @@ -709,218 +711,247 @@ 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, -): void { - if (chrome.runtime.lastError) { - logger.error(JSON.stringify(chrome.runtime.lastError)); - return; - } - console.log("HEADER", details.statusCode, details.url, details.responseHeaders) - 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); - if (values.length > 0) { - logger.info( - `Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`, - ); - redirectTabToWalletPage(details.tabId, values[0]); - } - } - return; -} -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; - } - return redirectTabToWalletPage( - tabId, - `/taler-uri/${encodeURIComponent(talerUri)}`, - ); -} - -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); - } -} - -function registerTalerHeaderListener( - // callback: (tabId: number, url: string) => void, -): void { - logger.info("setting up header listener"); - - const prevHeaderListener = currentHeaderListener; - const prevTabListener = currentTabListener; - - getPermissionsApi() - .containsHostPermissions() - .then((result) => { - //if there is a handler already, remove it - if ( - prevHeaderListener && - chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener) - ) { - 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); - } - - //if the result was positive, add the headerListener - if (result) { - console.log("headers on, disabled:", chrome?.webRequest?.onHeadersReceived === undefined) - if (chrome?.webRequest) { - chrome.webRequest.onHeadersReceived.addListener(headerListener, - { urls: ["<all_urls>"] }, - ["responseHeaders", "extraHeaders"] - ); - 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; - } - } else { - console.log("headers off") - } - - //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://*/*"], -}; +// 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 { +// if (chrome.runtime.lastError) { +// logger.error(JSON.stringify(chrome.runtime.lastError)); +// return; +// } +// console.log("HEADER", JSON.stringify(details, undefined, 2)) +// 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); +// if (values.length > 0) { +// logger.info( +// `Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`, +// ); +// const redirectUrl = redirectTabToWalletPage(details.tabId, values[0]); +// return { redirectUrl } +// } +// } +// 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)}`, +// ); +// } + +// 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); +// } +// } -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 +/** + * 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; + +// getPermissionsApi() +// .containsHostPermissions() +// .then((result) => { +// //if there is a handler already, remove it +// if ( +// prevHeaderListener && +// chrome?.webRequest?.onHeadersReceived?.hasListener(prevHeaderListener) +// ) { +// 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); +// } + +// //if the result was positive, add the headerListener +// if (result) { +// console.log("headers on, disabled:", chrome?.webRequest?.onHeadersReceived === undefined) +// if (chrome?.webRequest) { +// chrome.webRequest.onHeadersReceived.addListener(headerListener, +// { urls: ["<all_urls>"] }, +// ["responseHeaders", "extraHeaders"] +// ); +// // 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; +// } +// } else { +// console.log("headers off") +// } + +// //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 218422ded..02d11566a 100644 --- a/packages/taler-wallet-webextension/src/platform/dev.ts +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -44,8 +44,10 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { removeClipboardPermissions: async () => false, requestClipboardPermissions: async () => false, }), - registerTalerHeaderListener: () => false, - containsTalerHeaderListener: () => false, + + // registerDeclarativeRedirect: () => false, + // registerTalerHeaderListener: () => false, + // containsTalerHeaderListener: () => false, getWalletWebExVersion: () => ({ version: "none", }), @@ -88,7 +90,6 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { redirectTabToWalletPage: (tabId: number, page: string) => { alert("redirectTabToWalletPage not implemented yet"); }, - registerAllIncomingConnections: () => undefined, registerOnInstalled: () => Promise.resolve(), registerReloadOnNewVersion: () => undefined, diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts b/packages/taler-wallet-webextension/src/platform/firefox.ts index cc734ebf7..cca2833ad 100644 --- a/packages/taler-wallet-webextension/src/platform/firefox.ts +++ b/packages/taler-wallet-webextension/src/platform/firefox.ts @@ -26,9 +26,9 @@ import chromePlatform, { containsClipboardPermissions as chromeClipContains, removeClipboardPermissions as chromeClipRemove, requestClipboardPermissions as chromeClipRequest, - containsHostPermissions as chromeHostContains, - requestHostPermissions as chromeHostRequest, - removeHostPermissions as chromeHostRemove, + // containsHostPermissions as chromeHostContains, + // requestHostPermissions as chromeHostRequest, + // removeHostPermissions as chromeHostRemove, } from "./chrome.js"; const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { @@ -54,9 +54,9 @@ function addPermissionsListener(callback: (p: Permissions) => void): void { function getPermissionsApi(): CrossBrowserPermissionsApi { return { addPermissionsListener, - containsHostPermissions: chromeHostContains, - requestHostPermissions: chromeHostRequest, - removeHostPermissions: chromeHostRemove, + // 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 8ea071fb6..10b1f521b 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts @@ -46,54 +46,86 @@ const suffixIsNotXMLorPDF = const rootElementIsHTML = document.documentElement.nodeName && document.documentElement.nodeName.toLowerCase() === "html"; -const pageAcceptsTalerSupport = document.head.querySelector( - "meta[name=taler-support]", -); + +/** + * Listen to any HTML Element and react to it. + * - <meta name="taler-suport" /> will inject taler-support-lib + * - <meta name="taler-uri" /> will redirect to call to action + */ +function listenToHeaderMutation() { + new MutationObserver(async function (mutations) { + const autoOpen = await callBackground("isAutoOpenEnabled", undefined) + mutations.forEach((mut) => { + if (mut.type === "childList") { + mut.addedNodes.forEach((added) => { + if (added instanceof HTMLHeadElement) { + injectTalerSupportScript(added) + } else if (added instanceof HTMLMetaElement) { + const name = added.getAttribute("name") + if (!name) return; + if (autoOpen && name === "taler-uri") { + redirectToTalerActionHandler(added) + } + } + }); + } + }); + }).observe(document, { + childList: true, + subtree: true, + attributes: false, + }) +} + +function validateTalerUri(uri: string): boolean { + return ( + !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://")) + ); +} + +function convertURIToWebExtensionPath(uri: string) { + const url = new URL( + chrome.runtime.getURL(`static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`), + ); + return url.href; +} + // 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[]) => {}, + debug: (...msg: any[]) => { }, info: (...msg: any[]) => console.log(`${new Date().toISOString()} TALER`, ...msg), error: (...msg: any[]) => console.error(`${new Date().toISOString()} TALER`, ...msg), }; -async function start() { - if (shouldNotInject) { - return; - } - const debugEnabled = - pageAcceptsTalerSupport?.getAttribute("debug") === "true"; - if (debugEnabled) { - logger.debug = logger.info; - } - createBridgeWithExtension(); - logger.debug("bridged created"); +// logger.debug = logger.info - const shouldInject = await callBackground("isInjectionEnabled", undefined); +/** + */ +function redirectToTalerActionHandler(element: HTMLMetaElement) { + const uri = element.getAttribute("content"); + if (!uri) return; - if (shouldInject) { - injectTalerSupportScript(debugEnabled); - logger.debug("injection completed"); - } else { - logger.debug("injection is not enabled"); + if (!validateTalerUri(uri)) { + logger.error(`taler:// URI is invalid: ${uri}`); + return; } + + location.href = convertURIToWebExtensionPath(uri) } -/** - * 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; +function injectTalerSupportScript(head: HTMLHeadElement) { + const meta = head.querySelector("meta[name=taler-support]") + + const debugEnabled = meta?.getAttribute("debug") === "true"; + const scriptTag = document.createElement("script"); scriptTag.setAttribute("async", "false"); @@ -105,12 +137,17 @@ function injectTalerSupportScript(debugEnabled: boolean) { 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); - } + + callBackground("isInjectionEnabled", undefined).then(shouldInject => { + if (!shouldInject) return; + + try { + head.insertBefore(scriptTag, head.children.length ? head.children[0] : null); + } catch (e) { + logger.info("inserting link handler failed!"); + logger.error(e); + } + }); } /** @@ -146,6 +183,10 @@ export interface ExtensionOperations { request: void; response: boolean; }; + isAutoOpenEnabled: { + request: void; + response: boolean; + }; } export type MessageFromExtension<Op extends keyof ExtensionOperations> = { @@ -201,4 +242,11 @@ async function sendMessageToBackground<Op extends keyof ExtensionOperations>( }); } +function start() { + if (shouldNotInject) return; + listenToHeaderMutation(); + createBridgeWithExtension(); + logger.debug("bridged created"); +} + 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 index 34687306b..33c2bc249 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts @@ -22,7 +22,7 @@ */ const logger = { - debug: (...msg: any[]) => {}, + debug: (...msg: any[]) => { }, info: (...msg: any[]) => console.log(`${new Date().toISOString()} TALER`, ...msg), error: (...msg: any[]) => @@ -103,11 +103,6 @@ function buildApi(config: Readonly<Info>): API { ev.preventDefault(); ev.stopPropagation(); ev.stopImmediatePropagation(); - // another possibility is to change the location when the click is made - // or when the anchor is found - // hrefAttr.value = page - // TODO: explore different possibilities and maybe allow the configuration - // using the meta-tag return false; } diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index 2319fe30a..b27413a96 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -73,10 +73,10 @@ export function SettingsPage(): VNode { deviceName={name} setDeviceName={update} autoOpenToggle={{ - value: settings.autoOpenByHeader, + value: settings.autoOpen, button: { onClick: safely("update support injection", async () => { - updateSettings("autoOpenByHeader", !settings.autoOpenByHeader); + updateSettings("autoOpen", !settings.autoOpen); }), }, }} @@ -217,23 +217,6 @@ export function SettingsView({ <i18n.Translate>Add an exchange</i18n.Translate> </LinkPrimary> </div> - <EnabledBySettings name="advanceMode"> - <SubTitle> - <i18n.Translate>Navigator</i18n.Translate> - </SubTitle> - <Checkbox - label={i18n.str`Automatically open wallet based on page content`} - name="autoOpen" - description={ - <i18n.Translate> - Enabling this option below will make using the wallet faster, - but requires more permissions from your browser. - </i18n.Translate> - } - enabled={autoOpenToggle.value!} - onToggle={autoOpenToggle.button.onClick!} - /> - </EnabledBySettings> {coreVersion && (<Fragment> {LibtoolVersion.compare(coreVersion.version, WALLET_CORE_SUPPORTED_VERSION)?.compatible ? undefined : @@ -305,6 +288,17 @@ export function SettingsView({ enabled={injectTalerToggle.value!} onToggle={injectTalerToggle.button.onClick!} /> + <Checkbox + label={i18n.str`Automatically open wallet`} + name="autoOpen" + description={ + <i18n.Translate> + Open the wallet when a payment action is found. + </i18n.Translate> + } + enabled={autoOpenToggle.value!} + onToggle={autoOpenToggle.button.onClick!} + /> <SubTitle> <i18n.Translate>Version</i18n.Translate> </SubTitle> diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 8fb8211ae..9683bf34b 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -83,14 +83,14 @@ export interface BackgroundOperations { }; response: void; }; - containsHeaderListener: { - request: void; - response: ExtendedPermissionsResponse; - }; - toggleHeaderListener: { - request: boolean; - response: ExtendedPermissionsResponse; - }; + // containsHeaderListener: { + // request: void; + // response: ExtendedPermissionsResponse; + // }; + // toggleHeaderListener: { + // request: boolean; + // response: ExtendedPermissionsResponse; + // }; } export interface BackgroundApiClient { diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 60b071716..f3d2375c5 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -27,18 +27,12 @@ import { LogLevel, Logger, TalerErrorCode, - WalletDiagnostics, - WalletNotification, getErrorDetailFromException, makeErrorDetail, - parseTalerUri, setGlobalLogLevelFromString, - setLogLevelFromString, + setLogLevelFromString } from "@gnu-taler/taler-util"; -import { - ServiceWorkerHttpLib, - BrowserHttpLib, -} from "@gnu-taler/web-util/browser"; +import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import { DbAccess, OpenedPromise, @@ -52,11 +46,14 @@ import { importDb, openPromise, } from "@gnu-taler/taler-wallet-core"; +import { + BrowserHttpLib, + ServiceWorkerHttpLib, +} from "@gnu-taler/web-util/browser"; import { MessageFromFrontend, MessageResponse } from "./platform/api.js"; import { platform } from "./platform/background.js"; import { ExtensionOperations } from "./taler-wallet-interaction-loader.js"; -import { BackgroundOperations, ExtendedPermissionsResponse } from "./wxApi.js"; -import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; +import { BackgroundOperations } from "./wxApi.js"; /** * Currently active wallet instance. Might be unloaded and @@ -126,6 +123,7 @@ async function sum(ns: Array<number>): Promise<number> { const extensionHandlers: ExtensionHandlerType = { isInjectionEnabled, + isAutoOpenEnabled, }; async function isInjectionEnabled(): Promise<boolean> { @@ -133,9 +131,9 @@ async function isInjectionEnabled(): Promise<boolean> { return settings.injectTalerSupport === true; } -async function isHeaderListenerEnabled(): Promise<boolean> { +async function isAutoOpenEnabled(): Promise<boolean> { const settings = await platform.getSettingsFromStorage(); - return settings.autoOpenByHeader === true; + return settings.autoOpen === true; } const backendHandlers: BackendHandlerType = { @@ -144,14 +142,12 @@ const backendHandlers: BackendHandlerType = { resetDb, runGarbageCollector, setLoggingLevel, - containsHeaderListener, - toggleHeaderListener, }; -async function containsHeaderListener(): Promise<ExtendedPermissionsResponse> { - const result = await platform.containsTalerHeaderListener(); - return { newValue: result }; -} +// async function containsHeaderListener(): Promise<ExtendedPermissionsResponse> { +// const result = await platform.containsTalerHeaderListener(); +// return { newValue: result }; +// } async function setLoggingLevel({ tag, @@ -353,45 +349,55 @@ export async function wxMain(): Promise<void> { } catch (e) { console.error(e); } - // 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(); - }); - if (await isHeaderListenerEnabled()) { - if (await platform.getPermissionsApi().containsHostPermissions()) { - try { - platform.registerTalerHeaderListener(); - } catch (e) { - logger.error("could not register header listener", e); - } - } else { - await platform.getPermissionsApi().requestHostPermissions() - } - } + // 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 + */ + + // if (await isHeaderListenerEnabled()) { + // if (await platform.getPermissionsApi().containsHostPermissions()) { + // try { + // platform.registerTalerHeaderListener(); + // } catch (e) { + // logger.error("could not register header listener", e); + // } + // } else { + // 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) { - platform.registerTalerHeaderListener(); - return { newValue: true }; - } +// async function toggleHeaderListener( +// newVal: boolean, +// ): Promise<ExtendedPermissionsResponse> { +// logger.trace("new extended permissions value", newVal); +// if (newVal) { +// platform.registerTalerHeaderListener(); +// return { newValue: true }; +// } - const rem = await platform.getPermissionsApi().removeHostPermissions(); - logger.trace("permissions removed:", rem); - return { newValue: false }; -} +// const rem = await platform.getPermissionsApi().removeHostPermissions(); +// logger.trace("permissions removed:", rem); +// return { newValue: false }; +// } |