PageSourceSearch

https://votertools.voteidaho.gov/TEDElectionLink/_content/TEDxCommonUI/js/tedChartUpdates.zooq4nbrg1.js

js voteidaho.gov collected 2026-09-24 21:13:40 UTC 4,459 bytes, 75 lines download raw bytes

1// Shared poll transport for the chart islands (ChartForgeX SVG swap + ECharts setOption).
2//
3// Polling only by design (see plan): no EventSource, no Blazor circuit — just fetch() on an interval
4// with ETag/304 + jitter. Stateless and cache-collapsing, so it scales to many concurrent viewers of
5// shared data without holding connections open. Loaded once via a normal <script src> before the
6// per-engine island scripts.
7(function () {
8    "use strict";
9    if (window.tedChartUpdates) return;
10
11    // Start a poll loop. Returns a stop() function, which also carries a .poke() method.
12    //   urlOrFn    : endpoint URL, OR a function returning the URL each tick (so a live control
13    //                selection can be appended per request); honours If-None-Match → 304
14    //   intervalMs : base interval; actual wait is jittered ±20% to avoid a thundering herd
15    //   onPayload  : (payload) => void, called with the parsed JSON only when it changes (HTTP 200)
16    // stop.poke()  : fetch immediately (resetting the ETag) — used when a control selection changes
17    //                so the chart updates now instead of on the next interval.
18    function pollLoop(urlOrFn, intervalMs, onPayload, onChecked) {
19        var noop = function () { };
20        noop.poke = noop;
21        if (!urlOrFn || !(intervalMs > 0) || typeof onPayload !== "function") return noop;
22
23        var stopped = false;
24        var etag = null;
25        var timer = null;
26
27        function jitter(ms) { return Math.round(ms * (0.8 + Math.random() * 0.4)); }
28        function currentUrl() { return typeof urlOrFn === "function" ? urlOrFn() : urlOrFn; }
29
30        async function tick() {
31            if (stopped) return;
32            var url = currentUrl();
33            if (url) {
34                try {
35                    var headers = etag ? { "If-None-Match": etag } : {};
36                    var res = await fetch(url, { headers: headers, credentials: "same-origin" });
37                    if (!stopped && res.status === 200) {
38                        var tag = res.headers.get("ETag");
39                        if (tag) etag = tag;
40                        onPayload(await res.json());
41                    }
42                    // A conclusive answer — fresh payload (200) OR unchanged (304) — means the shown
43                    // data is confirmed current; surface it with the server's as-of instant
44                    // (X-Ted-Data-Refreshed, UTC ISO — the AsOf marker version the data was pulled
45                    // under). The header rides on BOTH statuses: the ETag is a content hash, so an
46                    // unchanged payload can still carry a newer as-of.
47                    if (!stopped && typeof onChecked === "function" && (res.status === 200 || res.status === 304)) {
48                        onChecked(res.headers.get("X-Ted-Data-Refreshed"));
49                    }
50                    // Transient errors → keep the current render, retry next tick.
51                } catch (e) {
52                    // Network blip — swallow and retry on the next interval.
53                }
54            }
55            if (!stopped) timer = setTimeout(tick, jitter(intervalMs));
56        }
57
58        function stop() { stopped = true; if (timer) clearTimeout(timer); }
59        // Re-poll now under the current URL (selection changed). Reset the ETag so a same-URL,
60        // changed-selection case still gets a fresh 200.
61        stop.poke = function () { if (stopped) return; etag = null; if (timer) clearTimeout(timer); tick(); };
62
63        // First poll runs almost immediately (a SHORT jittered delay, not a full interval): a board
64        // delivered from the SWR fragment cache — or a live in-app nav — shows the warm-time state
65        // (tiles AND the "Data as of" carrier), which is stale if the marker advanced since that warm.
66        // A prompt first tick re-pulls to CURRENT and carries the fresh X-Ted-Data-Refreshed (which
67        // rides even a 304), so the tiles and the side-menu card catch up in ~1-2s instead of sitting
68        // stale for a whole interval. tick() reschedules itself at the full jittered interval; the
69        // small first-tick jitter still spreads the initial page-load burst.
70        timer = setTimeout(tick, jitter(Math.min(intervalMs, 1500)));
71        return stop;
72    }
73
74    window.tedChartUpdates = { pollLoop: pollLoop };
75})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.