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