@camstack/sdk 1.2.9 → 1.2.11
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/__tests__/subscribe-event-multiplex.spec.d.ts +13 -0
- package/dist/index.cjs +60 -8
- package/dist/index.js +60 -8
- package/dist/system.d.ts +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `subscribeEvent` fans one WS subscription out to N listeners.
|
|
3
|
+
*
|
|
4
|
+
* Measured on the viewer's boot: ~40 tRPC subscriptions before first paint,
|
|
5
|
+
* of which 24 were twelve camera cards subscribing to the SAME two motion
|
|
6
|
+
* categories and filtering by deviceId inside their own callbacks. Each was a
|
|
7
|
+
* separate handshake on a transport that does not batch.
|
|
8
|
+
*
|
|
9
|
+
* The tests exercise the real `System` with a stubbed tRPC client, so
|
|
10
|
+
* what is asserted is the number of `live.onEvent.subscribe` calls the SDK
|
|
11
|
+
* actually makes — not a mock of the deduping itself.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -683,6 +683,17 @@ var System = class {
|
|
|
683
683
|
token;
|
|
684
684
|
_trpcClient;
|
|
685
685
|
_wsClient = null;
|
|
686
|
+
/**
|
|
687
|
+
* One underlying `live.onEvent` subscription per DISTINCT category, fanned
|
|
688
|
+
* out to every listener.
|
|
689
|
+
*
|
|
690
|
+
* Measured on the viewer's boot: ~40 subscriptions, of which 24 were twelve
|
|
691
|
+
* cameras subscribing to the SAME two motion categories and filtering by
|
|
692
|
+
* deviceId in their own callbacks. Each was its own WS handshake on a
|
|
693
|
+
* transport that does not batch. Deduping here needs no protocol change and
|
|
694
|
+
* no cooperation from callers — `subscribeEvent` keeps its exact shape.
|
|
695
|
+
*/
|
|
696
|
+
_eventSubs = /* @__PURE__ */ new Map();
|
|
686
697
|
mirror = null;
|
|
687
698
|
mirrorInit = null;
|
|
688
699
|
connected = false;
|
|
@@ -826,6 +837,7 @@ var System = class {
|
|
|
826
837
|
this.emitConnectionEvent("connecting");
|
|
827
838
|
this._wsClient?.close();
|
|
828
839
|
this.disposeMirror();
|
|
840
|
+
this.resetEventSubs();
|
|
829
841
|
this.connected = false;
|
|
830
842
|
this.connectedPromise = null;
|
|
831
843
|
this._trpcClient = this.buildTrpcClient();
|
|
@@ -1222,19 +1234,59 @@ var System = class {
|
|
|
1222
1234
|
* (`@camstack/types`) — passing a raw string works for forward-compat
|
|
1223
1235
|
* but loses type safety. The SDK forwards the value verbatim to the
|
|
1224
1236
|
* server's `live.onEvent` subscription.
|
|
1237
|
+
*
|
|
1238
|
+
* **N callers on one category share ONE subscription.** The caller's shape
|
|
1239
|
+
* is unchanged, and it is not asked to coordinate: twelve camera cards each
|
|
1240
|
+
* subscribing to the motion category and filtering by deviceId is the normal
|
|
1241
|
+
* pattern, and it used to open twelve WS subscriptions.
|
|
1225
1242
|
*/
|
|
1226
1243
|
subscribeEvent(category, cb) {
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1244
|
+
const listener = cb;
|
|
1245
|
+
let entry = this._eventSubs.get(category);
|
|
1246
|
+
if (entry === void 0) {
|
|
1247
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1248
|
+
const sub = this._trpcClient.live.onEvent.subscribe({ category }, { onData: (event) => {
|
|
1249
|
+
for (const fn of [...listeners]) try {
|
|
1250
|
+
fn(event);
|
|
1251
|
+
} catch {}
|
|
1252
|
+
} });
|
|
1253
|
+
entry = {
|
|
1254
|
+
listeners,
|
|
1255
|
+
unsubscribe: () => {
|
|
1256
|
+
try {
|
|
1257
|
+
sub.unsubscribe();
|
|
1258
|
+
} catch {}
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
this._eventSubs.set(category, entry);
|
|
1262
|
+
}
|
|
1263
|
+
entry.listeners.add(listener);
|
|
1264
|
+
let released = false;
|
|
1232
1265
|
return () => {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1266
|
+
if (released) return;
|
|
1267
|
+
released = true;
|
|
1268
|
+
const current = this._eventSubs.get(category);
|
|
1269
|
+
if (current === void 0) return;
|
|
1270
|
+
current.listeners.delete(listener);
|
|
1271
|
+
if (current.listeners.size === 0) {
|
|
1272
|
+
current.unsubscribe();
|
|
1273
|
+
this._eventSubs.delete(category);
|
|
1274
|
+
}
|
|
1236
1275
|
};
|
|
1237
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Drop every shared subscription.
|
|
1279
|
+
*
|
|
1280
|
+
* Called when the tRPC client is rebuilt: the handles above belong to the
|
|
1281
|
+
* OLD client and unsubscribing them is meaningless, while keeping them in
|
|
1282
|
+
* the map would make the next `subscribeEvent` attach a listener to a dead
|
|
1283
|
+
* socket and go silent — the worst outcome, because it looks like "no
|
|
1284
|
+
* events happened".
|
|
1285
|
+
*/
|
|
1286
|
+
resetEventSubs() {
|
|
1287
|
+
for (const entry of this._eventSubs.values()) entry.unsubscribe();
|
|
1288
|
+
this._eventSubs.clear();
|
|
1289
|
+
}
|
|
1238
1290
|
/** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
|
|
1239
1291
|
get trpcClient() {
|
|
1240
1292
|
return this._trpcClient;
|
package/dist/index.js
CHANGED
|
@@ -682,6 +682,17 @@ var System = class {
|
|
|
682
682
|
token;
|
|
683
683
|
_trpcClient;
|
|
684
684
|
_wsClient = null;
|
|
685
|
+
/**
|
|
686
|
+
* One underlying `live.onEvent` subscription per DISTINCT category, fanned
|
|
687
|
+
* out to every listener.
|
|
688
|
+
*
|
|
689
|
+
* Measured on the viewer's boot: ~40 subscriptions, of which 24 were twelve
|
|
690
|
+
* cameras subscribing to the SAME two motion categories and filtering by
|
|
691
|
+
* deviceId in their own callbacks. Each was its own WS handshake on a
|
|
692
|
+
* transport that does not batch. Deduping here needs no protocol change and
|
|
693
|
+
* no cooperation from callers — `subscribeEvent` keeps its exact shape.
|
|
694
|
+
*/
|
|
695
|
+
_eventSubs = /* @__PURE__ */ new Map();
|
|
685
696
|
mirror = null;
|
|
686
697
|
mirrorInit = null;
|
|
687
698
|
connected = false;
|
|
@@ -825,6 +836,7 @@ var System = class {
|
|
|
825
836
|
this.emitConnectionEvent("connecting");
|
|
826
837
|
this._wsClient?.close();
|
|
827
838
|
this.disposeMirror();
|
|
839
|
+
this.resetEventSubs();
|
|
828
840
|
this.connected = false;
|
|
829
841
|
this.connectedPromise = null;
|
|
830
842
|
this._trpcClient = this.buildTrpcClient();
|
|
@@ -1221,19 +1233,59 @@ var System = class {
|
|
|
1221
1233
|
* (`@camstack/types`) — passing a raw string works for forward-compat
|
|
1222
1234
|
* but loses type safety. The SDK forwards the value verbatim to the
|
|
1223
1235
|
* server's `live.onEvent` subscription.
|
|
1236
|
+
*
|
|
1237
|
+
* **N callers on one category share ONE subscription.** The caller's shape
|
|
1238
|
+
* is unchanged, and it is not asked to coordinate: twelve camera cards each
|
|
1239
|
+
* subscribing to the motion category and filtering by deviceId is the normal
|
|
1240
|
+
* pattern, and it used to open twelve WS subscriptions.
|
|
1224
1241
|
*/
|
|
1225
1242
|
subscribeEvent(category, cb) {
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1243
|
+
const listener = cb;
|
|
1244
|
+
let entry = this._eventSubs.get(category);
|
|
1245
|
+
if (entry === void 0) {
|
|
1246
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1247
|
+
const sub = this._trpcClient.live.onEvent.subscribe({ category }, { onData: (event) => {
|
|
1248
|
+
for (const fn of [...listeners]) try {
|
|
1249
|
+
fn(event);
|
|
1250
|
+
} catch {}
|
|
1251
|
+
} });
|
|
1252
|
+
entry = {
|
|
1253
|
+
listeners,
|
|
1254
|
+
unsubscribe: () => {
|
|
1255
|
+
try {
|
|
1256
|
+
sub.unsubscribe();
|
|
1257
|
+
} catch {}
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
this._eventSubs.set(category, entry);
|
|
1261
|
+
}
|
|
1262
|
+
entry.listeners.add(listener);
|
|
1263
|
+
let released = false;
|
|
1231
1264
|
return () => {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1265
|
+
if (released) return;
|
|
1266
|
+
released = true;
|
|
1267
|
+
const current = this._eventSubs.get(category);
|
|
1268
|
+
if (current === void 0) return;
|
|
1269
|
+
current.listeners.delete(listener);
|
|
1270
|
+
if (current.listeners.size === 0) {
|
|
1271
|
+
current.unsubscribe();
|
|
1272
|
+
this._eventSubs.delete(category);
|
|
1273
|
+
}
|
|
1235
1274
|
};
|
|
1236
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Drop every shared subscription.
|
|
1278
|
+
*
|
|
1279
|
+
* Called when the tRPC client is rebuilt: the handles above belong to the
|
|
1280
|
+
* OLD client and unsubscribing them is meaningless, while keeping them in
|
|
1281
|
+
* the map would make the next `subscribeEvent` attach a listener to a dead
|
|
1282
|
+
* socket and go silent — the worst outcome, because it looks like "no
|
|
1283
|
+
* events happened".
|
|
1284
|
+
*/
|
|
1285
|
+
resetEventSubs() {
|
|
1286
|
+
for (const entry of this._eventSubs.values()) entry.unsubscribe();
|
|
1287
|
+
this._eventSubs.clear();
|
|
1288
|
+
}
|
|
1237
1289
|
/** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
|
|
1238
1290
|
get trpcClient() {
|
|
1239
1291
|
return this._trpcClient;
|
package/dist/system.d.ts
CHANGED
|
@@ -75,6 +75,17 @@ export declare class System {
|
|
|
75
75
|
private token;
|
|
76
76
|
private _trpcClient;
|
|
77
77
|
private _wsClient;
|
|
78
|
+
/**
|
|
79
|
+
* One underlying `live.onEvent` subscription per DISTINCT category, fanned
|
|
80
|
+
* out to every listener.
|
|
81
|
+
*
|
|
82
|
+
* Measured on the viewer's boot: ~40 subscriptions, of which 24 were twelve
|
|
83
|
+
* cameras subscribing to the SAME two motion categories and filtering by
|
|
84
|
+
* deviceId in their own callbacks. Each was its own WS handshake on a
|
|
85
|
+
* transport that does not batch. Deduping here needs no protocol change and
|
|
86
|
+
* no cooperation from callers — `subscribeEvent` keeps its exact shape.
|
|
87
|
+
*/
|
|
88
|
+
private readonly _eventSubs;
|
|
78
89
|
private mirror;
|
|
79
90
|
private mirrorInit;
|
|
80
91
|
private connected;
|
|
@@ -382,8 +393,23 @@ export declare class System {
|
|
|
382
393
|
* (`@camstack/types`) — passing a raw string works for forward-compat
|
|
383
394
|
* but loses type safety. The SDK forwards the value verbatim to the
|
|
384
395
|
* server's `live.onEvent` subscription.
|
|
396
|
+
*
|
|
397
|
+
* **N callers on one category share ONE subscription.** The caller's shape
|
|
398
|
+
* is unchanged, and it is not asked to coordinate: twelve camera cards each
|
|
399
|
+
* subscribing to the motion category and filtering by deviceId is the normal
|
|
400
|
+
* pattern, and it used to open twelve WS subscriptions.
|
|
385
401
|
*/
|
|
386
402
|
subscribeEvent<TData = unknown>(category: string, cb: SystemLiveEventListener<TData>): () => void;
|
|
403
|
+
/**
|
|
404
|
+
* Drop every shared subscription.
|
|
405
|
+
*
|
|
406
|
+
* Called when the tRPC client is rebuilt: the handles above belong to the
|
|
407
|
+
* OLD client and unsubscribing them is meaningless, while keeping them in
|
|
408
|
+
* the map would make the next `subscribeEvent` attach a listener to a dead
|
|
409
|
+
* socket and go silent — the worst outcome, because it looks like "no
|
|
410
|
+
* events happened".
|
|
411
|
+
*/
|
|
412
|
+
private resetEventSubs;
|
|
387
413
|
/** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
|
|
388
414
|
get trpcClient(): TrpcClient;
|
|
389
415
|
/**
|