mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(street-level): camera ownership, mesh refresh, panel CSP (Sameh's review)
- Photo framing and FOLLOW claim the camera through the application's
navigation, injected with attachNavigation({ run, subscribeHandoff }):
aircraft and satellite tracking is released first, the cockpit refuses,
and FOLLOW stops when another feature takes the camera. No UI imports
in the layer.
- The mesh sampler follows the mesh floor's rules: it waits for the
visible tileset to finish streaming (visibleTilesetLoaded, now exposed
by the mesh floor service), needs a real bare-earth prior and keeps to
the shared meshFloorSampleWithinPrior window, which cameraFollow now
uses too. A cell is probed again once the camera is half as far from
it (down to 40 m), so a coarse early sample is replaced and redrawn.
- The MCP globe panel's CSP allows the Mapillary Graph API and its image
CDN (https://*.xx.fbcdn.net).
- The tile proxy reads bodies with the shared readResponseBytesCapped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
630a1abdae
commit
1fa129ea8e
13 files changed
+459
-79
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { readResponseBytesCapped } from '../common/http.js';
|
||||
import { stripTileLayers } from './trim.js';
|
||||
import {
|
||||
MAPILLARY_TILE_HOST,
|
||||
@@ -350,35 +351,20 @@ async function fetchUpstream(address, signal) {
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const declared = Number(response.headers.get('content-length'));
|
||||
if (Number.isFinite(declared) && declared > TILE_MAX_BYTES) {
|
||||
await response.body?.cancel().catch(() => {});
|
||||
// Chunked or compressed bodies have no Content-Length: the cap applies as
|
||||
// the body streams, and an oversized one also aborts the request.
|
||||
try {
|
||||
const bytes = await readResponseBytesCapped(
|
||||
response,
|
||||
TILE_MAX_BYTES,
|
||||
signal,
|
||||
);
|
||||
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
} catch (error) {
|
||||
if (error?.code !== 'RESPONSE_TOO_LARGE') throw error;
|
||||
oversize.abort();
|
||||
throw new TileUpstreamError(502, 'Mapillary tile exceeds size cap');
|
||||
}
|
||||
return readCapped(response, () => oversize.abort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a body, aborting once it passes TILE_MAX_BYTES: chunked or compressed
|
||||
* responses have no Content-Length to check first.
|
||||
*/
|
||||
async function readCapped(response, abortRequest) {
|
||||
if (!response.body) return Buffer.alloc(0);
|
||||
const reader = response.body.getReader();
|
||||
const chunks = [];
|
||||
let total = 0;
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
total += value.byteLength;
|
||||
if (total > TILE_MAX_BYTES) {
|
||||
abortRequest();
|
||||
await reader.cancel().catch(() => {});
|
||||
throw new TileUpstreamError(502, 'Mapillary tile exceeds size cap');
|
||||
}
|
||||
chunks.push(value);
|
||||
}
|
||||
return Buffer.concat(chunks, total);
|
||||
}
|
||||
|
||||
/** @type {{active: number, queue: Array<() => void>}} upstream slots */
|
||||
|
||||
Reference in new issue
Block a user