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