From b99ef78dea6c43d430e3aad4c9f3f8cff0a06844 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 15 Dec 2023 00:45:01 -0300 Subject: testing header listener --- .../taler-wallet-webextension/manifest-v2.json | 4 + .../taler-wallet-webextension/manifest-v3.json | 4 + .../taler-wallet-webextension/src/platform/api.ts | 13 +- .../src/platform/chrome.ts | 416 +++++++++++---------- .../taler-wallet-webextension/src/platform/dev.ts | 4 +- .../src/platform/firefox.ts | 12 +- .../src/wallet/DeveloperPage.tsx | 14 + packages/taler-wallet-webextension/src/wxApi.ts | 16 +- .../taler-wallet-webextension/src/wxBackend.ts | 97 +++-- 9 files changed, 315 insertions(+), 265 deletions(-) (limited to 'packages/taler-wallet-webextension') diff --git a/packages/taler-wallet-webextension/manifest-v2.json b/packages/taler-wallet-webextension/manifest-v2.json index 36d10f4f3..4717b1ab0 100644 --- a/packages/taler-wallet-webextension/manifest-v2.json +++ b/packages/taler-wallet-webextension/manifest-v2.json @@ -18,8 +18,12 @@ "permissions": [ "unlimitedStorage", "storage", + "webRequest", "activeTab" ], + "host_permissions": [ + "" + ], "web_accessible_resources": [ "static/wallet.html", "dist/taler-wallet-interaction-loader.js.map", diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json index ae1be5181..d6a303ed6 100644 --- a/packages/taler-wallet-webextension/manifest-v3.json +++ b/packages/taler-wallet-webextension/manifest-v3.json @@ -17,9 +17,13 @@ "storage", "activeTab", "scripting", + "webRequest", "declarativeContent", "alarms" ], + "host_permissions": [ + "" + ], "commands": { "_execute_action": { "suggested_key": { diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index d425a9992..a2b26441b 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; - // requestHostPermissions(): Promise; - // removeHostPermissions(): Promise; + containsHostPermissions(): Promise; + requestHostPermissions(): Promise; + removeHostPermissions(): Promise; containsClipboardPermissions(): Promise; requestClipboardPermissions(): Promise; @@ -207,6 +207,13 @@ export interface BackgroundPlatformAPI { ) => Promise, ): 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 04ecd80cc..7f384f7d4 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -60,6 +60,8 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { useServiceWorkerAsBackgroundProcess, keepAlive, listenNetworkConnectionState, + registerTalerHeaderListener, + containsTalerHeaderListener, }; export default api; @@ -149,6 +151,9 @@ function addPermissionsListener( function getPermissionsApi(): CrossBrowserPermissionsApi { return { + containsHostPermissions, + requestHostPermissions, + removeHostPermissions, addPermissionsListener, requestClipboardPermissions, removeClipboardPermissions, @@ -718,79 +723,81 @@ function listenNetworkConnectionState( }; } -// type HeaderListenerFunc = ( -// details: chrome.webRequest.WebResponseHeadersDetails, -// ) => void; -// let currentHeaderListener: HeaderListenerFunc | undefined = undefined; +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; +type TabListenerFunc = (tabId: number, info: chrome.tabs.TabChangeInfo) => void; +let currentTabListener: TabListenerFunc | undefined = undefined; -// function containsTalerHeaderListener(): boolean { -// return ( -// currentHeaderListener !== undefined || currentTabListener !== 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)}`, -// ); -// } +function headerListener( + details: chrome.webRequest.WebResponseHeadersDetails, +): chrome.webRequest.BlockingResponse | undefined { + if (chrome.runtime.lastError) { + logger.error(JSON.stringify(chrome.runtime.lastError)); + return; + } -// async function tabListener( -// tabId: number, -// info: chrome.tabs.TabChangeInfo, -// ): Promise { -// 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); -// } -// } + 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)}`, + ); +} + +async function tabListener( + tabId: number, + info: chrome.tabs.TabChangeInfo, +): Promise { + 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 @@ -821,144 +828,141 @@ function listenNetworkConnectionState( // }); // } -// 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: [""] }, -// ["responseHeaders", "extraHeaders"] -// ); -// // chrome.webRequest.onCompleted.addListener(headerListener, -// // { urls: [""] }, -// // ["responseHeaders", "extraHeaders"] -// // ); -// // chrome.webRequest.onResponseStarted.addListener(headerListener, -// // { urls: [""] }, -// // ["responseHeaders", "extraHeaders"] -// // ); -// // chrome.webRequest.onErrorOccurred.addListener(headerListener, -// // { 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)); -// } -// }); -// }); -// } +function registerTalerHeaderListener(): void { + logger.info("setting up header listener"); + + const prevHeaderListener = currentHeaderListener; + const prevTabListener = currentTabListener; + + 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); + } -// const hostPermissions = { -// permissions: ["webRequest"], -// origins: ["http://*/*", "https://*/*"], -// }; - -// export function containsHostPermissions(): Promise { -// return new Promise((res, rej) => { -// chrome.permissions.contains(hostPermissions, (resp) => { -// const le = chrome.runtime.lastError?.message; -// if (le) { -// rej(le); -// } -// res(resp); -// }); -// }); -// } + console.log("headers on, disabled:", chrome?.webRequest?.onHeadersReceived === undefined) + if (chrome?.webRequest) { + if (extensionIsManifestV3()) { + chrome.webRequest.onHeadersReceived.addListener(headerListener, + { urls: [""] }, + ["responseHeaders", "extraHeaders"] + ); + } else { + //Firefox doesnt support extra headers + chrome.webRequest.onHeadersReceived.addListener(headerListener, + { urls: [""] }, + ["responseHeaders"] + ); + } + // chrome.webRequest.onCompleted.addListener(headerListener, + // { urls: [""] }, + // ["responseHeaders", "extraHeaders"] + // ); + // chrome.webRequest.onResponseStarted.addListener(headerListener, + // { urls: [""] }, + // ["responseHeaders", "extraHeaders"] + // ); + // chrome.webRequest.onErrorOccurred.addListener(headerListener, + // { urls: [""] }, + // ["extraHeaders"] + // ); + currentHeaderListener = headerListener; + } -// export async function requestHostPermissions(): Promise { -// return new Promise((res, rej) => { -// chrome.permissions.request(hostPermissions, (resp) => { -// const le = chrome.runtime.lastError?.message; -// if (le) { -// rej(le); -// } -// res(resp); -// }); -// }); -// } + const tabsEvent: chrome.tabs.TabUpdatedEvent | undefined = + chrome?.tabs?.onUpdated; + if (tabsEvent) { + tabsEvent.addListener(tabListener); + currentTabListener = tabListener; + } -// export async function removeHostPermissions(): Promise { -// //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 + //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 { + 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 { + 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 { + //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 8d37930fa..51744e318 100644 --- a/packages/taler-wallet-webextension/src/platform/dev.ts +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -47,8 +47,8 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { }), // registerDeclarativeRedirect: () => false, - // registerTalerHeaderListener: () => false, - // containsTalerHeaderListener: () => 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 cca2833ad..cc734ebf7 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/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index 7c0f1938d..0a01b8a95 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -257,6 +257,20 @@ export function View({ export database + + + + + +