@camstack/addon-provider-wyze 0.2.67 → 0.2.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -12976,6 +12976,24 @@ var deviceProviderCapability = {
12976
12976
  })
12977
12977
  }
12978
12978
  };
12979
+ /**
12980
+ * Device Manager capability — hub-side singleton that unifies device persistence,
12981
+ * live registry access, and all management operations into a single tRPC surface.
12982
+ *
12983
+ * Replaces:
12984
+ * - `device-persistence` capability (persistence methods absorbed here)
12985
+ * - `device-management.router.ts` (deleted in Phase 2)
12986
+ * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
12987
+ *
12988
+ * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
12989
+ * fork into separate processes but never run on remote cluster agents. Therefore:
12990
+ * - No nodeId routing needed — this is a pure hub singleton.
12991
+ * - The hub's DeviceRegistry is the single source of truth for all live devices.
12992
+ * - No shadow registry or cross-node aggregation required.
12993
+ *
12994
+ * Forked workers register devices back to the hub via `ctx.devices`
12995
+ * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
12996
+ */
12979
12997
  /** One child-placement directive on a container's `childLayout`. Structurally
12980
12998
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
12981
12999
  * shape for the same field. The child is identified by its re-sync-stable
@@ -13074,7 +13092,12 @@ var ConfigEntrySchema = object({
13074
13092
  value: unknown(),
13075
13093
  description: string().optional()
13076
13094
  });
13077
- var LinkedDevicesModeSchema = _enum(["auto", "manual"]);
13095
+ /**
13096
+ * Only `manual` since D356: the operator picks, nothing links itself. The
13097
+ * field survives on the wire because a viewer already OTA'd parses it —
13098
+ * drop it once no shipped client reads `mode`.
13099
+ */
13100
+ var LinkedDevicesModeSchema = _enum(["manual"]);
13078
13101
  /** One resolved linked device — the compact projection consumers need. */
