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}