@camstack/types 1.2.50 → 1.2.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/automation/codegen.d.ts +39 -0
- package/dist/automation/index.d.ts +14 -0
- package/dist/automation/recipe.d.ts +194 -0
- package/dist/capabilities/core-blocks.cap.d.ts +17 -0
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/mqtt-broker.cap.d.ts +1 -1
- package/dist/capabilities/pipeline-analytics.cap.d.ts +6 -0
- package/dist/capabilities/snapshot.cap.d.ts +56 -0
- package/dist/device/declared-device.d.ts +22 -1
- package/dist/generated/addon-api.d.ts +7 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8493 -7756
- package/dist/index.mjs +8482 -7757
- package/dist/node.d.ts +2 -0
- package/dist/node.js +57 -0
- package/dist/node.mjs +57 -2
- package/dist/{sleep-7WqNZVcL.mjs → sleep-CT4lN1ij.mjs} +1 -0
- package/dist/{sleep-ocMLM2o5.js → sleep-DfF6vKCf.js} +1 -0
- package/dist/utils/expiring-url-signature.d.ts +27 -0
- package/package.json +1 -1
package/dist/node.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { ensureFfmpeg, getFfmpegDownloadUrl } from './deps/ffmpeg-downloader.js'
|
|
|
4
4
|
export { ensurePython, installPythonPackages, installPythonRequirements, getPythonDownloadUrl, PYTHON_VERSION, } from './deps/python-downloader.js';
|
|
5
5
|
export { FilesystemStorageProvider } from './storage/filesystem-storage-provider.js';
|
|
6
6
|
export { canonicalHash } from './utils/canonical-hash.js';
|
|
7
|
+
export { signExpiringUrl, verifyExpiringUrl } from './utils/expiring-url-signature.js';
|
|
8
|
+
export type { VerifyExpiringUrlInput } from './utils/expiring-url-signature.js';
|
|
7
9
|
export { canonicalDeviceFingerprint, diffExportTargets, resolveExportFingerprint, } from './utils/export-reconciler.js';
|
|
8
10
|
export type { DeviceExportShape, ExportDelta, ExportTargetEntry, } from './utils/export-reconciler.js';
|
|
9
11
|
export { FfmpegProcess } from './ffmpeg/process.js';
|
package/dist/node.js
CHANGED
|
@@ -601,6 +601,61 @@ var FilesystemStorageProvider = class {
|
|
|
601
601
|
}
|
|
602
602
|
};
|
|
603
603
|
//#endregion
|
|
604
|
+
//#region src/utils/expiring-url-signature.ts
|
|
605
|
+
/**
|
|
606
|
+
* The ONE derivation of "a signed, expiring URL".
|
|
607
|
+
*
|
|
608
|
+
* This repo mints unguessable, self-expiring links in three places now — the
|
|
609
|
+
* notification artifact plane, the Home Assistant media plane, and the snapshot
|
|
610
|
+
* link plane. The first two grew independently and are byte-identical logic
|
|
611
|
+
* (`hmac(secret, "<id>:<exp>")`, expiry checked before a constant-time compare),
|
|
612
|
+
* each restating the crypto locally because **addons never import each other**.
|
|
613
|
+
*
|
|
614
|
+
* That reason is real, and the conclusion drawn from it was wrong. Two copies of
|
|
615
|
+
* a signing scheme is how one of them quietly ends up with a different TTL, a
|
|
616
|
+
* different compare, or a missing expiry check, and nothing fails until a link
|
|
617
|
+
* that should have died keeps working. The fix is the same one D52 applies to
|
|
618
|
+
* crop geometry: one derivation, in a place every addon may depend on. A
|
|
619
|
+
* framework package is exactly that place — `@camstack/types/node`, off the root
|
|
620
|
+
* entry because `node:crypto` must never be traversed by a browser bundler.
|
|
621
|
+
*
|
|
622
|
+
* What this module deliberately does NOT decide: the TTL, the base URL, the
|
|
623
|
+
* shape of `id`, and the access level of the route. Those are per-plane policy
|
|
624
|
+
* and each caller states them where a reader can see them.
|
|
625
|
+
*/
|
|
626
|
+
/**
|
|
627
|
+
* The signature over `(id, expMs)`.
|
|
628
|
+
*
|
|
629
|
+
* `id` is whatever the plane uses to name the thing being served — an artifact
|
|
630
|
+
* id, a track id, a `<deviceId>:<width>` pair. It is joined with `:` so a caller
|
|
631
|
+
* must not put a `:` inside a field whose boundary matters; where a plane has
|
|
632
|
+
* more than one field, it composes them itself and owns that ambiguity.
|
|
633
|
+
*/
|
|
634
|
+
function signExpiringUrl(secret, id, expMs) {
|
|
635
|
+
return (0, node_crypto.createHmac)("sha256", secret).update(`${id}:${String(expMs)}`).digest("hex");
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Verify a request's `(id, exp, sig)`.
|
|
639
|
+
*
|
|
640
|
+
* **Expiry is checked BEFORE the compare**, so an expired link is refused
|
|
641
|
+
* whether or not its signature is valid — a leaked URL stops working on its own
|
|
642
|
+
* and cannot be kept alive by holding a correct signature. The compare itself is
|
|
643
|
+
* constant-time so a public route cannot be probed for the signature byte by
|
|
644
|
+
* byte.
|
|
645
|
+
*/
|
|
646
|
+
function verifyExpiringUrl(input) {
|
|
647
|
+
const { secret, id, exp, sig, nowMs } = input;
|
|
648
|
+
if (exp === void 0 || sig === void 0) return false;
|
|
649
|
+
const expMs = typeof exp === "number" ? exp : Number(exp);
|
|
650
|
+
if (!Number.isFinite(expMs)) return false;
|
|
651
|
+
if (expMs <= nowMs) return false;
|
|
652
|
+
const expected = signExpiringUrl(secret, id, expMs);
|
|
653
|
+
const a = Buffer.from(expected, "utf8");
|
|
654
|
+
const b = Buffer.from(sig, "utf8");
|
|
655
|
+
if (a.length !== b.length) return false;
|
|
656
|
+
return (0, node_crypto.timingSafeEqual)(a, b);
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
604
659
|
//#region src/utils/export-reconciler.ts
|
|
605
660
|
/**
|
|
606
661
|
* Compute the stable 64-char lowercase-hex fingerprint of a device's
|
|
@@ -1446,3 +1501,5 @@ exports.getPythonDownloadUrl = getPythonDownloadUrl;
|
|
|
1446
1501
|
exports.installPythonPackages = installPythonPackages;
|
|
1447
1502
|
exports.installPythonRequirements = installPythonRequirements;
|
|
1448
1503
|
exports.resolveExportFingerprint = resolveExportFingerprint;
|
|
1504
|
+
exports.signExpiringUrl = signExpiringUrl;
|
|
1505
|
+
exports.verifyExpiringUrl = verifyExpiringUrl;
|
package/dist/node.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as Fmp4BoxSplitter, o as buildFfmpegArgs, t as canonicalHash, u as isSoftwareDecode } from "./canonical-hash-rO1sRmEK.mjs";
|
|
2
2
|
import { t as errMsg } from "./err-msg-IQTHeDzc.mjs";
|
|
3
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
@@ -578,6 +578,61 @@ var FilesystemStorageProvider = class {
|
|
|
578
578
|
}
|
|
579
579
|
};
|
|
580
580
|
//#endregion
|
|
581
|
+
//#region src/utils/expiring-url-signature.ts
|
|
582
|
+
/**
|
|
583
|
+
* The ONE derivation of "a signed, expiring URL".
|
|
584
|
+
*
|
|
585
|
+
* This repo mints unguessable, self-expiring links in three places now — the
|
|
586
|
+
* notification artifact plane, the Home Assistant media plane, and the snapshot
|
|
587
|
+
* link plane. The first two grew independently and are byte-identical logic
|
|
588
|
+
* (`hmac(secret, "<id>:<exp>")`, expiry checked before a constant-time compare),
|
|
589
|
+
* each restating the crypto locally because **addons never import each other**.
|
|
590
|
+
*
|
|
591
|
+
* That reason is real, and the conclusion drawn from it was wrong. Two copies of
|
|
592
|
+
* a signing scheme is how one of them quietly ends up with a different TTL, a
|
|
593
|
+
* different compare, or a missing expiry check, and nothing fails until a link
|
|
594
|
+
* that should have died keeps working. The fix is the same one D52 applies to
|
|
595
|
+
* crop geometry: one derivation, in a place every addon may depend on. A
|
|
596
|
+
* framework package is exactly that place — `@camstack/types/node`, off the root
|
|
597
|
+
* entry because `node:crypto` must never be traversed by a browser bundler.
|
|
598
|
+
*
|
|
599
|
+
* What this module deliberately does NOT decide: the TTL, the base URL, the
|
|
600
|
+
* shape of `id`, and the access level of the route. Those are per-plane policy
|
|
601
|
+
* and each caller states them where a reader can see them.
|
|
602
|
+
*/
|
|
603
|
+
/**
|
|
604
|
+
* The signature over `(id, expMs)`.
|
|
605
|
+
*
|
|
606
|
+
* `id` is whatever the plane uses to name the thing being served — an artifact
|
|
607
|
+
* id, a track id, a `<deviceId>:<width>` pair. It is joined with `:` so a caller
|
|
608
|
+
* must not put a `:` inside a field whose boundary matters; where a plane has
|
|
609
|
+
* more than one field, it composes them itself and owns that ambiguity.
|
|
610
|
+
*/
|
|
611
|
+
function signExpiringUrl(secret, id, expMs) {
|
|
612
|
+
return createHmac("sha256", secret).update(`${id}:${String(expMs)}`).digest("hex");
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Verify a request's `(id, exp, sig)`.
|
|
616
|
+
*
|
|
617
|
+
* **Expiry is checked BEFORE the compare**, so an expired link is refused
|
|
618
|
+
* whether or not its signature is valid — a leaked URL stops working on its own
|
|
619
|
+
* and cannot be kept alive by holding a correct signature. The compare itself is
|
|
620
|
+
* constant-time so a public route cannot be probed for the signature byte by
|
|
621
|
+
* byte.
|
|
622
|
+
*/
|
|
623
|
+
function verifyExpiringUrl(input) {
|
|
624
|
+
const { secret, id, exp, sig, nowMs } = input;
|
|
625
|
+
if (exp === void 0 || sig === void 0) return false;
|
|
626
|
+
const expMs = typeof exp === "number" ? exp : Number(exp);
|
|
627
|
+
if (!Number.isFinite(expMs)) return false;
|
|
628
|
+
if (expMs <= nowMs) return false;
|
|
629
|
+
const expected = signExpiringUrl(secret, id, expMs);
|
|
630
|
+
const a = Buffer.from(expected, "utf8");
|
|
631
|
+
const b = Buffer.from(sig, "utf8");
|
|
632
|
+
if (a.length !== b.length) return false;
|
|
633
|
+
return timingSafeEqual(a, b);
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
581
636
|
//#region src/utils/export-reconciler.ts
|
|
582
637
|
/**
|
|
583
638
|
* Compute the stable 64-char lowercase-hex fingerprint of a device's
|
|
@@ -1403,4 +1458,4 @@ var Fmp4FragmentChild = class {
|
|
|
1403
1458
|
}
|
|
1404
1459
|
};
|
|
1405
1460
|
//#endregion
|
|
1406
|
-
export { FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, resolveExportFingerprint };
|
|
1461
|
+
export { FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, resolveExportFingerprint, signExpiringUrl, verifyExpiringUrl };
|
|
@@ -3279,6 +3279,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3279
3279
|
getSnapshot: (input) => dispatch("snapshot", "snapshot", "getSnapshot", "query", input),
|
|
3280
3280
|
invalidateCache: (input) => dispatch("snapshot", "snapshot", "invalidateCache", "mutation", input),
|
|
3281
3281
|
getSnapshotOverview: (input) => dispatch("snapshot", "snapshot", "getSnapshotOverview", "query", input),
|
|
3282
|
+
getSnapshotLinks: (input) => dispatch("snapshot", "snapshot", "getSnapshotLinks", "query", input),
|
|
3282
3283
|
getStatus: (input) => dispatch("snapshot", "snapshot", "getStatus", "query", input),
|
|
3283
3284
|
getDeviceSettingsContribution: (input) => dispatch("snapshot", "snapshot", "getDeviceSettingsContribution", "query", input),
|
|
3284
3285
|
getDeviceLiveContribution: (input) => dispatch("snapshot", "snapshot", "getDeviceLiveContribution", "query", input),
|
|
@@ -3279,6 +3279,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3279
3279
|
getSnapshot: (input) => dispatch("snapshot", "snapshot", "getSnapshot", "query", input),
|
|
3280
3280
|
invalidateCache: (input) => dispatch("snapshot", "snapshot", "invalidateCache", "mutation", input),
|
|
3281
3281
|
getSnapshotOverview: (input) => dispatch("snapshot", "snapshot", "getSnapshotOverview", "query", input),
|
|
3282
|
+
getSnapshotLinks: (input) => dispatch("snapshot", "snapshot", "getSnapshotLinks", "query", input),
|
|
3282
3283
|
getStatus: (input) => dispatch("snapshot", "snapshot", "getStatus", "query", input),
|
|
3283
3284
|
getDeviceSettingsContribution: (input) => dispatch("snapshot", "snapshot", "getDeviceSettingsContribution", "query", input),
|
|
3284
3285
|
getDeviceLiveContribution: (input) => dispatch("snapshot", "snapshot", "getDeviceLiveContribution", "query", input),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signature over `(id, expMs)`.
|
|
3
|
+
*
|
|
4
|
+
* `id` is whatever the plane uses to name the thing being served — an artifact
|
|
5
|
+
* id, a track id, a `<deviceId>:<width>` pair. It is joined with `:` so a caller
|
|
6
|
+
* must not put a `:` inside a field whose boundary matters; where a plane has
|
|
7
|
+
* more than one field, it composes them itself and owns that ambiguity.
|
|
8
|
+
*/
|
|
9
|
+
export declare function signExpiringUrl(secret: string, id: string, expMs: number): string;
|
|
10
|
+
export interface VerifyExpiringUrlInput {
|
|
11
|
+
readonly secret: string;
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/** Raw query value — a string off the wire, or already a number. */
|
|
14
|
+
readonly exp: string | number | undefined;
|
|
15
|
+
readonly sig: string | undefined;
|
|
16
|
+
readonly nowMs: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Verify a request's `(id, exp, sig)`.
|
|
20
|
+
*
|
|
21
|
+
* **Expiry is checked BEFORE the compare**, so an expired link is refused
|
|
22
|
+
* whether or not its signature is valid — a leaked URL stops working on its own
|
|
23
|
+
* and cannot be kept alive by holding a correct signature. The compare itself is
|
|
24
|
+
* constant-time so a public route cannot be probed for the signature byte by
|
|
25
|
+
* byte.
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyExpiringUrl(input: VerifyExpiringUrlInput): boolean;
|