mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(cctv): bound the Range header and release abandoned camera streams (#512)
Supersedes #253 by @Maher-Reven, ported onto the current component layout with fixes from review. Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied. Co-authored-by: Maher Kurdi <[email protected]>
This commit is contained in:
1 parent
428a6d7332
commit
da700be77f
8 files changed
+779
-2
No files matched your search
@@ -10,11 +10,13 @@ import {
|
||||
fetchCctvImageFromUpstream,
|
||||
fetchTxdotSnapshot,
|
||||
fetchCctvMediaUpstream,
|
||||
watchDownstreamClose,
|
||||
} from './cctv/media.js';
|
||||
import {
|
||||
CCTV_FRAME_FETCH_TIMEOUT_MS,
|
||||
CCTV_MAX_SOURCES_CEILING,
|
||||
} from './cctv/constants.js';
|
||||
import { sanitizeCctvRangeHeader } from './cctv/range.js';
|
||||
import { googleServerApiKey } from './places/google-key.js';
|
||||
export { CCTV_FRAME_FETCH_TIMEOUT_MS, fetchCctvImageFromUpstream };
|
||||
/**
|
||||
@@ -218,15 +220,31 @@ export function cctvProxy({ sourceRoot = process.cwd() } = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bound before the request goes out: most of the wait is before any
|
||||
// header arrives, and a viewer who leaves during it must take the
|
||||
// upstream request with them.
|
||||
const downstream = watchDownstreamClose(res);
|
||||
try {
|
||||
const upstreamHeaders = {
|
||||
'User-Agent': 'gods-eye-view-cctv-proxy/1.0',
|
||||
};
|
||||
const requestRange = req.headers?.range;
|
||||
// Never forward the client's own string: a Range this proxy does
|
||||
// not accept is dropped and the request proceeds without one.
|
||||
const requestRange = sanitizeCctvRangeHeader(req.headers?.range);
|
||||
if (requestRange) upstreamHeaders.Range = requestRange;
|
||||
const upstream = await fetchCctvMediaUpstream(mediaUrl, {
|
||||
headers: upstreamHeaders,
|
||||
signal: downstream.signal,
|
||||
});
|
||||
if (downstream.closed) {
|
||||
// The headers arrived for a viewer who is no longer there.
|
||||
try {
|
||||
await upstream.body?.cancel();
|
||||
} catch {
|
||||
/* already closed */
|
||||
}
|
||||
return;
|
||||
}
|
||||
const contentType = upstream.headers.get('content-type') || '';
|
||||
if (!upstream.ok) {
|
||||
try {
|
||||
@@ -283,6 +301,11 @@ export function cctvProxy({ sourceRoot = process.cwd() } = {}) {
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
if (downstream.closed) {
|
||||
// The viewer left mid-request. That is not a camera fault and
|
||||
// there is nobody to answer.
|
||||
return;
|
||||
}
|
||||
const timedOut =
|
||||
error?.name === 'AbortError' || error?.name === 'TimeoutError';
|
||||
setHealth(cameraId, {
|
||||
|
||||
Reference in new issue
Block a user