Skip to content

Commit 869a4a9

Browse files
committed
fix(windows): zip download 404 fallback via GitHub releases API
When releases/latest/download/HyperlinksSpaceApp_<version>.zip returns 404, resolve the portable zip asset from the latest release API and retry. Includes package/deps updates for the app build. Made-with: Cursor
1 parent 0dbc935 commit 869a4a9

3 files changed

Lines changed: 91 additions & 18 deletions

File tree

app/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"url": "https://github.com/HyperlinksSpace/HyperlinksSpaceBot.git"
99
},
1010
"main": "expo-router/entry",
11-
"version": "53.0.1294",
11+
"version": "53.0.1295",
1212
"type": "module",
1313
"engines": {
1414
"node": ">=18 <=22"

app/windows/build.cjs

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,70 @@ function githubLatestAssetUrl(fileName) {
103103
return `https://github.com/${UPDATE_GITHUB_OWNER}/${UPDATE_GITHUB_REPO}/releases/latest/download/${enc}`;
104104
}
105105

106+
const GITHUB_API_HEADERS = {
107+
Accept: "application/vnd.github+json",
108+
"User-Agent": "HyperlinksSpaceApp/electron-updater",
109+
};
110+
111+
/**
112+
* When /releases/latest/download/<name>.zip returns 404, find the portable zip on the latest release
113+
* via the GitHub API (asset names may differ slightly from artifactName).
114+
* @returns {Promise<string|null>} browser_download_url or null
115+
*/
116+
async function fetchPortableZipBrowserUrlFromGitHubApi(netFetch, version, preferredFileName) {
117+
const apiUrl = `https://api.github.com/repos/${UPDATE_GITHUB_OWNER}/${UPDATE_GITHUB_REPO}/releases/latest`;
118+
const res = await netFetch(apiUrl, { headers: GITHUB_API_HEADERS });
119+
if (!res.ok) {
120+
log(`[updater] GitHub API GET releases/latest: HTTP ${res.status}`);
121+
return null;
122+
}
123+
let data;
124+
try {
125+
data = await res.json();
126+
} catch (_) {
127+
return null;
128+
}
129+
const assets = Array.isArray(data.assets) ? data.assets : [];
130+
const zips = assets.filter((a) => a && typeof a.name === "string" && /\.zip$/i.test(a.name));
131+
const skipName = (n) =>
132+
/blockmap|\.7z\.|\.delta/i.test(n) || /-ia32-|arm64|\.msi$/i.test(n);
133+
const candidates = zips.filter((a) => !skipName(a.name));
134+
135+
const exact = candidates.find((a) => a.name === preferredFileName);
136+
if (exact?.browser_download_url) {
137+
log(`[updater] GitHub API: exact zip match ${exact.name}`);
138+
return exact.browser_download_url;
139+
}
140+
141+
const verLoose = String(version).trim();
142+
const withVersion = candidates.filter((a) => a.name.includes(verLoose));
143+
if (withVersion.length === 1 && withVersion[0].browser_download_url) {
144+
log(`[updater] GitHub API: single zip matching version ${verLoose}: ${withVersion[0].name}`);
145+
return withVersion[0].browser_download_url;
146+
}
147+
148+
const prefixed = candidates.find(
149+
(a) =>
150+
a.name.startsWith(WIN_PORTABLE_ZIP_PREFIX) ||
151+
/^HyperlinksSpaceApp[_-]/i.test(a.name) ||
152+
/Hyperlinks\s*Space/i.test(a.name),
153+
);
154+
if (prefixed?.browser_download_url) {
155+
log(`[updater] GitHub API: portable-like zip ${prefixed.name}`);
156+
return prefixed.browser_download_url;
157+
}
158+
159+
if (candidates.length === 1 && candidates[0].browser_download_url) {
160+
log(`[updater] GitHub API: only zip on release: ${candidates[0].name}`);
161+
return candidates[0].browser_download_url;
162+
}
163+
164+
log(
165+
`[updater] GitHub API: could not pick zip (candidates: ${candidates.map((c) => c.name).join(", ") || "none"})`,
166+
);
167+
return null;
168+
}
169+
106170
async function downloadToFile(netFetch, url, destPath, onProgress) {
107171
const res = await netFetch(url);
108172
if (!res.ok) {
@@ -506,21 +570,30 @@ function setupAutoUpdater() {
506570
fs.mkdirSync(extractDir, { recursive: true });
507571

508572
const zipPath = path.join(versionDir, meta.fileName);
509-
const zipUrl = githubLatestAssetUrl(meta.fileName);
510-
await downloadToFile(
511-
(u) => net.fetch(u),
512-
zipUrl,
513-
zipPath,
514-
(received, total) => {
515-
const dl = total > 0 ? received / total : 0;
516-
const dlPct = total > 0 ? Math.round(100 * dl) : 0;
517-
const overall = Math.min(PROGRESS_DOWNLOAD_CAP, Math.round(PROGRESS_DOWNLOAD_CAP * dl));
518-
pushUi({
519-
text: `Downloading and preparing update… ${dlPct}%`,
520-
percent: overall,
521-
});
522-
},
523-
);
573+
const primaryZipUrl = githubLatestAssetUrl(meta.fileName);
574+
const onZipProgress = (received, total) => {
575+
const dl = total > 0 ? received / total : 0;
576+
const dlPct = total > 0 ? Math.round(100 * dl) : 0;
577+
const overall = Math.min(PROGRESS_DOWNLOAD_CAP, Math.round(PROGRESS_DOWNLOAD_CAP * dl));
578+
pushUi({
579+
text: `Downloading and preparing update… ${dlPct}%`,
580+
percent: overall,
581+
});
582+
};
583+
try {
584+
await downloadToFile((u) => net.fetch(u), primaryZipUrl, zipPath, onZipProgress);
585+
} catch (e) {
586+
const msg = String(e?.message || e);
587+
if (!/404/.test(msg)) throw e;
588+
const altUrl = await fetchPortableZipBrowserUrlFromGitHubApi(
589+
(u, init) => net.fetch(u, init),
590+
meta.version,
591+
meta.fileName,
592+
);
593+
if (!altUrl) throw e;
594+
log(`[updater] primary zip 404; downloading from GitHub API URL`);
595+
await downloadToFile((u) => net.fetch(u), altUrl, zipPath, onZipProgress);
596+
}
524597

525598
pushUi({ text: "Verifying update…", percent: PROGRESS_DOWNLOAD_CAP + 2 });
526599

0 commit comments

Comments
 (0)