this repo has no description
1import { MoonlightBranch } from "@moonlight-mod/types";
2import type { MoonbaseNatives, RepositoryManifest } from "./types";
3import extractAsar from "@moonlight-mod/core/asar";
4import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants";
5import { parseTarGzip } from "nanotar";
6
7const githubRepo = "moonlight-mod/moonlight";
8const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
9const artifactName = "dist.tar.gz";
10
11const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
12const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
13
14export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`;
15
16async function getStableRelease(): Promise<{
17 name: string;
18 assets: {
19 name: string;
20 browser_download_url: string;
21 }[];
22}> {
23 const req = await fetch(githubApiUrl, {
24 cache: "no-store",
25 headers: {
26 "User-Agent": userAgent
27 }
28 });
29 return await req.json();
30}
31
32export default function getNatives(): MoonbaseNatives {
33 const logger = moonlightNode.getLogger("moonbase/natives");
34
35 return {
36 async checkForMoonlightUpdate() {
37 try {
38 if (moonlightNode.branch === MoonlightBranch.STABLE) {
39 const json = await getStableRelease();
40 return json.name !== moonlightNode.version ? json.name : null;
41 } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) {
42 const req = await fetch(nightlyRefUrl, {
43 cache: "no-store",
44 headers: {
45 "User-Agent": userAgent
46 }
47 });
48 const ref = (await req.text()).split("\n")[0];
49 return ref !== moonlightNode.version ? ref : null;
50 }
51
52 return null;
53 } catch (e) {
54 logger.error("Error checking for moonlight update", e);
55 return null;
56 }
57 },
58
59 async updateMoonlight() {
60 // Note: this won't do anything on browser, we should probably disable it
61 // entirely when running in browser.
62 async function downloadStable(): Promise<[ArrayBuffer, string]> {
63 const json = await getStableRelease();
64 const asset = json.assets.find((a) => a.name === artifactName);
65 if (!asset) throw new Error("Artifact not found");
66
67 logger.debug(`Downloading ${asset.browser_download_url}`);
68 const req = await fetch(asset.browser_download_url, {
69 cache: "no-store",
70 headers: {
71 "User-Agent": userAgent
72 }
73 });
74
75 return [await req.arrayBuffer(), json.name];
76 }
77
78 async function downloadNightly(): Promise<[ArrayBuffer, string]> {
79 logger.debug(`Downloading ${nightlyZipUrl}`);
80 const zipReq = await fetch(nightlyZipUrl, {
81 cache: "no-store",
82 headers: {
83 "User-Agent": userAgent
84 }
85 });
86
87 const refReq = await fetch(nightlyRefUrl, {
88 cache: "no-store",
89 headers: {
90 "User-Agent": userAgent
91 }
92 });
93 const ref = (await refReq.text()).split("\n")[0];
94
95 return [await zipReq.arrayBuffer(), ref];
96 }
97
98 const [tar, ref] =
99 moonlightNode.branch === MoonlightBranch.STABLE
100 ? await downloadStable()
101 : moonlightNode.branch === MoonlightBranch.NIGHTLY
102 ? await downloadNightly()
103 : [null, null];
104
105 if (!tar || !ref) return;
106
107 const dist = moonlightNodeSandboxed.fs.join(moonlightNode.getMoonlightDir(), distDir);
108 if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
109 await moonlightNodeSandboxed.fs.mkdir(dist);
110
111 logger.debug("Extracting update");
112 const files = await parseTarGzip(tar);
113 for (const file of files) {
114 if (!file.data) continue;
115 // @ts-expect-error What do you mean their own types are wrong
116 if (file.type !== "file") continue;
117
118 const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
119 const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
120 if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
121 await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
122 }
123
124 logger.debug("Writing version file:", ref);
125 const versionFile = moonlightNodeSandboxed.fs.join(moonlightNode.getMoonlightDir(), installedVersionFile);
126 await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
127
128 logger.debug("Update extracted");
129 },
130
131 async fetchRepositories(repos) {
132 const ret: Record<string, RepositoryManifest[]> = {};
133
134 for (const repo of repos) {
135 try {
136 const req = await fetch(repo, {
137 cache: "no-store",
138 headers: {
139 "User-Agent": userAgent
140 }
141 });
142 const json = await req.json();
143 ret[repo] = json;
144 } catch (e) {
145 logger.error(`Error fetching repository ${repo}`, e);
146 }
147 }
148
149 return ret;
150 },
151
152 async installExtension(manifest, url, repo) {
153 const req = await fetch(url, {
154 cache: "no-store",
155 headers: {
156 "User-Agent": userAgent
157 }
158 });
159
160 const dir = moonlightNode.getExtensionDir(manifest.id);
161 // remake it in case of updates
162 if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
163 await moonlightNodeSandboxed.fs.mkdir(dir);
164
165 const buffer = await req.arrayBuffer();
166 const files = extractAsar(buffer);
167 for (const [file, buf] of Object.entries(files)) {
168 const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
169 const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
170
171 if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
172 await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
173 }
174
175 await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
176 },
177
178 async deleteExtension(id) {
179 const dir = moonlightNode.getExtensionDir(id);
180 await moonlightNodeSandboxed.fs.rmdir(dir);
181 },
182
183 getExtensionConfig(id, key) {
184 const config = moonlightNode.config.extensions[id];
185 if (typeof config === "object") {
186 return config.config?.[key];
187 }
188
189 return undefined;
190 }
191 };
192}