import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { Icon24ArrowUpRightOutline } from '@vkontakte/icons'; import { combinedStatus, maintenanceEnd } from './maintenance.mjs'; import { overallStatus } from './snapshot.mjs'; const arrow = renderToStaticMarkup(createElement(Icon24ArrowUpRightOutline, { width: 16, height: 16 })); const states = ['operational', 'degraded', 'down', 'unknown', 'maintenance']; export const widgetCopy = { ru: { title: 'Статус сервисов RideBus', operational: 'Сервисы работают', degraded: 'Есть перебои', down: 'Сервисы недоступны', unknown: 'Нет свежих данных', maintenance: 'Плановые работы' }, en: { title: 'RideBus service status', operational: 'Services operational', degraded: 'Service disruption', down: 'Services unavailable', unknown: 'No fresh status data', maintenance: 'Scheduled maintenance' }, }; export function widgetOptions(params, services, { embed = false } = {}) { const allowed = embed ? ['services', 'lang', 'theme'] : ['services']; for (const key of params.keys()) if (!allowed.includes(key) || params.getAll(key).length !== 1) throw new Error('Invalid widget option'); const raw = params.get('services'); const ids = raw === null ? services.map(service => service.id) : raw.split(','); if (!ids.length || new Set(ids).size !== ids.length || ids.some(id => !services.some(service => service.id === id))) throw new Error('Unknown or duplicate widget service'); const locale = params.get('lang') || 'ru', theme = params.get('theme') || 'auto'; if (!['ru', 'en'].includes(locale) || !['light', 'dark', 'auto'].includes(theme)) throw new Error('Invalid widget locale or theme'); return { serviceIds: ids, locale, theme }; } export function widgetSummary(store, config, serviceIds, now = Date.now()) { const deadlines = []; const services = config.services.filter(service => serviceIds.includes(service.id)).map(service => { const state = store.state(service), scenario = store.scenario(service); const availabilityUntil = state ? state.checked_at + config.staleAfterSeconds * 1000 + 1 : 0; const scenarioUntil = scenario ? scenario.checked_at + config.scenarioIntervalSeconds * 3000 + 1 : 0; if (availabilityUntil > now) deadlines.push(availabilityUntil); if (scenarioUntil > now) deadlines.push(scenarioUntil); const availability = availabilityUntil > now ? state.status : 'unknown'; const journey = service.scenario ? { status: scenarioUntil > now ? scenario.status : 'unknown' } : null; return { id: service.id, status: combinedStatus(availability, journey, store.inMaintenance(service, now)) }; }); for (const window of store.maintenances()) { if (window.cancelledAt !== null || window.serviceIds.length && !window.serviceIds.some(id => serviceIds.includes(id))) continue; for (const boundary of [window.startsAt, maintenanceEnd(window)]) if (boundary > now) deadlines.push(boundary); } return { version: 1, generatedAt: now, expiresAt: Math.min(now + 90000, ...deadlines), overall: overallStatus(services), services }; } export function widgetDocument(summary, { serviceIds, locale, theme }) { const copy = widgetCopy[locale]; const payload = JSON.stringify({ summary, serviceIds, locale, copy, states }).replaceAll('<', '\\u003c'); return `