diff --git a/package.json b/package.json index 7cb1f3b6..7324df8f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test:ci": "pnpm test -- --reporters=default --reporters=github-actions" }, "dependencies": { + "awilix": "^10.0.1", "axios": "^1.5.1", "class-variance-authority": "^0.7.0", "clean-webpack-plugin": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 043d41b6..0fabcb87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + awilix: + specifier: ^10.0.1 + version: 10.0.1 axios: specifier: ^1.5.1 version: 1.6.2 @@ -2337,6 +2340,14 @@ packages: engines: {node: '>= 0.4'} dev: true + /awilix@10.0.1: + resolution: {integrity: sha512-+ylXK26rx4BVfzAB9AXQE+k/nomshpW8ISR+vZN2QecMiCoaT8km4DaIO0nazYSCX6nZCHYOFnoeCa5cE13NEA==} + engines: {node: '>=14.0.0'} + dependencies: + camel-case: 4.1.2 + fast-glob: 3.3.2 + dev: false + /aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} dev: false diff --git a/src/background/Background.ts b/src/background/Background.ts new file mode 100644 index 00000000..f13911f1 --- /dev/null +++ b/src/background/Background.ts @@ -0,0 +1,58 @@ +import { runtime, tabs } from 'webextension-polyfill' + +import { PaymentFlowService } from '@/background/grantFlow' + +import getSendingPaymentPointerHandler from '../messageHandlers/getSendingPaymentPointerHandler' +import isMonetizationReadyHandler from '../messageHandlers/isMonetizationReadyHandler' +import setIncomingPointerHandler from '../messageHandlers/setIncomingPointerHandler' +import { tabChangeHandler, tabUpdateHandler } from './tabHandlers' + +class Background { + private messageHandlers: any = [ + isMonetizationReadyHandler, + setIncomingPointerHandler, + getSendingPaymentPointerHandler, + ] + private subscriptions: any = [] + // TO DO: remove these from background into storage or state & use injection + grantFlow: PaymentFlowService | null = null + spentAmount: number = 0 + paymentStarted = false + + constructor() {} + + subscribeToMessages() { + this.subscriptions = this.messageHandlers.map((handler: any) => { + const listener: any = async (message: EXTMessage) => { + if (handler.type === message.type) { + try { + await handler.callback(message.data, this) + } catch (error) { + console.log('[===== Error in MessageListener =====]', error) + return error + } + } + } + + runtime.onMessage.addListener(listener) + + return () => { + runtime.onMessage.removeListener(listener) + } + }) + } + + subscribeToTabChanges() { + //Add Update listener for tab + tabs.onUpdated.addListener(tabUpdateHandler) + + //Add tab change listener + tabs.onActivated.addListener(tabChangeHandler) + } + + unsubscribeFromMessages() { + this.subscriptions.forEach((sub: any) => sub()) + } +} + +export default Background diff --git a/src/background/BackgroundContainer.ts b/src/background/BackgroundContainer.ts new file mode 100644 index 00000000..08d31d49 --- /dev/null +++ b/src/background/BackgroundContainer.ts @@ -0,0 +1,12 @@ +import { asClass, createContainer } from 'awilix' + +import Background from './Background' + +const BackgroundContainer = createContainer() + +BackgroundContainer.register({ + Background: asClass(Background).singleton(), + // TODO - add injectable services +}) + +export default BackgroundContainer diff --git a/src/background/index.ts b/src/background/index.ts index 7c65b53a..f3bb864a 100755 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,264 +1,14 @@ -import { action, Runtime, runtime, Tabs, tabs } from 'webextension-polyfill' +import BackgroundContainer from './BackgroundContainer' -import { PaymentFlowService } from '@/background/grantFlow' +const initialize = () => { + console.log('Start initialization') -const iconActive34 = runtime.getURL('assets/icons/icon-active-34.png') -const iconActive128 = runtime.getURL('assets/icons/icon-active-128.png') -const iconInactive34 = runtime.getURL('assets/icons/icon-inactive-34.png') -const iconInactive128 = runtime.getURL('assets/icons/icon-inactive-128.png') + const background = BackgroundContainer.resolve('Background') -// const SENDING_PAYMENT_POINTER_URL = 'https://ilp.rafiki.money/wmuser' // cel din extensie al userului -// const RECEIVING_PAYMENT_POINTER_URL = 'https://ilp.rafiki.money/web-page' // cel din dom + background.subscribeToMessages() + background.subscribeToTabChanges() -/** - * Define background script functions - * @type {class} - */ -class Background { - _port: number - grantFlow: PaymentFlowService | null = null - spentAmount: number = 0 - paymentStarted = false - - constructor() { - this.init() - } - - /** - * Document Ready - * - * @returns {void} - */ - init = async () => { - console.log('[===== Loaded Background Scripts =====]') - - //When extension installed - runtime.onInstalled.addListener(this.onInstalled) - - //Add message listener in Browser. - runtime.onMessage.addListener(this.onMessage) - - //Add Update listener for tab - tabs.onUpdated.addListener(this.onUpdatedTab) - - //Add New tab create listener - tabs.onCreated.addListener(this.onCreatedTab) - - //Add tab change listener - tabs.onActivated.addListener(this.handleTabChange) - } - - //TODO: Listeners - - /** - * Extension Installed - */ - onInstalled = () => { - console.log('[===== Installed Extension!] =====') - } - - /** - * Message Handler Function - * - * @param message - * @param sender - * @returns - */ - onMessage = async (message: EXTMessage, sender: Runtime.MessageSender) => { - try { - console.log('[===== Received message =====]', message, sender) - switch (message.type) { - case 'IS_MONETIZATION_READY': { - if (message?.data) { - this.updateIcon(message.data.monetization) - } - break - } - - case 'SET_INCOMING_POINTER': { - const { - incomingPayment: receivingPaymentPointerUrl, - paymentPointer: sendingPaymentPointerUrl, - amount, - } = message.data - - if (this.grantFlow?.sendingPaymentPointerUrl === sendingPaymentPointerUrl) { - if (!this.paymentStarted) { - this.paymentStarted = true - const currentTabId = await this.grantFlow?.getCurrentActiveTabId() - await tabs.sendMessage(currentTabId ?? 0, { type: 'START_PAYMENTS' }) - } - } else { - this.grantFlow = new PaymentFlowService( - sendingPaymentPointerUrl, - receivingPaymentPointerUrl, - amount, - ) - - this.grantFlow.initPaymentFlow() - } - - break - } - - case 'GET_SENDING_PAYMENT_POINTER': { - if (this.grantFlow) { - const { sendingPaymentPointerUrl, amount } = this.grantFlow - return { - type: 'SUCCESS', - data: { sendingPaymentPointerUrl, amount, started: this.paymentStarted }, - } - } - - return { - type: 'ERROR', - data: { sendingPaymentPointerUrl: '' }, - } - } - - case 'RUN_PAYMENT': { - if (this.grantFlow) { - this.grantFlow.sendPayment() - this.spentAmount = Number( - parseFloat(String(this.spentAmount + 1000000 / 10 ** 9)).toFixed(3), - ) - this.sendSpendAmount() - this.paymentStarted = true - } - break - } - - case 'PAUSE_PAYMENTS': { - this.paymentStarted = false - break - } - } - - return true // result to reply - } catch (error) { - console.log('[===== Error in MessageListener =====]', error) - return error - } - } - - /** - * Message from Long Live Connection - * - * @param msg - */ - onMessageFromExtension = (msg: EXTMessage) => { - console.log('[===== Message from Long Live Connection =====]', msg) - } - - /** - * - * @param tab - */ - onCreatedTab = (tab: Tabs.Tab) => { - console.log('[===== New Tab Created =====]', tab) - } - - sendSpendAmount() { - runtime.sendMessage({ - type: 'SPENT_AMOUNT', - data: { spentAmount: this.spentAmount }, - }) - } - - /** - * When changes tabs - * - * @param {*} tabId - * @param {*} changeInfo - * @param {*} tab - */ - onUpdatedTab = async (tabId: number, changeInfo: Tabs.OnUpdatedChangeInfoType, tab: Tabs.Tab) => { - if (tab.status === 'complete' && tab.url?.match(/^http/)) { - const response = await this.sendMessage(tab, { type: 'IS_MONETIZATION_READY' }) - if (response.data) { - await this.updateIcon(response.data.monetization) - } - } - } - - /** - * Get url from tabId - * - */ - getURLFromTab = async (tabId: number) => { - try { - const tab = await tabs.get(tabId) - return tab.url || '' - } catch (error) { - console.log(`[===== Could not get Tab Info$(tabId) in getURLFromTab =====]`, error) - throw '' - } - } - - /** - * Open new tab by url - * - */ - openNewTab = async (url: string) => { - try { - const tab = await tabs.create({ url }) - return tab - } catch (error) { - console.log(`[===== Error in openNewTab =====]`, error) - return null - } - } - - /** - * Close specific tab - * - * @param {number} tab - */ - closeTab = async (tab: Tabs.Tab) => { - try { - await tabs.remove(tab.id ?? 0) - } catch (error) { - console.log(`[===== Error in closeTab =====]`, error) - } - } - - /** - * send message - */ - sendMessage = async (tab: Tabs.Tab, msg: EXTMessage) => { - try { - const res = await tabs.sendMessage(tab.id ?? 0, msg) - return res - } catch (error) { - console.log(`[===== Error in sendMessage =====]`, error) - return null - } - } - - updateIcon = async (active: boolean) => { - const iconData = { - '34': active ? iconActive34 : iconInactive34, - '128': active ? iconActive128 : iconInactive128, - } - - if (action) { - await action.setIcon({ path: iconData }) - } else if (chrome.browserAction) { - chrome.browserAction.setIcon({ path: iconData }) - } - } - - handleTabChange = async (activeInfo: chrome.tabs.TabActiveInfo) => { - const tabId = activeInfo.tabId - - const tab = await tabs.get(tabId) - if (tab && tab.url?.includes('https') && tab.status === 'complete') { - const response = await this.sendMessage(tab, { type: 'IS_MONETIZATION_READY' }) - if (response?.data) { - this.updateIcon(response.data.monetization) - } - } - } + console.log('End initialization') } -export const background = new Background() +initialize() diff --git a/src/background/tabHandlers.ts b/src/background/tabHandlers.ts new file mode 100644 index 00000000..2e84a15a --- /dev/null +++ b/src/background/tabHandlers.ts @@ -0,0 +1,38 @@ +import { Tabs, tabs } from 'webextension-polyfill' + +import { sendMessageToTab } from '@/utils/sendMessages' + +import { updateIcon } from './utils' + +export const tabChangeHandler = async (activeInfo: chrome.tabs.TabActiveInfo) => { + const tabId = activeInfo.tabId + const tab = await tabs.get(tabId) + + if (tab && tab.url?.includes('https') && tab.status === 'complete') { + try { + const response = await sendMessageToTab(tab, { type: 'IS_MONETIZATION_READY' }) + if (response?.data) { + updateIcon(response.data.monetization) + } + } catch (error) { + console.log(`[===== Error in tabChangeHandler =====]`, error) + } + } +} + +export const tabUpdateHandler = async ( + tabId: number, + changeInfo: Tabs.OnUpdatedChangeInfoType, + tab: Tabs.Tab, +) => { + if (tab.status === 'complete' && tab.url?.match(/^http/)) { + try { + const response = await sendMessageToTab(tab, { type: 'IS_MONETIZATION_READY' }) + if (response?.data) { + updateIcon(response.data.monetization) + } + } catch (error) { + console.log(`[===== Error in tabUpdateHandler =====]`, error) + } + } +} diff --git a/src/background/utils.ts b/src/background/utils.ts new file mode 100644 index 00000000..92173ac3 --- /dev/null +++ b/src/background/utils.ts @@ -0,0 +1,19 @@ +import { action, runtime } from 'webextension-polyfill' + +const iconActive34 = runtime.getURL('assets/icons/icon-active-34.png') +const iconActive128 = runtime.getURL('assets/icons/icon-active-128.png') +const iconInactive34 = runtime.getURL('assets/icons/icon-inactive-34.png') +const iconInactive128 = runtime.getURL('assets/icons/icon-inactive-128.png') + +export const updateIcon = async (active: boolean) => { + const iconData = { + '34': active ? iconActive34 : iconInactive34, + '128': active ? iconActive128 : iconInactive128, + } + + if (action) { + await action.setIcon({ path: iconData }) + } else if (chrome.browserAction) { + chrome.browserAction.setIcon({ path: iconData }) + } +} diff --git a/src/messageHandlers/getSendingPaymentPointerHandler.ts b/src/messageHandlers/getSendingPaymentPointerHandler.ts new file mode 100644 index 00000000..b6f88649 --- /dev/null +++ b/src/messageHandlers/getSendingPaymentPointerHandler.ts @@ -0,0 +1,18 @@ +import Background from '@/background/Background' + +const getSendingPaymentPointerCallback = async (data: undefined, background: Background) => { + if (background.grantFlow) { + const { sendingPaymentPointerUrl, amount } = background.grantFlow + return { + type: 'SUCCESS', + data: { sendingPaymentPointerUrl, amount, started: background.paymentStarted }, + } + } + + return { + type: 'ERROR', + data: { sendingPaymentPointerUrl: '' }, + } +} + +export default { callback: getSendingPaymentPointerCallback, type: 'GET_SENDING_PAYMENT_POINTER' } diff --git a/src/messageHandlers/isMonetizationReadyHandler.ts b/src/messageHandlers/isMonetizationReadyHandler.ts new file mode 100644 index 00000000..9bb2cfc5 --- /dev/null +++ b/src/messageHandlers/isMonetizationReadyHandler.ts @@ -0,0 +1,13 @@ +import { updateIcon } from '@/background/utils' + +export type IsMonetizationReadyData = { + monetization: boolean +} + +const isMometizationReadyCallback = async (data: IsMonetizationReadyData) => { + await updateIcon(data.monetization) + + return true +} + +export default { callback: isMometizationReadyCallback, type: 'IS_MONETIZATION_READY' } diff --git a/src/messageHandlers/pausePaymentHandler.ts b/src/messageHandlers/pausePaymentHandler.ts new file mode 100644 index 00000000..80688c96 --- /dev/null +++ b/src/messageHandlers/pausePaymentHandler.ts @@ -0,0 +1,9 @@ +import Background from '@/background/Background' + +const pausePaymentCallback = async (data: undefined, background: Background) => { + background.paymentStarted = false + + return true +} + +export default { callback: pausePaymentCallback, type: 'PAUSE_PAYMENT' } diff --git a/src/messageHandlers/runPaymentHandler.ts b/src/messageHandlers/runPaymentHandler.ts new file mode 100644 index 00000000..19e3110d --- /dev/null +++ b/src/messageHandlers/runPaymentHandler.ts @@ -0,0 +1,21 @@ +import { runtime } from 'webextension-polyfill' + +import Background from '@/background/Background' + +const runPaymentCallback = async (data: undefined, background: Background) => { + if (background.grantFlow) { + background.grantFlow.sendPayment() + background.spentAmount = Number( + parseFloat(String(background.spentAmount + 1000000 / 10 ** 9)).toFixed(3), + ) + runtime.sendMessage({ + type: 'SPENT_AMOUNT', + data: { spentAmount: background.spentAmount }, + }) + background.paymentStarted = true + } + + return true +} + +export default { callback: runPaymentCallback, type: 'RUN_PAYMENT' } diff --git a/src/messageHandlers/setIncomingPointerHandler.ts b/src/messageHandlers/setIncomingPointerHandler.ts new file mode 100644 index 00000000..ca0b23bf --- /dev/null +++ b/src/messageHandlers/setIncomingPointerHandler.ts @@ -0,0 +1,38 @@ +import { tabs } from 'webextension-polyfill' + +import Background from '@/background/Background' +import { PaymentFlowService } from '@/background/grantFlow' + +export type SetIncomingPointerData = { + incomingPayment: string + paymentPointer: string + amount: string +} + +const setIncomingPointerCallback = async (data: SetIncomingPointerData, background: Background) => { + const { + incomingPayment: receivingPaymentPointerUrl, + paymentPointer: sendingPaymentPointerUrl, + amount, + } = data + + if (background.grantFlow?.sendingPaymentPointerUrl === sendingPaymentPointerUrl) { + if (!background.paymentStarted) { + background.paymentStarted = true + const currentTabId = await background.grantFlow?.getCurrentActiveTabId() + await tabs.sendMessage(currentTabId ?? 0, { type: 'START_PAYMENTS' }) + } + } else { + background.grantFlow = new PaymentFlowService( + sendingPaymentPointerUrl, + receivingPaymentPointerUrl, + amount, + ) + + background.grantFlow.initPaymentFlow() + } + + return true +} + +export default { callback: setIncomingPointerCallback, type: 'SET_INCOMING_POINTER' }