eufy-security-client 3.8.0 → 4.0.0-dev.31

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.
Files changed (49) hide show
  1. package/README.md +24 -0
  2. package/build/eufysecurity.d.ts +2 -0
  3. package/build/eufysecurity.js +17 -0
  4. package/build/eufysecurity.js.map +1 -1
  5. package/build/http/api.d.ts +2 -0
  6. package/build/http/api.js +39 -6
  7. package/build/http/api.js.map +1 -1
  8. package/build/http/decodeImageV2.d.ts +51 -0
  9. package/build/http/decodeImageV2.js +359 -0
  10. package/build/http/decodeImageV2.js.map +1 -0
  11. package/build/http/device.d.ts +13 -1
  12. package/build/http/device.js +70 -15
  13. package/build/http/device.js.map +1 -1
  14. package/build/http/interfaces.d.ts +1 -0
  15. package/build/http/models.d.ts +1 -0
  16. package/build/http/station.d.ts +9 -1
  17. package/build/http/station.js +108 -31
  18. package/build/http/station.js.map +1 -1
  19. package/build/http/types.d.ts +4 -0
  20. package/build/http/types.js +218 -7
  21. package/build/http/types.js.map +1 -1
  22. package/build/http/utils.js +19 -0
  23. package/build/http/utils.js.map +1 -1
  24. package/build/p2p/adts.d.ts +12 -0
  25. package/build/p2p/adts.js +185 -0
  26. package/build/p2p/adts.js.map +1 -0
  27. package/build/p2p/interfaces.d.ts +10 -0
  28. package/build/p2p/session.d.ts +3 -1
  29. package/build/p2p/session.js +138 -30
  30. package/build/p2p/session.js.map +1 -1
  31. package/build/p2p/types.d.ts +1 -0
  32. package/build/p2p/types.js +1 -0
  33. package/build/p2p/types.js.map +1 -1
  34. package/build/p2p/utils.d.ts +11 -0
  35. package/build/p2p/utils.js +76 -4
  36. package/build/p2p/utils.js.map +1 -1
  37. package/coverage/clover.xml +9121 -8765
  38. package/coverage/coverage-final.json +33 -31
  39. package/coverage/lcov-report/index.html +41 -41
  40. package/coverage/lcov.info +18234 -16336
  41. package/package.json +11 -8
  42. package/.idea/eufy-security-client.iml +0 -12
  43. package/.idea/modules.xml +0 -8
  44. package/.idea/vcs.xml +0 -7
  45. package/bin/act +0 -0
  46. package/coverage/lcov-report/cache.ts.html +0 -184
  47. package/coverage/lcov-report/error.ts.html +0 -871
  48. package/coverage/lcov-report/logging.ts.html +0 -598
  49. package/dont-care.js +0 -101
@@ -18,6 +18,7 @@ const ble_1 = require("./ble");
18
18
  const http_1 = require("../http");
19
19
  const utils_3 = require("../utils");
20
20
  const logging_1 = require("../logging");
21
+ const adts_1 = require("./adts");
21
22
  class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
22
23
  MAX_RETRIES = 10;
23
24
  MAX_COMMAND_RESULT_WAIT = 30 * 1000;
@@ -37,6 +38,11 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
37
38
  ESD_DISCONNECT_TIMEOUT = 30 * 1000;
38
39
  MAX_STREAM_DATA_WAIT = 5 * 1000;
39
40
  RESEND_NOT_ACKNOWLEDGED_COMMAND = 100;
41
+ streamTimeouts = {
42
+ streamDataWait: this.MAX_STREAM_DATA_WAIT,
43
+ audioCodecAnalyze: this.AUDIO_CODEC_ANALYZE_TIMEOUT,
44
+ expectedSeqNoWait: this.MAX_EXPECTED_SEQNO_WAIT,
45
+ };
40
46
  UDP_RECVBUFFERSIZE_BYTES = 1048576;
41
47
  MAX_PAYLOAD_BYTES = 1028;
42
48
  MAX_PACKET_BYTES = 1024;
