feat: publish Alta Local Camera Discovery
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const { resolveAppAssetPath } = require('../src/app-protocol');
|
||||
|
||||
const staticDir = path.join(__dirname, '..', 'static');
|
||||
|
||||
test('app protocol resolves only explicitly packaged renderer assets', () => {
|
||||
assert.equal(resolveAppAssetPath('app://bundle/index.html', staticDir), path.join(staticDir, 'index.html'));
|
||||
assert.equal(resolveAppAssetPath('app://bundle/styles.css?v=12', staticDir), path.join(staticDir, 'styles.css'));
|
||||
assert.equal(resolveAppAssetPath('app://bundle/alta-mark.png', staticDir), path.join(staticDir, 'alta-mark.png'));
|
||||
assert.equal(resolveAppAssetPath('app://other/index.html', staticDir), null);
|
||||
assert.equal(resolveAppAssetPath('app://bundle/../main.js', staticDir), null);
|
||||
assert.equal(resolveAppAssetPath('app://bundle/%2e%2e/main.js', staticDir), null);
|
||||
assert.equal(resolveAppAssetPath('file:///etc/passwd', staticDir), null);
|
||||
assert.equal(resolveAppAssetPath('app://bundle/not-packaged.js', staticDir), null);
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const {
|
||||
altaKeyFromName,
|
||||
cameraFromService,
|
||||
isLocalAddress,
|
||||
normalizeResults,
|
||||
scanCameras,
|
||||
} = require('../src/discovery');
|
||||
|
||||
const validService = (overrides = {}) => ({
|
||||
name: 'ABH7-C62U-GC3E',
|
||||
type: 'vaioncam',
|
||||
protocol: 'tcp',
|
||||
host: 'avacam-abh7-c62u-gc3e.local.',
|
||||
addresses: ['fe80::1', '10.0.0.223'],
|
||||
port: 443,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
test('extracts an Alta Key from service names', () => {
|
||||
assert.equal(altaKeyFromName('abh7-c62u-gc3e'), 'ABH7-C62U-GC3E');
|
||||
assert.equal(altaKeyFromName('ABH7-C62U-GC3E._vaioncam._tcp.local'), 'ABH7-C62U-GC3E');
|
||||
assert.equal(altaKeyFromName('not-a-key'), null);
|
||||
});
|
||||
|
||||
test('projects a correlated local Bonjour service into a safe camera row', () => {
|
||||
assert.deepEqual(cameraFromService(validService()), {
|
||||
alta_key: 'ABH7-C62U-GC3E',
|
||||
hostname: 'avacam-abh7-c62u-gc3e.local',
|
||||
web_ui_url: 'https://avacam-abh7-c62u-gc3e.local',
|
||||
ip_address: '10.0.0.223',
|
||||
port: 443,
|
||||
service: '_vaioncam._tcp.local',
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects uncorrelated or non-local service announcements', () => {
|
||||
assert.equal(cameraFromService(validService({ type: undefined })), null);
|
||||
assert.equal(cameraFromService(validService({ protocol: undefined })), null);
|
||||
assert.equal(cameraFromService(validService({ host: 'avacam-attacker.local.' })), null);
|
||||
assert.equal(cameraFromService(validService({ port: 8443 })), null);
|
||||
assert.equal(cameraFromService(validService({ addresses: ['203.0.113.10'] })), null);
|
||||
assert.equal(cameraFromService(validService({ addresses: [] })), null);
|
||||
});
|
||||
|
||||
test('recognizes supported private and link-local IP scopes', () => {
|
||||
for (const address of ['10.0.0.1', '172.16.0.1', '172.31.255.254', '192.168.1.1', '169.254.1.1', 'fe80::1', 'fc00::1', 'fd12::1']) {
|
||||
assert.equal(isLocalAddress(address), true, address);
|
||||
}
|
||||
for (const address of ['8.8.8.8', '172.32.0.1', '2001:4860:4860::8888', 'not-an-ip']) {
|
||||
assert.equal(isLocalAddress(address), false, address);
|
||||
}
|
||||
});
|
||||
|
||||
test('deduplicates and sorts cameras', () => {
|
||||
const a = { alta_key: 'AHG2-Z4ND-FVRN', ip_address: '10.0.0.192' };
|
||||
const b = { alta_key: 'ABH7-C62U-GC3E', ip_address: '10.0.0.223' };
|
||||
assert.deepEqual(normalizeResults([a, b, a]), [b, a]);
|
||||
});
|
||||
|
||||
test('scans through the Bonjour adapter and closes resources', async () => {
|
||||
let browserStopped = false;
|
||||
let bonjourDestroyed = false;
|
||||
class FakeBonjour {
|
||||
find(query) {
|
||||
assert.deepEqual(query, { type: 'vaioncam', protocol: 'tcp' });
|
||||
const browser = new EventEmitter();
|
||||
browser.stop = () => { browserStopped = true; };
|
||||
process.nextTick(() => browser.emit('up', validService()));
|
||||
return browser;
|
||||
}
|
||||
destroy() { bonjourDestroyed = true; }
|
||||
}
|
||||
const rows = await scanCameras({ durationMs: 5, BonjourClass: FakeBonjour });
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0].alta_key, 'ABH7-C62U-GC3E');
|
||||
assert.equal(browserStopped, true);
|
||||
assert.equal(bonjourDestroyed, true);
|
||||
});
|
||||
|
||||
test('caps scan results and closes discovery immediately at the cap', async () => {
|
||||
let browserStopped = false;
|
||||
class FakeBonjour {
|
||||
find() {
|
||||
const browser = new EventEmitter();
|
||||
browser.stop = () => { browserStopped = true; };
|
||||
process.nextTick(() => {
|
||||
for (let index = 0; index < 10; index += 1) {
|
||||
const key = `AAAA-BBBB-${String(index).padStart(4, '0')}`;
|
||||
browser.emit('up', validService({
|
||||
name: key,
|
||||
host: `avacam-${key.toLowerCase()}.local.`,
|
||||
addresses: [`10.0.0.${index + 1}`],
|
||||
}));
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
}
|
||||
destroy() {}
|
||||
}
|
||||
const rows = await scanCameras({ durationMs: 1000, maxResults: 3, BonjourClass: FakeBonjour });
|
||||
assert.equal(rows.length, 3);
|
||||
assert.equal(browserStopped, true);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
|
||||
test('Electron boundary stays isolated and packaged files are declared', () => {
|
||||
const main = fs.readFileSync(path.join(root, 'main.js'), 'utf8');
|
||||
const preload = fs.readFileSync(path.join(root, 'preload.js'), 'utf8');
|
||||
const appProtocol = fs.readFileSync(path.join(root, 'src', 'app-protocol.js'), 'utf8');
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||
assert.match(main, /nodeIntegration:\s*false/);
|
||||
assert.match(main, /contextIsolation:\s*true/);
|
||||
assert.match(main, /sandbox:\s*true/);
|
||||
assert.match(main, /isTrustedSender/);
|
||||
assert.match(main, /setPermissionCheckHandler/);
|
||||
assert.match(main, /setPermissionRequestHandler/);
|
||||
assert.match(main, /registerSchemesAsPrivileged/);
|
||||
assert.match(appProtocol, /app:\/\/bundle/);
|
||||
assert.doesNotMatch(main, /\.loadFile\s*\(/);
|
||||
assert.doesNotMatch(preload, /require\(['"](?:node:)?(?:fs|child_process|dgram)/);
|
||||
assert.match(preload, /exportCsv:\s*\(\)\s*=>\s*ipcRenderer\.invoke\('export-csv'\)/);
|
||||
assert.ok(pkg.build.files.includes('src/**/*'));
|
||||
assert.ok(pkg.build.files.includes('static/**/*'));
|
||||
assert.equal(pkg.build.win.icon, 'assets/icon.ico');
|
||||
assert.equal(pkg.build.electronFuses.runAsNode, false);
|
||||
assert.equal(pkg.build.electronFuses.enableNodeOptionsEnvironmentVariable, false);
|
||||
assert.equal(pkg.build.electronFuses.enableNodeCliInspectArguments, false);
|
||||
assert.equal(pkg.build.electronFuses.enableEmbeddedAsarIntegrityValidation, true);
|
||||
assert.equal(pkg.build.electronFuses.onlyLoadAppFromAsar, true);
|
||||
assert.equal(pkg.build.electronFuses.grantFileProtocolExtraPrivileges, false);
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { createScanSession } = require('../src/scan-session');
|
||||
|
||||
const camera = {
|
||||
alta_key: 'ABH7-C62U-GC3E',
|
||||
hostname: 'avacam-abh7-c62u-gc3e.local',
|
||||
web_ui_url: 'https://avacam-abh7-c62u-gc3e.local',
|
||||
ip_address: '10.0.0.223',
|
||||
port: 443,
|
||||
service: '_vaioncam._tcp.local',
|
||||
};
|
||||
|
||||
test('shares one scan, publishes defensive state, and binds camera URLs', async () => {
|
||||
let scanCalls = 0;
|
||||
let releaseScan;
|
||||
const scan = () => {
|
||||
scanCalls += 1;
|
||||
return new Promise((resolve) => { releaseScan = resolve; });
|
||||
};
|
||||
const times = [1000, 2345];
|
||||
const session = createScanSession({ scan, now: () => times.shift() });
|
||||
|
||||
const first = session.scanOnce();
|
||||
const second = session.scanOnce();
|
||||
assert.strictEqual(first, second);
|
||||
assert.equal(scanCalls, 1);
|
||||
assert.equal(session.allowedCameraUrl(camera.web_ui_url), null);
|
||||
assert.deepEqual(session.getLatestCameras(), []);
|
||||
|
||||
releaseScan([camera]);
|
||||
assert.deepEqual(await first, {
|
||||
ok: true,
|
||||
count: 1,
|
||||
duration_seconds: 1.3,
|
||||
cameras: [camera],
|
||||
});
|
||||
assert.equal(session.allowedCameraUrl(camera.web_ui_url), camera.web_ui_url);
|
||||
assert.equal(session.allowedCameraUrl(`${camera.web_ui_url}/?secret=value`), null);
|
||||
assert.equal(session.allowedCameraUrl('https://avacam-ahg2-z4nd-fvrn.local'), null);
|
||||
|
||||
const exposed = session.getLatestCameras();
|
||||
exposed[0].alta_key = 'MUTATED';
|
||||
assert.equal(session.getLatestCameras()[0].alta_key, camera.alta_key);
|
||||
});
|
||||
|
||||
test('revokes prior state at rescan and permits retry after failure', async () => {
|
||||
let call = 0;
|
||||
let releaseSecondScan;
|
||||
const scan = () => {
|
||||
call += 1;
|
||||
if (call === 1) return Promise.resolve([camera]);
|
||||
if (call === 2) return new Promise((_resolve, reject) => { releaseSecondScan = reject; });
|
||||
return Promise.resolve([camera]);
|
||||
};
|
||||
const session = createScanSession({ scan, now: () => 0 });
|
||||
|
||||
await session.scanOnce();
|
||||
assert.equal(session.allowedCameraUrl(camera.web_ui_url), camera.web_ui_url);
|
||||
|
||||
const failed = session.scanOnce();
|
||||
assert.equal(session.allowedCameraUrl(camera.web_ui_url), null);
|
||||
assert.deepEqual(session.getLatestCameras(), []);
|
||||
releaseSecondScan(new Error('network failed'));
|
||||
await assert.rejects(failed, /network failed/);
|
||||
|
||||
const retried = await session.scanOnce();
|
||||
assert.equal(call, 3);
|
||||
assert.equal(retried.count, 1);
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { isCameraUrl, normalizeCameraUrl } = require('../src/url-policy');
|
||||
|
||||
const cameraUrl = 'https://avacam-abh7-c62u-gc3e.local';
|
||||
|
||||
test('permits only root local Alta camera HTTPS URLs', () => {
|
||||
assert.equal(isCameraUrl(cameraUrl), true);
|
||||
assert.equal(isCameraUrl('http://avacam-abh7-c62u-gc3e.local'), false);
|
||||
assert.equal(isCameraUrl('https://example.com'), false);
|
||||
assert.equal(isCameraUrl('https://user:pass@avacam-abh7-c62u-gc3e.local'), false);
|
||||
assert.equal(isCameraUrl('https://avacam-abh7-c62u-gc3e.local:8443'), false);
|
||||
assert.equal(isCameraUrl(`${cameraUrl}/anything`), false);
|
||||
assert.equal(isCameraUrl(`${cameraUrl}/?secret=value`), false);
|
||||
assert.equal(isCameraUrl(`${cameraUrl}/#fragment`), false);
|
||||
});
|
||||
|
||||
test('normalizes camera URLs to their canonical form', () => {
|
||||
assert.equal(normalizeCameraUrl(`${cameraUrl}/`), cameraUrl);
|
||||
assert.equal(normalizeCameraUrl(`${cameraUrl.toUpperCase()}/`), cameraUrl);
|
||||
});
|
||||
Reference in New Issue
Block a user