190 lines
6.3 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
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
15
import { createEventEmitter } from "@moonlight-mod/core/util/event";
16
17
let initialized = false;
18
let logger: Logger;
19
20
function setCors() {
21
const data = getDynamicCors();
22
ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
23
ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
24
}
25
26
async 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
114
async 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
123
async 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
136
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
137
const isOverlay = window.location.href.indexOf("discord_overlay") > -1;
138
139
if (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
}
191