PageSourceSearch

https://www.radiokingston.org/wp-content/plugins/srr-onair/includes/js/onair-live.js

js radiokingston.org collected 2026-10-01 10:32:21 UTC 5,744 bytes, 201 lines download raw bytes

1/**
2 * srr-onair live hydration.
3 *
4 * Strategy:
5 *   1. Try the static-cache JSON file first (data-srr-onair-static-url).
6 *      That file is served directly by the webserver — no PHP, no MySQL,
7 *      no WordPress boot. This is the hot path for every poll.
8 *   2. If the file 404s, fails, or contains a stale valid_until, fall back
9 *      once to the REST endpoint (/onair/v1/render?t=<token>). The REST
10 *      handler regenerates the static file as a side effect, so subsequent
11 *      polls are back on the static path.
12 *
13 * Refreshes on DOMContentLoaded, every `interval` ms while the tab is
14 * visible, on visibilitychange to visible, and on pageshow (Safari/Firefox
15 * bfcache restore).
16 *
17 * Localized config (see srr_onair_enqueue_frontend_styles in onair.php):
18 *   window.SRR_ONAIR_LIVE = {
19 *     restUrl:   "https://example.com/wp-json/onair/v1/render",
20 *     interval:  60000,
21 *     debug:     false,
22 *     cacheBust: false  // dev-only — adds &_=Date.now() to fallback URL
23 *   };
24 */
25(function () {
26	'use strict';
27
28	var cfg = window.SRR_ONAIR_LIVE || {};
29	if (!cfg.restUrl) { return; }
30
31	var interval = parseInt(cfg.interval, 10);
32	if (!interval || interval < 10000) { interval = 60000; }
33
34	var DEBUG = !!cfg.debug;
35	var timer = null;
36	var inflight = new WeakSet();
37
38	function log() {
39		if (!DEBUG) { return; }
40		try { console.log.apply(console, ['[srr-onair-live]'].concat([].slice.call(arguments))); } catch (e) {}
41	}
42
43	function nowSeconds() { return Math.floor(Date.now() / 1000); }
44
45	function applyData(el, data) {
46		if (!data || typeof data.html !== 'string') { return; }
47		var currentRev = el.getAttribute('data-srr-onair-rev') || '';
48		if (data.rev && data.rev === currentRev) {
49			log('unchanged', el);
50		} else {
51			el.innerHTML = data.html;
52			if (data.rev) {
53				el.setAttribute('data-srr-onair-rev', data.rev);
54			}
55			log('updated', el, data.rev);
56		}
57		el.setAttribute('data-srr-onair-fresh', '1');
58	}
59
60	/**
61	 * Fetch via the REST endpoint. This boots WordPress, so it should only
62	 * fire (a) on first hydration of a never-seen widget config, or
63	 * (b) when the static file is past its valid_until window.
64	 */
65	function fetchRest(el) {
66		var token = el.getAttribute('data-srr-onair-token');
67		if (!token) { return Promise.reject(new Error('no token')); }
68
69		var url = cfg.restUrl
70			+ (cfg.restUrl.indexOf('?') === -1 ? '?' : '&')
71			+ 't=' + encodeURIComponent(token);
72
73		if (cfg.cacheBust) {
74			url += '&_=' + Date.now();
75		}
76
77		return fetch(url, {
78			method: 'GET',
79			credentials: 'omit',
80			headers: { 'Accept': 'application/json' }
81		}).then(function (resp) {
82			if (!resp.ok) { throw new Error('REST HTTP ' + resp.status); }
83			return resp.json();
84		});
85	}
86
87	/**
88	 * Fetch the static JSON file. Resolves with the parsed JSON on success,
89	 * or rejects so the caller can fall back to REST.
90	 *
91	 * We bust the browser HTTP cache with `&v=<floor(now/30s)>` so a single
92	 * URL doesn't get pinned to stale content by an over-eager intermediate
93	 * cache, but only every 30s so the same URL is reused across a poll
94	 * cycle and any CDN can still serve repeats.
95	 */
96	function fetchStatic(staticUrl) {
97		var bucket = Math.floor(nowSeconds() / 30);
98		var url = staticUrl
99			+ (staticUrl.indexOf('?') === -1 ? '?' : '&')
100			+ 'v=' + bucket;
101
102		return fetch(url, {
103			method: 'GET',
104			credentials: 'omit',
105			headers: { 'Accept': 'application/json' }
106		}).then(function (resp) {
107			if (!resp.ok) { throw new Error('static HTTP ' + resp.status); }
108			return resp.json();
109		});
110	}
111
112	function hydrate(el) {
113		if (inflight.has(el)) { return; }
114		inflight.add(el);
115
116		var staticUrl = el.getAttribute('data-srr-onair-static-url') || '';
117		var done = function () { inflight.delete(el); };
118
119		var doRest = function (reason) {
120			log('fallback-rest', reason, el);
121			fetchRest(el).then(function (data) {
122				applyData(el, data);
123			}).catch(function (err) {
124				log('rest-error', err, el);
125			}).then(done);
126		};
127
128		if (!staticUrl) {
129			doRest('no-static-url');
130			return;
131		}
132
133		fetchStatic(staticUrl).then(function (data) {
134			if (!data || typeof data.html !== 'string') {
135				doRest('static-malformed');
136				return;
137			}
138			// Static file has a valid_until embedded by the server. If we're
139			// past it, the schedule has rolled past a show boundary — hit
140			// REST once to regenerate, then we're back on the static path.
141			var validUntil = data.valid_until ? parseInt(data.valid_until, 10) : 0;
142			if (validUntil && validUntil <= nowSeconds()) {
143				doRest('static-stale');
144				return;
145			}
146			applyData(el, data);
147			done();
148		}).catch(function (err) {
149			doRest(err && err.message ? err.message : 'static-fetch-failed');
150		});
151	}
152
153	function hydrateAll() {
154		var nodes = document.querySelectorAll('[data-srr-onair-live="1"]');
155		for (var i = 0; i < nodes.length; i++) {
156			hydrate(nodes[i]);
157		}
158	}
159
160	function start() {
161		stop();
162		if (document.hidden) { return; }
163		timer = setInterval(hydrateAll, interval);
164	}
165
166	function stop() {
167		if (timer) { clearInterval(timer); timer = null; }
168	}
169
170	function init() {
171		hydrateAll();
172		start();
173	}
174
175	if (document.readyState === 'loading') {
176		document.addEventListener('DOMContentLoaded', init, false);
177	} else {
178		init();
179	}
180
181	// Refresh + restart timer when tab becomes visible.
182	document.addEventListener('visibilitychange', function () {
183		if (document.hidden) {
184			stop();
185		} else {
186			hydrateAll();
187			start();
188		}
189	}, false);
190
191	// Catches Safari/Firefox bfcache restore — page comes back with stale DOM.
192	window.addEventListener('pageshow', function (e) {
193		if (e.persisted) {
194			hydrateAll();
195			start();
196		}
197	}, false);
198
199	// Public hook for manual refresh (e.g., from station-control panels).
200	window.SRR_ONAIR_LIVE.refresh = hydrateAll;
201})();

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.