@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.
- package/dist/addon.d.ts +2 -0
- package/dist/addon.js +8 -3
- package/dist/addon.mjs +5 -4
- package/dist/capabilities/index.d.ts +22 -21
- package/dist/capabilities/recording-archive.cap.d.ts +1123 -0
- package/dist/capabilities/recording.cap.d.ts +268 -1022
- package/dist/capabilities/reserved-collection-sources.d.ts +46 -0
- package/dist/capabilities/videoclips.cap.d.ts +340 -11
- package/dist/generated/addon-api.d.ts +117 -110
- package/dist/generated/cap-status-types.d.ts +3 -3
- package/dist/generated/capability-router-map.d.ts +7 -7
- package/dist/generated/collection-array-methods.d.ts +1 -1
- package/dist/generated/device-proxy.d.ts +4 -4
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +2 -2
- package/dist/generated/system-proxy.d.ts +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2874 -2166
- package/dist/index.mjs +2853 -2165
- package/dist/interfaces/addon-peer-bytes.d.ts +244 -0
- package/dist/interfaces/addon.d.ts +19 -0
- package/dist/interfaces/camera-switches.d.ts +2 -2
- package/dist/interfaces/capability.d.ts +0 -1
- package/dist/{sleep-Da9UCU5_.js → sleep-9gM_dS-5.js} +107 -26
- package/dist/{sleep-i3eUVc-d.mjs → sleep-PEo0-Fz9.mjs} +78 -27
- package/package.json +1 -1
- package/dist/capabilities/events.cap.d.ts +0 -47
|
@@ -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
|
-
* `
|
|
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` | `
|
|
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
|
-
|
|
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
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
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() {
|