style: format terrain traffic fire and bike-share modules

This commit is contained in:
Sameh Khamis committed 2026-09-11 21:57:51 -07:00
1 parent f4054469d5
commit a2d6461b52
13 files changed
+638 -213

No files matched your search

+74 -19
View File
@@ -1,4 +1,8 @@
import { isAllowedGbfsHost, isAllowedGbfsPath, gbfsCacheControl } from '../../src/data/gbfsSource.js';
import {
isAllowedGbfsHost,
isAllowedGbfsPath,
gbfsCacheControl,
} from '../../src/data/gbfsSource.js';
// ---------------------------------------------------------------------------
// GBFS (General Bikeshare Feed Specification) proxy constants
@@ -23,7 +27,10 @@ export function gbfsProxy() {
server.middlewares.use('/api/gbfs', async (req, res) => {
try {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(405, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'Method Not Allowed' }));
return;
}
@@ -31,7 +38,10 @@ export function gbfsProxy() {
const url = new URL(req.url || '/', 'http://localhost');
const encodedTarget = url.pathname.replace(/^\/+/, '');
if (!encodedTarget) {
res.writeHead(400, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(400, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'Missing GBFS upstream target' }));
return;
}
@@ -40,7 +50,10 @@ export function gbfsProxy() {
try {
decodedTarget = decodeURIComponent(encodedTarget);
} catch {
res.writeHead(400, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(400, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'Invalid GBFS target encoding' }));
return;
}
@@ -49,31 +62,53 @@ export function gbfsProxy() {
try {
upstreamUrl = new URL(decodedTarget);
} catch {
res.writeHead(400, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(400, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'Invalid GBFS upstream URL' }));
return;
}
if (upstreamUrl.protocol !== 'https:') {
res.writeHead(400, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.end(JSON.stringify({ error: 'Only https GBFS targets are allowed' }));
res.writeHead(400, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(
JSON.stringify({ error: 'Only https GBFS targets are allowed' }),
);
return;
}
if (!isAllowedGbfsHost(upstreamUrl.hostname)) {
res.writeHead(403, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(403, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'GBFS host not allowed' }));
return;
}
if (!isAllowedGbfsPath(upstreamUrl.pathname)) {
res.writeHead(400, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.end(JSON.stringify({ error: 'Only station_information/station_status endpoints are allowed' }));
res.writeHead(400, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(
JSON.stringify({
error:
'Only station_information/station_status endpoints are allowed',
}),
);
return;
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GBFS_PROXY_TIMEOUT_MS);
const timeoutId = setTimeout(
() => controller.abort(),
GBFS_PROXY_TIMEOUT_MS,
);
let upstream;
try {
upstream = await fetch(upstreamUrl.toString(), {
@@ -91,18 +126,32 @@ export function gbfsProxy() {
// Limit response size to prevent memory exhaustion from malicious upstream
const GBFS_MAX_BODY_BYTES = 5 * 1024 * 1024; // 5 MB
const contentLength = Number(upstream.headers.get('content-length'));
if (Number.isFinite(contentLength) && contentLength > GBFS_MAX_BODY_BYTES) {
res.writeHead(502, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.end(JSON.stringify({ error: 'GBFS upstream response too large' }));
if (
Number.isFinite(contentLength) &&
contentLength > GBFS_MAX_BODY_BYTES
) {
res.writeHead(502, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(
JSON.stringify({ error: 'GBFS upstream response too large' }),
);
return;
}
const body = await upstream.text();
if (Buffer.byteLength(body, 'utf8') > GBFS_MAX_BODY_BYTES) {
res.writeHead(502, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.end(JSON.stringify({ error: 'GBFS upstream response too large' }));
res.writeHead(502, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(
JSON.stringify({ error: 'GBFS upstream response too large' }),
);
return;
}
const contentType = upstream.headers.get('content-type') || 'application/json';
const contentType =
upstream.headers.get('content-type') || 'application/json';
res.writeHead(upstream.status, {
'Content-Type': contentType,
'Cache-Control': gbfsCacheControl(upstreamUrl.pathname),
@@ -112,12 +161,18 @@ export function gbfsProxy() {
res.end(body);
} catch (error) {
if (error?.name === 'AbortError') {
res.writeHead(504, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(504, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'GBFS upstream timeout' }));
return;
}
console.error('[GBFS Proxy]', error?.message || String(error));
res.writeHead(502, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
res.writeHead(502, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
});
res.end(JSON.stringify({ error: 'GBFS proxy error' }));
}
});