diff --git a/src/background/background.js b/src/background/background.js index a30a646..b33f50f 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,8 +1,13 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 import { Manager } from "lib-iitc-manager"; import browser from "webextension-polyfill"; +import { IS_SCRIPTING_API, IS_USERSCRIPTS_API } from "@/userscripts/env"; import { _ } from "@/i18n"; -import { inject_plugin } from "./injector"; +import { + inject_gm_api, + inject_plugin, + is_userscripts_api_available, +} from "@/userscripts/wrapper"; import { onUpdatedListener, onRemovedListener, @@ -11,6 +16,7 @@ import { } from "./intel"; import "./requests"; import { strToBase64 } from "@/strToBase64"; +import { isIITCEnabled } from "@/userscripts/utils"; const manager = new Manager({ storage: browser.storage.local, @@ -37,17 +43,30 @@ const manager = new Manager({ } catch { // If popup is closed, message goes nowhere and an error occurs. Ignore. } + if (IS_USERSCRIPTS_API) { + isIITCEnabled().then((status) => { + if (status) { + init_userscripts_api(); + manager.inject().then(); + } + }); + } }, - inject_plugin: (plugin) => inject_plugin(plugin).then(), + inject_plugin: async (plugin) => { + await inject_plugin(plugin); + }, + is_daemon: IS_USERSCRIPTS_API, }); manager.run().then(); -const { onUpdated, onRemoved } = browser.tabs; -onUpdated.addListener((tabId, status, tab) => - onUpdatedListener(tabId, status, tab, manager) -); -onRemoved.addListener(onRemovedListener); +if (IS_SCRIPTING_API) { + const { onUpdated, onRemoved } = browser.tabs; + onUpdated.addListener((tabId, status, tab) => + onUpdatedListener(tabId, status, tab, manager) + ); + onRemoved.addListener(onRemovedListener); +} browser.runtime.onMessage.addListener(async (request) => { switch (request.type) { @@ -55,6 +74,9 @@ browser.runtime.onMessage.addListener(async (request) => { await onRequestOpenIntel(); break; case "toggleIITC": + if (IS_USERSCRIPTS_API) { + await manage_user_scripts_status(request.value); + } await onToggleIITC(request.value); break; case "xmlHttpRequestHandler": @@ -148,38 +170,84 @@ async function xmlHttpRequestHandler(data) { response: JSON.stringify(response), }); - const injectedCode = ` + const bridge_data = strToBase64(String(detail_stringify)); + if (IS_USERSCRIPTS_API) { + let allTabs = await browser.tabs.query({ status: "complete" }); + + allTabs = allTabs.filter(function (tab) { + return tab.status === "complete" && tab.url; + }); + + for (const tab of allTabs) { + await browser.tabs.sendMessage(tab.id, { + type: "xmlHttpRequestToCS", + value: bridge_data, + }); + } + } else { + const injectedCode = ` document.dispatchEvent(new CustomEvent('bridgeResponse', { - detail: "${strToBase64(String(detail_stringify))}" + detail: "${bridge_data}" })); `; - try { - await browser.tabs.executeScript(data.tab_id, { - code: injectedCode, - }); - } catch (error) { - console.error(`An error occurred while execute script: ${error.message}`); + try { + await browser.tabs.executeScript(data.tab_id, { + code: injectedCode, + }); + } catch (error) { + console.error( + `An error occurred while execute script: ${error.message}` + ); + } } } - const req = new XMLHttpRequest(); - req.onload = function () { - const response = { - readyState: this.readyState, - responseHeaders: this.responseHeaders, - responseText: this.responseText, - status: this.status, - statusText: this.statusText, + try { + const response = await fetch(data.url, { + method: data.method, + headers: data.headers, + body: data.method !== "GET" ? data.data : undefined, + credentials: data.user && data.password ? "include" : "same-origin", + }); + + const text = await response.text(); + + // Create a response object similar to the one in XMLHttpRequest + const responseObject = { + readyState: 4, + responseHeaders: "Not directly accessible with fetch", + responseText: text, + status: response.status, + statusText: response.statusText, }; - xmlResponse(data.tab_id, data.onload, response); - }; - req.open(data.method, data.url, true, data.user, data.password); - for (let [header_name, header_value] of Object.entries(data.headers)) { - req.setRequestHeader(header_name, header_value); + + await xmlResponse(data.tab_id, data.onload, responseObject); + } catch (error) { + console.error("Fetch error:", error); } +} + +const init_userscripts_api = () => { + if (!is_userscripts_api_available) return; + chrome.userScripts.configureWorld({ + csp: "script-src 'self' 'unsafe-inline'", + messaging: true, + }); + inject_gm_api(); +}; - req.send(data.data); +async function manage_user_scripts_status(status) { + if (status === true) { + init_userscripts_api(); + manager.inject().then(); + } else { + try { + await chrome.userScripts.unregister(); + } catch (e) { + console.log(e); + } + } } self.addEventListener("install", () => { diff --git a/src/background/injector.js b/src/background/injector.js index 5eb4e1b..efd71dd 100644 --- a/src/background/injector.js +++ b/src/background/injector.js @@ -1,40 +1,20 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 import browser from "webextension-polyfill"; -import { check_matching } from "lib-iitc-manager"; +import { + gm_api_for_plugin, + is_userscripts_api_available, +} from "@/userscripts/wrapper"; +import { getNiaTabsToInject, getPluginMatches } from "@/background/utils"; // TODO // https://developer.chrome.com/docs/extensions/reference/api/userScripts#developer_mode_for_extension_users -export async function inject_plugin(plugin) { - const tabs = await getTabsToInject(); - - const is_ingress_tab = (url) => { - return /https:\/\/(intel|missions).ingress.com\/*/.test(url); - }; - - chrome.userScripts.configureWorld({ - csp: "script-src 'self' 'unsafe-inline'", - }); - - try { - chrome.userScripts.unregister(["iitc"]); - } catch (error) { - console.log(error); - } - chrome.userScripts.register([ - { - id: "iitc", - matches: ["https://intel.ingress.com/*"], - js: [{ code: plugin.code }], - }, - ]); +export async function inject_plugin_via_content_scripts(plugin, use_gm_api) { + const tabs = await getNiaTabsToInject(plugin); for (let tab of Object.values(tabs)) { - if ( - (!is_ingress_tab(tab.url) || !check_matching(plugin, "")) && - !check_matching(plugin, tab.url) - ) { - continue; + if (use_gm_api) { + plugin.code = await gm_api_for_plugin(plugin, tab.id); } try { @@ -58,11 +38,39 @@ export async function inject_plugin(plugin) { } } -// Fetch all completly loaded Ingress Intel tabs -export async function getTabsToInject() { - let allTabs = await browser.tabs.query({ status: "complete" }); +export async function inject_plugin_via_userscripts_api(plugin, use_gm_api) { + if (!is_userscripts_api_available) return; + + if (use_gm_api) { + plugin.code = await gm_api_for_plugin(plugin, 0); + } - return allTabs.filter(function (tab) { - return tab.status === "complete" && tab.url; - }); + let scripts = []; + try { + scripts = await chrome.userScripts.getScripts(); + } catch (e) { + console.log(e); + return; + } + const plugin_obj = [ + { + id: plugin.uid, + matches: + plugin.uid === "gm_api" ? ["https://*/*"] : getPluginMatches(plugin), + js: [{ code: plugin.code }], + runAt: plugin.uid === "gm_api" ? "document_start" : "document_end", + world: "MAIN", + }, + ]; + + const is_exist = scripts.some((script) => script.id === plugin.uid); + if (!is_exist) { + await chrome.userScripts.register(plugin_obj); + return; + } + + const exist_script = scripts.find((script) => script.id === plugin.uid); + if (exist_script.js[0].code !== plugin_obj[0].js[0].code) { + await chrome.userScripts.update(plugin_obj); + } } diff --git a/src/background/intel.js b/src/background/intel.js index 49a6fd9..92a7ef0 100644 --- a/src/background/intel.js +++ b/src/background/intel.js @@ -1,6 +1,7 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 import browser from "webextension-polyfill"; -import { getTabsToInject } from "./injector"; +import { getTabsToInject } from "@/background/utils"; +import { isIITCEnabled } from "@/userscripts/utils"; let lastIITCTab = null; @@ -53,10 +54,8 @@ export function onRemovedListener(tabId) { } async function initialize(manager) { - const storage = await browser.storage.local.get(["IITC_is_enabled"]); - const status = storage["IITC_is_enabled"]; - - if (status !== false) { + const status = await isIITCEnabled(); + if (status) { await manager.inject(); } } diff --git a/src/background/requests.js b/src/background/requests.js index d2ccb64..777ce26 100644 --- a/src/background/requests.js +++ b/src/background/requests.js @@ -2,6 +2,7 @@ import browser from "webextension-polyfill"; import { parseMeta, ajaxGet, getUniqId } from "lib-iitc-manager"; +import { isIITCEnabled } from "@/userscripts/utils"; const IS_CHROME = !!global.chrome.app; const whitelist = [ @@ -65,10 +66,8 @@ async function bypass(tabId, url) { * @return {Promise} */ async function maybeInstallUserJs(tabId, url) { - const IITC_is_enabled = await browser.storage.local - .get(["IITC_is_enabled"]) - .then((data) => data.IITC_is_enabled); - if (IITC_is_enabled === false) { + const status = await isIITCEnabled(); + if (status === false) { await bypass(tabId, url); return; } diff --git a/src/background/utils.js b/src/background/utils.js new file mode 100644 index 0000000..36b6478 --- /dev/null +++ b/src/background/utils.js @@ -0,0 +1,38 @@ +//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 + +import browser from "webextension-polyfill"; +import { check_matching } from "lib-iitc-manager"; + +const is_ingress_tab = (url) => { + return /https:\/\/(intel|missions).ingress.com\/*/.test(url); +}; + +// Fetch all completly loaded tabs +export async function getTabsToInject() { + let allTabs = await browser.tabs.query({ status: "complete" }); + return allTabs.filter(function (tab) { + return tab.status === "complete" && tab.url; + }); +} + +// Filter all completly loaded Ingress Intel tabs +export async function getNiaTabsToInject(plugin) { + const tabs = await getTabsToInject(); + return Object.values(tabs).filter( + (tab) => + (is_ingress_tab(tab.url) && check_matching(plugin, "")) || + check_matching(plugin, tab.url) + ); +} + +export function getPluginMatches(plugin) { + let matches = []; + if (check_matching(plugin, "")) { + matches.push("https://intel.ingress.com/*"); + matches.push("https://missions.ingress.com/*"); + } + if (plugin.match) { + matches = matches.concat(plugin.match); + } + return matches; +} diff --git a/src/content-scripts/bridge.js b/src/content-scripts/bridge.js index 8d2e5dc..3fd85c7 100644 --- a/src/content-scripts/bridge.js +++ b/src/content-scripts/bridge.js @@ -1,6 +1,7 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 import browser from "webextension-polyfill"; +import { IS_USERSCRIPTS_API } from "@/userscripts/env"; import { inject } from "@/content-scripts/utils"; import { strToBase64 } from "@/strToBase64"; @@ -34,6 +35,14 @@ const xmlResponseBridge = async (data) => { .then(); }; +browser.runtime.onMessage.addListener(async (request) => { + switch (request.type) { + case "xmlHttpRequestToCS": + bridgeResponse(request.value); + break; + } +}); + // Sends the entire plugins scoped storage to the page context const getStorageBridge = async (req) => { const all_storage = await browser.storage.local.get(null); @@ -48,12 +57,8 @@ const getStorageBridge = async (req) => { response: JSON.stringify(plugins_storage), }); - const injectedCode = ` - document.dispatchEvent(new CustomEvent('bridgeResponse', { - detail: "${strToBase64(String(detail_stringify))}" - })); - `; - inject(injectedCode); + const bridge_base64_data = strToBase64(String(detail_stringify)); + bridgeResponse(bridge_base64_data); }; // Saves the value in the persistent storage in order to synchronize the data with the storage in the page context @@ -67,3 +72,20 @@ const setValueBridge = async (req) => { const delValueBridge = async (req) => { await browser.storage.local.remove(req.key); }; + +const bridgeResponse = (bridge_base64_data) => { + if (IS_USERSCRIPTS_API) { + dispatchEvent( + new CustomEvent("bridgeResponse", { + detail: bridge_base64_data, + }) + ); + } else { + const injectedCode = ` + document.dispatchEvent(new CustomEvent('bridgeResponse', { + detail: "${bridge_base64_data}" + })); + `; + inject(injectedCode); + } +}; diff --git a/src/content-scripts/loader.js b/src/content-scripts/loader.js index 67c75f4..bb7b63a 100644 --- a/src/content-scripts/loader.js +++ b/src/content-scripts/loader.js @@ -1,29 +1,21 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 -import browser from "webextension-polyfill"; -import { GM } from "./gm-api"; -import { inject, IITCButtonInitJS } from "./utils"; +import { IITCButtonInitJS } from "./utils"; import { bridgeAction } from "@/content-scripts/bridge"; +import { inject_gm_api } from "@/userscripts/wrapper"; +import { IS_USERSCRIPTS_API } from "@/userscripts/env"; +import { isIITCEnabled } from "@/userscripts/utils"; function preparePage() { - document.addEventListener("DOMContentLoaded", function () { - if (window.location.hostname === "intel.ingress.com") { - window.onload = function () {}; - document.body.onload = function () {}; - } - }); - - inject( - `((${GM.toString()}))()\n//# sourceURL=${browser.runtime.getURL( - "js/GM_api.js" - )}` - ); document.addEventListener("bridgeRequest", bridgeAction); + if (IS_USERSCRIPTS_API) return; + + inject_gm_api(); document.addEventListener("IITCButtonInitJS", IITCButtonInitJS); } -browser.storage.local.get(["IITC_is_enabled"]).then((data) => { - if (data["IITC_is_enabled"] !== false) { +isIITCEnabled().then((status) => { + if (status) { preparePage(); } }); diff --git a/src/content-scripts/utils.js b/src/content-scripts/utils.js index e04023a..ec2729f 100644 --- a/src/content-scripts/utils.js +++ b/src/content-scripts/utils.js @@ -1,61 +1,31 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 -import browser from "webextension-polyfill"; import { getUID } from "lib-iitc-manager"; -import { strToBase64 } from "@/strToBase64"; +import { gm_api_for_plugin } from "@/userscripts/wrapper"; const LOADED_PLUGINS = []; export function inject(code) { - // const script = document.createElement("script"); - // script.appendChild(document.createTextNode(code)); - // (document.body || document.head || document.documentElement).appendChild( - // script - // ); - // script.parentElement.removeChild(script); - console.log(code[0]); -} - -function getPluginHash(uid) { - return "VMin" + strToBase64(uid); + const script = document.createElement("script"); + script.appendChild(document.createTextNode(code)); + (document.body || document.head || document.documentElement).appendChild( + script + ); + script.parentElement.removeChild(script); } export async function IITCButtonInitJS(e) { const tab_id = e.detail.tab_id; const plugin = e.detail.plugin; - const meta = { ...plugin }; - delete meta.code; - const uid = plugin.uid ? plugin.uid : getUID(plugin); - let data_key = getPluginHash(uid); if (LOADED_PLUGINS.includes(uid)) { console.debug(`Plugin ${uid} is already loaded. Skip`); } else { LOADED_PLUGINS.push(uid); console.debug(`Plugin ${uid} loaded`); - const name = encodeURIComponent(plugin.name); - const injectedCode = [ - "((GM)=>{", - // an implementation of GM API v3 based on GM API v4 - "const GM_info = GM.info; const unsafeWindow = window;", - "const exportFunction = GM.exportFunction; const createObjectIn = GM.createObjectIn; const cloneInto = GM.cloneInto;", - "const GM_getValue = (key, value) => GM._getValueSync(key, value);", - "const GM_setValue = (key, value) => GM._setValueSync(key, value);", - "const GM_xmlhttpRequest = (details) => GM.xmlHttpRequest(details);", - - plugin.code, - // adding a new line in case the code ends with a line comment - plugin.code.endsWith("\n") ? "" : "\n", - `})(GM("${data_key}", ${tab_id}, ${JSON.stringify(meta)}))`, - - // Firefox lists .user.js among our own content scripts so a space at start will group them - `\n//# sourceURL=${browser.runtime.getURL( - "plugins/%20" + name + ".user.js" - )}`, - ].join(""); - - inject(injectedCode); + const code = gm_api_for_plugin(plugin, tab_id); + inject(code); } } diff --git a/src/manifest.json b/src/manifest.json index 9400ae0..14d866b 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "name": "IITC Button", "description": "__MSG_extDescription__", - "minimum_chrome_version": "120", + "minimum_chrome_version": "61", "browser_specific_settings": { "gecko": { "id": "iitc@modos189.ru", @@ -14,31 +14,11 @@ "permissions": [ "tabs", "storage", - "scripting", - "webRequest", - "userScripts", "unlimitedStorage" ], - "host_permissions": [ - "https://intel.ingress.com/*" - ], - "optional_host_permissions":[ - "https://*/*", - "http://*/*" - ], - "default_locale": "en", - "background": { - "service_worker": "js/background.js" - }, - "content_scripts": [ - { - "matches" : [""], - "run_at": "document_start", - "js": ["js/content-script.js"] - } - ], - "action": { + "background": {}, + "browser_action": { "default_popup": "popup.html", "default_title": "__MSG_titleDefault__", "default_icon": { @@ -49,9 +29,5 @@ "icons": { "48": "assets/icons/48/icon.png", "128": "assets/icons/128/icon.png" - }, - "content_security_policy": { - "extension_pages": "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws://localhost:9090 http://localhost:8000 https://github.com https://iitc.app; img-src 'self' https://iitc.app" - }, - "manifest_version": 3 + } } diff --git a/src/userscripts/env.js b/src/userscripts/env.js new file mode 100644 index 0000000..473347a --- /dev/null +++ b/src/userscripts/env.js @@ -0,0 +1,9 @@ +//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 + +import browser from "webextension-polyfill"; + +export const IS_CHROME = !!browser.runtime.OnInstalledReason.CHROME_UPDATE; +export const MANIFEST = browser.runtime.getManifest(); + +export const IS_USERSCRIPTS_API = IS_CHROME && MANIFEST.manifest_version === 3; +export const IS_SCRIPTING_API = !IS_USERSCRIPTS_API; diff --git a/src/content-scripts/gm-api.js b/src/userscripts/gm-api.js similarity index 95% rename from src/content-scripts/gm-api.js rename to src/userscripts/gm-api.js index 8b09d43..ab03f57 100644 --- a/src/content-scripts/gm-api.js +++ b/src/userscripts/gm-api.js @@ -1,5 +1,12 @@ //@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 export const GM = function () { + document.addEventListener("DOMContentLoaded", function () { + if (window.location.hostname === "intel.ingress.com") { + window.onload = function () {}; + document.body.onload = function () {}; + } + }); + const cache = {}; const defineProperty = Object.defineProperty; @@ -178,7 +185,7 @@ export const GM = function () { cloneInto: makeFunc((obj) => obj), }; }; - document.addEventListener("bridgeResponse", function (e) { + addEventListener("bridgeResponse", function (e) { const detail = JSON.parse(base64ToStr(e.detail)); const uuid = detail.task_uuid; diff --git a/src/userscripts/utils.js b/src/userscripts/utils.js new file mode 100644 index 0000000..e88bcfe --- /dev/null +++ b/src/userscripts/utils.js @@ -0,0 +1,10 @@ +//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 + +import browser from "webextension-polyfill"; + +export async function isIITCEnabled() { + const status = await browser.storage.local + .get(["IITC_is_enabled"]) + .then((data) => data["IITC_is_enabled"]); + return status !== false; +} diff --git a/src/userscripts/wrapper.js b/src/userscripts/wrapper.js new file mode 100644 index 0000000..483ba99 --- /dev/null +++ b/src/userscripts/wrapper.js @@ -0,0 +1,87 @@ +//@license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3 + +import browser from "webextension-polyfill"; +import { IS_USERSCRIPTS_API } from "@/userscripts/env"; +import { + inject_plugin_via_content_scripts, + inject_plugin_via_userscripts_api, +} from "@/background/injector"; +import { strToBase64 } from "@/strToBase64"; +import { getUID } from "lib-iitc-manager"; +import { inject } from "@/content-scripts/utils"; +import { GM } from "@/userscripts/gm-api"; +import { isIITCEnabled } from "@/userscripts/utils"; + +function getPluginHash(uid) { + return "VMin" + strToBase64(uid); +} + +export async function inject_plugin(plugin, use_gm_api) { + if (use_gm_api === undefined) use_gm_api = true; + + const iitc_status = await isIITCEnabled(); + if (iitc_status === false) return; + + if (IS_USERSCRIPTS_API) { + console.log("INJECT LIKE IS CHROME MV3"); + await inject_plugin_via_userscripts_api(plugin, use_gm_api); + } else { + console.log("INJECT LIKE OTHER BROWSER"); + await inject_plugin_via_content_scripts(plugin, use_gm_api); + } +} + +export function inject_gm_api() { + const plugin = { + uid: "gm_api", + code: `((${GM.toString()}))()\n//# sourceURL=${browser.runtime.getURL( + "js/GM_api.js" + )}`, + }; + + if (IS_USERSCRIPTS_API) { + inject_plugin_via_userscripts_api(plugin, false).then(); + } else { + inject(plugin.code); + } +} + +export async function gm_api_for_plugin(plugin, tab_id) { + const uid = plugin.uid ? plugin.uid : getUID(plugin); + let data_key = getPluginHash(uid); + const name = encodeURIComponent(plugin.name); + + const meta = { ...plugin }; + delete meta.code; + + return [ + "((GM)=>{", + // an implementation of GM API v3 based on GM API v4 + "const GM_info = GM.info; const unsafeWindow = window;", + "const exportFunction = GM.exportFunction; const createObjectIn = GM.createObjectIn; const cloneInto = GM.cloneInto;", + "const GM_getValue = (key, value) => GM._getValueSync(key, value);", + "const GM_setValue = (key, value) => GM._setValueSync(key, value);", + "const GM_xmlhttpRequest = (details) => GM.xmlHttpRequest(details);", + + plugin.code, + // adding a new line in case the code ends with a line comment + plugin.code.endsWith("\n") ? "" : "\n", + `})(GM("${data_key}", ${tab_id}, ${JSON.stringify(meta)}))`, + + // Firefox lists .user.js among our own content scripts so a space at start will group them + `\n//# sourceURL=${browser.runtime.getURL( + "plugins/%20" + name + ".user.js" + )}`, + ].join(""); +} + +export function is_userscripts_api_available() { + try { + // Property access which throws if developer mode is not enabled. + chrome.userScripts; + return true; + } catch { + // Not available. + return false; + } +} diff --git a/vue.config.js b/vue.config.js index c6dd67e..e708a61 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,3 +1,68 @@ +const manifest_transformer = (manifest) => { + const browser = process.env.BROWSER; + const manifest_version = process.env.MANIFEST_VERSION; + + if (manifest_version === "2") { + manifest_v2_transformer(manifest, browser); + } else if (manifest_version === "3") { + manifest_v3_transformer(manifest, browser); + } + manifest.manifest_version = parseInt(manifest_version); +}; + +const manifest_v2_transformer = (manifest, browser) => { + manifest.content_scripts = [ + { + matches: [""], + run_at: "document_start", + js: ["js/content-script.js"], + }, + ]; + manifest.permissions.push(""); + manifest.permissions.push("scripting"); + manifest.permissions.push("webRequest"); + manifest.permissions.push("webRequestBlocking"); + manifest.background.page = "background.html"; + + if (browser === "safari-ios") { + manifest.background.persistent = false; + } +}; + +const manifest_v3_transformer = (manifest, browser) => { + manifest.host_permissions = [ + "https://intel.ingress.com/*", + "https://missions.ingress.com/*", + "https://v.enl.one/", + ]; + manifest.optional_host_permissions = ["https://*/*", "http://*/*"]; + manifest.content_security_policy = { + extension_pages: + "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws://localhost:9090 http://localhost:8000 https://*; img-src 'self' https://iitc.app", + }; + manifest.content_scripts = [ + { + matches: [""], + run_at: "document_start", + js: ["js/content-script.js"], + }, + ]; + + manifest.action = manifest.browser_action; + delete manifest.browser_action; + + if (browser === "chrome") { + manifest.minimum_chrome_version = "120"; + manifest.permissions.push("userScripts"); + manifest.background.service_worker = "js/background.js"; + } else { + manifest.permissions.push("scripting"); + manifest.permissions.push("webRequest"); + manifest.permissions.push("webRequestBlocking"); + manifest.background.page = "background.html"; + } +}; + module.exports = { filenameHashing: false, productionSourceMap: false, @@ -35,13 +100,7 @@ module.exports = { }, }, manifestTransformer: (manifest) => { - if (process.env.BROWSER === "safari-ios") { - manifest.background.persistent = false; - } - if (process.env.BROWSER === "firefox") { - manifest.permissions.append("webRequestBlocking"); - manifest.background.page = "background.html"; - } + manifest_transformer(manifest); return manifest; }, },