|
15 | 15 | transition: "width 0.3s ease", |
16 | 16 | }); |
17 | 17 | document.body.appendChild(bar); |
| 18 | + let interval; |
18 | 19 | return { |
19 | 20 | start() { |
20 | 21 | let progress = 0; |
21 | | - this.interval = setInterval(() => { |
| 22 | + interval = setInterval(() => { |
22 | 23 | progress = Math.min(progress + Math.random() * 10, 90); |
23 | 24 | bar.style.width = progress + "%"; |
24 | 25 | }, 200); |
25 | 26 | }, |
26 | | - finish() { |
27 | | - clearInterval(this.interval); |
28 | | - bar.style.width = "100%"; |
29 | | - setTimeout(() => bar.remove(), 300); |
| 27 | + finish(success = true) { |
| 28 | + if (interval) { |
| 29 | + clearInterval(interval); |
| 30 | + } |
| 31 | + if (success) { |
| 32 | + bar.style.width = "100%"; |
| 33 | + setTimeout(() => bar.remove(), 300); |
| 34 | + } else { |
| 35 | + bar.remove(); |
| 36 | + } |
30 | 37 | }, |
31 | 38 | }; |
32 | 39 | } |
| 40 | + |
| 41 | + function buildCandidates(url) { |
| 42 | + const trimmed = url.trim(); |
| 43 | + if (!trimmed) return []; |
| 44 | + const queryIndex = trimmed.indexOf("?"); |
| 45 | + const base = queryIndex === -1 ? trimmed : trimmed.slice(0, queryIndex); |
| 46 | + const query = queryIndex === -1 ? "" : trimmed.slice(queryIndex); |
| 47 | + const candidates = []; |
| 48 | + if (base.endsWith(".wasm") && !base.endsWith(".wasm.br")) { |
| 49 | + candidates.push(`${base}.br${query}`); |
| 50 | + } |
| 51 | + candidates.push(trimmed); |
| 52 | + return candidates; |
| 53 | + } |
| 54 | + |
| 55 | + async function fetchWithFallback(candidates) { |
| 56 | + let lastError; |
| 57 | + for (const candidate of candidates) { |
| 58 | + try { |
| 59 | + const resp = await fetch(candidate); |
| 60 | + if (!resp.ok) { |
| 61 | + lastError = new Error(`unexpected status ${resp.status}`); |
| 62 | + continue; |
| 63 | + } |
| 64 | + return resp; |
| 65 | + } catch (err) { |
| 66 | + lastError = err; |
| 67 | + } |
| 68 | + } |
| 69 | + throw lastError || new Error("no wasm candidates provided"); |
| 70 | + } |
| 71 | + |
33 | 72 | async function load(url, { go, color, height, blur, skipLoader } = {}) { |
| 73 | + const candidates = buildCandidates(url); |
| 74 | + if (candidates.length === 0) { |
| 75 | + throw new Error("wasm url is empty"); |
| 76 | + } |
| 77 | + |
34 | 78 | let bar; |
35 | 79 | if (!skipLoader) { |
36 | 80 | bar = createBar({ color, height, blur }); |
37 | 81 | bar.start(); |
38 | 82 | } |
39 | | - const resp = await fetch(url); |
40 | | - const bytes = await resp.arrayBuffer(); |
41 | | - if (bar) bar.finish(); |
| 83 | + |
| 84 | + let response; |
| 85 | + try { |
| 86 | + response = await fetchWithFallback(candidates); |
| 87 | + } catch (err) { |
| 88 | + if (bar) bar.finish(false); |
| 89 | + console.error("Failed to load Wasm bundle", candidates, err); |
| 90 | + throw err; |
| 91 | + } |
| 92 | + |
| 93 | + const bytes = await response.arrayBuffer(); |
| 94 | + if (bar) bar.finish(true); |
42 | 95 | const result = await WebAssembly.instantiate(bytes, go.importObject); |
43 | 96 | go.run(result.instance); |
44 | 97 | return result; |
45 | 98 | } |
| 99 | + |
46 | 100 | global.WasmLoader = { load }; |
47 | 101 | })(window); |
0 commit comments