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