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