check if the script should be injected

remove header listener
This commit is contained in:
Sebastian 2023-04-18 10:47:53 -03:00
parent b1a0d034fc
commit b34f3568e8
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
16 changed files with 403 additions and 416 deletions

View File

@ -17,8 +17,7 @@
},
"permissions": [
"unlimitedStorage",
"http://*/*",
"https://*/*",
"storage",
"activeTab"
],
"web_accessible_resources": [
@ -28,21 +27,31 @@
"dist/taler-wallet-interaction-support.js.map",
"dist/taler-wallet-interaction-support.js"
],
"optional_permissions": [
"http://*/*",
"https://*/*",
"webRequest"
],
"content_scripts": [{
"id": "taler-wallet-interaction-support",
"matches": ["file://*/*", "http://*/*", "https://*/*"],
"js": ["dist/taler-wallet-interaction-loader.js"]
}],
"protocol_handlers": [
{
"protocol": "ext+taler+http",
"name": "Taler Wallet WebExtension",
"uriTemplate": "/static/wallet.html#/cta/taler-uri/%s"
},
{
"protocol": "web+taler+http",
"name": "Taler Wallet WebExtension",
"uriTemplate": "/static/wallet.html#/cta/taler-uri/%s"
},
{
"protocol": "ext+taler",
"name": "Taler Wallet WebExtension",
"uriTemplate": "/static/wallet.html#/cta/withdraw?d=1&talerWithdrawUri=%s"
"uriTemplate": "/static/wallet.html#/cta/taler-uri/%s"
},
{
"protocol": "web+taler",
"name": "Taler Wallet WebExtension",
"uriTemplate": "/static/wallet.html#/cta/taler-uri/%s"
}
],
"browser_action": {

View File

@ -14,6 +14,7 @@
},
"permissions": [
"unlimitedStorage",
"storage",
"activeTab",
"scripting",
"declarativeContent",
@ -26,9 +27,6 @@
}
}
},
"optional_permissions": [
"webRequest"
],
"content_scripts": [{
"id": "taler-wallet-interaction",
"matches": ["file://*/*", "http://*/*", "https://*/*"],
@ -50,10 +48,6 @@
]
}
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"action": {
"default_icon": {
"16": "static/img/taler-logo-16.png",

View File

@ -30,14 +30,8 @@ import { wxMain } from "./wxBackend.js";
console.log("Wallet setup for Dev API");
setupPlatform(devAPI);
try {
platform.registerOnInstalled(() => {
platform.openWalletPage("/welcome");
});
} catch (e) {
console.error(e);
async function start() {
await platform.notifyWhenAppIsReady();
await wxMain();
}
platform.notifyWhenAppIsReady(() => {
wxMain();
});
start();

View File

@ -43,6 +43,9 @@ if (isFirefox) {
}
// setGlobalLogLevelFromString("trace")
platform.notifyWhenAppIsReady(() => {
wxMain();
});
async function start() {
await platform.notifyWhenAppIsReady();
await wxMain();
}
start();

View File

@ -20,6 +20,12 @@ import { useBackendContext } from "../context/backend.js";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/foreground.js";
/**
* This is not implemented.
* Clipboard permission need to get ask the permission to the user
* based on user-intention
* @returns
*/
export function useClipboardPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false);
const api = useBackendContext();
@ -40,27 +46,27 @@ export function useClipboardPermissions(): ToggleHandler {
}
setEnabled(granted);
} else {
try {
await api.background
.call("toggleHeaderListener", false)
.then((r) => setEnabled(r.newValue));
} catch (e) {
console.log(e);
}
// try {
// await api.background
// .call("toggleHeaderListener", false)
// .then((r) => setEnabled(r.newValue));
// } catch (e) {
// console.log(e);
// }
}
return;
}
useEffect(() => {
async function getValue(): Promise<void> {
const res = await api.background.call(
"containsHeaderListener",
undefined,
);
setEnabled(res.newValue);
}
getValue();
}, []);
// useEffect(() => {
// async function getValue(): Promise<void> {
// const res = await api.background.call(
// "containsHeaderListener",
// undefined,
// );
// setEnabled(res.newValue);
// }
// getValue();
// }, []);
return {
value: enabled,

View File

@ -15,14 +15,7 @@
*/
import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
interface Settings {
injectTalerSupport: boolean;
}
const defaultSettings: Settings = {
injectTalerSupport: false,
};
import { Settings, defaultSettings } from "../platform/api.js";
function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
if (str === undefined) return undefined;
@ -42,7 +35,6 @@ export function useSettings(): [
const parsed: Settings = parse_json_or_undefined(value) ?? defaultSettings;
function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
const newValue = { ...parsed, [k]: v };
console.log("should update", k, v, parsed, newValue);
const json = JSON.stringify(newValue);
console.log(json);
update(json);

View File

@ -17,6 +17,10 @@
import { CoreApiResponse, NotificationType } from "@gnu-taler/taler-util";
import { WalletOperations } from "@gnu-taler/taler-wallet-core";
import { BackgroundOperations } from "../wxApi.js";
import {
ExtensionOperations,
MessageFromExtension,
} from "../taler-wallet-interaction-loader.js";
export interface Permissions {
/**
@ -35,9 +39,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>;
@ -53,9 +57,11 @@ export type MessageFromBackend = {
};
export type MessageFromFrontend<
Op extends BackgroundOperations | WalletOperations,
Op extends BackgroundOperations | WalletOperations | ExtensionOperations,
> = Op extends BackgroundOperations
? MessageFromFrontendBackground<keyof BackgroundOperations>
: Op extends ExtensionOperations
? MessageFromExtension<keyof ExtensionOperations>
: Op extends WalletOperations
? MessageFromFrontendWallet<keyof WalletOperations>
: never;
@ -81,11 +87,23 @@ export interface WalletWebExVersion {
version: string;
}
export interface Settings {
injectTalerSupport: boolean;
}
export const defaultSettings: Settings = {
injectTalerSupport: false,
};
/**
* Compatibility helpers needed for browsers that don't implement
* WebExtension APIs consistently.
*/
export interface BackgroundPlatformAPI {
/**
*
*/
getSettingsFromStorage(): Promise<Settings>;
/**
* Guarantee that the service workers don't die
*/
@ -116,16 +134,12 @@ export interface BackgroundPlatformAPI {
* Register a callback to be called when the wallet is ready to start
* @param callback
*/
notifyWhenAppIsReady(callback: () => void): void;
notifyWhenAppIsReady(): Promise<void>;
/**
* Get the wallet version from manifest
*/
getWalletWebExVersion(): WalletWebExVersion;
/**
* Frontend API
*/
containsTalerHeaderListener(): boolean;
/**
* Backend API
*/
@ -134,12 +148,6 @@ export interface BackgroundPlatformAPI {
* Backend API
*/
registerReloadOnNewVersion(): void;
/**
* Backend API
*/
registerTalerHeaderListener(
onHeader: (tabId: number, url: string) => void,
): void;
/**
* Permission API for checking and add a listener

View File

@ -31,10 +31,13 @@ import {
MessageFromFrontend,
MessageResponse,
Permissions,
Settings,
defaultSettings,
} from "./api.js";
const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
isFirefox,
getSettingsFromStorage,
findTalerUriInActiveTab,
findTalerUriInClipboard,
getPermissionsApi,
@ -49,11 +52,9 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
registerOnInstalled,
listenToAllChannels: listenToAllChannels as any,
registerReloadOnNewVersion,
registerTalerHeaderListener,
sendMessageToAllChannels,
sendMessageToBackground,
useServiceWorkerAsBackgroundProcess,
containsTalerHeaderListener,
keepAlive,
};
@ -61,6 +62,19 @@ export default api;
const logger = new Logger("chrome.ts");
async function getSettingsFromStorage(): Promise<Settings> {
const data = await chrome.storage.local.get("wallet-settings");
if (!data) return defaultSettings;
const settings = data["wallet-settings"];
if (!settings) return defaultSettings;
try {
const parsed = JSON.parse(settings);
return parsed;
} catch (e) {
return defaultSettings;
}
}
function keepAlive(callback: any): void {
if (extensionIsManifestV3()) {
chrome.alarms.create("wallet-worker", { periodInMinutes: 1 });
@ -78,10 +92,10 @@ function isFirefox(): boolean {
return false;
}
const hostPermissions = {
permissions: ["webRequest"],
origins: ["http://*/*", "https://*/*"],
};
// const hostPermissions = {
// permissions: ["webRequest"],
// origins: ["http://*/*", "https://*/*"],
// };
export function containsClipboardPermissions(): Promise<boolean> {
return new Promise((res, rej) => {
@ -96,17 +110,17 @@ 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 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) => {
@ -121,73 +135,67 @@ 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);
});
});
}
// 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 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;
export function containsTalerHeaderListener(): boolean {
return (
currentHeaderListener !== undefined || currentTabListener !== 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);
// }
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;
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));
// }
// });
// }
//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);
});
});
}
// 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) => {
@ -214,9 +222,9 @@ function addPermissionsListener(
function getPermissionsApi(): CrossBrowserPermissionsApi {
return {
addPermissionsListener,
containsHostPermissions,
requestHostPermissions,
removeHostPermissions,
// containsHostPermissions,
// requestHostPermissions,
// removeHostPermissions,
requestClipboardPermissions,
removeClipboardPermissions,
containsClipboardPermissions,
@ -227,12 +235,16 @@ function getPermissionsApi(): CrossBrowserPermissionsApi {
*
* @param callback function to be called
*/
function notifyWhenAppIsReady(callback: () => void): void {
if (extensionIsManifestV3()) {
callback();
} else {
window.addEventListener("load", callback);
}
function notifyWhenAppIsReady(): Promise<void> {
return new Promise((resolve, reject) => {
if (extensionIsManifestV3()) {
resolve();
} else {
window.addEventListener("load", () => {
resolve();
});
}
});
}
function openWalletURIFromPopup(maybeTalerUri: string): void {
@ -478,101 +490,6 @@ function getWalletWebExVersion(): WalletVersion {
return manifestData;
}
function registerTalerHeaderListener(
callback: (tabId: number, url: string) => void,
): void {
logger.trace("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 alertIcons = {
"16": "/static/img/taler-alert-16.png",
"19": "/static/img/taler-alert-19.png",
@ -750,7 +667,7 @@ function registerOnInstalled(callback: () => void): void {
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
callback();
}
registerIconChangeOnTalerContent();
await registerIconChangeOnTalerContent();
});
}

View File

@ -23,18 +23,17 @@ import {
MessageFromBackend,
MessageFromFrontend,
MessageResponse,
defaultSettings,
} from "./api.js";
const frames = ["popup", "wallet"];
const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
isFirefox: () => false,
getSettingsFromStorage: () => Promise.resolve(defaultSettings),
keepAlive: (cb: VoidFunction) => cb(),
findTalerUriInActiveTab: async () => undefined,
findTalerUriInClipboard: async () => undefined,
containsTalerHeaderListener: () => {
return true;
},
getPermissionsApi: () => ({
addPermissionsListener: () => undefined,
containsHostPermissions: async () => true,
@ -47,21 +46,23 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
getWalletWebExVersion: () => ({
version: "none",
}),
notifyWhenAppIsReady: (fn: () => void) => {
notifyWhenAppIsReady: () => {
let total = frames.length;
function waitAndNotify(): void {
total--;
if (total < 1) {
fn();
}
}
frames.forEach((f) => {
const theFrame = window.frames[f as any];
if (theFrame.location.href === "about:blank") {
waitAndNotify();
} else {
theFrame.addEventListener("load", waitAndNotify);
return new Promise((fn) => {
function waitAndNotify(): void {
total--;
if (total < 1) {
fn();
}
}
frames.forEach((f) => {
const theFrame = window.frames[f as any];
if (theFrame.location.href === "about:blank") {
waitAndNotify();
} else {
theFrame.addEventListener("load", waitAndNotify);
}
});
});
},
@ -80,9 +81,8 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
},
registerAllIncomingConnections: () => undefined,
registerOnInstalled: (fn: () => void) => undefined,
registerOnInstalled: () => Promise.resolve(),
registerReloadOnNewVersion: () => undefined,
registerTalerHeaderListener: () => undefined,
useServiceWorkerAsBackgroundProcess: () => false,

View File

@ -19,11 +19,10 @@ import {
CrossBrowserPermissionsApi,
ForegroundPlatformAPI,
Permissions,
Settings,
defaultSettings,
} from "./api.js";
import chromePlatform, {
containsHostPermissions as chromeHostContains,
removeHostPermissions as chromeHostRemove,
requestHostPermissions as chromeHostRequest,
containsClipboardPermissions as chromeClipContains,
removeClipboardPermissions as chromeClipRemove,
requestClipboardPermissions as chromeClipRequest,
@ -32,6 +31,7 @@ import chromePlatform, {
const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
...chromePlatform,
isFirefox,
getSettingsFromStorage,
getPermissionsApi,
notifyWhenAppIsReady,
redirectTabToWalletPage,
@ -51,25 +51,43 @@ 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,
};
}
async function getSettingsFromStorage(): Promise<Settings> {
//@ts-ignore
const data = await browser.storage.local.get("wallet-settings");
if (!data) return defaultSettings;
const settings = data["wallet-settings"];
if (!settings) return defaultSettings;
try {
const parsed = JSON.parse(settings);
return parsed;
} catch (e) {
return defaultSettings;
}
}
/**
*
* @param callback function to be called
*/
function notifyWhenAppIsReady(callback: () => void): void {
if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
callback();
} else {
window.addEventListener("load", callback);
}
function notifyWhenAppIsReady(): Promise<void> {
return new Promise((resolve) => {
if (chrome.runtime && chrome.runtime.getManifest().manifest_version === 3) {
resolve();
} else {
window.addEventListener("load", () => {
resolve();
});
}
});
}
function redirectTabToWalletPage(tabId: number, page: string): void {

View File

@ -14,6 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { CoreApiResponse } from "@gnu-taler/taler-util";
/**
* This will modify all the pages that the user load when navigating with Web Extension enabled
*
@ -62,7 +64,7 @@ const logger = {
console.error(`${new Date().toISOString()} TALER`, ...msg),
};
function start() {
async function start() {
if (shouldNotInject) {
return;
}
@ -73,8 +75,15 @@ function start() {
}
createBridgeWithExtension();
logger.debug("bridged created");
injectTalerSupportScript(debugEnabled);
logger.debug("done");
const shouldInject = await callBackground("isInjectionEnabled", undefined);
if (shouldInject) {
injectTalerSupportScript(debugEnabled);
logger.debug("injection completed");
} else {
logger.debug("injection is not enabled");
}
}
/**
@ -132,4 +141,65 @@ function createBridgeWithExtension() {
);
}
export interface ExtensionOperations {
isInjectionEnabled: {
request: void;
response: boolean;
};
}
export type MessageFromExtension<Op extends keyof ExtensionOperations> = {
channel: "extension";
operation: Op;
payload: ExtensionOperations[Op]["request"];
};
export type MessageResponse = CoreApiResponse;
async function callBackground<Op extends keyof ExtensionOperations>(
operation: Op,
payload: ExtensionOperations[Op]["request"],
): Promise<ExtensionOperations[Op]["response"]> {
const message: MessageFromExtension<Op> = {
channel: "extension",
operation,
payload,
};
const response = await sendMessageToBackground(message);
if (response.type === "error") {
throw new Error(`Background operation "${operation}" failed`);
}
return response.result as any;
}
let nextMessageIndex = 0;
async function sendMessageToBackground<Op extends keyof ExtensionOperations>(
message: MessageFromExtension<Op>,
): Promise<MessageResponse> {
const messageWithId = { ...message, id: `id_${nextMessageIndex++ % 1000}` };
return new Promise<any>((resolve, reject) => {
// logger.trace("send operation to the wallet background", message);
let timedout = false;
const timerId = setTimeout(() => {
timedout = true;
throw new Error("timeout");
// throw TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, {});
}, 5 * 1000); //five seconds
chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => {
if (timedout) {
return false; //already rejected
}
clearTimeout(timerId);
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
} else {
resolve(backgroundResponse);
}
// return true to keep the channel open
return true;
});
});
}
start();

View File

@ -92,7 +92,7 @@ function buildApi(config: Readonly<Info>): API {
}
const targetAttr = ev.currentTarget.attributes.getNamedItem("target");
const windowTarget =
targetAttr && targetAttr.value ? targetAttr.value : "taler-wallet";
targetAttr && targetAttr.value ? targetAttr.value : "_self";
const page = convertURIToWebExtensionPath(hrefAttr.value);
if (!page) {
logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`);

View File

@ -271,7 +271,7 @@ export function SettingsView({
<i18n.Translate>Navigator</i18n.Translate>
</SubTitle>
<Checkbox
label={i18n.str`Automatically inject Taler API in all pages`}
label={i18n.str`Inject Taler support in all pages`}
name="inject"
description={
<i18n.Translate>

View File

@ -100,7 +100,7 @@ export function View({
<i18n.Translate>Navigator</i18n.Translate>
</SubTitle>
<Checkbox
label={i18n.str`Automatically inject Taler API in all pages`}
label={i18n.str`Inject Taler support in all pages`}
name="inject"
description={
<i18n.Translate>

View File

@ -68,18 +68,10 @@ export interface BackgroundOperations {
request: void;
response: void;
};
containsHeaderListener: {
request: void;
response: ExtendedPermissionsResponse;
};
getDiagnostics: {
request: void;
response: WalletDiagnostics;
};
toggleHeaderListener: {
request: boolean;
response: ExtendedPermissionsResponse;
};
runGarbageCollector: {
request: void;
response: void;

View File

@ -24,42 +24,39 @@
* Imports.
*/
import {
classifyTalerUri,
Logger,
LogLevel,
Logger,
TalerErrorCode,
WalletDiagnostics,
getErrorDetailFromException,
makeErrorDetail,
setGlobalLogLevelFromString,
setLogLevelFromString,
TalerErrorCode,
TalerUriType,
WalletDiagnostics,
makeErrorDetail,
getErrorDetailFromException,
parseTalerUri,
TalerUriAction,
} from "@gnu-taler/taler-util";
import {
DbAccess,
deleteTalerDatabase,
exportDb,
importDb,
OpenedPromise,
openPromise,
openTalerDatabase,
SetTimeoutTimerAPI,
Wallet,
WalletOperations,
WalletStoresV1,
deleteTalerDatabase,
exportDb,
importDb,
openPromise,
openTalerDatabase,
} from "@gnu-taler/taler-wallet-core";
import { BrowserHttpLib } from "./browserHttpLib.js";
import { platform } from "./platform/background.js";
import {
MessageFromBackend,
MessageFromFrontend,
MessageResponse,
} from "./platform/api.js";
import { platform } from "./platform/background.js";
import { SynchronousCryptoWorkerFactory } from "./serviceWorkerCryptoWorkerFactory.js";
import { ServiceWorkerHttpLib } from "./serviceWorkerHttpLib.js";
import { BackgroundOperations, ExtendedPermissionsResponse } from "./wxApi.js";
import { ExtensionOperations } from "./taler-wallet-interaction-loader.js";
import { BackgroundOperations } from "./wxApi.js";
/**
* Currently active wallet instance. Might be unloaded and
@ -123,10 +120,11 @@ type BackendHandlerType = {
) => Promise<BackgroundOperations[Op]["response"]>;
};
async function containsHeaderListener(): Promise<ExtendedPermissionsResponse> {
const result = await platform.containsTalerHeaderListener();
return { newValue: result };
}
type ExtensionHandlerType = {
[Op in keyof ExtensionOperations]: (
req: ExtensionOperations[Op]["request"],
) => Promise<ExtensionOperations[Op]["response"]>;
};
async function resetDb(): Promise<void> {
await deleteTalerDatabase(indexedDB as any);
@ -153,20 +151,6 @@ async function runGarbageCollector(): Promise<void> {
logger.info("imported");
}
async function toggleHeaderListener(
newVal: boolean,
): Promise<ExtendedPermissionsResponse> {
logger.trace("new extended permissions value", newVal);
if (newVal) {
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
return { newValue: true };
}
const rem = await platform.getPermissionsApi().removeHostPermissions();
logger.trace("permissions removed:", rem);
return { newValue: false };
}
function freeze(time: number): Promise<void> {
return new Promise((res, rej) => {
setTimeout(res, time);
@ -177,14 +161,21 @@ async function sum(ns: Array<number>): Promise<number> {
return ns.reduce((prev, cur) => prev + cur, 0);
}
const extensionHandlers: ExtensionHandlerType = {
isInjectionEnabled,
};
async function isInjectionEnabled(): Promise<boolean> {
const settings = await platform.getSettingsFromStorage();
return settings.injectTalerSupport === true;
}
const backendHandlers: BackendHandlerType = {
freeze,
sum,
containsHeaderListener,
getDiagnostics,
resetDb,
runGarbageCollector,
toggleHeaderListener,
setLoggingLevel,
};
@ -203,55 +194,85 @@ async function setLoggingLevel({
}
}
async function dispatch<Op extends WalletOperations | BackgroundOperations>(
req: MessageFromFrontend<Op> & { id: string },
): Promise<MessageResponse> {
if (req.channel === "background") {
const handler = backendHandlers[req.operation] as (req: any) => any;
if (!handler) {
return {
type: "error",
id: req.id,
operation: String(req.operation),
error: getErrorDetailFromException(
Error(`unknown background operation`),
),
};
async function dispatch<
Op extends WalletOperations | BackgroundOperations | ExtensionOperations,
>(req: MessageFromFrontend<Op> & { id: string }): Promise<MessageResponse> {
switch (req.channel) {
case "background": {
const handler = backendHandlers[req.operation] as (req: any) => any;
if (!handler) {
return {
type: "error",
id: req.id,
operation: String(req.operation),
error: getErrorDetailFromException(
Error(`unknown background operation`),
),
};
}
try {
const result = await handler(req.payload);
return {
type: "response",
id: req.id,
operation: String(req.operation),
result,
};
} catch (er) {
return {
type: "error",
id: req.id,
error: getErrorDetailFromException(er),
operation: String(req.operation),
};
}
}
try {
const result = await handler(req.payload);
return {
type: "response",
id: req.id,
operation: String(req.operation),
result,
};
} catch (er) {
return {
type: "error",
id: req.id,
error: getErrorDetailFromException(er),
operation: String(req.operation),
};
case "extension": {
const handler = extensionHandlers[req.operation] as (req: any) => any;
if (!handler) {
return {
type: "error",
id: req.id,
operation: String(req.operation),
error: getErrorDetailFromException(
Error(`unknown extension operation`),
),
};
}
try {
const result = await handler(req.payload);
return {
type: "response",
id: req.id,
operation: String(req.operation),
result,
};
} catch (er) {
return {
type: "error",
id: req.id,
error: getErrorDetailFromException(er),
operation: String(req.operation),
};
}
}
}
case "wallet": {
const w = currentWallet;
if (!w) {
return {
type: "error",
id: req.id,
operation: req.operation,
error: makeErrorDetail(
TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
{},
"wallet core not available",
),
};
}
if (req.channel === "wallet") {
const w = currentWallet;
if (!w) {
return {
type: "error",
id: req.id,
operation: req.operation,
error: makeErrorDetail(
TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
{},
"wallet core not available",
),
};
return await w.handleCoreApiRequest(req.operation, req.id, req.payload);
}
return await w.handleCoreApiRequest(req.operation, req.id, req.payload);
}
const anyReq = req as any;
@ -261,7 +282,7 @@ async function dispatch<Op extends WalletOperations | BackgroundOperations>(
operation: String(anyReq.operation),
error: getErrorDetailFromException(
Error(
`unknown channel ${anyReq.channel}, should be "background" or "wallet"`,
`unknown channel ${anyReq.channel}, should be "background", "extension" or "wallet"`,
),
),
};
@ -330,23 +351,6 @@ async function reinitWallet(): Promise<void> {
return walletInit.resolve();
}
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 platform.redirectTabToWalletPage(
tabId,
`/taler-uri/${encodeURIComponent(talerUri)}`,
);
}
/**
* Main function to run for the WebExtension backend.
*
@ -370,30 +374,10 @@ export async function wxMain(): Promise<void> {
platform.registerAllIncomingConnections();
try {
platform.registerOnInstalled(() => {
await platform.registerOnInstalled(() => {
platform.openWalletPage("/welcome");
//
try {
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
} catch (e) {
logger.error("could not register header listener", e);
}
});
} catch (e) {
console.error(e);
}
// On platforms that support it, also listen to external
// modification of permissions.
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {
if (lastError) {
logger.error(
`there was a problem trying to get permission ${perm}`,
lastError,
);
return;
}
platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
});
}