Files
ridebus-status/public/widget-client.js
Zakhar Timoshenko cb1977b65f feat: add RideBus status widget integration
- 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.
2026-10-14 00:24:40 +03:00

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);
})();