@ywkrlerche/richardwho 1.0.24 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.js CHANGED
@@ -63,6 +63,7 @@
63
63
  // games from the extra collection carry their own pinned cover URL
64
64
  function logoUrl(game) {
65
65
  if (game.cover) return game.cover;
66
+ if (typeof game.logo === "string") return `logos/${encodeURIComponent(game.logo)}`; // PNG/JPG/GIF logo
66
67
  return game.logo ? `logos/${encodeURIComponent(game.id)}.webp` : null;
67
68
  }
68
69
  function fmtMB(bytes) {
@@ -220,19 +221,37 @@
220
221
 
221
222
  try {
222
223
  const pieces = await Promise.all(urls.map(async (url, i) => {
223
- const res = await fetch(url, { signal: controller.signal });
224
- if (!res.ok) throw new Error(`HTTP ${res.status} for ${url.split("/").pop()}`);
225
- if (!res.body) { const b = await res.arrayBuffer(); loaded[i] = b.byteLength; update(); return [b]; }
226
- const reader = res.body.getReader();
227
- const chunks = [];
228
- for (;;) {
229
- const { done, value } = await reader.read();
230
- if (done) break;
231
- chunks.push(value);
232
- loaded[i] += value.byteLength;
233
- update();
224
+ // Try the main link, then backups. jsDelivr sometimes fails the first fetch of a file it
225
+ // hasn't cached yet, and a download can drop mid-way, so a retry usually succeeds.
226
+ const sources = mirrorsFor(url);
227
+ let lastErr;
228
+ for (let attempt = 0; attempt < sources.length; attempt++) {
229
+ if (controller.signal.aborted) throw new DOMException("Aborted", "AbortError");
230
+ if (attempt) {
231
+ els.loaderStatus.textContent = `Connection hiccup, trying backup ${attempt} of ${sources.length - 1}…`;
232
+ await new Promise((r) => setTimeout(r, 600 * attempt));
233
+ }
234
+ loaded[i] = 0;
235
+ try {
236
+ const res = await fetch(sources[attempt], { signal: controller.signal });
237
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url.split("/").pop()}`);
238
+ if (!res.body) { const b = await res.arrayBuffer(); loaded[i] = b.byteLength; update(); return [b]; }
239
+ const reader = res.body.getReader();
240
+ const chunks = [];
241
+ for (;;) {
242
+ const { done, value } = await reader.read();
243
+ if (done) break;
244
+ chunks.push(value);
245
+ loaded[i] += value.byteLength;
246
+ update();
247
+ }
248
+ return chunks;
249
+ } catch (err) {
250
+ if (err.name === "AbortError") throw err;
251
+ lastErr = err;
252
+ }
234
253
  }
235
- return chunks;
254
+ throw lastErr;
236
255
  }));
237
256
  if (currentLoad?.id !== game.id) return;
238
257
  els.loaderStatus.textContent = "Starting…";
@@ -255,6 +274,22 @@
255
274
  }
256
275
  }
257
276
 
277
+ // Backup sources for a jsDelivr GitHub link, in the order to try them:
278
+ // the same link again, jsDelivr's second network, then GitHub's own file server.
279
+ // The extra collection's files are also published as gn-math/html at the same commit.
280
+ function mirrorsFor(url) {
281
+ const m = url.match(/^https:\/\/cdn\.jsdelivr\.net\/gh\/([^/]+)\/([^@/]+)@([^/]+)\/(.+)$/);
282
+ if (!m) return [url, url];
283
+ const [, user, repo, ref, path] = m;
284
+ const list = [url, url, `https://fastly.jsdelivr.net/gh/${user}/${repo}@${ref}/${path}`,
285
+ `https://raw.githubusercontent.com/${user}/${repo}/${ref}/${path}`];
286
+ if (user === "freebuisness" && repo === "html") {
287
+ list.push(`https://cdn.jsdelivr.net/gh/gn-math/html@${ref}/${path}`,
288
+ `https://raw.githubusercontent.com/gn-math/html/${ref}/${path}`);
289
+ }
290
+ return list;
291
+ }
292
+
258
293
  // The extra collection's launch files embed Google Analytics; remove it so players aren't tracked
259
294
  function stripTracking(html) {
260
295
  return html