13079
13102
  var LinkedDeviceSchema = object({
13080
13103
  deviceId: number(),
@@ -18702,6 +18725,9 @@ var RetrainStatusSchema = _enum([
18702
18725
  *
18703
18726
  * `debug` does NOT pin; it is attention, not durability.
18704
18727
  *
18728
+ * `debugNote` is the operator's own words about WHAT should be checked on this
18729
+ * track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
18730
+ *
18705
18731
  * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
18706
18732
  * A favourited track is skipped by retention the same way `staging` is, but
18707
18733
  * it does not enter `none|staging|trained` and has no staging budget.
@@ -18712,6 +18738,21 @@ var TrackFlagFields = {
18712
18738
  markForTrain: boolean().optional(),
18713
18739
  /** Operator marked this track for diagnostic attention. */
18714
18740
  debug: boolean().optional(),
18741
+ /**
18742
+ * What the operator wants CHECKED on this track, in their own words.
18743
+ *
18744
+ * The flag alone says "look at this" and nothing about what for; a debug set
18745
+ * reviewed hours later is a list of tracks with no question attached. Bound
18746
+ * to `debug` on the WRITE side — the body clears it when `debug` goes off,
18747
+ * and refuses it on a track whose debug is off — so a note can never outlive
18748
+ * the attention it belongs to.
18749
+ *
18750
+ * `''` is a real value ("marked, nothing to add"); the ABSENT key means
18751
+ * "leave whatever is stored alone", which is what lets a surface turn debug
18752
+ * on without a note (a cancelled prompt) and what lets it edit the note
18753
+ * without touching the flag.
18754
+ */
18755
+ debugNote: string().max(500).optional(),
18715
18756
  /** Operator favourited this track. Pins it against pruning. */
18716
18757
  favourited: boolean().optional()
18717
18758
  };
@@ -18738,6 +18779,12 @@ var TrackFlagsSchema = object({
18738
18779
  trackId: string(),
18739
18780
  markForTrain: boolean(),
18740
18781
  debug: boolean(),
18782
+ /** The note as it STANDS after the write — never absent here (a track with no
18783
+ * note reports `''`), for the same reason the booleans are required: a
18784
+ * surface that has just written must be able to render the note it now owns
18785
+ * without a re-fetch, and an absent key would read as "unchanged" on a
18786
+ * screen that has no previous value to keep. */
18787
+ debugNote: string(),
18741
18788
  favourited: boolean(),
18742
18789
  /** The lifecycle state the boolean was derived from. Required here (unlike on
18743
18790
  * a track row) because this shape is only ever produced by the write body,
@@ -41667,12 +41714,12 @@ var WyzeCloud = class {
41667
41714
  clearSession;
41668
41715
  constructor(apiKeyOrOpts, apiId) {
41669
41716
  if (typeof apiKeyOrOpts === "string") {
41670
- this.apiKey = apiKeyOrOpts;
41671
- this.keyId = apiId;
41717
+ this.apiKey = apiKeyOrOpts.trim();
41718
+ this.keyId = (apiId ?? "").trim();
41672
41719
  this.phoneId = generatePhoneId();
41673
41720
  } else {
41674
- this.apiKey = apiKeyOrOpts.apiKey;
41675
- this.keyId = apiKeyOrOpts.apiId;
41721
+ this.apiKey = apiKeyOrOpts.apiKey.trim();
41722
+ this.keyId = apiKeyOrOpts.apiId.trim();
41676
41723
  this.loadSession = apiKeyOrOpts.loadSession;
41677
41724
  this.saveSession = apiKeyOrOpts.saveSession;
41678
41725
  this.clearSession = apiKeyOrOpts.clearSession;
@@ -41693,6 +41740,7 @@ var WyzeCloud = class {
41693
41740
  * @throws WyzeAuthError if MFA is required
41694
41741
  */
41695
41742
  async login(email, password) {
41743
+ if (!this.apiKey || !this.keyId) throw new Error("Wyze login: missing API key or key id");
41696
41744
  const payload = {
41697
41745
  email: email.trim(),
41698
41746
  password: hashPassword(password)
@@ -42829,6 +42877,7 @@ var WyzeDTLSConn = class extends events.EventEmitter {
42829
42877
  frameHandler;
42830
42878
  closed = false;
42831
42879
  messageListener = null;
42880
+ statsTimer = null;
42832
42881
  constructor(opts) {
42833
42882
  super();
42834
42883
  this.host = opts.host;
@@ -42849,6 +42898,14 @@ var WyzeDTLSConn = class extends events.EventEmitter {
42849
42898
  * After this, call startVideo()/startAudio() to begin streaming.
42850
42899
  */
42851
42900
  async connect() {
42901
+ try {
42902
+ return await this.doConnect();
42903
+ } catch (err) {
42904
+ this.close();
42905
+ throw err;
42906
+ }
42907
+ }
42908
+ async doConnect() {
42852
42909
  this.socket = dgram.createSocket("udp4");
42853
42910
  await new Promise((r) => this.socket.bind(0, r));
42854
42911
  const localPort = this.socket.address().port;
@@ -43468,6 +43525,10 @@ var WyzeDTLSConn = class extends events.EventEmitter {
43468
43525
  this.serverConn = null;
43469
43526
  }
43470
43527
  if (this.ackTicker) clearInterval(this.ackTicker);
43528
+ if (this.statsTimer) {
43529
+ clearInterval(this.statsTimer);
43530
+ this.statsTimer = null;
43531
+ }
43471
43532
  if (this.messageListener && this.socket) this.socket.removeListener("message", this.messageListener);
43472
43533
  this.frameHandler.close();
43473
43534
  if (this.socket) {
@@ -43768,9 +43829,12 @@ var WyzeDTLSConn = class extends events.EventEmitter {
43768
43829
  let frameData = 0;
43769
43830
  let keepalives = 0;
43770
43831
  const startedAt = Date.now();
43771
- const statsTimer = setInterval(() => {
43832
+ this.statsTimer = setInterval(() => {
43772
43833
  if (this.closed) {
43773
- clearInterval(statsTimer);
43834
+ if (this.statsTimer) {
43835
+ clearInterval(this.statsTimer);
43836
+ this.statsTimer = null;
43837
+ }
43774
43838
  return;
43775
43839
  }
43776
43840
  const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(0);
@@ -43820,7 +43884,7 @@ var WyzeDTLSConn = class extends events.EventEmitter {
43820
43884
  b.writeUInt32LE(this.avSeq, 4);
43821
43885
  this.avSeq++;
43822
43886
  b.writeUInt16LE(28672, 16);
43823
- b.writeUInt16LE(this.seqCmd, 18);
43887
+ b.writeUInt16LE(this.seqCmd & 65535, 18);
43824
43888
  b.writeUInt32LE(1, 20);
43825
43889
  b.writeUInt32LE(payload.length + 4, 24);
43826
43890
  b.writeUInt32LE(this.seqCmd, 28);
@@ -43893,7 +43957,7 @@ var WyzeDTLSConn = class extends events.EventEmitter {
43893
43957
  b[3] = 11;
43894
43958
  b.writeUInt16LE(bs, 4);
43895
43959
  b.writeUInt16LE(this.txSeq, 6);
43896
- this.txSeq++;
43960
+ this.txSeq = this.txSeq + 1 & 65535;
43897
43961
  b.writeUInt16LE(1031, 8);
43898
43962
  b.writeUInt16LE(33, 10);
43899
43963
  this.sid.copy(b, 12, 0, 2);
@@ -44475,10 +44539,17 @@ async function createWyzeRfc4571Server(opts) {
44475
44539
  logger
44476
44540
  });
44477
44541
  logger.log?.(`[wyze-rfc4571] Connecting to ${camera.nickname} (${camera.ip})...`);
44478
- await conn.connect();
44479
- logger.log?.(`[wyze-rfc4571] Connected, starting video...`);
44480
- await conn.startVideo(frameSize, bitrate);
44481
- await conn.startAudio();
44542
+ try {
44543
+ await conn.connect();
44544
+ logger.log?.(`[wyze-rfc4571] Connected, starting video...`);
44545
+ await conn.startVideo(frameSize, bitrate);
44546
+ await conn.startAudio();
44547
+ } catch (e) {
44548
+ try {
44549
+ conn.close();
44550
+ } catch {}
44551
+ throw e;
44552
+ }
44482
44553
  const rebuildSdp = () => {
44483
44554
  if (videoSdpPart) sdp = buildFullSdp(videoSdpPart, audioSdpPart || void 0);
44484
44555
  };
@@ -44636,7 +44707,9 @@ async function createWyzeRfc4571Server(opts) {
44636
44707
  return {
44637
44708
  host: addr.address,
44638
44709
  port: addr.port,
44639
- sdp,
44710
+ get sdp() {
44711
+ return sdp;
44712
+ },
44640
44713
  videoType,
44641
44714
  connection: conn,
44642
44715
  close: closeFn,
package/dist/addon.mjs CHANGED
@@ -12955,6 +12955,24 @@ var deviceProviderCapability = {
12955
12955
  })
12956
12956
  }
12957
12957
  };
12958
+ /**
12959
+ * Device Manager capability — hub-side singleton that unifies device persistence,
12960
+ * live registry access, and all management operations into a single tRPC surface.
12961
+ *
12962
+ * Replaces:
12963
+ * - `device-persistence` capability (persistence methods absorbed here)
12964
+ * - `device-management.router.ts` (deleted in Phase 2)
12965
+ * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
12966
+ *
12967
+ * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
12968
+ * fork into separate processes but never run on remote cluster agents. Therefore:
12969
+ * - No nodeId routing needed — this is a pure hub singleton.
12970
+ * - The hub's DeviceRegistry is the single source of truth for all live devices.
12971
+ * - No shadow registry or cross-node aggregation required.
12972
+ *
12973
+ * Forked workers register devices back to the hub via `ctx.devices`
12974
+ * (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
12975
+ */
12958
12976
  /** One child-placement directive on a container's `childLayout`. Structurally
12959
12977
  * identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
12960
12978
  * shape for the same field. The child is identified by its re-sync-stable
@@ -13053,7 +13071,12 @@ var ConfigEntrySchema = object({
13053
13071
  value: unknown(),
13054
13072
  description: string().optional()
13055
13073
  });
13056
- var LinkedDevicesModeSchema = _enum(["auto", "manual"]);
13074
+ /**
13075
+ * Only `manual` since D356: the operator picks, nothing links itself. The
13076
+ * field survives on the wire because a viewer already OTA'd parses it —
13077
+ * drop it once no shipped client reads `mode`.
13078
+ */
13079
+ var LinkedDevicesModeSchema = _enum(["manual"]);
13057
13080
  /** One resolved linked device — the compact projection consumers need. */
13058
13081
  var LinkedDeviceSchema = object({
13059
13082
  deviceId: number(),
@@ -18681,6 +18704,9 @@ var RetrainStatusSchema = _enum([
18681
18704
  *
18682
18705
  * `debug` does NOT pin; it is attention, not durability.
18683
18706
  *
18707
+ * `debugNote` is the operator's own words about WHAT should be checked on this
18708
+ * track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
18709
+ *
18684
18710
  * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
18685
18711
  * A favourited track is skipped by retention the same way `staging` is, but
18686
18712
  * it does not enter `none|staging|trained` and has no staging budget.
@@ -18691,6 +18717,21 @@ var TrackFlagFields = {
18691
18717
  markForTrain: boolean().optional(),
18692
18718
  /** Operator marked this track for diagnostic attention. */
18693
18719
  debug: boolean().optional(),
18720
+ /**
18721
+ * What the operator wants CHECKED on this track, in their own words.
18722
+ *
18723
+ * The flag alone says "look at this" and nothing about what for; a debug set
18724
+ * reviewed hours later is a list of tracks with no question attached. Bound
18725
+ * to `debug` on the WRITE side — the body clears it when `debug` goes off,
18726
+ * and refuses it on a track whose debug is off — so a note can never outlive
18727
+ * the attention it belongs to.
18728
+ *
18729
+ * `''` is a real value ("marked, nothing to add"); the ABSENT key means
18730
+ * "leave whatever is stored alone", which is what lets a surface turn debug
18731
+ * on without a note (a cancelled prompt) and what lets it edit the note
18732
+ * without touching the flag.
18733
+ */
18734
+ debugNote: string().max(500).optional(),
18694
18735
  /** Operator favourited this track. Pins it against pruning. */
18695
18736
  favourited: boolean().optional()
18696
18737
  };
@@ -18717,6 +18758,12 @@ var TrackFlagsSchema = object({
18717
18758
  trackId: string(),
18718
18759
  markForTrain: boolean(),
18719
18760
  debug: boolean(),
18761
+ /** The note as it STANDS after the write — never absent here (a track with no
18762
+ * note reports `''`), for the same reason the booleans are required: a
18763
+ * surface that has just written must be able to render the note it now owns
18764
+ * without a re-fetch, and an absent key would read as "unchanged" on a
18765
+ * screen that has no previous value to keep. */
18766
+ debugNote: string(),
18720
18767
  favourited: boolean(),
18721
18768
  /** The lifecycle state the boolean was derived from. Required here (unlike on
18722
18769
  * a track row) because this shape is only ever produced by the write body,
@@ -41646,12 +41693,12 @@ var WyzeCloud = class {
41646
41693
  clearSession;
41647
41694
  constructor(apiKeyOrOpts, apiId) {
41648
41695
  if (typeof apiKeyOrOpts === "string") {
41649
- this.apiKey = apiKeyOrOpts;
41650
- this.keyId = apiId;
41696
+ this.apiKey = apiKeyOrOpts.trim();
41697
+ this.keyId = (apiId ?? "").trim();
41651
41698
  this.phoneId = generatePhoneId();
41652
41699
  } else {
41653
- this.apiKey = apiKeyOrOpts.apiKey;
41654
- this.keyId = apiKeyOrOpts.apiId;
41700
+ this.apiKey = apiKeyOrOpts.apiKey.trim();
41701
+ this.keyId = apiKeyOrOpts.apiId.trim();
41655
41702
  this.loadSession = apiKeyOrOpts.loadSession;
41656
41703
  this.saveSession = apiKeyOrOpts.saveSession;
41657
41704
  this.clearSession = apiKeyOrOpts.clearSession;
@@ -41672,6 +41719,7 @@ var WyzeCloud = class {
41672
41719
  * @throws WyzeAuthError if MFA is required
41673
41720
  */
41674
41721
  async login(email, password) {
41722
+ if (!this.apiKey || !this.keyId) throw new Error("Wyze login: missing API key or key id");
41675
41723
  const payload = {
41676
41724
  email: email.trim(),
41677
41725
  password: hashPassword(password)
@@ -42808,6 +42856,7 @@ var WyzeDTLSConn = class extends EventEmitter {
42808
42856
  frameHandler;
42809
42857
  closed = false;
42810
42858
  messageListener = null;
42859
+ statsTimer = null;
42811
42860
  constructor(opts) {
42812
42861
  super();
42813
42862
  this.host = opts.host;
@@ -42828,6 +42877,14 @@ var WyzeDTLSConn = class extends EventEmitter {
42828
42877
  * After this, call startVideo()/startAudio() to begin streaming.
42829
42878
  */
42830
42879
  async connect() {
42880
+ try {
42881
+ return await this.doConnect();
42882
+ } catch (err) {
42883
+ this.close();
42884
+ throw err;
42885
+ }
42886
+ }
42887
+ async doConnect() {
42831
42888
  this.socket = dgram.createSocket("udp4");
42832
42889
  await new Promise((r) => this.socket.bind(0, r));
42833
42890
  const localPort = this.socket.address().port;
@@ -43447,6 +43504,10 @@ var WyzeDTLSConn = class extends EventEmitter {
43447
43504
  this.serverConn = null;
43448
43505
  }
43449
43506
  if (this.ackTicker) clearInterval(this.ackTicker);
43507
+ if (this.statsTimer) {
43508
+ clearInterval(this.statsTimer);
43509
+ this.statsTimer = null;
43510
+ }
43450
43511
  if (this.messageListener && this.socket) this.socket.removeListener("message", this.messageListener);
43451
43512
  this.frameHandler.close();
43452
43513
  if (this.socket) {
@@ -43747,9 +43808,12 @@ var WyzeDTLSConn = class extends EventEmitter {
43747
43808
  let frameData = 0;
43748
43809
  let keepalives = 0;
43749
43810
  const startedAt = Date.now();
43750
- const statsTimer = setInterval(() => {
43811
+ this.statsTimer = setInterval(() => {
43751
43812
  if (this.closed) {
43752
- clearInterval(statsTimer);
43813
+ if (this.statsTimer) {
43814
+ clearInterval(this.statsTimer);
43815
+ this.statsTimer = null;
43816
+ }
43753
43817
  return;
43754
43818
  }
43755
43819
  const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(0);
@@ -43799,7 +43863,7 @@ var WyzeDTLSConn = class extends EventEmitter {
43799
43863
  b.writeUInt32LE(this.avSeq, 4);
43800
43864
  this.avSeq++;
43801
43865
  b.writeUInt16LE(28672, 16);
43802
- b.writeUInt16LE(this.seqCmd, 18);
43866
+ b.writeUInt16LE(this.seqCmd & 65535, 18);
43803
43867
  b.writeUInt32LE(1, 20);
43804
43868
  b.writeUInt32LE(payload.length + 4, 24);
43805
43869
  b.writeUInt32LE(this.seqCmd, 28);
@@ -43872,7 +43936,7 @@ var WyzeDTLSConn = class extends EventEmitter {
43872
43936
  b[3] = 11;
43873
43937
  b.writeUInt16LE(bs, 4);
43874
43938
  b.writeUInt16LE(this.txSeq, 6);
43875
- this.txSeq++;
43939
+ this.txSeq = this.txSeq + 1 & 65535;
43876
43940
  b.writeUInt16LE(1031, 8);
43877
43941
  b.writeUInt16LE(33, 10);
43878
43942
  this.sid.copy(b, 12, 0, 2);
@@ -44454,10 +44518,17 @@ async function createWyzeRfc4571Server(opts) {
44454
44518
  logger
44455
44519
  });
44456
44520
  logger.log?.(`[wyze-rfc4571] Connecting to ${camera.nickname} (${camera.ip})...`);
44457
- await conn.connect();
44458
- logger.log?.(`[wyze-rfc4571] Connected, starting video...`);
44459
- await conn.startVideo(frameSize, bitrate);
44460
- await conn.startAudio();
44521
+ try {
44522
+ await conn.connect();
44523
+ logger.log?.(`[wyze-rfc4571] Connected, starting video...`);
44524
+ await conn.startVideo(frameSize, bitrate);
44525
+ await conn.startAudio();
44526
+ } catch (e) {
44527
+ try {
44528
+ conn.close();
44529
+ } catch {}
44530
+ throw e;
44531
+ }
44461
44532
  const rebuildSdp = () => {
44462
44533
  if (videoSdpPart) sdp = buildFullSdp(videoSdpPart, audioSdpPart || void 0);
44463
44534
  };
@@ -44615,7 +44686,9 @@ async function createWyzeRfc4571Server(opts) {
44615
44686
  return {
44616
44687
  host: addr.address,
44617
44688
  port: addr.port,
44618
- sdp,
44689
+ get sdp() {
44690
+ return sdp;
44691
+ },
44619
44692
  videoType,
44620
44693
  connection: conn,
44621
44694
  close: closeFn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-wyze",
3
- "version": "0.2.67",
3
+ "version": "0.2.69",
4
4
  "description": "Wyze camera device-provider addon for CamStack — wraps the @apocaliss92/wyze-bridge-js P2P/DTLS client, feeding the stream-broker via the pull-rfc4571 lazy-publish path (a structural twin of addon-provider-reolink)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -74,7 +74,7 @@
74
74
  "publish": "npm publish --access public"
75
75
  },
76
76
  "devDependencies": {
77
- "@apocaliss92/wyze-bridge-js": "file:./vendor/apocaliss92-wyze-bridge-js-0.1.0.tgz",
77
+ "@apocaliss92/wyze-bridge-js": "file:./vendor/apocaliss92-wyze-bridge-js-0.1.2.tgz",
78
78
  "@camstack/types": "*",
79
79
  "typescript": "~6.0.3",
80
80
  "vite": "^8.0.11",