pike/glyph Public Public: anyone can read this; nobody can change it.
forked from apps/glyph
workbenchthis machine
Files cd3c7d74b323cac4ab0520540ce33bcf71a582db
| 1 | // The page stays current without a reload. /events is a stream of what |
| 2 | // changed; each event becomes `live` and `live:<type>` on `document`, and |
| 3 | // any element with data-live="<types>" is refetched and swapped when one of |
| 4 | // its types arrives — server-rendered, so the page never renders twice. |
| 5 | // Without this file the page is the same page, one reload behind. |
| 6 | |
| 7 | let since = ""; |
| 8 | let tries = 0; |
| 9 | let source; |
| 10 | |
| 11 | function connect() { |
| 12 | source = new EventSource(since ? `/events?since=${since}` : "/events"); |
| 13 | source.onopen = () => { |
| 14 | if (tries > 0) refresh(); // Reconnected: whatever was missed, catch up. |
| 15 | tries = 0; |
| 16 | }; |
| 17 | source.onmessage = (message) => { |
| 18 | since = message.lastEventId; |
| 19 | const { type, data } = JSON.parse(message.data); |
| 20 | document.dispatchEvent(new CustomEvent("live", { detail: { type, data } })); |
| 21 | document.dispatchEvent(new CustomEvent(`live:${type}`, { detail: data })); |
| 22 | }; |
| 23 | // The browser retries a dropped connection itself; a refused one it gives |
| 24 | // up on, and then we try again, later each time. |
| 25 | source.onerror = () => { |
| 26 | if (source.readyState !== EventSource.CLOSED) return; |
| 27 | source.close(); |
| 28 | setTimeout(connect, Math.min(30_000, 1000 * 2 ** tries++)); |
| 29 | }; |
| 30 | } |
| 31 | |
| 32 | let refreshing = false; |
| 33 | let again = false; |
| 34 | async function refresh(types) { |
| 35 | const regions = [...document.querySelectorAll("[data-live]")].filter( |
| 36 | (el) => |
| 37 | types === undefined || |
| 38 | el.dataset.live.split(" ").some((t) => types.includes(t)), |
| 39 | ); |
| 40 | if (regions.length === 0) return; |
| 41 | if (refreshing) { |
| 42 | again = true; |
| 43 | return; |
| 44 | } |
| 45 | refreshing = true; |
| 46 | try { |
| 47 | const response = await fetch(location.href, { |
| 48 | headers: { accept: "text/html" }, |
| 49 | }); |
| 50 | const fresh = new DOMParser().parseFromString( |
| 51 | await response.text(), |
| 52 | "text/html", |
| 53 | ); |
| 54 | for (const el of regions) { |
| 55 | const next = fresh.getElementById(el.id); |
| 56 | if (next) el.replaceWith(next); |
| 57 | } |
| 58 | } finally { |
| 59 | refreshing = false; |
| 60 | if (again) { |
| 61 | again = false; |
| 62 | refresh(); |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | document.addEventListener("live", (event) => refresh([event.detail.type])); |
| 68 | connect(); |