From ddfb40e50cef0abddc7690b23562b1ca5aeb3fdd Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 25 Mar 2022 16:57:27 -0300 Subject: new dev environment --- .../taler-wallet-webextension/src/platform/api.ts | 123 ++++++++++++++-- .../src/platform/chrome.ts | 38 +++-- .../taler-wallet-webextension/src/platform/dev.ts | 159 +++++++++++++++++++++ 3 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/platform/dev.ts (limited to 'packages/taler-wallet-webextension/src/platform') diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index 9b4e02ffb..112f9721c 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -38,7 +38,7 @@ export interface CrossBrowserPermissionsApi { request(p: Permissions): Promise; remove(p: Permissions): Promise; - addPermissionsListener(callback: (p: Permissions) => void): void; + addPermissionsListener(callback: (p: Permissions, lastError?: string) => void): void; } @@ -57,31 +57,132 @@ export interface WalletVersion { */ export interface PlatformAPI { /** + * FIXME: should not be needed + * * check if the platform is firefox */ isFirefox(): boolean; + /** - * + * Permission API for checking and add a listener */ getPermissionsApi(): CrossBrowserPermissionsApi; + + /** + * Backend API + * + * Register a callback to be called when the wallet is ready to start + * @param callback + */ notifyWhenAppIsReady(callback: () => void): void; - openWalletURIFromPopup(uriType: TalerUriType, talerUri: string): void; + + /** + * Popup API + * + * Used when an TalerURI is found and open up from the popup UI. + * Closes the popup and open the URI into the wallet UI. + * + * @param talerUri + */ + openWalletURIFromPopup(talerUri: string): void; + + /** + * Backend API + * + * Open a page into the wallet UI + * @param page + */ openWalletPage(page: string): void; + + /** + * Popup API + * + * Open a page into the wallet UI and closed the popup + * @param page + */ openWalletPageFromPopup(page: string): void; - setMessageToWalletBackground(operation: string, payload: any): Promise; - listenToWalletNotifications(listener: (m: any) => void): () => void; - sendMessageToAllChannels(message: MessageFromBackend): void; - registerAllIncomingConnections(): void; - registerOnNewMessage(onNewMessage: (message: any, sender: any, callback: any) => void): void; - registerReloadOnNewVersion(): void; + + /** + * Backend API + * + * When a tab has been detected to have a Taler action the background process + * can use this function to redirect the tab to the wallet UI + * + * @param tabId + * @param page + */ redirectTabToWalletPage(tabId: number, page: string): void; + + /** + * Get the wallet version from manifest + */ getWalletVersion(): WalletVersion; + + + /** + * Backend API + */ + registerAllIncomingConnections(): void; + /** + * Backend API + */ + registerReloadOnNewVersion(): void; + /** + * Backend API + */ registerTalerHeaderListener(onHeader: (tabId: number, url: string) => void): void; + /** + * Backend API + */ registerOnInstalled(callback: () => void): void; + + /** + * Backend API + * + * Check if background process run as service worker. This is used from the + * wallet use different http api and crypto worker. + */ useServiceWorkerAsBackgroundProcess(): boolean; - getLastError(): string | undefined; - searchForTalerLinks(): string | undefined; + + /** + * Popup API + * + * Read the current tab html and try to find any Taler URI or QR code present. + * + * @return Taler URI if found + */ findTalerUriInActiveTab(): Promise; + + /** + * Used from the frontend to send commands to the wallet + * + * @param operation + * @param payload + * + * @return response from the backend + */ + sendMessageToWalletBackground(operation: string, payload: any): Promise; + + /** + * Used from the frontend to receive notifications about new information + * @param listener + * @return function to unsubscribe the listener + */ + listenToWalletBackground(listener: (message: MessageFromBackend) => void): () => void; + + /** + * Use by the wallet backend to receive operations from frontend (popup & wallet) + * and send a response back. + * + * @param onNewMessage + */ + listenToAllChannels(onNewMessage: (message: any, sender: any, sendResponse: (r: CoreApiResponse) => void) => void): void; + + /** + * Used by the wallet backend to send notification about new information + * @param message + */ + sendMessageToAllChannels(message: MessageFromBackend): void; } export let platform: PlatformAPI = undefined as any; diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index dada23c57..91ccc220d 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -14,17 +14,16 @@ TALER; see the file COPYING. If not, see */ -import { TalerUriType } from "@gnu-taler/taler-util"; +import { classifyTalerUri, CoreApiResponse, TalerUriType } from "@gnu-taler/taler-util"; import { getReadRequestPermissions } from "../permissions"; import { CrossBrowserPermissionsApi, MessageFromBackend, Permissions, PlatformAPI } from "./api.js"; const api: PlatformAPI = { isFirefox, findTalerUriInActiveTab, - getLastError, getPermissionsApi, getWalletVersion, - listenToWalletNotifications, + listenToWalletBackground, notifyWhenAppIsReady, openWalletPage, openWalletPageFromPopup, @@ -32,12 +31,11 @@ const api: PlatformAPI = { redirectTabToWalletPage, registerAllIncomingConnections, registerOnInstalled, - registerOnNewMessage, + listenToAllChannels, registerReloadOnNewVersion, registerTalerHeaderListener, - searchForTalerLinks, sendMessageToAllChannels, - setMessageToWalletBackground, + sendMessageToWalletBackground, useServiceWorkerAsBackgroundProcess } @@ -50,7 +48,7 @@ function isFirefox(): boolean { export function contains(p: Permissions): Promise { return new Promise((res, rej) => { chrome.permissions.contains(p, (resp) => { - const le = getLastError() + const le = chrome.runtime.lastError?.message if (le) { rej(le) } @@ -62,7 +60,7 @@ export function contains(p: Permissions): Promise { export async function request(p: Permissions): Promise { return new Promise((res, rej) => { chrome.permissions.request(p, (resp) => { - const le = getLastError() + const le = chrome.runtime.lastError?.message if (le) { rej(le) } @@ -74,7 +72,7 @@ export async function request(p: Permissions): Promise { export async function remove(p: Permissions): Promise { return new Promise((res, rej) => { chrome.permissions.remove(p, (resp) => { - const le = getLastError() + const le = chrome.runtime.lastError?.message if (le) { rej(le) } @@ -83,9 +81,12 @@ export async function remove(p: Permissions): Promise { }) } -function addPermissionsListener(callback: (p: Permissions) => void): void { +function addPermissionsListener(callback: (p: Permissions, lastError?: string) => void): void { console.log("addPermissionListener is not supported for Firefox"); - chrome.permissions.onAdded.addListener(callback) + chrome.permissions.onAdded.addListener((perm: Permissions) => { + const lastError = chrome.runtime.lastError?.message; + callback(perm, lastError) + }) } function getPermissionsApi(): CrossBrowserPermissionsApi { @@ -107,7 +108,9 @@ function notifyWhenAppIsReady(callback: () => void) { } -function openWalletURIFromPopup(uriType: TalerUriType, talerUri: string) { +function openWalletURIFromPopup(talerUri: string) { + const uriType = classifyTalerUri(talerUri); + let url: string | undefined = undefined; switch (uriType) { case TalerUriType.TalerWithdraw: @@ -150,7 +153,7 @@ function openWalletPageFromPopup(page: string) { ); } -async function setMessageToWalletBackground(operation: string, payload: any): Promise { +async function sendMessageToWalletBackground(operation: string, payload: any): Promise { return new Promise((resolve, reject) => { chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => { if (chrome.runtime.lastError) { @@ -164,7 +167,7 @@ async function setMessageToWalletBackground(operation: string, payload: any): Pr } let notificationPort: chrome.runtime.Port | undefined; -function listenToWalletNotifications(listener: (m: any) => void) { +function listenToWalletBackground(listener: (m: any) => void) { if (notificationPort === undefined) { notificationPort = chrome.runtime.connect({ name: "notifications" }) } @@ -203,7 +206,7 @@ function registerAllIncomingConnections() { }); } -function registerOnNewMessage(cb: (message: any, sender: any, callback: any) => void) { +function listenToAllChannels(cb: (message: any, sender: any, callback: (r: CoreApiResponse) => void) => void) { chrome.runtime.onMessage.addListener((m, s, c) => { cb(m, s, c) @@ -311,11 +314,6 @@ function useServiceWorkerAsBackgroundProcess() { return chrome.runtime.getManifest().manifest_version === 3 } -function getLastError() { - return chrome.runtime.lastError?.message; -} - - function searchForTalerLinks(): string | undefined { let found; found = document.querySelector("a[href^='taler://'") diff --git a/packages/taler-wallet-webextension/src/platform/dev.ts b/packages/taler-wallet-webextension/src/platform/dev.ts new file mode 100644 index 000000000..a761bb014 --- /dev/null +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -0,0 +1,159 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { classifyTalerUri, CoreApiResponse, TalerUriType } from "@gnu-taler/taler-util"; +import { MessageFromBackend, PlatformAPI } from "./api"; + +const frames = ["popup", "wallet"] + +const api: PlatformAPI = ({ + isFirefox: () => false, + findTalerUriInActiveTab: async () => undefined, + getPermissionsApi: () => ({ + addPermissionsListener: () => undefined, contains: async () => true, remove: async () => false, request: async () => false + }), + getWalletVersion: () => ({ + version: 'none' + }), + notifyWhenAppIsReady: (fn: () => void) => { + let total = frames.length + function waitAndNotify() { + total-- + if (total < 1) { + console.log('done') + fn() + } + } + frames.forEach(f => { + const theFrame = window.frames[f as any] + if (theFrame.location.href === 'about:blank') { + waitAndNotify() + } else { + theFrame.addEventListener("load", waitAndNotify) + } + }) + }, + + openWalletPage: (page: string) => { + window.frames['wallet' as any].location = `/wallet.html#${page}` + }, + openWalletPageFromPopup: (page: string) => { + window.parent.frames['wallet' as any].location = `/wallet.html#${page}` + window.location.href = "about:blank" + }, + openWalletURIFromPopup: (page: string) => { + alert('openWalletURIFromPopup not implemented yet') + }, + redirectTabToWalletPage: (tabId: number, page: string) => { + alert('redirectTabToWalletPage not implemented yet') + }, + + registerAllIncomingConnections: () => undefined, + registerOnInstalled: (fn: () => void) => fn(), + registerReloadOnNewVersion: () => undefined, + registerTalerHeaderListener: () => undefined, + + useServiceWorkerAsBackgroundProcess: () => false, + + listenToAllChannels: (fn: (m: any, s: any, c: (r: CoreApiResponse) => void) => void) => { + window.addEventListener("message", (event: MessageEvent) => { + if (event.data.type !== 'command') return + const sender = event.data.header.replyMe + fn(event.data.body, sender, (resp: CoreApiResponse) => { + if (event.source) { + const msg: IframeMessageResponse = { + type: "response", + header: { responseId: sender }, + body: resp + } + window.parent.postMessage(msg) + } + }) + }) + }, + sendMessageToAllChannels: (message: MessageFromBackend) => { + Array.from(window.frames).forEach(w => { + try { + w.postMessage({ + header: {}, body: message + }); + } catch (e) { + console.error(e) + } + }) + }, + listenToWalletBackground: (onNewMessage: (m: MessageFromBackend) => void) => { + function listener(event: MessageEvent) { + if (event.data.type !== 'notification') return + onNewMessage(event.data.body) + } + window.parent.addEventListener("message", listener) + return () => { + window.parent.removeEventListener("message", listener) + } + }, + sendMessageToWalletBackground: async (operation: string, payload: any) => { + const replyMe = `reply-${Math.floor(Math.random() * 100000)}` + const message: IframeMessageCommand = { + type: 'command', + header: { replyMe }, + body: { operation, payload, id: "(none)" } + } + window.parent.postMessage(message) + + return new Promise((res, rej) => { + function listener(event: MessageEvent) { + if (event.data.type !== "response" || event.data.header.responseId !== replyMe) { + return + } + res(event.data.body) + window.parent.removeEventListener("message", listener) + } + window.parent.addEventListener("message", listener, { + + }) + }) + + }, +}) + +type IframeMessageType = IframeMessageNotification | IframeMessageResponse | IframeMessageCommand; +interface IframeMessageNotification { + type: "notification"; + header: { + }, + body: MessageFromBackend +} +interface IframeMessageResponse { + type: "response"; + header: { + responseId: string; + }, + body: CoreApiResponse +} + +interface IframeMessageCommand { + type: "command"; + header: { + replyMe: string; + }, + body: { + operation: any, id: string, payload: any + } +} + +export default api; + -- cgit v1.2.3