@mega-yfue/eufy-sdk 0.5.0-beta.84 → 0.5.0-beta.86

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
@@ -5347,6 +5347,8 @@ var CAMERA_CMD = {
5347
5347
  * (1013). (Some models omit full colour; enum in {@link NightVision}.)
5348
5348
  */
5349
5349
  NIGHT_VISION_TYPE: 1277,
5350
+ /** T8442 infrared night vision on/off: direct L1 int-string, hardware verified (0/1). */
5351
+ IC_NIGHT_VISION_TYPE: 1013,
5350
5352
  /**
5351
5353
  * Push-notification STYLE — how much a detection push carries: text alone, a thumbnail, or text
5352
5354
  * followed by a thumbnail. App `CMD_INDOOR_PUSH_NOTIFY_TYPE`, and the "indoor" is a misnomer: the
@@ -5537,6 +5539,9 @@ function powerValue(on, ctx) {
5537
5539
  function isT8410HomeBase3(ctx) {
5538
5540
  return ctx.model === "T8410" && ctx.stationSerial?.startsWith("T8030") === true;
5539
5541
  }
5542
+ function isT8442(ctx) {
5543
+ return ctx.model === "T8442";
5544
+ }
5540
5545
  function usesHomeBasePowerPayload(ctx) {
5541
5546
  if (!isT8410HomeBase3(ctx) || !ctx.firmwareVersion || !/^\d+(?:\.\d+){2,3}$/.test(ctx.firmwareVersion))
5542
5547
  return false;
@@ -5758,12 +5763,22 @@ var CAMERA_MEMBERS = {
5758
5763
  type: "enum",
5759
5764
  kind: "enum",
5760
5765
  enumValues: { 0: "Off", 1: "Infrared", 2: "Full Color" },
5766
+ enumValuesFor: (ctx) => isT8442(ctx) ? { 0: "Off", 1: "Infrared" } : void 0,
5761
5767
  provenance: "verified",
5762
5768
  available: isCameraCodec,
5763
- description: "Night-vision mode (NIGHT_VISION_TYPE 1277): 0 = off, 1 = infrared (B&W), 2 = full colour. \u2705 wire verified live (T8425 ch3): 1350 SET_PAYLOAD, mChannel 0, {channel:N, night_sion:mode}. Enum labels are best-guess. Some models omit full colour.",
5769
+ readAvailable: (ctx) => !isT8442(ctx),
5770
+ readAliases: [{ paramType: 2002, available: isT8442 }],
5771
+ description: "Night-vision mode: 0 = off, 1 = infrared (B&W), 2 = full colour where supported. T8425 uses NIGHT_VISION_TYPE 1277 (1350 SET_PAYLOAD, night_sion); T8442 reports 2002 and writes direct IC_NIGHT_VISION_TYPE 1013 (int-string, 0/1). Both wires verified live.",
5764
5772
  write: (v, ctx) => {
5765
5773
  const nv = coerceEnumValue(NightVision, v);
5766
- return nv == null ? void 0 : setPayload(CAMERA_CMD.NIGHT_VISION_TYPE, { channel: ctx.channel, night_sion: nv }, ctx, 0, 0, "auto");
5774
+ if (nv == null)
5775
+ return void 0;
5776
+ if (isT8442(ctx)) {
5777
+ if (nv === NightVision.FullColor)
5778
+ throw new Error("nightVision: Full Color is not supported on T8442 (only Off/Infrared)");
5779
+ return setScalar(CAMERA_CMD.IC_NIGHT_VISION_TYPE, nv, ctx, "int-string");
5780
+ }
5781
+ return setPayload(CAMERA_CMD.NIGHT_VISION_TYPE, { channel: ctx.channel, night_sion: nv }, ctx, 0, 0, "auto");
5767
5782
  }
5768
5783
  },
5769
5784
  /**
@@ -16227,6 +16242,10 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16227
16242
  * change (the level-2 key arrived after an initial level-1 start), otherwise sends the 1139 nudge.
16228
16243
  */
16229
16244
  liveStartedChannels = /* @__PURE__ */ new Map();
16245
+ /** The `cipher_id` this connection's `CMD_GATEWAYINFO` reply named, once its negotiation has read it. */
16246
+ gatewayCipherId;
16247
+ /** The `n` of the last `[0x01, n]` header this connection sent in the clear — see {@link nextClearCommandBytes}. */
16248
+ clearCommandCount = 0;
16230
16249
  unackedLiveStarts = /* @__PURE__ */ new Map();
16231
16250
  liveStartRetransmitTimer;
16232
16251
  logger;
@@ -16424,6 +16443,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16424
16443
  this.level2Negotiating = true;
16425
16444
  const generation = this.connectionGeneration;
16426
16445
  const cipherId = gatewayInfoCipherId(gwPayload);
16446
+ this.gatewayCipherId = cipherId;
16427
16447
  this.trace({ phase: "level2-negotiating", cipherId });
16428
16448
  void (async () => {
16429
16449
  try {
@@ -16530,6 +16550,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16530
16550
  this.level2Pending = this.cfg.resolveCipherKey !== void 0;
16531
16551
  this.level2Negotiating = false;
16532
16552
  this.level2Reprompted = false;
16553
+ this.gatewayCipherId = void 0;
16554
+ this.clearCommandCount = 0;
16533
16555
  }
16534
16556
  const socket = dgram.createSocket("udp4");
16535
16557
  this.socket = socket;
@@ -16820,7 +16842,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16820
16842
  this.sendMediaPayloadLevel2(CMD_START_REALTIME_MEDIA, channel, accountId, {});
16821
16843
  return;
16822
16844
  }
16823
- const want = this.level2Key ? "l2" : "l1";
16845
+ const want = this.level2Key ? "l2" : this.sendsInTheClear() ? "clear" : "l1";
16824
16846
  if (opts?.force) {
16825
16847
  for (const pending of this.unackedLiveStarts.values())
16826
16848
  if (pending.channel === channel)
@@ -16850,8 +16872,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16850
16872
  * `video_type` is 12, and `transaction` is the millisecond timestamp as a string. The device answers a
16851
16873
  * start carrying these values with a keyframe; `encryptkey` is the modulus it RSA-wraps each keyframe's
16852
16874
  * AES media key with (unwrapped by {@link decodeVideoFrame}).
16875
+ *
16876
+ * With `clearMsgId`, the body of a start sent in the clear: `{accountId, encryptkey, msg_id, streamtype: 1}`
16877
+ * only, `msg_id` being `clearMsgId`, which changes from one start to the next.
16853
16878
  */
16854
- startLiveJson(channel, accountId) {
16879
+ startLiveJson(channel, accountId, clearMsgId) {
16880
+ if (clearMsgId !== void 0) {
16881
+ return Buffer.from(JSON.stringify({
16882
+ commandType: CMD_START_LIVE,
16883
+ data: { accountId, encryptkey: this.rsaModulus(), msg_id: clearMsgId, streamtype: 1 }
16884
+ }), "utf-8");
16885
+ }
16855
16886
  const now2 = Date.now();
16856
16887
  return Buffer.from(JSON.stringify({
16857
16888
  commandType: CMD_START_LIVE,
@@ -16882,25 +16913,31 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16882
16913
  * (GCM, signCode 8, frame type 10) when the session has a negotiated key, else level-1 (AES-128-ECB,
16883
16914
  * signCode 1, frame type 11) — chosen by the session key, not the device family. The camera RSA-wraps
16884
16915
  * each keyframe's AES media key with the `encryptkey` modulus (unwrapped by {@link decodeVideoFrame}).
16916
+ * A session that {@link sendsInTheClear} sends it unencrypted instead: signCode 0, frame type 0, header
16917
+ * bytes `[0x01, n]` from {@link nextClearCommandBytes}, and `n` as the body's `msg_id`.
16885
16918
  */
16886
16919
  sendStartLiveOwnSession(channel, accountId) {
16887
16920
  if (!this.connectAddress)
16888
16921
  return;
16889
- const plain = this.startLiveJson(channel, accountId);
16890
16922
  let payload;
16891
16923
  let signCode;
16892
16924
  let magic;
16893
16925
  let frameType;
16894
16926
  if (this.level2Key) {
16895
- const enc2 = this.encryptLevel2(plain);
16927
+ const enc2 = this.encryptLevel2(this.startLiveJson(channel, accountId));
16896
16928
  if (!enc2)
16897
16929
  return;
16898
16930
  payload = enc2;
16899
16931
  signCode = 8;
16900
16932
  magic = [8, 0];
16901
16933
  frameType = START_LIVE_FRAME_TYPE_L2;
16934
+ } else if (this.sendsInTheClear()) {
16935
+ magic = this.nextClearCommandBytes();
16936
+ payload = this.startLiveJson(channel, accountId, magic[1]);
16937
+ signCode = 0;
16938
+ frameType = 0;
16902
16939
  } else {
16903
- payload = encryptP2PData(paddingP2PData(plain), this.level1Key);
16940
+ payload = encryptP2PData(paddingP2PData(this.startLiveJson(channel, accountId)), this.level1Key);
16904
16941
  signCode = 1;
16905
16942
  magic = [1, 0];
16906
16943
  frameType = START_LIVE_FRAME_TYPE_L1;
@@ -16920,6 +16957,21 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16920
16957
  this.armLiveStartRetransmit();
16921
16958
  this.send(this.connectAddress, RequestMessageType.DATA, data);
16922
16959
  }
16960
+ /**
16961
+ * Whether this session's own-session start and stop go in the clear: its `CMD_GATEWAYINFO` reply named
16962
+ * `cipher_id` 0.
16963
+ */
16964
+ sendsInTheClear() {
16965
+ return this.gatewayCipherId === 0;
16966
+ }
16967
+ /**
16968
+ * Header bytes 10-11 of this connection's next command sent in the clear: `[0x01, n]`, with `n` counting
16969
+ * those commands from 1 and never 0. A retransmit repeats its `n`, and the device's reply echoes it.
16970
+ */
16971
+ nextClearCommandBytes() {
16972
+ this.clearCommandCount = this.clearCommandCount % 255 + 1;
16973
+ return [1, this.clearCommandCount];
16974
+ }
16923
16975
  /**
16924
16976
  * Repeat unacknowledged own-session live starts until the device takes one. Runs only while a start is
16925
16977
  * outstanding.
@@ -17082,6 +17134,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
17082
17134
  * `uint32`, the same 4-byte body as the attached talkback frames. The app sends this frame on closing
17083
17135
  * an own-session camera's live view; the camera stops streaming on it within a second, and keeps
17084
17136
  * streaming through the 1350-wrapped form.
17137
+ * - own-session on a session that {@link sendsInTheClear}: the same 4-byte body unencrypted, signCode 0,
17138
+ * header bytes from {@link nextClearCommandBytes}.
17085
17139
  * - without a level-2 key: the bare 1004 command.
17086
17140
  */
17087
17141
  stopLiveMedia(channel = STATION_CHANNEL3, accountId = "", homeBaseAttached = false) {
@@ -17094,12 +17148,19 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
17094
17148
  clearInterval(this.liveStartRetransmitTimer);
17095
17149
  this.liveStartRetransmitTimer = void 0;
17096
17150
  }
17151
+ const body = Buffer.allocUnsafe(4);
17152
+ body.writeUInt32LE(channel >>> 0, 0);
17097
17153
  if (this.level2Key && !homeBaseAttached) {
17098
- const body = Buffer.allocUnsafe(4);
17099
- body.writeUInt32LE(channel >>> 0, 0);
17100
17154
  this.sendRawLevel2Bytes(body, channel, CMD_STOP_REALTIME_MEDIA, 8);
17101
17155
  } else if (this.level2Key) {
17102
17156
  this.sendMediaPayloadLevel2(CMD_STOP_REALTIME_MEDIA, channel, accountId, {});
17157
+ } else if (!homeBaseAttached && this.sendsInTheClear() && this.connectAddress) {
17158
+ const data = Buffer.concat([
17159
+ buildCommandHeader(this.seqNumber, CMD_STOP_REALTIME_MEDIA),
17160
+ buildRawCommandPayload(body, channel, 0, this.nextClearCommandBytes(), 0)
17161
+ ]);
17162
+ this.seqNumber = this.seqNumber + 1 & 65535;
17163
+ this.send(this.connectAddress, RequestMessageType.DATA, data);
17103
17164
  } else {
17104
17165
  this.sendCommand(CMD_STOP_REALTIME_MEDIA, channel);
17105
17166
  }