@mega-yfue/eufy-sdk 0.3.0-beta.0 → 0.3.0-beta.2

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
@@ -5813,6 +5813,7 @@ var LIGHT_SWITCH_JSON_TYPES = /* @__PURE__ */ new Set([
5813
5813
  DeviceType.CAMERA_4G_S330
5814
5814
  ]);
5815
5815
  var LIGHT_SWITCH_INT_STRING_TYPES = /* @__PURE__ */ new Set([
5816
+ DeviceType.FLOODLIGHT_CAMERA_8423,
5816
5817
  DeviceType.INDOOR_OUTDOOR_CAMERA_1080P_NO_LIGHT,
5817
5818
  DeviceType.INDOOR_OUTDOOR_CAMERA_2K,
5818
5819
  DeviceType.INDOOR_OUTDOOR_CAMERA_1080P,
@@ -15139,6 +15140,7 @@ var LIVE_START_RETRANSMIT_MS = 150;
15139
15140
  var LIVE_START_ACK_DEADLINE_MS = 3e3;
15140
15141
  var SEQUENCE_LOOKBACK = 32768;
15141
15142
  var STALE_RETRANSMIT_DEPTH = 1024;
15143
+ var PUNCH_PROBE_SOCKETS = 7;
15142
15144
  var MAX_TRACED_DATAGRAM_GAPS = 8;
15143
15145
  var CMD_SET_PAYLOAD = 1350;
15144
15146
  var CMD_NOTIFY_PAYLOAD = 1351;
@@ -15200,8 +15202,12 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15200
15202
  lookupTimer;
15201
15203
  heartbeatTimer;
15202
15204
  connectTimer;
15203
- /** Our own bound host:port, self-reported inside LOOKUP_WITH_KEY requests (see sendLookups). */
15204
- selfAddress;
15205
+ /** Extra sockets registered alongside {@link socket} while connecting; see {@link PUNCH_PROBE_SOCKETS}. */
15206
+ probeSockets = [];
15207
+ /** Cloud lookup inputs resolved once for a connection. */
15208
+ cloudLookup;
15209
+ /** Local outbound IPv4 reported inside LOOKUP_WITH_KEY requests. */
15210
+ selfHost;
15205
15211
  /** In-flight multi-datagram frame per data channel (see onData). */
15206
15212
  pendingByDataType = /* @__PURE__ */ new Map();
15207
15213
  /** Last datagram sequence number seen per dataType — used to detect a lost/reordered datagram
@@ -15496,7 +15502,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15496
15502
  }
15497
15503
  const socket = dgram.createSocket("udp4");
15498
15504
  this.socket = socket;
15499
- socket.on("message", (msg, rinfo) => this.onMessage(msg, rinfo));
15505
+ socket.on("message", (msg, rinfo) => this.onMessage(msg, rinfo, socket));
15500
15506
  socket.on("error", (e) => this.emit("error", e));
15501
15507
  await new Promise((resolve) => {
15502
15508
  socket.bind(() => {
@@ -15507,15 +15513,20 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15507
15513
  resolve();
15508
15514
  });
15509
15515
  });
15510
- const boundPort = socket.address().port;
15511
15516
  void _P2PSession.detectLocalIp().then((host) => {
15512
15517
  if (host && !this.closed)
15513
- this.selfAddress = { host, port: boundPort };
15518
+ this.selfHost = host;
15514
15519
  });
15520
+ const { dskKey, cloudAddresses } = this.cfg;
15521
+ this.cloudLookup = dskKey && cloudAddresses?.length ? { key: dskKey, addresses: cloudAddresses } : void 0;
15522
+ if (this.cloudLookup)
15523
+ await this.startPunchProbes();
15524
+ if (this.closed)
15525
+ return;
15515
15526
  this.trace({
15516
15527
  phase: "lookup-channels",
15517
15528
  local: !this.cfg.noBroadcast || this.cfg.localAddress !== void 0,
15518
- cloud: this.cfg.dskKey !== void 0 && (this.cfg.cloudAddresses?.length ?? 0) > 0
15529
+ cloud: this.cloudLookup !== void 0
15519
15530
  });
15520
15531
  this.sendLookups();
15521
15532
  this.lookupTimer = setInterval(() => this.sendLookups(), LOOKUP_RETRY_MS);
@@ -15593,14 +15604,20 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15593
15604
  this.send({ host: "255.255.255.255", port: LOCAL_LOOKUP_PORT }, RequestMessageType.LOCAL_LOOKUP, localPayload);
15594
15605
  if (this.cfg.localAddress)
15595
15606
  this.send({ host: this.cfg.localAddress, port: LOCAL_LOOKUP_PORT }, RequestMessageType.LOCAL_LOOKUP, localPayload);
15596
- if (this.cfg.dskKey && this.cfg.cloudAddresses?.length) {
15597
- const payload = this.selfAddress ? buildLookupWithKeyPayload(this.cfg.p2pDid, this.selfAddress.host, this.selfAddress.port, this.cfg.dskKey) : buildLookupWithKeyPayload2(this.cfg.p2pDid, this.cfg.dskKey);
15598
- const type = this.selfAddress ? RequestMessageType.LOOKUP_WITH_KEY : RequestMessageType.LOOKUP_WITH_KEY2;
15599
- for (const addr of this.cfg.cloudAddresses)
15600
- this.send(addr, type, payload);
15601
- this.logger.debug(`[p2p] ${this.cfg.stationSn} sendLookups: cloud -> ${this.cfg.cloudAddresses.map((a) => `${a.host}:${a.port}`).join(", ")} self=${this.selfAddress?.host}:${this.selfAddress?.port}`);
15607
+ const lookup2 = this.cloudLookup;
15608
+ if (lookup2) {
15609
+ for (const socket of [this.socket, ...this.probeSockets])
15610
+ this.sendCloudLookup(socket, lookup2);
15611
+ this.logger.debug(`[p2p] ${this.cfg.stationSn} sendLookups: cloud -> ${lookup2.addresses.map((a) => `${a.host}:${a.port}`).join(", ")} self=${this.selfHost ?? "unknown"}`);
15602
15612
  }
15603
15613
  }
15614
+ /** Register a bound socket's own port with each configured cloud lookup address. */
15615
+ sendCloudLookup(socket, lookup2) {
15616
+ const payload = this.selfHost ? buildLookupWithKeyPayload(this.cfg.p2pDid, this.selfHost, socket.address().port, lookup2.key) : buildLookupWithKeyPayload2(this.cfg.p2pDid, lookup2.key);
15617
+ const type = this.selfHost ? RequestMessageType.LOOKUP_WITH_KEY : RequestMessageType.LOOKUP_WITH_KEY2;
15618
+ for (const addr of lookup2.addresses)
15619
+ this.send(addr, type, payload, socket);
15620
+ }
15604
15621
  /**
15605
15622
  * Route one inbound UDP datagram by its message type, tracing every non-DATA one and any type this session
15606
15623
  * does not model.
@@ -15612,28 +15629,30 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15612
15629
  * recur across later probes of it. Correlate an unmodelled type against a WORKING session before reading it
15613
15630
  * as a cause.
15614
15631
  */
15615
- onMessage(msg, rinfo) {
15632
+ onMessage(msg, rinfo, socket = this.socket) {
15633
+ if (!socket)
15634
+ return;
15616
15635
  if (!hasHeader(msg, ResponseMessageType.DATA)) {
15617
15636
  this.logger.debug(`[p2p] ${this.cfg.stationSn} <<< ${rinfo.address}:${rinfo.port} header=${msg.subarray(0, 2).toString("hex")} len=${msg.length}`);
15618
15637
  }
15619
15638
  if (hasHeader(msg, ResponseMessageType.LOCAL_LOOKUP_RESP)) {
15620
15639
  if (!this.connected)
15621
- this.beginCheckCam({ host: rinfo.address, port: rinfo.port });
15640
+ this.beginCheckCam({ host: rinfo.address, port: rinfo.port }, socket);
15622
15641
  } else if (hasHeader(msg, ResponseMessageType.LOOKUP_ADDR) || hasHeader(msg, ResponseMessageType.LOOKUP_ADDR2)) {
15623
15642
  if (!this.connected) {
15624
15643
  const addr = parseLookupAddr(msg);
15625
15644
  this.logger.debug(`[p2p] ${this.cfg.stationSn} LOOKUP_ADDR -> ${addr.host}:${addr.port}`);
15626
15645
  if (addr.host !== "0.0.0.0")
15627
- this.beginCheckCam(addr);
15646
+ this.beginCheckCam(addr, socket);
15628
15647
  }
15629
15648
  } else if (hasHeader(msg, ResponseMessageType.CAM_ID) || hasHeader(msg, ResponseMessageType.TURN_SERVER_CAM_ID)) {
15630
- this.onConnected({ host: rinfo.address, port: rinfo.port });
15649
+ this.onConnected({ host: rinfo.address, port: rinfo.port }, socket);
15631
15650
  } else if (hasHeader(msg, ResponseMessageType.PONG)) {
15632
15651
  this.lastPongData = msg.length > 4 ? msg.subarray(4) : void 0;
15633
15652
  this.lastPongAt = Date.now();
15634
15653
  this.pathStaleTraced = false;
15635
15654
  } else if (hasHeader(msg, ResponseMessageType.PING)) {
15636
- this.send({ host: rinfo.address, port: rinfo.port }, RequestMessageType.PONG);
15655
+ this.send({ host: rinfo.address, port: rinfo.port }, RequestMessageType.PONG, void 0, socket);
15637
15656
  } else if (hasHeader(msg, ResponseMessageType.ACK)) {
15638
15657
  this.onAck(msg);
15639
15658
  } else if (hasHeader(msg, ResponseMessageType.DATA)) {
@@ -15643,17 +15662,67 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15643
15662
  this.logger.debug(`[p2p] ${this.cfg.stationSn} UNHANDLED payload hex: ${msg.toString("hex")}`);
15644
15663
  }
15645
15664
  }
15646
- beginCheckCam(addr) {
15665
+ beginCheckCam(addr, socket) {
15647
15666
  this.logger.debug(`[p2p] ${this.cfg.stationSn} beginCheckCam -> ${addr.host}:${addr.port} (+/-3)`);
15648
15667
  const payload = buildCheckCamPayload(this.cfg.p2pDid);
15649
- this.send(addr, RequestMessageType.CHECK_CAM, payload);
15668
+ this.send(addr, RequestMessageType.CHECK_CAM, payload, socket);
15650
15669
  for (let p = addr.port - 3; p <= addr.port + 3; p++)
15651
15670
  if (p !== addr.port && p > 0)
15652
- this.send({ host: addr.host, port: p }, RequestMessageType.CHECK_CAM, payload);
15671
+ this.send({ host: addr.host, port: p }, RequestMessageType.CHECK_CAM, payload, socket);
15672
+ }
15673
+ /** Bind additional UDP source ports before the first cloud lookup. */
15674
+ async startPunchProbes() {
15675
+ await Promise.all(Array.from({ length: PUNCH_PROBE_SOCKETS }, async () => {
15676
+ const probe = dgram.createSocket("udp4");
15677
+ probe.on("error", (error) => this.logger.warn("[p2p] lookup socket error", error.message));
15678
+ probe.on("message", (msg, rinfo) => this.onMessage(msg, rinfo, probe));
15679
+ this.probeSockets.push(probe);
15680
+ await new Promise((resolve) => {
15681
+ let settled = false;
15682
+ const finish = (bound) => {
15683
+ if (settled)
15684
+ return;
15685
+ settled = true;
15686
+ probe.off("error", onError);
15687
+ probe.off("close", onClose);
15688
+ if (!bound) {
15689
+ this.probeSockets = this.probeSockets.filter((socket) => socket !== probe);
15690
+ try {
15691
+ probe.close();
15692
+ } catch {
15693
+ }
15694
+ }
15695
+ resolve();
15696
+ };
15697
+ const onError = () => finish(false);
15698
+ const onClose = () => finish(false);
15699
+ probe.once("error", onError);
15700
+ probe.once("close", onClose);
15701
+ probe.bind(() => finish(true));
15702
+ });
15703
+ }));
15653
15704
  }
15654
- onConnected(addr) {
15705
+ /** Close all lookup sockets except the one that completed the handshake. */
15706
+ stopPunchProbes(winner) {
15707
+ for (const probe of this.probeSockets) {
15708
+ if (probe === winner)
15709
+ continue;
15710
+ probe.removeAllListeners("message");
15711
+ probe.close();
15712
+ }
15713
+ this.probeSockets = [];
15714
+ }
15715
+ onConnected(addr, socket) {
15655
15716
  if (this.connected)
15656
15717
  return;
15718
+ this.stopPunchProbes(socket);
15719
+ if (this.socket !== socket) {
15720
+ this.socket?.removeAllListeners();
15721
+ this.socket?.close();
15722
+ socket.removeAllListeners("error");
15723
+ socket.on("error", (error) => this.emit("error", error));
15724
+ this.socket = socket;
15725
+ }
15657
15726
  this.connected = true;
15658
15727
  this.connectedAtMs = Date.now();
15659
15728
  this.connecting = false;
@@ -16540,11 +16609,11 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16540
16609
  }
16541
16610
  this.emit("data", frame);
16542
16611
  }
16543
- send(addr, type, payload) {
16544
- if (!this.socket)
16612
+ send(addr, type, payload, socket = this.socket) {
16613
+ if (!socket)
16545
16614
  return;
16546
16615
  const packet = frameMessage(type, payload);
16547
- this.socket.send(packet, addr.port, addr.host, (err) => {
16616
+ socket.send(packet, addr.port, addr.host, (err) => {
16548
16617
  if (err)
16549
16618
  this.logger.warn(`[p2p] send err ${addr.host}:${addr.port}`, err.message);
16550
16619
  });
@@ -16553,6 +16622,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16553
16622
  if (this.closed)
16554
16623
  return;
16555
16624
  this.closed = true;
16625
+ this.stopPunchProbes();
16556
16626
  this.connectionGeneration += 1;
16557
16627
  this.level2Key = void 0;
16558
16628
  this.level2Seq = 0;