From 1559718ad0e1c29c285067c02354768df1cce62e Mon Sep 17 00:00:00 2001 From: Sid Vishnoi <8426945+sidvishnoi@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:16:30 +0530 Subject: [PATCH] refactor(bg/openPayments): use `Promise.withResolvers` --- src/background/services/openPayments.ts | 88 +++++++++++++------------ src/shared/helpers.test.ts | 17 +++++ src/shared/helpers.ts | 14 ++++ 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index cb62cc5f..a2487506 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -25,7 +25,11 @@ import type { Browser, Tabs } from 'webextension-polyfill'; import { getExchangeRates, getRateOfPay, toAmount } from '../utils'; import { exportJWK, generateEd25519KeyPair } from '@/shared/crypto'; import { bytesToHex } from '@noble/hashes/utils'; -import { ErrorWithKey, getWalletInformation } from '@/shared/helpers'; +import { + ErrorWithKey, + getWalletInformation, + withResolvers, +} from '@/shared/helpers'; import { AddFundsPayload, ConnectWalletPayload } from '@/shared/messages'; import { DEFAULT_RATE_OF_PAY, @@ -609,48 +613,50 @@ export class OpenPaymentsService { } private async getInteractionInfo(url: string): Promise { - return await new Promise((resolve, reject) => { - this.browser.tabs.create({ url }).then((tab) => { - if (!tab.id) return; - const tabCloseListener: TabRemovedCallback = (tabId) => { - if (tabId !== tab.id) return; + const { resolve, reject, promise } = withResolvers(); + + const tab = await this.browser.tabs.create({ url }); + if (!tab.id) { + reject(new Error('Could not create tab')); + return promise; + } + + const tabCloseListener: TabRemovedCallback = (tabId) => { + if (tabId !== tab.id) return; + this.browser.tabs.onRemoved.removeListener(tabCloseListener); + reject(new ErrorWithKey('connectWallet_error_tabClosed')); + }; + + const getInteractionInfo: TabUpdateCallback = async (tabId, changeInfo) => { + if (tabId !== tab.id) return; + try { + const tabUrl = new URL(changeInfo.url || ''); + const interactRef = tabUrl.searchParams.get('interact_ref'); + const hash = tabUrl.searchParams.get('hash'); + const result = tabUrl.searchParams.get('result'); + + if ( + (interactRef && hash) || + result === 'grant_rejected' || + result === 'grant_invalid' + ) { + this.browser.tabs.onUpdated.removeListener(getInteractionInfo); this.browser.tabs.onRemoved.removeListener(tabCloseListener); - reject(new ErrorWithKey('connectWallet_error_tabClosed')); - }; - - const getInteractionInfo: TabUpdateCallback = async ( - tabId, - changeInfo, - ) => { - if (tabId !== tab.id) return; - try { - const tabUrl = new URL(changeInfo.url || ''); - const interactRef = tabUrl.searchParams.get('interact_ref'); - const hash = tabUrl.searchParams.get('hash'); - const result = tabUrl.searchParams.get('result'); - - if ( - (interactRef && hash) || - result === 'grant_rejected' || - result === 'grant_invalid' - ) { - this.browser.tabs.onUpdated.removeListener(getInteractionInfo); - this.browser.tabs.onRemoved.removeListener(tabCloseListener); - } - - if (interactRef && hash) { - resolve({ interactRef, hash, tabId }); - } - } catch { - /* do nothing */ - } - }; - - this.browser.tabs.onRemoved.addListener(tabCloseListener); - this.browser.tabs.onUpdated.addListener(getInteractionInfo); - }); - }); + } + + if (interactRef && hash) { + resolve({ interactRef, hash, tabId }); + } + } catch { + /* do nothing */ + } + }; + + this.browser.tabs.onRemoved.addListener(tabCloseListener); + this.browser.tabs.onUpdated.addListener(getInteractionInfo); + + return promise; } async disconnectWallet() { diff --git a/src/shared/helpers.test.ts b/src/shared/helpers.test.ts index 6c43a9e6..7a9a8674 100644 --- a/src/shared/helpers.test.ts +++ b/src/shared/helpers.test.ts @@ -4,6 +4,7 @@ import { objectEquals, removeQueryParams, getNextOccurrence, + withResolvers, } from './helpers'; describe('objectEquals', () => { @@ -34,6 +35,22 @@ describe('removeQueryParams', () => { }); }); +describe('withResolvers', () => { + it('resolves', async () => { + const r = withResolvers(); + r.resolve(true); + r.reject(false); + await expect(r.promise).resolves.toBe(true); + }); + + it('rejects', async () => { + const r = withResolvers(); + r.reject(false); + r.resolve(true); + await expect(r.promise).rejects.toBe(false); + }); +}); + describe('isOkState', () => { it('should return true if no state is set', () => { expect(isOkState({})).toBe(true); diff --git a/src/shared/helpers.ts b/src/shared/helpers.ts index af490c9f..3c146a9b 100644 --- a/src/shared/helpers.ts +++ b/src/shared/helpers.ts @@ -293,6 +293,20 @@ export const removeQueryParams = (urlString: string) => { return url.origin + url.pathname; }; +/** + * Polyfill for `Promise.withResolvers()` + */ +export function withResolvers() { + let resolve: (value: T | PromiseLike) => void; + let reject: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + // @ts-expect-error we know TypeScript! + return { resolve, reject, promise }; +} + export const isOkState = (state: Storage['state']) => { return Object.values(state).every((value) => value === false); };