initial commit

This commit is contained in:
2026-10-08 22:56:41 +03:00
commit 4a0785d003
63 changed files with 4863 additions and 0 deletions

24
scripts/probe-api.mjs Normal file
View File

@@ -0,0 +1,24 @@
const key = process.env.RIDEBUS_API_KEY;
if (!key) { console.log('RideBus API key is not configured.'); process.exit(1); }
const base = (process.env.RIDEBUS_API_BASE_URL || 'https://api.ridebus.by/').replace(/\/+$/, '') + '/';
const cityId = process.env.BOARD_CITY_ID || '1';
async function get(path) {
const response = await fetch(new URL(path, base), {
headers: { 'X-API-Key': key, 'X-City-Id': cityId, Accept: 'application/json' },
signal: AbortSignal.timeout(12000), redirect: 'error',
});
if (!response.ok) throw new Error(`API returned HTTP ${response.status}`);
return response.json();
}
try {
const stops = await get('api/v1/stops');
const matches = stops.filter(stop => /швейн/i.test(stop.name));
console.log(JSON.stringify(matches.map(({ id, name, description, routes_count }) => ({ id, name, description, routes_count })), null, 2));
for (const stop of matches) {
const [details, eta] = await Promise.all([get(`api/v1/stops/${stop.id}`), get(`api/v1/eta/stop/${stop.id}`)]);
console.log(JSON.stringify({ stopId: stop.id, routes: details.routes.map(({ id, route_number, name }) => ({ id, route_number, name })), upcomingCount: eta.routes.reduce((count, route) => count + (route.arrivals?.length || 0), 0) }, null, 2));
}
} catch (error) {
console.error(error.cause?.code || error.message);
process.exitCode = 1;
}

31
scripts/shell-cache.mjs Normal file
View File

@@ -0,0 +1,31 @@
import { createHash } from 'node:crypto';
export function shellCachePlugin() {
return {
name: 'ridebus-offline-shell',
generateBundle(_options, bundle) {
const files = ['/', '/favicon.svg', '/manifest.webmanifest', ...Object.keys(bundle).map(file => '/' + file)];
const version = createHash('sha256').update(Object.keys(bundle).join('|')).digest('hex').slice(0, 12);
this.emitFile({ type: 'asset', fileName: 'sw.js', source: `
const CACHE = 'ridebus-board-${version}';
const FILES = ${JSON.stringify(files)};
self.addEventListener('install', event => {
event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(FILES)));
});
self.addEventListener('activate', event => {
event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key.startsWith('ridebus-board-') && key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim()));
});
self.addEventListener('message', event => { if (event.data === 'ACTIVATE') self.skipWaiting(); });
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (event.request.method !== 'GET' || url.origin !== self.location.origin || url.pathname.startsWith('/api/') || url.pathname === '/healthz') return;
if (event.request.mode === 'navigate') {
event.respondWith(fetch(event.request).catch(() => caches.open(CACHE).then(cache => cache.match('/'))));
} else if (FILES.includes(url.pathname)) {
event.respondWith(caches.open(CACHE).then(async cache => (await cache.match(url.pathname)) || fetch(event.request)));
}
});
` });
},
};
}