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