this repo has no description
at v1.3.5 6.0 kB view raw
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"; 14 15let initialized = false; 16let logger: Logger; 17 18function setCors() { 19 const data = getDynamicCors(); 20 ipcRenderer.invoke(constants.ipcSetCorsList, data.cors); 21 ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked); 22} 23 24async 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 110async 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 119async 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 132const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 133const isOverlay = window.location.href.indexOf("discord_overlay") > -1; 134 135if (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}