From 22b88b3cecf43594eddaf5771867e8ec88fba00e Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Mon, 29 Apr 2024 09:24:42 +0300 Subject: [PATCH 01/11] One time payment progress --- src/background/services/background.ts | 20 ++-- src/background/services/monetization.ts | 30 ++++-- src/background/services/openPayments.ts | 6 +- src/background/services/paymentSession.ts | 116 +++++++++++++++++++++- src/background/services/storage.ts | 19 +++- src/background/services/tabEvents.ts | 3 +- src/background/utils.ts | 4 +- src/manifest.json | 34 +++++-- src/popup/components/PayWebsiteForm.tsx | 11 +- src/popup/lib/utils.ts | 8 ++ src/popup/pages/Home.tsx | 57 +++++------ src/shared/messages.ts | 2 +- src/shared/types.ts | 4 +- 13 files changed, 240 insertions(+), 74 deletions(-) diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 9559a81f..1abf0e95 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -33,7 +33,10 @@ export class Background { bindTabHandlers() { this.browser.tabs.onRemoved.addListener(this.tabEvents.onRemovedTab) - // this.browser.tabs.onUpdated.addListener(this.tabEvents.onUpdatedTab) + // this.browser.webNavigation.onCommitted.addListener((details) => { + // console.log(details) + // }) + this.browser.tabs.onUpdated.addListener(this.tabEvents.onUpdatedTab) } bindMessageHandler() { @@ -58,11 +61,8 @@ export class Background { return case PopupToBackgroundAction.PAY_WEBSITE: - this.logger.debug( - PopupToBackgroundAction.PAY_WEBSITE, - message.payload - ) - throw new Error('Not implemented') + await this.monetizationService.pay(message.payload.amount) + return case ContentToBackgroundAction.CHECK_WALLET_ADDRESS_URL: return success( @@ -121,12 +121,4 @@ export class Background { } }) } - - bindOnTabActivated() { - // this.browser.tabs.onActivated.addListener() - } - - bindOnTabUpdated() { - // this.browser.tabs.onUpdated.addListener() - } } diff --git a/src/background/services/monetization.ts b/src/background/services/monetization.ts index 51940613..9fd6d84e 100644 --- a/src/background/services/monetization.ts +++ b/src/background/services/monetization.ts @@ -1,5 +1,5 @@ import { OpenPaymentsService, StorageService } from '.' -import { Runtime } from 'webextension-polyfill' +import { type Browser, type Runtime } from 'webextension-polyfill' import { Logger } from '@/shared/logger' import { ResumeMonetizationPayload, @@ -7,7 +7,7 @@ import { StopMonetizationPayload } from '@/shared/messages' import { PaymentSession } from './paymentSession' -import { getSender, getTabId } from '../utils' +import { getCurrentActiveTab, getSender, getTabId } from '../utils' export class MonetizationService { private sessions: { @@ -17,7 +17,8 @@ export class MonetizationService { constructor( private logger: Logger, private openPaymentsService: OpenPaymentsService, - private storage: StorageService + private storage: StorageService, + private browser: Browser ) { this.sessions = {} } @@ -26,13 +27,12 @@ export class MonetizationService { payload: StartMonetizationPayload, sender: Runtime.MessageSender ) { - // TODO: This is not ideal. We should not receive monetization events - // from the content script if WM is disabled or a wallet is not connected. + // TODO: This is not ideal. We should not receive monetization events + // from the content script if WM is disabled or a wallet is not connected. const { connected, enabled } = await this.storage.get([ 'enabled', 'connected' ]) - if (connected === false || enabled === false) return const { requestId, walletAddress } = payload const { tabId, frameId } = getSender(sender) @@ -61,7 +61,10 @@ export class MonetizationService { ) this.sessions[tabId].set(requestId, session) - void session.start() + + if (connected === true && enabled === true) { + void session.start() + } } stopPaymentSession( @@ -117,4 +120,17 @@ export class MonetizationService { delete this.sessions[tabId] this.logger.debug(`Cleared ${sessions.size} sessions for tab ${tabId}.`) } + + async pay(amount: number) { + const tab = await getCurrentActiveTab(this.browser) + if (!tab || !tab.id) return + + const sessions = this.sessions[tab.id] + const splitAmount = amount / sessions.size + + for (const session of sessions.values()) { + session.pay(splitAmount) + } + + } } diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index d217a3a5..4c03eb70 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -14,7 +14,7 @@ import { signMessage } from 'http-message-signatures/lib/httpbis' import { createContentDigestHeader } from 'httpbis-digest-headers' import { Browser } from 'webextension-polyfill' import { - getCurrentActiveTabId, + getCurrentActiveTab, getExchangeRates, getRateOfPay, toAmount @@ -388,7 +388,7 @@ export class OpenPaymentsService { } private async confirmPayment(url: string): Promise { - const currentTabId = await getCurrentActiveTabId(this.browser) + const currentTab = await getCurrentActiveTab(this.browser) return await new Promise((res) => { if (url) { @@ -401,7 +401,7 @@ export class OpenPaymentsService { const hash = tabUrl.searchParams.get('hash') if (tabId === tab.id && interactRef && hash) { - this.browser.tabs.update(currentTabId, { active: true }) + this.browser.tabs.update(currentTab.id, { active: true }) this.browser.tabs.remove(tab.id) res({ interactRef, hash }) } diff --git a/src/background/services/paymentSession.ts b/src/background/services/paymentSession.ts index 2c26a6c4..98d0fd95 100644 --- a/src/background/services/paymentSession.ts +++ b/src/background/services/paymentSession.ts @@ -1,5 +1,6 @@ import { OpenPaymentsService } from './openPayments' import { + IncomingPayment, OutgoingPayment, Quote, WalletAddress, @@ -53,7 +54,7 @@ export class PaymentSession { return } - await this.createIncomingPayment() + await this.setIncomingPaymentUrl() while (this.active) { let quote: Quote | undefined @@ -152,7 +153,12 @@ export class PaymentSession { } } - async createIncomingPayment() { + async setIncomingPaymentUrl() { + const incomingPayment = await this.createIncomingPayment() + this.incomingPaymentUrl = incomingPayment.id + } + + async createIncomingPayment(): Promise { const incomingPaymentGrant = await this.openPaymentsService.client!.grant.request( { @@ -163,7 +169,7 @@ export class PaymentSession { access: [ { type: 'incoming-payment', - actions: ['create', 'read', 'list'], + actions: ['create'], identifier: this.receiver.id } ] @@ -196,6 +202,108 @@ export class PaymentSession { accessToken: incomingPaymentGrant.continue.access_token.value }) - this.incomingPaymentUrl = incomingPayment.id + return incomingPayment + } + + // TODO: Needs refactoring - breaks DRY + async pay(amount: number) { + const incomingPayment = await this.createIncomingPayment() + const data = await this.storage.get(['token', 'walletAddress']) + + let token = data.token + const walletAddress = data.walletAddress + + if (token == null || walletAddress == null) { + return + } + + let quote: Quote | undefined + let outgoingPayment: OutgoingPayment | undefined + + try { + if (!quote) { + quote = await this.openPaymentsService.client!.quote.create( + { + url: walletAddress.resourceServer, + accessToken: token.value + }, + { + method: 'ilp', + receiver: incomingPayment.id, + walletAddress: walletAddress.id, + debitAmount: { + value: (amount * 10 ** walletAddress.assetScale).toFixed(0), + assetScale: walletAddress.assetScale, + assetCode: walletAddress.assetCode + } + } + ) + } + outgoingPayment = + await this.openPaymentsService.client!.outgoingPayment.create( + { + url: walletAddress.resourceServer, + accessToken: token.value + }, + { + walletAddress: walletAddress.id, + quoteId: quote.id, + metadata: { + source: 'Web Monetization' + } + } + ) + } catch (e) { + /** + * Unhandled exceptions: + * - Expired incoming payment: if the incoming payment is expired when + * trying to create a quote, create a new incoming payment + * + */ + if (e instanceof OpenPaymentsClientError) { + // Status code 403 -> expired access token + if (e.status === 403) { + const rotatedToken = + await this.openPaymentsService.client!.token.rotate({ + accessToken: token.value, + url: token.manage + }) + + token = { + value: rotatedToken.access_token.value, + manage: rotatedToken.access_token.manage + } + + void this.storage.set({ + token: { + value: rotatedToken.access_token.value, + manage: rotatedToken.access_token.manage + } + }) + } + + throw new Error(e.message) + } + } finally { + if (outgoingPayment) { + const { receiveAmount, receiver: incomingPayment } = outgoingPayment + + quote = undefined + outgoingPayment = undefined + + sendMonetizationEvent({ + tabId: this.tabId, + frameId: this.frameId, + payload: { + requestId: this.requestId, + details: { + receiveAmount, + incomingPayment, + paymentPointer: this.receiver.id + } + } + }) + } + } } } diff --git a/src/background/services/storage.ts b/src/background/services/storage.ts index fd525972..8dd5d92e 100644 --- a/src/background/services/storage.ts +++ b/src/background/services/storage.ts @@ -2,6 +2,7 @@ import { Logger } from '@/shared/logger' import type { PopupStore, Storage, StorageKey } from '@/shared/types' import EventEmitter from 'events' import { type Browser } from 'webextension-polyfill' +import { getCurrentActiveTab } from '../utils' const defaultStorage = { connected: false, @@ -55,6 +56,7 @@ export class StorageService extends EventEmitter { // TODO: Exception list (post-v1) - return data for the current website async getPopupData(): Promise { + let url: string | undefined const data = await this.get([ 'enabled', 'connected', @@ -66,7 +68,21 @@ export class StorageService extends EventEmitter { 'publicKey' ]) - return data + const tab = await getCurrentActiveTab(this.browser) + + if (tab && tab.url) { + try { + const tabUrl = new URL(tab.url) + if (tabUrl.protocol === 'https:') { + // Do not include search params + url = `${tabUrl.origin}${tabUrl.pathname}` + } + } catch (_) { + // noop + } + } + + return { ...data, url } } async keyPairExists(): Promise { @@ -84,5 +100,4 @@ export class StorageService extends EventEmitter { return false } - } diff --git a/src/background/services/tabEvents.ts b/src/background/services/tabEvents.ts index a5221705..abf672a9 100644 --- a/src/background/services/tabEvents.ts +++ b/src/background/services/tabEvents.ts @@ -10,7 +10,8 @@ export class TabEvents { // TODO: This is not ideal. Find a better way to clear the sessions for a specific tab. // When closing the tab, we receive the STOP_MONETIZATION message as well. // Maybe check if the tab is closed in the content script? - onUpdatedTab = (tabId: number) => { + onUpdatedTab = (tabId: number, ...rest: any) => { + console.log(tabId, rest) this.monetizationService.clearTabSessions(tabId) } } diff --git a/src/background/utils.ts b/src/background/utils.ts index d18806e2..9d3abb62 100644 --- a/src/background/utils.ts +++ b/src/background/utils.ts @@ -21,12 +21,12 @@ export const updateIcon = async (active: boolean) => { } } -export const getCurrentActiveTabId = async (browser: Browser) => { +export const getCurrentActiveTab = async (browser: Browser) => { const activeTabs = await browser.tabs.query({ active: true, currentWindow: true }) - return activeTabs[0].id + return activeTabs[0] } interface ToAmountParams { diff --git a/src/manifest.json b/src/manifest.json index 3d594c3f..a2433ee1 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -10,19 +10,37 @@ "default_locale": "en", "content_scripts": [ { - "matches": ["http://*/*", "https://*/*", ""], - "js": ["content/content.js"] + "matches": [ + "http://*/*", + "https://*/*", + "" + ], + "js": [ + "content/content.js" + ] }, { "run_at": "document_start", - "matches": ["http://*/*", "https://*/*"], - "js": ["contentStatic/contentStatic.js"] + "matches": [ + "http://*/*", + "https://*/*" + ], + "js": [ + "contentStatic/contentStatic.js" + ] } ], "background": { - "scripts": ["background/background.js"] + "scripts": [ + "background/background.js" + ] }, - "permissions": ["tabs", "storage", "https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json"], + "permissions": [ + "tabs", + "storage", + "webNavigation", + "https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json" + ], "browser_action": { "default_icon": "assets/icons/icon-34.png", "default_title": "Web Monetization", @@ -36,7 +54,9 @@ "background/*", "openapi/*" ], - "host_permissions": ["https://*"], + "host_permissions": [ + "https://*" + ], "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "browser_specific_settings": { "gecko": { diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index db20bff3..bc42d20d 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -2,7 +2,10 @@ import { Button } from '@/popup/components/ui/Button' import { Input } from '@/popup/components/ui/Input' import { PopupStateContext } from '@/popup/lib/context' import { payWebsite } from '@/popup/lib/messages' -import { getCurrencySymbol, charIsNumber } from '@/popup/lib/utils' +import { + getCurrencySymbol, + charIsNumber, +} from '@/popup/lib/utils' import React from 'react' import { useForm } from 'react-hook-form' import { numericFormatter } from 'react-number-format' @@ -13,7 +16,7 @@ interface PayWebsiteFormProps { export const PayWebsiteForm = () => { const { - state: { walletAddress } + state: { walletAddress, url } } = React.useContext(PopupStateContext) const { register, @@ -34,7 +37,7 @@ export const PayWebsiteForm = () => { addOn={getCurrencySymbol(walletAddress.assetCode)} label={
- Pay URL + Pay {url}
} placeholder="0.00" @@ -79,7 +82,7 @@ export const PayWebsiteForm = () => { loading={isSubmitting} aria-label="Connect your wallet" > - Connect + Pay ) diff --git a/src/popup/lib/utils.ts b/src/popup/lib/utils.ts index 610b45cf..4da3a1ee 100644 --- a/src/popup/lib/utils.ts +++ b/src/popup/lib/utils.ts @@ -1,3 +1,5 @@ +import browser from 'webextension-polyfill'; + export const getCurrencySymbol = (assetCode: string): string => { return new Intl.NumberFormat('en-US', { currency: assetCode, @@ -29,3 +31,9 @@ export function roundWithPrecision(num: number, precision: number) { const multiplier = Math.pow(10, precision) return Math.round(num * multiplier) / multiplier } + +export async function getCurrentTabURL(): Promise { + const tab = await browser.tabs.query({active: true, currentWindow: true}) + return tab[0].url +} + diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index f589726e..4abc5f28 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -5,6 +5,7 @@ import { Slider } from '../components/ui/Slider' import { updateRateOfPay } from '../lib/messages' import { Label } from '../components/ui/Label' import { getCurrencySymbol, roundWithPrecision } from '../lib/utils' +import { PayWebsiteForm } from '../components/PayWebsiteForm' export const Component = () => { const { @@ -13,7 +14,8 @@ export const Component = () => { rateOfPay, minRateOfPay, maxRateOfPay, - walletAddress + walletAddress, + url }, dispatch } = React.useContext(PopupStateContext) @@ -42,36 +44,35 @@ export const Component = () => { }) } - if (!enabled) { - return ( -
- -

- Web Monetization has been turned off. -

-
- ) - } - return (
-
- - -
- - {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour - + {!enabled ? ( +
+ +

+ Web Monetization has been turned off. +

+
+ ) : ( +
+ + +
+ + {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour test + +
-
+ )} + {url ? : null}
) } diff --git a/src/shared/messages.ts b/src/shared/messages.ts index a69f3211..472b27ce 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -38,7 +38,7 @@ export interface ConnectWalletPayload { } export interface PayWebsitePayload { - amount: string + amount: number } export interface UpdateRateOfPayPayload { diff --git a/src/shared/types.ts b/src/shared/types.ts index 2fd7ed7b..0bb07659 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -60,7 +60,9 @@ export type StorageKey = keyof Storage export type PopupStore = Omit< Storage, 'privateKey' | 'keyId' | 'exceptionList' | 'token' | 'grant' -> +> & { + url: string | undefined +} export type DeepNonNullable = { [P in keyof T]?: NonNullable From c8ffc73adb046a5ed6e74caa25d95648cb2f88f0 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Mon, 29 Apr 2024 21:08:41 +0300 Subject: [PATCH 02/11] Fix type errors --- src/background/services/background.ts | 3 -- src/background/services/monetization.ts | 7 +--- src/background/services/tabEvents.ts | 3 +- src/popup/components/PayWebsiteForm.tsx | 7 +--- src/popup/lib/messages.ts | 2 - src/popup/lib/utils.ts | 6 --- src/popup/pages/Home.tsx | 52 +++++++++++++------------ src/shared/messages.ts | 2 +- 8 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 1abf0e95..7737349d 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -33,9 +33,6 @@ export class Background { bindTabHandlers() { this.browser.tabs.onRemoved.addListener(this.tabEvents.onRemovedTab) - // this.browser.webNavigation.onCommitted.addListener((details) => { - // console.log(details) - // }) this.browser.tabs.onUpdated.addListener(this.tabEvents.onUpdatedTab) } diff --git a/src/background/services/monetization.ts b/src/background/services/monetization.ts index 9fd6d84e..cac34804 100644 --- a/src/background/services/monetization.ts +++ b/src/background/services/monetization.ts @@ -27,8 +27,6 @@ export class MonetizationService { payload: StartMonetizationPayload, sender: Runtime.MessageSender ) { - // TODO: This is not ideal. We should not receive monetization events - // from the content script if WM is disabled or a wallet is not connected. const { connected, enabled } = await this.storage.get([ 'enabled', 'connected' @@ -121,16 +119,15 @@ export class MonetizationService { this.logger.debug(`Cleared ${sessions.size} sessions for tab ${tabId}.`) } - async pay(amount: number) { + async pay(amount: string) { const tab = await getCurrentActiveTab(this.browser) if (!tab || !tab.id) return const sessions = this.sessions[tab.id] - const splitAmount = amount / sessions.size + const splitAmount = Number(amount) / sessions.size for (const session of sessions.values()) { session.pay(splitAmount) } - } } diff --git a/src/background/services/tabEvents.ts b/src/background/services/tabEvents.ts index abf672a9..a5221705 100644 --- a/src/background/services/tabEvents.ts +++ b/src/background/services/tabEvents.ts @@ -10,8 +10,7 @@ export class TabEvents { // TODO: This is not ideal. Find a better way to clear the sessions for a specific tab. // When closing the tab, we receive the STOP_MONETIZATION message as well. // Maybe check if the tab is closed in the content script? - onUpdatedTab = (tabId: number, ...rest: any) => { - console.log(tabId, rest) + onUpdatedTab = (tabId: number) => { this.monetizationService.clearTabSessions(tabId) } } diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index bc42d20d..0d38c5cb 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -2,10 +2,7 @@ import { Button } from '@/popup/components/ui/Button' import { Input } from '@/popup/components/ui/Input' import { PopupStateContext } from '@/popup/lib/context' import { payWebsite } from '@/popup/lib/messages' -import { - getCurrencySymbol, - charIsNumber, -} from '@/popup/lib/utils' +import { getCurrencySymbol, charIsNumber } from '@/popup/lib/utils' import React from 'react' import { useForm } from 'react-hook-form' import { numericFormatter } from 'react-number-format' @@ -57,7 +54,7 @@ export const PayWebsiteForm = () => { errorMessage={errors.amount?.message} {...register('amount', { required: { value: true, message: 'Amount is required.' }, - valueAsNumber: false, + valueAsNumber: true, onBlur: (e: React.FocusEvent) => { setValue( 'amount', diff --git a/src/popup/lib/messages.ts b/src/popup/lib/messages.ts index 7ba8e3b1..b159d406 100644 --- a/src/popup/lib/messages.ts +++ b/src/popup/lib/messages.ts @@ -1,6 +1,4 @@ import { - BackgroundToContentAction, - BackgroundToContentActionPayload, BackgroundToContentMessage, MessageManager, PopupToBackgroundAction, diff --git a/src/popup/lib/utils.ts b/src/popup/lib/utils.ts index 4da3a1ee..405fc19b 100644 --- a/src/popup/lib/utils.ts +++ b/src/popup/lib/utils.ts @@ -31,9 +31,3 @@ export function roundWithPrecision(num: number, precision: number) { const multiplier = Math.pow(10, precision) return Math.round(num * multiplier) / multiplier } - -export async function getCurrentTabURL(): Promise { - const tab = await browser.tabs.query({active: true, currentWindow: true}) - return tab[0].url -} - diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index 4abc5f28..fd9c4aaa 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -44,34 +44,36 @@ export const Component = () => { }) } + if (!enabled) { + return ( +
+ +

+ Web Monetization has been turned off. +

+
+ ) + } + return (
- {!enabled ? ( -
- -

- Web Monetization has been turned off. -

-
- ) : ( -
- - -
- - {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour test - -
+
+ + +
+ + {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour test +
- )} +
{url ? : null}
) diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 472b27ce..a69f3211 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -38,7 +38,7 @@ export interface ConnectWalletPayload { } export interface PayWebsitePayload { - amount: number + amount: string } export interface UpdateRateOfPayPayload { From e0516da4250e61e356468b130a395883b37efc77 Mon Sep 17 00:00:00 2001 From: Diana Fulga Date: Tue, 7 May 2024 14:38:25 +0300 Subject: [PATCH 03/11] feat: OTP Refine UI (#244) Refine UI Co-authored-by: Diana Fulga --- src/popup/components/PayWebsiteForm.tsx | 12 ++++++------ src/popup/pages/Home.tsx | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index 0d38c5cb..b38a76b6 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -27,15 +27,15 @@ export const PayWebsiteForm = () => { }) return ( -
+ - Pay {url} -
+

+ Pay {url} +

} placeholder="0.00" onKeyDown={(e) => { @@ -74,12 +74,12 @@ export const PayWebsiteForm = () => { /> ) diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index fd9c4aaa..67c3f286 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -56,7 +56,7 @@ export const Component = () => { } return ( -
+
+ +
+ {url ? : null}
) From a9fd9868fa3e85078821d2c1a9f99e197748317e Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Wed, 8 May 2024 14:10:34 +0300 Subject: [PATCH 04/11] OTP UI Progress --- src/background/services/background.ts | 5 ++- src/background/services/monetization.ts | 21 ++++++++- src/background/services/paymentSession.ts | 6 +-- src/popup/components/PayWebsiteForm.tsx | 13 ++++-- src/popup/pages/Home.tsx | 52 +++++++++++------------ src/shared/messages.ts | 1 + 6 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 7737349d..acdd73d4 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -58,8 +58,9 @@ export class Background { return case PopupToBackgroundAction.PAY_WEBSITE: - await this.monetizationService.pay(message.payload.amount) - return + return success( + await this.monetizationService.pay(message.payload.amount) + ) case ContentToBackgroundAction.CHECK_WALLET_ADDRESS_URL: return success( diff --git a/src/background/services/monetization.ts b/src/background/services/monetization.ts index cac34804..4a216796 100644 --- a/src/background/services/monetization.ts +++ b/src/background/services/monetization.ts @@ -8,6 +8,7 @@ import { } from '@/shared/messages' import { PaymentSession } from './paymentSession' import { getCurrentActiveTab, getSender, getTabId } from '../utils' +import { success } from '@/shared/helpers' export class MonetizationService { private sessions: { @@ -124,10 +125,28 @@ export class MonetizationService { if (!tab || !tab.id) return const sessions = this.sessions[tab.id] + + if (!sessions) { + throw new Error('This website is not monetized.') + } + + let totalSentAmount = BigInt(0) const splitAmount = Number(amount) / sessions.size + const promises = [] for (const session of sessions.values()) { - session.pay(splitAmount) + promises.push(session.pay(splitAmount)) } + + ;(await Promise.allSettled(promises)).forEach((p) => { + if (p.status === 'fulfilled') { + totalSentAmount += BigInt(p.value?.value ?? 0) + } + }) + + if (totalSentAmount === BigInt(0)) { + throw new Error('Could not facilitate payment for current website.') + } + } } diff --git a/src/background/services/paymentSession.ts b/src/background/services/paymentSession.ts index 98d0fd95..7e1d1eac 100644 --- a/src/background/services/paymentSession.ts +++ b/src/background/services/paymentSession.ts @@ -282,15 +282,11 @@ export class PaymentSession { }) } - throw new Error(e.message) } } finally { if (outgoingPayment) { const { receiveAmount, receiver: incomingPayment } = outgoingPayment - quote = undefined - outgoingPayment = undefined - sendMonetizationEvent({ tabId: this.tabId, frameId: this.frameId, @@ -305,5 +301,7 @@ export class PaymentSession { }) } } + + return outgoingPayment?.debitAmount } } diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index b38a76b6..930f8030 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -19,22 +19,27 @@ export const PayWebsiteForm = () => { register, formState: { errors, isSubmitting }, setValue, - handleSubmit + handleSubmit, + ...form } = useForm() const onSubmit = handleSubmit(async (data) => { - await payWebsite(data) + const response = await payWebsite(data) + if (!response.success) { + form.setError('root', { message: response.message }) + } }) return (
+ {errors.root ?

{errors.root.message}

: 'no message'} - Pay {url} +

+ Pay {url}

} placeholder="0.00" diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index 67c3f286..98417479 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -44,36 +44,34 @@ export const Component = () => { }) } - if (!enabled) { - return ( -
- -

- Web Monetization has been turned off. -

-
- ) - } - return (
-
- - -
- - {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour test - + {enabled ? ( +
+ + +
+ + {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour test + +
+
+ ) : ( +
+ +

+ Web Monetization has been turned off. +

-
+ )}
diff --git a/src/shared/messages.ts b/src/shared/messages.ts index a69f3211..0a100a0d 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -135,6 +135,7 @@ export type BackgroundToContentBackgroundMessage = { }[BackgroundToContentAction] export type ToContentMessage = BackgroundToContentBackgroundMessage + export class MessageManager { constructor(private browser: Browser) {} From b2cb005946c41dfef5ca73596086a866954829bb Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Wed, 8 May 2024 14:19:55 +0300 Subject: [PATCH 05/11] Update Open Payments SDK (local only) --- src/background/services/openPayments.ts | 42 ++++++++++++++++--------- webpack/plugins.ts | 4 +-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index 4c03eb70..dbc56cb7 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -189,34 +189,46 @@ export class OpenPaymentsService { this.client = await createAuthenticatedClient({ walletAddressUrl, - authenticatedRequestInterceptor: async (config) => { - if (!config.method || !config.url) { + authenticatedRequestInterceptor: async (request) => { + if (!request.method || !request.url) { throw new Error('Cannot intercept request: url or method missing') } + const initialRequest = request.clone() + const headers = await this.createHeaders({ request: { - method: config.method, - url: config.url, - headers: JSON.parse(JSON.stringify(config.headers)), - body: config.data ? JSON.stringify(config.data) : undefined + method: request.method, + url: request.url, + headers: JSON.parse( + JSON.stringify(Object.fromEntries(request.headers)) + ), + body: request.body + ? JSON.stringify(await request.json()) + : undefined }, privateKey: ed.etc.hexToBytes(privateKey), keyId }) - if (config.data) { - config.headers['Content-Type'] = headers['Content-Type'] - // Kept receiving console errors for setting unsafe header. - // Keeping this as a comment for now. - // config.headers['Content-Length'] = headers['Content-Length'] - config.headers['Content-Digest'] = headers['Content-Digest'] + if (request.body) { + initialRequest.headers.set( + 'Content-Type', + headers['Content-Type'] as string + ) + initialRequest.headers.set( + 'Content-Digest', + headers['Content-Digest'] as string + ) } - config.headers['Signature'] = headers['Signature'] - config.headers['Signature-Input'] = headers['Signature-Input'] + initialRequest.headers.set('Signature', headers['Signature']) + initialRequest.headers.set( + 'Signature-Input', + headers['Signature-Input'] + ) - return config + return initialRequest } }) } diff --git a/webpack/plugins.ts b/webpack/plugins.ts index f5e0b563..9653f89a 100644 --- a/webpack/plugins.ts +++ b/webpack/plugins.ts @@ -45,9 +45,9 @@ export const getMainPlugins = (outputDir: string, target: Target): any[] => [ { from: path.resolve( ROOT_DIR, - 'node_modules/@interledger/open-payments/dist/openapi' + 'node_modules/@interledger/open-payments/dist/openapi/specs' ), - to: path.resolve(ROOT_DIR, `${outputDir}/${target}/openapi`), + to: path.resolve(ROOT_DIR, `${outputDir}/${target}/specs`), globOptions: { ignore: ['**/generated/**'] } From 34d32a35b97f252f7f821e5d8deab55055867989 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Thu, 9 May 2024 17:00:00 +0300 Subject: [PATCH 06/11] One time payment UI --- package.json | 1 + pnpm-lock.yaml | 22 ++++++++++ src/background/services/monetization.ts | 2 - src/background/services/openPayments.ts | 1 + src/manifest.json | 2 +- src/popup/Popup.tsx | 10 +++-- src/popup/components/PayWebsiteForm.tsx | 55 +++++++++++++++++++++---- src/shared/messages.ts | 4 +- 8 files changed, 81 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index a45b6989..7196c3c7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.12.0", "events": "^3.3.0", + "framer-motion": "^11.1.9", "http-message-signatures": "^1.0.4", "httpbis-digest-headers": "^1.0.0", "loglevel": "^1.9.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb0989c3..fb47507c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ dependencies: events: specifier: ^3.3.0 version: 3.3.0 + framer-motion: + specifier: ^11.1.9 + version: 11.1.9(react-dom@18.2.0)(react@18.2.0) http-message-signatures: specifier: ^1.0.4 version: 1.0.4 @@ -4455,6 +4458,25 @@ packages: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true + /framer-motion@11.1.9(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-flECDIPV4QDNcOrDafVFiIazp8X01HFpzc01eDKJsdNH/wrATcYydJSH9JbPWMS8UD5lZlw+J1sK8LG2kICgqw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.2 + dev: false + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} diff --git a/src/background/services/monetization.ts b/src/background/services/monetization.ts index 4a216796..eba30f10 100644 --- a/src/background/services/monetization.ts +++ b/src/background/services/monetization.ts @@ -8,7 +8,6 @@ import { } from '@/shared/messages' import { PaymentSession } from './paymentSession' import { getCurrentActiveTab, getSender, getTabId } from '../utils' -import { success } from '@/shared/helpers' export class MonetizationService { private sessions: { @@ -147,6 +146,5 @@ export class MonetizationService { if (totalSentAmount === BigInt(0)) { throw new Error('Could not facilitate payment for current website.') } - } } diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index dbc56cb7..a79cfb59 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -188,6 +188,7 @@ export class OpenPaymentsService { const { privateKey, keyId } = await this.getPrivateKeyInformation() this.client = await createAuthenticatedClient({ + validateResponses: false, walletAddressUrl, authenticatedRequestInterceptor: async (request) => { if (!request.method || !request.url) { diff --git a/src/manifest.json b/src/manifest.json index a2433ee1..d3c6e30f 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -57,7 +57,7 @@ "host_permissions": [ "https://*" ], - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "content_security_policy": "script-src 'self'; object-src 'self'", "browser_specific_settings": { "gecko": { "id": "tech@interledger.com" diff --git a/src/popup/Popup.tsx b/src/popup/Popup.tsx index e7bd1d00..8fcae52d 100644 --- a/src/popup/Popup.tsx +++ b/src/popup/Popup.tsx @@ -1,6 +1,6 @@ import { MainLayout } from '@/popup/components/layout/MainLayout' import { PopupContextProvider } from './lib/context' - +import { LazyMotion, domAnimation } from 'framer-motion' import React from 'react' import { ProtectedRoute } from '@/popup/components/ProtectedRoute' import { @@ -43,8 +43,10 @@ const router = createMemoryRouter(routes) export const Popup = () => { return ( - - - + + + + + ) } diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index 930f8030..bb3854d3 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -3,15 +3,29 @@ import { Input } from '@/popup/components/ui/Input' import { PopupStateContext } from '@/popup/lib/context' import { payWebsite } from '@/popup/lib/messages' import { getCurrencySymbol, charIsNumber } from '@/popup/lib/utils' -import React from 'react' +import React, { useMemo } from 'react' import { useForm } from 'react-hook-form' import { numericFormatter } from 'react-number-format' +import { AnimatePresence, m } from 'framer-motion' +import { Spinner } from './Icons' +import { cn } from '@/shared/helpers' interface PayWebsiteFormProps { amount: string } +const BUTTON_STATE = { + idle: 'Send now', + loading: , + success: 'Payment successful' +} + export const PayWebsiteForm = () => { + const [buttonState, setButtonState] = + React.useState('idle') + const isIdle = useMemo(() => { + return buttonState === 'idle' + }, [buttonState]) const { state: { walletAddress, url } } = React.useContext(PopupStateContext) @@ -24,15 +38,26 @@ export const PayWebsiteForm = () => { } = useForm() const onSubmit = handleSubmit(async (data) => { + if (buttonState !== 'idle') return + + setButtonState('loading') + const response = await payWebsite(data) + if (!response.success) { + setButtonState('idle') form.setError('root', { message: response.message }) + } else { + setButtonState('success') + form.reset() + setTimeout(() => { + setButtonState('idle') + }, 2000) } }) return ( - {errors.root ?

{errors.root.message}

: 'no message'} { } })} /> + {errors.root ? ( + {errors.root.message} + ) : null} ) diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 0a100a0d..db66bdcc 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -139,9 +139,9 @@ export type ToContentMessage = BackgroundToContentBackgroundMessage export class MessageManager { constructor(private browser: Browser) {} - async send( + async send( message: TMessages - ): Promise> { + ): Promise> { return await this.browser.runtime.sendMessage(message) } From b46e34931db454c9ab42822776cf3ed7538054f4 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Thu, 9 May 2024 17:01:46 +0300 Subject: [PATCH 07/11] Update SDK version --- package.json | 2 +- pnpm-lock.yaml | 100 ++++++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/package.json b/package.json index 7196c3c7..74c805fd 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test:ci": "pnpm test -- --reporters=default --reporters=github-actions" }, "dependencies": { - "@interledger/open-payments": "^6.7.0", + "@interledger/open-payments": "^6.10.0", "@noble/ed25519": "^2.1.0", "@noble/hashes": "^1.4.0", "assert": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb47507c..707e6321 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@interledger/open-payments': - specifier: ^6.7.0 - version: 6.7.0 + specifier: ^6.10.0 + version: 6.10.0 '@noble/ed25519': specifier: ^2.1.0 version: 2.1.0 @@ -921,28 +921,27 @@ packages: uuid: 9.0.1 dev: false - /@interledger/open-payments@6.7.0: - resolution: {integrity: sha512-ibIpNHNZknhYWCy/SqUNWybwDkkhaM/H9lIXruEKsBogkh5l/ouXw78DrOYuzTTIb+3T4x6LlTJk/PGBht8S8Q==} + /@interledger/open-payments@6.10.0: + resolution: {integrity: sha512-iqkx7LfFdtT2BiN4GlHmcLq14J8O/eqREprALwdHIYUoL5O/ie3HpiUvrJRnR5zaIwnOXk9YLbHI39FMplfJRA==} dependencies: '@interledger/http-signature-utils': 2.0.2 - '@interledger/openapi': 1.2.1 - axios: 1.6.8 + '@interledger/openapi': 2.0.1 base64url: 3.0.1 http-message-signatures: 0.1.2 + ky: 1.2.4 pino: 8.19.0 uuid: 9.0.1 transitivePeerDependencies: - - debug - supports-color dev: false - /@interledger/openapi@1.2.1: - resolution: {integrity: sha512-CVEMjLH94svT5ikaojplgZ3YlZAe7ml6G6bt0ZCbqkbwHszUwY1E6RexZZejiGju5QvwS/6Jl/Op5ymv/ECaLg==} + /@interledger/openapi@2.0.1: + resolution: {integrity: sha512-eS5mzfb6sHa0M3YvlYl1ahlTQn7Jng2VF++agS5XeL93ZrFnSaniyARNdLU0r/tJcUQxKLVSCcoKn84mU9X+pg==} dependencies: '@apidevtools/json-schema-ref-parser': 10.1.0 - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - koa: 2.15.0 + ajv: 8.13.0 + ajv-formats: 2.1.1(ajv@8.13.0) + koa: 2.15.3 openapi-default-setter: 12.1.3 openapi-request-coercer: 12.1.3 openapi-request-validator: 12.1.3 @@ -1543,11 +1542,11 @@ packages: /@types/lodash.clonedeep@4.5.9: resolution: {integrity: sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==} dependencies: - '@types/lodash': 4.14.202 + '@types/lodash': 4.17.1 dev: false - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + /@types/lodash@4.17.1: + resolution: {integrity: sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==} dev: false /@types/minimatch@5.1.2: @@ -2108,6 +2107,18 @@ packages: optional: true dependencies: ajv: 8.12.0 + dev: true + + /ajv-formats@2.1.1(ajv@8.13.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.13.0 + dev: false /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} @@ -2142,6 +2153,16 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 + dev: true + + /ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: false /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} @@ -2390,6 +2411,7 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} @@ -2439,16 +2461,6 @@ packages: engines: {node: '>=4'} dev: true - /axios@1.6.8: - resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -2694,7 +2706,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: mime-types: 2.1.35 - ylru: 1.3.2 + ylru: 1.4.0 dev: false /call-bind@1.0.7: @@ -2896,6 +2908,7 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: true /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} @@ -3326,6 +3339,7 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dev: true /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -4410,16 +4424,6 @@ packages: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true - /follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: false - /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -4453,6 +4457,7 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -5928,8 +5933,8 @@ packages: koa-compose: 4.1.0 dev: false - /koa@2.15.0: - resolution: {integrity: sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==} + /koa@2.15.3: + resolution: {integrity: sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==} engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} dependencies: accepts: 1.3.8 @@ -5959,6 +5964,11 @@ packages: - supports-color dev: false + /ky@1.2.4: + resolution: {integrity: sha512-CfSrf4a0yj1n6WgPT6kQNQOopIGLkQzqSAXo05oKByaH7G3SiqW4a8jGox0p9whMXqO49H7ljgigivrMyycAVA==} + engines: {node: '>=18'} + dev: false + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -6397,8 +6407,8 @@ packages: /openapi-request-validator@12.1.3: resolution: {integrity: sha512-HW1sG00A9Hp2oS5g8CBvtaKvRAc4h5E4ksmuC5EJgmQ+eAUacL7g+WaYCrC7IfoQaZrjxDfeivNZUye/4D8pwA==} dependencies: - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) + ajv: 8.13.0 + ajv-formats: 2.1.1(ajv@8.13.0) content-type: 1.0.5 openapi-jsonschema-parameters: 12.1.3 openapi-types: 12.1.3 @@ -6408,7 +6418,7 @@ packages: /openapi-response-validator@12.1.3: resolution: {integrity: sha512-beZNb6r1SXAg1835S30h9XwjE596BYzXQFAEZlYAoO2imfxAu5S7TvNFws5k/MMKMCOFTzBXSjapqEvAzlblrQ==} dependencies: - ajv: 8.12.0 + ajv: 8.13.0 openapi-types: 12.1.3 dev: false @@ -6921,10 +6931,6 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false - /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true @@ -8708,8 +8714,8 @@ packages: buffer-crc32: 0.2.13 dev: true - /ylru@1.3.2: - resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} + /ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} engines: {node: '>= 4.0.0'} dev: false From 287f60ccb13dfd930da24861abdf12a48bb0b7db Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Fri, 10 May 2024 09:28:43 +0300 Subject: [PATCH 08/11] Display error message --- src/background/services/paymentSession.ts | 1 + src/popup/components/Icons.tsx | 20 +++++++++++++++++ src/popup/components/PayWebsiteForm.tsx | 27 ++++++++++++++++++----- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/background/services/paymentSession.ts b/src/background/services/paymentSession.ts index 7e1d1eac..7d6e3306 100644 --- a/src/background/services/paymentSession.ts +++ b/src/background/services/paymentSession.ts @@ -239,6 +239,7 @@ export class PaymentSession { } ) } + outgoingPayment = await this.openPaymentsService.client!.outgoingPayment.create( { diff --git a/src/popup/components/Icons.tsx b/src/popup/components/Icons.tsx index 60d29275..cc44fca9 100644 --- a/src/popup/components/Icons.tsx +++ b/src/popup/components/Icons.tsx @@ -181,3 +181,23 @@ export const CheckIcon = (props: React.SVGProps) => { ) } + +export const XIcon = (props: React.SVGProps) => { + return ( + + + + ) +} diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index bb3854d3..ae5be972 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -7,7 +7,7 @@ import React, { useMemo } from 'react' import { useForm } from 'react-hook-form' import { numericFormatter } from 'react-number-format' import { AnimatePresence, m } from 'framer-motion' -import { Spinner } from './Icons' +import { Spinner, XIcon } from './Icons' import { cn } from '@/shared/helpers' interface PayWebsiteFormProps { @@ -58,12 +58,32 @@ export const PayWebsiteForm = () => { return (
+ + {errors.root ? ( + +
+ + {errors.root.message} +
+
+ ) : null} +
+

Pay {url}

} @@ -102,9 +122,6 @@ export const PayWebsiteForm = () => { } })} /> - {errors.root ? ( - {errors.root.message} - ) : null}