this repo has no description
1import { webFrame, ipcRenderer, contextBridge } from "electron";
2import fs from "node:fs";
3import path from "node:path";
4
5import { readConfig, writeConfig } from "@moonlight-mod/core/config";
6import { constants, MoonlightBranch } from "@moonlight-mod/types";
7import { getExtensions } from "@moonlight-mod/core/extension";
8import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
9import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
10import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
11import createFS from "@moonlight-mod/core/fs";
12import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors";
13import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
14
15let initialized = false;
16let logger: Logger;
17
18function setCors() {
19 const data = getDynamicCors();
20 ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
21 ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
22}
23
24async function injectGlobals() {
25 global.moonlightNodeSandboxed = {
26 fs: createFS(),
27 addCors(url) {
28 registerCors(url);
29 if (initialized) setCors();
30 },
31 addBlocked(url) {
32 registerBlocked(url);
33 if (initialized) setCors();
34 }
35 };
36
37 let config = await readConfig();
38 initLogger(config);
39 logger = new Logger("node-preload");
40
41 const extensions = await getExtensions();
42 const processedExtensions = await loadExtensions(extensions);
43 const moonlightDir = await getMoonlightDir();
44 const extensionsPath = await getExtensionsPath();
45
46 global.moonlightNode = {
47 get config() {
48 return config;
49 },
50 extensions,
51 processedExtensions,
52 nativesCache: {},
53 isBrowser: false,
54
55 version: MOONLIGHT_VERSION,
56 branch: MOONLIGHT_BRANCH as MoonlightBranch,
57
58 getConfig(ext) {
59 return getConfig(ext, config);
60 },
61 getConfigOption(ext, name) {
62 const manifest = getManifest(extensions, ext);
63 return getConfigOption(ext, name, config, manifest?.settings);
64 },
65 setConfigOption(ext, name, value) {
66 setConfigOption(config, ext, name, value);
67 this.writeConfig(config);
68 },
69 async writeConfig(newConfig) {
70 await writeConfig(newConfig);
71 config = newConfig;
72 },
73
74 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
75 getLogger: (id: string) => {
76 return new Logger(id);
77 },
78 getMoonlightDir() {
79 return moonlightDir;
80 },
81 getExtensionDir: (ext: string) => {
82 return path.join(extensionsPath, ext);
83 }
84 };
85
86 await loadProcessedExtensions(processedExtensions);
87 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
88
89 const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []);
90 for (const cors of extCors) {
91 registerCors(cors);
92 }
93
94 for (const repo of moonlightNode.config.repositories) {
95 const url = new URL(repo);
96 url.pathname = "/";
97 registerCors(url.toString());
98 }
99
100 const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []);
101 for (const blocked of extBlocked) {
102 registerBlocked(blocked);
103 }
104
105 setCors();
106
107 initialized = true;
108}
109
110async function loadPreload() {
111 const webPreloadPath = path.join(__dirname, "web-preload.js");
112 const webPreload = fs.readFileSync(webPreloadPath, "utf8");
113 await webFrame.executeJavaScript(webPreload);
114
115 const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }");
116 await func();
117}
118
119async function init() {
120 try {
121 await injectGlobals();
122 await loadPreload();
123 } catch (e) {
124 const message = e instanceof Error ? e.stack : e;
125 await ipcRenderer.invoke(constants.ipcMessageBox, {
126 title: "moonlight node-preload error",
127 message: message
128 });
129 }
130}
131
132ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
133 (async () => {
134 try {
135 await init();
136 logger.debug("Blocked scripts:", blockedScripts);
137
138 const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
139 logger.debug("Old preload path:", oldPreloadPath);
140 if (oldPreloadPath) require(oldPreloadPath);
141
142 // Do this to get global.DiscordNative assigned
143 // @ts-expect-error Lying to discord_desktop_core
144 process.emit("loaded");
145
146 function replayScripts() {
147 const scripts = [...document.querySelectorAll("script")].filter(
148 (script) => script.src && blockedScripts.some((url) => url.includes(script.src))
149 );
150
151 blockedScripts.reverse();
152 for (const url of blockedScripts) {
153 if (url.includes("/sentry.")) continue;
154
155 const script = scripts.find((script) => url.includes(script.src))!;
156 const newScript = document.createElement("script");
157 for (const attr of script.attributes) {
158 if (attr.name === "src") attr.value += "?inj";
159 newScript.setAttribute(attr.name, attr.value);
160 }
161 script.remove();
162 document.documentElement.appendChild(newScript);
163 }
164 }
165
166 if (document.readyState === "complete") {
167 replayScripts();
168 } else {
169 window.addEventListener("load", replayScripts);
170 }
171 } catch (e) {
172 logger.error("Error restoring original scripts:", e);
173 }
174 })();
175});