aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx56
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx6
-rw-r--r--packages/taler-wallet-webextension/manifest-v3.json22
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useSettings.ts2
-rw-r--r--packages/taler-wallet-webextension/src/platform/api.ts18
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts473
-rw-r--r--packages/taler-wallet-webextension/src/platform/dev.ts7
-rw-r--r--packages/taler-wallet-webextension/src/platform/firefox.ts12
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts120
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts7
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.tsx32
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts16
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts114
13 files changed, 483 insertions, 402 deletions
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index b7d7e5520..4001fd093 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -105,30 +105,30 @@ export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doCon
return
}
switch (hasError.case) {
- case "previously-aborted": return notify({
- type: "error",
- title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
- })
- case "no-exchange-or-reserve-selected": return notify({
- type: "error",
- title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
- })
- case "invalid-id": return notify({
- type: "error",
- title: i18n.str`The operation id is invalid.`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
- });
- case "not-found": return notify({
- type: "error",
- title: i18n.str`The operation was not found.`,
- description: hasError.detail.hint as TranslatedString,
- debug: hasError.detail,
- });
+ case "previously-aborted": return notify({
+ type: "error",
+ title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ })
+ case "no-exchange-or-reserve-selected": return notify({
+ type: "error",
+ title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ })
+ case "invalid-id": return notify({
+ type: "error",
+ title: i18n.str`The operation id is invalid.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The operation was not found.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
default: assertUnreachable(hasError)
}
})
@@ -408,6 +408,7 @@ export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
const [notification, notify, errorHandler] = useLocalNotification()
+ const talerWithdrawUri = stringifyWithdrawUri(uri);
useEffect(() => {
//Taler Wallet WebExtension is listening to headers response and tab updates.
//In the SPA there is no header response with the Taler URI so
@@ -415,8 +416,11 @@ export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> {
// WebExtension will be using
// https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
document.title = `${document.title} ${uri.withdrawalOperationId}`;
+ const meta = document.createElement("meta")
+ meta.setAttribute("name", "taler-uri")
+ meta.setAttribute("content", talerWithdrawUri)
+ document.head.insertBefore(meta, document.head.children.length ? document.head.children[0] : null)
}, []);
- const talerWithdrawUri = stringifyWithdrawUri(uri);
async function onClose() {
errorHandler(async () => {
@@ -447,7 +451,7 @@ export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> {
}
return <Fragment>
- <ShowLocalNotification notification={notification} />
+ <ShowLocalNotification notification={notification} />
<div class="flex justify-end mt-4">
<button type="button"
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index ca2a89f48..22bf604f2 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -39,6 +39,7 @@ export function QrCodeSection({
onAborted: () => void;
}): VNode {
const { i18n } = useTranslationContext();
+ const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
useEffect(() => {
//Taler Wallet WebExtension is listening to headers response and tab updates.
//In the SPA there is no header response with the Taler URI so
@@ -46,8 +47,11 @@ export function QrCodeSection({
// WebExtension will be using
// https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
+ const meta = document.createElement("meta")
+ meta.setAttribute("name", "taler-uri")
+ meta.setAttribute("content", talerWithdrawUri)
+ document.head.insertBefore(meta, document.head.children.length ? document.head.children[0] : null)
}, []);
- const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
const [notification, notify, handleError] = useLocalNotification()
const { api } = useBankCoreApiContext()
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 };
+// }