@@ -1240,7 +1246,7 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1240
1246
  this.currentMessageState[dataType].waitForSeqNoTimeout = setTimeout(() => {
1241
1247
  this.endStream(dataType, true);
1242
1248
  this.currentMessageState[dataType].waitForSeqNoTimeout = undefined;
1243
- }, this.MAX_EXPECTED_SEQNO_WAIT);
1249
+ }, this.streamTimeouts.expectedSeqNoWait);
1244
1250
  if (!this.currentMessageState[dataType].queuedData.get(message.seqNo)) {
1245
1251
  this.currentMessageState[dataType].queuedData.set(message.seqNo, message);
1246
1252
  logging_1.rootP2PLogger.trace(`Received message - DATA ${types_1.P2PDataType[message.type]} - Received not expected sequence, added to the queue for future processing`, {
@@ -1514,24 +1520,36 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1514
1520
  let return_code = 0;
1515
1521
  let resultData;
1516
1522
  if (message.bytesToRead > 0) {
1517
- if (message.signCode > 0) {
1518
- try {
1519
- message.data = (0, utils_1.decryptP2PData)(message.data, this.p2pKey);
1523
+ if (message.signCode > 0 && message.data.length > 0) {
1524
+ if (message.data.length % 16 === 0) {
1525
+ try {
1526
+ message.data = (0, utils_1.decryptP2PData)(message.data, this.p2pKey);
1527
+ }
1528
+ catch (err) {
1529
+ const error = (0, error_1.ensureError)(err);
1530
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - Decrypt Error`, {
1531
+ error: (0, utils_3.getError)(error),
1532
+ stationSN: this.rawStation.station_sn,
1533
+ message: {
1534
+ seqNo: message.seqNo,
1535
+ channel: message.channel,
1536
+ commandType: types_1.CommandType[message.commandId],
1537
+ signCode: message.signCode,
1538
+ type: message.type,
1539
+ dataType: types_1.P2PDataType[message.dataType],
1540
+ data: message.data.toString("hex"),
1541
+ },
1542
+ });
1543
+ }
1520
1544
  }
1521
- catch (err) {
1522
- const error = (0, error_1.ensureError)(err);
1523
- logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - Decrypt Error`, {
1524
- error: (0, utils_3.getError)(error),
1545
+ else {
1546
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - Skipping decryption, data not block-aligned`, {
1525
1547
  stationSN: this.rawStation.station_sn,
1526
- message: {
1527
- seqNo: message.seqNo,
1528
- channel: message.channel,
1529
- commandType: types_1.CommandType[message.commandId],
1530
- signCode: message.signCode,
1531
- type: message.type,
1532
- dataType: types_1.P2PDataType[message.dataType],
1533
- data: message.data.toString("hex"),
1534
- },
1548
+ seqNo: message.seqNo,
1549
+ commandType: types_1.CommandType[message.commandId],
1550
+ signCode: message.signCode,
1551
+ dataLength: message.data.length,
1552
+ mod16: message.data.length % 16,
1535
1553
  });
1536
1554
  }
1537
1555
  }
@@ -1770,6 +1788,7 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1770
1788
  }
1771
1789
  isIFrame(data, isKeyFrame) {
1772
1790
  if (this.rawStation.station_sn.startsWith("T8410") ||
1791
+ this.rawStation.station_sn.startsWith("T8417") ||
1773
1792
  this.rawStation.station_sn.startsWith("T8400") ||
1774
1793
  this.rawStation.station_sn.startsWith("T8401") ||
1775
1794
  this.rawStation.station_sn.startsWith("T8411") ||
@@ -1801,9 +1820,9 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1801
1820
  clearTimeout(this.currentMessageState[dataType].p2pStreamingTimeout);
1802
1821
  }
1803
1822
  this.currentMessageState[dataType].p2pStreamingTimeout = setTimeout(() => {
1804
- logging_1.rootP2PLogger.info(`Stopping the station stream for the device ${this.deviceSNs[this.currentMessageState[dataType].p2pStreamChannel]?.sn}, because we haven't received any data for ${this.MAX_STREAM_DATA_WAIT / 1000} seconds`);
1823
+ logging_1.rootP2PLogger.info(`Stopping the station stream for the device ${this.deviceSNs[this.currentMessageState[dataType].p2pStreamChannel]?.sn}, because we haven't received any data for ${this.streamTimeouts.streamDataWait / 1000} seconds`);
1805
1824
  this.endStream(dataType, sendStopCommand);
1806
- }, this.MAX_STREAM_DATA_WAIT);
1825
+ }, this.streamTimeouts.streamDataWait);
1807
1826
  }
1808
1827
  handleDataBinaryAndVideo(message) {
1809
1828
  if (!this.currentMessageState[message.dataType].invalidStream) {
@@ -1883,6 +1902,7 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1883
1902
  this.currentMessageState[message.dataType].p2pStreamMetadata.videoWidth = videoMetaData.videoWidth;
1884
1903
  if (!this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1885
1904
  if (this.rawStation.station_sn.startsWith("T8410") ||
1905
+ this.rawStation.station_sn.startsWith("T8417") ||
1886
1906
  this.rawStation.station_sn.startsWith("T8400") ||
1887
1907
  this.rawStation.station_sn.startsWith("T8401") ||
1888
1908
  this.rawStation.station_sn.startsWith("T8411") ||
@@ -1972,7 +1992,7 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
1972
1992
  this.currentMessageState[message.dataType].p2pStreamNotStarted) {
1973
1993
  this.emitStreamStartEvent(message.dataType);
1974
1994
  }
1975
- }, this.AUDIO_CODEC_ANALYZE_TIMEOUT);
1995
+ }, this.streamTimeouts.audioCodecAnalyze);
1976
1996
  }
1977
1997
  }
1978
1998
  if (this.currentMessageState[message.dataType].p2pStreamNotStarted) {
@@ -2059,7 +2079,18 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
2059
2079
  this.emitStreamStartEvent(message.dataType);
2060
2080
  }
2061
2081
  }
2062
- this.currentMessageState[message.dataType].audioStream?.push(audio_data);
2082
+ {
2083
+ const codec = this.currentMessageState[message.dataType].p2pStreamMetadata.audioCodec;
2084
+ const stream = this.currentMessageState[message.dataType].audioStream;
2085
+ if (stream && (codec === types_1.AudioCodec.AAC || codec === types_1.AudioCodec.AAC_LC)) {
2086
+ for (const frame of (0, adts_1.normalizeAdtsFrames)(audio_data)) {
2087
+ stream.push(frame);
2088
+ }
2089
+ }
2090
+ else {
2091
+ stream?.push(audio_data);
2092
+ }
2093
+ }
2063
2094
  break;
2064
2095
  default:
2065
2096
  logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - Not implemented message`, {
@@ -2376,8 +2407,17 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
2376
2407
  this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_BATTERY_LEVEL, payload.slBattery);
2377
2408
  this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_STATUS, payload.slState);
2378
2409
  }
2410
+ else if (json.cmd === types_1.CommandType.CMD_HUB_NOTIFY_UPDATE) {
2411
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Homebase notify update`, {
2412
+ stationSN: this.rawStation.station_sn,
2413
+ commandIdName: types_1.CommandType[json.cmd],
2414
+ commandId: json.cmd,
2415
+ message: data.toString(),
2416
+ });
2417
+ this.emit("hub notify update");
2418
+ }
2379
2419
  else {
2380
- logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Not implemented`, {
2420
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Not implemented 1`, {
2381
2421
  stationSN: this.rawStation.station_sn,
2382
2422
  commandIdName: types_1.CommandType[json.cmd],
2383
2423
  commandId: json.cmd,
@@ -2424,9 +2464,10 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
2424
2464
  device_1.Device.isLockWifiT8502(this.rawStation.devices[0]?.device_type) ||
2425
2465
  device_1.Device.isLockWifiT8510P(this.rawStation.devices[0]?.device_type, this.rawStation.devices[0]?.device_sn) ||
2426
2466
  device_1.Device.isLockWifiT8520P(this.rawStation.devices[0]?.device_type, this.rawStation.devices[0]?.device_sn) ||
2427
- device_1.Device.isLockWifiT85V0(this.rawStation.devices[0]?.device_type, this.rawStation.devices[0]?.device_sn) ||
2467
+ device_1.Device.isLockWifiT85V0(this.rawStation.devices[0]?.device_type) ||
2428
2468
  device_1.Device.isLockWifiT8531(this.rawStation.devices[0]?.device_type) ||
2429
- device_1.Device.isLockWifiT85L0(this.rawStation.devices[0]?.device_type)) {
2469
+ device_1.Device.isLockWifiT85L0(this.rawStation.devices[0]?.device_type) ||
2470
+ device_1.Device.isLockWifiT85P0(this.rawStation.devices[0]?.device_type)) {
2430
2471
  this.emit("sequence error", message.channel, types_1.SmartLockCommand[payload.bus_type == types_1.SmartLockFunctionType.TYPE_2
2431
2472
  ? types_1.SmartLockBleCommandFunctionType2[payload.lock_cmd]
2432
2473
  : types_1.SmartLockBleCommandFunctionType1[payload.lock_cmd]], payload.seq_num, payload.dev_sn);
@@ -2842,8 +2883,29 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
2842
2883
  this.emit("storage info hb3", message.channel, payload.body);
2843
2884
  }
2844
2885
  }
