this repo has no description

rework loader to use script blocking like web ext

working towards support of esm and fix a rare race condition

Co-authored-by: NotNite <hi@notnite.com>

Changed files
+109 -23
packages
injector
src
node-preload
src
types
web-preload
src
+54 -7
packages/injector/src/index.ts
···
let hasOpenAsar = false;
let openAsarConfigPreload: string | undefined;
+
const scriptUrls = ["web.", "sentry."];
+
const blockedScripts = new Set<string>();
+
const replayedScripts = new Set<string>();
+
let blockingScripts = false;
+
let replayingScripts = false;
+
ipcMain.on(constants.ipcGetOldPreloadPath, (e) => {
e.returnValue = oldPreloadPath;
});
+
ipcMain.on(constants.ipcGetAppData, (e) => {
e.returnValue = app.getPath("appData");
});
···
});
function patchCsp(headers: Record<string, string[]>) {
-
const directives = ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src", "prefetch-src"];
-
const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"];
+
const directives = [
+
"script-src",
+
"style-src",
+
"connect-src",
+
"img-src",
+
"font-src",
+
"media-src",
+
"worker-src",
+
"prefetch-src"
+
];
+
const values = ["*", "blob:", "data:", "'unsafe-inline'", "'unsafe-eval'", "disclip:"];
const csp = "content-security-policy";
if (headers[csp] == null) return;
···
class BrowserWindow extends ElectronBrowserWindow {
constructor(opts: BrowserWindowConstructorOptions) {
-
oldPreloadPath = opts.webPreferences!.preload;
-
const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1;
-
if (isMainWindow) opts.webPreferences!.preload = require.resolve("./node-preload.js");
+
if (isMainWindow) {
+
if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload;
+
opts.webPreferences!.preload = require.resolve("./node-preload.js");
+
}
// Event for modifying window options
moonlightHost.events.emit("window-options", opts, isMainWindow);
···
}
});
-
// Allow plugins to block some URLs,
-
// this is needed because multiple webRequest handlers cannot be registered at once
this.webContents.session.webRequest.onBeforeRequest((details, cb) => {
+
// Block scripts for node-preload kickoff
+
if (details.resourceType === "script" && isMainWindow) {
+
if (scriptUrls.some((url) => details.url.includes(url))) {
+
if (!replayingScripts) {
+
blockingScripts = true;
+
blockedScripts.add(details.url);
+
} else {
+
replayedScripts.add(details.url);
+
if (replayedScripts.size === scriptUrls.length) {
+
replayingScripts = false;
+
replayedScripts.clear();
+
}
+
}
+
}
+
+
const shouldBlock = blockingScripts;
+
+
if (blockedScripts.size === scriptUrls.length && blockingScripts) {
+
setTimeout(() => {
+
logger.debug("Kicking off node-preload");
+
this.webContents.send(constants.ipcNodePreloadKickoff, Array.from(blockedScripts));
+
blockingScripts = false;
+
blockedScripts.clear();
+
replayingScripts = true;
+
}, 0);
+
}
+
+
if (shouldBlock) return cb({ cancel: true });
+
}
+
+
// Allow plugins to block some URLs,
+
// this is needed because multiple webRequest handlers cannot be registered at once
cb({ cancel: blockedUrls.some((u) => u.test(details.url)) });
});
+49 -6
packages/node-preload/src/index.ts
···
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
let initialized = false;
+
let logger: Logger;
function setCors() {
const data = getDynamicCors();
···
let config = await readConfig();
initLogger(config);
+
logger = new Logger("node-preload");
+
const extensions = await getExtensions();
const processedExtensions = await loadExtensions(extensions);
const moonlightDir = await getMoonlightDir();
···
const webPreloadPath = path.join(__dirname, "web-preload.js");
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
await webFrame.executeJavaScript(webPreload);
+
+
const func = await webFrame.executeJavaScript("async () => { await window._moonlightBrowserLoad(); }");
+
await func();
}
-
async function init(oldPreloadPath: string) {
+
async function init() {
try {
await injectGlobals();
await loadPreload();
···
message: message
});
}
+
}
+
+
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
+
(async () => {
+
try {
+
await init();
+
logger.debug("Blocked scripts:", blockedScripts);
+
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
+
logger.debug("Old preload path:", oldPreloadPath);
+
if (oldPreloadPath) require(oldPreloadPath);
+
+
// Do this to get global.DiscordNative assigned
+
// @ts-expect-error Lying to discord_desktop_core
+
process.emit("loaded");
+
+
function replayScripts() {
+
const scripts = [...document.querySelectorAll("script")].filter(
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
+
);
-
// Let Discord start even if we fail
-
if (oldPreloadPath) require(oldPreloadPath);
-
}
+
blockedScripts.reverse();
+
for (const url of blockedScripts) {
+
const script = scripts.find((script) => url.includes(script.src))!;
+
const newScript = document.createElement("script");
+
for (const attr of script.attributes) {
+
if (attr.name === "src") attr.value += "?inj";
+
newScript.setAttribute(attr.name, attr.value);
+
}
+
script.remove();
+
document.documentElement.appendChild(newScript);
+
}
+
}
-
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
-
init(oldPreloadPath);
+
if (document.readyState === "complete") {
+
replayScripts();
+
} else {
+
window.addEventListener("load", replayScripts);
+
}
+
} catch (e) {
+
logger.error("Error restoring original scripts:", e);
+
}
+
})();
+
});
+2
packages/types/src/constants.ts
···
export const repoUrlFile = ".moonlight-repo-url";
export const installedVersionFile = ".moonlight-installed-version";
+
export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff";
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
+
export const ipcGetAppData = "_moonlight_getAppData";
export const ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop";
export const ipcMessageBox = "_moonlight_messageBox";
+4 -10
packages/web-preload/src/index.ts
···
logger.error("Error setting up web-preload", e);
}
-
if (MOONLIGHT_ENV === "web-preload") {
-
window.addEventListener("DOMContentLoaded", () => {
-
installStyles();
-
});
+
if (document.readyState === "complete") {
+
installStyles();
} else {
-
installStyles();
+
window.addEventListener("load", installStyles);
}
}
-
if (MOONLIGHT_ENV === "web-preload") {
-
load();
-
} else {
-
window._moonlightBrowserLoad = load;
-
}
+
window._moonlightBrowserLoad = load;