diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/platform/chrome.ts')
-rw-r--r-- | packages/taler-wallet-webextension/src/platform/chrome.ts | 268 |
1 files changed, 187 insertions, 81 deletions
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index b0934f107..3151bd6ab 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -59,6 +59,8 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { useServiceWorkerAsBackgroundProcess, keepAlive, listenNetworkConnectionState, + registerTalerHeaderListener, + containsTalerHeaderListener, }; export default api; @@ -95,10 +97,6 @@ function isFirefox(): boolean { return false; } -// const hostPermissions = { -// permissions: ["webRequest"], -// origins: ["http://*/*", "https://*/*"], -// }; export function containsClipboardPermissions(): Promise<boolean> { return new Promise((res, rej) => { @@ -113,18 +111,6 @@ export function containsClipboardPermissions(): Promise<boolean> { }); } -// 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 requestClipboardPermissions(): Promise<boolean> { return new Promise((res, rej) => { res(false); @@ -138,67 +124,7 @@ export async function requestClipboardPermissions(): Promise<boolean> { }); } -// 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); -// }); -// }); -// } - -// 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; - -// 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); -// }); -// }); -// } + export function removeClipboardPermissions(): Promise<boolean> { return new Promise((res, rej) => { @@ -225,9 +151,9 @@ function addPermissionsListener( function getPermissionsApi(): CrossBrowserPermissionsApi { return { addPermissionsListener, - // containsHostPermissions, - // requestHostPermissions, - // removeHostPermissions, + containsHostPermissions, + requestHostPermissions, + removeHostPermissions, requestClipboardPermissions, removeClipboardPermissions, containsClipboardPermissions, @@ -365,7 +291,7 @@ async function sendMessageToBackground< const timerId = setTimeout(() => { timedout = true; reject(TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, {}) ); - }, 20 * 1000); //five seconds + }, 20 * 1000); chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { if (timedout) { return false; //already rejected @@ -782,3 +708,183 @@ function listenNetworkConnectionState( window.removeEventListener("online", notifyOnline); }; } + +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 registerTalerHeaderListener( + callback: (tabId: number, url: string) => void, +): void { + logger.info("setting up header listener"); + + function headerListener( + details: chrome.webRequest.WebResponseHeadersDetails, + ): void { + 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); + if (values.length > 0) { + logger.info( + `Found a Taler URI in a response header for the request ${details.url} from tab ${details.tabId}`, + ); + callback(details.tabId, values[0]); + } + } + return; + } + + 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}`); + callback(tabId, uri); + } + } + + 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) + ) { + chrome.webRequest.onHeadersReceived.removeListener(prevHeaderListener); + } + if ( + prevTabListener && + chrome?.tabs?.onUpdated?.hasListener(prevTabListener) + ) { + chrome.tabs.onUpdated.removeListener(prevTabListener); + } + + //if the result was positive, add the headerListener + if (result) { + const headersEvent: + | chrome.webRequest.WebResponseHeadersEvent + | undefined = chrome?.webRequest?.onHeadersReceived; + if (headersEvent) { + headersEvent.addListener(headerListener, { urls: ["<all_urls>"] }, [ + "responseHeaders", + ]); + 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 |