From f831dea5d69b92fd7eaacbd073732778fcec788d Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 10 Jan 2024 20:30:12 +0100 Subject: [PATCH] Support both firefox and chrome --- package.json | 3 + pnpm-lock.yaml | 7 ++ src/background/messages/prepareStream.ts | 123 ++++++++++++++++------- src/hooks/useDomain.ts | 21 +--- src/hooks/useVersion.ts | 3 +- src/utils/tabs.ts | 30 ++++++ 6 files changed, 133 insertions(+), 54 deletions(-) create mode 100644 src/utils/tabs.ts diff --git a/package.json b/package.json index 7bc6d2e..70328e3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "scripts": { "dev": "plasmo dev", "build": "plasmo build", + "build:firefox": "plasmo build --target=firefox-mv2", "package": "plasmo package", + "package:firefox": "plasmo package --target=firefox-mv2", "lint": "eslint --ext .tsx,.ts src", "lint:fix": "eslint --fix --ext .tsx,.ts src", "lint:report": "eslint --ext .tsx,.ts --output-file eslint_report.json --format json src", @@ -22,6 +24,7 @@ }, "devDependencies": { "@types/chrome": "0.0.251", + "@types/firefox-webext-browser": "^120.0.0", "@types/node": "20.9.0", "@types/react": "18.2.37", "@types/react-dom": "18.2.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a83d3d..c508e4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ devDependencies: '@types/chrome': specifier: 0.0.251 version: 0.0.251 + '@types/firefox-webext-browser': + specifier: ^120.0.0 + version: 120.0.0 '@types/node': specifier: 20.9.0 version: 20.9.0 @@ -2767,6 +2770,10 @@ packages: resolution: {integrity: sha512-Kpi2GXQyYJdjL8mFclL1eDgihn1SIzorMZjD94kdPZh9E4VxGOeyjPxi5LpsM4Zku7P0reqegZTt2GxhmA9VBg==} dev: true + /@types/firefox-webext-browser@120.0.0: + resolution: {integrity: sha512-L+tDlwNeq0kQGfAYc2sNfKhRWJz9CNRvlbq9HnLibKUiJ3VTThG8sj7xrJF4CtKpEA9eBAr91Z2nnKIAy+xUJg==} + dev: true + /@types/har-format@1.2.15: resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} dev: true diff --git a/src/background/messages/prepareStream.ts b/src/background/messages/prepareStream.ts index f19978f..dbb995e 100644 --- a/src/background/messages/prepareStream.ts +++ b/src/background/messages/prepareStream.ts @@ -13,10 +13,11 @@ interface Request extends BaseRequest { const mapHeadersToDeclarativeNetRequestHeaders = ( headers: Record, -): chrome.declarativeNetRequest.ModifyHeaderInfo[] => { + op: string, +): { header: string; operation: any; value: string }[] => { return Object.entries(headers).map(([name, value]) => ({ header: name, - operation: chrome.declarativeNetRequest.HeaderOperation.SET, + operation: op, value, })); }; @@ -25,43 +26,91 @@ const handler: PlasmoMessaging.MessageHandler = async (re try { await assertDomainWhitelist(req.body.requestDomain); - await chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: [req.body.ruleId], - addRules: [ - { - id: req.body.ruleId, - condition: { - requestDomains: req.body.targetDomains, + if (chrome) { + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [req.body.ruleId], + addRules: [ + { + id: req.body.ruleId, + condition: { + requestDomains: req.body.targetDomains, + }, + action: { + type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, + ...(req.body.requestHeaders + ? { + requestHeaders: mapHeadersToDeclarativeNetRequestHeaders( + req.body.requestHeaders, + chrome.declarativeNetRequest.HeaderOperation.SET, + ), + } + : {}), + responseHeaders: [ + { + header: 'Access-Control-Allow-Origin', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: '*', + }, + { + header: 'Access-Control-Allow-Methods', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + }, + { + header: 'Access-Control-Allow-Headers', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: '*', + }, + ...mapHeadersToDeclarativeNetRequestHeaders( + req.body.responseHeaders ?? {}, + chrome.declarativeNetRequest.HeaderOperation.SET, + ), + ], + }, }, - action: { - type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, - ...(req.body.requestHeaders && { - requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(req.body.requestHeaders), - }), - responseHeaders: [ - { - header: 'Access-Control-Allow-Origin', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: '*', - }, - { - header: 'Access-Control-Allow-Methods', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - }, - { - header: 'Access-Control-Allow-Headers', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: '*', - }, - ...mapHeadersToDeclarativeNetRequestHeaders(req.body.responseHeaders ?? {}), - ], + ], + }); + if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message); + } else { + browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [req.body.ruleId], + addRules: [ + { + id: req.body.ruleId, + condition: { + requestDomains: req.body.targetDomains, + }, + action: { + type: 'modifyHeaders', + ...(req.body.requestHeaders + ? { + requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(req.body.requestHeaders, 'set'), + } + : {}), + responseHeaders: [ + { + header: 'Access-Control-Allow-Origin', + operation: 'set', + value: '*', + }, + { + header: 'Access-Control-Allow-Methods', + operation: 'set', + value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + }, + { + header: 'Access-Control-Allow-Headers', + operation: 'set', + value: '*', + }, + ...mapHeadersToDeclarativeNetRequestHeaders(req.body.responseHeaders ?? {}, 'set'), + ], + }, }, - }, - ], - }); - - if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message); + ], + }); + if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message); + } res.send({ success: true, diff --git a/src/hooks/useDomain.ts b/src/hooks/useDomain.ts index 69e4ca7..1f0ad57 100644 --- a/src/hooks/useDomain.ts +++ b/src/hooks/useDomain.ts @@ -1,28 +1,17 @@ import { useEffect, useState } from 'react'; import { makeUrlIntoDomain } from '~utils/domains'; - -function queryCurrentDomain(cb: (domain: string | null) => void) { - chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => { - const url = tabs[0]?.url; - if (!url) cb(null); - else cb(url); - }); -} +import { listenToTabChanges, queryCurrentDomain, stopListenToTabChanges } from '~utils/tabs'; export function useDomain(): null | string { const [domain, setDomain] = useState(null); useEffect(() => { - queryCurrentDomain(setDomain); - function listen() { - queryCurrentDomain(setDomain); - } - chrome.tabs.onActivated.addListener(listen); - chrome.tabs.onUpdated.addListener(listen); + const listen = () => queryCurrentDomain(setDomain); + listen(); + listenToTabChanges(listen); return () => { - chrome.tabs.onActivated.removeListener(listen); - chrome.tabs.onUpdated.removeListener(listen); + stopListenToTabChanges(listen); }; }, []); diff --git a/src/hooks/useVersion.ts b/src/hooks/useVersion.ts index d78e000..3db2c6c 100644 --- a/src/hooks/useVersion.ts +++ b/src/hooks/useVersion.ts @@ -1,6 +1,7 @@ export function getVersion(ops?: { prefixed?: boolean }) { const prefix = ops?.prefixed ? 'v' : ''; - return `${prefix}${chrome.runtime.getManifest().version}`; + const manifest = (chrome || browser).runtime.getManifest(); + return `${prefix}${manifest.version}`; } export function useVersion(ops?: { prefixed?: boolean }) { diff --git a/src/utils/tabs.ts b/src/utils/tabs.ts new file mode 100644 index 0000000..0e95cb4 --- /dev/null +++ b/src/utils/tabs.ts @@ -0,0 +1,30 @@ +export function queryCurrentDomain(cb: (domain: string | null) => void) { + const handle = (tabUrl: string | null) => { + if (!tabUrl) cb(null); + else cb(tabUrl); + }; + const ops = { active: true, currentWindow: true } as const; + + if (chrome) chrome.tabs.query(ops).then((tabs) => handle(tabs[0]?.url)); + else browser.tabs.query(ops).then((tabs) => handle(tabs[0]?.url)); +} + +export function listenToTabChanges(cb: () => void) { + if (chrome) { + chrome.tabs.onActivated.addListener(cb); + chrome.tabs.onUpdated.addListener(cb); + } else if (browser) { + browser.tabs.onActivated.addListener(cb); + browser.tabs.onUpdated.addListener(cb); + } +} + +export function stopListenToTabChanges(cb: () => void) { + if (chrome) { + chrome.tabs.onActivated.removeListener(cb); + chrome.tabs.onUpdated.removeListener(cb); + } else if (browser) { + browser.tabs.onActivated.removeListener(cb); + browser.tabs.onUpdated.removeListener(cb); + } +}