@mega-yfue/eufy-sdk 0.5.0-beta.7 → 0.5.0-beta.71

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 (60) hide show
  1. package/dist/client/device-registry.d.ts +24 -2
  2. package/dist/client/eufy-mega.d.ts +83 -10
  3. package/dist/client/types.d.ts +1 -1
  4. package/dist/core/contracts.d.ts +71 -30
  5. package/dist/core/store.d.ts +4 -0
  6. package/dist/core/types.d.ts +2 -0
  7. package/dist/index.js +3611 -603
  8. package/dist/index.js.map +4 -4
  9. package/dist/model/capabilities/arming.d.ts +39 -4
  10. package/dist/model/capabilities/camera.d.ts +24 -10
  11. package/dist/model/capabilities/dp-catalog.d.ts +4 -3
  12. package/dist/model/capabilities/index.d.ts +16 -3
  13. package/dist/model/capabilities/light.d.ts +3 -0
  14. package/dist/model/capabilities/lock.d.ts +21 -7
  15. package/dist/model/capabilities/motion.d.ts +5 -0
  16. package/dist/model/capabilities/mower.d.ts +230 -0
  17. package/dist/model/capabilities/rtsp.d.ts +16 -3
  18. package/dist/model/capabilities/smart-drop.d.ts +20 -0
  19. package/dist/model/capabilities/suction.d.ts +5 -5
  20. package/dist/model/capabilities/types.d.ts +10 -0
  21. package/dist/model/capabilities/vacuum-clean.d.ts +19 -13
  22. package/dist/model/classify.d.ts +1 -2
  23. package/dist/model/device-family.d.ts +8 -0
  24. package/dist/model/param-dictionary.d.ts +6 -0
  25. package/dist/model/param-namespace.d.ts +1 -1
  26. package/dist/model/registry.d.ts +3 -2
  27. package/dist/model/types.d.ts +26 -12
  28. package/dist/transport/abortable.d.ts +8 -0
  29. package/dist/transport/http/mega-client.d.ts +34 -2
  30. package/dist/transport/mqtt/broker-discovery.d.ts +2 -1
  31. package/dist/transport/mqtt/command-router.d.ts +3 -2
  32. package/dist/transport/p2p/codec.d.ts +16 -0
  33. package/dist/transport/p2p/command-router.d.ts +27 -35
  34. package/dist/transport/p2p/fmp4.d.ts +12 -2
  35. package/dist/transport/p2p/live-trace.d.ts +11 -2
  36. package/dist/transport/p2p/p2p-session.d.ts +59 -12
  37. package/dist/transport/p2p/readable-egress.d.ts +4 -0
  38. package/dist/transport/p2p/recording-download.d.ts +4 -3
  39. package/dist/transport/p2p/shared-live-source.d.ts +27 -13
  40. package/dist/transport/p2p/talkback.d.ts +21 -10
  41. package/dist/transport/p2p/video.d.ts +2 -0
  42. package/dist/transport/rtc/call-live.d.ts +41 -0
  43. package/dist/transport/rtc/command-router.d.ts +141 -0
  44. package/dist/transport/rtc/live.d.ts +69 -0
  45. package/dist/transport/rtc/peer.d.ts +122 -0
  46. package/dist/transport/rtc/portal-keepalive.d.ts +11 -0
  47. package/dist/transport/rtc/portal-packet.d.ts +72 -0
  48. package/dist/transport/rtc/ptcs-framer.d.ts +86 -0
  49. package/dist/transport/rtc/scall-sdp.d.ts +50 -0
  50. package/dist/transport/rtc/session.d.ts +95 -0
  51. package/dist/transport/rtc/signaling.d.ts +139 -0
  52. package/dist/transport/token-echo.d.ts +8 -0
  53. package/dist/transport/tuya/account.d.ts +8 -5
  54. package/dist/transport/tuya/client.d.ts +19 -5
  55. package/dist/transport/tuya/command-router.d.ts +10 -14
  56. package/dist/transport/tuya/dp-codec.d.ts +2 -23
  57. package/dist/transport/tuya/et3.d.ts +22 -0
  58. package/dist/transport/tuya/request.d.ts +18 -8
  59. package/dist/transport/tuya/sign.d.ts +7 -0
  60. package/package.json +2 -1
