@mega-yfue/eufy-sdk 0.5.0-beta.93 → 0.5.0-beta.95

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
@@ -15193,6 +15193,8 @@ __export(codec_exports, {
15193
15193
  buildLookupWithKeyPayload: () => buildLookupWithKeyPayload,
15194
15194
  buildLookupWithKeyPayload2: () => buildLookupWithKeyPayload2,
15195
15195
  buildRawCommandPayload: () => buildRawCommandPayload,
15196
+ buildRelayPacketPayload: () => buildRelayPacketPayload,
15197
+ buildRelayRequestPayload: () => buildRelayRequestPayload,
15196
15198
  buildStringCommandPayload: () => buildStringCommandPayload,
15197
15199
  buildStringPairCommandPayload: () => buildStringPairCommandPayload,
15198
15200
  buildVoidCommandPayload: () => buildVoidCommandPayload,
@@ -15211,6 +15213,9 @@ __export(codec_exports, {
15211
15213
  paddingP2PData: () => paddingP2PData,
15212
15214
  parseDataFrameHeader: () => parseDataFrameHeader,
15213
15215
  parseLookupAddr: () => parseLookupAddr,
15216
+ parseRelayList: () => parseRelayList,
15217
+ parseRelayPortAck: () => parseRelayPortAck,
15218
+ parseRelayTo: () => parseRelayTo,
15214
15219
  readNullTerminatedString: () => readNullTerminatedString,
15215
15220
  stringWithLength: () => stringWithLength
15216
15221
  });
@@ -15219,7 +15224,12 @@ var MAGIC_WORD = "XZYH";
15219
15224
  var RequestMessageType = {
15220
15225
  LOCAL_LOOKUP: Buffer.from([241, 48]),
15221
15226
  LOOKUP_WITH_KEY: Buffer.from([241, 38]),
15227
+ /** `ListReq_DSK`: the same body as a variant-2 lookup, answered with {@link ResponseMessageType.LIST_REQ_ACK}. */
15222
15228
  LOOKUP_WITH_KEY2: Buffer.from([241, 106]),
15229
+ RLY_HELLO: Buffer.from([241, 112]),
15230
+ RLY_PORT: Buffer.from([241, 114]),
15231
+ RLY_REQ: Buffer.from([241, 128]),
15232
+ RLY_PKT: Buffer.from([241, 131]),
15223
15233
  CHECK_CAM: Buffer.from([241, 65]),
15224
15234
  CHECK_CAM2: Buffer.from([241, 66]),
15225
15235
  PING: Buffer.from([241, 224]),
@@ -15231,14 +15241,24 @@ var RequestMessageType = {
15231
15241
  var ResponseMessageType = {
15232
15242
  LOCAL_LOOKUP_RESP: Buffer.from([241, 65]),
15233
15243
  LOOKUP_ADDR: Buffer.from([241, 64]),
15234
- LOOKUP_ADDR2: Buffer.from([241, 130]),
15244
+ /** The relay servers a cloud lookup server names: `[count:1][pad:3]` then `count` address records. */
15245
+ LIST_REQ_ACK: Buffer.from([241, 105]),
15246
+ RLY_HELLO_ACK: Buffer.from([241, 113]),
15247
+ /** A relay's answer to RLY_PORT: `[mark:4 BE][port:2 BE][pad:2]`, the port being the one it relays on. */
15248
+ RLY_PORT_ACK: Buffer.from([241, 115]),
15249
+ RLY_REQ_ACK: Buffer.from([241, 129]),
15250
+ /** A relay endpoint to register with: one address record, then `[handle:4 BE]`. */
15251
+ RLY_TO: Buffer.from([241, 130]),
15235
15252
  CAM_ID: Buffer.from([241, 66]),
15253
+ /** A lookup server's answer to a P2P request: `[result:1]` first, 0 when the request is accepted. */
15254
+ P2P_REQ_ACK: Buffer.from([241, 33]),
15236
15255
  /**
15237
15256
  * The device's own address record, sent in answer to CHECK_CAM ahead of CAM_ID: its 20-byte device id, one
15238
- * record in `encodeSelfAddress`'s shape naming the address it answers from, then 8 zero bytes (44-byte payload).
15257
+ * record in `encodeAddress`'s shape naming the address it answers from, then 8 zero bytes (44-byte payload).
15239
15258
  */
15240
15259
  CAM_ADDR: Buffer.from([241, 67]),
15241
- TURN_SERVER_CAM_ID: Buffer.from([241, 132]),
15260
+ /** A relay reporting the path to the device ready: the device's 20-byte id, sent from the relay endpoint. */
15261
+ RLY_RDY: Buffer.from([241, 132]),
15242
15262
  PING: Buffer.from([241, 224]),
15243
15263
  PONG: Buffer.from([241, 225]),
15244
15264
  DATA: Buffer.from([241, 208]),
@@ -15398,7 +15418,7 @@ function buildLocalLookupPayload() {
15398
15418
  function buildLookupWithKeyPayload2(p2pDid, dskKey) {
15399
15419
  return Buffer.concat([p2pDidToBuffer(p2pDid), Buffer.from(dskKey), Buffer.from([0, 0, 0, 0])]);
15400
15420
  }
15401
- function encodeSelfAddress(host, port) {
15421
+ function encodeAddress(host, port) {
15402
15422
  const out = Buffer.alloc(16);
15403
15423
  out.writeUInt16BE(2, 0);
15404
15424
  out.writeUInt16LE(port, 2);
@@ -15412,12 +15432,22 @@ function encodeSelfAddress(host, port) {
15412
15432
  function buildLookupWithKeyPayload(p2pDid, selfHost, selfPort, dskKey) {
15413
15433
  return Buffer.concat([
15414
15434
  p2pDidToBuffer(p2pDid),
15415
- encodeSelfAddress(selfHost, selfPort),
15435
+ encodeAddress(selfHost, selfPort),
15416
15436
  Buffer.from([2, 5, 1, 5]),
15417
15437
  Buffer.from(dskKey),
15418
15438
  Buffer.from([0, 0, 0, 0])
15419
15439
  ]);
15420
15440
  }
15441
+ function buildRelayRequestPayload(p2pDid, relay, mark) {
15442
+ const tail = Buffer.alloc(4);
15443
+ tail.writeUInt32BE(mark >>> 0, 0);
15444
+ return Buffer.concat([p2pDidToBuffer(p2pDid), encodeAddress(relay.host, relay.port), tail]);
15445
+ }
15446
+ function buildRelayPacketPayload(handle, p2pDid) {
15447
+ const head = Buffer.alloc(4);
15448
+ head.writeUInt32BE(handle >>> 0, 0);
15449
+ return Buffer.concat([head, p2pDidToBuffer(p2pDid), Buffer.alloc(4)]);
15450
+ }
15421
15451
  function buildCheckCamPayload(p2pDid) {
15422
15452
  return Buffer.concat([p2pDidToBuffer(p2pDid), Buffer.from([0, 0, 0])]);
15423
15453
  }
@@ -15517,10 +15547,30 @@ function parseDataFrameHeader(frame) {
15517
15547
  type: frame.readUInt8(14)
15518
15548
  };
15519
15549
  }
15550
+ function readAddress(buf, offset) {
15551
+ return {
15552
+ host: `${buf[offset + 7]}.${buf[offset + 6]}.${buf[offset + 5]}.${buf[offset + 4]}`,
15553
+ port: buf.readUInt16LE(offset + 2)
15554
+ };
15555
+ }
15520
15556
  function parseLookupAddr(msg) {
15521
- const port = msg.subarray(6, 8).readUInt16LE();
15522
- const host = `${msg[11]}.${msg[10]}.${msg[9]}.${msg[8]}`;
15523
- return { host, port };
15557
+ return readAddress(msg, 4);
15558
+ }
15559
+ function parseRelayList(msg) {
15560
+ const out = [];
15561
+ const count2 = msg.length > 4 ? msg[4] : 0;
15562
+ for (let i = 0; i < count2 && 8 + 16 * (i + 1) <= msg.length; i++) {
15563
+ const addr = readAddress(msg, 8 + 16 * i);
15564
+ if (addr.host !== "0.0.0.0")
15565
+ out.push(addr);
15566
+ }
15567
+ return out;
15568
+ }
15569
+ function parseRelayPortAck(msg) {
15570
+ return msg.length >= 10 ? { mark: msg.readUInt32BE(4), port: msg.readUInt16BE(8) } : void 0;
15571
+ }
15572
+ function parseRelayTo(msg) {
15573
+ return msg.length >= 24 ? { address: readAddress(msg, 4), handle: msg.readUInt32BE(20) } : void 0;
15524
15574
  }
15525
15575
  function readNullTerminatedString(data, encoding = "utf8") {
15526
15576
  const end = data.indexOf(0);
@@ -16140,6 +16190,8 @@ import { setTimeout as sleep2 } from "node:timers/promises";
16140
16190
  // dist/transport/p2p/p2p-session.js
16141
16191
  import { EventEmitter as EventEmitter2 } from "node:events";
16142
16192
  import dgram from "node:dgram";
16193
+ import { resolve4 } from "node:dns/promises";
16194
+ import { isIP as isIP2 } from "node:net";
16143
16195
  import { constants as constants2, createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, generateKeyPairSync, privateDecrypt as privateDecrypt2, randomBytes as randomBytes4 } from "node:crypto";
16144
16196
 
16145
16197
  // dist/transport/p2p/live-trace.js
@@ -16153,8 +16205,10 @@ var LOCAL_LOOKUP_PORT = 32108;
16153
16205
  var HEARTBEAT_MS = 5e3;
16154
16206
  var PATH_SILENCE_MS = HEARTBEAT_MS * 3;
16155
16207
  var LOOKUP_RETRY_MS = 1e3;
16208
+ var RELAY_AFTER_ACKED_ROUNDS = 24;
16209
+ var RELAY_PACKET_COPIES = 4;
16156
16210
  var RECEIVE_BUFFER_BYTES = 4 * 1024 * 1024;
16157
- var CONNECT_TIMEOUT_MS = 15e3;
16211
+ var CONNECT_TIMEOUT_MS = 45e3;
16158
16212
  var STATION_CHANNEL3 = 255;
16159
16213
  var CMD_GATEWAYINFO = 1100;
16160
16214
  var CMD_START_REALTIME_MEDIA = 1003;
@@ -16177,6 +16231,19 @@ var LIVE_START_RETRANSMIT_MS = 150;
16177
16231
  var LIVE_START_ACK_DEADLINE_MS = 3e3;
16178
16232
  var SEQUENCE_LOOKBACK = 32768;
16179
16233
  var STALE_RETRANSMIT_DEPTH = 1024;
16234
+ async function resolveLookupServers(addresses) {
16235
+ const resolved = await Promise.all(addresses.map(async (addr) => {
16236
+ if (isIP2(addr.host))
16237
+ return addr;
16238
+ try {
16239
+ const [host] = await resolve4(addr.host);
16240
+ return host ? { host, port: addr.port } : void 0;
16241
+ } catch {
16242
+ return void 0;
16243
+ }
16244
+ }));
16245
+ return resolved.filter((addr) => addr !== void 0);
16246
+ }
16180
16247
  var PUNCH_PROBE_SOCKETS = 7;
16181
16248
  var REORDER_WAIT_MS = 250;
16182
16249
  var VIDEO_REORDER_WAIT_MS = 700;
@@ -16297,6 +16364,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16297
16364
  probeSockets = [];
16298
16365
  /** Cloud lookup inputs resolved once for a connection. */
16299
16366
  cloudLookup;
16367
+ /**
16368
+ * Lookup rounds since the lookup server acknowledged this connect's P2P request, or `undefined` while it
16369
+ * has not; see {@link RELAY_AFTER_ACKED_ROUNDS}.
16370
+ */
16371
+ ackedRounds;
16372
+ /**
16373
+ * The relay handshake of a connect that has fallen back to one, by the step it waits on: the relay list
16374
+ * (`list`, answering ListReq_DSK), a relay's hello acknowledgement (`hello`), that relay's port (`port`),
16375
+ * then the relay-ready the device's registration produces (`request`, re-sending RLY_REQ meanwhile).
16376
+ */
16377
+ relay;
16300
16378
  /** Local outbound IPv4 reported inside LOOKUP_WITH_KEY requests. */
16301
16379
  selfHost;
16302
16380
  /**
@@ -16646,6 +16724,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16646
16724
  this.connecting = true;
16647
16725
  this.closed = false;
16648
16726
  this.connectionGeneration += 1;
16727
+ this.ackedRounds = void 0;
16649
16728
  this.resetInboundSequencing();
16650
16729
  if (!this.level2KeyServes("set-payload")) {
16651
16730
  this.level2Pending = this.cfg.resolveCipherKey !== void 0;
@@ -16674,6 +16753,13 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16674
16753
  });
16675
16754
  const { dskKey, cloudAddresses } = this.cfg;
16676
16755
  this.cloudLookup = dskKey && cloudAddresses?.length ? { key: dskKey, addresses: cloudAddresses } : void 0;
16756
+ if (this.cloudLookup && cloudAddresses) {
16757
+ const generation = this.connectionGeneration;
16758
+ void resolveLookupServers(cloudAddresses).then((addresses) => {
16759
+ if (this.cloudLookup && !this.closed && generation === this.connectionGeneration)
16760
+ this.cloudLookup.addresses = addresses;
16761
+ });
16762
+ }
16677
16763
  if (this.cloudLookup)
16678
16764
  await this.startPunchProbes();
16679
16765
  if (this.closed)
@@ -16688,7 +16774,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16688
16774
  this.lookupTimer = setInterval(() => this.sendLookups(), LOOKUP_RETRY_MS);
16689
16775
  this.connectTimer = setTimeout(() => {
16690
16776
  if (!this.connected) {
16691
- this.emit("error", new Error(`P2P connect timeout for ${this.cfg.stationSn}`));
16777
+ this.emit("error", new Error(`P2P connect timeout for ${this.cfg.stationSn} (${this.relayProgress()})`));
16692
16778
  void this.close();
16693
16779
  }
16694
16780
  }, CONNECT_TIMEOUT_MS);
@@ -16766,6 +16852,84 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16766
16852
  this.sendCloudLookup(socket, lookup2);
16767
16853
  this.logger.debug(`[p2p] ${this.cfg.stationSn} sendLookups: cloud -> ${lookup2.addresses.map((a) => `${a.host}:${a.port}`).join(", ")} self=${this.selfHost ?? "unknown"}`);
16768
16854
  }
16855
+ this.countAckedRound();
16856
+ this.sendRelay();
16857
+ }
16858
+ /** Step {@link ackedRounds} for one lookup round, and start the relay handshake once it passes its threshold. */
16859
+ countAckedRound() {
16860
+ if (this.ackedRounds === void 0 || this.relay || !this.cloudLookup)
16861
+ return;
16862
+ this.ackedRounds += 1;
16863
+ if (this.ackedRounds > RELAY_AFTER_ACKED_ROUNDS)
16864
+ this.relay = { step: "list", relays: [] };
16865
+ }
16866
+ /**
16867
+ * How far this connect got towards a relay: the handshake step it was waiting on (`list` meaning no lookup
16868
+ * server named a relay), `not started` where it had not fallen back yet, or `no cloud lookup` where it could
16869
+ * not.
16870
+ */
16871
+ relayProgress() {
16872
+ return `relay: ${this.relay?.step ?? (this.cloudLookup ? "not started" : "no cloud lookup")}`;
16873
+ }
16874
+ /**
16875
+ * Send the relay handshake's message for the step it waits on, from the session's own socket.
16876
+ *
16877
+ * The order is the vendor client's: ListReq_DSK to the cloud lookup servers, RLY_HELLO to every relay they
16878
+ * list, RLY_PORT to the first of those relays that acknowledges, then RLY_REQ to the lookup servers naming
16879
+ * that relay's port and mark. The lookup server has the device register with the relay, and the relay
16880
+ * answers RLY_RDY.
16881
+ */
16882
+ sendRelay() {
16883
+ const relay = this.relay;
16884
+ const lookup2 = this.cloudLookup;
16885
+ if (!relay || !lookup2 || this.connected)
16886
+ return;
16887
+ if (relay.step === "list") {
16888
+ const payload = buildLookupWithKeyPayload2(this.cfg.p2pDid, lookup2.key);
16889
+ for (const addr of lookup2.addresses)
16890
+ this.send(addr, RequestMessageType.LOOKUP_WITH_KEY2, payload);
16891
+ } else if (relay.step === "hello") {
16892
+ for (const addr of relay.relays)
16893
+ this.send(addr, RequestMessageType.RLY_HELLO);
16894
+ } else if (relay.step === "port" && relay.via) {
16895
+ this.send(relay.via, RequestMessageType.RLY_PORT);
16896
+ } else if (relay.step === "request" && relay.via && relay.mark !== void 0) {
16897
+ const payload = buildRelayRequestPayload(this.cfg.p2pDid, relay.via, relay.mark);
16898
+ for (const addr of lookup2.addresses)
16899
+ this.send(addr, RequestMessageType.RLY_REQ, payload);
16900
+ }
16901
+ }
16902
+ /** Advance the relay handshake on one of its answers, arriving on the session's own socket. */
16903
+ onRelayMessage(msg, rinfo) {
16904
+ const relay = this.relay;
16905
+ if (!relay || this.connected)
16906
+ return;
16907
+ if (hasHeader(msg, ResponseMessageType.LIST_REQ_ACK)) {
16908
+ if (relay.step !== "list" && relay.step !== "hello")
16909
+ return;
16910
+ for (const listed of parseRelayList(msg))
16911
+ if (!relay.relays.some((known) => known.host === listed.host && known.port === listed.port))
16912
+ relay.relays.push(listed);
16913
+ if (relay.relays.length)
16914
+ relay.step = "hello";
16915
+ } else if (hasHeader(msg, ResponseMessageType.RLY_HELLO_ACK)) {
16916
+ const listed = relay.relays.some((known) => known.host === rinfo.address && known.port === rinfo.port);
16917
+ if (relay.step !== "hello" || !listed)
16918
+ return;
16919
+ relay.via = { host: rinfo.address, port: rinfo.port };
16920
+ relay.step = "port";
16921
+ } else if (hasHeader(msg, ResponseMessageType.RLY_PORT_ACK)) {
16922
+ const ack = parseRelayPortAck(msg);
16923
+ if (relay.step !== "port" || !relay.via || !ack)
16924
+ return;
16925
+ relay.via = { host: relay.via.host, port: ack.port };
16926
+ relay.mark = ack.mark;
16927
+ relay.step = "request";
16928
+ } else {
16929
+ return;
16930
+ }
16931
+ this.logger.debug(`[p2p] ${this.cfg.stationSn} relay -> ${relay.step}`);
16932
+ this.sendRelay();
16769
16933
  }
16770
16934
  /** Register a bound socket's own port with each configured cloud lookup address. */
16771
16935
  sendCloudLookup(socket, lookup2) {
@@ -16778,15 +16942,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16778
16942
  * Route one inbound UDP datagram by its message type, tracing every non-DATA one and any type this session
16779
16943
  * does not model.
16780
16944
  *
16781
- * An unmodelled type is not by itself evidence about a session that is failing. `0xf169` — a relay-pool
16782
- * listing answering a cloud lookup, which a connected session has no use for — reaches the UNHANDLED branch
16783
- * on the sessions of cameras that stream and cameras that do not alike. `0xf121` has been observed
16784
- * straddling a level-2 wait on a camera whose failure to deliver video had a separate cause, and did not
16785
- * recur across later probes of it. Correlate an unmodelled type against a WORKING session before reading it
16786
- * as a cause.
16945
+ * An unmodelled type is not by itself evidence about a session that is failing. Correlate one against a
16946
+ * WORKING session before reading it as a cause.
16947
+ *
16948
+ * P2P_REQ_ACK is the lookup server acknowledging this connect's P2P request; the first one carrying result 0
16949
+ * starts the round count {@link RELAY_AFTER_ACKED_ROUNDS} measures.
16787
16950
  *
16788
16951
  * CAM_ADDR is recognised and dropped: it names the address the device is already answering from, and the
16789
- * CAM_ID that follows it is what completes the connect.
16952
+ * CAM_ID that follows it is what completes the connect. RLY_TO is a relay endpoint, never the device: it is
16953
+ * answered with {@link RELAY_PACKET_COPIES} RLY_PKTs carrying its handle, and the RLY_RDY naming this
16954
+ * session's device completes the connect with the relay as the peer. The other relay answers advance the
16955
+ * handshake {@link sendRelay} drives, and are dropped outside it.
16790
16956
  */
16791
16957
  onMessage(msg, rinfo, socket = this.socket) {
16792
16958
  if (!socket)
@@ -16802,14 +16968,33 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16802
16968
  if (hasHeader(msg, ResponseMessageType.LOCAL_LOOKUP_RESP)) {
16803
16969
  if (!this.connected)
16804
16970
  this.beginCheckCam({ host: rinfo.address, port: rinfo.port }, socket);
16805
- } else if (hasHeader(msg, ResponseMessageType.LOOKUP_ADDR) || hasHeader(msg, ResponseMessageType.LOOKUP_ADDR2)) {
16971
+ } else if (hasHeader(msg, ResponseMessageType.LOOKUP_ADDR)) {
16806
16972
  if (!this.connected) {
16807
16973
  const addr = parseLookupAddr(msg);
16808
16974
  this.logger.debug(`[p2p] ${this.cfg.stationSn} LOOKUP_ADDR -> ${addr.host}:${addr.port}`);
16809
16975
  if (addr.host !== "0.0.0.0")
16810
16976
  this.beginCheckCam(addr, socket);
16811
16977
  }
16812
- } else if (hasHeader(msg, ResponseMessageType.CAM_ID) || hasHeader(msg, ResponseMessageType.TURN_SERVER_CAM_ID)) {
16978
+ } else if (hasHeader(msg, ResponseMessageType.RLY_TO)) {
16979
+ const to = parseRelayTo(msg);
16980
+ if (!this.connected && to && to.address.host !== "0.0.0.0") {
16981
+ this.logger.debug(`[p2p] ${this.cfg.stationSn} RLY_TO -> ${to.address.host}:${to.address.port}`);
16982
+ const payload = buildRelayPacketPayload(to.handle, this.cfg.p2pDid);
16983
+ for (let i = 0; i < RELAY_PACKET_COPIES; i++)
16984
+ this.send(to.address, RequestMessageType.RLY_PKT, payload, socket);
16985
+ }
16986
+ } else if (hasHeader(msg, ResponseMessageType.RLY_RDY)) {
16987
+ if (msg.subarray(4, 24).equals(p2pDidToBuffer(this.cfg.p2pDid))) {
16988
+ this.logger.debug(`[p2p] ${this.cfg.stationSn} RLY_RDY from relay ${rinfo.address}:${rinfo.port}`);
16989
+ this.onConnected({ host: rinfo.address, port: rinfo.port }, socket);
16990
+ }
16991
+ } else if (hasHeader(msg, ResponseMessageType.P2P_REQ_ACK)) {
16992
+ if (!this.connected && this.ackedRounds === void 0 && msg.length > 4 && msg[4] === 0)
16993
+ this.ackedRounds = 0;
16994
+ } else if (hasHeader(msg, ResponseMessageType.LIST_REQ_ACK) || hasHeader(msg, ResponseMessageType.RLY_HELLO_ACK) || hasHeader(msg, ResponseMessageType.RLY_PORT_ACK) || hasHeader(msg, ResponseMessageType.RLY_REQ_ACK)) {
16995
+ if (socket === this.socket)
16996
+ this.onRelayMessage(msg, rinfo);
16997
+ } else if (hasHeader(msg, ResponseMessageType.CAM_ID)) {
16813
16998
  this.onConnected({ host: rinfo.address, port: rinfo.port }, socket);
16814
16999
  } else if (hasHeader(msg, ResponseMessageType.END)) {
16815
17000
  if (fromConnectedPeer)
@@ -16897,6 +17082,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16897
17082
  clearInterval(this.lookupTimer);
16898
17083
  if (this.connectTimer)
16899
17084
  clearTimeout(this.connectTimer);
17085
+ this.relay = void 0;
16900
17086
  this.logger.debug(`[p2p] ${this.cfg.stationSn} connected ${addr.host}:${addr.port}`);
16901
17087
  this.sendCommand(CMD_GATEWAYINFO);
16902
17088
  this.send(addr, RequestMessageType.PING, this.lastPongData);
@@ -18076,6 +18262,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
18076
18262
  clearInterval(this.heartbeatTimer);
18077
18263
  if (this.connectTimer)
18078
18264
  clearTimeout(this.connectTimer);
18265
+ this.relay = void 0;
18079
18266
  if (this.audioRetransmitTimer)
18080
18267
  clearInterval(this.audioRetransmitTimer);
18081
18268
  this.audioRetransmitTimer = void 0;
@@ -22086,6 +22273,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22086
22273
  */
22087
22274
  async openMediaSession(parentSn, channel, signal) {
22088
22275
  const session = await this.openSession(_P2PCommandRouter.mediaSessionKey(parentSn, channel), parentSn);
22276
+ await this.awaitConnected(session, parentSn, signal);
22089
22277
  let ready = await abortable(session.awaitLevel2Key(LEVEL2_GRACE_MS, "call"), signal);
22090
22278
  if (!ready && session.repromptLevel2Key()) {
22091
22279
  ready = await abortable(session.awaitLevel2Key(LEVEL2_GRACE_MS, "call"), signal);
@@ -22094,6 +22282,30 @@ var P2PCommandRouter = class _P2PCommandRouter {
22094
22282
  throw new StationKeyUnavailableError(parentSn);
22095
22283
  return session;
22096
22284
  }
22285
+ /**
22286
+ * Wait for `session` to connect, for up to the session's own connect deadline, and throw
22287
+ * {@link StationUnreachableError} when it does not.
22288
+ *
22289
+ * `connect()` resolves once the lookups are under way, not once a path answers, so a session handed back by
22290
+ * {@link openSession} may still be connecting: a relay connect lands about 25 s in. A level-2 grace counted
22291
+ * before this would be spent on the connect rather than on the key.
22292
+ */
22293
+ async awaitConnected(session, parentSn, signal) {
22294
+ const t0 = Date.now();
22295
+ let waitedMs = 0;
22296
+ if (!session.isConnected) {
22297
+ this.traceOnStation(session, { phase: "session-connect-wait", waitMs: P2P_STATION_WAITS.connect });
22298
+ while (!session.isConnected && Date.now() - t0 < P2P_STATION_WAITS.connect) {
22299
+ signal?.throwIfAborted();
22300
+ await sleep2(200);
22301
+ }
22302
+ waitedMs = Date.now() - t0;
22303
+ this.traceOnStation(session, session.isConnected ? { phase: "session-connected", waitedMs } : { phase: "session-unreachable", waitedMs });
22304
+ }
22305
+ signal?.throwIfAborted();
22306
+ if (!session.isConnected)
22307
+ throw new StationUnreachableError(parentSn, waitedMs);
22308
+ }
22097
22309
  /** Open (or reuse) the session filed under `key`, dialling `parentSn`'s endpoint. */
22098
22310
  async openSession(key, parentSn) {
22099
22311
  return this.manager.acquire(key, async (register) => {
@@ -23143,20 +23355,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
23143
23355
  stationAdmin: typeof stationAdminId !== "string" ? "unstated" : stationAdminId === this.deps.mega.auth?.userId ? "self" : "other",
23144
23356
  ...stationModel ? { stationModel } : {}
23145
23357
  });
23146
- const t0 = Date.now();
23147
- let waitedMs = 0;
23148
- if (!session.isConnected) {
23149
- this.traceOnStation(session, { phase: "session-connect-wait", waitMs: P2P_STATION_WAITS.connect });
23150
- while (!session.isConnected && Date.now() - t0 < P2P_STATION_WAITS.connect) {
23151
- opts.signal?.throwIfAborted();
23152
- await sleep2(200);
23153
- }
23154
- waitedMs = Date.now() - t0;
23155
- this.traceOnStation(session, session.isConnected ? { phase: "session-connected", waitedMs } : { phase: "session-unreachable", waitedMs });
23156
- }
23157
- opts.signal?.throwIfAborted();
23158
- if (!session.isConnected)
23159
- throw new StationUnreachableError(parentSn, waitedMs);
23358
+ await this.awaitConnected(session, parentSn, opts.signal);
23160
23359
  if (opts.waitLevel2) {
23161
23360
  if (opts.waitLevel2 === "settle") {
23162
23361
  await abortable(session.awaitLevel2Key(LEVEL2_SETTLE_MS, "session"), opts.signal);
@@ -25570,10 +25769,10 @@ var RtcCommandRouter = class {
25570
25769
  import { randomBytes as randomBytes5 } from "node:crypto";
25571
25770
 
25572
25771
  // dist/transport/mqtt/broker-discovery.js
25573
- import { resolve4 } from "node:dns/promises";
25772
+ import { resolve4 as resolve42 } from "node:dns/promises";
25574
25773
  async function resolveBrokerIps(hostname) {
25575
25774
  try {
25576
- return await resolve4(hostname);
25775
+ return await resolve42(hostname);
25577
25776
  } catch {
25578
25777
  return [];
25579
25778
  }