wxt
Version:
⚡ Next-gen Web Extension Framework
164 lines (163 loc) • 5.88 kB
JavaScript
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";
//#region src/utils/internal/logger.ts
function print(method, ...args) {
if (import.meta.env.MODE === "production") return;
if (typeof args[0] === "string") method(`[wxt] ${args.shift()}`, ...args);
else method("[wxt]", ...args);
}
/** Wrapper around `console` with a "[wxt]" prefix */
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)
};
//#endregion
//#region src/utils/internal/dev-server-websocket.ts
let ws;
/** Connect to the websocket and listen for messages. */
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;
}
//#endregion
//#region src/virtual/utils/keep-service-worker-alive.ts
/** https://developer.chrome.com/blog/longer-esw-lifetimes/ */
function keepServiceWorkerAlive() {
setInterval(async () => {
await browser.runtime.getPlatformInfo();
}, 5e3);
}
//#endregion
//#region src/virtual/utils/reload-content-scripts.ts
function reloadContentScript(payload) {
if (browser.runtime.getManifest().manifest_version == 2) reloadContentScriptMv2(payload);
else 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");
}
//#endregion
//#region src/virtual/background-entrypoint.ts
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;
}
var background_entrypoint_default = result;
//#endregion
export { background_entrypoint_default as default };