mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
style: format terrain traffic fire and bike-share modules
This commit is contained in:
1 parent
f4054469d5
commit
a2d6461b52
13 files changed
+638
-213
No files matched your search
+74
-19
@@ -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' }));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user