187 lines
6.2 kB
1
import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types";
2
import { readConfig } from "./config";
3
import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
4
import Logger from "./util/logger";
5
6
const logger = new Logger("core/extension");
7
8
async 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
27
async 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
112
async 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
128
async 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
173
export 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
}
188