- Implemented StatusWidget component for embedding RideBus service status. - Created widget-client.js for client-side interaction with the status widget. - Developed install-widget-integrations script to automate integration into PWA, website, and admin projects. - Introduced server-side widget rendering in widget.mjs for generating status widget HTML. - Added comprehensive tests for widget functionality and integration.
49 lines
2.5 KiB
JavaScript
49 lines
2.5 KiB
JavaScript
(() => {
|
|
const { summary, serviceIds, locale, copy, states } = JSON.parse(document.getElementById('widget-data').textContent);
|
|
const link = document.querySelector('.widget'), label = document.querySelector('.label');
|
|
let expiry, expiryTimer, pollTimer, active = false;
|
|
link.href = new URL(`/?lang=${locale}`, location.origin).href;
|
|
const notify = () => { if (parent !== window) parent.postMessage({ type: 'ridebus-status-widget', version: 1, status: link.dataset.status }, '*'); };
|
|
function show(status) {
|
|
link.dataset.status = status;
|
|
label.textContent = copy[status];
|
|
link.setAttribute('aria-label', `${copy.title}: ${copy[status]}`);
|
|
notify();
|
|
}
|
|
function apply(data, elapsedMs = 0) {
|
|
if (data?.version !== 1 || !states.includes(data.overall) || !Number.isFinite(data.generatedAt) || !Number.isFinite(data.expiresAt)) throw new Error('Invalid status');
|
|
clearTimeout(expiryTimer);
|
|
expiry = Date.now() + Math.max(0, Math.min(90000, data.expiresAt - data.generatedAt - elapsedMs));
|
|
show(expiry > Date.now() ? data.overall : 'unknown');
|
|
expiryTimer = setTimeout(() => show('unknown'), Math.max(0, expiry - Date.now()));
|
|
}
|
|
async function refresh() {
|
|
if (active || document.hidden) return;
|
|
active = true;
|
|
clearTimeout(pollTimer);
|
|
const started = performance.now();
|
|
try {
|
|
const url = new URL('/api/widget', location.origin);
|
|
url.searchParams.set('services', serviceIds.join(','));
|
|
const response = await fetch(url, { cache: 'no-store', credentials: 'omit', signal: AbortSignal.timeout(5000) });
|
|
if (!response.ok) throw new Error('Status unavailable');
|
|
const data = await response.json();
|
|
apply(data, performance.now() - started);
|
|
} catch { clearTimeout(expiryTimer); show('unknown'); }
|
|
finally { active = false; if (!document.hidden) pollTimer = setTimeout(refresh, 30000); }
|
|
}
|
|
window.addEventListener('message', event => {
|
|
if (event.source === parent && event.data?.type === 'ridebus-status-widget-request') {
|
|
if (Date.now() >= expiry) show('unknown'); else notify();
|
|
}
|
|
});
|
|
document.addEventListener('visibilitychange', () => {
|
|
clearTimeout(pollTimer);
|
|
if (!document.hidden) { if (Date.now() >= expiry) show('unknown'); void refresh(); }
|
|
});
|
|
window.addEventListener('online', () => void refresh());
|
|
window.addEventListener('offline', () => { clearTimeout(expiryTimer); show('unknown'); });
|
|
apply(summary, performance.now());
|
|
pollTimer = setTimeout(refresh, 30000);
|
|
})();
|