176 lines
5.4 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
setConfigOption(ext, name, value) {
66
setConfigOption(config, ext, name, value);
67
this.writeConfig(config);
68
},
69
70
getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
71
getLogger: (id: string) => {
72
return new Logger(id);
73
},
74
75
getMoonlightDir() {
76
return moonlightDir;
77
},
78
getExtensionDir: (ext: string) => {
79
return path.join(extensionsPath, ext);
80
},
81
async writeConfig(newConfig) {
82
await writeConfig(newConfig);
83
config = newConfig;
84
}
85
};
86
87
await loadProcessedExtensions(processedExtensions);
88
contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
89
90
const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []);
91
for (const cors of extCors) {
92
registerCors(cors);
93
}
94
95
for (const repo of moonlightNode.config.repositories) {
96
const url = new URL(repo);
97
url.pathname = "/";
98
registerCors(url.toString());
99
}
100
101
const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []);
102
for (const blocked of extBlocked) {
103
registerBlocked(blocked);
104
}
105
106
setCors();
107
108
initialized = true;
109
}
110
111
async function loadPreload() {
112
const webPreloadPath = path.join(__dirname, "web-preload.js");
113
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
114
await webFrame.executeJavaScript(webPreload);
115
116
const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }");
117
await func();
118
}
119
120
async function init() {
121
try {
122
await injectGlobals();
123
await loadPreload();
124
} catch (e) {
125
const message = e instanceof Error ? e.stack : e;
126
await ipcRenderer.invoke(constants.ipcMessageBox, {
127
title: "moonlight node-preload error",
128
message: message
129
});
130
}
131
}
132
133
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
134
(async () => {
135
try {
136
await init();
137
logger.debug("Blocked scripts:", blockedScripts);
138
139
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
140
logger.debug("Old preload path:", oldPreloadPath);
141
if (oldPreloadPath) require(oldPreloadPath);
142
143
// Do this to get global.DiscordNative assigned
144
// @ts-expect-error Lying to discord_desktop_core
145
process.emit("loaded");
146
147
function replayScripts() {
148
const scripts = [...document.querySelectorAll("script")].filter(
149
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
150
);
151
152
blockedScripts.reverse();
153
for (const url of blockedScripts) {
154
if (url.includes("/sentry.")) continue;
155
156
const script = scripts.find((script) => url.includes(script.src))!;
157
const newScript = document.createElement("script");
158
for (const attr of script.attributes) {
159
if (attr.name === "src") attr.value += "?inj";
160
newScript.setAttribute(attr.name, attr.value);
161
}
162
script.remove();
163
document.documentElement.appendChild(newScript);
164
}
165
}
166
167
if (document.readyState === "complete") {
168
replayScripts();
169
} else {
170
window.addEventListener("load", replayScripts);
171
}
172
} catch (e) {
173
logger.error("Error restoring original scripts:", e);
174
}
175
})();
176
});
177