2886
+ else if (json.cmd === 6246) {
2887
+ const payload = json.payload;
2888
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD Livestream status`, { stationSN: this.rawStation.station_sn, payload: payload });
2889
+ if (payload?.num !== undefined) {
2890
+ if (payload.num > 0) {
2891
+ this.emit("rtsp livestream started", message.channel);
2892
+ }
2893
+ else {
2894
+ this.emit("rtsp livestream stopped", message.channel);
2895
+ }
2896
+ }
2897
+ }
2898
+ else if (json.cmd === types_1.CommandType.CMD_HUB_NOTIFY_UPDATE) {
2899
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Homebase notify update`, {
2900
+ stationSN: this.rawStation.station_sn,
2901
+ commandIdName: types_1.CommandType[json.cmd],
2902
+ commandId: json.cmd,
2903
+ message: data.toString(),
2904
+ });
2905
+ this.emit("hub notify update");
2906
+ }
2845
2907
  else {
2846
- logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Not implemented`, {
2908
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_NOTIFY_PAYLOAD - Not implemented 2`, {
2847
2909
  stationSN: this.rawStation.station_sn,
2848
2910
  commandIdName: types_1.CommandType[json.cmd],
2849
2911
  commandId: json.cmd,
@@ -3212,7 +3274,10 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
3212
3274
  data: data.toString("hex"),
3213
3275
  cipherID: cipherID,
3214
3276
  });
