@camstack/types 1.2.245 → 1.2.247

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.
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Addon→addon **byte transport** (D613) — the facility that used to not exist.
3
+ *
4
+ * ## What this replaces
5
+ *
6
+ * An addon could SERVE bytes ({@link AddonDataPlane}) and it could CALL another
7
+ * addon ({@link AddonContext.api}), and neither of those reaches the other
8
+ * addon's bytes:
9
+ *
10
+ * - the data-plane listener binds `127.0.0.1` behind a per-listener secret that
11
+ * **only the hub** presents, and the hub's `/addon/<id>/…` proxy in front of
12
+ * it takes a session cookie, a `cst_` token scoped `{type:'addon'}` or a JWT
13
+ * — an addon holds none of those;
14
+ * - `ctx.api.<cap>.subscribe` is refused on an addon caller by design, so a
15
+ * streaming cap method is not available either;
16
+ * - a unary cap method CAN carry base64, and that is the shape this facility
17
+ * exists to stop: the payload is held whole (~1.33×) in the producer AND in
18
+ * the caller, on a hub this repo has already OOM'd (D9/D18). Every such
19
+ * method has therefore had to carry a byte bound, and
20
+ * `videoclips.readClipBytes` refused a long `high` twin at 50 MiB because of
21
+ * it.
22
+ *
23
+ * The repo worked around the gap TWICE, identically: a cap call mints a URL
24
+ * carrying a token and the consumer dials it directly — the broker's restreams
25
+ * (`getStreamWithCodec`, dialled by the recorder and HomeKit) and the clip dial
26
+ * (`videoclips.dialClipStream`, D597). Each pair re-built an `http.createServer`,
27
+ * a token map, a TTL, a sweep, a one-shot rule, an outstanding bound, a
28
+ * backpressure gate and a consumer-side opener. This facility is that habit,
29
+ * promoted: the mechanism is the framework's, the CONSENT stays the producer's.
30
+ *
31
+ * ## The shape, and why it is this one
32
+ *
33
+ * **Nothing is reachable that was not OFFERED.** There is no
34
+ * `dial(addonId, path)`: a consumer cannot address a producer at all. A
35
+ * producer calls {@link AddonPeerBytes.offer} — usually from inside a cap
36
+ * method it already gates — and gets back a {@link PeerBytesTicket} it may hand
37
+ * to the caller. One offer is ONE body and ONE take. That is the whole
38
+ * authority model, and it is deliberately the same one the two precedents
39
+ * already use, because the alternative (the hub handing a consumer the
40
+ * producer's facility secret) would replace a per-request consent with a
41
+ * standing one, and would hand over a credential good for every prefix that
42
+ * addon serves.
43
+ *
44
+ * **The hub's secret is not touched.** This is a SECOND listener, with its own
45
+ * per-offer tokens. The property "only the hub presents the data-plane secret"
46
+ * is preserved by leaving it alone rather than by widening it.
47
+ *
48
+ * **Same host only, refused by name across a node boundary.** The listener
49
+ * binds loopback, so a ticket minted on `little-unraid` names a URL that means
50
+ * something else entirely on the hub. Every ticket therefore carries the
51
+ * {@link PeerBytesTicket.hostNodeId} it was minted on, and
52
+ * {@link AddonPeerBytes.open} refuses `cross-node` when that is not this
53
+ * process's host — it never dials, and it never silently reaches a DIFFERENT
54
+ * process that happens to hold that port. Device providers run on agents, so
55
+ * this is a real branch and not a theoretical one. Widening it means exposing
56
+ * an addon's listener on a routable interface, which is a security decision for
57
+ * the operator and not a refactor.
58
+ *
59
+ * **This is not a frame pipe.** One offer carries ONE body, taken once, with a
60
+ * TTL measured in seconds and a bound on outstanding offers. Media crosses a
61
+ * process boundary here the way D9/D18 already allow: on demand, compressed, by
62
+ * handle. A caller that minted an offer per frame would exhaust the outstanding
63
+ * bound inside a second and be refused by name.
64
+ */
65
+ import type { IncomingMessage, ServerResponse } from 'node:http';
66
+ import type { Readable } from 'node:stream';
67
+ import { z } from 'zod';
68
+ /**
69
+ * How long a minted ticket stays takeable.
70
+ *
71
+ * The consumer's dial is the next thing that happens after the cap call that
72
+ * minted it returns — measured well under 100 ms on the same host for the clip
73
+ * dial this is generalised from. Thirty seconds covers a busy hub and is short
74
+ * enough that a ticket which leaked into a log is worth nothing by the time
75
+ * anybody reads it.
76
+ */
77
+ export declare const PEER_BYTES_TICKET_TTL_MS = 30000;
78
+ /**
79
+ * Tickets minted and not yet taken or expired, per addon process.
80
+ *
81
+ * A bound, not a budget: it exists so a caller that mints and never dials
82
+ * cannot grow a map without limit, and so a per-frame misuse of this facility
83
+ * is refused by name rather than working slowly.
84
+ */
85
+ export declare const PEER_BYTES_MAX_OUTSTANDING = 64;
86
+ /**
87
+ * How long the producer waits on a consumer that has stopped reading before it
88
+ * cuts the body.
89
+ *
90
+ * D447: a raw HTTP stream reads no chunk the socket has not taken. The producer
91
+ * honours `write()`'s return and awaits `drain`; a peer that never drains is
92
+ * a peer that is gone, and holding the body for it is the unbounded buffer this
93
+ * whole facility exists to avoid.
94
+ */
95
+ export declare const PEER_BYTES_STALL_MS = 15000;
96
+ /** Bytes handed to the socket per `write()` when the producer holds a buffer. */
97
+ export declare const PEER_BYTES_CHUNK_BYTES: number;
98
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
99
+ export declare const PeerBytesTicketSchema: z.ZodObject<{
100
+ url: z.ZodString;
101
+ hostNodeId: z.ZodString;
102
+ expiresAtMs: z.ZodNumber;
103
+ declaredBytes: z.ZodNullable<z.ZodNumber>;
104
+ }, z.core.$strip>;
105
+ /**
106
+ * A one-shot handle on ONE body, on ONE host.
107
+ *
108
+ * Travels back to the consumer as the answer of the cap method that minted it,
109
+ * over the trusted hub↔addon channel — the same route `ClipStreamDialSchema`
110
+ * and the restream URLs already travel. It is a bearer credential with a
111
+ * seconds-long life: never persist one, never log the `url`.
112
+ *
113
+ * One shape, one definition: the wire schema IS the type (`z.infer`), so a cap
114
+ * that carries a ticket and the facility that mints one cannot drift.
115
+ */
116
+ export type PeerBytesTicket = z.infer<typeof PeerBytesTicketSchema>;
117
+ export type PeerBytesOfferRefusalCode =
118
+ /** {@link PEER_BYTES_MAX_OUTSTANDING} tickets are already outstanding. */
119
+ 'offer-exhausted'
120
+ /** The loopback listener could not bind. */
121
+ | 'listener-failed';
122
+ export type PeerBytesOfferResult = {
123
+ readonly kind: 'ticket';
124
+ readonly ticket: PeerBytesTicket;
125
+ } | {
126
+ readonly kind: 'refused';
127
+ readonly code: PeerBytesOfferRefusalCode;
128
+ readonly detail: string;
129
+ };
130
+ /** What the producer is publishing, and what to call it in the log. */
131
+ export interface PeerBytesOfferOptions {
132
+ /**
133
+ * The body, as a real Node handler — `res.writeHead(...)` then the producer's
134
+ * own writes. Use it when the producer streams something it is generating;
135
+ * for the two common shapes prefer {@link AddonPeerBytes.offerBuffer} and
136
+ * {@link AddonPeerBytes.offerFile}, which already honour `write()`'s return,
137
+ * cut a peer that never drains and log every exit.
138
+ */
139
+ readonly handler: PeerBytesHandler;
140
+ /** What this body IS, for the log line. Not a filename; a kind. */
141
+ readonly label: string;
142
+ /** The camera this body belongs to. Every log line about a device carries it. */
143
+ readonly deviceId?: number;
144
+ /** See {@link PeerBytesTicket.declaredBytes}. */
145
+ readonly declaredBytes?: number;
146
+ readonly ttlMs?: number;
147
+ }
148
+ export type PeerBytesHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
149
+ /** A buffer the producer already holds, written out in bounded chunks. */
150
+ export interface PeerBytesBufferOffer {
151
+ readonly bytes: Buffer;
152
+ readonly contentType: string;
153
+ readonly label: string;
154
+ readonly deviceId?: number;
155
+ readonly ttlMs?: number;
156
+ }
157
+ /** A file on the producer's own disk, streamed — nothing is read into memory. */
158
+ export interface PeerBytesFileOffer {
159
+ readonly path: string;
160
+ readonly contentType: string;
161
+ readonly label: string;
162
+ readonly deviceId?: number;
163
+ readonly ttlMs?: number;
164
+ }
165
+ export type PeerBytesOpenRefusalCode =
166
+ /** The ticket was minted on another HOST; loopback does not cross it. */
167
+ 'cross-node'
168
+ /** Its TTL ran out before the dial. */
169
+ | 'ticket-expired'
170
+ /** Not a URL this facility mints. Nothing was dialled. */
171
+ | 'ticket-malformed'
172
+ /** The dial itself failed — no listener, connection refused, socket error. */
173
+ | 'unreachable'
174
+ /** The producer answered, and its answer was not a body (`dial-used`, 404, 5xx). */
175
+ | 'producer-refused';
176
+ export type PeerBytesOpenResult = {
177
+ readonly kind: 'body';
178
+ readonly body: PeerBytesBody;
179
+ } | {
180
+ readonly kind: 'refused';
181
+ readonly code: PeerBytesOpenRefusalCode;
182
+ readonly detail: string;
183
+ /** The producer's HTTP status, when there was one. */
184
+ readonly status?: number;
185
+ };
186
+ /**
187
+ * A taken body.
188
+ *
189
+ * `stream` is handed over **paused**, and that is load-bearing rather than
190
+ * tidy. Node flushes the body bytes it already holds on `process.nextTick` the
191
+ * moment a `data` listener attaches, and that tick runs BEFORE any promise
192
+ * continuation the consumer was going to use to get ready — which is exactly
193
+ * how the broker's first dial threw away the head of every stream (`ftyp`,
194
+ * `moov`) while fourteen unit tests stayed green, because the fake opener only
195
+ * ever delivered after the `await` (D597). Here the facility pauses the
196
+ * response before it resolves, so a consumer that takes a tick to get ready
197
+ * cannot lose the head.
198
+ */
199
+ export interface PeerBytesBody {
200
+ readonly status: number;
201
+ readonly contentType: string | null;
202
+ /** The producer's `content-length`, or `null` when it declared none (D393). */
203
+ readonly declaredBytes: number | null;
204
+ /** PAUSED. Nothing flows until the consumer reads it. */
205
+ readonly stream: Readable;
206
+ /**
207
+ * Drain the whole body into `path`, honouring backpressure, under a bound.
208
+ *
209
+ * Over the bound the transfer is ABORTED and the partial file removed — half
210
+ * a video is worse than an honest refusal, and this repo has shipped a
211
+ * 262-byte MP4 to a phone once already.
212
+ */
213
+ writeToFile(path: string, options: PeerBytesWriteOptions): Promise<PeerBytesWriteResult>;
214
+ /** Give up on the body. Idempotent; safe after it has ended. */
215
+ cancel(reason: string): void;
216
+ }
217
+ export interface PeerBytesWriteOptions {
218
+ /** Hard ceiling. A body that exceeds it is aborted, never truncated. */
219
+ readonly maxBytes: number;
220
+ }
221
+ export type PeerBytesWriteResult = {
222
+ readonly kind: 'written';
223
+ readonly bytes: number;
224
+ } | {
225
+ readonly kind: 'refused';
226
+ readonly code: 'too-large' | 'transfer-failed' | 'write-failed';
227
+ readonly detail: string;
228
+ /** How much had arrived when it stopped. */
229
+ readonly bytes: number;
230
+ };
231
+ /**
232
+ * The facility. Injected by the kernel; absent when the addon is loaded in
233
+ * isolation (tests, tooling), which is why `AddonContext.peerBytes` is optional.
234
+ */
235
+ export interface AddonPeerBytes {
236
+ /** This process's HOST node — the hub or a named agent, never a runner id. */
237
+ readonly hostNodeId: string;
238
+ /** Tickets minted and not yet taken or expired. */
239
+ readonly outstanding: number;
240
+ offer(options: PeerBytesOfferOptions): Promise<PeerBytesOfferResult>;
241
+ offerBuffer(options: PeerBytesBufferOffer): Promise<PeerBytesOfferResult>;
242
+ offerFile(options: PeerBytesFileOffer): Promise<PeerBytesOfferResult>;
243
+ open(ticket: PeerBytesTicket): Promise<PeerBytesOpenResult>;
244
+ }
@@ -3,6 +3,7 @@ import type { CapabilityDefinition, InferProvider } from '../capabilities/capabi
3
3
  import type { CustomActionCaller, CustomActionsSpec } from '../capabilities/custom-actions.js';
4
4
  import type { PipelineSlot } from '../types/pipeline.js';
5
5
  import type { AddonDataPlane } from './addon-data-plane.js';
6
+ import type { AddonPeerBytes } from './addon-peer-bytes.js';
6
7
  import type { CapabilityDeclaration } from './capability.js';
7
8
  import type { ConfigUISchemaWithValues, FieldProbeResult } from './config-ui.js';
8
9
  import type { IEventBus, ReadinessScope } from './event-bus.js';
@@ -656,6 +657,24 @@ export interface AddonContext<TConfig = Record<string, unknown>> {
656
657
  * tooling), which is why the field is optional.
657
658
  */
658
659
  readonly dataPlane?: AddonDataPlane;
660
+ /**
661
+ * Addon→addon **byte transport** (D613) — how a consumer addon gets another
662
+ * addon's bytes without either of them holding the payload whole.
663
+ *
664
+ * `dataPlane` lets an addon serve the HUB; this lets it serve a SIBLING. The
665
+ * producer offers one body and gets a one-shot ticket it hands back through
666
+ * the cap method it already gates; the consumer opens the ticket and streams.
667
+ * Same host only — a ticket from another node is refused `cross-node` by
668
+ * name, never dialled. See {@link AddonPeerBytes}.
669
+ *
670
+ * Use it instead of a base64 field on a unary cap method. That shape holds
671
+ * the payload whole (~1.33×) in the producer AND in the caller and is why
672
+ * every such method has had to carry a byte bound (D9/D18).
673
+ *
674
+ * Injected by the kernel; absent when the addon is loaded in isolation
675
+ * (tests, tooling), which is why the field is optional.
676
+ */
677
+ readonly peerBytes?: AddonPeerBytes;
659
678
  /**
660
679
  * Runtime accessor for the capability registry — exposed to addons that
661
680
  * need to enumerate peers of a collection cap (e.g. `turn-provider`) or
@@ -11,7 +11,7 @@
11
11
  * component's original options — detection to the detection-pipeline wrapper
12
12
  * binding, audio analysis to its own, recording to `RecordingConfig.enabled`
13
13
  * (which was always first-class; the switch was a veneer over
14
- * `recording.setDeviceConfig`), notifications to a notification-center
14
+ * `recordingArchive.setDeviceConfig`), notifications to a notification-center
15
15
  * per-device setting, the two camera planes to their own components.
16
16
  *
17
17
  * What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
@@ -37,7 +37,7 @@
37
37
  * | `stream-broker` | `deviceManager.setDisabled` | `StreamBrokerManager.reconcileAllCatalogs` releases the brokers; `ensureBroker` refuses re-creation |
38
38
  * | `object-detection` | `deviceManager.setWrapperActive('detection-pipeline')` | `PipelineSettingsStore.resolvePipelineForDevice` returns `{ steps: [], audio: null }` |
39
39
  * | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
40
- * | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
40
+ * | `recording` | `recordingArchive.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
41
41
  * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
42
42
  * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
43
43
  * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
@@ -183,7 +183,6 @@ export interface CapabilityProviderMap {
183
183
  'zone-analytics': InferProvider<typeof import('../capabilities/zone-analytics.cap.js').zoneAnalyticsCapability>;
184
184
  'audio-metrics': InferProvider<typeof import('../capabilities/audio-metrics.cap.js').audioMetricsCapability>;
185
185
  motion: InferProvider<typeof import('../capabilities/motion.cap.js').motionCapability>;
186
- events: InferProvider<typeof import('../capabilities/events.cap.js').eventsCapability>;
187
186
  'native-object-detection': InferProvider<typeof import('../capabilities/native-object-detection.cap.js').nativeObjectDetectionCapability>;
188
187
  'device-ops': InferProvider<typeof import('../capabilities/device-ops.cap.js').deviceOpsCapability>;
189
188
  system: InferProvider<typeof import('../capabilities/system.cap.js').systemCapability>;
@@ -1349,6 +1349,56 @@ var DATAPLANE_SECRET_HEADER = "x-camstack-dataplane-secret";
1349
1349
  */
1350
1350
  var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
1351
1351
  //#endregion
1352
+ //#region src/interfaces/addon-peer-bytes.ts
1353
+ /**
1354
+ * How long a minted ticket stays takeable.
1355
+ *
1356
+ * The consumer's dial is the next thing that happens after the cap call that
1357
+ * minted it returns — measured well under 100 ms on the same host for the clip
1358
+ * dial this is generalised from. Thirty seconds covers a busy hub and is short
1359
+ * enough that a ticket which leaked into a log is worth nothing by the time
1360
+ * anybody reads it.
1361
+ */
1362
+ var PEER_BYTES_TICKET_TTL_MS = 3e4;
1363
+ /**
1364
+ * Tickets minted and not yet taken or expired, per addon process.
1365
+ *
1366
+ * A bound, not a budget: it exists so a caller that mints and never dials
1367
+ * cannot grow a map without limit, and so a per-frame misuse of this facility
1368
+ * is refused by name rather than working slowly.
1369
+ */
1370
+ var PEER_BYTES_MAX_OUTSTANDING = 64;
1371
+ /**
1372
+ * How long the producer waits on a consumer that has stopped reading before it
1373
+ * cuts the body.
1374
+ *
1375
+ * D447: a raw HTTP stream reads no chunk the socket has not taken. The producer
1376
+ * honours `write()`'s return and awaits `drain`; a peer that never drains is
1377
+ * a peer that is gone, and holding the body for it is the unbounded buffer this
1378
+ * whole facility exists to avoid.
1379
+ */
1380
+ var PEER_BYTES_STALL_MS = 15e3;
1381
+ /** Bytes handed to the socket per `write()` when the producer holds a buffer. */
1382
+ var PEER_BYTES_CHUNK_BYTES = 256 * 1024;
1383
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
1384
+ var PeerBytesTicketSchema = zod.z.object({
1385
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
1386
+ url: zod.z.string().min(1),
1387
+ /**
1388
+ * The HOST node this URL means something on — the hub or a named agent,
1389
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
1390
+ * refuses `cross-node` by name when they differ, without dialling.
1391
+ */
1392
+ hostNodeId: zod.z.string().min(1),
1393
+ expiresAtMs: zod.z.number().int().nonnegative(),
1394
+ /**
1395
+ * What the producer DECLARED the body to be, when it knows — `null` when it
1396
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
1397
+ * must be able to tell "the producer did not say" from "the body is empty".
1398
+ */
1399
+ declaredBytes: zod.z.number().int().nonnegative().nullable()
1400
+ });
1401
+ //#endregion
1352
1402
  //#region src/capabilities/schemas/streaming-shared.ts
1353
1403
  /** Shared Zod schemas used across streaming capabilities. */
1354
1404
  var CamProfileSchema = zod.z.enum([
@@ -3581,11 +3631,6 @@ function createDeviceProxy(api, binding, opts) {
3581
3631
  },
3582
3632
  enumSensor: { getStatus: (input) => dispatch("enum-sensor", "enumSensor", "getStatus", "query", input) },
3583
3633
  eventEmitter: { getStatus: (input) => dispatch("event-emitter", "eventEmitter", "getStatus", "query", input) },
3584
- events: {
3585
- getEvents: (input) => dispatch("events", "events", "getEvents", "query", input),
3586
- getEventThumbnail: (input) => dispatch("events", "events", "getEventThumbnail", "query", input),
3587
- getEventClipUrl: (input) => dispatch("events", "events", "getEventClipUrl", "query", input)
3588
- },
3589
3634
  fanControl: {
3590
3635
  setPercentage: (input) => dispatch("fan-control", "fanControl", "setPercentage", "mutation", input),
3591
3636
  setPreset: (input) => dispatch("fan-control", "fanControl", "setPreset", "mutation", input),
@@ -3837,6 +3882,13 @@ function createDeviceProxy(api, binding, opts) {
3837
3882
  setSettings: (input) => dispatch("ptz-autotrack", "ptzAutotrack", "setSettings", "mutation", input)
3838
3883
  },
3839
3884
  reboot: { reboot: (input) => dispatch("reboot", "reboot", "reboot", "mutation", input) },
3885
+ recording: {
3886
+ listSources: (input) => dispatch("recording", "recording", "listSources", "query", input),
3887
+ getAvailability: (input) => dispatch("recording", "recording", "getAvailability", "query", input),
3888
+ getDaysWithRecordings: (input) => dispatch("recording", "recording", "getDaysWithRecordings", "query", input),
3889
+ getPlayback: (input) => dispatch("recording", "recording", "getPlayback", "query", input),
3890
+ getPlaybackOptions: (input) => dispatch("recording", "recording", "getPlaybackOptions", "query", input)
3891
+ },
3840
3892
  recordingOnboard: {
3841
3893
  getOptions: (input) => dispatch("recording-onboard", "recordingOnboard", "getOptions", "query", input),
3842
3894
  setSettings: (input) => dispatch("recording-onboard", "recordingOnboard", "setSettings", "mutation", input),
@@ -3912,7 +3964,9 @@ function createDeviceProxy(api, binding, opts) {
3912
3964
  listSources: (input) => dispatch("videoclips", "videoclips", "listSources", "query", input),
3913
3965
  getClipPlayback: (input) => dispatch("videoclips", "videoclips", "getClipPlayback", "query", input),
3914
3966
  readClipBytes: (input) => dispatch("videoclips", "videoclips", "readClipBytes", "query", input),
3915
- dialClipStream: (input) => dispatch("videoclips", "videoclips", "dialClipStream", "query", input)
3967
+ offerClipBytes: (input) => dispatch("videoclips", "videoclips", "offerClipBytes", "query", input),
3968
+ dialClipStream: (input) => dispatch("videoclips", "videoclips", "dialClipStream", "query", input),
3969
+ getPlaybackOptions: (input) => dispatch("videoclips", "videoclips", "getPlaybackOptions", "query", input)
3916
3970
  },
3917
3971
  waterHeater: {
3918
3972
  setTargetTemp: (input) => dispatch("water-heater", "waterHeater", "setTargetTemp", "mutation", input),
@@ -4072,25 +4126,22 @@ function createDeviceProxy(api, binding, opts) {
4072
4126
  listPlates: (input) => dispatchSystem("plateGallery", "listPlates", "query", input),
4073
4127
  getPlateByTrack: (input) => dispatchSystem("plateGallery", "getPlateByTrack", "query", input)
4074
4128
  },
4075
- recording: {
4076
- getAvailability: (input) => dispatchSystem("recording", "getAvailability", "query", input),
4077
- getDaysWithRecordings: (input) => dispatchSystem("recording", "getDaysWithRecordings", "query", input),
4078
- getPlaybackManifest: (input) => dispatchSystem("recording", "getPlaybackManifest", "query", input),
4079
- getDeviceConfig: (input) => dispatchSystem("recording", "getDeviceConfig", "query", input),
4080
- locateSegment: (input) => dispatchSystem("recording", "locateSegment", "query", input),
4081
- readSegmentBytes: (input) => dispatchSystem("recording", "readSegmentBytes", "query", input),
4082
- readGopBytes: (input) => dispatchSystem("recording", "readGopBytes", "query", input),
4083
- readWindowBytes: (input) => dispatchSystem("recording", "readWindowBytes", "query", input),
4084
- setDeviceConfig: (input) => dispatchSystem("recording", "setDeviceConfig", "mutation", input),
4085
- rescanStorage: (input) => dispatchSystem("recording", "rescanStorage", "mutation", input),
4086
- pruneFootage: (input) => dispatchSystem("recording", "pruneFootage", "mutation", input),
4087
- deleteFootprint: (input) => dispatchSystem("recording", "deleteFootprint", "mutation", input),
4088
- renderGif: (input) => dispatchSystem("recording", "renderGif", "mutation", input),
4089
- renderClip: (input) => dispatchSystem("recording", "renderClip", "mutation", input),
4090
- getStatus: (input) => dispatchSystem("recording", "getStatus", "query", input),
4091
- getDeviceSettingsContribution: (input) => dispatchSystem("recording", "getDeviceSettingsContribution", "query", input),
4092
- getDeviceLiveContribution: (input) => dispatchSystem("recording", "getDeviceLiveContribution", "query", input),
4093
- applyDeviceSettingsPatch: (input) => dispatchSystem("recording", "applyDeviceSettingsPatch", "mutation", input)
4129
+ recordingArchive: {
4130
+ getDeviceConfig: (input) => dispatchSystem("recordingArchive", "getDeviceConfig", "query", input),
4131
+ locateSegment: (input) => dispatchSystem("recordingArchive", "locateSegment", "query", input),
4132
+ readSegmentBytes: (input) => dispatchSystem("recordingArchive", "readSegmentBytes", "query", input),
4133
+ readGopBytes: (input) => dispatchSystem("recordingArchive", "readGopBytes", "query", input),
4134
+ readWindowBytes: (input) => dispatchSystem("recordingArchive", "readWindowBytes", "query", input),
4135
+ setDeviceConfig: (input) => dispatchSystem("recordingArchive", "setDeviceConfig", "mutation", input),
4136
+ rescanStorage: (input) => dispatchSystem("recordingArchive", "rescanStorage", "mutation", input),
4137
+ pruneFootage: (input) => dispatchSystem("recordingArchive", "pruneFootage", "mutation", input),
4138
+ deleteFootprint: (input) => dispatchSystem("recordingArchive", "deleteFootprint", "mutation", input),
4139
+ renderGif: (input) => dispatchSystem("recordingArchive", "renderGif", "mutation", input),
4140
+ renderClip: (input) => dispatchSystem("recordingArchive", "renderClip", "mutation", input),
4141
+ getStatus: (input) => dispatchSystem("recordingArchive", "getStatus", "query", input),
4142
+ getDeviceSettingsContribution: (input) => dispatchSystem("recordingArchive", "getDeviceSettingsContribution", "query", input),
4143
+ getDeviceLiveContribution: (input) => dispatchSystem("recordingArchive", "getDeviceLiveContribution", "query", input),
4144
+ applyDeviceSettingsPatch: (input) => dispatchSystem("recordingArchive", "applyDeviceSettingsPatch", "mutation", input)
4094
4145
  },
4095
4146
  recordingExport: { listExports: (input) => dispatchSystem("recordingExport", "listExports", "query", input) },
4096
4147
  streamBroker: {
@@ -4155,7 +4206,6 @@ var DEVICE_SCOPED_CAPS = new Set([
4155
4206
  "doorbell",
4156
4207
  "enum-sensor",
4157
4208
  "event-emitter",
4158
- "events",
4159
4209
  "fan-control",
4160
4210
  "feature-probe",
4161
4211
  "flood",
@@ -4187,6 +4237,7 @@ var DEVICE_SCOPED_CAPS = new Set([
4187
4237
  "ptz",
4188
4238
  "ptz-autotrack",
4189
4239
  "reboot",
4240
+ "recording",
4190
4241
  "recording-onboard",
4191
4242
  "recording-signal",
4192
4243
  "scene-monitor",
@@ -4547,12 +4598,42 @@ Object.defineProperty(exports, "FrameHandleSchema", {
4547
4598
  return FrameHandleSchema;
4548
4599
  }
4549
4600
  });
4601
+ Object.defineProperty(exports, "PEER_BYTES_CHUNK_BYTES", {
4602
+ enumerable: true,
4603
+ get: function() {
4604
+ return PEER_BYTES_CHUNK_BYTES;
4605
+ }
4606
+ });
4607
+ Object.defineProperty(exports, "PEER_BYTES_MAX_OUTSTANDING", {
4608
+ enumerable: true,
4609
+ get: function() {
4610
+ return PEER_BYTES_MAX_OUTSTANDING;
4611
+ }
4612
+ });
4613
+ Object.defineProperty(exports, "PEER_BYTES_STALL_MS", {
4614
+ enumerable: true,
4615
+ get: function() {
4616
+ return PEER_BYTES_STALL_MS;
4617
+ }
4618
+ });
4619
+ Object.defineProperty(exports, "PEER_BYTES_TICKET_TTL_MS", {
4620
+ enumerable: true,
4621
+ get: function() {
4622
+ return PEER_BYTES_TICKET_TTL_MS;
4623
+ }
4624
+ });
4550
4625
  Object.defineProperty(exports, "PROVIDER_PIN_FIELD", {
4551
4626
  enumerable: true,
4552
4627
  get: function() {
4553
4628
  return PROVIDER_PIN_FIELD;
4554
4629
  }
4555
4630
  });
4631
+ Object.defineProperty(exports, "PeerBytesTicketSchema", {
4632
+ enumerable: true,
4633
+ get: function() {
4634
+ return PeerBytesTicketSchema;
4635
+ }
4636
+ });
4556
4637
  Object.defineProperty(exports, "ProfileRtspEntrySchema", {
4557
4638
  enumerable: true,
4558
4639
  get: function() {