mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Merge branch 'main' into contrib/gods-001-coordinate-empty-components-20261011-reviewed
This commit is contained in:
commit
c7c63a00ce
4 files changed
+74
-3
No files matched your search
@@ -195,6 +195,12 @@
|
||||
largest cities) and `CCTV_VEGVESEN_VIDEO=0` keeps stills only. Attributed
|
||||
under NLOD 2.0.
|
||||
|
||||
- Live HLS cameras play on a page opened over plain HTTP from another machine
|
||||
(`HOST=0.0.0.0`). The client lease id came from `crypto.randomUUID`, which
|
||||
browsers only expose in secure contexts, so starting a live feed threw
|
||||
before playback. The id now falls back to a v4 UUID built from
|
||||
`crypto.getRandomValues`, which the server's lease check accepts.
|
||||
|
||||
## [0.2.1] — 2026-10-02 — God's Eye View through Your Agent (with security fixes and other features)
|
||||
|
||||
- Ship each bundled data pack once. The region, marine, admin-boundary,
|
||||
|
||||
@@ -5222,7 +5222,9 @@ its paint loop. Camera deactivation destroys the decoder, and fatal playback
|
||||
errors revert to the labeled frame fallback. Camera changes clear the old panel
|
||||
frame. hls.js is imported only when an HLS camera starts. Two live sessions may
|
||||
coexist with at most eight independent consumer leases each; no-access lease
|
||||
expiry is 15 seconds. Closing one consumer leaves other consumers running. Encrypted/fMP4/byte-range playlists fail
|
||||
expiry is 15 seconds. Closing one consumer leaves other consumers running. Lease
|
||||
ids use `crypto.randomUUID` where the page is a secure context and an equivalent
|
||||
v4 id from `crypto.getRandomValues` on plain-HTTP LAN pages. Encrypted/fMP4/byte-range playlists fail
|
||||
closed. Performance and live agency reliability require recorded validation.
|
||||
|
||||
## Wind forecast models
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
/**
|
||||
* A v4 UUID for the client lease. `crypto.randomUUID` only exists in secure
|
||||
* contexts, so a page opened over plain HTTP from another machine (the
|
||||
* HOST=0.0.0.0 LAN opt-in) has none; `getRandomValues` exists in every context
|
||||
* and yields the same shape the server's lease check accepts.
|
||||
*/
|
||||
export function createLeaseId(cryptoImpl = globalThis.crypto) {
|
||||
if (typeof cryptoImpl.randomUUID === 'function') {
|
||||
return cryptoImpl.randomUUID();
|
||||
}
|
||||
const bytes = cryptoImpl.getRandomValues(new Uint8Array(16));
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0'));
|
||||
return [
|
||||
hex.slice(0, 4),
|
||||
hex.slice(4, 6),
|
||||
hex.slice(6, 8),
|
||||
hex.slice(8, 10),
|
||||
hex.slice(10),
|
||||
]
|
||||
.map((group) => group.join(''))
|
||||
.join('-');
|
||||
}
|
||||
|
||||
/** One decoder per active camera; both surfaces consume this video element. */
|
||||
export function attachCctvVideo(
|
||||
video,
|
||||
@@ -7,13 +32,14 @@ export function attachCctvVideo(
|
||||
loadHls = () => import('hls.js'),
|
||||
onFailure = () => {},
|
||||
fetchImpl = globalThis.fetch,
|
||||
cryptoImpl = globalThis.crypto,
|
||||
} = {},
|
||||
) {
|
||||
// Preserve replay for existing finite video feeds; live HLS must not loop.
|
||||
video.loop = feedType !== 'hls';
|
||||
let disposed = false;
|
||||
let hls = null;
|
||||
const leaseId = feedType === 'hls' ? globalThis.crypto.randomUUID() : null;
|
||||
const leaseId = feedType === 'hls' ? createLeaseId(cryptoImpl) : null;
|
||||
const mediaUrl =
|
||||
feedType === 'hls'
|
||||
? `${url}${url.includes('?') ? '&' : '?'}lease=${leaseId}`
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { attachCctvVideo } from './videoPlayback.js';
|
||||
import { webcrypto } from 'node:crypto';
|
||||
import { attachCctvVideo, createLeaseId } from './videoPlayback.js';
|
||||
|
||||
const LEASE_V4 =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
|
||||
/** What an insecure context exposes: getRandomValues but no randomUUID. */
|
||||
const insecureCrypto = {
|
||||
getRandomValues: (array) => webcrypto.getRandomValues(array),
|
||||
};
|
||||
function video() {
|
||||
const v = new EventTarget();
|
||||
Object.assign(v, {
|
||||
@@ -69,6 +78,34 @@ test('native HLS releases its client lease without response-header access', asyn
|
||||
assert.equal(releases[0].init.method, 'DELETE');
|
||||
});
|
||||
|
||||
test('lease ids are v4 UUIDs with or without crypto.randomUUID', () => {
|
||||
assert.match(createLeaseId(), LEASE_V4);
|
||||
const ids = new Set();
|
||||
for (let i = 0; i < 64; i++) ids.add(createLeaseId(insecureCrypto));
|
||||
assert.equal(ids.size, 64);
|
||||
for (const id of ids) assert.match(id, LEASE_V4);
|
||||
});
|
||||
|
||||
test('HLS starts on a plain-HTTP LAN page without crypto.randomUUID', async () => {
|
||||
const source = video();
|
||||
source.canPlayType = () => 'probably';
|
||||
const playback = attachCctvVideo(source, '/api/cctv/media/a', 'hls', {
|
||||
loadHls: async () => ({ default: { isSupported: () => false } }),
|
||||
fetchImpl: async () => {},
|
||||
cryptoImpl: insecureCrypto,
|
||||
});
|
||||
try {
|
||||
await playback.ready;
|
||||
const lease = new URL(
|
||||
source.src,
|
||||
'http://192.168.1.10:4173/',
|
||||
).searchParams.get('lease');
|
||||
assert.match(lease, LEASE_V4);
|
||||
} finally {
|
||||
playback.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test('finite video feeds retain looping while live HLS does not loop', async () => {
|
||||
for (const feedType of ['mp4', 'webm', 'hls']) {
|
||||
const source = video();
|
||||
|
||||
Reference in new issue
Block a user