From 2ac82fd7dc1d375f8149bc58394131e1c7b5ee25 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Tue, 13 Feb 2024 06:41:12 +0200 Subject: [PATCH 1/4] feat: add code container (#96) * Add button variants * Update CSS vars * Add component and tests * Remove comments * Address feedback and fix stylings --- src/components/__tests__/code.test.tsx | 30 ++++++++++++++ src/components/button.tsx | 2 + src/components/code.tsx | 55 ++++++++++++++++++++++++++ src/components/icons.tsx | 32 +++++++++++++++ src/popup/index.css | 1 + tailwind.config.ts | 1 + 6 files changed, 121 insertions(+) create mode 100644 src/components/__tests__/code.test.tsx create mode 100644 src/components/code.tsx diff --git a/src/components/__tests__/code.test.tsx b/src/components/__tests__/code.test.tsx new file mode 100644 index 000000000..12f3bd1ab --- /dev/null +++ b/src/components/__tests__/code.test.tsx @@ -0,0 +1,30 @@ +import { fireEvent, render } from '@testing-library/react' +import React from 'react' + +import { Code } from '../code' + +describe('Code', () => { + it('should render the code component', () => { + const { queryByRole, container } = render() + const code = container.querySelector('code') + + 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() + + fireEvent.click(copyButton) + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test') + }) +}) diff --git a/src/components/button.tsx b/src/components/button.tsx index 4566fc043..6d6f75a89 100644 --- a/src/components/button.tsx +++ b/src/components/button.tsx @@ -16,9 +16,11 @@ const buttonVariants = cva( variant: { default: 'bg-button-base text-white hover:bg-button-base-hover', destructive: 'bg-error text-error hover:bg-error-hover', + ghost: '', }, size: { default: 'py-4 px-6 font-medium', + icon: 'h-6 w-6', }, fullWidth: { true: 'w-full', diff --git a/src/components/code.tsx b/src/components/code.tsx new file mode 100644 index 000000000..e82b0d173 --- /dev/null +++ b/src/components/code.tsx @@ -0,0 +1,55 @@ +import React from 'react' + +import { cn } from '@/utils/cn' + +import { Button } from './button' +import { CheckIcon, ClipboardIcon } from './icons' + +interface CodeProps extends React.HTMLAttributes { + value: string +} + +export const Code = ({ value, className, ...props }: CodeProps) => { + return ( +
+ {value} + +
+ ) +} + +interface CopyButtonProps extends React.HTMLAttributes { + value: string +} + +const CopyButton = ({ value, ...props }: CopyButtonProps) => { + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + if (hasCopied === true) { + setTimeout(() => { + setHasCopied(false) + }, 2000) + } + }, [hasCopied]) + + return ( + + ) +} diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 11950a586..351d5e112 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -93,3 +93,35 @@ export const DollarSign = (props: React.SVGProps) => { ) } + +export const ClipboardIcon = (props: React.SVGProps) => { + return ( + + + + ) +} + +export const CheckIcon = (props: React.SVGProps) => { + return ( + + + + ) +} diff --git a/src/popup/index.css b/src/popup/index.css index ea53da127..8bd1f96f9 100644 --- a/src/popup/index.css +++ b/src/popup/index.css @@ -14,6 +14,7 @@ /* Background colors */ --bg-primary: 59 130 246; + --bg-nav-active: 226 232 240; --bg-error: 254 226 226; --bg-error-hover: 254 202 202; --bg-button-base: 86 183 181; diff --git a/tailwind.config.ts b/tailwind.config.ts index f44ecc347..2b9443c67 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -21,6 +21,7 @@ module.exports = { backgroundColor: { primary: 'rgb(var(--bg-primary) / )', error: 'rgb(var(--bg-error) / )', + 'nav-active': 'rgb(var(--bg-nav-active) / )', 'error-hover': 'rgb(var(--bg-error-hover) / )', 'button-base': 'rgb(var(--bg-button-base) / )', 'button-base-hover': 'rgb(var(--bg-button-base-hover) / )', From 7a42437721af6ecf5c0dc7a61eb186e2dd834320 Mon Sep 17 00:00:00 2001 From: Diana Fulga Date: Mon, 19 Feb 2024 13:06:29 +0200 Subject: [PATCH 2/4] feat: settings page - logic, ui (#99) * settings page - logic, ui * Implement feedback --------- Co-authored-by: Diana Fulga --- src/components/icons.tsx | 31 +++- src/components/layout/header.tsx | 12 ++ src/components/radio-group.tsx | 13 +- src/popup/Popup.scss | 1 - src/popup/Popup.tsx | 2 +- src/popup/index.css | 2 +- src/popup/pages/Settings.tsx | 159 +++++++++++++++++- .../__tests__/popup-context.test.tsx | 2 +- src/providers/index.ts | 2 +- .../{popup-context.tsx => popup.provider.tsx} | 2 + src/providers/popup.state.tsx | 11 ++ src/providers/providers.interface.ts | 2 + 12 files changed, 229 insertions(+), 10 deletions(-) rename src/providers/{popup-context.tsx => popup.provider.tsx} (95%) create mode 100644 src/providers/popup.state.tsx diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 351d5e112..7964b5d6d 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -94,6 +94,35 @@ export const DollarSign = (props: React.SVGProps) => { ) } +export const WarningSign = (props: React.SVGProps) => { + return ( + + + + + + + + + ) +} + export const ClipboardIcon = (props: React.SVGProps) => { return ( ) => { ) -} +} \ No newline at end of file diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 77b84fb5e..cd37c4ff0 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -4,6 +4,8 @@ import { runtime } from 'webextension-polyfill' import { ArrowBack, Settings } from '../icons' import { ROUTES } from '../router-provider' +import { usePopup } from '@/providers/popup.state' +import { Switch } from '../switch' const Logo = runtime.getURL('assets/images/logo.svg') @@ -29,6 +31,15 @@ const NavigationButton = () => { } export const Header = () => { + const { + data: { wmEnabled }, + setData, + } = usePopup() + + const switchWmEnabled = () => { + setData(prevState => ({ ...prevState, wmEnabled: !prevState.wmEnabled })) + } + return (
@@ -37,6 +48,7 @@ export const Header = () => {
+
) diff --git a/src/components/radio-group.tsx b/src/components/radio-group.tsx index 862cc0a4e..4f1cb44c4 100644 --- a/src/components/radio-group.tsx +++ b/src/components/radio-group.tsx @@ -86,6 +86,7 @@ export interface RadioGroupProps disabled?: boolean items: Omit[] name: string + handleChange?: (value: string) => void } export const RadioGroup = ({ @@ -95,8 +96,13 @@ export const RadioGroup = ({ fullWidth, disabled, className, + handleChange, + value, }: RadioGroupProps) => { - const checkedItem = useMemo(() => items.findIndex(item => item.checked), [items]) + const checkedItem = useMemo( + () => items.findIndex(item => item.checked || item.value === value), + [items, value], + ) const [selected, setSelected] = useState(checkedItem) const handleKeyDown = (event: React.KeyboardEvent) => { @@ -140,7 +146,10 @@ export const RadioGroup = ({ disabled={disabled} checked={selected === index} noSelected={selected === -1 && index === 0} - onChange={() => setSelected(index)} + onChange={() => { + setSelected(index) + if (handleChange) handleChange(item.value) + }} /> ))} diff --git a/src/popup/Popup.scss b/src/popup/Popup.scss index da855dc02..62e29e09b 100644 --- a/src/popup/Popup.scss +++ b/src/popup/Popup.scss @@ -33,7 +33,6 @@ body { flex-direction: column; align-items: center; justify-content: center; - min-height: 192px; flex-basis: auto; padding: 0 20px 10px; diff --git a/src/popup/Popup.tsx b/src/popup/Popup.tsx index 82d733ffa..f0cfab5e8 100644 --- a/src/popup/Popup.tsx +++ b/src/popup/Popup.tsx @@ -3,7 +3,7 @@ import './Popup.scss' import React from 'react' import { RouterProvider } from '@/components/router-provider' -import { PopupProvider } from '@/providers/popup-context' +import { PopupProvider } from '@/providers/popup.provider' const Popup = () => { return ( diff --git a/src/popup/index.css b/src/popup/index.css index 8bd1f96f9..a2c5f513c 100644 --- a/src/popup/index.css +++ b/src/popup/index.css @@ -30,6 +30,6 @@ /* Popup */ --popup-width: 448px; - --popup-height: 559px; + --popup-height: 600px; } } diff --git a/src/popup/pages/Settings.tsx b/src/popup/pages/Settings.tsx index eacdd66aa..86a0b5406 100644 --- a/src/popup/pages/Settings.tsx +++ b/src/popup/pages/Settings.tsx @@ -1,5 +1,160 @@ -import React from 'react' +import React, { useState } from 'react' + +import { Input } from '@/components/input' +import { Label } from '@/components/label' +import { RadioGroup } from '@/components/radio-group' +import { Button } from '@/components/button' +import { DollarSign, WarningSign } from '@/components/icons' + +import { usePopup } from '@/providers/popup.state' export const Settings = () => { - return <>Settings + const { + data: { + connected, + wmEnabled, + wallet, + amount, + amountType: { recurring }, + }, + setData, + } = usePopup() + + const [_wallet, setWallet] = useState(wallet) + const [_walletError, setWalletError] = useState('') + const [_amount, setAmount] = useState(`${amount}`) + const [_amountError, setAmountError] = useState('') + const [_recurring, setRecurring] = useState(`${recurring}`) + const [loading, setLoading] = useState(false) + + const handleChangeWallet = (event: any) => { + setWallet(event.target.value) + } + + const handleChangeAmount = (event: any) => { + setAmount(event.target.value) + } + + const handleChangeRecurring = (value: string) => { + setRecurring(value) + } + + const handleConnect = () => { + if (loading) return + + setWalletError('') + setAmountError('') + + setLoading(true) + let errors = false + + if (!_wallet) { + errors = true + setWalletError('Please fill in wallet address') + } + if (!_amount) { + errors = true + setAmountError('Please fill in the amount') + } else if (!parseFloat(_amount)) { + errors = true + setAmountError('Amount must be a number bigger than 0') + } + + if (errors) { + setLoading(false) + return + } + + setWalletError('') + setAmountError('') + + setData(prevState => ({ + ...prevState, + amount: parseFloat(_amount), + wallet: _wallet, + amountType: { recurring: _recurring === 'true' }, + connected: true, + })) + + setLoading(false) + } + + const handleDisconnect = () => { + setData(prevState => ({ + ...prevState, + connected: false, + })) + } + + const switchConnect = () => { + if (!connected) handleConnect() + else handleDisconnect() + } + + if (!wmEnabled) + return ( +
+ +

Web Monetization has been turned off.

+
+ ) + + return ( + <> + +
+

+ Get a wallet address from a provider before connecting it below. Please find a list of + available wallets +

+ + here. + +
+ + + } + /> + + + + ) } diff --git a/src/providers/__tests__/popup-context.test.tsx b/src/providers/__tests__/popup-context.test.tsx index ed841a6cb..cb6996be3 100644 --- a/src/providers/__tests__/popup-context.test.tsx +++ b/src/providers/__tests__/popup-context.test.tsx @@ -1,7 +1,7 @@ import { act, render, screen } from '@testing-library/react' import React, { useContext } from 'react' -import { defaultData, PopupContext, PopupProvider } from '../popup-context' +import { defaultData, PopupContext, PopupProvider } from '../popup.provider' const TestComponent = () => { const { data, setData } = useContext(PopupContext) diff --git a/src/providers/index.ts b/src/providers/index.ts index 35b996f2c..f6e55cc2d 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,3 +1,3 @@ -import { PopupProvider } from './popup-context' +import { PopupProvider } from './popup.provider' export { PopupProvider } diff --git a/src/providers/popup-context.tsx b/src/providers/popup.provider.tsx similarity index 95% rename from src/providers/popup-context.tsx rename to src/providers/popup.provider.tsx index 392f2c7f2..db7634d2f 100644 --- a/src/providers/popup-context.tsx +++ b/src/providers/popup.provider.tsx @@ -3,6 +3,8 @@ import React, { createContext, useState } from 'react' import { PopupContextValue, TPopupContext } from './providers.interface' export const defaultData = { + connected: false, + wallet: '', amount: 0, amountType: { recurring: true, diff --git a/src/providers/popup.state.tsx b/src/providers/popup.state.tsx new file mode 100644 index 000000000..9671d9e30 --- /dev/null +++ b/src/providers/popup.state.tsx @@ -0,0 +1,11 @@ +import { useContext } from 'react' + +import { PopupContext } from './popup.provider' + +export const usePopup = () => { + const context = useContext(PopupContext) + if (context === undefined) { + throw new Error('usePopup must be used with a PopupContext') + } + return context +} diff --git a/src/providers/providers.interface.ts b/src/providers/providers.interface.ts index 9be002fdc..14603169c 100644 --- a/src/providers/providers.interface.ts +++ b/src/providers/providers.interface.ts @@ -6,6 +6,8 @@ export interface PopupContextValue { } export type TPopupContext = { + connected: boolean + wallet: string amount: number amountType: { recurring: boolean From 57a329152fcb4a45c7f4b36e1af647d9ee0ce982 Mon Sep 17 00:00:00 2001 From: Diana Fulga Date: Mon, 19 Feb 2024 13:25:29 +0200 Subject: [PATCH 3/4] chore: Move load (#97) * Move load * Revent to mv2 * Implement feedback --------- Co-authored-by: Diana Fulga --- src/background/Background.ts | 25 +++++--- src/background/grantFlow.ts | 3 + src/content/index.ts | 14 +++++ src/content/index.tsx | 26 --------- src/content/messageListener.ts | 15 +++-- src/content/static/index.ts | 13 +++++ src/{utils => content/static}/polyfill.ts | 2 - src/manifest/chrome-v3.json | 70 +++++++++++------------ src/manifest/chrome.json | 5 ++ src/types/message.d.ts | 1 + webpack.config.utils.ts | 5 +- 11 files changed, 97 insertions(+), 82 deletions(-) create mode 100755 src/content/index.ts delete mode 100755 src/content/index.tsx create mode 100644 src/content/static/index.ts rename src/{utils => content/static}/polyfill.ts (96%) diff --git a/src/background/Background.ts b/src/background/Background.ts index f13911f16..b7ca80a58 100644 --- a/src/background/Background.ts +++ b/src/background/Background.ts @@ -1,4 +1,4 @@ -import { runtime, tabs } from 'webextension-polyfill' +import { Runtime, runtime, tabs } from 'webextension-polyfill' import { PaymentFlowService } from '@/background/grantFlow' @@ -23,15 +23,24 @@ class Background { subscribeToMessages() { this.subscriptions = this.messageHandlers.map((handler: any) => { - const listener: any = async (message: EXTMessage) => { + const listener: any = ( + message: EXTMessage, + sender: Runtime.MessageSender, + sendResponse: (res: any) => void, + ) => { if (handler.type === message.type) { - try { - await handler.callback(message.data, this) - } catch (error) { - console.log('[===== Error in MessageListener =====]', error) - return error - } + handler + .callback(message.data, this) + .then((res: any) => { + sendResponse(res) + }) + .catch((error: any) => { + console.log('[===== Error in MessageListener =====]', error) + sendResponse(error) + }) } + + return true } runtime.onMessage.addListener(listener) diff --git a/src/background/grantFlow.ts b/src/background/grantFlow.ts index 18fac5fb0..ce5cd9373 100644 --- a/src/background/grantFlow.ts +++ b/src/background/grantFlow.ts @@ -94,6 +94,9 @@ export class PaymentFlowService { this.continuationRequestToken = continuationRequest.continuationRequestToken const currentTabId = await this.getCurrentActiveTabId() + + await tabs.sendMessage(currentTabId ?? 0, { type: 'LOAD' }) + await tabs.sendMessage(currentTabId ?? 0, { type: 'START_PAYMENTS' }) } diff --git a/src/content/index.ts b/src/content/index.ts new file mode 100755 index 000000000..5ede1b8d4 --- /dev/null +++ b/src/content/index.ts @@ -0,0 +1,14 @@ +import { runtime } from 'webextension-polyfill' + +import { initMonetizationTagManager } from '@/utils/monetizationTagManager' + +import { loadObserver } from './linksObserver' +import MessageListener from './messageListener' + +runtime.onMessage.addListener(MessageListener) + +// DEBUG PURPOSE +loadObserver() + +// TBD - check logic +initMonetizationTagManager() diff --git a/src/content/index.tsx b/src/content/index.tsx deleted file mode 100755 index 56d02f73d..000000000 --- a/src/content/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { runtime } from 'webextension-polyfill' - -import { initMonetizationTagManager } from '@/utils/monetizationTagManager' -import { wm2Polyfill } from '@/utils/polyfill' - -import { loadObserver } from './linksObserver' -import MessageListener from './messageListener' - -// import "./content.css"; - -runtime.onMessage.addListener(MessageListener) - -function inject(configure: (_script: HTMLScriptElement) => void) { - const script = document.createElement('script') - configure(script) - document.documentElement.appendChild(script) - // document.documentElement.removeChild(script) -} - -// eslint-disable-next-line @typescript-eslint/no-extra-semi -;(function injectCode(code: string) { - inject(script => (script.innerHTML = code)) -})(wm2Polyfill) - -loadObserver() -initMonetizationTagManager() diff --git a/src/content/messageListener.ts b/src/content/messageListener.ts index e61fed350..709a491d0 100644 --- a/src/content/messageListener.ts +++ b/src/content/messageListener.ts @@ -1,16 +1,15 @@ -// import { Runtime } from 'webextension-polyfill' - import { PaymentSender } from '@/content/monetization' const paymentSender = new PaymentSender() -export const onRequest = async ( - msg: EXTMessage, - // sender: Runtime.SendMessageOptionsType, -): Promise => { - // console.log('~~~~~~~', msg) - +export const onRequest = async (msg: EXTMessage): Promise => { switch (msg.type) { + case 'LOAD': { + const monetizationTag = document.querySelector('link[rel="monetization"]') + monetizationTag?.dispatchEvent(new Event('load')) + break + } + case 'IS_MONETIZATION_READY': { const monetizationTag = document.querySelector('link[rel="monetization"]') diff --git a/src/content/static/index.ts b/src/content/static/index.ts new file mode 100644 index 000000000..7bdea7c1b --- /dev/null +++ b/src/content/static/index.ts @@ -0,0 +1,13 @@ +import { wm2Polyfill } from '@/content/static/polyfill' + +function inject(configure: (_script: HTMLScriptElement) => void) { + const script = document.createElement('script') + configure(script) + document.documentElement.appendChild(script) + document.documentElement.removeChild(script) +} + +// eslint-disable-next-line @typescript-eslint/no-extra-semi +;(function injectCode(code: string) { + inject(script => (script.innerHTML = code)) +})(wm2Polyfill) diff --git a/src/utils/polyfill.ts b/src/content/static/polyfill.ts similarity index 96% rename from src/utils/polyfill.ts rename to src/content/static/polyfill.ts index 6acf624ee..e394da202 100644 --- a/src/utils/polyfill.ts +++ b/src/content/static/polyfill.ts @@ -1,7 +1,5 @@ // language=JavaScript export const wm2Polyfill = ` - var monetizationTag = document.querySelector('link[rel="monetization"]'); - monetizationTag.dispatchEvent(new Event('load')); const dbg = () => { } dbg('setonmonetization property start') diff --git a/src/manifest/chrome-v3.json b/src/manifest/chrome-v3.json index 622b6a886..f505e5078 100644 --- a/src/manifest/chrome-v3.json +++ b/src/manifest/chrome-v3.json @@ -1,39 +1,37 @@ { - "name": "__MSG_appName__", - "version": "1.0.1", - "manifest_version": 3, - "description": "__MSG_appDescription__", - "icons": { - "34": "assets/icons/icon-34.png", - "128": "assets/icons/icon-128.png" + "name": "__MSG_appName__", + "version": "1.0.1", + "manifest_version": 3, + "description": "__MSG_appDescription__", + "icons": { + "34": "assets/icons/icon-34.png", + "128": "assets/icons/icon-128.png" + }, + "default_locale": "en", + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content/content.js"] }, - "default_locale": "en", - "content_scripts": [ - { - "matches": ["http://*/*", "https://*/*"], - "js": ["content/content.js"] - } - ], - "background": { - "service_worker": "background/background.js" - }, - "permissions": ["tabs", "storage"], - "host_permissions": ["http://*/*", "https://*/*"], - "options_ui": { - "page": "options/index.html" - }, - "action": { - "default_icon": { - "16": "assets/icons/icon-16.png", - "48": "assets/icons/icon-48.png" - }, - "default_title": "Web Monetization", - "default_popup": "popup/index.html" - }, - "web_accessible_resources": [ - { - "resources": ["assets/*", "content/*", "options/*", "popup/*", "background/*"], - "matches": [""] - } - ] + { + "run_at": "document_start", + "matches": ["http://*/*", "https://*/*"], + "js": ["contentStatic/contentStatic.js"] + } + ], + "background": { + "service_worker": "background/background.js" + }, + "permissions": ["tabs", "storage"], + "host_permissions": ["http://*/*", "https://*/*"], + "action": { + "default_title": "Web Monetization", + "default_popup": "popup/index.html" + }, + "web_accessible_resources": [ + { + "resources": ["assets/*", "content/*", "options/*", "popup/*", "background/*"], + "matches": [""] + } + ] } diff --git a/src/manifest/chrome.json b/src/manifest/chrome.json index 8805a11e1..ed41c9474 100644 --- a/src/manifest/chrome.json +++ b/src/manifest/chrome.json @@ -12,6 +12,11 @@ { "matches": ["http://*/*", "https://*/*", ""], "js": ["content/content.js"] + }, + { + "run_at": "document_start", + "matches": ["http://*/*", "https://*/*"], + "js": ["contentStatic/contentStatic.js"] } ], "background": { diff --git a/src/types/message.d.ts b/src/types/message.d.ts index 8dc5b546a..8f46fc779 100644 --- a/src/types/message.d.ts +++ b/src/types/message.d.ts @@ -10,6 +10,7 @@ declare type EXTMessageType = | 'STOP_PAYMENTS' | 'PAYMENT_SUCCESS' | 'PAUSE_PAYMENTS' + | 'LOAD' declare type EXTMessage = { type: EXTMessageType diff --git a/webpack.config.utils.ts b/webpack.config.utils.ts index 3361087be..32614c348 100644 --- a/webpack.config.utils.ts +++ b/webpack.config.utils.ts @@ -49,7 +49,7 @@ const EnvConfig: EnvironmentConfig = { ? Directories.DIST_DIR : Directories.DEV_DIR, ...(process.env.NODE_ENV ? { NODE_ENV: process.env.NODE_ENV } : { NODE_ENV: 'development' }), - ...(process.env.TARGET ? { TARGET: process.env.TARGET } : { TARGET: 'chrome' }), + ...(process.env.TARGET ? { TARGET: process.env.TARGET } : { TARGET: 'chrome-v3' }), } /** @@ -109,7 +109,8 @@ export const getOutput = (browserDir: string, outputDir = Directories.DEV_DIR) = export const getEntry = (sourceDir = Directories.SRC_DIR) => { return { popup: [path.resolve(__dirname, `${sourceDir}/popup/index.tsx`)], - content: [path.resolve(__dirname, `${sourceDir}/content/index.tsx`)], + content: [path.resolve(__dirname, `${sourceDir}/content/index.ts`)], + contentStatic: [path.resolve(__dirname, `${sourceDir}/content/static/index.ts`)], background: [path.resolve(__dirname, `${sourceDir}/background/index.ts`)], } } From b94489888e75e980fe15d0b8e4902a8cac4a2e97 Mon Sep 17 00:00:00 2001 From: Ionut Anin Date: Mon, 19 Feb 2024 16:50:06 +0200 Subject: [PATCH 4/4] feat: extension background storage (#98) * extension storage config, sync background storage with popup context storage * code review updates * fix storage import --- src/background/Background.ts | 14 ++++++- src/components/switch.tsx | 5 ++- src/messageHandlers/getStorageData.ts | 13 +++++++ .../isMonetizationReadyHandler.ts | 4 +- src/popup/pages/Home.tsx | 1 + .../__tests__/popup-context.test.tsx | 9 +++++ src/providers/popup.provider.tsx | 13 ++++++- src/types/message.d.ts | 2 + src/utils/storage.ts | 39 ++++++++++++++++++- 9 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/messageHandlers/getStorageData.ts diff --git a/src/background/Background.ts b/src/background/Background.ts index b7ca80a58..019cd86cc 100644 --- a/src/background/Background.ts +++ b/src/background/Background.ts @@ -1,17 +1,22 @@ -import { Runtime, runtime, tabs } from 'webextension-polyfill' +import browser, { Runtime, runtime, tabs } from 'webextension-polyfill' import { PaymentFlowService } from '@/background/grantFlow' +import { defaultData } from '@/utils/storage' import getSendingPaymentPointerHandler from '../messageHandlers/getSendingPaymentPointerHandler' +import getStorageData from '../messageHandlers/getStorageData' import isMonetizationReadyHandler from '../messageHandlers/isMonetizationReadyHandler' import setIncomingPointerHandler from '../messageHandlers/setIncomingPointerHandler' import { tabChangeHandler, tabUpdateHandler } from './tabHandlers' +const storage = browser.storage.local + class Background { private messageHandlers: any = [ isMonetizationReadyHandler, setIncomingPointerHandler, getSendingPaymentPointerHandler, + getStorageData, ] private subscriptions: any = [] // TO DO: remove these from background into storage or state & use injection @@ -19,7 +24,12 @@ class Background { spentAmount: number = 0 paymentStarted = false - constructor() {} + constructor() { + storage + .set({ data: defaultData }) + .then(() => console.log('Default data stored successfully')) + .catch((error: any) => console.error('Error storing data:', error)) + } subscribeToMessages() { this.subscriptions = this.messageHandlers.map((handler: any) => { diff --git a/src/components/switch.tsx b/src/components/switch.tsx index 5771072c1..fdeef9c53 100644 --- a/src/components/switch.tsx +++ b/src/components/switch.tsx @@ -33,10 +33,11 @@ export interface SwitchProps extends VariantProps, React.HTMLAttributes { checked?: boolean + onChange?: (_event: React.ChangeEvent) => void } export const Switch = forwardRef(function Switch( - { size, className, ...props }, + { size, className, onChange = () => {}, ...props }, ref, ) { return ( @@ -45,6 +46,8 @@ export const Switch = forwardRef(function Switch( role="switch" ref={ref} type="checkbox" + checked={props.checked} + onChange={onChange} {...props} className="peer absolute opacity-0 -translate-x-[100%] pointer-events-none" /> diff --git a/src/messageHandlers/getStorageData.ts b/src/messageHandlers/getStorageData.ts new file mode 100644 index 000000000..1152a5195 --- /dev/null +++ b/src/messageHandlers/getStorageData.ts @@ -0,0 +1,13 @@ +import browser from 'webextension-polyfill' + +const storage = browser.storage.local + +const getStorageData = async () => { + const data = await storage.get('data') + return { + type: 'SUCCESS', + data, + } +} + +export default { callback: getStorageData, type: 'GET_STORAGE_DATA' } diff --git a/src/messageHandlers/isMonetizationReadyHandler.ts b/src/messageHandlers/isMonetizationReadyHandler.ts index 9bb2cfc5f..07081b777 100644 --- a/src/messageHandlers/isMonetizationReadyHandler.ts +++ b/src/messageHandlers/isMonetizationReadyHandler.ts @@ -4,10 +4,10 @@ export type IsMonetizationReadyData = { monetization: boolean } -const isMometizationReadyCallback = async (data: IsMonetizationReadyData) => { +const isMonetizationReadyCallback = async (data: IsMonetizationReadyData) => { await updateIcon(data.monetization) return true } -export default { callback: isMometizationReadyCallback, type: 'IS_MONETIZATION_READY' } +export default { callback: isMonetizationReadyCallback, type: 'IS_MONETIZATION_READY' } diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index 46a81f0e5..a9df48107 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -71,6 +71,7 @@ export const Home = () => { const getSendingPaymentPointer = async () => { const response = await sendMessage({ type: 'GET_SENDING_PAYMENT_POINTER' }) + console.log('getSendingPaymentPointer', response) setSendingPaymentPointer(response.data.sendingPaymentPointerUrl) const { sendingPaymentPointerUrl: paymentPointer, amount } = response.data diff --git a/src/providers/__tests__/popup-context.test.tsx b/src/providers/__tests__/popup-context.test.tsx index cb6996be3..63fbe7e57 100644 --- a/src/providers/__tests__/popup-context.test.tsx +++ b/src/providers/__tests__/popup-context.test.tsx @@ -3,6 +3,15 @@ import React, { useContext } from 'react' import { defaultData, PopupContext, PopupProvider } from '../popup.provider' +jest.mock('webextension-polyfill', () => ({ + runtime: { + onMessage: { + addListener: jest.fn(), + removeListener: jest.fn(), + }, + }, +})) + const TestComponent = () => { const { data, setData } = useContext(PopupContext) diff --git a/src/providers/popup.provider.tsx b/src/providers/popup.provider.tsx index db7634d2f..4f2e10b6d 100644 --- a/src/providers/popup.provider.tsx +++ b/src/providers/popup.provider.tsx @@ -1,4 +1,6 @@ -import React, { createContext, useState } from 'react' +import React, { createContext, useEffect, useState } from 'react' + +import { getStorageData } from '@/utils/storage' import { PopupContextValue, TPopupContext } from './providers.interface' @@ -29,6 +31,15 @@ export const PopupContext = createContext({ export const PopupProvider: React.FC = ({ children }) => { const [data, setData] = useState(defaultData) + useEffect(() => { + ;(async () => { + const storageData = await getStorageData() + setData(storageData as TPopupContext) + })() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return {children} } diff --git a/src/types/message.d.ts b/src/types/message.d.ts index 8f46fc779..1fe988d8f 100644 --- a/src/types/message.d.ts +++ b/src/types/message.d.ts @@ -11,6 +11,8 @@ declare type EXTMessageType = | 'PAYMENT_SUCCESS' | 'PAUSE_PAYMENTS' | 'LOAD' + | 'GET_STORAGE_DATA' + | 'SET_STORAGE_DATA' declare type EXTMessage = { type: EXTMessageType diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 24995aebf..8461674af 100755 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,3 +1,38 @@ -import { storage } from 'webextension-polyfill' +import { TPopupContext } from '@/providers/providers.interface' +import { sendMessage } from '@/utils/sendMessages' -export default storage.sync ? storage.sync : storage.local +export interface ExtensionStorageData { + amount: number + amountType: { + recurring: boolean + } + rateOfPay: number + wmEnabled: boolean + accessTokenQuote: string + accessTokenOutgoing: string + refreshToken: string + manageUrl: string +} + +export const defaultData: ExtensionStorageData = { + amount: 0, + amountType: { + recurring: true, + }, + rateOfPay: 0.36, + wmEnabled: true, + accessTokenQuote: '', + accessTokenOutgoing: '', + refreshToken: '', + manageUrl: '', +} + +export const getStorageData = async () => { + try { + const response = await sendMessage({ type: 'GET_STORAGE_DATA' }) + return response?.data as TPopupContext + } catch (error) { + console.error('Error fetching storage data:', error) + return null + } +}