@mega-yfue/eufy-sdk 0.5.0-beta.60 → 0.5.0-beta.62

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/index.js CHANGED
@@ -11487,6 +11487,8 @@ var MODEL_REGISTRY = {
11487
11487
  // Sensors — the concrete sensor kind is curated (DeviceType 2 alone is ambiguous).
11488
11488
  T8900: { codec: "sensor", caps: ["contact"], name: "Entry Sensor" },
11489
11489
  T8910: { codec: "sensor", caps: ["battery"], name: "Motion Sensor" },
11490
+ // Smoke Alarm E10. `smoke` is curated: its model hint matches the name, not the T-code.
11491
+ T90S0: { codec: "sensor", caps: ["smoke"], name: "Smoke Alarm E10" },
11490
11492
  // Keypad (DeviceType 11).
11491
11493
  T8960: { codec: "keypad", name: "Keypad" },
11492
11494
  // Clean line — robot vacuums (Tuya-DP transport) + robot mowers (own `mower` codec, Tuya P2P).
@@ -19635,7 +19637,7 @@ var SharedLiveSource = class {
19635
19637
  stream.on("audio", (frame) => this.onAudio(frame));
19636
19638
  stream.on("stop", () => this.onUpstreamEnd());
19637
19639
  stream.on("error", (err) => this.onUpstreamError(err));
19638
- stream.on("unacknowledged", () => this.onStartUnacknowledged());
19640
+ stream.on("unacknowledged", () => this.askForFreshSession("start unacknowledged"));
19639
19641
  stream.start();
19640
19642
  }
19641
19643
  /**
@@ -19654,30 +19656,31 @@ var SharedLiveSource = class {
19654
19656
  this.armWarmWatch();
19655
19657
  }
19656
19658
  /**
19657
- * A start was abandoned unacknowledged. Ask for a replacement session where nothing has been delivered yet.
19659
+ * Ask the owner for a replacement session where nothing has been delivered yet, answering whether one is coming.
19658
19660
  *
19659
- * The abandonment is roughly twenty byte-identical sends with no reply, against acknowledgement latencies of
19660
- * 4–37 ms awake and 238 ms waking, so it is the session that is not being heard rather than a slow device —
19661
- * `P2PSession` says as much: the camera was never told to stream, so this warm-up can only time out. Where
19662
- * media has already flowed the abandonment means something else and this does nothing.
19661
+ * An unacknowledged start is roughly twenty byte-identical sends with no reply, against acknowledgement latencies
19662
+ * of 4–37 ms awake and 238 ms waking, so it is the session that is not being heard rather than a slow device: the
19663
+ * camera was never told to stream. Where media has already flowed the abandonment means something else and this
19664
+ * does nothing. The DEADLINE is left running: the window belongs to the attempt, not to the session it started on.
19663
19665
  *
19664
- * The retry ticker is stopped while a replacement is awaited, because every tick it issues goes to the same
19665
- * unheard session. The DEADLINE is left running: the window belongs to the attempt, not to the session it
19666
- * started on.
19666
+ * The retry ticker is stopped only once the owner has said it is replacing the session: an owner that cannot
19667
+ * (an attached camera shares its HomeBase session) leaves the source re-issuing as it always did.
19667
19668
  */
19668
- onStartUnacknowledged() {
19669
+ askForFreshSession(why) {
19669
19670
  if (this.disposed || this.sessionReplacementAsked)
19670
- return;
19671
+ return false;
19671
19672
  if (this.delivered.video || this.delivered.audio || this.delivered.keyframe)
19672
- return;
19673
+ return false;
19673
19674
  if (!this.opts.onSessionUnreachable)
19674
- return;
19675
+ return false;
19675
19676
  this.sessionReplacementAsked = true;
19676
- this.logger.debug(`${this.tag} start unacknowledged with nothing delivered \u2014 asking for a fresh session`);
19677
+ if (!this.opts.onSessionUnreachable())
19678
+ return false;
19679
+ this.logger.debug(`${this.tag} ${why} with nothing delivered \u2014 warming again on a fresh session`);
19677
19680
  if (this.warmRetryTimer)
19678
19681
  clearInterval(this.warmRetryTimer);
19679
19682
  this.warmRetryTimer = void 0;
19680
- this.opts.onSessionUnreachable();
19683
+ return true;
19681
19684
  }
19682
19685
  /**
19683
19686
  * Warm again on a session the owner has replaced, inside the deadline the first attempt started.
@@ -19720,17 +19723,24 @@ var SharedLiveSource = class {
19720
19723
  * {@link fruitlessReissues} for why one is not enough and why what the stream delivered earlier is not
19721
19724
  * evidence. Once media arrives the keepalive is what is wanted, and an attached camera re-sends a full start
19722
19725
  * either way.
19726
+ *
19727
+ * A forced start that a whole interval later has still produced nothing is a session that acknowledges and
19728
+ * does not serve: a fresh session on the same camera delivers a keyframe within a second, so a warm-up asks
19729
+ * for one rather than forcing starts onto this one until its deadline. Wired only: a cold battery camera is
19730
+ * slow rather than deaf, measured at about 8 s to first media, and replacing its session would cost a wake.
19723
19731
  */
19724
19732
  reissueStart() {
19725
19733
  const current = this.stream;
19726
19734
  if (!current?.nudge)
19727
19735
  return;
19728
- this.warmAttempts++;
19729
19736
  const arrivedSinceWatch = this.delivered.video || this.delivered.audio;
19730
19737
  if (arrivedSinceWatch)
19731
19738
  this.fruitlessReissues = 0;
19732
19739
  else
19733
19740
  this.fruitlessReissues++;
19741
+ if (this.fruitlessReissues > 2 && this._state === "warming" && this.powered === "wired" && this.askForFreshSession("forced start served nothing"))
19742
+ return;
19743
+ this.warmAttempts++;
19734
19744
  current.nudge(!arrivedSinceWatch && this.fruitlessReissues > 1);
19735
19745
  }
19736
19746
  /**
@@ -22420,11 +22430,11 @@ var P2PCommandRouter = class _P2PCommandRouter {
22420
22430
  void this.manager.close(station).catch((error) => this.reportError(error instanceof Error ? error : new Error(String(error)))).finally(() => this.dropLiveSource(key));
22421
22431
  }
22422
22432
  /**
22423
- * Replace the session under a warming source whose media start nothing acknowledged, and warm again on it.
22433
+ * Replace the session under a warming source it is not serving, and warm again on it.
22424
22434
  *
22425
22435
  * Only a STANDALONE device's session is replaced, for the reason {@link onLiveStartFailed} gives: an attached
22426
22436
  * camera shares its HomeBase session with every other camera on it, and closing that to recover one would
22427
- * drop the rest. Such a source keeps the re-issue it always had.
22437
+ * drop the rest. Such a source keeps the re-issue it always had, which the answer (false) tells it.
22428
22438
  *
22429
22439
  * The source is left warming throughout, holding the deadline it started, so this either produces a stream
22430
22440
  * within that window or fails exactly as it would have. A replacement that cannot be opened leaves the
@@ -22433,7 +22443,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22433
22443
  replaceUnreachableSession(sn, key, held) {
22434
22444
  const station = this.stationKeyOf(sn);
22435
22445
  if (station !== sn)
22436
- return;
22446
+ return false;
22437
22447
  void (async () => {
22438
22448
  try {
22439
22449
  await this.manager.close(station);
@@ -22446,6 +22456,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22446
22456
  this.reportError(error instanceof Error ? error : new Error(String(error)));
22447
22457
  }
22448
22458
  })();
22459
+ return true;
22449
22460
  }
22450
22461
  /**
22451
22462
  * Warn when a caller asks for a shared source with options that disagree with the ones it was built