@@ -32,9 +32,9 @@ export declare class TuyaCommandRouter {
32
32
  /** Promise that resolves once Tuya login has completed. Reset by {@link bind}. */
33
33
  private loginOnce;
34
34
  /**
35
- * eufy SN → Tuya device ids, populated by the facade via {@link registerDevice}.
36
- * The facade extracts the Tuya id from the `raw` device record fields (`tuya_uuid`,
37
- * `tuya_virtual_id`, `tuya_device_id`, `virtualId`) and registers it once the device list loads.
35
+ * eufy SN → Tuya device ids, populated via {@link registerDevice} once the device list loads.
36
+ * The Tuya id is the record's first non-empty `tuya_uuid` / `tuya_virtual_id` / `tuya_device_id` /
37
+ * `virtualId`, or the device's serial when the record carries none of them.
38
38
  */
39
39
  private readonly snMap;
40
40
  constructor(config?: TuyaCommandRouterConfig);
@@ -48,21 +48,17 @@ export declare class TuyaCommandRouter {
48
48
  */
49
49
  bind(userId: string, regionShard?: string, isoCode?: string): void;
50
50
  /**
51
- * Register a eufy SN → Tuya devId mapping. Called by the facade for each `eufy_home_tuya`
52
- * device after the cloud device list loads. The facade extracts the Tuya id from the device's
53
- * raw record (`tuya_uuid` / `tuya_virtual_id` / `tuya_device_id` / `virtualId` fields).
54
- * `gwId` defaults to `devId` — standalone devices share the two.
51
+ * Register a eufy SN → Tuya devId mapping for an `eufy_home_tuya` device. `devId` is the record's
52
+ * first non-empty `tuya_uuid` / `tuya_virtual_id` / `tuya_device_id` / `virtualId`, or the device's
53
+ * serial when the record carries none of them. `gwId` defaults to `devId` — standalone devices
54
+ * share the two.
55
55
  */
56
56
  registerDevice(sn: string, devId: string, gwId?: string): void;
57
57
  private ensureLoggedIn;
58
58
  /**
59
- * Fetch a device's cached DPs from the Tuya cloud (`thing.m.device.cache.dp.get`) and deliver
60
- * the raw DP map to the caller. Used for initial state hydration after MQTT subscribe — gets the
61
- * last-known state without waiting for the first realtime push.
62
- *
63
- * The response shape from `getDeviceDps` is not yet pinned from a live capture. The defensive
64
- * extraction tries both `result.dps` (a nested map) and bare `result` (a flat map), and returns
65
- * `null` when neither yields a non-empty record.
59
+ * Read a device's current DPs from the Tuya cloud (`smartlife.m.device.dp.get`) and answer the DP
60
+ * map: the reply's flat `result`, kept to integer DP keys with scalar values. Answers `null` for an
61
+ * unregistered device, a failed reply, or a reply with no DPs.
66
62
  */
67
63
  fetchDps(sn: string): Promise<Record<string, unknown> | null>;
68
64
  /**
@@ -1,14 +1,9 @@
1
1
  /**
2
- * Inbound Tuya DP event parsing and routing for eufy Home/Clean (`eufy_home_tuya`) devices.
2
+ * Inbound Tuya DP parsing and routing for eufy Home/Clean (`eufy_home_tuya`) devices.
3
3
  *
4
- * {@link parseTuyaDpReport} unwraps the Anker MQTT `{head, payload}` envelope and extracts the
5
- * raw DP map. {@link parseTuyaDpEvent} then validates and normalises it to a numeric-keyed record.
4
+ * {@link parseTuyaDpEvent} validates a Tuya cloud DP map and normalises it to a numeric-keyed record.
6
5
  * {@link TuyaDpRouter} delivers the result to the registered {@link TuyaDpInbound} listener so the
7
6
  * capability layer can consume Tuya state through the same typed getters as AIoT.
8
- *
9
- * Inbound messages arrive on `cmd/eufy_home/{model}/{sn}/res` with `sign_code: 0` (no additional
10
- * encryption — confirmed from clean-device captures). If a future capture shows `sign_code ≠ 0` the
11
- * payload will be AES-128-ECB encrypted with the device's localKey before the JSON can be parsed.
12
7
  */
13
8
  import type { TuyaDpInbound } from "../../core/contracts.js";
14
9
  /**
@@ -21,22 +16,6 @@ import type { TuyaDpInbound } from "../../core/contracts.js";
21
16
  * valid DP entry was found.
22
17
  */
23
18
  export declare function parseTuyaDpEvent(payload: unknown): Record<number, boolean | number | string> | null;
24
- /**
25
- * Extract a raw DP map from an inbound Anker MQTT message for a `eufy_home_tuya` device.
26
- *
27
- * Messages arrive in the same `{head, payload}` envelope the AIoT path uses, where `payload` is a
28
- * JSON **string**:
29
- * ```json
30
- * { "head": { "cmd": 65537, "cmd_status": 2, "sign_code": 0, ... },
31
- * "payload": "{\"t\":\"…\",\"protocol\":2,\"data\":{\"104\":80,\"106\":0}}" }
32
- * ```
33
- * Two DP layouts inside the decoded payload are handled: Tuya-native (`data.dps`) and AIoT-direct
34
- * (`data` with integer-keyed DP ids). Falls back to flat `envelope.dps` / `envelope.data.dps`
35
- * shapes for any pre-parsed delivery. Returns `undefined` when no recognised shape is found.
36
- *
37
- * @internal
38
- */
39
- export declare function parseTuyaDpReport(raw: unknown): Record<string, unknown> | undefined;
40
19
  /**
41
20
  * Routes validated inbound Tuya DP events to the registered {@link TuyaDpInbound} listener.
42
21
  *
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The per-request AES-128 key, `getEncryptoKey(requestId, ecode)`: take the lowercase hex of
3
+ * `HMAC-SHA256(key = requestId, msg = K[+ "_" + ecode])`, keep the first 16 hex characters, and use
4
+ * those characters as 16 ASCII bytes. `K` is the app HMAC key ({@link resolveSignKey}).
5
+ *
6
+ * `ecode` is the session's per-login encryption code (`User.ecode`, read from the decrypted
7
+ * `login.reg` reply). `ecode === null` selects the pre-session variant `msg = K` used to open the
8
+ * `token.get` and `login.reg` replies themselves, before any `ecode` is known.
9
+ */
10
+ export declare function deriveBodyKey(requestId: string, ecode: string | null): Buffer;
11
+ /**
12
+ * Encrypt an `et=3` request `postData`: AES-128-GCM under a random 12-byte nonce, laid out
13
+ * `nonce(12) ‖ ciphertext ‖ tag(16)` then base64 — the inverse of {@link decryptReply}, keyed by the
14
+ * same {@link deriveBodyKey}. No AAD. The request is signed over this blob.
15
+ */
16
+ export declare function encryptBody(key: Buffer, json: string): string;
17
+ /**
18
+ * Decrypt an `et=3` reply `result`: base64 → AES-128-GCM-decrypt-verify (`nonce = blob[0:12]`,
19
+ * `tag = blob[-16:]`, ciphertext between), gunzip when the plaintext is gzip-framed, and return the
20
+ * plaintext JSON string. Throws when the blob is too short or the GCM tag fails to authenticate.
21
+ */
22
+ export declare function decryptReply(key: Buffer, resultB64: string): string;
@@ -2,9 +2,10 @@
2
2
  * Tuya / Thingclips `api.json` request assembly + transport.
3
3
  *
4
4
  * Every call is a single `POST https://a1.tuyaeu.com/api.json` (EU) whose body is
5
- * **form-urlencoded plaintext params** (NOT an encrypted blob) plus a `sign`. This module builds
6
- * that param map for a given action, form-encodes it, POSTs it, and parses Tuya's response
7
- * envelope. The one non-plaintext piece — the `sign` digest — is delegated to a {@link TuyaSigner}.
5
+ * **form-urlencoded params** plus a `sign`. Under `et=3` the `postData` param is an AES-128-GCM blob
6
+ * and the reply `result` is encrypted the same way, with the same per-request key. This module builds
7
+ * that param map for a given action, form-encodes it, POSTs it, and parses Tuya's response envelope.
8
+ * The `sign` digest is delegated to a {@link TuyaSigner}.
8
9
  */
9
10
  /** Injectable POST transport — takes (url, form-body), returns the parsed JSON envelope. Native `fetch` by default. */
10
11
  export type TuyaHttpPost = (url: string, body: string) => Promise<unknown>;
@@ -24,7 +25,10 @@ export declare const TUYA_SDK_VERSION = "7.5.0";
24
25
  export declare const TUYA_APP_VERSION = "6.0.51_26722";
25
26
  /** A single action to call: the `a` (action) + `v` (version) + optional pre-serialized `postData`. */
26
27
  export interface TuyaAction {
27
- /** Action name, e.g. `thing.m.device.dp.publish`. */
28
+ /**
29
+ * Action name as it goes on the wire, e.g. `smartlife.m.device.dp.publish`. The vendor SDK names
30
+ * its actions `thing.*` and rewrites a leading `thing` to `smartlife` before sending.
31
+ */
28
32
  a: string;
29
33
  /** Per-action version, e.g. `"1.0"` / `"2.0"`. */
30
34
  v: string;
@@ -43,6 +47,11 @@ export interface TuyaSession {
43
47
  * `"7cbfe6d8"` for this app's appId. Hardcode it.
44
48
  */
45
49
  chKey: string;
50
+ /**
51
+ * Per-login encryption code (`User.ecode`), read from the decrypted `login.reg` reply. Absent until
52
+ * login completes; it keys et=3 body encryption and reply decryption for every session call.
53
+ */
54
+ ecode?: string;
46
55
  }
47
56
  /**
48
57
  * The constant / environment fields of a request. All are plaintext and (except the sign-relevant
@@ -127,14 +136,14 @@ export interface SendOptions {
127
136
  export declare function sendApiRequest<T = unknown>(params: Record<string, string>, opts?: SendOptions): Promise<TuyaEnvelope<T>>;
128
137
  /**
129
138
  * CONTROL: publish device data-points. `dps` is `{ "<dpId>": <value> }`; note it is JSON-stringified
130
- * INSIDE the postData (Tuya nests the dps map as a string). Action `thing.m.device.dp.publish` v2.0.
139
+ * INSIDE the postData (Tuya nests the dps map as a string). Action `smartlife.m.device.dp.publish` v2.0.
131
140
  */
132
141
  export declare function buildPublishDpsAction(devId: string, gwId: string, dps: Record<string, unknown>): TuyaAction;
133
142
  /**
134
- * READ/dump: fetch a device's cached data-points. Action `thing.m.device.cache.dp.get` v2.0.
135
- * TODO(verify): `dpCacheType` default (1) was not pinned from the capture.
143
+ * READ: a device's current data-points. Action `smartlife.m.device.dp.get` v1.0, postData `{ devId }`.
144
+ * The reply `result` is a flat `{ "<dpId>": value }` map.
136
145
  */
137
- export declare function buildGetDeviceDpsAction(devId: string, dpCacheType?: number): TuyaAction;
146
+ export declare function buildGetDeviceDpsAction(devId: string): TuyaAction;
138
147
  /**
139
148
  * LOGIN step 1: fetch a pre-login RSA token for a Tuya uid.
140
149
  * Wire-confirmed from the eufy Security app (`com.oceanwing.battery.cam`):
@@ -146,6 +155,7 @@ export declare function buildUsernameTokenGetAction(countryCode: string, usernam
146
155
  * LOGIN step 2: uid password login + auto-register.
147
156
  * Wire-confirmed from the eufy Security app: wire action
148
157
  * `smartlife.m.user.uid.password.login.reg`, v=1.0. `passwd` = hex of RSA-PKCS1-encrypt(MD5hex(aesPassword)).
158
+ * `options` is the JSON string `{"group": 1}`, as the app sends it.
149
159
  * On success returns `{ sid, uid }`.
150
160
  */
151
161
  export declare function buildPasswordLoginRegAction(countryCode: string, uid: string, passwd: string, token: string): TuyaAction;
@@ -44,6 +44,13 @@ export declare class StubSigner implements TuyaSigner {
44
44
  * The env var `TUYA_SIGN_KEY` can override this for non-standard builds.
45
45
  */
46
46
  export declare const TUYA_SIGN_K: string;
47
+ /**
48
+ * Resolve the app-wide HMAC key {@link TUYA_SIGN_K}: an explicit `override` wins, then the
49
+ * `TUYA_SIGN_KEY` env var (for custom builds), then the built-in constant. Shared by the request
50
+ * signer ({@link HmacSigner}) and the et=3 body-key derivation, which key off the same `K`.
51
+ * @internal
52
+ */
53
+ export declare function resolveSignKey(override?: string): string;
47
54
  /**
48
55
  * The real {@link TuyaSigner}: `sign = HMAC-SHA256(K, preimage)` as lowercase hex.
49
56
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.5.0-beta.7",
3
+ "version": "0.5.0-beta.71",
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",
@@ -74,6 +74,7 @@
74
74
  "dependencies": {
75
75
  "jpeg-js": "^0.4.4",
76
76
  "mqtt": "^5.16.0",
77
+ "node-datachannel": "^0.33.4",
77
78
  "protobufjs": "^8.8.0"
78
79
  },
79
80
  "devDependencies": {