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

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.
@@ -8,7 +8,12 @@ export declare const MAGIC_WORD = "XZYH";
8
8
  export declare const RequestMessageType: {
9
9
  readonly LOCAL_LOOKUP: Buffer<ArrayBuffer>;
10
10
  readonly LOOKUP_WITH_KEY: Buffer<ArrayBuffer>;
11
+ /** `ListReq_DSK`: the same body as a variant-2 lookup, answered with {@link ResponseMessageType.LIST_REQ_ACK}. */
11
12
  readonly LOOKUP_WITH_KEY2: Buffer<ArrayBuffer>;
13
+ readonly RLY_HELLO: Buffer<ArrayBuffer>;
14
+ readonly RLY_PORT: Buffer<ArrayBuffer>;
15
+ readonly RLY_REQ: Buffer<ArrayBuffer>;
16
+ readonly RLY_PKT: Buffer<ArrayBuffer>;
12
17
  readonly CHECK_CAM: Buffer<ArrayBuffer>;
13
18
  readonly CHECK_CAM2: Buffer<ArrayBuffer>;
14
19
  readonly PING: Buffer<ArrayBuffer>;
@@ -20,14 +25,24 @@ export declare const RequestMessageType: {
20
25
  export declare const ResponseMessageType: {
21
26
  readonly LOCAL_LOOKUP_RESP: Buffer<ArrayBuffer>;
22
27
  readonly LOOKUP_ADDR: Buffer<ArrayBuffer>;
23
- readonly LOOKUP_ADDR2: Buffer<ArrayBuffer>;
28
+ /** The relay servers a cloud lookup server names: `[count:1][pad:3]` then `count` address records. */
29
+ readonly LIST_REQ_ACK: Buffer<ArrayBuffer>;
30
+ readonly RLY_HELLO_ACK: Buffer<ArrayBuffer>;
31
+ /** A relay's answer to RLY_PORT: `[mark:4 BE][port:2 BE][pad:2]`, the port being the one it relays on. */
32
+ readonly RLY_PORT_ACK: Buffer<ArrayBuffer>;
33
+ readonly RLY_REQ_ACK: Buffer<ArrayBuffer>;
34
+ /** A relay endpoint to register with: one address record, then `[handle:4 BE]`. */
35
+ readonly RLY_TO: Buffer<ArrayBuffer>;
24
36
  readonly CAM_ID: Buffer<ArrayBuffer>;
37
+ /** A lookup server's answer to a P2P request: `[result:1]` first, 0 when the request is accepted. */
38
+ readonly P2P_REQ_ACK: Buffer<ArrayBuffer>;
25
39
  /**
26
40
  * The device's own address record, sent in answer to CHECK_CAM ahead of CAM_ID: its 20-byte device id, one
27
- * record in `encodeSelfAddress`'s shape naming the address it answers from, then 8 zero bytes (44-byte payload).
41
+ * record in `encodeAddress`'s shape naming the address it answers from, then 8 zero bytes (44-byte payload).
28
42
  */
29
43
  readonly CAM_ADDR: Buffer<ArrayBuffer>;
30
- readonly TURN_SERVER_CAM_ID: Buffer<ArrayBuffer>;
44
+ /** A relay reporting the path to the device ready: the device's 20-byte id, sent from the relay endpoint. */
45
+ readonly RLY_RDY: Buffer<ArrayBuffer>;
31
46
  readonly PING: Buffer<ArrayBuffer>;
32
47
  readonly PONG: Buffer<ArrayBuffer>;
33
48
  readonly DATA: Buffer<ArrayBuffer>;
@@ -160,6 +175,10 @@ export declare function buildLookupWithKeyPayload2(p2pDid: string, dskKey: strin
160
175
  * its exact semantics (and whether it's validated) are unconfirmed.
161
176
  */
162
177
  export declare function buildLookupWithKeyPayload(p2pDid: string, selfHost: string, selfPort: number, dskKey: string): Buffer;
178
+ /** RLY_REQ payload: `[p2pDid:20][relay address record:16][mark:4 BE]`, naming the relay port and mark a RLY_PORT_ACK gave. */
179
+ export declare function buildRelayRequestPayload(p2pDid: string, relay: Address, mark: number): Buffer;
180
+ /** RLY_PKT payload: `[handle:4 BE][p2pDid:20][role:1][pad:3]`; a connecting client's role byte is 0. */
181
+ export declare function buildRelayPacketPayload(handle: number, p2pDid: string): Buffer;
163
182
  /** CHECK_CAM hole-punch payload: `[p2pDid:20][0x000000]`. */
164
183
  export declare function buildCheckCamPayload(p2pDid: string): Buffer;
165
184
  /** 10-byte command header: `[dataTypeHeader:2][seq:2 BE]["XZYH"][cmd:2 LE]`. */
@@ -205,7 +224,19 @@ export declare function buildRawCommandPayload(data: Buffer, channel?: number, s
205
224
  export declare function buildAckPayload(dataTypeHeader: Buffer, seqNo: number): Buffer;
206
225
  /** Parse the 16-byte data-frame header that follows the "XZYH" magic. */
207
226
  export declare function parseDataFrameHeader(frame: Buffer): P2PDataFrameHeader;
208
- /** Parse a LOOKUP_ADDR / LOOKUP_ADDR2 response into a device address. */
227
+ /** Parse a LOOKUP_ADDR or RLY_TO message's leading address record. */
209
228
  export declare function parseLookupAddr(msg: Buffer): Address;
229
+ /** The relay servers a LIST_REQ_ACK names, in the order listed, without the unspecified address; records past the message's end are dropped. */
230
+ export declare function parseRelayList(msg: Buffer): Address[];
231
+ /** A RLY_PORT_ACK's mark and relay port, or `undefined` for a message too short to carry them. */
232
+ export declare function parseRelayPortAck(msg: Buffer): {
233
+ mark: number;
234
+ port: number;
235
+ } | undefined;
236
+ /** A RLY_TO's relay endpoint and handle, or `undefined` for a message too short to carry them. */
237
+ export declare function parseRelayTo(msg: Buffer): {
238
+ address: Address;
239
+ handle: number;
240
+ } | undefined;
210
241
  /** Read a NUL-terminated UTF-8 string (CMD_NOTIFY_PAYLOAD carries JSON this way). */
211
242
  export declare function readNullTerminatedString(data: Buffer, encoding?: BufferEncoding): string;
@@ -22,7 +22,7 @@ import { FragmentRecording } from "./fragment-recording.js";
22
22
  * negotiation is re-prompted once.
23
23
  */
24
24
  export declare const P2P_STATION_WAITS: {
25
- readonly connect: 15000;
25
+ readonly connect: 45000;
26
26
  readonly level2Grace: 25000;
27
27
  readonly level2Settle: 8000;
28
28
  };
@@ -218,6 +218,15 @@ export declare class P2PCommandRouter {
218
218
  * carry the start is not a connected session, so this refuses rather than returning one.
219
219
  */
220
220
  private openMediaSession;
221
+ /**
222
+ * Wait for `session` to connect, for up to the session's own connect deadline, and throw
223
+ * {@link StationUnreachableError} when it does not.
224
+ *
225
+ * `connect()` resolves once the lookups are under way, not once a path answers, so a session handed back by
226
+ * {@link openSession} may still be connecting: a relay connect lands about 25 s in. A level-2 grace counted
227
+ * before this would be spent on the connect rather than on the key.
228
+ */
229
+ private awaitConnected;
221
230
  /** Open (or reuse) the session filed under `key`, dialling `parentSn`'s endpoint. */
222
231
  private openSession;
223
232
  /**
@@ -23,8 +23,13 @@ import { type Logger } from "../../core/logger.js";
23
23
  * a connection that reaches this closes itself, so nothing addressed to that station can succeed afterwards.
24
24
  * Published because it bounds every wait on a session connecting — a second number for the same deadline
25
25
  * elsewhere would outlive the connection it waits on and charge the difference to every failure.
26
+ *
27
+ * The relay handshake starts once the lookup server has acknowledged the P2P request for 24 lookup rounds, so
28
+ * about 25 s in. The vendor app's own remote connect has a 30 s budget, which leaves the handshake 3–6 s.
29
+ * Forty-five seconds keeps the vendor's threshold and gives the handshake about 20 s to complete. A station that
30
+ * never acknowledges the request, as an offline one does, takes this long to fail.
26
31
  */
27
- export declare const CONNECT_TIMEOUT_MS = 15000;
32
+ export declare const CONNECT_TIMEOUT_MS = 45000;
28
33
  /**
29
34
  * The channel a command addresses the station itself on, rather than one of its cameras, and the value a
30
35
  * session's channel-taking methods resolve an omitted channel to.
@@ -140,6 +145,17 @@ export declare class P2PSession extends EventEmitter {
140
145
  private probeSockets;
141
146
  /** Cloud lookup inputs resolved once for a connection. */
142
147
  private cloudLookup?;
148
+ /**
149
+ * Lookup rounds since the lookup server acknowledged this connect's P2P request, or `undefined` while it
150
+ * has not; see {@link RELAY_AFTER_ACKED_ROUNDS}.
151
+ */
152
+ private ackedRounds?;
153
+ /**
154
+ * The relay handshake of a connect that has fallen back to one, by the step it waits on: the relay list
155
+ * (`list`, answering ListReq_DSK), a relay's hello acknowledgement (`hello`), that relay's port (`port`),
156
+ * then the relay-ready the device's registration produces (`request`, re-sending RLY_REQ meanwhile).
157
+ */
158
+ private relay?;
143
159
  /** Local outbound IPv4 reported inside LOOKUP_WITH_KEY requests. */
144
160
  private selfHost?;
145
161
  /**
@@ -341,21 +357,42 @@ export declare class P2PSession extends EventEmitter {
341
357
  */
342
358
  private static detectLocalIp;
343
359
  private sendLookups;
360
+ /** Step {@link ackedRounds} for one lookup round, and start the relay handshake once it passes its threshold. */
361
+ private countAckedRound;
362
+ /**
363
+ * How far this connect got towards a relay: the handshake step it was waiting on (`list` meaning no lookup
364
+ * server named a relay), `not started` where it had not fallen back yet, or `no cloud lookup` where it could
365
+ * not.
366
+ */
367
+ private relayProgress;
368
+ /**
369
+ * Send the relay handshake's message for the step it waits on, from the session's own socket.
370
+ *
371
+ * The order is the vendor client's: ListReq_DSK to the cloud lookup servers, RLY_HELLO to every relay they
372
+ * list, RLY_PORT to the first of those relays that acknowledges, then RLY_REQ to the lookup servers naming
373
+ * that relay's port and mark. The lookup server has the device register with the relay, and the relay
374
+ * answers RLY_RDY.
375
+ */
376
+ private sendRelay;
377
+ /** Advance the relay handshake on one of its answers, arriving on the session's own socket. */
378
+ private onRelayMessage;
344
379
  /** Register a bound socket's own port with each configured cloud lookup address. */
345
380
  private sendCloudLookup;
346
381
  /**
347
382
  * Route one inbound UDP datagram by its message type, tracing every non-DATA one and any type this session
348
383
  * does not model.
349
384
  *
350
- * An unmodelled type is not by itself evidence about a session that is failing. `0xf169` — a relay-pool
351
- * listing answering a cloud lookup, which a connected session has no use for — reaches the UNHANDLED branch
352
- * on the sessions of cameras that stream and cameras that do not alike. `0xf121` has been observed
353
- * straddling a level-2 wait on a camera whose failure to deliver video had a separate cause, and did not
354
- * recur across later probes of it. Correlate an unmodelled type against a WORKING session before reading it
355
- * as a cause.
385
+ * An unmodelled type is not by itself evidence about a session that is failing. Correlate one against a
386
+ * WORKING session before reading it as a cause.
387
+ *
388
+ * P2P_REQ_ACK is the lookup server acknowledging this connect's P2P request; the first one carrying result 0
389
+ * starts the round count {@link RELAY_AFTER_ACKED_ROUNDS} measures.
356
390
  *
357
391
  * CAM_ADDR is recognised and dropped: it names the address the device is already answering from, and the
358
- * CAM_ID that follows it is what completes the connect.
392
+ * CAM_ID that follows it is what completes the connect. RLY_TO is a relay endpoint, never the device: it is
393
+ * answered with {@link RELAY_PACKET_COPIES} RLY_PKTs carrying its handle, and the RLY_RDY naming this
394
+ * session's device completes the connect with the relay as the peer. The other relay answers advance the
395
+ * handshake {@link sendRelay} drives, and are dropped outside it.
359
396
  */
360
397
  private onMessage;
361
398
  private beginCheckCam;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.5.0-beta.93",
3
+ "version": "0.5.0-beta.94",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",