mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge remote-tracking branch 'upstream/main' into i18n-spanish
This commit is contained in:
commit
0b58265cee
14 files changed
+575
-6
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { readResponseTextCapped } from './common/http.js';
|
||||
import { makeRateLimiter, clientKey } from './common/rate-limit.js';
|
||||
import {
|
||||
isAllowedGbfsHost,
|
||||
isAllowedGbfsPath,
|
||||
@@ -17,6 +18,19 @@ export const GBFS_CLIENT_IDENTIFIER = 'gods-eye-view';
|
||||
|
||||
export const GBFS_MAX_BODY_BYTES = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
/**
|
||||
* Requests/minute/IP admitted by the proxy.
|
||||
*
|
||||
* The bikeshare layer's own demand is small and known: `src/layers/bikeshare/
|
||||
* registry.js` catalogues 15 systems, station information is cached per city
|
||||
* after its first fetch, and station status refreshes once per city every
|
||||
* `STATUS_POLL_MS` (60 s) with in-flight requests deduplicated. Even with every
|
||||
* catalogued system in camera range at once that is about 15 requests a minute,
|
||||
* so 120 is eight times a maximal real session while still capping a caller
|
||||
* using this proxy as an open relay against a third-party GBFS host.
|
||||
*/
|
||||
export const GBFS_RATE_PER_MIN = 120;
|
||||
|
||||
function gbfsRedirectHost(location, requestUrl) {
|
||||
if (!location) return '';
|
||||
try {
|
||||
@@ -121,7 +135,12 @@ export async function fetchGbfsUpstream(
|
||||
*
|
||||
* @returns {import('vite').Plugin}
|
||||
*/
|
||||
export function gbfsProxy() {
|
||||
export function gbfsProxy({ ratePerMinute = GBFS_RATE_PER_MIN } = {}) {
|
||||
const allow = makeRateLimiter({
|
||||
windowMs: 60_000,
|
||||
max: ratePerMinute,
|
||||
globalMax: ratePerMinute * 3,
|
||||
});
|
||||
const installMiddleware = (server) => {
|
||||
server.middlewares.use('/api/gbfs', async (req, res) => {
|
||||
try {
|
||||
@@ -134,6 +153,19 @@ export function gbfsProxy() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Frequency, checked before the target is even parsed: the allowlist
|
||||
// already says WHERE this proxy may go, nothing said HOW OFTEN. A
|
||||
// refused request performs no outbound fetch and starts no timer.
|
||||
if (!allow(clientKey(req))) {
|
||||
res.writeHead(429, {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
'Retry-After': '10',
|
||||
});
|
||||
res.end(JSON.stringify({ error: 'Rate limit exceeded' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(req.url || '/', 'http://localhost');
|
||||
const encodedTarget = url.pathname.replace(/^\/+/, '');
|
||||
if (!encodedTarget) {
|
||||
|
||||
Reference in new issue
Block a user