this repo has no description
at v1.3.7 6.2 kB view raw
1import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types"; 2import { readConfig } from "./config"; 3import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; 4import Logger from "./util/logger"; 5 6const logger = new Logger("core/extension"); 7 8async function findManifests(dir: string): Promise<string[]> { 9 const ret = []; 10 11 if (await moonlightNodeSandboxed.fs.exists(dir)) { 12 for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) { 13 const path = moonlightNodeSandboxed.fs.join(dir, file); 14 if (file === "manifest.json") { 15 ret.push(path); 16 } 17 18 if (!(await moonlightNodeSandboxed.fs.isFile(path))) { 19 ret.push(...(await findManifests(path))); 20 } 21 } 22 } 23 24 return ret; 25} 26 27async function loadDetectedExtensions( 28 dir: string, 29 type: ExtensionLoadSource, 30 seen: Set<string> 31): Promise<DetectedExtension[]> { 32 const ret: DetectedExtension[] = []; 33 34 const manifests = await findManifests(dir); 35 for (const manifestPath of manifests) { 36 try { 37 if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue; 38 const dir = moonlightNodeSandboxed.fs.dirname(manifestPath); 39 40 const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath)); 41 if (seen.has(manifest.id)) { 42 logger.warn(`Duplicate extension found, skipping: ${manifest.id}`); 43 continue; 44 } 45 seen.add(manifest.id); 46 47 const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js"); 48 const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js"); 49 const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js"); 50 51 // if none exist (empty manifest) don't give a shit 52 if ( 53 !moonlightNodeSandboxed.fs.exists(webPath) && 54 !moonlightNodeSandboxed.fs.exists(nodePath) && 55 !moonlightNodeSandboxed.fs.exists(hostPath) 56 ) { 57 continue; 58 } 59 60 const web = (await moonlightNodeSandboxed.fs.exists(webPath)) 61 ? await moonlightNodeSandboxed.fs.readFileString(webPath) 62 : undefined; 63 64 let url: string | undefined = undefined; 65 const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile); 66 if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) { 67 url = await moonlightNodeSandboxed.fs.readFileString(urlPath); 68 } 69 70 const wpModules: Record<string, string> = {}; 71 const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules"); 72 if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) { 73 const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath); 74 75 for (const wpModuleFile of wpModulesFile) { 76 if (wpModuleFile.endsWith(".js")) { 77 wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString( 78 moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile) 79 ); 80 } 81 } 82 } 83 84 const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css"); 85 86 ret.push({ 87 id: manifest.id, 88 manifest, 89 source: { 90 type, 91 url 92 }, 93 scripts: { 94 web, 95 webPath: web != null ? webPath : undefined, 96 webpackModules: wpModules, 97 nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined, 98 hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined, 99 style: (await moonlightNodeSandboxed.fs.exists(stylePath)) 100 ? await moonlightNodeSandboxed.fs.readFileString(stylePath) 101 : undefined 102 } 103 }); 104 } catch (err) { 105 logger.error(`Failed to load extension from "${manifestPath}":`, err); 106 } 107 } 108 109 return ret; 110} 111 112async function getExtensionsNative(): Promise<DetectedExtension[]> { 113 const config = await readConfig(); 114 const res = []; 115 const seen = new Set<string>(); 116 117 res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen))); 118 119 for (const devSearchPath of config.devSearchPaths ?? []) { 120 res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen))); 121 } 122 123 res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen))); 124 125 return res; 126} 127 128async function getExtensionsBrowser(): Promise<DetectedExtension[]> { 129 const ret: DetectedExtension[] = []; 130 const seen = new Set<string>(); 131 132 const coreExtensionsFs: Record<string, string> = JSON.parse( 133 // @ts-expect-error shut up 134 _moonlight_coreExtensionsStr 135 ); 136 const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0]))); 137 138 for (const ext of coreExtensions) { 139 if (!coreExtensionsFs[`${ext}/index.js`]) continue; 140 const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]); 141 const web = coreExtensionsFs[`${ext}/index.js`]; 142 143 const wpModules: Record<string, string> = {}; 144 const wpModulesPath = `${ext}/webpackModules`; 145 for (const wpModuleFile of Object.keys(coreExtensionsFs)) { 146 if (wpModuleFile.startsWith(wpModulesPath)) { 147 wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile]; 148 } 149 } 150 151 ret.push({ 152 id: manifest.id, 153 manifest, 154 source: { 155 type: ExtensionLoadSource.Core 156 }, 157 scripts: { 158 web, 159 webpackModules: wpModules, 160 style: coreExtensionsFs[`${ext}/style.css`] 161 } 162 }); 163 seen.add(manifest.id); 164 } 165 166 if (await moonlightNodeSandboxed.fs.exists("/extensions")) { 167 ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen))); 168 } 169 170 return ret; 171} 172 173export async function getExtensions(): Promise<DetectedExtension[]> { 174 webPreload: { 175 return moonlightNode.extensions; 176 } 177 178 browser: { 179 return await getExtensionsBrowser(); 180 } 181 182 nodeTarget: { 183 return await getExtensionsNative(); 184 } 185 186 throw new Error("Called getExtensions() outside of node-preload/web-preload"); 187}