3215
- const encryptedKey = (0, utils_1.readNullTerminatedBuffer)(data.subarray(4));
3277
+ // Keep full raw buffer for ECDH — readNullTerminatedBuffer truncates binary ECIES envelopes at 0x00 bytes
3278
+ const rawEncryptedKey = data.subarray(4);
3279
+ const encryptedKey = (0, utils_1.readNullTerminatedBuffer)(rawEncryptedKey);
3280
+ const isECDHDevice = this.rawStation.station_sn.startsWith("T8214") || this.rawStation.station_sn.startsWith("T8425");
3216
3281
  this.api
3217
3282
  .getCipher(/*this.rawStation.station_sn, */ cipherID, this.rawStation.member.admin_user_id)
3218
3283
  .then((cipher) => {
@@ -3224,10 +3289,49 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
3224
3289
  cipher: JSON.stringify(cipher),
3225
3290
  });
3226
3291
  if (cipher !== undefined) {
3227
- this.encryption = types_1.EncryptionType.LEVEL_2;
3228
- const rsa = (0, utils_1.getRSAPrivateKey)(cipher.private_key, this.enableEmbeddedPKCS1Support);
3229
- this.p2pKey = rsa.decrypt(encryptedKey);
3230
- logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - set encryption level 2`, { stationSN: this.rawStation.station_sn, key: this.p2pKey.toString("hex") });
3292
+ // Try RSA first
3293
+ try {
3294
+ this.encryption = types_1.EncryptionType.LEVEL_2;
3295
+ const rsa = (0, utils_1.getRSAPrivateKey)(cipher.private_key, this.enableEmbeddedPKCS1Support);
3296
+ this.p2pKey = rsa.decrypt(encryptedKey);
3297
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - RSA success - set encryption level 2`, { stationSN: this.rawStation.station_sn, key: this.p2pKey.toString("hex") });
3298
+ }
3299
+ catch (rsaErr) {
3300
+ const rsaError = (0, error_1.ensureError)(rsaErr);
3301
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - RSA decrypt failed`, {
3302
+ error: (0, utils_3.getError)(rsaError),
3303
+ stationSN: this.rawStation.station_sn,
3304
+ isECDHDevice: isECDHDevice,
3305
+ hasEccKey: !!cipher.ecc_private_key,
3306
+ });
3307
+ // Try ECDH only for known ECDH devices (T8214/T8425)
3308
+ if (isECDHDevice && cipher.ecc_private_key) {
3309
+ try {
3310
+ this.encryption = types_1.EncryptionType.LEVEL_2;
3311
+ this.p2pKey = (0, utils_1.decryptP2PKeyECDH)(rawEncryptedKey, cipher.ecc_private_key);
3312
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - ECDH success - set encryption level 2`, {
3313
+ stationSN: this.rawStation.station_sn,
3314
+ key: this.p2pKey.toString("hex"),
3315
+ keyLength: this.p2pKey.length,
3316
+ });
3317
+ }
3318
+ catch (ecdhErr) {
3319
+ const ecdhError = (0, error_1.ensureError)(ecdhErr);
3320
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - ECDH also failed, falling back to Level 1`, {
3321
+ error: (0, utils_3.getError)(ecdhError),
3322
+ stationSN: this.rawStation.station_sn,
3323
+ });
3324
+ this.encryption = types_1.EncryptionType.LEVEL_1;
3325
+ this.p2pKey = Buffer.from((0, utils_1.getP2PCommandEncryptionKey)(this.rawStation.station_sn, this.rawStation.p2p_did));
3326
+ }
3327
+ }
3328
+ else {
3329
+ // Non-ECDH device or no ECC key — fall back to Level 1
3330
+ this.encryption = types_1.EncryptionType.LEVEL_1;
3331
+ this.p2pKey = Buffer.from((0, utils_1.getP2PCommandEncryptionKey)(this.rawStation.station_sn, this.rawStation.p2p_did));
3332
+ logging_1.rootP2PLogger.debug(`Handle DATA ${types_1.P2PDataType[message.dataType]} - CMD_GATEWAYINFO - RSA failed, set encryption level 1`, { stationSN: this.rawStation.station_sn, key: this.p2pKey.toString("hex") });
3333
+ }
3334
+ }
3231
3335
  }
3232
3336
  else {
3233
3337
  this.encryption = types_1.EncryptionType.LEVEL_1;
@@ -3519,6 +3623,9 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
3519
3623
  isLiveStreaming(channel) {
3520
3624
  return this.isStreaming(channel, types_1.P2PDataType.VIDEO);
3521
3625
  }
3626
+ setStreamTimeouts(options) {
3627
+ this.streamTimeouts = { ...this.streamTimeouts, ...options };
3628
+ }
3522
3629
  isCurrentlyStreaming() {
3523
3630
  for (const element of Object.values(this.currentMessageState)) {
3524
3631
  if (element.p2pStreaming || element.p2pTalkback)
@@ -3710,6 +3817,7 @@ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
3710
3817
  return false;
3711
3818
  }
3712
3819
  startTalkback(channel = 0) {
3820
+ (0, utils_1.resetTalkbackCounters)();
3713
3821
  this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback = true;
3714
3822
  this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel = channel;
3715
3823
  this.initializeTalkbackStream(channel);