@camera.ui/sdk 0.0.2 → 0.0.4
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/LICENSE.md +1 -1
- package/README.md +3 -3
- package/dist/camera/events.js +2 -0
- package/dist/camera/index.js +3 -1
- package/dist/external.js +7 -0
- package/dist/index.d.ts +7132 -4248
- package/dist/index.js +3 -11
- package/dist/internal/contract-validators.js +21 -0
- package/dist/internal/index.d.ts +915 -0
- package/dist/internal/index.js +9 -0
- package/dist/internal/sensor-triggers.js +2 -0
- package/dist/internal/shared-utils.js +86 -0
- package/dist/internal/streaming-internal.js +1 -0
- package/dist/manager/index.js +1 -1
- package/dist/observable/index.js +419 -0
- package/dist/plugin/api.js +21 -0
- package/dist/plugin/contract.js +101 -114
- package/dist/plugin/helper.js +277 -0
- package/dist/plugin/index.js +4 -1
- package/dist/plugin/interfaces.js +51 -1
- package/dist/plugin/notifier.js +23 -0
- package/dist/plugin/oauth.js +1 -0
- package/dist/sensor/audio.js +103 -81
- package/dist/sensor/base.js +350 -318
- package/dist/sensor/battery.js +73 -59
- package/dist/sensor/classifier.js +117 -0
- package/dist/sensor/clip.js +30 -0
- package/dist/sensor/contact.js +37 -18
- package/dist/sensor/detection.js +4 -0
- package/dist/sensor/doorbell.js +52 -38
- package/dist/sensor/face.js +71 -86
- package/dist/sensor/garage.js +121 -0
- package/dist/sensor/humidity.js +52 -0
- package/dist/sensor/index.js +17 -11
- package/dist/sensor/leak.js +52 -0
- package/dist/sensor/licensePlate.js +70 -79
- package/dist/sensor/light.js +82 -38
- package/dist/sensor/lock.js +99 -0
- package/dist/sensor/motion.js +85 -70
- package/dist/sensor/object.js +73 -94
- package/dist/sensor/occupancy.js +52 -0
- package/dist/sensor/ptz.js +114 -100
- package/dist/sensor/securitySystem.js +98 -0
- package/dist/sensor/siren.js +75 -43
- package/dist/sensor/smoke.js +52 -0
- package/dist/sensor/spec.js +1 -0
- package/dist/sensor/switch.js +72 -0
- package/dist/sensor/temperature.js +52 -0
- package/dist/storage/index.js +1 -2
- package/dist/types.js +1 -0
- package/docs/.vitepress/config.ts +77 -0
- package/docs/.vitepress/theme/index.ts +5 -0
- package/docs/.vitepress/theme/style.css +117 -0
- package/docs/index.md +16 -0
- package/docs/logo.png +0 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/favicon-16.ico +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/logo.svg +1 -0
- package/examples/README.md +7 -0
- package/examples/getting-started.md +535 -0
- package/package.json +36 -23
- package/scripts/build-example-docs.mjs +62 -0
- package/tsconfig.node.json +3 -2
- package/typedoc.json +42 -0
- package/dist/sensor/guards.js +0 -133
- package/dist/sensor/types.js +0 -46
- package/dist/service/base.js +0 -96
- package/dist/service/index.js +0 -3
- /package/dist/camera/{types.js → enums.js} +0 -0
- /package/dist/{manager/types.js → camera/frames.js} +0 -0
- /package/dist/{plugin/types.js → internal/camera-config-internal.js} +0 -0
- /package/dist/{service/services.js → internal/camera-enums.js} +0 -0
- /package/dist/{service/types.js → internal/camera-wire.js} +0 -0
- /package/dist/{storage/schema.js → internal/manager-rpc.js} +0 -0
- /package/dist/{storage/storages.js → internal/sensor-rpc.js} +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './contract-validators.js';
|
|
2
|
+
export * from './manager-rpc.js';
|
|
3
|
+
export * from './shared-utils.js';
|
|
4
|
+
export * from './sensor-rpc.js';
|
|
5
|
+
export * from './camera-wire.js';
|
|
6
|
+
export * from './sensor-triggers.js';
|
|
7
|
+
export * from './streaming-internal.js';
|
|
8
|
+
export * from './camera-enums.js';
|
|
9
|
+
export * from './camera-config-internal.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep equality check for arbitrary values.
|
|
3
|
+
*
|
|
4
|
+
* Recursively compares primitives, arrays, and plain objects. Object
|
|
5
|
+
* comparison ignores property declaration order (only key/value pairs
|
|
6
|
+
* matter). Set `ignoreOrder` to `true` to compare arrays as multisets,
|
|
7
|
+
* i.e. ignoring element order.
|
|
8
|
+
*
|
|
9
|
+
* Typically used for property-change detection on sensors: a property
|
|
10
|
+
* update is only emitted when the new value is not deeply equal to the
|
|
11
|
+
* previous value, which avoids redundant events for unchanged data.
|
|
12
|
+
*
|
|
13
|
+
* @param first - First value to compare.
|
|
14
|
+
*
|
|
15
|
+
* @param second - Second value to compare.
|
|
16
|
+
*
|
|
17
|
+
* @param ignoreOrder - If `true`, arrays are compared ignoring element order.
|
|
18
|
+
*
|
|
19
|
+
* @returns `true` if the values are deeply equal.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { isEqual } from '@camera.ui/sdk/internal';
|
|
24
|
+
*
|
|
25
|
+
* isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true
|
|
26
|
+
* isEqual([1, 2, 3], [3, 2, 1], true); // true (ignoreOrder)
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function isEqual(first, second, ignoreOrder = false) {
|
|
30
|
+
// Same reference or both primitive and equal
|
|
31
|
+
if (first === second) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
// Handle null/undefined
|
|
35
|
+
if (first === null || first === undefined || second === null || second === undefined) {
|
|
36
|
+
return first === second;
|
|
37
|
+
}
|
|
38
|
+
// Different types
|
|
39
|
+
const firstType = first.constructor?.name;
|
|
40
|
+
const secondType = second.constructor?.name;
|
|
41
|
+
if (firstType !== secondType) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Array comparison
|
|
45
|
+
if (Array.isArray(first) && Array.isArray(second)) {
|
|
46
|
+
if (first.length !== second.length) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (ignoreOrder) {
|
|
50
|
+
const secondCopy = [...second];
|
|
51
|
+
return first.every((item) => {
|
|
52
|
+
const index = secondCopy.findIndex((secondItem) => isEqual(item, secondItem, ignoreOrder));
|
|
53
|
+
if (index === -1)
|
|
54
|
+
return false;
|
|
55
|
+
secondCopy.splice(index, 1);
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
for (let i = 0; i < first.length; i++) {
|
|
61
|
+
if (!isEqual(first[i], second[i], ignoreOrder)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Object comparison
|
|
69
|
+
if (firstType === 'Object' && secondType === 'Object') {
|
|
70
|
+
const firstObj = first;
|
|
71
|
+
const secondObj = second;
|
|
72
|
+
const fKeys = Object.keys(firstObj);
|
|
73
|
+
const sKeys = Object.keys(secondObj);
|
|
74
|
+
if (fKeys.length !== sKeys.length) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
for (const key of fKeys) {
|
|
78
|
+
if (!isEqual(firstObj[key], secondObj[key], ignoreOrder)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// Primitive comparison (fallback)
|
|
85
|
+
return first === second;
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/manager/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight reactive primitives for camera.ui.
|
|
3
|
+
*
|
|
4
|
+
* Provides cold Observables, multicast Subjects (Subject, BehaviorSubject,
|
|
5
|
+
* ReplaySubject) and a small set of composable operators for building
|
|
6
|
+
* property-change notifications and event streams throughout the SDK.
|
|
7
|
+
*/
|
|
8
|
+
// ── Disposable ───────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Subscription handle returned by `subscribe()`.
|
|
11
|
+
*
|
|
12
|
+
* Call `dispose()` (or its alias `unsubscribe()`) to detach the listener
|
|
13
|
+
* and run any teardown logic registered by the producer. Disposing twice
|
|
14
|
+
* is a no-op.
|
|
15
|
+
*/
|
|
16
|
+
export class Disposable {
|
|
17
|
+
#closed = false;
|
|
18
|
+
#teardown;
|
|
19
|
+
constructor(teardown) {
|
|
20
|
+
this.#teardown = teardown;
|
|
21
|
+
}
|
|
22
|
+
get closed() {
|
|
23
|
+
return this.#closed;
|
|
24
|
+
}
|
|
25
|
+
dispose() {
|
|
26
|
+
if (this.#closed)
|
|
27
|
+
return;
|
|
28
|
+
this.#closed = true;
|
|
29
|
+
this.#teardown();
|
|
30
|
+
}
|
|
31
|
+
unsubscribe() {
|
|
32
|
+
this.dispose();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ── Observable ───────────────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Cold producer of a push-based value stream.
|
|
38
|
+
*
|
|
39
|
+
* The producer function passed to the constructor is executed once per
|
|
40
|
+
* `subscribe()` call, so each subscriber gets its own independent run.
|
|
41
|
+
* `subscribe()` returns a {@link Disposable} that stops the stream and
|
|
42
|
+
* triggers any teardown registered by the producer.
|
|
43
|
+
*/
|
|
44
|
+
export class Observable {
|
|
45
|
+
/** @internal */
|
|
46
|
+
_subscribe;
|
|
47
|
+
constructor(subscribe) {
|
|
48
|
+
this._subscribe = subscribe;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Start the producer for this subscriber and route emitted values to `callback`.
|
|
52
|
+
*
|
|
53
|
+
* @param callback - Receiver invoked once per emitted value.
|
|
54
|
+
*
|
|
55
|
+
* @returns Disposable that stops the stream and runs producer teardown.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const sub = sensor.onPropertyChanged.subscribe((change) => {
|
|
60
|
+
* console.log(change.property, change.value);
|
|
61
|
+
* });
|
|
62
|
+
* // later
|
|
63
|
+
* sub.dispose();
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
subscribe(callback) {
|
|
67
|
+
return this._subscribe(callback);
|
|
68
|
+
}
|
|
69
|
+
pipe(...operators) {
|
|
70
|
+
let result = this;
|
|
71
|
+
for (const op of operators) {
|
|
72
|
+
result = op(result);
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ── Subject ──────────────────────────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Multicast value source.
|
|
80
|
+
*
|
|
81
|
+
* Calls to `next(value)` are dispatched synchronously to every active
|
|
82
|
+
* subscriber. `complete()` releases all subscribers and locks the
|
|
83
|
+
* Subject so further `next()` calls become no-ops. `subscribe()`
|
|
84
|
+
* returns a {@link Disposable} for individual cleanup.
|
|
85
|
+
*/
|
|
86
|
+
export class Subject {
|
|
87
|
+
#subscribers = new Set();
|
|
88
|
+
#completed = false;
|
|
89
|
+
get closed() {
|
|
90
|
+
return this.#completed;
|
|
91
|
+
}
|
|
92
|
+
next(value) {
|
|
93
|
+
if (this.#completed)
|
|
94
|
+
return;
|
|
95
|
+
for (const cb of this.#subscribers) {
|
|
96
|
+
cb(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
complete() {
|
|
100
|
+
if (this.#completed)
|
|
101
|
+
return;
|
|
102
|
+
this.#completed = true;
|
|
103
|
+
this.#subscribers.clear();
|
|
104
|
+
}
|
|
105
|
+
subscribe(callback) {
|
|
106
|
+
if (this.#completed) {
|
|
107
|
+
return new Disposable(() => { });
|
|
108
|
+
}
|
|
109
|
+
this.#subscribers.add(callback);
|
|
110
|
+
return new Disposable(() => {
|
|
111
|
+
this.#subscribers.delete(callback);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
pipe(...operators) {
|
|
115
|
+
let result = this.asObservable();
|
|
116
|
+
for (const op of operators) {
|
|
117
|
+
result = op(result);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns a read-only Observable that mirrors this Subject without
|
|
123
|
+
* exposing `next()` or `complete()`. Useful for handing out a public
|
|
124
|
+
* stream while keeping write access internal.
|
|
125
|
+
*
|
|
126
|
+
* @returns Read-only Observable view of this Subject.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const subject = new Subject<number>();
|
|
131
|
+
* export const events$ = subject.asObservable();
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
asObservable() {
|
|
135
|
+
return new Observable((cb) => this.subscribe(cb));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── BehaviorSubject ──────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Subject seeded with an initial value that always remembers the latest
|
|
141
|
+
* emission. New subscribers receive the current value immediately on
|
|
142
|
+
* `subscribe()` and then all subsequent values. The current value is
|
|
143
|
+
* also accessible synchronously via `value` and `getValue()`.
|
|
144
|
+
*/
|
|
145
|
+
export class BehaviorSubject extends Subject {
|
|
146
|
+
#value;
|
|
147
|
+
constructor(initialValue) {
|
|
148
|
+
super();
|
|
149
|
+
this.#value = initialValue;
|
|
150
|
+
}
|
|
151
|
+
next(value) {
|
|
152
|
+
this.#value = value;
|
|
153
|
+
super.next(value);
|
|
154
|
+
}
|
|
155
|
+
getValue() {
|
|
156
|
+
return this.#value;
|
|
157
|
+
}
|
|
158
|
+
get value() {
|
|
159
|
+
return this.#value;
|
|
160
|
+
}
|
|
161
|
+
subscribe(callback) {
|
|
162
|
+
const disposable = super.subscribe(callback);
|
|
163
|
+
if (!this.closed) {
|
|
164
|
+
callback(this.#value);
|
|
165
|
+
}
|
|
166
|
+
return disposable;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── ReplaySubject ────────────────────────────────────────────────────
|
|
170
|
+
/**
|
|
171
|
+
* Subject that buffers up to the last `bufferSize` values (configurable,
|
|
172
|
+
* defaults to unbounded). New subscribers immediately receive every
|
|
173
|
+
* buffered value in order before continuing with live emissions.
|
|
174
|
+
*/
|
|
175
|
+
export class ReplaySubject extends Subject {
|
|
176
|
+
#buffer = [];
|
|
177
|
+
#bufferSize;
|
|
178
|
+
constructor(bufferSize = Infinity) {
|
|
179
|
+
super();
|
|
180
|
+
this.#bufferSize = bufferSize;
|
|
181
|
+
}
|
|
182
|
+
next(value) {
|
|
183
|
+
if (this.closed)
|
|
184
|
+
return;
|
|
185
|
+
this.#buffer.push(value);
|
|
186
|
+
if (this.#buffer.length > this.#bufferSize) {
|
|
187
|
+
this.#buffer.shift();
|
|
188
|
+
}
|
|
189
|
+
super.next(value);
|
|
190
|
+
}
|
|
191
|
+
subscribe(callback) {
|
|
192
|
+
// Replay buffered values before subscribing to live values
|
|
193
|
+
for (const value of this.#buffer) {
|
|
194
|
+
callback(value);
|
|
195
|
+
}
|
|
196
|
+
return super.subscribe(callback);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// ── Operators ────────────────────────────────────────────────────────
|
|
200
|
+
/**
|
|
201
|
+
* Emit only the values for which `predicate` returns `true`.
|
|
202
|
+
*
|
|
203
|
+
* @param predicate - Test invoked for each value; truthy passes downstream.
|
|
204
|
+
*
|
|
205
|
+
* @returns Operator that filters the source stream.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* import { filter } from '@camera.ui/sdk';
|
|
210
|
+
*
|
|
211
|
+
* sensor.onPropertyChanged
|
|
212
|
+
* .pipe(filter((c) => c.property === 'detected'))
|
|
213
|
+
* .subscribe((c) => console.log('motion:', c.value));
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export function filter(predicate) {
|
|
217
|
+
return (source) => new Observable((cb) => source.subscribe((value) => {
|
|
218
|
+
if (predicate(value))
|
|
219
|
+
cb(value);
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Apply `transform` to each emitted value and emit the result.
|
|
224
|
+
*
|
|
225
|
+
* @param transform - Projection invoked for each upstream value.
|
|
226
|
+
*
|
|
227
|
+
* @returns Operator that maps every value into a new shape.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* import { map } from '@camera.ui/sdk';
|
|
232
|
+
*
|
|
233
|
+
* sensor.onPropertyChanged
|
|
234
|
+
* .pipe(map((c) => c.value as number))
|
|
235
|
+
* .subscribe((n) => console.log('battery %:', n));
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export function map(transform) {
|
|
239
|
+
return (source) => new Observable((cb) => source.subscribe((value) => {
|
|
240
|
+
cb(transform(value));
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Emit a value only when it differs from the previous one. Uses `===` by
|
|
245
|
+
* default, or an optional custom comparator (e.g. for deep equality).
|
|
246
|
+
*
|
|
247
|
+
* @param comparator - Equality function; return `true` to suppress duplicates.
|
|
248
|
+
*
|
|
249
|
+
* @returns Operator that drops consecutive equal values.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* import { distinctUntilChanged } from '@camera.ui/sdk';
|
|
254
|
+
*
|
|
255
|
+
* sensor.onPropertyChanged
|
|
256
|
+
* .pipe(distinctUntilChanged((a, b) => a.value === b.value))
|
|
257
|
+
* .subscribe((c) => console.log('changed:', c));
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export function distinctUntilChanged(comparator) {
|
|
261
|
+
return (source) => new Observable((cb) => {
|
|
262
|
+
let hasValue = false;
|
|
263
|
+
let lastValue;
|
|
264
|
+
const compare = comparator ?? ((a, b) => a === b);
|
|
265
|
+
return source.subscribe((value) => {
|
|
266
|
+
if (!hasValue || !compare(lastValue, value)) {
|
|
267
|
+
hasValue = true;
|
|
268
|
+
lastValue = value;
|
|
269
|
+
cb(value);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Emit `[previous, current]` pairs for every value after the first.
|
|
276
|
+
*
|
|
277
|
+
* @returns Operator that yields adjacent value pairs.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* import { pairwise } from '@camera.ui/sdk';
|
|
282
|
+
*
|
|
283
|
+
* sensor.onPropertyChanged
|
|
284
|
+
* .pipe(pairwise())
|
|
285
|
+
* .subscribe(([prev, curr]) => console.log(prev.value, '->', curr.value));
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
export function pairwise() {
|
|
289
|
+
return (source) => new Observable((cb) => {
|
|
290
|
+
let hasValue = false;
|
|
291
|
+
let prev;
|
|
292
|
+
return source.subscribe((value) => {
|
|
293
|
+
if (hasValue) {
|
|
294
|
+
cb([prev, value]);
|
|
295
|
+
}
|
|
296
|
+
hasValue = true;
|
|
297
|
+
prev = value;
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Project each source value to a list and flatten the results into the
|
|
303
|
+
* output stream.
|
|
304
|
+
*
|
|
305
|
+
* @param project - Function returning an array of values for each input.
|
|
306
|
+
*
|
|
307
|
+
* @returns Operator that flattens projected arrays into a single stream.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* import { mergeMap } from '@camera.ui/sdk';
|
|
312
|
+
*
|
|
313
|
+
* motionSensor.onPropertyChanged
|
|
314
|
+
* .pipe(mergeMap((c) => (c.property === 'detections' ? c.value as any[] : [])))
|
|
315
|
+
* .subscribe((det) => console.log('detection:', det));
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
export function mergeMap(project) {
|
|
319
|
+
return (source) => new Observable((cb) => {
|
|
320
|
+
let index = 0;
|
|
321
|
+
return source.subscribe((value) => {
|
|
322
|
+
const results = project(value, index++);
|
|
323
|
+
for (const r of results) {
|
|
324
|
+
cb(r);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Multicast a cold Observable through a Subject, sharing a single upstream
|
|
331
|
+
* subscription among all subscribers (reference-counted). Supply a custom
|
|
332
|
+
* connector (e.g. `() => new ReplaySubject(1)`) to change buffering.
|
|
333
|
+
*
|
|
334
|
+
* @param config - Optional config with a `connector` factory for the Subject.
|
|
335
|
+
*
|
|
336
|
+
* @param config.connector - Factory returning the multicast Subject to use.
|
|
337
|
+
*
|
|
338
|
+
* @returns Operator that multicasts the source.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* import { share, ReplaySubject } from '@camera.ui/sdk';
|
|
343
|
+
*
|
|
344
|
+
* const events$ = source$.pipe(share({ connector: () => new ReplaySubject(1) }));
|
|
345
|
+
* events$.subscribe((v) => console.log('a', v));
|
|
346
|
+
* events$.subscribe((v) => console.log('b', v));
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
export function share(config) {
|
|
350
|
+
return (source) => {
|
|
351
|
+
let subject = null;
|
|
352
|
+
let sourceDisposable = null;
|
|
353
|
+
let refCount = 0;
|
|
354
|
+
return new Observable((cb) => {
|
|
355
|
+
if (!subject) {
|
|
356
|
+
subject = config?.connector ? config.connector() : new Subject();
|
|
357
|
+
sourceDisposable = source.subscribe((value) => {
|
|
358
|
+
subject.next(value);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
refCount++;
|
|
362
|
+
const sub = subject.subscribe(cb);
|
|
363
|
+
return new Disposable(() => {
|
|
364
|
+
sub.dispose();
|
|
365
|
+
refCount--;
|
|
366
|
+
if (refCount === 0) {
|
|
367
|
+
sourceDisposable?.dispose();
|
|
368
|
+
sourceDisposable = null;
|
|
369
|
+
subject = null;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// ── Utilities ────────────────────────────────────────────────────────
|
|
376
|
+
/**
|
|
377
|
+
* Subscribe to the source and return a Promise that resolves with its first
|
|
378
|
+
* emitted value, then disposes the subscription. Rejects if the source has
|
|
379
|
+
* already completed without emitting.
|
|
380
|
+
*
|
|
381
|
+
* @param observable - Source stream to await.
|
|
382
|
+
*
|
|
383
|
+
* @returns Promise that resolves with the first emitted value.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* import { firstValueFrom } from '@camera.ui/sdk';
|
|
388
|
+
*
|
|
389
|
+
* const change = await firstValueFrom(sensor.onPropertyChanged);
|
|
390
|
+
* console.log('first change:', change.property);
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
export function firstValueFrom(observable) {
|
|
394
|
+
return new Promise((resolve, reject) => {
|
|
395
|
+
let resolved = false;
|
|
396
|
+
// The callback may fire synchronously during subscribe() (BehaviorSubject/
|
|
397
|
+
// ReplaySubject), so declare sub up front to avoid the temporal dead zone.
|
|
398
|
+
let sub = undefined;
|
|
399
|
+
sub = observable.subscribe((value) => {
|
|
400
|
+
if (!resolved) {
|
|
401
|
+
resolved = true;
|
|
402
|
+
// sub may not be assigned yet if value is emitted synchronously (BehaviorSubject/ReplaySubject)
|
|
403
|
+
if (sub) {
|
|
404
|
+
sub.dispose();
|
|
405
|
+
}
|
|
406
|
+
resolve(value);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
// Already resolved synchronously — clean up immediately
|
|
410
|
+
if (resolved) {
|
|
411
|
+
sub.dispose();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// If the observable completed without emitting (closed subject)
|
|
415
|
+
if ('closed' in observable && observable.closed && !resolved) {
|
|
416
|
+
reject(new Error('Observable completed without emitting a value'));
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle events emitted on the PluginAPI EventEmitter. Plugins subscribe
|
|
3
|
+
* with `api.on(API_EVENT.X, handler)` to react to host-driven phase changes.
|
|
4
|
+
*/
|
|
5
|
+
export var API_EVENT;
|
|
6
|
+
(function (API_EVENT) {
|
|
7
|
+
/**
|
|
8
|
+
* Emitted exactly once after the plugin has been constructed, all assigned
|
|
9
|
+
* cameras have been wired up, and `configureCameras()` has returned. Use
|
|
10
|
+
* this to start background work that must wait until the camera set is
|
|
11
|
+
* stable (timers, model warm-up, outbound connections).
|
|
12
|
+
*/
|
|
13
|
+
API_EVENT["FINISH_LAUNCHING"] = "finishLaunching";
|
|
14
|
+
/**
|
|
15
|
+
* Emitted when the host is tearing the plugin down (graceful stop, reload
|
|
16
|
+
* or process exit). Listeners must release resources synchronously enough
|
|
17
|
+
* to finish before the host kills the process — open files, sockets,
|
|
18
|
+
* timers, child processes.
|
|
19
|
+
*/
|
|
20
|
+
API_EVENT["SHUTDOWN"] = "shutdown";
|
|
21
|
+
})(API_EVENT || (API_EVENT = {}));
|