mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
commit
702ecc38e2
8 files changed
+154
-7
No files matched your search
@@ -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();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseCoordinateQuery } from './coordinateParser.js';
|
||||
test('search refuses omitted components and repeated separators', () => {
|
||||
for (const q of ['30,, -97', ',30,-97', '30,-97,', '30;; -97', '30,; -97'])
|
||||
assert.equal(parseCoordinateQuery(q), null, q);
|
||||
for (const q of ['30,-97', '30; -97', '30 -97', 'N 30, W 97'])
|
||||
assert.deepEqual(
|
||||
[parseCoordinateQuery(q).lat, parseCoordinateQuery(q).lon],
|
||||
[30, -97],
|
||||
);
|
||||
});
|
||||
@@ -49,8 +49,8 @@ function splitComponents(query) {
|
||||
const parts = /[,;]/.test(text)
|
||||
? text.split(/[,;]/)
|
||||
: text.split(/\s+(?=[+-]?[.\d]|[NSEW]\s*[+-]?[.\d])/i);
|
||||
const trimmed = parts.map((part) => part.trim()).filter(Boolean);
|
||||
return trimmed.length === 2 ? trimmed : null;
|
||||
const trimmed = parts.map((part) => part.trim());
|
||||
return trimmed.length === 2 && trimmed.every(Boolean) ? trimmed : null;
|
||||
}
|
||||
|
||||
/** Present a parsed pair the way a coordinate is normally written. */
|
||||
|
||||
@@ -704,6 +704,51 @@ test('narrow-screen rails pin every hosted panel glow inside its panel box', ()
|
||||
}
|
||||
});
|
||||
|
||||
test('right layout ignores a CSS-hidden expanded panel when deciding exclusivity', () => {
|
||||
const f = fixture('right');
|
||||
const weather = element('weather-panel', { height: 300 });
|
||||
weather.computed.display = 'none';
|
||||
f.stack.children.push(weather);
|
||||
weather.parentElement = f.stack;
|
||||
|
||||
f.run();
|
||||
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), false);
|
||||
assert.equal(f.stack.dataset.expandedCount, '0');
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
|
||||
assert.equal(f.second.getAttribute('aria-hidden'), undefined);
|
||||
assert.equal(
|
||||
weather.style.getPropertyValue('--right-panel-allocated-height'),
|
||||
'',
|
||||
);
|
||||
});
|
||||
|
||||
test('right layout still restores panels that exclusive presentation hides', () => {
|
||||
const f = fixture('right');
|
||||
// Model layers.css: an exclusive rail hides its collapsed panels.
|
||||
f.options.windowRef.getComputedStyle = (node) => ({
|
||||
...node.computed,
|
||||
display:
|
||||
f.stack.classList.contains('layout-exclusive') &&
|
||||
node.classList.contains('collapsed')
|
||||
? 'none'
|
||||
: node.computed.display,
|
||||
});
|
||||
f.expand(f.first, 250);
|
||||
f.second.classList.add('layout-auto-collapsed');
|
||||
f.stack.classList.add('layout-exclusive');
|
||||
|
||||
f.run();
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), true);
|
||||
assert.equal(f.second.getAttribute('aria-hidden'), 'true');
|
||||
|
||||
f.options.hud.visible = false;
|
||||
f.run();
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
assert.equal(f.second.classList.contains('layout-auto-collapsed'), false);
|
||||
assert.deepEqual(f.collapsed, ['second']);
|
||||
});
|
||||
|
||||
test('right layout ignores a hidden panel: no lane, no gap, no auto-collapse', () => {
|
||||
const f = fixture('right');
|
||||
const imagery = element('recent-imagery-panel', { height: 0 });
|
||||
|
||||
@@ -69,10 +69,17 @@ export function layoutRightPanelRail({
|
||||
// A collapse may schedule one follow-up, which only measures and allocates.
|
||||
const isCollapseRetry = pendingCollapseRetries.delete(stack);
|
||||
|
||||
// A floating (lifted-out) panel is not laid out by the rail.
|
||||
// A floating (lifted-out) panel is not laid out by the rail. Neither is one
|
||||
// that other CSS hides, such as an event view. Exclusive presentation hides
|
||||
// collapsed panels itself; those stay in the rail so they can be restored.
|
||||
const hiddenByRail = (panel) =>
|
||||
stack.classList.contains('layout-exclusive') &&
|
||||
panel.classList.contains('collapsed');
|
||||
const panels = [...stack.children].filter(
|
||||
(panel) =>
|
||||
panel.matches('[data-panel-id]:not(.panel-floating)') && !panel.hidden,
|
||||
panel.matches('[data-panel-id]:not(.panel-floating)') &&
|
||||
!panel.hidden &&
|
||||
(hiddenByRail(panel) || getComputedStyle(panel).display !== 'none'),
|
||||
);
|
||||
if (!hud.visible || hud.variant !== 'tactical') {
|
||||
for (const panel of panels.filter((item) =>
|
||||
|
||||
Reference in new issue
Block a user