UNPKG

wxt

Version:

⚡ Next-gen Web Extension Framework

194 lines (187 loc) 5.87 kB
import definition from 'virtual:user-background-entrypoint'; import { initPlugins } from 'virtual:wxt-plugins'; import { browser } from 'wxt/browser'; import { MatchPattern } from 'wxt/utils/match-patterns'; function print(method, ...args) { if (import.meta.env.MODE === "production") return; if (typeof args[0] === "string") { const message = args.shift(); method(`[wxt] ${message}`, ...args); } else { method("[wxt]", ...args); } } const logger = { debug: (...args) => print(console.debug, ...args), log: (...args) => print(console.log, ...args), warn: (...args) => print(console.warn, ...args), error: (...args) => print(console.error, ...args) }; let ws; function getDevServerWebSocket() { if (import.meta.env.COMMAND !== "serve") throw Error( "Must be running WXT dev command to connect to call getDevServerWebSocket()" ); if (ws == null) { const serverUrl = __DEV_SERVER_ORIGIN__; logger.debug("Connecting to dev server @", serverUrl); ws = new WebSocket(serverUrl, "vite-hmr"); ws.addWxtEventListener = ws.addEventListener.bind(ws); ws.sendCustom = (event, payload) => ws?.send(JSON.stringify({ type: "custom", event, payload })); ws.addEventListener("open", () => { logger.debug("Connected to dev server"); }); ws.addEventListener("close", () => { logger.debug("Disconnected from dev server"); }); ws.addEventListener("error", (event) => { logger.error("Failed to connect to dev server", event); }); ws.addEventListener("message", (e) => { try { const message = JSON.parse(e.data); if (message.type === "custom") { ws?.dispatchEvent( new CustomEvent(message.event, { detail: message.data }) ); } } catch (err) { logger.error("Failed to handle message", err); } }); } return ws; } function keepServiceWorkerAlive() { setInterval(async () => { await browser.runtime.getPlatformInfo(); }, 5e3); } function reloadContentScript(payload) { const manifest = browser.runtime.getManifest(); if (manifest.manifest_version == 2) { void reloadContentScriptMv2(); } else { void reloadContentScriptMv3(payload); } } async function reloadContentScriptMv3({ registration, contentScript }) { if (registration === "runtime") { await reloadRuntimeContentScriptMv3(contentScript); } else { await reloadManifestContentScriptMv3(contentScript); } } async function reloadManifestContentScriptMv3(contentScript) { const id = `wxt:${contentScript.js[0]}`; logger.log("Reloading content script:", contentScript); const registered = await browser.scripting.getRegisteredContentScripts(); logger.debug("Existing scripts:", registered); const existing = registered.find((cs) => cs.id === id); if (existing) { logger.debug("Updating content script", existing); await browser.scripting.updateContentScripts([ { ...contentScript, id, css: contentScript.css ?? [] } ]); } else { logger.debug("Registering new content script..."); await browser.scripting.registerContentScripts([ { ...contentScript, id, css: contentScript.css ?? [] } ]); } await reloadTabsForContentScript(contentScript); } async function reloadRuntimeContentScriptMv3(contentScript) { logger.log("Reloading content script:", contentScript); const registered = await browser.scripting.getRegisteredContentScripts(); logger.debug("Existing scripts:", registered); const matches = registered.filter((cs) => { const hasJs = contentScript.js?.find((js) => cs.js?.includes(js)); const hasCss = contentScript.css?.find((css) => cs.css?.includes(css)); return hasJs || hasCss; }); if (matches.length === 0) { logger.log( "Content script is not registered yet, nothing to reload", contentScript ); return; } await browser.scripting.updateContentScripts(matches); await reloadTabsForContentScript(contentScript); } async function reloadTabsForContentScript(contentScript) { const allTabs = await browser.tabs.query({}); const matchPatterns = contentScript.matches.map( (match) => new MatchPattern(match) ); const matchingTabs = allTabs.filter((tab) => { const url = tab.url; if (!url) return false; return !!matchPatterns.find((pattern) => pattern.includes(url)); }); await Promise.all( matchingTabs.map(async (tab) => { try { await browser.tabs.reload(tab.id); } catch (err) { logger.warn("Failed to reload tab:", err); } }) ); } async function reloadContentScriptMv2(_payload) { throw Error("TODO: reloadContentScriptMv2"); } if (import.meta.env.COMMAND === "serve") { try { const ws = getDevServerWebSocket(); ws.addWxtEventListener("wxt:reload-extension", () => { browser.runtime.reload(); }); ws.addWxtEventListener("wxt:reload-content-script", (event) => { reloadContentScript(event.detail); }); if (import.meta.env.MANIFEST_VERSION === 3) { ws.addEventListener( "open", () => ws.sendCustom("wxt:background-initialized") ); keepServiceWorkerAlive(); } } catch (err) { logger.error("Failed to setup web socket connection with dev server", err); } browser.commands.onCommand.addListener((command) => { if (command === "wxt:reload-extension") { browser.runtime.reload(); } }); } let result; try { initPlugins(); result = definition.main(); if (result instanceof Promise) { console.warn( "The background's main() function return a promise, but it must be synchronous" ); } } catch (err) { logger.error("The background crashed on startup!"); throw err; } const result$1 = result; export { result$1 as default };