From 65f543755907de6b4ebbb9eed4b1b9c2c74300d8 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Mon, 26 Aug 2024 11:22:56 +0300 Subject: [PATCH 1/2] add trailing comma --- .github/actions/constants.cjs | 6 +- .github/actions/delete-artifacts.cjs | 12 +- .github/actions/get-workflow-artifacts.cjs | 14 +- .github/actions/validate-stable-release.cjs | 2 +- .prettierrc.js | 6 +- esbuild/config.ts | 14 +- esbuild/dev.ts | 18 +- esbuild/plugins.ts | 44 ++--- esbuild/prod.ts | 22 +-- jest.config.ts | 10 +- scripts/build.ts | 12 +- src/background/container.ts | 20 +-- src/background/services/background.ts | 34 ++-- src/background/services/deduplicator.ts | 6 +- src/background/services/events.ts | 4 +- src/background/services/heartbeat.ts | 8 +- src/background/services/monetization.ts | 38 ++-- src/background/services/openPayments.ts | 170 +++++++++--------- src/background/services/paymentSession.ts | 87 ++++----- src/background/services/sendToPopup.ts | 4 +- src/background/services/storage.ts | 32 ++-- src/background/services/tabEvents.ts | 32 ++-- src/background/services/tabState.ts | 12 +- src/background/utils.test.ts | 10 +- src/background/utils.ts | 16 +- src/content/container.ts | 12 +- src/content/debug.ts | 4 +- src/content/messages.ts | 2 +- src/content/polyfill.ts | 12 +- src/content/services/contentScript.ts | 8 +- src/content/services/frameManager.ts | 40 ++--- .../services/monetizationTagManager.ts | 56 +++--- src/content/utils.ts | 6 +- src/popup/Popup.tsx | 32 ++-- src/popup/components/ConnectWalletForm.tsx | 34 ++-- src/popup/components/ErrorKeyRevoked.tsx | 10 +- src/popup/components/ErrorMessage.tsx | 4 +- src/popup/components/LoadingSpinner.tsx | 8 +- src/popup/components/OutOfFunds.tsx | 26 +-- src/popup/components/PayWebsiteForm.tsx | 14 +- src/popup/components/WalletInformation.tsx | 2 +- src/popup/components/WarningMessage.tsx | 2 +- src/popup/components/layout/Header.tsx | 2 +- src/popup/components/ui/Button.tsx | 24 +-- src/popup/components/ui/Code.tsx | 2 +- src/popup/components/ui/Input.tsx | 18 +- src/popup/components/ui/Label.tsx | 4 +- src/popup/components/ui/RadioGroup.tsx | 16 +- src/popup/components/ui/Slider.tsx | 2 +- src/popup/components/ui/Switch.tsx | 16 +- .../components/ui/__tests__/Button.test.tsx | 12 +- .../components/ui/__tests__/Code.test.tsx | 4 +- src/popup/components/ui/__tests__/Input.tsx | 8 +- .../components/ui/__tests__/RadioGroup.tsx | 20 +-- src/popup/components/ui/__tests__/Switch.tsx | 2 +- src/popup/lib/context.tsx | 10 +- src/popup/lib/hooks.test.tsx | 8 +- src/popup/lib/hooks.ts | 2 +- src/popup/lib/utils.test.ts | 2 +- src/popup/lib/utils.ts | 6 +- src/popup/pages/ErrorKeyRevoked.tsx | 8 +- src/popup/pages/Home.tsx | 14 +- src/popup/pages/OutOfFunds.tsx | 2 +- src/popup/pages/OutOfFunds_AddFunds.tsx | 2 +- src/shared/crypto.ts | 2 +- src/shared/helpers.test.ts | 42 ++--- src/shared/helpers.ts | 28 +-- src/shared/logger.ts | 2 +- src/shared/messages.ts | 14 +- tailwind.config.ts | 20 +-- 70 files changed, 601 insertions(+), 596 deletions(-) diff --git a/.github/actions/constants.cjs b/.github/actions/constants.cjs index 0c499cc7..4666ed45 100644 --- a/.github/actions/constants.cjs +++ b/.github/actions/constants.cjs @@ -10,7 +10,7 @@ const BADGE = const BROWSERS = ['chrome', 'firefox'] const COLORS = { green: '3fb950', - red: 'd73a49' + red: 'd73a49', } const TEMPLATE_VARS = { tableBody: '{{ TABLE_BODY }}', @@ -18,12 +18,12 @@ const TEMPLATE_VARS = { conclusion: '{{ CONCLUSION }}', badgeColor: '{{ BADGE_COLOR }}', badgeLabel: '{{ BADGE_LABEL }}', - jobLogs: '{{ JOB_LOGS }}' + jobLogs: '{{ JOB_LOGS }}', } module.exports = { BADGE, BROWSERS, COLORS, - TEMPLATE_VARS + TEMPLATE_VARS, } diff --git a/.github/actions/delete-artifacts.cjs b/.github/actions/delete-artifacts.cjs index 7cdf1fe9..e7fbb48e 100644 --- a/.github/actions/delete-artifacts.cjs +++ b/.github/actions/delete-artifacts.cjs @@ -10,7 +10,7 @@ async function getBrowserArtifacts({ github, context }, name) { const result = await github.rest.actions.listArtifactsForRepo({ owner: context.repo.owner, repo: context.repo.repo, - name + name, }) return result.data.artifacts } @@ -22,8 +22,8 @@ async function getBrowserArtifacts({ github, context }, name) { async function getPRArtifacts({ github, context }, prNumber) { const data = await Promise.all( BROWSERS.map((browser) => - getBrowserArtifacts({ github, context }, `${prNumber}-${browser}`) - ) + getBrowserArtifacts({ github, context }, `${prNumber}-${browser}`), + ), ) /** @type {{id: number}[]} */ @@ -52,9 +52,9 @@ module.exports = async ({ github, context, core }) => { github.rest.actions.deleteArtifact({ owner, repo, - artifact_id: artifact.id - }) - ) + artifact_id: artifact.id, + }), + ), ) console.log(`Deleted ${artifacts.length} artifacts for PR #${prNumber}.`) diff --git a/.github/actions/get-workflow-artifacts.cjs b/.github/actions/get-workflow-artifacts.cjs index cd89b196..487bbe18 100644 --- a/.github/actions/get-workflow-artifacts.cjs +++ b/.github/actions/get-workflow-artifacts.cjs @@ -12,13 +12,13 @@ const ARTIFACTS_DATA = { chrome: { name: 'Chrome', url: '', - size: '' + size: '', }, firefox: { name: 'Firefox', url: '', - size: '' - } + size: '', + }, } /** @@ -57,7 +57,7 @@ module.exports = async ({ github, context, core }) => { const jobLogsUrl = `${baseUrl}/actions/runs/${context.payload.workflow_run.id}` const template = await fs.readFile( './.github/actions/templates/build-status.md', - 'utf8' + 'utf8', ) /** @type {string[]} */ @@ -72,7 +72,7 @@ module.exports = async ({ github, context, core }) => { const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, - run_id: runId + run_id: runId, }) artifacts.data.artifacts.forEach((artifact) => { @@ -87,12 +87,12 @@ module.exports = async ({ github, context, core }) => { if (!url && !size) { const badgeUrl = getBadge('failure', COLORS.red, name) tableRows.push( - `${badgeUrl}N/A` + `${badgeUrl}N/A`, ) } else { const badgeUrl = getBadge('success', COLORS.green, `${name} (${size})`) tableRows.push( - `${badgeUrl}Download` + `${badgeUrl}Download`, ) } }) diff --git a/.github/actions/validate-stable-release.cjs b/.github/actions/validate-stable-release.cjs index d3f3ebd0..8be80fde 100644 --- a/.github/actions/validate-stable-release.cjs +++ b/.github/actions/validate-stable-release.cjs @@ -24,7 +24,7 @@ module.exports = async ({ github, context }) => { await github.rest.repos.getReleaseByTag({ owner, repo, - tag: versionTag + tag: versionTag, }) throw new Error('Release already promoted to stable') } catch (error) { diff --git a/.prettierrc.js b/.prettierrc.js index 43ce286a..5a0b04e4 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,6 @@ module.exports = { singleQuote: true, - trailingComma: 'none', + trailingComma: 'all', jsxSingleQuote: false, semi: false, plugins: ['prettier-plugin-tailwindcss'], @@ -13,6 +13,6 @@ module.exports = { 'twStyle', 'twMerge', 'twJoin', - 'cn' - ] + 'cn', + ], } diff --git a/esbuild/config.ts b/esbuild/config.ts index 5f99811c..ed68b036 100644 --- a/esbuild/config.ts +++ b/esbuild/config.ts @@ -22,20 +22,20 @@ export const options: BuildOptions = { entryPoints: [ { in: path.join(SRC_DIR, 'background', 'index.ts'), - out: path.join('background', 'background') + out: path.join('background', 'background'), }, { in: path.join(SRC_DIR, 'content', 'index.ts'), - out: path.join('content', 'content') + out: path.join('content', 'content'), }, { in: path.join(SRC_DIR, 'content', 'polyfill.ts'), - out: path.join('polyfill', 'polyfill') + out: path.join('polyfill', 'polyfill'), }, { in: path.join(SRC_DIR, 'popup', 'index.tsx'), - out: path.join('popup', 'popup') - } + out: path.join('popup', 'popup'), + }, ], bundle: true, legalComments: 'none', @@ -44,7 +44,7 @@ export const options: BuildOptions = { format: 'iife', write: true, logLevel: 'info', - treeShaking: true + treeShaking: true, } export type WebExtensionManifest = Manifest.WebExtensionManifest & { @@ -53,5 +53,5 @@ export type WebExtensionManifest = Manifest.WebExtensionManifest & { export const SERVE_PORTS: Record = { chrome: 7000, - firefox: 7002 + firefox: 7002, } diff --git a/esbuild/dev.ts b/esbuild/dev.ts index d5b7bf46..f118a802 100644 --- a/esbuild/dev.ts +++ b/esbuild/dev.ts @@ -7,7 +7,7 @@ import { typecheckPlugin } from '@jgoz/esbuild-plugin-typecheck' export const getDevOptions = ({ outDir, target, - channel + channel, }: Omit & { outDir: string }): BuildOptions => { @@ -17,19 +17,19 @@ export const getDevOptions = ({ minify: false, plugins: getPlugins({ outDir, dev: true, target, channel }).concat([ typecheckPlugin({ buildMode: 'readonly', watch: true }), - liveReloadPlugin({ target }) + liveReloadPlugin({ target }), ]), define: { NODE_ENV: JSON.stringify('development'), CONFIG_LOG_LEVEL: JSON.stringify('DEBUG'), CONFIG_PERMISSION_HOSTS: JSON.stringify({ - origins: ['http://*/*', 'https://*/*'] + origins: ['http://*/*', 'https://*/*'], }), CONFIG_ALLOWED_PROTOCOLS: JSON.stringify(['http:', 'https:']), CONFIG_OPEN_PAYMENTS_REDIRECT_URL: JSON.stringify( - 'https://webmonetization.org/welcome' - ) - } + 'https://webmonetization.org/welcome', + ), + }, } } @@ -72,7 +72,7 @@ function liveReloadPlugin({ target }: { target: Target }): ESBuildPlugin { const contents = await readFile(args.path, 'utf8') return { contents: reloadScriptBackground + '\n' + contents, - loader: 'ts' as const + loader: 'ts' as const, } }) @@ -80,9 +80,9 @@ function liveReloadPlugin({ target }: { target: Target }): ESBuildPlugin { const contents = await readFile(args.path, 'utf8') return { contents: contents + '\n\n\n' + reloadScriptPopup, - loader: 'tsx' as const + loader: 'tsx' as const, } }) - } + }, } } diff --git a/esbuild/plugins.ts b/esbuild/plugins.ts index 73cdfa3b..5cf652c0 100644 --- a/esbuild/plugins.ts +++ b/esbuild/plugins.ts @@ -11,14 +11,14 @@ import { SRC_DIR, ROOT_DIR, type BuildArgs, - type WebExtensionManifest + type WebExtensionManifest, } from './config' export const getPlugins = ({ outDir, target, channel, - dev + dev, }: BuildArgs & { outDir: string }): ESBuildPlugin[] => { @@ -33,36 +33,36 @@ export const getPlugins = ({ name: 'crypto-for-extension', setup(build) { build.onResolve({ filter: /^crypto$/ }, () => ({ - path: require.resolve('crypto-browserify') + path: require.resolve('crypto-browserify'), })) - } + }, }, ignorePackagePlugin([/@apidevtools[/|\\]json-schema-ref-parser/]), esbuildStylePlugin({ extract: true, postcss: { - plugins: [tailwind, autoprefixer] - } + plugins: [tailwind, autoprefixer], + }, }), copy({ resolveFrom: ROOT_DIR, assets: [ { from: path.join(SRC_DIR, 'popup', 'index.html'), - to: path.join(outDir, 'popup', 'index.html') + to: path.join(outDir, 'popup', 'index.html'), }, { from: path.join(SRC_DIR, '_locales/**/*'), - to: path.join(outDir, '_locales') + to: path.join(outDir, '_locales'), }, { from: path.join(SRC_DIR, 'assets/**/*'), - to: path.join(outDir, 'assets') - } + to: path.join(outDir, 'assets'), + }, ], - watch: dev + watch: dev, }), - processManifestPlugin({ outDir, dev, target, channel }) + processManifestPlugin({ outDir, dev, target, channel }), ] } @@ -73,7 +73,7 @@ function ignorePackagePlugin(ignores: RegExp[]): ESBuildPlugin { setup(build) { build.onResolve({ filter: /.*/, namespace: 'ignore' }, (args) => ({ path: args.path, - namespace: 'ignore' + namespace: 'ignore', })) for (const ignorePattern of ignores) { build.onResolve({ filter: ignorePattern }, (args) => { @@ -82,9 +82,9 @@ function ignorePackagePlugin(ignores: RegExp[]): ESBuildPlugin { } build.onLoad({ filter: /.*/, namespace: 'ignore' }, () => ({ - contents: '' + contents: '', })) - } + }, } } @@ -92,7 +92,7 @@ function processManifestPlugin({ outDir, target, channel, - dev + dev, }: BuildArgs & { outDir: string }): ESBuildPlugin { return { name: 'process-manifest', @@ -102,7 +102,7 @@ function processManifestPlugin({ const dest = path.join(outDir, 'manifest.json') const json = JSON.parse( - await fs.readFile(src, 'utf8') + await fs.readFile(src, 'utf8'), ) as WebExtensionManifest // Transform manifest as targets have different expectations // @ts-expect-error Only for IDE. No target accepts it @@ -114,7 +114,7 @@ function processManifestPlugin({ const [year, month, day] = [ now.getFullYear(), now.getMonth() + 1, - now.getDate() + now.getDate(), ] json.version = `${year}.${month}.${day}` if (target !== 'firefox') { @@ -145,7 +145,7 @@ function processManifestPlugin({ if (target === 'firefox') { // @ts-expect-error Firefox doesn't support Service Worker in MV3 yet json.background = { - scripts: [json.background.service_worker] + scripts: [json.background.service_worker], } json.content_scripts?.forEach((contentScript) => { // TODO: Remove this when Firefox supports `world` - at least last 10 @@ -159,7 +159,7 @@ function processManifestPlugin({ await fs.writeFile(dest, JSON.stringify(json, null, 2)) }) - } + }, } } @@ -169,9 +169,9 @@ function cleanPlugin(dirs: string[]): ESBuildPlugin { setup(build) { build.onStart(async () => { await Promise.all( - dirs.map((dir) => fs.rm(dir, { recursive: true, force: true })) + dirs.map((dir) => fs.rm(dir, { recursive: true, force: true })), ) }) - } + }, } } diff --git a/esbuild/prod.ts b/esbuild/prod.ts index e6c8a32c..d5207804 100644 --- a/esbuild/prod.ts +++ b/esbuild/prod.ts @@ -11,7 +11,7 @@ import { typecheckPlugin } from '@jgoz/esbuild-plugin-typecheck' export const getProdOptions = ({ outDir, target, - channel + channel, }: Omit & { outDir: string }): BuildOptions => { @@ -22,7 +22,7 @@ export const getProdOptions = ({ plugins: getPlugins({ outDir, dev: false, target, channel }).concat([ typecheckPlugin({ buildMode: 'readonly' }), preservePolyfillClassNamesPlugin({ outDir }), - zipPlugin({ outDir, target, channel }) + zipPlugin({ outDir, target, channel }), ]), define: { NODE_ENV: JSON.stringify('production'), @@ -30,16 +30,16 @@ export const getProdOptions = ({ CONFIG_PERMISSION_HOSTS: JSON.stringify({ origins: ['https://*/*'] }), CONFIG_ALLOWED_PROTOCOLS: JSON.stringify(['https:']), CONFIG_OPEN_PAYMENTS_REDIRECT_URL: JSON.stringify( - 'https://webmonetization.org/welcome' - ) - } + 'https://webmonetization.org/welcome', + ), + }, } } function zipPlugin({ outDir, target, - channel + channel, }: { channel: Channel target: Target @@ -50,7 +50,7 @@ function zipPlugin({ setup(build) { build.onEnd(async () => { const manifest = JSON.parse( - await fs.readFile(path.join(outDir, 'manifest.json'), 'utf8') + await fs.readFile(path.join(outDir, 'manifest.json'), 'utf8'), ) as WebExtensionManifest let zipName = `${target}-${manifest.version}.zip` @@ -70,7 +70,7 @@ function zipPlugin({ archive.glob('**/*', { cwd: outDir, ignore: ['meta.json'] }) await archive.finalize() }) - } + }, } } @@ -78,7 +78,7 @@ function zipPlugin({ * Unmangles the MonetizationEvent class */ function preservePolyfillClassNamesPlugin({ - outDir + outDir, }: { outDir: string }): ESBuildPlugin { @@ -101,13 +101,13 @@ function preservePolyfillClassNamesPlugin({ .replace(definitionRegex, `class MonetizationEvent extends Event`) .replace( `window.MonetizationEvent=${minifiedName}`, - `window.MonetizationEvent=MonetizationEvent` + `window.MonetizationEvent=MonetizationEvent`, ) .replaceAll(`new ${minifiedName}`, 'new MonetizationEvent') await fs.writeFile(polyfillPath, result) }) - } + }, } } diff --git a/jest.config.ts b/jest.config.ts index f911435f..b0dfb36a 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -7,25 +7,25 @@ export default { '!src/**/*.css', '!src/**/*.svg', '!src/**/*.d.ts', - '!src/**/index.ts' + '!src/**/index.ts', ], coverageDirectory: 'coverage', coverageProvider: 'v8', maxWorkers: '50%', moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], moduleNameMapper: { - '@/(.*)': '/src/$1' + '@/(.*)': '/src/$1', }, setupFilesAfterEnv: ['./jest.setup.ts'], testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'], testEnvironment: 'jsdom', testPathIgnorePatterns: [ '/node_modules/', - '/jest.config.ts' + '/jest.config.ts', ], transform: { '^.+\\.(js|jsx)$': 'babel-jest', '^.+\\.(ts|tsx)?$': 'ts-jest', - '\\.(css|less|scss|sass|svg)$': 'jest-transform-stub' - } + '\\.(css|less|scss|sass|svg)$': 'jest-transform-stub', + }, } diff --git a/scripts/build.ts b/scripts/build.ts index 8365cb9a..f8d4fb0e 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -13,7 +13,7 @@ import { options, SERVE_PORTS, Target, - TARGETS + TARGETS, } from '../esbuild/config' import { getDevOptions } from '../esbuild/dev' import { getProdOptions } from '../esbuild/prod' @@ -46,7 +46,7 @@ sade('build [target]', true) } console.log( - `Building target: "${options.target}" with channel: "${options.channel}"` + `Building target: "${options.target}" with channel: "${options.channel}"`, ) return options.dev ? buildWatch(options) : build(options) }) @@ -57,13 +57,13 @@ async function build({ target, channel }: BuildArgs) { const result = await esbuild.build({ ...options, ...getProdOptions({ outDir: OUTPUT_DIR, target, channel }), - outdir: OUTPUT_DIR + outdir: OUTPUT_DIR, }) if (result.metafile) { fs.writeFileSync( path.join(OUTPUT_DIR, 'meta.json'), - JSON.stringify(result.metafile) + JSON.stringify(result.metafile), ) } } @@ -73,14 +73,14 @@ async function buildWatch({ target, channel }: BuildArgs) { const ctx = await esbuild.context({ ...options, ...getDevOptions({ outDir: OUTPUT_DIR, target, channel }), - outdir: OUTPUT_DIR + outdir: OUTPUT_DIR, }) try { await ctx.serve({ host: 'localhost', port: SERVE_PORTS[target], - servedir: OUTPUT_DIR + servedir: OUTPUT_DIR, }) } catch (error) { console.log(error.message) diff --git a/src/background/container.ts b/src/background/container.ts index f88c001e..b2c74bfc 100644 --- a/src/background/container.ts +++ b/src/background/container.ts @@ -10,14 +10,14 @@ import { SendToPopup, EventsService, Heartbeat, - Deduplicator + Deduplicator, } from './services' import { createLogger, Logger } from '@/shared/logger' import { LOG_LEVEL } from '@/shared/defines' import { tFactory, type Translation } from '@/shared/helpers' import { MessageManager, - type BackgroundToContentMessage + type BackgroundToContentMessage, } from '@/shared/messages' export interface Cradle { @@ -39,7 +39,7 @@ export interface Cradle { export const configureContainer = () => { const container = createContainer({ - injectionMode: InjectionMode.PROXY + injectionMode: InjectionMode.PROXY, }) const logger = createLogger(LOG_LEVEL) @@ -52,22 +52,22 @@ export const configureContainer = () => { deduplicator: asClass(Deduplicator) .singleton() .inject(() => ({ - logger: logger.getLogger('deduplicator') + logger: logger.getLogger('deduplicator'), })), storage: asClass(StorageService) .singleton() .inject(() => ({ - logger: logger.getLogger('storage') + logger: logger.getLogger('storage'), })), openPaymentsService: asClass(OpenPaymentsService) .singleton() .inject(() => ({ - logger: logger.getLogger('open-payments') + logger: logger.getLogger('open-payments'), })), monetizationService: asClass(MonetizationService) .singleton() .inject(() => ({ - logger: logger.getLogger('monetization') + logger: logger.getLogger('monetization'), })), message: asClass(MessageManager).singleton(), tabEvents: asClass(TabEvents).singleton(), @@ -75,14 +75,14 @@ export const configureContainer = () => { background: asClass(Background) .singleton() .inject(() => ({ - logger: logger.getLogger('main') + logger: logger.getLogger('main'), })), tabState: asClass(TabState) .singleton() .inject(() => ({ - logger: logger.getLogger('tab-state') + logger: logger.getLogger('tab-state'), })), - heartbeat: asClass(Heartbeat).singleton() + heartbeat: asClass(Heartbeat).singleton(), }) return container diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 5919e11c..0c6c19dd 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -4,7 +4,7 @@ import { failure, getNextOccurrence, getWalletInformation, - success + success, } from '@/shared/helpers' import { OpenPaymentsClientError } from '@interledger/open-payments/dist/client/error' import { getCurrentActiveTab, OPEN_PAYMENTS_ERRORS } from '@/background/utils' @@ -34,7 +34,7 @@ export class Background { tabEvents, sendToPopup, events, - heartbeat + heartbeat, }: Cradle) { Object.assign(this, { browser, @@ -45,7 +45,7 @@ export class Background { tabEvents, logger, events, - heartbeat + heartbeat, }) } @@ -76,7 +76,7 @@ export class Background { const renewDate = getNextOccurrence(recurringGrant.amount.interval) this.browser.alarms.create(ALARM_RESET_OUT_OF_FUNDS, { - when: renewDate.valueOf() + when: renewDate.valueOf(), }) const resetOutOfFundsState: AlarmCallback = (alarm) => { if (alarm.name !== ALARM_RESET_OUT_OF_FUNDS) return @@ -89,7 +89,7 @@ export class Background { bindWindowHandlers() { this.browser.windows.onFocusChanged.addListener(async () => { const windows = await this.browser.windows.getAll({ - windowTypes: ['normal', 'panel', 'popup'] + windowTypes: ['normal', 'panel', 'popup'], }) windows.forEach(async (w) => { const activeTab = ( @@ -103,14 +103,14 @@ export class Background { if (w.focused) { this.logger.debug( - `Trying to resume monetization for window=${w.id}, activeTab=${activeTab.id} (URL: ${activeTab.url})` + `Trying to resume monetization for window=${w.id}, activeTab=${activeTab.id} (URL: ${activeTab.url})`, ) void this.monetizationService.resumePaymentSessionsByTabId( - activeTab.id + activeTab.id, ) } else { this.logger.debug( - `Trying to pause monetization for window=${w.id}, activeTab=${activeTab.id} (URL: ${activeTab.url})` + `Trying to pause monetization for window=${w.id}, activeTab=${activeTab.id} (URL: ${activeTab.url})`, ) void this.monetizationService.stopPaymentSessionsByTabId(activeTab.id) } @@ -172,12 +172,12 @@ export class Background { case 'UPDATE_RATE_OF_PAY': return success( - await this.storage.updateRate(message.payload.rateOfPay) + await this.storage.updateRate(message.payload.rateOfPay), ) case 'PAY_WEBSITE': return success( - await this.monetizationService.pay(message.payload.amount) + await this.monetizationService.pay(message.payload.amount), ) // endregion @@ -185,27 +185,27 @@ export class Background { // region Content case 'CHECK_WALLET_ADDRESS_URL': return success( - await getWalletInformation(message.payload.walletAddressUrl) + await getWalletInformation(message.payload.walletAddressUrl), ) case 'START_MONETIZATION': await this.monetizationService.startPaymentSession( message.payload, - sender + sender, ) return case 'STOP_MONETIZATION': await this.monetizationService.stopPaymentSession( message.payload, - sender + sender, ) return case 'RESUME_MONETIZATION': await this.monetizationService.resumePaymentSession( message.payload, - sender + sender, ) return @@ -225,7 +225,7 @@ export class Background { this.logger.error(message.action, e.message) return failure(e.message) } - } + }, ) } @@ -252,7 +252,7 @@ export class Background { }) this.events.on('storage.balance_update', (balance) => - this.sendToPopup.send('SET_BALANCE', balance) + this.sendToPopup.send('SET_BALANCE', balance), ) } @@ -268,7 +268,7 @@ export class Background { if (migrated) { const prevVersion = data.version ?? 1 this.logger.info( - `Migrated from ${prevVersion} to ${migrated.version}` + `Migrated from ${prevVersion} to ${migrated.version}`, ) } } diff --git a/src/background/services/deduplicator.ts b/src/background/services/deduplicator.ts index b5856e55..40d05b20 100644 --- a/src/background/services/deduplicator.ts +++ b/src/background/services/deduplicator.ts @@ -22,7 +22,7 @@ export class Deduplicator { dedupe>( fn: T, - { cacheFnArgs = false, wait = 5000 }: Partial = {} + { cacheFnArgs = false, wait = 5000 }: Partial = {}, ): T { return ((...args: Parameters): ReturnType => { const key = this.generateCacheKey(fn, args, cacheFnArgs) @@ -30,7 +30,7 @@ export class Deduplicator { if (entry) { this.logger.debug( - `Deduplicating function=${fn.name}, ${cacheFnArgs ? 'args=' + JSON.stringify(args) : 'without args'}` + `Deduplicating function=${fn.name}, ${cacheFnArgs ? 'args=' + JSON.stringify(args) : 'without args'}`, ) return entry.promise as ReturnType } @@ -55,7 +55,7 @@ export class Deduplicator { private generateCacheKey( fn: AsyncFn, args: any[], - cacheFnArgs: boolean + cacheFnArgs: boolean, ): string { let key = fn.name if (cacheFnArgs) { diff --git a/src/background/services/events.ts b/src/background/services/events.ts index bd4e12f8..8116ba22 100644 --- a/src/background/services/events.ts +++ b/src/background/services/events.ts @@ -24,14 +24,14 @@ export class EventsService extends EventEmitter { on( eventName: TEvent, - listener: (param: BackgroundEvents[TEvent]) => void + listener: (param: BackgroundEvents[TEvent]) => void, ): this { return super.on(eventName, listener) } once( eventName: TEvent, - listener: (param: BackgroundEvents[TEvent]) => void + listener: (param: BackgroundEvents[TEvent]) => void, ): this { return super.once(eventName, listener) } diff --git a/src/background/services/heartbeat.ts b/src/background/services/heartbeat.ts index 1e0d8e5b..9c993c5d 100644 --- a/src/background/services/heartbeat.ts +++ b/src/background/services/heartbeat.ts @@ -18,19 +18,19 @@ export class Heartbeat { // first minute that our extension stays alive. setTimeout( () => alarms.create('keep-alive-alarm-0', { periodInMinutes: 1 }), - 0 + 0, ) setTimeout( () => alarms.create('keep-alive-alarm-1', { periodInMinutes: 1 }), - 15 * 1000 + 15 * 1000, ) setTimeout( () => alarms.create('keep-alive-alarm-2', { periodInMinutes: 1 }), - 30 * 1000 + 30 * 1000, ) setTimeout( () => alarms.create('keep-alive-alarm-3', { periodInMinutes: 1 }), - 45 * 1000 + 45 * 1000, ) alarms.onAlarm.addListener(() => { diff --git a/src/background/services/monetization.ts b/src/background/services/monetization.ts index 4b4e6537..6c05e082 100644 --- a/src/background/services/monetization.ts +++ b/src/background/services/monetization.ts @@ -2,7 +2,7 @@ import type { Runtime } from 'webextension-polyfill' import { ResumeMonetizationPayload, StartMonetizationPayload, - StopMonetizationPayload + StopMonetizationPayload, } from '@/shared/messages' import { PaymentSession } from './paymentSession' import { computeRate, getCurrentActiveTab, getSender, getTabId } from '../utils' @@ -30,7 +30,7 @@ export class MonetizationService { events, openPaymentsService, tabState, - message + message, }: Cradle) { Object.assign(this, { logger, @@ -40,7 +40,7 @@ export class MonetizationService { browser, events, tabState, - message + message, }) this.registerEventListeners() @@ -48,7 +48,7 @@ export class MonetizationService { async startPaymentSession( payload: StartMonetizationPayload[], - sender: Runtime.MessageSender + sender: Runtime.MessageSender, ) { if (!payload.length) { throw new Error('Unexpected: payload is empty') @@ -58,18 +58,18 @@ export class MonetizationService { enabled, rateOfPay, connected, - walletAddress: connectedWallet + walletAddress: connectedWallet, } = await this.storage.get([ 'state', 'enabled', 'connected', 'rateOfPay', - 'walletAddress' + 'walletAddress', ]) if (!rateOfPay || !connectedWallet) { this.logger.error( - `Did not find rate of pay or connect wallet information. Received rate=${rateOfPay}, wallet=${connectedWallet}. Payment session will not be initialized.` + `Did not find rate of pay or connect wallet information. Received rate=${rateOfPay}, wallet=${connectedWallet}. Payment session will not be initialized.`, ) return } @@ -101,7 +101,7 @@ export class MonetizationService { this.tabState, removeQueryParams(url!), this.logger, - this.message + this.message, ) sessions.set(requestId, session) @@ -142,7 +142,7 @@ export class MonetizationService { async stopPaymentSession( payload: StopMonetizationPayload[], - sender: Runtime.MessageSender + sender: Runtime.MessageSender, ) { let needsAdjustAmount = false const tabId = getTabId(sender) @@ -187,7 +187,7 @@ export class MonetizationService { async resumePaymentSession( payload: ResumeMonetizationPayload[], - sender: Runtime.MessageSender + sender: Runtime.MessageSender, ) { const tabId = getTabId(sender) const sessions = this.tabState.getSessions(tabId) @@ -200,7 +200,7 @@ export class MonetizationService { const { state, connected, enabled } = await this.storage.get([ 'state', 'connected', - 'enabled' + 'enabled', ]) if (!enabled || !this.canTryPayment(connected, state)) return @@ -221,7 +221,7 @@ export class MonetizationService { const { state, connected, enabled } = await this.storage.get([ 'state', 'connected', - 'enabled' + 'enabled', ]) if (!enabled || !this.canTryPayment(connected, state)) return @@ -259,7 +259,7 @@ export class MonetizationService { const splitAmount = Number(amount) / payableSessions.length // TODO: handle paying across two grants (when one grant doesn't have enough funds) const results = await Promise.allSettled( - payableSessions.map((session) => session.pay(splitAmount)) + payableSessions.map((session) => session.pay(splitAmount)), ) const totalSentAmount = results @@ -278,7 +278,7 @@ export class MonetizationService { private canTryPayment( connected: Storage['connected'], - state: Storage['state'] + state: Storage['state'], ): boolean { if (!connected) return false if (isOkState(state)) return true @@ -372,7 +372,7 @@ export class MonetizationService { 'walletAddress', 'oneTimeGrant', 'recurringGrant', - 'publicKey' + 'publicKey', ]) const balance = await this.storage.getBalance() const tab = await getCurrentActiveTab(this.browser) @@ -393,7 +393,7 @@ export class MonetizationService { } const isSiteMonetized = this.tabState.isTabMonetized(tab.id!) const hasAllSessionsInvalid = this.tabState.tabHasAllSessionsInvalid( - tab.id! + tab.id!, ) return { @@ -402,16 +402,16 @@ export class MonetizationService { url, grants: { oneTime: oneTimeGrant?.amount, - recurring: recurringGrant?.amount + recurring: recurringGrant?.amount, }, isSiteMonetized, - hasAllSessionsInvalid + hasAllSessionsInvalid, } } private async adjustSessionsAmount( sessions: PaymentSession[], - rate: AmountValue + rate: AmountValue, ): Promise { try { await Promise.all(sessions.map((session) => session.adjustAmount(rate))) diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index ba362455..9df7ee4e 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -3,19 +3,19 @@ import type { AccessToken, AmountValue, GrantDetails, - WalletAmount + WalletAmount, } from 'shared/types' import { type AuthenticatedClient, createAuthenticatedClient, - OpenPaymentsClientError + OpenPaymentsClientError, } from '@interledger/open-payments/dist/client' import { isFinalizedGrant, isPendingGrant, type IncomingPayment, type OutgoingPaymentWithSpentAmounts as OutgoingPayment, - type WalletAddress + type WalletAddress, } from '@interledger/open-payments/dist/types' import * as ed from '@noble/ed25519' import { type Request } from 'http-message-signatures' @@ -30,7 +30,7 @@ import { AddFundsPayload, ConnectWalletPayload } from '@/shared/messages' import { DEFAULT_RATE_OF_PAY, MAX_RATE_OF_PAY, - MIN_RATE_OF_PAY + MIN_RATE_OF_PAY, } from '../config' import { OPEN_PAYMENTS_REDIRECT_URL } from '@/shared/defines' import type { Cradle } from '../container' @@ -92,17 +92,17 @@ type TabUpdateCallback = Parameters[0] const enum ErrorCode { CONTINUATION_FAILED = 'continuation_failed', - HASH_FAILED = 'hash_failed' + HASH_FAILED = 'hash_failed', } const enum GrantResult { SUCCESS = 'grant_success', - ERROR = 'grant_error' + ERROR = 'grant_error', } const enum InteractionIntent { CONNECT = 'connect', - FUNDS = 'funds' + FUNDS = 'funds', } export class OpenPaymentsService { @@ -150,7 +150,7 @@ export class OpenPaymentsService { 'connected', 'walletAddress', 'oneTimeGrant', - 'recurringGrant' + 'recurringGrant', ]) this.isGrantUsable.recurring = !!recurringGrant @@ -174,7 +174,7 @@ export class OpenPaymentsService { } throw new Error( - 'Could not create OpenPayments client. Missing `privateKey` and `keyId`.' + 'Could not create OpenPayments client. Missing `privateKey` and `keyId`.', ) } @@ -182,10 +182,10 @@ export class OpenPaymentsService { return { 'Content-Digest': createContentDigestHeader( JSON.stringify(JSON.parse(body)), - ['sha-512'] + ['sha-512'], ), 'Content-Length': new TextEncoder().encode(body).length.toString(), - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', } } @@ -195,14 +195,14 @@ export class OpenPaymentsService { alg: 'ed25519', async sign(data: Uint8Array) { return Buffer.from(await ed.signAsync(data, key.slice(16))) - } + }, } } private async createSignatureHeaders({ request, privateKey, - keyId + keyId, }: SignOptions): Promise { const components = ['@method', '@target-uri'] if (request.headers['Authorization'] || request.headers['authorization']) { @@ -219,25 +219,25 @@ export class OpenPaymentsService { name: 'sig1', params: ['keyid', 'created'], fields: components, - key: signingKey + key: signingKey, }, { url: request.url, method: request.method, - headers: request.headers - } + headers: request.headers, + }, ) return { Signature: headers['Signature'] as string, - 'Signature-Input': headers['Signature-Input'] as string + 'Signature-Input': headers['Signature-Input'] as string, } } private async createHeaders({ request, privateKey, - keyId + keyId, }: SignOptions): Promise { if (request.body) { const contentHeaders = this.createContentHeaders(request.body) @@ -247,12 +247,12 @@ export class OpenPaymentsService { const signatureHeaders = await this.createSignatureHeaders({ request, privateKey, - keyId + keyId, }) return { ...request.headers, - ...signatureHeaders + ...signatureHeaders, } } @@ -275,42 +275,42 @@ export class OpenPaymentsService { method: request.method, url: request.url, headers: JSON.parse( - JSON.stringify(Object.fromEntries(request.headers)) + JSON.stringify(Object.fromEntries(request.headers)), ), body: request.body ? JSON.stringify(await request.json()) - : undefined + : undefined, }, privateKey: ed.etc.hexToBytes(privateKey), - keyId + keyId, }) if (request.body) { initialRequest.headers.set( 'Content-Type', - headers['Content-Type'] as string + headers['Content-Type'] as string, ) initialRequest.headers.set( 'Content-Digest', - headers['Content-Digest'] as string + headers['Content-Digest'] as string, ) } initialRequest.headers.set('Signature', headers['Signature']) initialRequest.headers.set( 'Signature-Input', - headers['Signature-Input'] + headers['Signature-Input'], ) return initialRequest - } + }, }) } async connectWallet({ walletAddressUrl, amount, - recurring + recurring, }: ConnectWalletPayload) { const walletAddress = await getWalletInformation(walletAddressUrl) const exchangeRates = await getExchangeRates() @@ -327,17 +327,17 @@ export class OpenPaymentsService { rateOfPay = getRateOfPay({ rate: DEFAULT_RATE_OF_PAY, exchangeRate, - assetScale: walletAddress.assetScale + assetScale: walletAddress.assetScale, }) minRateOfPay = getRateOfPay({ rate: MIN_RATE_OF_PAY, exchangeRate, - assetScale: walletAddress.assetScale + assetScale: walletAddress.assetScale, }) maxRateOfPay = getRateOfPay({ rate: MAX_RATE_OF_PAY, exchangeRate, - assetScale: walletAddress.assetScale + assetScale: walletAddress.assetScale, }) await this.initClient(walletAddress.id) @@ -345,7 +345,7 @@ export class OpenPaymentsService { amount, walletAddress, recurring, - InteractionIntent.CONNECT + InteractionIntent.CONNECT, ) await this.storage.set({ @@ -353,7 +353,7 @@ export class OpenPaymentsService { rateOfPay, minRateOfPay, maxRateOfPay, - connected: true + connected: true, }) } @@ -361,14 +361,14 @@ export class OpenPaymentsService { const { walletAddress, ...grants } = await this.storage.get([ 'walletAddress', 'oneTimeGrant', - 'recurringGrant' + 'recurringGrant', ]) await this.completeGrant( amount, walletAddress!, recurring, - InteractionIntent.FUNDS + InteractionIntent.FUNDS, ) // cancel existing grants of same type, if any @@ -385,19 +385,19 @@ export class OpenPaymentsService { amount: string, walletAddress: WalletAddress, recurring: boolean, - intent: InteractionIntent + intent: InteractionIntent, ): Promise { const transformedAmount = toAmount({ value: amount, recurring, - assetScale: walletAddress.assetScale + assetScale: walletAddress.assetScale, }) const clientNonce = crypto.randomUUID() const grant = await this.createOutgoingPaymentGrant({ clientNonce, walletAddress, - amount: transformedAmount + amount: transformedAmount, }).catch((err) => { if (isInvalidClientError(err)) { const msg = this.t('connectWallet_error_invalidClient') @@ -407,7 +407,7 @@ export class OpenPaymentsService { }) const { interactRef, hash, tabId } = await this.getInteractionInfo( - grant.interact.redirect + grant.interact.redirect, ) await this.verifyInteractionHash({ @@ -415,13 +415,13 @@ export class OpenPaymentsService { interactNonce: grant.interact.finish, interactRef, hash, - authServer: walletAddress.authServer + authServer: walletAddress.authServer, }).catch(async (e) => { await this.redirectToWelcomeScreen( tabId, GrantResult.ERROR, intent, - ErrorCode.HASH_FAILED + ErrorCode.HASH_FAILED, ) throw e }) @@ -429,17 +429,17 @@ export class OpenPaymentsService { const continuation = await this.client!.grant.continue( { url: grant.continue.uri, - accessToken: grant.continue.access_token.value + accessToken: grant.continue.access_token.value, }, { - interact_ref: interactRef - } + interact_ref: interactRef, + }, ).catch(async (e) => { await this.redirectToWelcomeScreen( tabId, GrantResult.ERROR, intent, - ErrorCode.CONTINUATION_FAILED + ErrorCode.CONTINUATION_FAILED, ) throw e }) @@ -453,24 +453,24 @@ export class OpenPaymentsService { amount: transformedAmount as Required, accessToken: { value: continuation.access_token.value, - manageUrl: continuation.access_token.manage + manageUrl: continuation.access_token.manage, }, continue: { accessToken: continuation.continue.access_token.value, - url: continuation.continue.uri - } + url: continuation.continue.uri, + }, } if (grantDetails.type === 'recurring') { await this.storage.set({ recurringGrant: grantDetails, - recurringGrantSpentAmount: '0' + recurringGrantSpentAmount: '0', }) this.isGrantUsable.recurring = true } else { await this.storage.set({ oneTimeGrant: grantDetails, - oneTimeGrantSpentAmount: '0' + oneTimeGrantSpentAmount: '0', }) this.isGrantUsable.oneTime = true } @@ -484,32 +484,32 @@ export class OpenPaymentsService { tabId: NonNullable, result: GrantResult, intent: InteractionIntent, - errorCode?: ErrorCode + errorCode?: ErrorCode, ) { const url = new URL(OPEN_PAYMENTS_REDIRECT_URL) url.searchParams.set('result', result) url.searchParams.set('intent', intent) if (errorCode) url.searchParams.set('errorCode', errorCode) await this.browser.tabs.update(tabId, { - url: url.toString() + url: url.toString(), }) } private async createOutgoingPaymentGrant({ amount, walletAddress, - clientNonce + clientNonce, }: CreateOutgoingPaymentGrantParams) { const grant = await this.client!.grant.request( { - url: walletAddress.authServer + url: walletAddress.authServer, }, { access_token: { access: [ { type: 'quote', - actions: ['create'] + actions: ['create'], }, { type: 'outgoing-payment', @@ -519,22 +519,22 @@ export class OpenPaymentsService { debitAmount: { value: amount.value, assetScale: walletAddress.assetScale, - assetCode: walletAddress.assetCode + assetCode: walletAddress.assetCode, }, - interval: amount.interval - } - } - ] + interval: amount.interval, + }, + }, + ], }, interact: { start: ['redirect'], finish: { method: 'redirect', uri: OPEN_PAYMENTS_REDIRECT_URL, - nonce: clientNonce - } - } - } + nonce: clientNonce, + }, + }, + }, ) if (!isPendingGrant(grant)) { @@ -549,16 +549,16 @@ export class OpenPaymentsService { interactRef, interactNonce, hash, - authServer + authServer, }: VerifyInteractionHashParams): Promise { const grantEndpoint = new URL(authServer).origin + '/' const data = new TextEncoder().encode( - `${clientNonce}\n${interactNonce}\n${interactRef}\n${grantEndpoint}` + `${clientNonce}\n${interactNonce}\n${interactRef}\n${grantEndpoint}`, ) const digest = await crypto.subtle.digest('SHA-256', data) const calculatedHash = btoa( - String.fromCharCode.apply(null, new Uint8Array(digest)) + String.fromCharCode.apply(null, new Uint8Array(digest)), ) if (calculatedHash !== hash) throw new Error('Invalid interaction hash') } @@ -569,7 +569,7 @@ export class OpenPaymentsService { if (!tab.id) return const getInteractionInfo: TabUpdateCallback = async ( tabId, - changeInfo + changeInfo, ) => { if (tabId !== tab.id) return try { @@ -601,7 +601,7 @@ export class OpenPaymentsService { async disconnectWallet() { const { recurringGrant, oneTimeGrant } = await this.storage.get([ 'recurringGrant', - 'oneTimeGrant' + 'oneTimeGrant', ]) if (!recurringGrant && !oneTimeGrant) { return @@ -640,19 +640,19 @@ export class OpenPaymentsService { await this.storage.set({ privateKey: bytesToHex(privateKey), publicKey: btoa(JSON.stringify(jwk)), - keyId + keyId, }) } async createOutgoingPayment({ walletAddress, amount, - incomingPaymentId + incomingPaymentId, }: CreateOutgoingPaymentParams): Promise { const outgoingPayment = (await this.client!.outgoingPayment.create( { accessToken: this.token.value, - url: walletAddress.resourceServer + url: walletAddress.resourceServer, }, { incomingPayment: incomingPaymentId, @@ -660,18 +660,18 @@ export class OpenPaymentsService { debitAmount: { value: amount, assetCode: walletAddress.assetCode, - assetScale: walletAddress.assetScale + assetScale: walletAddress.assetScale, }, metadata: { - source: 'Web Monetization' - } - } + source: 'Web Monetization', + }, + }, )) as OutgoingPayment if (outgoingPayment.grantSpentDebitAmount) { this.storage.updateSpentAmount( this.grant!.type, - outgoingPayment.grantSpentDebitAmount.value + outgoingPayment.grantSpentDebitAmount.value, ) } await this.storage.setState({ out_of_funds: false }) @@ -682,12 +682,12 @@ export class OpenPaymentsService { async probeDebitAmount( amount: AmountValue, incomingPayment: IncomingPayment['id'], - sender: WalletAddress + sender: WalletAddress, ): Promise { await this.client!.quote.create( { url: sender.resourceServer, - accessToken: this.token.value + accessToken: this.token.value, }, { method: 'ilp', @@ -696,9 +696,9 @@ export class OpenPaymentsService { debitAmount: { value: amount, assetCode: sender.assetCode, - assetScale: sender.assetScale - } - } + assetScale: sender.assetScale, + }, + }, ) } @@ -727,7 +727,7 @@ export class OpenPaymentsService { this.logger.debug('Switching from grant', this.grant?.type) const { oneTimeGrant, recurringGrant } = await this.storage.get([ 'oneTimeGrant', - 'recurringGrant' + 'recurringGrant', ]) if (this.grant?.type === 'recurring') { this.isGrantUsable.recurring = false @@ -752,11 +752,11 @@ export class OpenPaymentsService { const rotate = this.deduplicator.dedupe(this.client!.token.rotate) const newToken = await rotate({ url: this.token.manageUrl, - accessToken: this.token.value + accessToken: this.token.value, }) const accessToken: AccessToken = { value: newToken.access_token.value, - manageUrl: newToken.access_token.manage + manageUrl: newToken.access_token.manage, } if (this.grant.type === 'recurring') { this.storage.set({ recurringGrant: { ...this.grant, accessToken } }) diff --git a/src/background/services/paymentSession.ts b/src/background/services/paymentSession.ts index dbc26f22..cfbb3aed 100644 --- a/src/background/services/paymentSession.ts +++ b/src/background/services/paymentSession.ts @@ -2,7 +2,7 @@ import { isPendingGrant, type IncomingPayment, type OutgoingPayment, - type WalletAddress + type WalletAddress, } from '@interledger/open-payments/dist/types' import { bigIntMax, convert } from '@/shared/helpers' import { transformBalance } from '@/popup/lib/utils' @@ -11,7 +11,7 @@ import { isKeyRevokedError, isNonPositiveAmountError, isOutOfBalanceError, - isTokenExpiredError + isTokenExpiredError, } from './openPayments' import { getNextSendableAmount } from '@/background/utils' import type { EventsService, OpenPaymentsService, TabState } from '.' @@ -19,7 +19,7 @@ import type { BackgroundToContentMessage, MessageManager, MonetizationEventDetails, - MonetizationEventPayload + MonetizationEventPayload, } from '@/shared/messages' import type { AmountValue } from '@/shared/types' import type { Logger } from '@/shared/logger' @@ -59,7 +59,7 @@ export class PaymentSession { private tabState: TabState, private url: string, private logger: Logger, - private message: MessageManager + private message: MessageManager, ) {} async adjustAmount(rate: AmountValue): Promise { @@ -94,14 +94,14 @@ export class PaymentSession { const amountInReceiversScale = convert( amountToSend, senderAssetScale, - receiverAssetScale + receiverAssetScale, ) if (amountInReceiversScale === 0n) { amountToSend = convert( MIN_SEND_AMOUNT, receiverAssetScale, - senderAssetScale + senderAssetScale, ) } } @@ -113,7 +113,7 @@ export class PaymentSession { const amountIter = getNextSendableAmount( senderAssetScale, receiverAssetScale, - bigIntMax(amountToSend, MIN_SEND_AMOUNT) + bigIntMax(amountToSend, MIN_SEND_AMOUNT), ) amountToSend = BigInt(amountIter.next().value) @@ -126,7 +126,7 @@ export class PaymentSession { await this.openPaymentsService.probeDebitAmount( amountToSend.toString(), this.incomingPaymentUrl, - this.sender + this.sender, ) this.setAmount(amountToSend) break @@ -139,7 +139,7 @@ export class PaymentSession { } else if (isInvalidReceiverError(e)) { this.markInvalid() this.events.emit('open_payments.invalid_receiver', { - tabId: this.tabId + tabId: this.tabId, }) break } else { @@ -205,13 +205,13 @@ export class PaymentSession { private debug(message: string) { this.logger.debug( `[PAYMENT SESSION] requestId=${this.requestId}; receiver=${this.receiver.id}\n\n`, - ` ${message}` + ` ${message}`, ) } async start(source: PaymentSessionSource) { this.debug( - `Attempting to start; source=${source} active=${this.active} disabled=${this.isDisabled} isInvalid=${this.isInvalid}` + `Attempting to start; source=${source} active=${this.active} disabled=${this.isDisabled} isInvalid=${this.isInvalid}`, ) if (this.active || this.isDisabled || this.isInvalid) return this.debug(`Session started; source=${source}`) @@ -222,7 +222,7 @@ export class PaymentSession { const { waitTime, monetizationEvent } = this.tabState.getOverpayingDetails( this.tabId, this.url, - this.receiver.id + this.receiver.id, ) this.debug(`Overpaying: waitTime=${waitTime}`) @@ -230,7 +230,7 @@ export class PaymentSession { if (monetizationEvent && source !== 'tab-change') { this.sendMonetizationEvent({ requestId: this.requestId, - details: monetizationEvent + details: monetizationEvent, }) } @@ -261,7 +261,7 @@ export class PaymentSession { () => { continuePayment() }, - this.shouldRetryImmediately ? 0 : this.intervalInMs + this.shouldRetryImmediately ? 0 : this.intervalInMs, ) }) } @@ -273,7 +273,7 @@ export class PaymentSession { () => { continuePayment() }, - this.shouldRetryImmediately ? 0 : this.intervalInMs + this.shouldRetryImmediately ? 0 : this.intervalInMs, ) }, waitTime) } @@ -284,7 +284,7 @@ export class PaymentSession { this.tabId, this.frameId, 'MONETIZATION_EVENT', - payload + payload, ) } @@ -308,16 +308,16 @@ export class PaymentSession { } private async createIncomingPayment( - source: IncomingPaymentSource + source: IncomingPaymentSource, ): Promise { const expiresAt = new Date( - Date.now() + 1000 * (source === 'continuous' ? 60 * 10 : 30) + Date.now() + 1000 * (source === 'continuous' ? 60 * 10 : 30), ).toISOString() const incomingPaymentGrant = await this.openPaymentsService.client!.grant.request( { - url: this.receiver.authServer + url: this.receiver.authServer, }, { access_token: { @@ -325,11 +325,11 @@ export class PaymentSession { { type: 'incoming-payment', actions: ['create'], - identifier: this.receiver.id - } - ] - } - } + identifier: this.receiver.id, + }, + ], + }, + }, ) if (isPendingGrant(incomingPaymentGrant)) { @@ -340,27 +340,27 @@ export class PaymentSession { await this.openPaymentsService.client!.incomingPayment.create( { url: this.receiver.resourceServer, - accessToken: incomingPaymentGrant.access_token.value + accessToken: incomingPaymentGrant.access_token.value, }, { walletAddress: this.receiver.id, expiresAt, metadata: { - source: 'Web Monetization' - } - } + source: 'Web Monetization', + }, + }, ) if (incomingPayment.expiresAt) { this.incomingPaymentExpiresAt = new Date( - incomingPayment.expiresAt + incomingPayment.expiresAt, ).valueOf() } // Revoke grant to avoid leaving users with unused, dangling grants. await this.openPaymentsService.client!.grant.cancel({ url: incomingPaymentGrant.continue.uri, - accessToken: incomingPaymentGrant.continue.access_token.value + accessToken: incomingPaymentGrant.continue.access_token.value, }) return incomingPayment @@ -378,7 +378,7 @@ export class PaymentSession { return } throw error - } + }, ) if (!incomingPayment) return @@ -388,7 +388,7 @@ export class PaymentSession { outgoingPayment = await this.openPaymentsService.createOutgoingPayment({ walletAddress: this.sender, incomingPaymentId: incomingPayment.id, - amount: (amount * 10 ** this.sender.assetScale).toFixed(0) + amount: (amount * 10 ** this.sender.assetScale).toFixed(0), }) } catch (e) { if (isKeyRevokedError(e)) { @@ -409,12 +409,12 @@ export class PaymentSession { currency: receiveAmount.assetCode, value: transformBalance( receiveAmount.value, - receiveAmount.assetScale - ) + receiveAmount.assetScale, + ), }, incomingPayment, - paymentPointer: this.receiver.id - } + paymentPointer: this.receiver.id, + }, }) } } @@ -433,21 +433,24 @@ export class PaymentSession { await this.openPaymentsService.createOutgoingPayment({ walletAddress: this.sender, incomingPaymentId: this.incomingPaymentUrl, - amount: this.amount + amount: this.amount, }) const { receiveAmount, receiver: incomingPayment } = outgoingPayment const monetizationEventDetails: MonetizationEventDetails = { amountSent: { currency: receiveAmount.assetCode, - value: transformBalance(receiveAmount.value, receiveAmount.assetScale) + value: transformBalance( + receiveAmount.value, + receiveAmount.assetScale, + ), }, incomingPayment, - paymentPointer: this.receiver.id + paymentPointer: this.receiver.id, } this.sendMonetizationEvent({ requestId: this.requestId, - details: monetizationEventDetails + details: monetizationEventDetails, }) // TO DO: find a better source of truth for deciding if overpaying is applicable @@ -455,7 +458,7 @@ export class PaymentSession { this.tabState.saveOverpaying(this.tabId, this.url, { walletAddressId: this.receiver.id, monetizationEvent: monetizationEventDetails, - intervalInMs: this.intervalInMs + intervalInMs: this.intervalInMs, }) } this.shouldRetryImmediately = false @@ -484,7 +487,7 @@ export class PaymentSession { ) { this.markInvalid() this.events.emit('open_payments.invalid_receiver', { - tabId: this.tabId + tabId: this.tabId, }) } else { this.shouldRetryImmediately = true diff --git a/src/background/services/sendToPopup.ts b/src/background/services/sendToPopup.ts index aef24db5..cfc731b4 100644 --- a/src/background/services/sendToPopup.ts +++ b/src/background/services/sendToPopup.ts @@ -2,7 +2,7 @@ import type { Runtime } from 'webextension-polyfill' import { BACKGROUND_TO_POPUP_CONNECTION_NAME as CONNECTION_NAME, type BackgroundToPopupMessage, - type BackgroundToPopupMessagesMap + type BackgroundToPopupMessagesMap, } from '@/shared/messages' import type { Cradle } from '@/background/container' @@ -41,7 +41,7 @@ export class SendToPopup { async send( type: T, - data: BackgroundToPopupMessagesMap[T] + data: BackgroundToPopupMessagesMap[T], ) { if (!this.isConnected) { this.queue.set(type, data) diff --git a/src/background/services/storage.ts b/src/background/services/storage.ts index 7d9ace01..76d5ecfc 100644 --- a/src/background/services/storage.ts +++ b/src/background/services/storage.ts @@ -4,7 +4,7 @@ import type { GrantDetails, Storage, StorageKey, - WalletAmount + WalletAmount, } from '@/shared/types' import { bigIntMax, objectEquals, ThrottleBatch } from '@/shared/helpers' import { computeBalance } from '../utils' @@ -30,7 +30,7 @@ const defaultStorage = { oneTimeGrantSpentAmount: '0', rateOfPay: null, minRateOfPay: null, - maxRateOfPay: null + maxRateOfPay: null, } satisfies Omit export class StorageService { @@ -48,17 +48,17 @@ export class StorageService { this.setSpentAmountRecurring = new ThrottleBatch( (amount) => this.setSpentAmount('recurring', amount), (args) => [args.reduce((max, [v]) => bigIntMax(max, v), '0')], - 1000 + 1000, ) this.setSpentAmountOneTime = new ThrottleBatch( (amount) => this.setSpentAmount('one-time', amount), (args) => [args.reduce((max, [v]) => bigIntMax(max, v), '0')], - 1000 + 1000, ) } async get( - keys?: TKey[] + keys?: TKey[], ): Promise<{ [Key in TKey[][number]]: Storage[Key] }> { const data = await this.browser.storage.local.get(keys) return data as { [Key in TKey[][number]]: Storage[Key] } @@ -149,7 +149,7 @@ export class StorageService { await this.set({ state: newState }) this.events.emit('storage.state_update', { state: newState, - prevState: prevState + prevState: prevState, }) return true } @@ -179,21 +179,21 @@ export class StorageService { 'recurringGrant', 'recurringGrantSpentAmount', 'oneTimeGrant', - 'oneTimeGrantSpentAmount' + 'oneTimeGrantSpentAmount', ]) const balanceRecurring = computeBalance( data.recurringGrant, - data.recurringGrantSpentAmount + data.recurringGrantSpentAmount, ) const balanceOneTime = computeBalance( data.oneTimeGrant, - data.oneTimeGrantSpentAmount + data.oneTimeGrantSpentAmount, ) const balance = balanceRecurring + balanceOneTime return { total: balance.toString(), recurring: balanceRecurring.toString(), - oneTime: balanceOneTime.toString() + oneTime: balanceOneTime.toString(), } } @@ -207,7 +207,7 @@ export class StorageService { * @param existingData Existing data from previous version. */ type Migration = ( - existingData: Record + existingData: Record, ) => [data: Record, deleteKeys?: string[]] // There was never a migration to reach 1. @@ -233,16 +233,16 @@ const MIGRATIONS: Record = { value: data.amount.value as string, ...(type === 'recurring' ? { interval: data.amount.interval as string } - : {}) + : {}), } as Required, accessToken: { value: data.token.value as string, - manageUrl: data.token.manage as string + manageUrl: data.token.manage as string, }, continue: { url: data.grant.continueUri as string, - accessToken: data.grant.accessToken as string - } + accessToken: data.grant.accessToken as string, + }, } if (type === 'recurring') { @@ -265,5 +265,5 @@ const MIGRATIONS: Record = { : {} data.state = newState satisfies Storage['state'] return [data] - } + }, } diff --git a/src/background/services/tabEvents.ts b/src/background/services/tabEvents.ts index 059a988c..1e306725 100644 --- a/src/background/services/tabEvents.ts +++ b/src/background/services/tabEvents.ts @@ -9,43 +9,43 @@ const ICONS = { default: { 32: runtime.getURL('assets/icons/32x32/default.png'), 48: runtime.getURL('assets/icons/48x48/default.png'), - 128: runtime.getURL('assets/icons/128x128/default.png') + 128: runtime.getURL('assets/icons/128x128/default.png'), }, default_gray: { 32: runtime.getURL('assets/icons/32x32/default-gray.png'), 48: runtime.getURL('assets/icons/48x48/default-gray.png'), - 128: runtime.getURL('assets/icons/128x128/default-gray.png') + 128: runtime.getURL('assets/icons/128x128/default-gray.png'), }, enabled_hasLinks: { 32: runtime.getURL('assets/icons/32x32/enabled-has-links.png'), 48: runtime.getURL('assets/icons/48x48/enabled-has-links.png'), - 128: runtime.getURL('assets/icons/128x128/enabled-has-links.png') + 128: runtime.getURL('assets/icons/128x128/enabled-has-links.png'), }, enabled_noLinks: { 32: runtime.getURL('assets/icons/32x32/enabled-no-links.png'), 48: runtime.getURL('assets/icons/48x48/enabled-no-links.png'), - 128: runtime.getURL('assets/icons/128x128/enabled-no-links.png') + 128: runtime.getURL('assets/icons/128x128/enabled-no-links.png'), }, enabled_warn: { 32: runtime.getURL('assets/icons/32x32/enabled-warn.png'), 48: runtime.getURL('assets/icons/48x48/enabled-warn.png'), - 128: runtime.getURL('assets/icons/128x128/enabled-warn.png') + 128: runtime.getURL('assets/icons/128x128/enabled-warn.png'), }, disabled_hasLinks: { 32: runtime.getURL('assets/icons/32x32/disabled-has-links.png'), 48: runtime.getURL('assets/icons/48x48/disabled-has-links.png'), - 128: runtime.getURL('assets/icons/128x128/disabled-has-links.png') + 128: runtime.getURL('assets/icons/128x128/disabled-has-links.png'), }, disabled_noLinks: { 32: runtime.getURL('assets/icons/32x32/disabled-no-links.png'), 48: runtime.getURL('assets/icons/48x48/disabled-no-links.png'), - 128: runtime.getURL('assets/icons/128x128/disabled-no-links.png') + 128: runtime.getURL('assets/icons/128x128/disabled-no-links.png'), }, disabled_warn: { 32: runtime.getURL('assets/icons/32x32/disabled-warn.png'), 48: runtime.getURL('assets/icons/48x48/disabled-warn.png'), - 128: runtime.getURL('assets/icons/128x128/disabled-warn.png') - } + 128: runtime.getURL('assets/icons/128x128/disabled-warn.png'), + }, } type CallbackTab> = @@ -64,7 +64,7 @@ export class TabEvents { tabState, sendToPopup, t, - browser + browser, }) } @@ -106,19 +106,19 @@ export class TabEvents { : false, hasTabAllSessionsInvalid: boolean = tabId ? this.tabState.tabHasAllSessionsInvalid(tabId) - : false + : false, ) => { const { enabled, connected, state } = await this.storage.get([ 'enabled', 'connected', - 'state' + 'state', ]) const { path, title, isMonetized } = this.getIconAndTooltip({ enabled, connected, state, isTabMonetized, - hasTabAllSessionsInvalid + hasTabAllSessionsInvalid, }) this.sendToPopup.send('SET_IS_MONETIZED', isMonetized) @@ -130,7 +130,7 @@ export class TabEvents { private setIconAndTooltip = async ( path: (typeof ICONS)[keyof typeof ICONS], title: string, - tabId?: TabId + tabId?: TabId, ) => { await this.browser.action.setIcon({ path, tabId }) await this.browser.action.setTitle({ title, tabId }) @@ -141,7 +141,7 @@ export class TabEvents { connected, state, isTabMonetized, - hasTabAllSessionsInvalid + hasTabAllSessionsInvalid, }: { enabled: Storage['enabled'] connected: Storage['connected'] @@ -176,7 +176,7 @@ export class TabEvents { return { path: iconData, isMonetized: isTabMonetized, - title + title, } } } diff --git a/src/background/services/tabState.ts b/src/background/services/tabState.ts index ef40ef5f..aae3b534 100644 --- a/src/background/services/tabState.ts +++ b/src/background/services/tabState.ts @@ -25,7 +25,7 @@ export class TabState { constructor({ logger }: Cradle) { Object.assign(this, { - logger + logger, }) } @@ -42,7 +42,7 @@ export class TabState { getOverpayingDetails( tabId: TabId, url: string, - walletAddressId: string + walletAddressId: string, ): { waitTime: number; monetizationEvent?: MonetizationEventDetails } { const key = this.getOverpayingStateKey(url, walletAddressId) const state = this.state.get(tabId)?.get(key) @@ -51,19 +51,19 @@ export class TabState { if (state && state.expiresAtTimestamp > now) { return { waitTime: state.expiresAtTimestamp - now, - monetizationEvent: state.monetizationEvent + monetizationEvent: state.monetizationEvent, } } return { - waitTime: 0 + waitTime: 0, } } saveOverpaying( tabId: TabId, url: string, - details: SaveOverpayingDetails + details: SaveOverpayingDetails, ): void { const { intervalInMs, walletAddressId, monetizationEvent } = details if (!intervalInMs) return @@ -79,7 +79,7 @@ export class TabState { tabState.set(key, { monetizationEvent, expiresAtTimestamp: expiresAtTimestamp, - lastPaymentTimestamp: now + lastPaymentTimestamp: now, }) this.state.set(tabId, tabState) } else { diff --git a/src/background/utils.test.ts b/src/background/utils.test.ts index ffaba9c7..43f3f3a2 100644 --- a/src/background/utils.test.ts +++ b/src/background/utils.test.ts @@ -22,7 +22,7 @@ describe('getNextSendableAmount', () => { '27508', '27515', '27527', - '27547' + '27547', ]) }) @@ -36,7 +36,7 @@ describe('getNextSendableAmount', () => { '8027500', '15027500', '27027500', - '47027500' + '47027500', ]) }) @@ -49,7 +49,7 @@ describe('getNextSendableAmount', () => { '150', '270', '470', - '800' + '800', ]) }) @@ -62,7 +62,7 @@ describe('getNextSendableAmount', () => { '15', '27', '47', - '80' + '80', ]) }) @@ -75,7 +75,7 @@ describe('getNextSendableAmount', () => { '15', '27', '47', - '80' + '80', ]) }) }) diff --git a/src/background/utils.ts b/src/background/utils.ts index 1a378abf..24821d27 100644 --- a/src/background/utils.ts +++ b/src/background/utils.ts @@ -7,7 +7,7 @@ export const getCurrentActiveTab = async (browser: Browser) => { const window = await browser.windows.getLastFocused() const activeTabs = await browser.tabs.query({ active: true, - windowId: window.id + windowId: window.id, }) return activeTabs[0] } @@ -21,19 +21,19 @@ interface ToAmountParams { export const toAmount = ({ value, recurring, - assetScale + assetScale, }: ToAmountParams): WalletAmount => { const interval = `R/${new Date().toISOString()}/P1M` return { value: Math.floor(parseFloat(value) * 10 ** assetScale).toString(), - ...(recurring ? { interval } : {}) + ...(recurring ? { interval } : {}), } } export const OPEN_PAYMENTS_ERRORS: Record = { 'invalid client': - 'Please make sure that you uploaded the public key for your desired wallet address.' + 'Please make sure that you uploaded the public key for your desired wallet address.', } export interface GetRateOfPayParams { @@ -45,7 +45,7 @@ export interface GetRateOfPayParams { export const getRateOfPay = ({ rate, exchangeRate, - assetScale + assetScale, }: GetRateOfPayParams) => { const scaleDiff = assetScale - DEFAULT_SCALE @@ -66,7 +66,7 @@ export const getExchangeRates = async (): Promise => { const response = await fetch(EXCHANGE_RATES_URL) if (!response.ok) { throw new Error( - `Could not fetch exchange rates. [Status code: ${response.status}]` + `Could not fetch exchange rates. [Status code: ${response.status}]`, ) } const rates = await response.json() @@ -97,7 +97,7 @@ export const computeRate = (rate: string, sessionsCount: number): AmountValue => export function computeBalance( grant?: GrantDetails | null, - grantSpentAmount?: AmountValue | null + grantSpentAmount?: AmountValue | null, ) { if (!grant?.amount) return 0n const total = BigInt(grant.amount.value) @@ -110,7 +110,7 @@ export function computeBalance( export function* getNextSendableAmount( senderAssetScale: number, receiverAssetScale: number, - amount: bigint = 0n + amount: bigint = 0n, ): Generator { const EXPONENTIAL_INCREASE = 0.5 diff --git a/src/content/container.ts b/src/content/container.ts index 069971f0..35e0a149 100644 --- a/src/content/container.ts +++ b/src/content/container.ts @@ -7,7 +7,7 @@ import { LOG_LEVEL } from '@/shared/defines' import { FrameManager } from './services/frameManager' import { type ContentToBackgroundMessage, - MessageManager + MessageManager, } from '@/shared/messages' export interface Cradle { @@ -23,7 +23,7 @@ export interface Cradle { export const configureContainer = () => { const container = createContainer({ - injectionMode: InjectionMode.PROXY + injectionMode: InjectionMode.PROXY, }) const logger = createLogger(LOG_LEVEL) @@ -37,18 +37,18 @@ export const configureContainer = () => { frameManager: asClass(FrameManager) .singleton() .inject(() => ({ - logger: logger.getLogger('content-script:frameManager') + logger: logger.getLogger('content-script:frameManager'), })), monetizationTagManager: asClass(MonetizationTagManager) .singleton() .inject(() => ({ - logger: logger.getLogger('content-script:tagManager') + logger: logger.getLogger('content-script:tagManager'), })), contentScript: asClass(ContentScript) .singleton() .inject(() => ({ - logger: logger.getLogger('content-script:main') - })) + logger: logger.getLogger('content-script:main'), + })), }) return container diff --git a/src/content/debug.ts b/src/content/debug.ts index 9a50b4bf..a8b015f2 100644 --- a/src/content/debug.ts +++ b/src/content/debug.ts @@ -16,7 +16,7 @@ const listenForLinkChange = (mutationsList: MutationRecord[]) => { return acc }, - [] + [], ) if (monetizationLinks.length) { @@ -41,7 +41,7 @@ export const loadObserver = () => { const observeOptions = { attributes: true, childList: true, - subtree: true + subtree: true, } observer.observe(document, observeOptions) diff --git a/src/content/messages.ts b/src/content/messages.ts index 1d745383..4eb30d26 100644 --- a/src/content/messages.ts +++ b/src/content/messages.ts @@ -5,5 +5,5 @@ export enum ContentToContentAction { IS_MONETIZATION_ALLOWED_ON_STOP = 'IS_MONETIZATION_ALLOWED_ON_STOP', START_MONETIZATION = 'START_MONETIZATION', STOP_MONETIZATION = 'STOP_MONETIZATION', - RESUME_MONETIZATION = 'RESUME_MONETIZATION' + RESUME_MONETIZATION = 'RESUME_MONETIZATION', } diff --git a/src/content/polyfill.ts b/src/content/polyfill.ts index 68ece957..edc276fd 100644 --- a/src/content/polyfill.ts +++ b/src/content/polyfill.ts @@ -28,7 +28,7 @@ import type { MonetizationEventPayload } from '@/shared/messages' } else { throw new Error('val must be a function, got ' + typeof val) } - } + }, } const supportsOriginal = DOMTokenList.prototype.supports @@ -44,7 +44,7 @@ import type { MonetizationEventPayload } from '@/shared/messages' const relList = Object.getOwnPropertyDescriptor( HTMLLinkElement.prototype, - 'relList' + 'relList', )! const relListGetOriginal = relList.get! @@ -67,7 +67,7 @@ import type { MonetizationEventPayload } from '@/shared/messages' constructor( type: 'monetization', - eventInitDict: MonetizationEventPayload['details'] + eventInitDict: MonetizationEventPayload['details'], ) { super(type, { bubbles: true }) const { amountSent, incomingPayment, paymentPointer } = eventInitDict @@ -103,10 +103,10 @@ import type { MonetizationEventPayload } from '@/shared/messages' const monetizationTag = event.target monetizationTag.dispatchEvent( - new MonetizationEvent('monetization', event.detail) + new MonetizationEvent('monetization', event.detail), ) }, - { capture: true } + { capture: true }, ) window.addEventListener( @@ -120,6 +120,6 @@ import type { MonetizationEventPayload } from '@/shared/messages' ? new Function(attribute).bind(event.target) : null }, - { capture: true } + { capture: true }, ) })() diff --git a/src/content/services/contentScript.ts b/src/content/services/contentScript.ts index de615fb2..6fbe1c6a 100644 --- a/src/content/services/contentScript.ts +++ b/src/content/services/contentScript.ts @@ -17,14 +17,14 @@ export class ContentScript { window, logger, monetizationTagManager, - frameManager + frameManager, }: Cradle) { Object.assign(this, { browser, window, logger, monetizationTagManager, - frameManager + frameManager, }) this.isTopFrame = window === window.top @@ -51,7 +51,7 @@ export class ContentScript { switch (message.action) { case 'MONETIZATION_EVENT': this.monetizationTagManager.dispatchMonetizationEvent( - message.payload + message.payload, ) return @@ -67,7 +67,7 @@ export class ContentScript { this.logger.error(message.action, e.message) return failure(e.message) } - } + }, ) } diff --git a/src/content/services/frameManager.ts b/src/content/services/frameManager.ts index d9870370..6a4cd929 100644 --- a/src/content/services/frameManager.ts +++ b/src/content/services/frameManager.ts @@ -2,7 +2,7 @@ import { ContentToContentAction } from '../messages' import type { ResumeMonetizationPayload, StartMonetizationPayload, - StopMonetizationPayload + StopMonetizationPayload, } from '@/shared/messages' import type { Cradle } from '@/content/container' @@ -24,15 +24,15 @@ export class FrameManager { window, document, logger, - message + message, }) this.documentObserver = new MutationObserver((records) => - this.onWholeDocumentObserved(records) + this.onWholeDocumentObserved(records), ) this.frameAllowAttrObserver = new MutationObserver((records) => - this.onFrameAllowAttrChange(records) + this.onFrameAllowAttrChange(records), ) } @@ -52,7 +52,7 @@ export class FrameManager { private observeDocumentForFrames() { this.documentObserver.observe(this.document, { subtree: true, - childList: true + childList: true, }) } @@ -60,7 +60,7 @@ export class FrameManager { this.frameAllowAttrObserver.observe(frame, { childList: false, attributeOldValue: true, - attributeFilter: ['allow'] + attributeFilter: ['allow'], }) } @@ -94,7 +94,7 @@ export class FrameManager { private async onAddedFrame(frame: HTMLIFrameElement) { this.frames.set(frame, { frameId: null, - requestIds: [] + requestIds: [], }) } @@ -106,7 +106,7 @@ export class FrameManager { const stopMonetizationTags: StopMonetizationPayload[] = frameDetails?.requestIds.map((requestId) => ({ requestId, - intent: 'remove' + intent: 'remove', })) || [] if (stopMonetizationTags.length) { this.message.send('STOP_MONETIZATION', stopMonetizationTags) @@ -156,7 +156,7 @@ export class FrameManager { this.run() } }, - { once: true } + { once: true }, ) } @@ -185,7 +185,7 @@ export class FrameManager { ![ ContentToContentAction.INITIALIZE_IFRAME, ContentToContentAction.IS_MONETIZATION_ALLOWED_ON_START, - ContentToContentAction.IS_MONETIZATION_ALLOWED_ON_RESUME + ContentToContentAction.IS_MONETIZATION_ALLOWED_ON_RESUME, ].includes(message) ) { return @@ -203,7 +203,7 @@ export class FrameManager { event.stopPropagation() this.frames.set(frame, { frameId: id, - requestIds: [] + requestIds: [], }) return @@ -213,16 +213,16 @@ export class FrameManager { this.frames.set(frame, { frameId: id, requestIds: payload.map( - (p: StartMonetizationPayload) => p.requestId - ) + (p: StartMonetizationPayload) => p.requestId, + ), }) event.source.postMessage( { message: ContentToContentAction.START_MONETIZATION, id, - payload + payload, }, - '*' + '*', ) } @@ -234,16 +234,16 @@ export class FrameManager { this.frames.set(frame, { frameId: id, requestIds: payload.map( - (p: ResumeMonetizationPayload) => p.requestId - ) + (p: ResumeMonetizationPayload) => p.requestId, + ), }) event.source.postMessage( { message: ContentToContentAction.RESUME_MONETIZATION, id, - payload + payload, }, - '*' + '*', ) } return @@ -252,7 +252,7 @@ export class FrameManager { return } }, - { capture: true } + { capture: true }, ) } } diff --git a/src/content/services/monetizationTagManager.ts b/src/content/services/monetizationTagManager.ts index 7dd1cc6e..e9e4fea3 100644 --- a/src/content/services/monetizationTagManager.ts +++ b/src/content/services/monetizationTagManager.ts @@ -8,7 +8,7 @@ import type { MonetizationEventPayload, ResumeMonetizationPayload, StartMonetizationPayload, - StopMonetizationPayload + StopMonetizationPayload, } from '@/shared/messages' import { ContentToContentAction } from '../messages' import type { Cradle } from '@/content/container' @@ -39,14 +39,14 @@ export class MonetizationTagManager extends EventEmitter { window, document, logger, - message + message, }) this.documentObserver = new MutationObserver((records) => - this.onWholeDocumentObserved(records) + this.onWholeDocumentObserved(records), ) this.monetizationTagAttrObserver = new MutationObserver((records) => - this.onMonetizationTagAttrsChange(records) + this.onMonetizationTagAttrsChange(records), ) document.addEventListener('visibilitychange', async () => { @@ -81,8 +81,8 @@ export class MonetizationTagManager extends EventEmitter { tag.dispatchEvent( new CustomEvent('__wm_ext_monetization', { detail: mozClone(details, this.document), - bubbles: true - }) + bubbles: true, + }), ) }) return @@ -201,7 +201,7 @@ export class MonetizationTagManager extends EventEmitter { try { const { requestId, walletAddress } = this.getTagDetails( target, - 'onChangeDisabled' + 'onChangeDisabled', ) if (isDisabled) { stopMonetizationTags.push({ requestId, intent: 'disable' }) @@ -262,7 +262,7 @@ export class MonetizationTagManager extends EventEmitter { this.monetizationTagAttrObserver.observe(tag, { childList: false, attributeOldValue: true, - attributeFilter: ['href', 'disabled', 'rel', 'crossorigin', 'type'] + attributeFilter: ['href', 'disabled', 'rel', 'crossorigin', 'type'], }) } @@ -271,7 +271,7 @@ export class MonetizationTagManager extends EventEmitter { if (!tagDetails) { throw new Error( - `${caller}: tag not tracked: ${tag.outerHTML.slice(0, 200)}` + `${caller}: tag not tracked: ${tag.outerHTML.slice(0, 200)}`, ) } @@ -282,7 +282,7 @@ export class MonetizationTagManager extends EventEmitter { async onChangedWalletAddressUrl( tag: MonetizationTag, wasDisabled = false, - isDisabled = false + isDisabled = false, ) { let stopMonetizationTag = null @@ -304,7 +304,7 @@ export class MonetizationTagManager extends EventEmitter { ) { this.fireOnMonetizationAttrChangedEvent({ node: record.target, - changeDetected: true + changeDetected: true, }) } } @@ -312,7 +312,7 @@ export class MonetizationTagManager extends EventEmitter { private fireOnMonetizationAttrChangedEvent({ node, - changeDetected = false + changeDetected = false, }: FireOnMonetizationChangeIfHaveAttributeParams) { const attribute = node.getAttribute('onmonetization') @@ -320,7 +320,7 @@ export class MonetizationTagManager extends EventEmitter { const customEvent = new CustomEvent('__wm_ext_onmonetization_attr_change', { bubbles: true, - detail: mozClone({ attribute }, this.document) + detail: mozClone({ attribute }, this.document), }) node.dispatchEvent(customEvent) @@ -353,11 +353,11 @@ export class MonetizationTagManager extends EventEmitter { this.run() } }, - { once: true } + { once: true }, ) } }, - { once: true } + { once: true }, ) } @@ -366,9 +366,9 @@ export class MonetizationTagManager extends EventEmitter { this.window.parent.postMessage( { message: ContentToContentAction.INITIALIZE_IFRAME, - id: this.id + id: this.id, }, - '*' + '*', ) } @@ -376,7 +376,7 @@ export class MonetizationTagManager extends EventEmitter { if (this.isTopFrame) { monetizationTags = this.document.querySelectorAll( - 'link[rel="monetization"]' + 'link[rel="monetization"]', ) } else { const monetizationTag: MonetizationTag | null = @@ -418,7 +418,7 @@ export class MonetizationTagManager extends EventEmitter { this.documentObserver.observe(this.document, { subtree: true, childList: true, - attributeFilter: ['onmonetization'] + attributeFilter: ['onmonetization'], }) } @@ -439,7 +439,7 @@ export class MonetizationTagManager extends EventEmitter { // Add tag to list & start monetization private async onAddedTag( tag: MonetizationTag, - crtRequestId?: string + crtRequestId?: string, ): Promise { const walletAddress = await this.checkTag(tag) if (!walletAddress) return null @@ -447,7 +447,7 @@ export class MonetizationTagManager extends EventEmitter { const requestId = crtRequestId ?? crypto.randomUUID() const details: MonetizationTagDetails = { walletAddress, - requestId + requestId, } this.monetizationTags.set(tag, details) @@ -466,9 +466,9 @@ export class MonetizationTagManager extends EventEmitter { { message: ContentToContentAction.IS_MONETIZATION_ALLOWED_ON_START, id: this.id, - payload: tags + payload: tags, }, - '*' + '*', ) } } @@ -488,9 +488,9 @@ export class MonetizationTagManager extends EventEmitter { { message: ContentToContentAction.IS_MONETIZATION_ALLOWED_ON_RESUME, id: this.id, - payload: tags + payload: tags, }, - '*' + '*', ) } } @@ -508,18 +508,18 @@ export class MonetizationTagManager extends EventEmitter { } private async validateWalletAddress( - tag: MonetizationTag + tag: MonetizationTag, ): Promise { const walletAddressUrl = tag.href.trim() try { checkWalletAddressUrlFormat(walletAddressUrl) const response = await this.message.send('CHECK_WALLET_ADDRESS_URL', { - walletAddressUrl + walletAddressUrl, }) if (response.success === false) { throw new Error( - `Could not retrieve wallet address information for ${JSON.stringify(walletAddressUrl)}.` + `Could not retrieve wallet address information for ${JSON.stringify(walletAddressUrl)}.`, ) } diff --git a/src/content/utils.ts b/src/content/utils.ts index 0b59fc81..9be68bfa 100644 --- a/src/content/utils.ts +++ b/src/content/utils.ts @@ -7,7 +7,7 @@ export function checkWalletAddressUrlFormat(walletAddressUrl: string): void { if (url.protocol !== 'https:') { throw new WalletAddressFormatError( `Wallet address URL must be specified as a fully resolved https:// url, ` + - `got ${JSON.stringify(walletAddressUrl)} ` + `got ${JSON.stringify(walletAddressUrl)} `, ) } } catch (e) { @@ -15,7 +15,7 @@ export function checkWalletAddressUrlFormat(walletAddressUrl: string): void { throw e } else { throw new WalletAddressFormatError( - `Invalid wallet address URL: ${JSON.stringify(walletAddressUrl)}` + `Invalid wallet address URL: ${JSON.stringify(walletAddressUrl)}`, ) } } @@ -24,7 +24,7 @@ export function checkWalletAddressUrlFormat(walletAddressUrl: string): void { if (hash || search || port || username || password) { throw new WalletAddressFormatError( - `Wallet address URL must not contain query/fragment/port/username/password elements. Received: ${JSON.stringify({ hash, search, port, username, password })}` + `Wallet address URL must not contain query/fragment/port/username/password elements. Received: ${JSON.stringify({ hash, search, port, username, password })}`, ) } } diff --git a/src/popup/Popup.tsx b/src/popup/Popup.tsx index 8ff68902..9d355f2d 100644 --- a/src/popup/Popup.tsx +++ b/src/popup/Popup.tsx @@ -3,7 +3,7 @@ import { BrowserContextProvider, MessageContextProvider, PopupContextProvider, - TranslationContextProvider + TranslationContextProvider, } from './lib/context' import { LazyMotion, domAnimation } from 'framer-motion' import React from 'react' @@ -12,7 +12,7 @@ import { ProtectedRoute } from '@/popup/components/ProtectedRoute' import { RouteObject, RouterProvider, - createMemoryRouter + createMemoryRouter, } from 'react-router-dom' export const ROUTES_PATH = { @@ -21,7 +21,7 @@ export const ROUTES_PATH = { MISSING_HOST_PERMISSION: '/missing-host-permission', OUT_OF_FUNDS: '/out-of-funds', OUT_OF_FUNDS_ADD_FUNDS: '/out-of-funds/s/add-funds', - ERROR_KEY_REVOKED: '/error/key-revoked' + ERROR_KEY_REVOKED: '/error/key-revoked', } as const export const routes = [ @@ -33,36 +33,36 @@ export const routes = [ children: [ { path: ROUTES_PATH.HOME, - lazy: () => import('./pages/Home') - } - ] + lazy: () => import('./pages/Home'), + }, + ], }, { children: [ { path: ROUTES_PATH.MISSING_HOST_PERMISSION, - lazy: () => import('./pages/MissingHostPermission') + lazy: () => import('./pages/MissingHostPermission'), }, { path: ROUTES_PATH.ERROR_KEY_REVOKED, - lazy: () => import('./pages/ErrorKeyRevoked') + lazy: () => import('./pages/ErrorKeyRevoked'), }, { path: ROUTES_PATH.OUT_OF_FUNDS, - lazy: () => import('./pages/OutOfFunds') + lazy: () => import('./pages/OutOfFunds'), }, { path: ROUTES_PATH.OUT_OF_FUNDS_ADD_FUNDS, - lazy: () => import('./pages/OutOfFunds_AddFunds') + lazy: () => import('./pages/OutOfFunds_AddFunds'), }, { path: ROUTES_PATH.SETTINGS, - lazy: () => import('./pages/Settings') - } - ] - } - ] - } + lazy: () => import('./pages/Settings'), + }, + ], + }, + ], + }, ] satisfies RouteObject[] const router = createMemoryRouter(routes) diff --git a/src/popup/components/ConnectWalletForm.tsx b/src/popup/components/ConnectWalletForm.tsx index 74c0825d..878bf4f8 100644 --- a/src/popup/components/ConnectWalletForm.tsx +++ b/src/popup/components/ConnectWalletForm.tsx @@ -9,7 +9,7 @@ import { charIsNumber, formatNumber, getCurrencySymbol, - toWalletAddressUrl + toWalletAddressUrl, } from '@/popup/lib/utils' import { useForm } from 'react-hook-form' import { useMessage } from '@/popup/lib/context' @@ -32,7 +32,7 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { formState: { errors, isSubmitting }, clearErrors, setError, - setValue + setValue, } = useForm({ criteriaMode: 'firstError', mode: 'onSubmit', @@ -40,8 +40,8 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { defaultValues: { recurring: localStorage?.getItem('recurring') === 'true' || false, amount: localStorage?.getItem('amountValue') || undefined, - walletAddressUrl: localStorage?.getItem('walletAddressUrl') || undefined - } + walletAddressUrl: localStorage?.getItem('walletAddressUrl') || undefined, + }, }) const [currencySymbol, setCurrencySymbol] = React.useState<{ symbol: string @@ -57,24 +57,24 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { const walletAddress = await getWalletInformation(url.toString()) setCurrencySymbol({ symbol: getCurrencySymbol(walletAddress.assetCode), - scale: walletAddress.assetScale + scale: walletAddress.assetScale, }) } catch { setError('walletAddressUrl', { type: 'validate', - message: 'Invalid wallet address.' + message: 'Invalid wallet address.', }) } }, - [clearErrors, setError] + [clearErrors, setError], ) const handleOnChangeAmount = async ( - e: React.ChangeEvent + e: React.ChangeEvent, ) => { const amountValue = formatNumber( +e.currentTarget.value, - currencySymbol.scale + currencySymbol.scale, ) debounceSync(() => { localStorage?.setItem('amountValue', amountValue) @@ -82,7 +82,7 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { } const handleOnChangeWalletAddressUrl = async ( - e: React.ChangeEvent + e: React.ChangeEvent, ) => { const walletAddressUrl = e.currentTarget.value debounceSync(() => { @@ -94,7 +94,7 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { const recurring = e.currentTarget.checked debounceSync( () => localStorage?.setItem('recurring', `${recurring}`), - 100 + 100, )() } @@ -110,12 +110,12 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { onSubmit={handleSubmit(async (data) => { const response = await message.send('CONNECT_WALLET', { ...data, - walletAddressUrl: toWalletAddressUrl(data.walletAddressUrl) + walletAddressUrl: toWalletAddressUrl(data.walletAddressUrl), }) if (!response.success) { setError('walletAddressUrl', { type: 'validate', - message: response.message + message: response.message, }) } })} @@ -150,7 +150,7 @@ export const ConnectWalletForm = ({ publicKey }: ConnectWalletFormProps) => { onBlur: (e: React.FocusEvent) => { getWalletCurrency(e.currentTarget.value) }, - onChange: handleOnChangeWalletAddressUrl + onChange: handleOnChangeWalletAddressUrl, })} /> { onBlur: (e: React.FocusEvent) => { setValue( 'amount', - formatNumber(+e.currentTarget.value, currencySymbol.scale) + formatNumber(+e.currentTarget.value, currencySymbol.scale), ) }, - onChange: handleOnChangeAmount + onChange: handleOnChangeAmount, })} />
diff --git a/src/popup/components/ErrorKeyRevoked.tsx b/src/popup/components/ErrorKeyRevoked.tsx index 3d83ab2c..0822d44e 100644 --- a/src/popup/components/ErrorKeyRevoked.tsx +++ b/src/popup/components/ErrorKeyRevoked.tsx @@ -24,12 +24,12 @@ export const ErrorKeyRevoked = ({ disconnectWallet, reconnectWallet, onReconnect, - onDisconnect + onDisconnect, }: Props) => { const [screen, setScreen, clearScreen] = useLocalStorage( 'keyRevokedScreen', 'main', - { maxAge: 2 * 60 } + { maxAge: 2 * 60 }, ) if (screen === 'main') { @@ -67,7 +67,7 @@ interface MainScreenProps { const MainScreen = ({ disconnectWallet, onDisconnect, - requestReconnect + requestReconnect, }: MainScreenProps) => { const t = useTranslation() const [errorMsg, setErrorMsg] = React.useState('') @@ -127,14 +127,14 @@ interface ReconnectScreenProps { const ReconnectScreen = ({ info, reconnectWallet, - onReconnect + onReconnect, }: ReconnectScreenProps) => { const t = useTranslation() const { handleSubmit, formState: { errors, isSubmitting }, clearErrors, - setError + setError, } = useForm({ criteriaMode: 'firstError', mode: 'onSubmit' }) const requestReconnect = async () => { diff --git a/src/popup/components/ErrorMessage.tsx b/src/popup/components/ErrorMessage.tsx index 060006a2..ee581a6c 100644 --- a/src/popup/components/ErrorMessage.tsx +++ b/src/popup/components/ErrorMessage.tsx @@ -15,7 +15,7 @@ export const ErrorMessage = React.forwardRef( ref={ref} className={cn( 'break-word mb-4 flex items-center gap-2 rounded-xl border border-red-300 bg-red-500/10 px-3 py-2', - className + className, )} > @@ -25,7 +25,7 @@ export const ErrorMessage = React.forwardRef(
) - } + }, ) ErrorMessage.displayName = 'ErrorMessage' diff --git a/src/popup/components/LoadingSpinner.tsx b/src/popup/components/LoadingSpinner.tsx index 5e6e10b0..108b8a85 100644 --- a/src/popup/components/LoadingSpinner.tsx +++ b/src/popup/components/LoadingSpinner.tsx @@ -7,12 +7,12 @@ const loadingSpinnerStyles = cva('animate-spin text-white', { variants: { variant: { md: 'h-4 w-4', - lg: 'h-6 w-6' - } + lg: 'h-6 w-6', + }, }, defaultVariants: { - variant: 'lg' - } + variant: 'lg', + }, }) export type LoadingIndicatorProps = VariantProps diff --git a/src/popup/components/OutOfFunds.tsx b/src/popup/components/OutOfFunds.tsx index ca7367bd..eec43dfd 100644 --- a/src/popup/components/OutOfFunds.tsx +++ b/src/popup/components/OutOfFunds.tsx @@ -7,7 +7,7 @@ import { charIsNumber, formatNumber, getCurrencySymbol, - transformBalance + transformBalance, } from '@/popup/lib/utils' import { useTranslation } from '@/popup/lib/context' import { getNextOccurrence } from '@/shared/helpers' @@ -26,7 +26,7 @@ export const OutOfFunds = ({ info, grantOneTime, grantRecurring, - onChooseOption + onChooseOption, }: OutOfFundsProps) => { if (!grantOneTime && !grantRecurring) { throw new Error('Provide at least one of grantOneTime and grantRecurring') @@ -75,7 +75,7 @@ export function AddFunds({ info, defaultAmount, recurring, - requestAddFunds + requestAddFunds, }: AddFundsProps) { const t = useTranslation() const { @@ -83,14 +83,14 @@ export function AddFunds({ handleSubmit, formState: { errors, isSubmitting }, setError, - setValue + setValue, } = useForm({ criteriaMode: 'firstError', mode: 'onSubmit', reValidateMode: 'onBlur', defaultValues: { - amount: transformBalance(defaultAmount, info.assetScale) - } + amount: transformBalance(defaultAmount, info.assetScale), + }, }) const currencySymbol = getCurrencySymbol(info.assetCode) @@ -101,7 +101,7 @@ export function AddFunds({ onSubmit={handleSubmit(async (data) => { const response = await requestAddFunds({ amount: data.amount, - recurring: !!recurring + recurring: !!recurring, }) if (!response.success) { setError('root', { message: response.message }) @@ -124,7 +124,7 @@ export function AddFunds({ description={ recurring ? t('outOfFundsAddFunds_label_amountDescriptionRecurring', [ - getNextOccurrenceDate('P1M') + getNextOccurrenceDate('P1M'), ]) : t('outOfFundsAddFunds_label_amountDescriptionOneTime') } @@ -147,7 +147,7 @@ export function AddFunds({ valueAsNumber: false, onBlur: (e: React.FocusEvent) => { setValue('amount', formatNumber(+e.currentTarget.value, 2)) - } + }, })} /> @@ -169,7 +169,7 @@ export function AddFunds({ function RecurringAutoRenewInfo({ grantRecurring, - info + info, }: Pick) { const t = useTranslation() @@ -180,19 +180,19 @@ function RecurringAutoRenewInfo({ const renewDate = getNextOccurrence(grantRecurring.interval, new Date()) const renewDateLocalized = renewDate.toLocaleString(undefined, { dateStyle: 'medium', - timeStyle: 'short' + timeStyle: 'short', }) return t('outOfFunds_error_textDoNothing', [ `${currencySymbol}${amount}`, - renewDateLocalized + renewDateLocalized, ]) } function getNextOccurrenceDate(period: 'P1M', baseDate = new Date()) { const date = getNextOccurrence( `R/${baseDate.toISOString()}/${period}`, - baseDate + baseDate, ) return date.toLocaleDateString(undefined, { dateStyle: 'medium' }) } diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index 9807fba8..c6de3231 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -4,7 +4,7 @@ import { useMessage, usePopupState } from '@/popup/lib/context' import { getCurrencySymbol, charIsNumber, - formatNumber + formatNumber, } from '@/popup/lib/utils' import React, { useMemo } from 'react' import { useForm } from 'react-hook-form' @@ -20,13 +20,13 @@ interface PayWebsiteFormProps { const BUTTON_STATE = { idle: 'Send now', loading: , - success: 'Payment successful' + success: 'Payment successful', } export const PayWebsiteForm = () => { const message = useMessage() const { - state: { walletAddress, url } + state: { walletAddress, url }, } = usePopupState() const [buttonState, setButtonState] = React.useState('idle') @@ -66,7 +66,7 @@ export const PayWebsiteForm = () => { { onBlur: (e: React.FocusEvent) => { setValue( 'amount', - formatNumber(+e.currentTarget.value, walletAddress.assetScale) + formatNumber(+e.currentTarget.value, walletAddress.assetScale), ) - } + }, })} /> ) - } + }, ) diff --git a/src/popup/components/ui/Code.tsx b/src/popup/components/ui/Code.tsx index 931e0b39..adde744f 100644 --- a/src/popup/components/ui/Code.tsx +++ b/src/popup/components/ui/Code.tsx @@ -12,7 +12,7 @@ export const Code = ({ value, className, ...props }: CodeProps) => {
diff --git a/src/popup/components/ui/Input.tsx b/src/popup/components/ui/Input.tsx index 6d9cf837..0ded69e2 100644 --- a/src/popup/components/ui/Input.tsx +++ b/src/popup/components/ui/Input.tsx @@ -7,22 +7,22 @@ const inputVariants = cva( [ 'h-14 w-full rounded-xl border border-2 px-4 text-base text-medium', 'focus:border-focus focus:outline-none', - 'placeholder-disabled' + 'placeholder-disabled', ], { variants: { variant: { - default: 'border-base' + default: 'border-base', }, disabled: { - true: 'border-transparent bg-disabled' - } + true: 'border-transparent bg-disabled', + }, }, defaultVariants: { - variant: 'default' - } - } + variant: 'default', + }, + }, ) export interface InputProps @@ -46,7 +46,7 @@ export const Input = forwardRef(function Input( className, ...props }, - ref + ref, ) { const id = React.useId() return ( @@ -67,7 +67,7 @@ export const Input = forwardRef(function Input( inputVariants({ disabled }), addOn && 'pl-10', errorMessage && 'border-error', - className + className, )} disabled={disabled ?? false} aria-disabled={disabled ?? false} diff --git a/src/popup/components/ui/Label.tsx b/src/popup/components/ui/Label.tsx index 40502dc2..9596c551 100644 --- a/src/popup/components/ui/Label.tsx +++ b/src/popup/components/ui/Label.tsx @@ -4,7 +4,7 @@ import React, { forwardRef } from 'react' import { cn } from '@/shared/helpers' const labelVariants = cva( - 'flex items-center px-2 font-medium leading-6 text-medium' + 'flex items-center px-2 font-medium leading-6 text-medium', ) export interface LabelProps @@ -15,7 +15,7 @@ export interface LabelProps export const Label = forwardRef(function Label( { className, children, ...props }, - ref + ref, ) { return (
- ) -} + ); +}; diff --git a/src/popup/components/layout/MainLayout.tsx b/src/popup/components/layout/MainLayout.tsx index e4e175ac..4f8174b5 100644 --- a/src/popup/components/layout/MainLayout.tsx +++ b/src/popup/components/layout/MainLayout.tsx @@ -1,11 +1,11 @@ -import React from 'react' -import { Outlet } from 'react-router-dom' +import React from 'react'; +import { Outlet } from 'react-router-dom'; -import { Header } from './Header' +import { Header } from './Header'; const Divider = () => { - return
-} + return
; +}; export const MainLayout = () => { return ( @@ -16,5 +16,5 @@ export const MainLayout = () => {
- ) -} + ); +}; diff --git a/src/popup/components/ui/Button.tsx b/src/popup/components/ui/Button.tsx index dedc0719..bc21e6fd 100644 --- a/src/popup/components/ui/Button.tsx +++ b/src/popup/components/ui/Button.tsx @@ -1,8 +1,8 @@ -import { type VariantProps, cva } from 'class-variance-authority' -import React, { forwardRef } from 'react' +import { type VariantProps, cva } from 'class-variance-authority'; +import React, { forwardRef } from 'react'; -import { LoadingSpinner } from '@/popup/components/LoadingSpinner' -import { cn } from '@/shared/helpers' +import { LoadingSpinner } from '@/popup/components/LoadingSpinner'; +import { cn } from '@/shared/helpers'; const buttonVariants = cva( [ @@ -34,14 +34,14 @@ const buttonVariants = cva( size: 'default', }, }, -) +); export interface ButtonProps extends VariantProps, React.ButtonHTMLAttributes { - loading?: boolean + loading?: boolean; /** Optional only when children are passed */ - ['aria-label']?: string + ['aria-label']?: string; } export const Button = forwardRef( @@ -72,6 +72,6 @@ export const Button = forwardRef( > {loading ? : children} - ) + ); }, -) +); diff --git a/src/popup/components/ui/Code.tsx b/src/popup/components/ui/Code.tsx index adde744f..71998828 100644 --- a/src/popup/components/ui/Code.tsx +++ b/src/popup/components/ui/Code.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { Button } from './Button' -import { CheckIcon, ClipboardIcon } from '../Icons' -import { cn } from '@/shared/helpers' +import React from 'react'; +import { Button } from './Button'; +import { CheckIcon, ClipboardIcon } from '../Icons'; +import { cn } from '@/shared/helpers'; interface CodeProps extends React.HTMLAttributes { - value: string + value: string; } export const Code = ({ value, className, ...props }: CodeProps) => { @@ -21,23 +21,23 @@ export const Code = ({ value, className, ...props }: CodeProps) => {
- ) -} + ); +}; interface CopyButtonProps extends React.HTMLAttributes { - value: string + value: string; } const CopyButton = ({ value, ...props }: CopyButtonProps) => { - const [hasCopied, setHasCopied] = React.useState(false) + const [hasCopied, setHasCopied] = React.useState(false); React.useEffect(() => { if (hasCopied === true) { setTimeout(() => { - setHasCopied(false) - }, 2000) + setHasCopied(false); + }, 2000); } - }, [hasCopied]) + }, [hasCopied]); return ( - ) -} + ); +}; diff --git a/src/popup/components/ui/Input.tsx b/src/popup/components/ui/Input.tsx index 0ded69e2..49c4a65e 100644 --- a/src/popup/components/ui/Input.tsx +++ b/src/popup/components/ui/Input.tsx @@ -1,7 +1,7 @@ -import { type VariantProps, cva } from 'class-variance-authority' -import React, { forwardRef } from 'react' -import { cn } from '@/shared/helpers' -import { Label } from '@/popup/components/ui/Label' +import { type VariantProps, cva } from 'class-variance-authority'; +import React, { forwardRef } from 'react'; +import { cn } from '@/shared/helpers'; +import { Label } from '@/popup/components/ui/Label'; const inputVariants = cva( [ @@ -23,16 +23,16 @@ const inputVariants = cva( variant: 'default', }, }, -) +); export interface InputProps extends VariantProps, React.InputHTMLAttributes { - errorMessage?: string - disabled?: boolean - addOn?: React.ReactNode - label?: React.ReactNode - description?: React.ReactNode + errorMessage?: string; + disabled?: boolean; + addOn?: React.ReactNode; + label?: React.ReactNode; + description?: React.ReactNode; } export const Input = forwardRef(function Input( @@ -48,7 +48,7 @@ export const Input = forwardRef(function Input( }, ref, ) { - const id = React.useId() + const id = React.useId(); return (
{label ? : null} @@ -80,5 +80,5 @@ export const Input = forwardRef(function Input(

{errorMessage}

)}
- ) -}) + ); +}); diff --git a/src/popup/components/ui/Label.tsx b/src/popup/components/ui/Label.tsx index 9596c551..b17df2f3 100644 --- a/src/popup/components/ui/Label.tsx +++ b/src/popup/components/ui/Label.tsx @@ -1,16 +1,16 @@ -import { type VariantProps, cva } from 'class-variance-authority' -import React, { forwardRef } from 'react' +import { type VariantProps, cva } from 'class-variance-authority'; +import React, { forwardRef } from 'react'; -import { cn } from '@/shared/helpers' +import { cn } from '@/shared/helpers'; const labelVariants = cva( 'flex items-center px-2 font-medium leading-6 text-medium', -) +); export interface LabelProps extends VariantProps, React.LabelHTMLAttributes { - children: React.ReactNode + children: React.ReactNode; } export const Label = forwardRef(function Label( @@ -21,5 +21,5 @@ export const Label = forwardRef(function Label( - ) -}) + ); +}); diff --git a/src/popup/components/ui/RadioGroup.tsx b/src/popup/components/ui/RadioGroup.tsx index 7ed959e9..c3ff2a51 100644 --- a/src/popup/components/ui/RadioGroup.tsx +++ b/src/popup/components/ui/RadioGroup.tsx @@ -1,17 +1,17 @@ -import { type VariantProps, cva } from 'class-variance-authority' -import React, { useEffect, useMemo, useState } from 'react' +import { type VariantProps, cva } from 'class-variance-authority'; +import React, { useEffect, useMemo, useState } from 'react'; -import { cn } from '@/shared/helpers' +import { cn } from '@/shared/helpers'; export interface RadioProps { - checked?: boolean - label?: string - value: string - name: string - id?: string - disabled?: boolean - onChange?: any - noSelected?: boolean + checked?: boolean; + label?: string; + value: string; + name: string; + id?: string; + disabled?: boolean; + onChange?: any; + noSelected?: boolean; } export const Radio = ({ @@ -24,12 +24,12 @@ export const Radio = ({ checked, noSelected, }: RadioProps): JSX.Element => { - const inputId = id || `id-${name}-${value}` - const divId = `div-${inputId}` + const inputId = id || `id-${name}-${value}`; + const divId = `div-${inputId}`; useEffect(() => { - if (checked) document.getElementById(divId)?.focus() - }, [checked, divId]) + if (checked) document.getElementById(divId)?.focus(); + }, [checked, divId]); return (
- ) -} + ); +}; const radioGroupVariants = cva(['flex gap-3'], { variants: { @@ -78,15 +78,15 @@ const radioGroupVariants = cva(['flex gap-3'], { defaultVariants: { variant: 'default', }, -}) +}); export interface RadioGroupProps extends VariantProps, React.InputHTMLAttributes { - disabled?: boolean - items: Omit[] - name: string - handleChange?: (value: string) => void + disabled?: boolean; + items: Omit[]; + name: string; + handleChange?: (value: string) => void; } export const RadioGroup = ({ @@ -102,22 +102,22 @@ export const RadioGroup = ({ const checkedItem = useMemo( () => items.findIndex((item) => item.checked || item.value === value), [items, value], - ) - const [selected, setSelected] = useState(checkedItem) + ); + const [selected, setSelected] = useState(checkedItem); const handleKeyDown = (event: React.KeyboardEvent) => { if (event.code === 'ArrowRight' || event.code === 'ArrowDown') { - event.preventDefault() + event.preventDefault(); - const nextIndex = (selected >= 0 ? selected + 1 : 1) % items.length - setSelected(nextIndex) + const nextIndex = (selected >= 0 ? selected + 1 : 1) % items.length; + setSelected(nextIndex); } else if (event.code === 'ArrowLeft' || event.code === 'ArrowUp') { - event.preventDefault() + event.preventDefault(); - const prevIndex = selected > 0 ? selected - 1 : items.length - 1 - setSelected(prevIndex) + const prevIndex = selected > 0 ? selected - 1 : items.length - 1; + setSelected(prevIndex); } - } + }; useEffect(() => { const handleKeyPress = (event: KeyboardEvent) => { @@ -125,15 +125,15 @@ export const RadioGroup = ({ selected === -1 && (event.code === 'Enter' || event.code === 'Space') ) { - setSelected(0) + setSelected(0); } - } + }; - document.addEventListener('keypress', handleKeyPress) + document.addEventListener('keypress', handleKeyPress); return () => { - document.removeEventListener('keypress', handleKeyPress) - } - }, [selected]) + document.removeEventListener('keypress', handleKeyPress); + }; + }, [selected]); return (
{ - setSelected(index) - if (handleChange) handleChange(item.value) + setSelected(index); + if (handleChange) handleChange(item.value); }} /> ))}
- ) -} + ); +}; diff --git a/src/popup/components/ui/Slider.tsx b/src/popup/components/ui/Slider.tsx index 7a47aa80..da729c6d 100644 --- a/src/popup/components/ui/Slider.tsx +++ b/src/popup/components/ui/Slider.tsx @@ -1,16 +1,16 @@ -import React, { forwardRef } from 'react' +import React, { forwardRef } from 'react'; -import { cn } from '@/shared/helpers' +import { cn } from '@/shared/helpers'; export interface SliderProps extends React.InputHTMLAttributes { - errorMessage?: string - disabled?: boolean - icon?: React.ReactNode - min?: number - max?: number - value?: number - onChange?: (_event: React.ChangeEvent) => void + errorMessage?: string; + disabled?: boolean; + icon?: React.ReactNode; + min?: number; + max?: number; + value?: number; + onChange?: (_event: React.ChangeEvent) => void; } const sliderClasses = ` @@ -27,7 +27,7 @@ const sliderClasses = ` [&::-webkit-slider-thumb]:disabled:bg-disabled-strong w-full h-1 bg-disabled-strong rounded-lg appearance-none cursor-pointer dark:bg-disabled-strong -` +`; export const Slider = forwardRef(function Slider( { @@ -61,5 +61,5 @@ export const Slider = forwardRef(function Slider(

{errorMessage}

)} - ) -}) + ); +}); diff --git a/src/popup/components/ui/Switch.tsx b/src/popup/components/ui/Switch.tsx index 8a2ce80e..24146770 100644 --- a/src/popup/components/ui/Switch.tsx +++ b/src/popup/components/ui/Switch.tsx @@ -1,7 +1,7 @@ -import { type VariantProps, cva } from 'class-variance-authority' -import React, { forwardRef } from 'react' +import { type VariantProps, cva } from 'class-variance-authority'; +import React, { forwardRef } from 'react'; -import { cn } from '@/shared/helpers' +import { cn } from '@/shared/helpers'; const switchVariants = cva( [ @@ -27,14 +27,14 @@ const switchVariants = cva( size: 'default', }, }, -) +); export interface SwitchProps extends VariantProps, React.HTMLAttributes { - checked?: boolean - label?: string - onChange?: (e: React.ChangeEvent) => void + checked?: boolean; + label?: string; + onChange?: (e: React.ChangeEvent) => void; } export const Switch = forwardRef(function Switch( @@ -55,5 +55,5 @@ export const Switch = forwardRef(function Switch(
{label ? {label} : null} - ) -}) + ); +}); diff --git a/src/popup/components/ui/__tests__/Button.test.tsx b/src/popup/components/ui/__tests__/Button.test.tsx index 24a58254..dab04dbd 100644 --- a/src/popup/components/ui/__tests__/Button.test.tsx +++ b/src/popup/components/ui/__tests__/Button.test.tsx @@ -1,68 +1,68 @@ -import { render } from '@testing-library/react' -import React from 'react' +import { render } from '@testing-library/react'; +import React from 'react'; -import { Button } from '@/popup/components/ui/Button' +import { Button } from '@/popup/components/ui/Button'; describe('Button', () => { it('should render a button with the `aria-label` attribute', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).toHaveAttribute('aria-label', 'test button') - }) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).toHaveAttribute('aria-label', 'test button'); + }); it('should default to `type="button"`', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).toHaveAttribute('type', 'button') - }) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).toHaveAttribute('type', 'button'); + }); it('should not have the `disabled` attribute and `aria-disabled="false"` if `loading` is false', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).not.toHaveAttribute('disabled') - expect(queryByRole('button')).toHaveAttribute('aria-disabled', 'false') - expect(queryByRole('button')).not.toBeDisabled() - }) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).not.toHaveAttribute('disabled'); + expect(queryByRole('button')).toHaveAttribute('aria-disabled', 'false'); + expect(queryByRole('button')).not.toBeDisabled(); + }); it('should have the `disabled` and `aria-disabled="true"` attributes if `loading` is true', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).toHaveAttribute('disabled') - expect(queryByRole('button')).toHaveAttribute('aria-disabled', 'true') - expect(queryByRole('button')).toBeDisabled() - }) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).toHaveAttribute('disabled'); + expect(queryByRole('button')).toHaveAttribute('aria-disabled', 'true'); + expect(queryByRole('button')).toBeDisabled(); + }); it('should have the `bg-button-base` class by default', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).toHaveClass('bg-button-base') - }) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).toHaveClass('bg-button-base'); + }); it('should have the `bg-error` class when the `destructive` variant is passed', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('button')).toBeInTheDocument() - expect(queryByRole('button')).toHaveClass('bg-error') - }) -}) + expect(queryByRole('button')).toBeInTheDocument(); + expect(queryByRole('button')).toHaveClass('bg-error'); + }); +}); diff --git a/src/popup/components/ui/__tests__/Code.test.tsx b/src/popup/components/ui/__tests__/Code.test.tsx index 53408882..fe4fb530 100644 --- a/src/popup/components/ui/__tests__/Code.test.tsx +++ b/src/popup/components/ui/__tests__/Code.test.tsx @@ -1,30 +1,30 @@ -import { fireEvent, render } from '@testing-library/react' -import React from 'react' +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; -import { Code } from '../Code' +import { Code } from '../Code'; describe('Code', () => { it('should render the code component', () => { - const { queryByRole, container } = render() - const code = container.querySelector('code') + const { queryByRole, container } = render(); + const code = container.querySelector('code'); - expect(code).toBeInTheDocument() - expect(code).toHaveTextContent('test') - expect(queryByRole('button')).toHaveAttribute('aria-label', 'copy') - }) + expect(code).toBeInTheDocument(); + expect(code).toHaveTextContent('test'); + expect(queryByRole('button')).toHaveAttribute('aria-label', 'copy'); + }); it('calls clipboard.writeText with the correct value', () => { Object.assign(navigator, { clipboard: { writeText: jest.fn(), }, - }) + }); - const { getByRole } = render() - const copyButton = getByRole('button') - expect(copyButton).toBeInTheDocument() + const { getByRole } = render(); + const copyButton = getByRole('button'); + expect(copyButton).toBeInTheDocument(); - fireEvent.click(copyButton) - expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test') - }) -}) + fireEvent.click(copyButton); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test'); + }); +}); diff --git a/src/popup/components/ui/__tests__/Input.tsx b/src/popup/components/ui/__tests__/Input.tsx index 0fdbfee7..d4f120ad 100644 --- a/src/popup/components/ui/__tests__/Input.tsx +++ b/src/popup/components/ui/__tests__/Input.tsx @@ -1,62 +1,62 @@ -import { render } from '@testing-library/react' -import React from 'react' +import { render } from '@testing-library/react'; +import React from 'react'; -import { Input } from '@/popup/components/ui/Input' +import { Input } from '@/popup/components/ui/Input'; describe('Input', () => { it('should default to `type="text"`', () => { - const { queryByLabelText } = render() + const { queryByLabelText } = render(); - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).toHaveAttribute('type', 'text') - }) + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).toHaveAttribute('type', 'text'); + }); it('should not have the `disabled` attribute and `aria-disabled="false"` if `loading` is false', () => { - const { queryByLabelText } = render() + const { queryByLabelText } = render(); - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).not.toHaveAttribute('disabled') + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).not.toHaveAttribute('disabled'); expect(queryByLabelText('test input')).toHaveAttribute( 'aria-disabled', 'false', - ) - expect(queryByLabelText('test input')).not.toBeDisabled() - }) + ); + expect(queryByLabelText('test input')).not.toBeDisabled(); + }); it('should have the `border-base` class by default', () => { - const { queryByLabelText } = render() + const { queryByLabelText } = render(); - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).toHaveClass('border-base') - }) + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).toHaveClass('border-base'); + }); it('should have the `pl-10` class when the `addOn` variant is passed', () => { const { queryByLabelText } = render( , - ) + ); - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).toHaveClass('pl-10') - }) + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).toHaveClass('pl-10'); + }); it('should have the `bg-disabled` and `border-transparent` classes when the `disabled` variant is passed', () => { const { queryByLabelText } = render( , - ) + ); - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).toHaveClass('bg-disabled') - expect(queryByLabelText('test input')).toHaveClass('border-transparent') - }) + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).toHaveClass('bg-disabled'); + expect(queryByLabelText('test input')).toHaveClass('border-transparent'); + }); it('should have the `aria-invalid` and `aria-describedby` attributes if errorMessage is present', () => { const { queryByLabelText, queryByText } = render( , - ) - - expect(queryByLabelText('test input')).toBeInTheDocument() - expect(queryByLabelText('test input')).toHaveAttribute('aria-invalid') - expect(queryByLabelText('test input')).toHaveAttribute('aria-describedby') - expect(queryByText('some error')).toBeInTheDocument() - }) -}) + ); + + expect(queryByLabelText('test input')).toBeInTheDocument(); + expect(queryByLabelText('test input')).toHaveAttribute('aria-invalid'); + expect(queryByLabelText('test input')).toHaveAttribute('aria-describedby'); + expect(queryByText('some error')).toBeInTheDocument(); + }); +}); diff --git a/src/popup/components/ui/__tests__/RadioGroup.tsx b/src/popup/components/ui/__tests__/RadioGroup.tsx index d5019a9b..5004a2c1 100644 --- a/src/popup/components/ui/__tests__/RadioGroup.tsx +++ b/src/popup/components/ui/__tests__/RadioGroup.tsx @@ -1,116 +1,116 @@ -import { fireEvent, render } from '@testing-library/react' -import React from 'react' +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; -import { Radio, RadioGroup } from '../RadioGroup' +import { Radio, RadioGroup } from '../RadioGroup'; describe('RadioGroup', () => { const radioItems = [ { label: 'Option 1', value: 'option1', checked: true }, { label: 'Option 2', value: 'option2' }, - ] + ]; it('should have the `flex-row` class when the `inline` variant is passed', () => { const { queryByRole } = render( , - ) + ); - expect(queryByRole('radiogroup')).toBeInTheDocument() - expect(queryByRole('radiogroup')).toHaveClass('flex-row') - }) + expect(queryByRole('radiogroup')).toBeInTheDocument(); + expect(queryByRole('radiogroup')).toHaveClass('flex-row'); + }); it('renders radio group correctly with items', () => { const { getByRole } = render( , - ) + ); - const radioGroup = getByRole('radiogroup') - expect(radioGroup).toBeInTheDocument() - expect(radioGroup.childNodes.length).toBe(2) // Ensure two radio buttons are rendered - }) + const radioGroup = getByRole('radiogroup'); + expect(radioGroup).toBeInTheDocument(); + expect(radioGroup.childNodes.length).toBe(2); // Ensure two radio buttons are rendered + }); it('renders radio group with no element checked by default', () => { const radioItemsNotChecked = [ { label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' }, - ] + ]; const { getByLabelText } = render( , - ) + ); - const firstRadioButton = getByLabelText('Option 1') - const secondRadioButton = getByLabelText('Option 2') + const firstRadioButton = getByLabelText('Option 1'); + const secondRadioButton = getByLabelText('Option 2'); - expect(firstRadioButton).not.toBeChecked() - expect(secondRadioButton).not.toBeChecked() - }) + expect(firstRadioButton).not.toBeChecked(); + expect(secondRadioButton).not.toBeChecked(); + }); it('handles keyboard navigation', () => { const { getByLabelText } = render( , - ) + ); - const radioGroup = getByLabelText('Option 1') - fireEvent.keyDown(radioGroup, { key: 'ArrowRight', code: 'ArrowRight' }) - let secondRadioButton = getByLabelText('Option 2') - expect(secondRadioButton).toBeChecked() + const radioGroup = getByLabelText('Option 1'); + fireEvent.keyDown(radioGroup, { key: 'ArrowRight', code: 'ArrowRight' }); + let secondRadioButton = getByLabelText('Option 2'); + expect(secondRadioButton).toBeChecked(); - fireEvent.keyDown(radioGroup, { key: 'ArrowLeft', code: 'ArrowLeft' }) - let firstRadioButton = getByLabelText('Option 1') - expect(firstRadioButton).toBeChecked() + fireEvent.keyDown(radioGroup, { key: 'ArrowLeft', code: 'ArrowLeft' }); + let firstRadioButton = getByLabelText('Option 1'); + expect(firstRadioButton).toBeChecked(); - fireEvent.keyDown(radioGroup, { key: 'ArrowUp', code: 'ArrowUp' }) - secondRadioButton = getByLabelText('Option 2') - expect(secondRadioButton).toBeChecked() + fireEvent.keyDown(radioGroup, { key: 'ArrowUp', code: 'ArrowUp' }); + secondRadioButton = getByLabelText('Option 2'); + expect(secondRadioButton).toBeChecked(); - fireEvent.keyDown(radioGroup, { key: 'ArrowDown', code: 'ArrowDown' }) - firstRadioButton = getByLabelText('Option 1') - expect(firstRadioButton).toBeChecked() - }) + fireEvent.keyDown(radioGroup, { key: 'ArrowDown', code: 'ArrowDown' }); + firstRadioButton = getByLabelText('Option 1'); + expect(firstRadioButton).toBeChecked(); + }); it('changes selection on arrow keys', () => { const { getByLabelText } = render( , - ) + ); - const radioGroup = getByLabelText('Option 1') - fireEvent.keyDown(radioGroup, { key: 'ArrowRight', code: 'ArrowRight' }) - fireEvent.keyDown(radioGroup, { key: 'Enter', code: 'Enter' }) - const secondRadioButton = getByLabelText('Option 2') - expect(secondRadioButton).toBeChecked() - }) + const radioGroup = getByLabelText('Option 1'); + fireEvent.keyDown(radioGroup, { key: 'ArrowRight', code: 'ArrowRight' }); + fireEvent.keyDown(radioGroup, { key: 'Enter', code: 'Enter' }); + const secondRadioButton = getByLabelText('Option 2'); + expect(secondRadioButton).toBeChecked(); + }); it('changes selection on clicking radio buttons', () => { const { getByLabelText } = render( , - ) + ); - const secondRadioButton = getByLabelText('Option 2') - fireEvent.click(secondRadioButton) - expect(secondRadioButton).toBeChecked() - }) -}) + const secondRadioButton = getByLabelText('Option 2'); + fireEvent.click(secondRadioButton); + expect(secondRadioButton).toBeChecked(); + }); +}); describe('Radio', () => { it('renders radio button correctly with label', () => { const { getByLabelText } = render( , - ) + ); - const radioButton = getByLabelText('Option 1') - expect(radioButton).toBeInTheDocument() - expect(radioButton).toHaveAttribute('type', 'radio') - expect(radioButton).not.toBeChecked() + const radioButton = getByLabelText('Option 1'); + expect(radioButton).toBeInTheDocument(); + expect(radioButton).toHaveAttribute('type', 'radio'); + expect(radioButton).not.toBeChecked(); - fireEvent.click(radioButton) - expect(radioButton).toBeChecked() - }) + fireEvent.click(radioButton); + expect(radioButton).toBeChecked(); + }); it('renders disabled radio button', () => { const { getByLabelText } = render( , - ) + ); - const radioButton = getByLabelText('Option 1') - expect(radioButton).toBeDisabled() - }) -}) + const radioButton = getByLabelText('Option 1'); + expect(radioButton).toBeDisabled(); + }); +}); diff --git a/src/popup/components/ui/__tests__/Slider.tsx b/src/popup/components/ui/__tests__/Slider.tsx index 69049601..3bc56755 100644 --- a/src/popup/components/ui/__tests__/Slider.tsx +++ b/src/popup/components/ui/__tests__/Slider.tsx @@ -1,30 +1,30 @@ -import '@testing-library/jest-dom' +import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react' -import React from 'react' +import { render, screen } from '@testing-library/react'; +import React from 'react'; -import { Slider } from '../Slider' +import { Slider } from '../Slider'; describe('Slider Component', () => { it('renders without crashing', () => { - render() - expect(screen.getByRole('slider')).toBeInTheDocument() - }) + render(); + expect(screen.getByRole('slider')).toBeInTheDocument(); + }); it('handles disabled prop', () => { - render() - expect(screen.getByRole('slider')).toBeDisabled() - }) + render(); + expect(screen.getByRole('slider')).toBeDisabled(); + }); it('displays error message when provided', () => { - const errorMessage = 'Error message' - render() - expect(screen.getByText(errorMessage)).toBeInTheDocument() - }) + const errorMessage = 'Error message'; + render(); + expect(screen.getByText(errorMessage)).toBeInTheDocument(); + }); it('passes additional props to the input', () => { - const testName = 'test-name' - render() - expect(screen.getByRole('slider')).toHaveAttribute('name', testName) - }) -}) + const testName = 'test-name'; + render(); + expect(screen.getByRole('slider')).toHaveAttribute('name', testName); + }); +}); diff --git a/src/popup/components/ui/__tests__/Switch.tsx b/src/popup/components/ui/__tests__/Switch.tsx index 803844c8..b796d7ed 100644 --- a/src/popup/components/ui/__tests__/Switch.tsx +++ b/src/popup/components/ui/__tests__/Switch.tsx @@ -1,71 +1,73 @@ -import '@testing-library/jest-dom' +import '@testing-library/jest-dom'; -import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; -import { Switch } from '@/popup/components/ui/Switch' +import { Switch } from '@/popup/components/ui/Switch'; describe('Switch', () => { it('renders without crashing', () => { - render() - expect(screen.getByRole('switch')).toBeInTheDocument() - }) + render(); + expect(screen.getByRole('switch')).toBeInTheDocument(); + }); it('applies default classes', () => { - render() - const switchElement = screen.getByRole('switch').nextSibling - expect(switchElement).toHaveClass('w-[42px] h-[26px] before:h-5 before:w-5') - }) + render(); + const switchElement = screen.getByRole('switch').nextSibling; + expect(switchElement).toHaveClass( + 'w-[42px] h-[26px] before:h-5 before:w-5', + ); + }); it('applies small size classes when size prop is small', () => { - render() - const switchElement = screen.getByRole('switch').nextSibling + render(); + const switchElement = screen.getByRole('switch').nextSibling; expect(switchElement).toHaveClass( 'w-9 h-[22px] before:h-4 before:w-4 before:left-[3px]', - ) - }) + ); + }); it('forwards ref to input element', () => { - const ref = React.createRef() - render() - expect(ref.current).toBeInstanceOf(HTMLInputElement) - }) + const ref = React.createRef(); + render(); + expect(ref.current).toBeInstanceOf(HTMLInputElement); + }); it('forwards checked prop to input element', () => { - render() - const inputElement = screen.getByRole('switch') - expect(inputElement).toBeChecked() - }) + render(); + const inputElement = screen.getByRole('switch'); + expect(inputElement).toBeChecked(); + }); it('handles additional props', () => { - render() - const inputElement = screen.getByRole('switch') - expect(inputElement).toHaveAttribute('aria-label', 'Custom Switch') - }) + render(); + const inputElement = screen.getByRole('switch'); + expect(inputElement).toHaveAttribute('aria-label', 'Custom Switch'); + }); it('applies custom class names', () => { - const customClass = 'custom-class' - render() - const switchElement = screen.getByRole('switch').nextSibling - expect(switchElement).toHaveClass(customClass) - }) + const customClass = 'custom-class'; + render(); + const switchElement = screen.getByRole('switch').nextSibling; + expect(switchElement).toHaveClass(customClass); + }); it('toggles switch state when clicked', () => { - render() - const inputElement = screen.getByRole('switch') - expect(inputElement).not.toBeChecked() + render(); + const inputElement = screen.getByRole('switch'); + expect(inputElement).not.toBeChecked(); - fireEvent.click(inputElement) - expect(inputElement).toBeChecked() + fireEvent.click(inputElement); + expect(inputElement).toBeChecked(); - fireEvent.click(inputElement) - expect(inputElement).not.toBeChecked() - }) + fireEvent.click(inputElement); + expect(inputElement).not.toBeChecked(); + }); it('handles additional HTML attributes', () => { - const testId = 'switch-test' - render() - const switchElement = screen.getByTestId(testId) - expect(switchElement).toBeInTheDocument() - }) -}) + const testId = 'switch-test'; + render(); + const switchElement = screen.getByTestId(testId); + expect(switchElement).toBeInTheDocument(); + }); +}); diff --git a/src/popup/index.tsx b/src/popup/index.tsx index a3db5cf9..f889856d 100755 --- a/src/popup/index.tsx +++ b/src/popup/index.tsx @@ -1,9 +1,9 @@ -import './index.css' +import './index.css'; -import React from 'react' -import ReactDOM from 'react-dom/client' +import React from 'react'; +import ReactDOM from 'react-dom/client'; -import { Popup } from './Popup' +import { Popup } from './Popup'; -const root = ReactDOM.createRoot(document.getElementById('popup-container')!) -root.render() +const root = ReactDOM.createRoot(document.getElementById('popup-container')!); +root.render(); diff --git a/src/popup/lib/context.tsx b/src/popup/lib/context.tsx index 54adbc33..3fee31b9 100644 --- a/src/popup/lib/context.tsx +++ b/src/popup/lib/context.tsx @@ -1,13 +1,13 @@ -import React, { type PropsWithChildren } from 'react' -import type { Browser } from 'webextension-polyfill' -import { tFactory, type Translation } from '@/shared/helpers' -import type { DeepNonNullable, PopupStore } from '@/shared/types' +import React, { type PropsWithChildren } from 'react'; +import type { Browser } from 'webextension-polyfill'; +import { tFactory, type Translation } from '@/shared/helpers'; +import type { DeepNonNullable, PopupStore } from '@/shared/types'; import { BACKGROUND_TO_POPUP_CONNECTION_NAME as CONNECTION_NAME, MessageManager, type PopupToBackgroundMessage, type BackgroundToPopupMessage, -} from '@/shared/messages' +} from '@/shared/messages'; // #region PopupState export enum ReducerActionType { @@ -19,141 +19,141 @@ export enum ReducerActionType { export type PopupState = Required< DeepNonNullable> & Pick -> +>; export interface PopupContext { - state: Required> - dispatch: React.Dispatch + state: Required>; + dispatch: React.Dispatch; } interface ReducerActionMock { - type: ReducerActionType - data?: any + type: ReducerActionType; + data?: any; } interface SetDataAction extends ReducerActionMock { - type: ReducerActionType.SET_DATA - data: PopupState + type: ReducerActionType.SET_DATA; + data: PopupState; } interface ToggleWMAction extends ReducerActionMock { - type: ReducerActionType.TOGGLE_WM + type: ReducerActionType.TOGGLE_WM; } interface SetConnected extends ReducerActionMock { - type: ReducerActionType.SET_CONNECTED + type: ReducerActionType.SET_CONNECTED; data: { - value: boolean - } + value: boolean; + }; } interface UpdateRateOfPayAction extends ReducerActionMock { - type: ReducerActionType.UPDATE_RATE_OF_PAY + type: ReducerActionType.UPDATE_RATE_OF_PAY; data: { - rateOfPay: string - } + rateOfPay: string; + }; } -type BackgroundToPopupAction = BackgroundToPopupMessage +type BackgroundToPopupAction = BackgroundToPopupMessage; export type ReducerActions = | SetDataAction | ToggleWMAction | SetConnected | UpdateRateOfPayAction - | BackgroundToPopupAction + | BackgroundToPopupAction; -const PopupStateContext = React.createContext({} as PopupContext) +const PopupStateContext = React.createContext({} as PopupContext); -export const usePopupState = () => React.useContext(PopupStateContext) +export const usePopupState = () => React.useContext(PopupStateContext); const reducer = (state: PopupState, action: ReducerActions): PopupState => { switch (action.type) { case ReducerActionType.SET_DATA: { - return action.data + return action.data; } case ReducerActionType.TOGGLE_WM: { return { ...state, enabled: !state.enabled, - } + }; } case ReducerActionType.SET_CONNECTED: - return { ...state, connected: action.data.value } + return { ...state, connected: action.data.value }; case ReducerActionType.UPDATE_RATE_OF_PAY: { return { ...state, rateOfPay: action.data.rateOfPay, - } + }; } case 'SET_STATE': - return { ...state, state: action.data.state } + return { ...state, state: action.data.state }; case 'SET_IS_MONETIZED': - return { ...state, isSiteMonetized: action.data } + return { ...state, isSiteMonetized: action.data }; case 'SET_BALANCE': - return { ...state, balance: action.data.total } + return { ...state, balance: action.data.total }; default: - return state + return state; } -} +}; interface PopupContextProviderProps { - children: React.ReactNode + children: React.ReactNode; } export function PopupContextProvider({ children }: PopupContextProviderProps) { - const browser = useBrowser() - const message = useMessage() - const [isLoading, setIsLoading] = React.useState(true) - const [state, dispatch] = React.useReducer(reducer, {} as PopupState) + const browser = useBrowser(); + const message = useMessage(); + const [isLoading, setIsLoading] = React.useState(true); + const [state, dispatch] = React.useReducer(reducer, {} as PopupState); React.useEffect(() => { async function get() { - const response = await message.send('GET_CONTEXT_DATA') + const response = await message.send('GET_CONTEXT_DATA'); if (response.success) { - dispatch({ type: ReducerActionType.SET_DATA, data: response.payload }) - setIsLoading(false) + dispatch({ type: ReducerActionType.SET_DATA, data: response.payload }); + setIsLoading(false); } } - get() - }, [message]) + get(); + }, [message]); React.useEffect(() => { - const port = browser.runtime.connect({ name: CONNECTION_NAME }) + const port = browser.runtime.connect({ name: CONNECTION_NAME }); port.onMessage.addListener((message: BackgroundToPopupMessage) => { switch (message.type) { case 'SET_BALANCE': case 'SET_STATE': case 'SET_IS_MONETIZED': - return dispatch(message) + return dispatch(message); } - }) + }); port.onDisconnect.addListener(() => { // nothing to do - }) + }); return () => { - port.disconnect() - } - }, [browser]) + port.disconnect(); + }; + }, [browser]); if (isLoading) { - return <>Loading + return <>Loading; } return ( {children} - ) + ); } // #endregion // #region Browser -const BrowserContext = React.createContext({} as Browser) +const BrowserContext = React.createContext({} as Browser); -export const useBrowser = () => React.useContext(BrowserContext) +export const useBrowser = () => React.useContext(BrowserContext); export const BrowserContextProvider = ({ browser, @@ -163,45 +163,45 @@ export const BrowserContextProvider = ({ {children} - ) -} + ); +}; // #endregion // #region Translation -const TranslationContext = React.createContext((v: string) => v) +const TranslationContext = React.createContext((v: string) => v); -export const useTranslation = () => React.useContext(TranslationContext) +export const useTranslation = () => React.useContext(TranslationContext); export const TranslationContextProvider = ({ children }: PropsWithChildren) => { - const browser = useBrowser() - const t = tFactory(browser) + const browser = useBrowser(); + const t = tFactory(browser); return ( {children} - ) -} + ); +}; // #endregion // #region Translation const MessageContext = React.createContext< MessageManager ->({} as MessageManager) +>({} as MessageManager); -export const useMessage = () => React.useContext(MessageContext) +export const useMessage = () => React.useContext(MessageContext); export const MessageContextProvider = ({ children }: PropsWithChildren) => { - const browser = useBrowser() - const message = new MessageManager({ browser }) + const browser = useBrowser(); + const message = new MessageManager({ browser }); return ( {children} - ) -} + ); +}; // #endregion diff --git a/src/popup/lib/hooks.test.tsx b/src/popup/lib/hooks.test.tsx index d79ea8a0..d8468ec9 100644 --- a/src/popup/lib/hooks.test.tsx +++ b/src/popup/lib/hooks.test.tsx @@ -1,28 +1,28 @@ -import React from 'react' -import { fireEvent, render } from '@testing-library/react' -import { useLocalStorage } from './hooks' +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { useLocalStorage } from './hooks'; describe('useLocalStorage', () => { - const defaultMaxAge = 1000 * 24 * 60 * 60 - let now = Date.now() - let defaultExpiresAt = now + defaultMaxAge + const defaultMaxAge = 1000 * 24 * 60 * 60; + let now = Date.now(); + let defaultExpiresAt = now + defaultMaxAge; beforeAll(() => { - jest.useFakeTimers() - }) + jest.useFakeTimers(); + }); beforeEach(() => { - localStorage.clear() - now = jest.getRealSystemTime() - defaultExpiresAt = now + defaultMaxAge - }) + localStorage.clear(); + now = jest.getRealSystemTime(); + defaultExpiresAt = now + defaultMaxAge; + }); afterAll(() => { - jest.useRealTimers() - localStorage.clear() - }) + jest.useRealTimers(); + localStorage.clear(); + }); function TestComponent({ maxAge = defaultMaxAge }: { maxAge?: number }) { const [data, setData, clear] = useLocalStorage('name', 'John Doe', { maxAge, - }) + }); return ( <>

{data}

@@ -39,105 +39,105 @@ describe('useLocalStorage', () => { Clear data - ) + ); } it('sets localStorage based on default value', () => { - const { getByTestId } = render() - expect(getByTestId('data')).toHaveTextContent('John Doe') - expect(localStorage.getItem('name')).not.toBeNull() - const stored = JSON.parse(localStorage.getItem('name')!) - expect(stored.value).toBe('John Doe') - expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt) - }) + const { getByTestId } = render(); + expect(getByTestId('data')).toHaveTextContent('John Doe'); + expect(localStorage.getItem('name')).not.toBeNull(); + const stored = JSON.parse(localStorage.getItem('name')!); + expect(stored.value).toBe('John Doe'); + expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt); + }); it('gets localStorage value instead of default', () => { localStorage.setItem( 'name', JSON.stringify({ value: 'Johnny', expiresAt: defaultExpiresAt }), - ) - const { getByTestId } = render() - expect(getByTestId('data')).toHaveTextContent('Johnny') - }) + ); + const { getByTestId } = render(); + expect(getByTestId('data')).toHaveTextContent('Johnny'); + }); it('changes localStorage and state value', () => { localStorage.setItem( 'name', JSON.stringify({ value: 'Johnny', expiresAt: defaultExpiresAt }), - ) - const { getByTestId } = render() + ); + const { getByTestId } = render(); - fireEvent.click(getByTestId('set')) - expect(getByTestId('data')).toHaveTextContent('John Wick') + fireEvent.click(getByTestId('set')); + expect(getByTestId('data')).toHaveTextContent('John Wick'); - expect(localStorage.getItem('name')).not.toBeNull() - const stored = JSON.parse(localStorage.getItem('name')!) - expect(stored.value).toBe('John Wick') - expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt) - }) + expect(localStorage.getItem('name')).not.toBeNull(); + const stored = JSON.parse(localStorage.getItem('name')!); + expect(stored.value).toBe('John Wick'); + expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt); + }); it('changes localStorage and state value using callback', () => { localStorage.setItem( 'name', JSON.stringify({ value: 'Johnny', expiresAt: defaultExpiresAt }), - ) - const { getByTestId } = render() + ); + const { getByTestId } = render(); - fireEvent.click(getByTestId('set-cb')) - expect(getByTestId('data')).toHaveTextContent('JohnnyFoo') + fireEvent.click(getByTestId('set-cb')); + expect(getByTestId('data')).toHaveTextContent('JohnnyFoo'); - expect(localStorage.getItem('name')).not.toBeNull() - const stored = JSON.parse(localStorage.getItem('name')!) - expect(stored.value).toBe('JohnnyFoo') - expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt) - }) + expect(localStorage.getItem('name')).not.toBeNull(); + const stored = JSON.parse(localStorage.getItem('name')!); + expect(stored.value).toBe('JohnnyFoo'); + expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt); + }); it('should remove item from localStorage when clear is called', () => { - const { getByTestId } = render() + const { getByTestId } = render(); - fireEvent.click(getByTestId('clear')) - expect(getByTestId('data')).toHaveTextContent('John Doe') - expect(localStorage.getItem('name')).toBeNull() - }) + fireEvent.click(getByTestId('clear')); + expect(getByTestId('data')).toHaveTextContent('John Doe'); + expect(localStorage.getItem('name')).toBeNull(); + }); it('should be able to set value again after it was removed from localStorage', () => { - const { getByTestId } = render() + const { getByTestId } = render(); - fireEvent.click(getByTestId('clear')) - fireEvent.click(getByTestId('set')) + fireEvent.click(getByTestId('clear')); + fireEvent.click(getByTestId('set')); - expect(localStorage.getItem('name')).not.toBeNull() - const stored = JSON.parse(localStorage.getItem('name')!) - expect(stored.value).toBe('John Wick') - expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt) - }) + expect(localStorage.getItem('name')).not.toBeNull(); + const stored = JSON.parse(localStorage.getItem('name')!); + expect(stored.value).toBe('John Wick'); + expect(stored.expiresAt).toBeGreaterThan(defaultExpiresAt); + }); it('should respect maxAge', () => { - const maxAge = 5 - const ui = - - expect(localStorage.getItem('name')).toBeNull() - - const { getByTestId, unmount } = render(ui) - expect(getByTestId('data')).toHaveTextContent('John Doe') - expect(localStorage.getItem('name')).not.toBeNull() - - fireEvent.click(getByTestId('set')) - const now = Date.now() - expect(getByTestId('data')).toHaveTextContent('John Wick') - expect(localStorage.getItem('name')).not.toBeNull() - const stored = JSON.parse(localStorage.getItem('name')!) - expect(stored.value).toBe('John Wick') - expect(stored.expiresAt).toBeGreaterThanOrEqual(now + maxAge * 1_000) - expect(stored.expiresAt).toBeLessThan(defaultExpiresAt) - - jest.setSystemTime(now + (maxAge + 1) * 1_000) - jest.advanceTimersByTime(now + (maxAge + 1) * 1_000) - - unmount() - const remounted = render(ui) - expect(remounted.getByTestId('data')).toHaveTextContent('John Doe') - expect(localStorage.getItem('name')).not.toBeNull() - expect(JSON.parse(localStorage.getItem('name')!).value).toBe('John Doe') - }) -}) + const maxAge = 5; + const ui = ; + + expect(localStorage.getItem('name')).toBeNull(); + + const { getByTestId, unmount } = render(ui); + expect(getByTestId('data')).toHaveTextContent('John Doe'); + expect(localStorage.getItem('name')).not.toBeNull(); + + fireEvent.click(getByTestId('set')); + const now = Date.now(); + expect(getByTestId('data')).toHaveTextContent('John Wick'); + expect(localStorage.getItem('name')).not.toBeNull(); + const stored = JSON.parse(localStorage.getItem('name')!); + expect(stored.value).toBe('John Wick'); + expect(stored.expiresAt).toBeGreaterThanOrEqual(now + maxAge * 1_000); + expect(stored.expiresAt).toBeLessThan(defaultExpiresAt); + + jest.setSystemTime(now + (maxAge + 1) * 1_000); + jest.advanceTimersByTime(now + (maxAge + 1) * 1_000); + + unmount(); + const remounted = render(ui); + expect(remounted.getByTestId('data')).toHaveTextContent('John Doe'); + expect(localStorage.getItem('name')).not.toBeNull(); + expect(JSON.parse(localStorage.getItem('name')!).value).toBe('John Doe'); + }); +}); diff --git a/src/popup/lib/hooks.ts b/src/popup/lib/hooks.ts index 44702066..78d258ca 100644 --- a/src/popup/lib/hooks.ts +++ b/src/popup/lib/hooks.ts @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; /** * Store data in browser's local storage. Helpful in retrieving state after @@ -15,47 +15,47 @@ export function useLocalStorage( defaultValue: T, { maxAge = 1000 * 24 * 60 * 60 }: Partial<{ maxAge: number }> = {}, ) { - const hasLocalStorage = typeof localStorage !== 'undefined' - maxAge *= 1000 + const hasLocalStorage = typeof localStorage !== 'undefined'; + maxAge *= 1000; - type Stored = { value: T; expiresAt: number } + type Stored = { value: T; expiresAt: number }; const isWellFormed = React.useCallback((obj: any): obj is Stored => { - if (typeof obj !== 'object' || obj == null) return false - if (!obj.expiresAt || !Number.isSafeInteger(obj.expiresAt)) return false - return typeof obj.value !== 'undefined' - }, []) + if (typeof obj !== 'object' || obj == null) return false; + if (!obj.expiresAt || !Number.isSafeInteger(obj.expiresAt)) return false; + return typeof obj.value !== 'undefined'; + }, []); const [value, setValue] = React.useState(() => { - if (!hasLocalStorage) return defaultValue + if (!hasLocalStorage) return defaultValue; - const storedValue = localStorage.getItem(key) - if (!storedValue) return defaultValue + const storedValue = localStorage.getItem(key); + if (!storedValue) return defaultValue; try { - const data = JSON.parse(storedValue) + const data = JSON.parse(storedValue); if (isWellFormed(data) && data.expiresAt > Date.now()) { - return data.value + return data.value; } else { - localStorage.removeItem(key) + localStorage.removeItem(key); } } catch { - localStorage.removeItem(key) + localStorage.removeItem(key); } - return defaultValue - }) + return defaultValue; + }); React.useEffect(() => { - if (!hasLocalStorage) return - const expiresAt = Date.now() + maxAge - const data: Stored = { value, expiresAt } - localStorage.setItem(key, JSON.stringify(data)) - }, [value, key, defaultValue, maxAge, hasLocalStorage]) + if (!hasLocalStorage) return; + const expiresAt = Date.now() + maxAge; + const data: Stored = { value, expiresAt }; + localStorage.setItem(key, JSON.stringify(data)); + }, [value, key, defaultValue, maxAge, hasLocalStorage]); const clearStorage = () => { if (hasLocalStorage) { - localStorage.removeItem(key) + localStorage.removeItem(key); } - } + }; - return [value, setValue, clearStorage] as const + return [value, setValue, clearStorage] as const; } diff --git a/src/popup/lib/utils.test.ts b/src/popup/lib/utils.test.ts index 301ff3ae..3d91c57f 100644 --- a/src/popup/lib/utils.test.ts +++ b/src/popup/lib/utils.test.ts @@ -1,53 +1,53 @@ -import { formatNumber, toWalletAddressUrl } from './utils' +import { formatNumber, toWalletAddressUrl } from './utils'; describe('formatNumber', () => { it('should display right format for integers', () => { - expect(formatNumber(5, 2)).toEqual('5.00') + expect(formatNumber(5, 2)).toEqual('5.00'); - expect(formatNumber(5, 4)).toEqual('5.00') + expect(formatNumber(5, 4)).toEqual('5.00'); - expect(formatNumber(5, 9)).toEqual('5.00') - }) + expect(formatNumber(5, 9)).toEqual('5.00'); + }); it('should display right format for real numbers bigger than 1', () => { - expect(formatNumber(5.9, 2)).toEqual('5.90') + expect(formatNumber(5.9, 2)).toEqual('5.90'); - expect(formatNumber(5.09, 4)).toEqual('5.09') + expect(formatNumber(5.09, 4)).toEqual('5.09'); - expect(formatNumber(5.009, 4)).toEqual('5.009') + expect(formatNumber(5.009, 4)).toEqual('5.009'); - expect(formatNumber(5.0009, 4)).toEqual('5.0009') + expect(formatNumber(5.0009, 4)).toEqual('5.0009'); - expect(formatNumber(5.000009, 9)).toEqual('5.000009') + expect(formatNumber(5.000009, 9)).toEqual('5.000009'); - expect(formatNumber(5.000000009, 9)).toEqual('5.000000009') - }) + expect(formatNumber(5.000000009, 9)).toEqual('5.000000009'); + }); it('should display right format for real numbers smaller than 1', () => { - expect(formatNumber(0.09, 2)).toEqual('0.09') + expect(formatNumber(0.09, 2)).toEqual('0.09'); - expect(formatNumber(0.0009, 4)).toEqual('0.0009') + expect(formatNumber(0.0009, 4)).toEqual('0.0009'); - expect(formatNumber(0.000000009, 9)).toEqual('0.000000009') + expect(formatNumber(0.000000009, 9)).toEqual('0.000000009'); - expect(formatNumber(0.00009, 9)).toEqual('0.00009') + expect(formatNumber(0.00009, 9)).toEqual('0.00009'); - expect(formatNumber(0.000000009, 9, true)).toEqual('9e-9') + expect(formatNumber(0.000000009, 9, true)).toEqual('9e-9'); - expect(formatNumber(0.00009, 9, true)).toEqual('9e-5') + expect(formatNumber(0.00009, 9, true)).toEqual('9e-5'); - expect(formatNumber(0.0000109, 9, true)).toEqual('1.09e-5') + expect(formatNumber(0.0000109, 9, true)).toEqual('1.09e-5'); - expect(formatNumber(0.000010009, 9, true)).toEqual('1.0009e-5') + expect(formatNumber(0.000010009, 9, true)).toEqual('1.0009e-5'); - expect(formatNumber(0.000100009, 9)).toEqual('0.000100009') - }) -}) + expect(formatNumber(0.000100009, 9)).toEqual('0.000100009'); + }); +}); describe('toWalletAddressUrl', () => { it('converts from short form to long form', () => { expect(toWalletAddressUrl('$wallet.com/bob')).toEqual( 'https://wallet.com/bob', - ) - }) -}) + ); + }); +}); diff --git a/src/popup/lib/utils.ts b/src/popup/lib/utils.ts index 09c9789e..b5e2e4ab 100644 --- a/src/popup/lib/utils.ts +++ b/src/popup/lib/utils.ts @@ -8,29 +8,29 @@ export const getCurrencySymbol = (assetCode: string): string => { }) .format(0) .replace(/0/g, '') - .trim() -} + .trim(); +}; export const transformBalance = ( amount: string | bigint, scale: number, ): string => { - const value = BigInt(amount) - const divisor = BigInt(10 ** scale) + const value = BigInt(amount); + const divisor = BigInt(10 ** scale); - const integerPart = (value / divisor).toString() - const fractionalPart = (value % divisor).toString().padStart(scale, '0') + const integerPart = (value / divisor).toString(); + const fractionalPart = (value % divisor).toString().padStart(scale, '0'); - return `${integerPart}.${fractionalPart}` -} + return `${integerPart}.${fractionalPart}`; +}; export function charIsNumber(char?: string) { - return !!(char || '').match(/\d|\./) + return !!(char || '').match(/\d|\./); } export function roundWithPrecision(num: number, precision: number) { - const multiplier = Math.pow(10, precision) - return Math.round(num * multiplier) / multiplier + const multiplier = Math.pow(10, precision); + return Math.round(num * multiplier) / multiplier; } export function formatNumber( @@ -40,29 +40,29 @@ export function formatNumber( ): string { // TO DO: handle scale 0 - if (!value) return '0.00' + if (!value) return '0.00'; // to avoid floating point issues on multiplication - const pow2 = +(value * 100).toFixed(9) - const pow4 = +(value * 10 ** 4).toFixed(9) + const pow2 = +(value * 100).toFixed(9); + const pow4 = +(value * 10 ** 4).toFixed(9); if (scale <= 2 || (pow2 >= 1 && pow2 - Math.floor(pow2) === 0)) { - return value.toFixed(2) + return value.toFixed(2); } else if (scale >= 3 && scale <= 4) { - return value.toString() + return value.toString(); } else { if (pow4 >= 1 || !allowExponential) { - let fixedScale = 5 - let powN = +(value * 10 ** fixedScale).toFixed(9) + let fixedScale = 5; + let powN = +(value * 10 ** fixedScale).toFixed(9); while (powN - Math.floor(powN) > 0 && fixedScale < scale) { - ++fixedScale - powN = +(value * 10 ** fixedScale).toFixed(9) + ++fixedScale; + powN = +(value * 10 ** fixedScale).toFixed(9); } - return value.toFixed(fixedScale) - } else return value.toExponential() + return value.toFixed(fixedScale); + } else return value.toExponential(); } } export function toWalletAddressUrl(s: string): string { - return s.startsWith('$') ? s.replace('$', 'https://') : s + return s.startsWith('$') ? s.replace('$', 'https://') : s; } diff --git a/src/popup/pages/ErrorKeyRevoked.tsx b/src/popup/pages/ErrorKeyRevoked.tsx index d3e33264..48e97249 100644 --- a/src/popup/pages/ErrorKeyRevoked.tsx +++ b/src/popup/pages/ErrorKeyRevoked.tsx @@ -1,36 +1,36 @@ -import React from 'react' -import { ErrorKeyRevoked } from '@/popup/components/ErrorKeyRevoked' +import React from 'react'; +import { ErrorKeyRevoked } from '@/popup/components/ErrorKeyRevoked'; import { useMessage, usePopupState, ReducerActionType, -} from '@/popup/lib/context' -import { useNavigate } from 'react-router-dom' -import { ROUTES_PATH } from '@/popup/Popup' +} from '@/popup/lib/context'; +import { useNavigate } from 'react-router-dom'; +import { ROUTES_PATH } from '@/popup/Popup'; export const Component = () => { - const message = useMessage() + const message = useMessage(); const { state: { publicKey, walletAddress }, dispatch, - } = usePopupState() - const navigate = useNavigate() + } = usePopupState(); + const navigate = useNavigate(); const onReconnect = () => { dispatch({ type: 'SET_STATE', data: { state: {}, prevState: {} }, - }) - navigate(ROUTES_PATH.HOME) - } + }); + navigate(ROUTES_PATH.HOME); + }; const onDisconnect = () => { dispatch({ type: ReducerActionType.SET_CONNECTED, data: { value: false }, - }) - navigate(ROUTES_PATH.HOME) - } + }); + navigate(ROUTES_PATH.HOME); + }; return ( { disconnectWallet={() => message.send('DISCONNECT_WALLET')} onDisconnect={onDisconnect} /> - ) -} + ); +}; diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index 54c1c354..0edae1e4 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -1,25 +1,25 @@ -import React from 'react' +import React from 'react'; import { ReducerActionType, usePopupState, useMessage, -} from '@/popup/lib/context' -import { WarningSign } from '@/popup/components/Icons' -import { Slider } from '../components/ui/Slider' -import { Label } from '../components/ui/Label' +} from '@/popup/lib/context'; +import { WarningSign } from '@/popup/components/Icons'; +import { Slider } from '../components/ui/Slider'; +import { Label } from '../components/ui/Label'; import { formatNumber, getCurrencySymbol, roundWithPrecision, -} from '../lib/utils' -import { PayWebsiteForm } from '../components/PayWebsiteForm' -import { SiteNotMonetized } from '@/popup/components/SiteNotMonetized' -import { debounceAsync } from '@/shared/helpers' -import { Switch } from '../components/ui/Switch' -import { AllSessionsInvalid } from '@/popup/components/AllSessionsInvalid' +} from '../lib/utils'; +import { PayWebsiteForm } from '../components/PayWebsiteForm'; +import { SiteNotMonetized } from '@/popup/components/SiteNotMonetized'; +import { debounceAsync } from '@/shared/helpers'; +import { Switch } from '../components/ui/Switch'; +import { AllSessionsInvalid } from '@/popup/components/AllSessionsInvalid'; export const Component = () => { - const message = useMessage() + const message = useMessage(); const { state: { enabled, @@ -33,53 +33,53 @@ export const Component = () => { hasAllSessionsInvalid, }, dispatch, - } = usePopupState() + } = usePopupState(); const rate = React.useMemo(() => { - const r = Number(rateOfPay) / 10 ** walletAddress.assetScale - const roundedR = roundWithPrecision(r, walletAddress.assetScale) + const r = Number(rateOfPay) / 10 ** walletAddress.assetScale; + const roundedR = roundWithPrecision(r, walletAddress.assetScale); - return formatNumber(roundedR, walletAddress.assetScale, true) - }, [rateOfPay, walletAddress.assetScale]) + return formatNumber(roundedR, walletAddress.assetScale, true); + }, [rateOfPay, walletAddress.assetScale]); const remainingBalance = React.useMemo(() => { - const val = Number(balance) / 10 ** walletAddress.assetScale - const rounded = roundWithPrecision(val, walletAddress.assetScale) - return formatNumber(rounded, walletAddress.assetScale, true) - }, [balance, walletAddress.assetScale]) + const val = Number(balance) / 10 ** walletAddress.assetScale; + const rounded = roundWithPrecision(val, walletAddress.assetScale); + return formatNumber(rounded, walletAddress.assetScale, true); + }, [balance, walletAddress.assetScale]); const updateRateOfPay = React.useRef( debounceAsync(async (rateOfPay: string) => { - const response = await message.send('UPDATE_RATE_OF_PAY', { rateOfPay }) + const response = await message.send('UPDATE_RATE_OF_PAY', { rateOfPay }); if (!response.success) { // TODO: Maybe reset to old state, but not while user is active (avoid // sluggishness in UI) } }, 1000), - ) + ); const onRateChange = async (event: React.ChangeEvent) => { - const rateOfPay = event.currentTarget.value + const rateOfPay = event.currentTarget.value; dispatch({ type: ReducerActionType.UPDATE_RATE_OF_PAY, data: { rateOfPay, }, - }) - void updateRateOfPay.current(rateOfPay) - } + }); + void updateRateOfPay.current(rateOfPay); + }; const onChangeWM = () => { - message.send('TOGGLE_WM') - dispatch({ type: ReducerActionType.TOGGLE_WM, data: {} }) - } + message.send('TOGGLE_WM'); + dispatch({ type: ReducerActionType.TOGGLE_WM, data: {} }); + }; if (!isSiteMonetized) { - return + return ; } if (hasAllSessionsInvalid) { - return + return ; } return ( @@ -124,5 +124,5 @@ export const Component = () => { {url ? : null}
- ) -} + ); +}; diff --git a/src/popup/pages/MissingHostPermission.tsx b/src/popup/pages/MissingHostPermission.tsx index 54a892d7..41954fad 100644 --- a/src/popup/pages/MissingHostPermission.tsx +++ b/src/popup/pages/MissingHostPermission.tsx @@ -1,11 +1,11 @@ -import React from 'react' -import { PERMISSION_HOSTS } from '@/shared/defines' -import { WarningSign } from '@/popup/components/Icons' -import { useBrowser, useTranslation } from '@/popup/lib/context' +import React from 'react'; +import { PERMISSION_HOSTS } from '@/shared/defines'; +import { WarningSign } from '@/popup/components/Icons'; +import { useBrowser, useTranslation } from '@/popup/lib/context'; export const Component = () => { - const browser = useBrowser() - const t = useTranslation() + const browser = useBrowser(); + const t = useTranslation(); return (
@@ -26,12 +26,12 @@ export const Component = () => { browser.permissions.request(PERMISSION_HOSTS).finally(() => { // So we open popup with refreshed state, avoiding additional message passing. // Firefox closes popup automatically. - window.close() + window.close(); }) } > Grant permission
- ) -} + ); +}; diff --git a/src/popup/pages/OutOfFunds.tsx b/src/popup/pages/OutOfFunds.tsx index a717d553..643ce9a4 100644 --- a/src/popup/pages/OutOfFunds.tsx +++ b/src/popup/pages/OutOfFunds.tsx @@ -1,15 +1,15 @@ -import React from 'react' -import { useNavigate } from 'react-router-dom' -import { OutOfFunds } from '@/popup/components/OutOfFunds' -import { usePopupState } from '@/popup/lib/context' -import { ROUTES_PATH } from '@/popup/Popup' -import type { State } from '@/popup/pages/OutOfFunds_AddFunds' +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { OutOfFunds } from '@/popup/components/OutOfFunds'; +import { usePopupState } from '@/popup/lib/context'; +import { ROUTES_PATH } from '@/popup/Popup'; +import type { State } from '@/popup/pages/OutOfFunds_AddFunds'; export const Component = () => { const { state: { grants, walletAddress }, - } = usePopupState() - const navigate = useNavigate() + } = usePopupState(); + const navigate = useNavigate(); return ( { grantOneTime={grants.oneTime} grantRecurring={grants.recurring} onChooseOption={(recurring) => { - const state: State = { recurring } - navigate(ROUTES_PATH.OUT_OF_FUNDS_ADD_FUNDS, { state }) + const state: State = { recurring }; + navigate(ROUTES_PATH.OUT_OF_FUNDS_ADD_FUNDS, { state }); }} /> - ) -} + ); +}; diff --git a/src/popup/pages/OutOfFunds_AddFunds.tsx b/src/popup/pages/OutOfFunds_AddFunds.tsx index d9b5283f..a46f04b7 100644 --- a/src/popup/pages/OutOfFunds_AddFunds.tsx +++ b/src/popup/pages/OutOfFunds_AddFunds.tsx @@ -1,19 +1,19 @@ -import React from 'react' -import { useLocation } from 'react-router-dom' -import { AddFunds } from '@/popup/components/OutOfFunds' -import { usePopupState, useMessage } from '@/popup/lib/context' +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { AddFunds } from '@/popup/components/OutOfFunds'; +import { usePopupState, useMessage } from '@/popup/lib/context'; -export type State = { recurring: boolean } +export type State = { recurring: boolean }; export const Component = () => { - const message = useMessage() + const message = useMessage(); const { state: { grants, walletAddress }, - } = usePopupState() - const location = useLocation() + } = usePopupState(); + const location = useLocation(); - const state: State = { recurring: false, ...location.state } - const defaultAmount = grants.recurring?.value ?? grants.oneTime!.value + const state: State = { recurring: false, ...location.state }; + const defaultAmount = grants.recurring?.value ?? grants.oneTime!.value; return ( { defaultAmount={defaultAmount} recurring={state.recurring} requestAddFunds={async (data) => { - const res = await message.send('ADD_FUNDS', data) - return res + const res = await message.send('ADD_FUNDS', data); + return res; }} /> - ) -} + ); +}; diff --git a/src/popup/pages/Settings.tsx b/src/popup/pages/Settings.tsx index 3ade8d10..ebc1e511 100644 --- a/src/popup/pages/Settings.tsx +++ b/src/popup/pages/Settings.tsx @@ -1,14 +1,14 @@ -import { ConnectWalletForm } from '@/popup/components/ConnectWalletForm' -import { WalletInformation } from '@/popup/components/WalletInformation' -import { usePopupState } from '@/popup/lib/context' -import React from 'react' +import { ConnectWalletForm } from '@/popup/components/ConnectWalletForm'; +import { WalletInformation } from '@/popup/components/WalletInformation'; +import { usePopupState } from '@/popup/lib/context'; +import React from 'react'; export const Component = () => { - const { state } = usePopupState() + const { state } = usePopupState(); if (state.connected) { - return + return ; } else { - return + return ; } -} +}; diff --git a/src/shared/crypto.ts b/src/shared/crypto.ts index 9bebe836..078a9d3e 100644 --- a/src/shared/crypto.ts +++ b/src/shared/crypto.ts @@ -1,7 +1,7 @@ -import * as ed from '@noble/ed25519' +import * as ed from '@noble/ed25519'; export async function generateEd25519KeyPair() { - const rawPrivateKey = ed.utils.randomPrivateKey() + const rawPrivateKey = ed.utils.randomPrivateKey(); // PKCS#8 format (version + algorithm) // Adding these values upfront solves the future import of the key using // `crypto.subtle.importKey` once the WebCrypto API supports the Ed25519 algorithm. @@ -10,26 +10,26 @@ export async function generateEd25519KeyPair() { 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, ...rawPrivateKey, ]) - const publicKey = await ed.getPublicKeyAsync(rawPrivateKey) + const publicKey = await ed.getPublicKeyAsync(rawPrivateKey); - return { privateKey, publicKey } + return { privateKey, publicKey }; } export function exportJWK(key: Uint8Array, kid: string) { - const string = String.fromCharCode.apply(null, key) + const string = String.fromCharCode.apply(null, key); - const base64 = btoa(string) + const base64 = btoa(string); // Based on the JWK Spec - base64url encoded. // https://datatracker.ietf.org/doc/html/rfc7517#section-3 const base64Url = base64 .replace(/\+/g, '-') .replace(/\//g, '_') - .replace(/=+$/, '') + .replace(/=+$/, ''); return { kty: 'OKP', crv: 'Ed25519', x: base64Url, kid, - } + }; } diff --git a/src/shared/defines.ts b/src/shared/defines.ts index 97475e00..663caad8 100644 --- a/src/shared/defines.ts +++ b/src/shared/defines.ts @@ -1,11 +1,11 @@ -import type { LogLevelDesc } from 'loglevel' +import type { LogLevelDesc } from 'loglevel'; -declare const CONFIG_LOG_LEVEL: LogLevelDesc -declare const CONFIG_PERMISSION_HOSTS: { origins: string[] } -declare const CONFIG_ALLOWED_PROTOCOLS: string[] -declare const CONFIG_OPEN_PAYMENTS_REDIRECT_URL: string +declare const CONFIG_LOG_LEVEL: LogLevelDesc; +declare const CONFIG_PERMISSION_HOSTS: { origins: string[] }; +declare const CONFIG_ALLOWED_PROTOCOLS: string[]; +declare const CONFIG_OPEN_PAYMENTS_REDIRECT_URL: string; -export const LOG_LEVEL = CONFIG_LOG_LEVEL -export const PERMISSION_HOSTS = CONFIG_PERMISSION_HOSTS -export const ALLOWED_PROTOCOLS = CONFIG_ALLOWED_PROTOCOLS -export const OPEN_PAYMENTS_REDIRECT_URL = CONFIG_OPEN_PAYMENTS_REDIRECT_URL +export const LOG_LEVEL = CONFIG_LOG_LEVEL; +export const PERMISSION_HOSTS = CONFIG_PERMISSION_HOSTS; +export const ALLOWED_PROTOCOLS = CONFIG_ALLOWED_PROTOCOLS; +export const OPEN_PAYMENTS_REDIRECT_URL = CONFIG_OPEN_PAYMENTS_REDIRECT_URL; diff --git a/src/shared/helpers.test.ts b/src/shared/helpers.test.ts index dae89ec4..6c43a9e6 100644 --- a/src/shared/helpers.test.ts +++ b/src/shared/helpers.test.ts @@ -1,124 +1,124 @@ -import { addDays, addMonths, addSeconds } from 'date-fns' +import { addDays, addMonths, addSeconds } from 'date-fns'; import { isOkState, objectEquals, removeQueryParams, getNextOccurrence, -} from './helpers' +} from './helpers'; describe('objectEquals', () => { it('should return true if objects are equal', () => { - expect(objectEquals({}, {})).toBe(true) - expect(objectEquals({ a: 1 }, { a: 1 })).toBe(true) - expect(objectEquals({ b: 2, a: 1 }, { a: 1, b: 2 })).toBe(true) - expect(objectEquals({ a: 1 }, { a: 1, b: undefined })).toBe(true) - }) + expect(objectEquals({}, {})).toBe(true); + expect(objectEquals({ a: 1 }, { a: 1 })).toBe(true); + expect(objectEquals({ b: 2, a: 1 }, { a: 1, b: 2 })).toBe(true); + expect(objectEquals({ a: 1 }, { a: 1, b: undefined })).toBe(true); + }); it('should return false if objects are not equal', () => { - expect(objectEquals({ a: 1 }, { a: 2 })).toBe(false) - expect(objectEquals({ a: 1 }, { b: 1 })).toBe(false) - }) -}) + expect(objectEquals({ a: 1 }, { a: 2 })).toBe(false); + expect(objectEquals({ a: 1 }, { b: 1 })).toBe(false); + }); +}); describe('removeQueryParams', () => { it('should remove the query params from the URL', () => { expect(removeQueryParams('https://example.com?foo=bar#baz')).toBe( 'https://example.com/', - ) - }) + ); + }); it('should normalize the URL if there are no query params', () => { expect(removeQueryParams('https://example.com')).toBe( 'https://example.com/', - ) - }) -}) + ); + }); +}); describe('isOkState', () => { it('should return true if no state is set', () => { - expect(isOkState({})).toBe(true) + expect(isOkState({})).toBe(true); expect( isOkState({ key_revoked: false, missing_host_permissions: false }), - ).toBe(true) - }) + ).toBe(true); + }); it('should return false if any state is set', () => { expect( isOkState({ key_revoked: true, missing_host_permissions: false }), - ).toBe(false) + ).toBe(false); expect( isOkState({ key_revoked: false, missing_host_permissions: true }), - ).toBe(false) - }) -}) + ).toBe(false); + }); +}); describe('getNextOccurrence', () => { - const now = new Date() - const nowISO = now.toISOString() - const dateJan = new Date('2024-01-03T00:00:00.000Z') - const dateJanEnd = new Date('2024-01-30T00:00:00.000Z') - const dateFeb = new Date('2023-02-03T00:00:00.000Z') - const dateFebLeap = new Date('2024-02-29T00:00:00.000Z') - const dateApr = new Date('2024-04-03T00:00:00.000Z') + const now = new Date(); + const nowISO = now.toISOString(); + const dateJan = new Date('2024-01-03T00:00:00.000Z'); + const dateJanEnd = new Date('2024-01-30T00:00:00.000Z'); + const dateFeb = new Date('2023-02-03T00:00:00.000Z'); + const dateFebLeap = new Date('2024-02-29T00:00:00.000Z'); + const dateApr = new Date('2024-04-03T00:00:00.000Z'); it('should return the next occurrence with /P1M', () => { expect( getNextOccurrence(`R/${dateJan.toISOString()}/P1M`, dateJan), - ).toEqual(addMonths(dateJan, 1)) + ).toEqual(addMonths(dateJan, 1)); expect( getNextOccurrence(`R/${dateJan.toISOString()}/P1M`, addDays(dateJan, 2)), - ).toEqual(addMonths(dateJan, 1)) + ).toEqual(addMonths(dateJan, 1)); expect( getNextOccurrence(`R/${dateJanEnd.toISOString()}/P1M`, dateJanEnd), - ).toEqual(new Date('2024-03-01T00:00:00.000Z')) + ).toEqual(new Date('2024-03-01T00:00:00.000Z')); expect( getNextOccurrence(`R/${dateFeb.toISOString()}/P1M`, dateFeb), - ).toEqual(addMonths(dateFeb, 1)) + ).toEqual(addMonths(dateFeb, 1)); expect( getNextOccurrence(`R/${dateFebLeap.toISOString()}/P1M`, dateFebLeap), - ).toEqual(addMonths(dateFebLeap, 1)) + ).toEqual(addMonths(dateFebLeap, 1)); expect( getNextOccurrence(`R/${dateApr.toISOString()}/P1M`, dateApr), - ).toEqual(addMonths(dateApr, 1)) - }) + ).toEqual(addMonths(dateApr, 1)); + }); it('should return next occurrence with /P1W', () => { expect( getNextOccurrence(`R/${dateJan.toISOString()}/P1W`, dateJan), - ).toEqual(addDays(dateJan, 7)) + ).toEqual(addDays(dateJan, 7)); expect( getNextOccurrence(`R/${dateFeb.toISOString()}/P1W`, dateFeb), - ).toEqual(addDays(dateFeb, 7)) + ).toEqual(addDays(dateFeb, 7)); expect( getNextOccurrence(`R/${dateFebLeap.toISOString()}/P1W`, dateFebLeap), - ).toEqual(addDays(dateFebLeap, 7)) + ).toEqual(addDays(dateFebLeap, 7)); expect( getNextOccurrence(`R/${dateApr.toISOString()}/P1W`, dateApr), - ).toEqual(addDays(dateApr, 7)) - }) + ).toEqual(addDays(dateApr, 7)); + }); it('should throw if no more occurrences are possible', () => { - const interval = `R1/${dateJan.toISOString()}/P1M` - const errorMsg = /No next occurrence is possible/ + const interval = `R1/${dateJan.toISOString()}/P1M`; + const errorMsg = /No next occurrence is possible/; expect(() => getNextOccurrence(interval, addMonths(dateJan, 0)), - ).not.toThrow(errorMsg) + ).not.toThrow(errorMsg); expect(() => getNextOccurrence(interval, addDays(dateJan, 10))).not.toThrow( errorMsg, - ) + ); expect(() => getNextOccurrence(interval, addMonths(dateJan, 1))).toThrow( errorMsg, - ) + ); expect(() => getNextOccurrence(interval, addMonths(dateJan, 2))).toThrow( errorMsg, - ) - }) + ); + }); it('should return the next occurrence with /PT', () => { expect(getNextOccurrence(`R/${nowISO}/PT30S`, now)).toEqual( addSeconds(now, 30), - ) - }) -}) + ); + }); +}); diff --git a/src/shared/helpers.ts b/src/shared/helpers.ts index e43d10be..5f990de2 100644 --- a/src/shared/helpers.ts +++ b/src/shared/helpers.ts @@ -1,25 +1,25 @@ -import type { SuccessResponse } from '@/shared/messages' -import type { WalletAddress } from '@interledger/open-payments/dist/types' -import { cx, CxOptions } from 'class-variance-authority' -import { twMerge } from 'tailwind-merge' -import { addSeconds } from 'date-fns/addSeconds' -import { isAfter } from 'date-fns/isAfter' -import { isBefore } from 'date-fns/isBefore' -import { parse, toSeconds } from 'iso8601-duration' -import type { Browser } from 'webextension-polyfill' -import type { Storage, RepeatingInterval, AmountValue } from './types' +import type { SuccessResponse } from '@/shared/messages'; +import type { WalletAddress } from '@interledger/open-payments/dist/types'; +import { cx, CxOptions } from 'class-variance-authority'; +import { twMerge } from 'tailwind-merge'; +import { addSeconds } from 'date-fns/addSeconds'; +import { isAfter } from 'date-fns/isAfter'; +import { isBefore } from 'date-fns/isBefore'; +import { parse, toSeconds } from 'iso8601-duration'; +import type { Browser } from 'webextension-polyfill'; +import type { Storage, RepeatingInterval, AmountValue } from './types'; export const cn = (...inputs: CxOptions) => { - return twMerge(cx(inputs)) -} + return twMerge(cx(inputs)); +}; export const formatCurrency = (value: any): string => { if (value < 1) { - return `${Math.round(value * 100)}c` + return `${Math.round(value * 100)}c`; } else { - return `$${parseFloat(value).toFixed(2)}` + return `$${parseFloat(value).toFixed(2)}`; } -} +}; const isWalletAddress = (o: any): o is WalletAddress => { return ( @@ -33,8 +33,8 @@ const isWalletAddress = (o: any): o is WalletAddress => { typeof o.authServer === 'string' && o.resourceServer && typeof o.resourceServer === 'string' - ) -} + ); +}; export const getWalletInformation = async ( walletAddressUrl: string, @@ -43,55 +43,55 @@ export const getWalletInformation = async ( headers: { Accept: 'application/json', }, - }) - const json = await response.json() + }); + const json = await response.json(); if (!isWalletAddress(json)) { - throw new Error('Invalid wallet address response.') + throw new Error('Invalid wallet address response.'); } - return json -} + return json; +}; export const success = ( payload: TPayload, ): SuccessResponse => ({ success: true, payload, -}) +}); export const failure = (message: string) => ({ success: false, message, -}) +}); -export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) +export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export const notNullOrUndef = ( t: T | null | undefined, name = '', ): T | never => { if (t == null) { - throw new Error(`Expecting not null for ${name}`) + throw new Error(`Expecting not null for ${name}`); } else { - return t + return t; } -} +}; export function debounceAsync>( func: (...args: T) => R, wait: number, ) { - let timeout: ReturnType | null = null + let timeout: ReturnType | null = null; return function (...args: T) { return new Promise>((resolve) => { - if (timeout != null) clearTimeout(timeout) + if (timeout != null) clearTimeout(timeout); timeout = setTimeout(() => { - timeout = null - void Promise.resolve(func(...args)).then(resolve) - }, wait) - }) - } + timeout = null; + void Promise.resolve(func(...args)).then(resolve); + }, wait); + }); + }; } // Based on https://stackoverflow.com/a/27078401 @@ -103,30 +103,30 @@ export function throttle( trailing: false, }, ) { - let result: R - let timeout: ReturnType | null = null - let previous = 0 + let result: R; + let timeout: ReturnType | null = null; + let previous = 0; const later = (...args: T) => { - previous = options.leading === false ? 0 : Date.now() - timeout = null - result = func(...args) - } + previous = options.leading === false ? 0 : Date.now(); + timeout = null; + result = func(...args); + }; return (...args: T) => { - const now = Date.now() - if (!previous && options.leading === false) previous = now - const remaining = wait - (now - previous) + const now = Date.now(); + if (!previous && options.leading === false) previous = now; + const remaining = wait - (now - previous); if (remaining <= 0 || remaining > wait) { if (timeout) { - clearTimeout(timeout) - timeout = null + clearTimeout(timeout); + timeout = null; } - previous = now - result = func(...args) + previous = now; + result = func(...args); } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining, ...args) + timeout = setTimeout(later, remaining, ...args); } - return result - } + return result; + }; } /** @@ -147,27 +147,27 @@ export function throttle( * ``` */ export class ThrottleBatch { - private argsList: Args[] = [] - private throttled: () => void + private argsList: Args[] = []; + private throttled: () => void; constructor( private func: (...arg: Args) => R, private argsReducer: (args: Args[]) => [...Args], wait: number, ) { - this.throttled = throttle(() => this.flush(), wait, { leading: true }) + this.throttled = throttle(() => this.flush(), wait, { leading: true }); } enqueue(...data: Args) { - this.argsList.push(data) - void this.throttled() + this.argsList.push(data); + void this.throttled(); } flush() { - if (!this.argsList.length) return - const args = this.argsReducer(this.argsList.slice()) - this.argsList = [] - return this.func(...args) + if (!this.argsList.length) return; + const args = this.argsReducer(this.argsList.slice()); + this.argsList = []; + return this.func(...args); } } @@ -175,33 +175,33 @@ export function debounceSync( func: (...args: T) => R, wait: number, ) { - let timeout: ReturnType | null = null + let timeout: ReturnType | null = null; return function (...args: T) { return new Promise((resolve) => { - if (timeout != null) clearTimeout(timeout) + if (timeout != null) clearTimeout(timeout); timeout = setTimeout(() => { - timeout = null - resolve(func(...args)) - }, wait) - }) - } + timeout = null; + resolve(func(...args)); + }, wait); + }); + }; } export function convert(value: bigint, source: number, target: number) { - const scaleDiff = target - source + const scaleDiff = target - source; if (scaleDiff > 0) { - return value * BigInt(Math.pow(10, scaleDiff)) + return value * BigInt(Math.pow(10, scaleDiff)); } - return value / BigInt(Math.pow(10, -scaleDiff)) + return value / BigInt(Math.pow(10, -scaleDiff)); } export function bigIntMax(a: T, b: T): T { - return BigInt(a) > BigInt(b) ? a : b + return BigInt(a) > BigInt(b) ? a : b; } -type TranslationKeys = keyof typeof import('../_locales/en/messages.json') +type TranslationKeys = keyof typeof import('../_locales/en/messages.json'); -export type Translation = ReturnType +export type Translation = ReturnType; export function tFactory(browser: Pick) { /** * Helper over calling cumbersome `this.browser.i18n.getMessage(key)` with @@ -210,51 +210,51 @@ export function tFactory(browser: Pick) { return ( key: T, substitutions?: string | string[], - ) => browser.i18n.getMessage(key, substitutions) + ) => browser.i18n.getMessage(key, substitutions); } -type Primitive = string | number | boolean | null | undefined +type Primitive = string | number | boolean | null | undefined; // Warn: Not a nested object equals or a deepEquals function export function objectEquals>(a: T, b: T) { - const keysA = Object.keys(a) - const keysB = Object.keys(b) - return JSON.stringify(a, keysA.sort()) === JSON.stringify(b, keysB.sort()) + const keysA = Object.keys(a); + const keysB = Object.keys(b); + return JSON.stringify(a, keysA.sort()) === JSON.stringify(b, keysB.sort()); } export const removeQueryParams = (urlString: string) => { - const url = new URL(urlString) - return url.origin + url.pathname -} + const url = new URL(urlString); + return url.origin + url.pathname; +}; export const isOkState = (state: Storage['state']) => { - return Object.values(state).every((value) => value === false) -} + return Object.values(state).every((value) => value === false); +}; const REPEATING_INTERVAL_REGEX = - /^R(\d*)\/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\/(P.+)$/ + /^R(\d*)\/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\/(P.+)$/; export const getNextOccurrence = ( interval: RepeatingInterval, base = new Date(), ): Date => { - const match = interval.match(REPEATING_INTERVAL_REGEX) + const match = interval.match(REPEATING_INTERVAL_REGEX); if (!match) { - throw new Error(`Invalid interval: ${interval}`) + throw new Error(`Invalid interval: ${interval}`); } - const count = match[1] ? parseInt(match[1], 10) : null - const startDate = new Date(match[2]) - const pattern = parse(match[3]) - const seconds = toSeconds(pattern, base) + const count = match[1] ? parseInt(match[1], 10) : null; + const startDate = new Date(match[2]); + const pattern = parse(match[3]); + const seconds = toSeconds(pattern, base); if (count && isAfter(base, addSeconds(startDate, count * seconds))) { - throw new Error('No next occurrence is possible beyond given time') + throw new Error('No next occurrence is possible beyond given time'); } - let date = new Date(startDate) + let date = new Date(startDate); while (!isBefore(base, date)) { - date = addSeconds(date, seconds) + date = addSeconds(date, seconds); } - return date -} + return date; +}; diff --git a/src/shared/logger.ts b/src/shared/logger.ts index ea7b3b74..bae7ad04 100644 --- a/src/shared/logger.ts +++ b/src/shared/logger.ts @@ -1,26 +1,26 @@ -import log from 'loglevel' +import log from 'loglevel'; // TODO: Disable debug logging in production export const createLogger = (level: log.LogLevelDesc = 'DEBUG') => { /** This disables all logging below the given level, */ - log.setLevel(level, false) + log.setLevel(level, false); - const factory = log.methodFactory + const factory = log.methodFactory; log.methodFactory = (methodName, logLevel, loggerName) => { - const raw = factory(methodName, logLevel, loggerName) + const raw = factory(methodName, logLevel, loggerName); return function (...args) { const messages = [ `%c${loggerName as string}`, 'font-weight: bold; text-transform: uppercase; background: #2f8785; padding-inline: 5px;', ...args, - ] - raw(...messages) - } - } - log.rebuild() + ]; + raw(...messages); + }; + }; + log.rebuild(); - return log -} + return log; +}; -export type Logger = log.RootLogger +export type Logger = log.RootLogger; diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 652d1a39..f9dea560 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -1,52 +1,55 @@ -import type { WalletAddress, OutgoingPayment } from '@interledger/open-payments' -import type { Browser } from 'webextension-polyfill' -import type { AmountValue, Storage } from '@/shared/types' -import type { PopupState } from '@/popup/lib/context' +import type { + WalletAddress, + OutgoingPayment, +} from '@interledger/open-payments'; +import type { Browser } from 'webextension-polyfill'; +import type { AmountValue, Storage } from '@/shared/types'; +import type { PopupState } from '@/popup/lib/context'; // #region MessageManager export interface SuccessResponse { - success: true - payload: TPayload + success: true; + payload: TPayload; } export interface ErrorResponse { - success: false - message: string + success: false; + message: string; } export type Response = | SuccessResponse - | ErrorResponse + | ErrorResponse; -type MessageMap = Record +type MessageMap = Record; type MessagesWithInput = { - [K in keyof T as T[K]['input'] extends never ? never : K]: T[K] -} + [K in keyof T as T[K]['input'] extends never ? never : K]: T[K]; +}; type MessagesWithoutInput = { - [K in keyof T as T[K]['input'] extends never ? K : never]: T[K] -} + [K in keyof T as T[K]['input'] extends never ? K : never]: T[K]; +}; export class MessageManager { - private browser: Browser + private browser: Browser; constructor({ browser }: { browser: Browser }) { - this.browser = browser + this.browser = browser; } async send, K extends keyof TT>( action: K, payload: TT[K]['input'], - ): Promise> + ): Promise>; async send, K extends keyof TT>( action: K, payload?: never, - ): Promise> + ): Promise>; async send( action: K, payload?: TMessages[K]['input'] extends void ? never : TMessages[K]['input'], ): Promise> { - return await this.browser.runtime.sendMessage({ action, payload }) + return await this.browser.runtime.sendMessage({ action, payload }); } async sendToTab( @@ -59,8 +62,8 @@ export class MessageManager { ? ErrorResponse : Response > { - const message = { action, payload } - return await this.browser.tabs.sendMessage(tabId, message, { frameId }) + const message = { action, payload }; + return await this.browser.tabs.sendMessage(tabId, message, { frameId }); } async sendToActiveTab( @@ -71,183 +74,183 @@ export class MessageManager { ? ErrorResponse : Response > { - const window = await this.browser.windows.getCurrent() + const window = await this.browser.windows.getCurrent(); const activeTabs = await this.browser.tabs.query({ active: true, windowId: window.id, - }) - const activeTab = activeTabs[0] - const message = { action, payload } - return await this.browser.tabs.sendMessage(activeTab.id as number, message) + }); + const activeTab = activeTabs[0]; + const message = { action, payload }; + return await this.browser.tabs.sendMessage(activeTab.id as number, message); } } // #endregion // #region Popup ↦ BG export interface ConnectWalletPayload { - walletAddressUrl: string - amount: string - recurring: boolean + walletAddressUrl: string; + amount: string; + recurring: boolean; } export interface AddFundsPayload { - amount: string - recurring: boolean + amount: string; + recurring: boolean; } export interface PayWebsitePayload { - amount: string + amount: string; } export interface UpdateRateOfPayPayload { - rateOfPay: string + rateOfPay: string; } export type PopupToBackgroundMessage = { GET_CONTEXT_DATA: { - input: never - output: PopupState - } + input: never; + output: PopupState; + }; CONNECT_WALLET: { - input: ConnectWalletPayload - output: never - } + input: ConnectWalletPayload; + output: never; + }; RECONNECT_WALLET: { - input: never - output: never - } + input: never; + output: never; + }; ADD_FUNDS: { - input: AddFundsPayload - output: never - } + input: AddFundsPayload; + output: never; + }; DISCONNECT_WALLET: { - input: never - output: never - } + input: never; + output: never; + }; TOGGLE_WM: { - input: never - output: never - } + input: never; + output: never; + }; PAY_WEBSITE: { - input: PayWebsitePayload - output: never - } + input: PayWebsitePayload; + output: never; + }; UPDATE_RATE_OF_PAY: { - input: UpdateRateOfPayPayload - output: never - } -} + input: UpdateRateOfPayPayload; + output: never; + }; +}; // #endregion // #region Content ↦ BG export interface CheckWalletAddressUrlPayload { - walletAddressUrl: string + walletAddressUrl: string; } export interface StartMonetizationPayload { - walletAddress: WalletAddress - requestId: string + walletAddress: WalletAddress; + requestId: string; } export interface StopMonetizationPayload { - requestId: string - intent?: 'remove' | 'disable' + requestId: string; + intent?: 'remove' | 'disable'; } export interface ResumeMonetizationPayload { - requestId: string + requestId: string; } export interface IsTabMonetizedPayload { - value: boolean + value: boolean; } export type ContentToBackgroundMessage = { CHECK_WALLET_ADDRESS_URL: { - input: CheckWalletAddressUrlPayload - output: WalletAddress - } + input: CheckWalletAddressUrlPayload; + output: WalletAddress; + }; STOP_MONETIZATION: { - input: StopMonetizationPayload[] - output: never - } + input: StopMonetizationPayload[]; + output: never; + }; START_MONETIZATION: { - input: StartMonetizationPayload[] - output: never - } + input: StartMonetizationPayload[]; + output: never; + }; RESUME_MONETIZATION: { - input: ResumeMonetizationPayload[] - output: never - } + input: ResumeMonetizationPayload[]; + output: never; + }; IS_WM_ENABLED: { - input: never - output: boolean - } -} + input: never; + output: boolean; + }; +}; // #endregion // #region To BG type ToBackgroundMessageMap = PopupToBackgroundMessage & - ContentToBackgroundMessage + ContentToBackgroundMessage; export type ToBackgroundMessage = { [K in keyof ToBackgroundMessageMap]: { - action: K - payload: ToBackgroundMessageMap[K]['input'] - } -}[keyof ToBackgroundMessageMap] + action: K; + payload: ToBackgroundMessageMap[K]['input']; + }; +}[keyof ToBackgroundMessageMap]; // #endregion // #region BG ↦ Content export interface MonetizationEventDetails { - amountSent: PaymentCurrencyAmount - incomingPayment: OutgoingPayment['receiver'] - paymentPointer: WalletAddress['id'] + amountSent: PaymentCurrencyAmount; + incomingPayment: OutgoingPayment['receiver']; + paymentPointer: WalletAddress['id']; } export interface MonetizationEventPayload { - requestId: string - details: MonetizationEventDetails + requestId: string; + details: MonetizationEventDetails; } export interface EmitToggleWMPayload { - enabled: boolean + enabled: boolean; } export type BackgroundToContentMessage = { MONETIZATION_EVENT: { - input: MonetizationEventPayload - output: never - } + input: MonetizationEventPayload; + output: never; + }; EMIT_TOGGLE_WM: { - input: EmitToggleWMPayload - output: never - } -} + input: EmitToggleWMPayload; + output: never; + }; +}; export type ToContentMessage = { [K in keyof BackgroundToContentMessage]: { - action: K - payload: BackgroundToContentMessage[K]['input'] - } -}[keyof BackgroundToContentMessage] + action: K; + payload: BackgroundToContentMessage[K]['input']; + }; +}[keyof BackgroundToContentMessage]; // #endregion // #region BG ↦ Popup -export const BACKGROUND_TO_POPUP_CONNECTION_NAME = 'popup' +export const BACKGROUND_TO_POPUP_CONNECTION_NAME = 'popup'; // These methods are fire-and-forget, nothing is returned. export interface BackgroundToPopupMessagesMap { - SET_BALANCE: Record<'recurring' | 'oneTime' | 'total', AmountValue> - SET_IS_MONETIZED: boolean - SET_STATE: { state: Storage['state']; prevState: Storage['state'] } - SET_ALL_SESSIONS_INVALID: boolean + SET_BALANCE: Record<'recurring' | 'oneTime' | 'total', AmountValue>; + SET_IS_MONETIZED: boolean; + SET_STATE: { state: Storage['state']; prevState: Storage['state'] }; + SET_ALL_SESSIONS_INVALID: boolean; } export type BackgroundToPopupMessage = { [K in keyof BackgroundToPopupMessagesMap]: { - type: K - data: BackgroundToPopupMessagesMap[K] - } -}[keyof BackgroundToPopupMessagesMap] + type: K; + data: BackgroundToPopupMessagesMap[K]; + }; +}[keyof BackgroundToPopupMessagesMap]; // #endregion diff --git a/src/shared/types.ts b/src/shared/types.ts index 1ee2863c..fc5ff6a8 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,43 +1,43 @@ -import type { WalletAddress } from '@interledger/open-payments/dist/types' -import type { Tabs } from 'webextension-polyfill' +import type { WalletAddress } from '@interledger/open-payments/dist/types'; +import type { Tabs } from 'webextension-polyfill'; /** Bigint amount, before transformation with assetScale */ -export type AmountValue = string +export type AmountValue = string; /** https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals */ -export type RepeatingInterval = string +export type RepeatingInterval = string; /** Wallet amount */ export interface WalletAmount { - value: string - interval?: RepeatingInterval + value: string; + interval?: RepeatingInterval; } /** Amount interface - used in the `exceptionList` */ export interface Amount { - value: AmountValue - interval: number + value: AmountValue; + interval: number; } export interface AccessToken { - value: AmountValue - manageUrl: string + value: AmountValue; + manageUrl: string; } interface GrantDetailsBase { - type: string - accessToken: AccessToken - continue: { url: string; accessToken: string } + type: string; + accessToken: AccessToken; + continue: { url: string; accessToken: string }; } export interface OneTimeGrant extends GrantDetailsBase { - type: 'one-time' - amount: Omit + type: 'one-time'; + amount: Omit; } export interface RecurringGrant extends GrantDetailsBase { - type: 'recurring' - amount: Required + type: 'recurring'; + amount: Required; } -export type GrantDetails = OneTimeGrant | RecurringGrant +export type GrantDetails = OneTimeGrant | RecurringGrant; export type ExtensionState = | never // just added for code formatting @@ -46,44 +46,44 @@ export type ExtensionState = /** The public key no longer exists or valid in connected wallet */ | 'key_revoked' /** The wallet is out of funds, cannot make payments */ - | 'out_of_funds' + | 'out_of_funds'; export interface Storage { /** * Storage structure version. Used in migrations. Numbers are sequential. * Inspired by database upgrades in IndexedDB API. */ - version: number + version: number; /** If web monetization is enabled */ - enabled: boolean + enabled: boolean; /** If a wallet is connected or not */ - connected: boolean + connected: boolean; /** Extension state */ - state: Partial> + state: Partial>; - rateOfPay?: string | undefined | null - minRateOfPay?: string | undefined | null - maxRateOfPay?: string | undefined | null + rateOfPay?: string | undefined | null; + minRateOfPay?: string | undefined | null; + maxRateOfPay?: string | undefined | null; /** User wallet address information */ - walletAddress?: WalletAddress | undefined | null + walletAddress?: WalletAddress | undefined | null; - recurringGrant?: RecurringGrant | undefined | null - recurringGrantSpentAmount?: AmountValue | undefined | null - oneTimeGrant?: OneTimeGrant | undefined | null - oneTimeGrantSpentAmount?: AmountValue | undefined | null + recurringGrant?: RecurringGrant | undefined | null; + recurringGrantSpentAmount?: AmountValue | undefined | null; + oneTimeGrant?: OneTimeGrant | undefined | null; + oneTimeGrantSpentAmount?: AmountValue | undefined | null; /** Exception list with websites and each specific amount */ exceptionList: { - [website: string]: Amount - } + [website: string]: Amount; + }; /** Key information */ - publicKey: string - privateKey: string - keyId: string + publicKey: string; + privateKey: string; + keyId: string; } -export type StorageKey = keyof Storage +export type StorageKey = keyof Storage; export type PopupStore = Omit< Storage, @@ -94,18 +94,18 @@ export type PopupStore = Omit< | 'recurringGrant' | 'oneTimeGrant' > & { - balance: AmountValue - isSiteMonetized: boolean - url: string | undefined + balance: AmountValue; + isSiteMonetized: boolean; + url: string | undefined; grants?: Partial<{ - oneTime: OneTimeGrant['amount'] - recurring: RecurringGrant['amount'] - }> - hasAllSessionsInvalid: boolean -} + oneTime: OneTimeGrant['amount']; + recurring: RecurringGrant['amount']; + }>; + hasAllSessionsInvalid: boolean; +}; export type DeepNonNullable = { - [P in keyof T]?: NonNullable -} + [P in keyof T]?: NonNullable; +}; -export type TabId = NonNullable +export type TabId = NonNullable; diff --git a/tailwind.config.ts b/tailwind.config.ts index 9f09769b..b40ced6a 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,5 +1,5 @@ -import { Config } from 'tailwindcss' -import forms from '@tailwindcss/forms' +import { Config } from 'tailwindcss'; +import forms from '@tailwindcss/forms'; module.exports = { content: [ @@ -47,4 +47,4 @@ module.exports = { }, }, plugins: [forms], -} satisfies Config +} satisfies Config;