wxt
Version:
⚡ Next-gen Web Extension Framework
194 lines (187 loc) • 5.87 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';
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 };