186 lines
6.0 kB
1
import { webFrame, ipcRenderer, contextBridge } from "electron";
2
import fs from "node:fs";
3
import path from "node:path";
4
5
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
6
import { constants, MoonlightBranch } from "@moonlight-mod/types";
7
import { getExtensions } from "@moonlight-mod/core/extension";
8
import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
9
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
10
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
11
import createFS from "@moonlight-mod/core/fs";
12
import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors";
13
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
14
15
let initialized = false;
16
let logger: Logger;
17
18
function setCors() {
19
const data = getDynamicCors();
20
ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
21
ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
22
}
23
24
async 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
async setConfigOption(ext, name, value) {
66
setConfigOption(config, ext, name, value);
67
await 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
110
async 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
119
async 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
132
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
133
const isOverlay = window.location.href.indexOf("discord_overlay") > -1;
134
135
if (isOverlay) {
136
// The overlay has an inline script tag to call to DiscordNative, so we'll
137
// just load it immediately. Somehow moonlight still loads in this env, I
138
// have no idea why - so I suspect it's just forwarding render calls or
139
// something from the original process
140
require(oldPreloadPath);
141
} else {
142
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
143
(async () => {
144
try {
145
await init();
146
logger.debug("Blocked scripts:", blockedScripts);
147
148
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
149
logger.debug("Old preload path:", oldPreloadPath);
150
if (oldPreloadPath) require(oldPreloadPath);
151
152
// Do this to get global.DiscordNative assigned
153
// @ts-expect-error Lying to discord_desktop_core
154
process.emit("loaded");
155
156
function replayScripts() {
157
const scripts = [...document.querySelectorAll("script")].filter(
158
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
159
);
160
161
blockedScripts.reverse();
162
for (const url of blockedScripts) {
163
if (url.includes("/sentry.")) continue;
164
165
const script = scripts.find((script) => url.includes(script.src))!;
166
const newScript = document.createElement("script");
167
for (const attr of script.attributes) {
168
if (attr.name === "src") attr.value += "?inj";
169
newScript.setAttribute(attr.name, attr.value);
170
}
171
script.remove();
172
document.documentElement.appendChild(newScript);
173
}
174
}
175
176
if (document.readyState === "complete") {
177
replayScripts();
178
} else {
179
window.addEventListener("load", replayScripts);
180
}
181
} catch (e) {
182
logger.error("Error restoring original scripts:", e);
183
}
184
})();
185
});
186
}
187