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

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
@@ -16227,6 +16227,10 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16227
16227
  * change (the level-2 key arrived after an initial level-1 start), otherwise sends the 1139 nudge.
16228
16228
  */
16229
16229
  liveStartedChannels = /* @__PURE__ */ new Map();
16230
+ /** The `cipher_id` this connection's `CMD_GATEWAYINFO` reply named, once its negotiation has read it. */
16231
+ gatewayCipherId;
16232
+ /** The `n` of the last `[0x01, n]` header this connection sent in the clear — see {@link nextClearCommandBytes}. */
16233
+ clearCommandCount = 0;
16230
16234
  unackedLiveStarts = /* @__PURE__ */ new Map();
16231
16235
  liveStartRetransmitTimer;
16232
16236
  logger;
@@ -16424,6 +16428,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16424
16428
  this.level2Negotiating = true;
16425
16429
  const generation = this.connectionGeneration;
16426
16430
  const cipherId = gatewayInfoCipherId(gwPayload);
16431
+ this.gatewayCipherId = cipherId;
16427
16432
  this.trace({ phase: "level2-negotiating", cipherId });
16428
16433
  void (async () => {
16429
16434
  try {
@@ -16530,6 +16535,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16530
16535
  this.level2Pending = this.cfg.resolveCipherKey !== void 0;
16531
16536
  this.level2Negotiating = false;
16532
16537
  this.level2Reprompted = false;
16538
+ this.gatewayCipherId = void 0;
16539
+ this.clearCommandCount = 0;
16533
16540
  }
16534
16541
  const socket = dgram.createSocket("udp4");
16535
16542
  this.socket = socket;
@@ -16820,7 +16827,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16820
16827
  this.sendMediaPayloadLevel2(CMD_START_REALTIME_MEDIA, channel, accountId, {});
16821
16828
  return;
16822
16829
  }
16823
- const want = this.level2Key ? "l2" : "l1";
16830
+ const want = this.level2Key ? "l2" : this.sendsInTheClear() ? "clear" : "l1";
16824
16831
  if (opts?.force) {
16825
16832
  for (const pending of this.unackedLiveStarts.values())
16826
16833
  if (pending.channel === channel)
@@ -16850,8 +16857,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16850
16857
  * `video_type` is 12, and `transaction` is the millisecond timestamp as a string. The device answers a
16851
16858
  * start carrying these values with a keyframe; `encryptkey` is the modulus it RSA-wraps each keyframe's
16852
16859
  * AES media key with (unwrapped by {@link decodeVideoFrame}).
16860
+ *
16861
+ * With `clearMsgId`, the body of a start sent in the clear: `{accountId, encryptkey, msg_id, streamtype: 1}`
16862
+ * only, `msg_id` being `clearMsgId`, which changes from one start to the next.
16853
16863
  */
16854
- startLiveJson(channel, accountId) {
16864
+ startLiveJson(channel, accountId, clearMsgId) {
16865
+ if (clearMsgId !== void 0) {
16866
+ return Buffer.from(JSON.stringify({
16867
+ commandType: CMD_START_LIVE,
16868
+ data: { accountId, encryptkey: this.rsaModulus(), msg_id: clearMsgId, streamtype: 1 }
16869
+ }), "utf-8");
16870
+ }
16855
16871
  const now2 = Date.now();
16856
16872
  return Buffer.from(JSON.stringify({
16857
16873
  commandType: CMD_START_LIVE,
@@ -16882,25 +16898,31 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16882
16898
  * (GCM, signCode 8, frame type 10) when the session has a negotiated key, else level-1 (AES-128-ECB,
16883
16899
  * signCode 1, frame type 11) — chosen by the session key, not the device family. The camera RSA-wraps
16884
16900
  * each keyframe's AES media key with the `encryptkey` modulus (unwrapped by {@link decodeVideoFrame}).
16901
+ * A session that {@link sendsInTheClear} sends it unencrypted instead: signCode 0, frame type 0, header
16902
+ * bytes `[0x01, n]` from {@link nextClearCommandBytes}, and `n` as the body's `msg_id`.
16885
16903
  */
16886
16904
  sendStartLiveOwnSession(channel, accountId) {
16887
16905
  if (!this.connectAddress)
16888
16906
  return;
16889
- const plain = this.startLiveJson(channel, accountId);
16890
16907
  let payload;
16891
16908
  let signCode;
16892
16909
  let magic;
16893
16910
  let frameType;
16894
16911
  if (this.level2Key) {
16895
- const enc2 = this.encryptLevel2(plain);
16912
+ const enc2 = this.encryptLevel2(this.startLiveJson(channel, accountId));
16896
16913
  if (!enc2)
16897
16914
  return;
16898
16915
  payload = enc2;
16899
16916
  signCode = 8;
16900
16917
  magic = [8, 0];
16901
16918
  frameType = START_LIVE_FRAME_TYPE_L2;
16919
+ } else if (this.sendsInTheClear()) {
16920
+ magic = this.nextClearCommandBytes();
16921
+ payload = this.startLiveJson(channel, accountId, magic[1]);
16922
+ signCode = 0;
16923
+ frameType = 0;
16902
16924
  } else {
16903
- payload = encryptP2PData(paddingP2PData(plain), this.level1Key);
16925
+ payload = encryptP2PData(paddingP2PData(this.startLiveJson(channel, accountId)), this.level1Key);
16904
16926
  signCode = 1;
16905
16927
  magic = [1, 0];
16906
16928
  frameType = START_LIVE_FRAME_TYPE_L1;
@@ -16920,6 +16942,21 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16920
16942
  this.armLiveStartRetransmit();
16921
16943
  this.send(this.connectAddress, RequestMessageType.DATA, data);
16922
16944
  }
16945
+ /**
16946
+ * Whether this session's own-session start and stop go in the clear: its `CMD_GATEWAYINFO` reply named
16947
+ * `cipher_id` 0.
16948
+ */
16949
+ sendsInTheClear() {
16950
+ return this.gatewayCipherId === 0;
16951
+ }
16952
+ /**
16953
+ * Header bytes 10-11 of this connection's next command sent in the clear: `[0x01, n]`, with `n` counting
16954
+ * those commands from 1 and never 0. A retransmit repeats its `n`, and the device's reply echoes it.
16955
+ */
16956
+ nextClearCommandBytes() {
16957
+ this.clearCommandCount = this.clearCommandCount % 255 + 1;
16958
+ return [1, this.clearCommandCount];
16959
+ }
16923
16960
  /**
16924
16961
  * Repeat unacknowledged own-session live starts until the device takes one. Runs only while a start is
16925
16962
  * outstanding.
@@ -17082,6 +17119,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
17082
17119
  * `uint32`, the same 4-byte body as the attached talkback frames. The app sends this frame on closing
17083
17120
  * an own-session camera's live view; the camera stops streaming on it within a second, and keeps
17084
17121
  * streaming through the 1350-wrapped form.
17122
+ * - own-session on a session that {@link sendsInTheClear}: the same 4-byte body unencrypted, signCode 0,
17123
+ * header bytes from {@link nextClearCommandBytes}.
17085
17124
  * - without a level-2 key: the bare 1004 command.
17086
17125
  */
17087
17126
  stopLiveMedia(channel = STATION_CHANNEL3, accountId = "", homeBaseAttached = false) {
@@ -17094,12 +17133,19 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
17094
17133
  clearInterval(this.liveStartRetransmitTimer);
17095
17134
  this.liveStartRetransmitTimer = void 0;
17096
17135
  }
17136
+ const body = Buffer.allocUnsafe(4);
17137
+ body.writeUInt32LE(channel >>> 0, 0);
17097
17138
  if (this.level2Key && !homeBaseAttached) {
17098
- const body = Buffer.allocUnsafe(4);
17099
- body.writeUInt32LE(channel >>> 0, 0);
17100
17139
  this.sendRawLevel2Bytes(body, channel, CMD_STOP_REALTIME_MEDIA, 8);
17101
17140
  } else if (this.level2Key) {
17102
17141
  this.sendMediaPayloadLevel2(CMD_STOP_REALTIME_MEDIA, channel, accountId, {});
17142
+ } else if (!homeBaseAttached && this.sendsInTheClear() && this.connectAddress) {
17143
+ const data = Buffer.concat([
17144
+ buildCommandHeader(this.seqNumber, CMD_STOP_REALTIME_MEDIA),
17145
+ buildRawCommandPayload(body, channel, 0, this.nextClearCommandBytes(), 0)
17146
+ ]);
17147
+ this.seqNumber = this.seqNumber + 1 & 65535;
17148
+ this.send(this.connectAddress, RequestMessageType.DATA, data);
17103
17149
  } else {
17104
17150
  this.sendCommand(CMD_STOP_REALTIME_MEDIA, channel);
17105
17151
  }