eufy-security-client 4.0.0 → 4.1.0-dev.36
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/README.md +32 -0
- package/build/eufysecurity.d.ts +15 -0
- package/build/eufysecurity.js +58 -6
- package/build/eufysecurity.js.map +1 -1
- package/build/http/api.js +1 -1
- package/build/http/api.js.map +1 -1
- package/build/http/decodeImageV2.d.ts +12 -8
- package/build/http/decodeImageV2.js +17 -12
- package/build/http/decodeImageV2.js.map +1 -1
- package/build/http/index.d.ts +2 -0
- package/build/http/index.js +2 -0
- package/build/http/index.js.map +1 -1
- package/build/http/interfaces.d.ts +2 -0
- package/build/http/megaApi.d.ts +186 -0
- package/build/http/megaApi.js +513 -0
- package/build/http/megaApi.js.map +1 -0
- package/build/http/megaCrypto.d.ts +84 -0
- package/build/http/megaCrypto.js +129 -0
- package/build/http/megaCrypto.js.map +1 -0
- package/build/http/megaInterfaces.d.ts +83 -0
- package/build/http/megaInterfaces.js +3 -0
- package/build/http/megaInterfaces.js.map +1 -0
- package/build/http/megaTransition.d.ts +103 -0
- package/build/http/megaTransition.js +203 -0
- package/build/http/megaTransition.js.map +1 -0
- package/build/http/station.d.ts +1 -0
- package/build/http/station.js +4 -0
- package/build/http/station.js.map +1 -1
- package/build/http/types.d.ts +7 -1
- package/build/http/types.js +6 -0
- package/build/http/types.js.map +1 -1
- package/build/http/utils.d.ts +10 -0
- package/build/http/utils.js +34 -10
- package/build/http/utils.js.map +1 -1
- package/build/interfaces.d.ts +2 -0
- package/build/p2p/interfaces.d.ts +2 -0
- package/build/p2p/session.js +17 -0
- package/build/p2p/session.js.map +1 -1
- package/build/p2p/types.d.ts +1 -0
- package/build/p2p/types.js +1 -0
- package/build/p2p/types.js.map +1 -1
- package/coverage/clover.xml +15420 -0
- package/coverage/coverage-final.json +37 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +176 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +29542 -0
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { type MegaIdentity } from "./megaCrypto";
|
|
2
|
+
import { MegaResult, MegaCaptcha, MegaCaptchaAnswer, MegaUserMqttInfo, MegaMqttConnectConfig, MegaApiOptions, MegaSession } from "./megaInterfaces";
|
|
3
|
+
export type { MegaResult, MegaCaptcha, MegaCaptchaAnswer, MegaUserMqttInfo, MegaMqttConnectConfig, MegaApiOptions, MegaSession, } from "./megaInterfaces";
|
|
4
|
+
/**
|
|
5
|
+
* Eufy "eufy_mega" v6 backend client.
|
|
6
|
+
*
|
|
7
|
+
* Talks to the new per-service `*.eufy.com` microservices (behind APISIX) that the
|
|
8
|
+
* official app 6.0.50+ uses. Each cluster (us-pr, eu-pr, …) requires its own
|
|
9
|
+
* key/exchange handshake → per-cluster {@link MegaIdentity} (keyIdent + sharedKey).
|
|
10
|
+
* Bodies are ECDH-encrypted (`X-Encryption-Info: algo_ecdh`) and every request is
|
|
11
|
+
* signed (`X-Signature`).
|
|
12
|
+
*
|
|
13
|
+
* This is intentionally a SEPARATE class from HTTPApi: the legacy backend still
|
|
14
|
+
* works for not-yet-migrated devices, so we don't want to risk that path.
|
|
15
|
+
*
|
|
16
|
+
* Heavily rate-limited (1 request / ~3s) — the Eufy WAF rate-limits aggressive probing.
|
|
17
|
+
*/
|
|
18
|
+
/** Per-cluster server static public key (uncompressed hex) for the ECIES bootstrap. */
|
|
19
|
+
export declare const MEGA_SERVER_STATIC_PUBKEY = "04ebc77a23c7191f8c97fb2a7676710f64ddadfe5305fa80c8855476b024c6ad3d8c18d4be9d720c5a578167f899e0818d3a19de2e804407034b4a88cfdb7ae995";
|
|
20
|
+
/**
|
|
21
|
+
* Salted change-detection hash for the persisted session: invalidate the cached mega session when
|
|
22
|
+
* the credentials change. Salted with the stable device id so a leaked persistent file isn't an
|
|
23
|
+
* offline-cracking aid the way a bare md5(user:pass) would be.
|
|
24
|
+
*/
|
|
25
|
+
export declare const megaLoginHash: (email: string, password: string, openudid: string) => string;
|
|
26
|
+
export declare class MegaHTTPApi {
|
|
27
|
+
private readonly ab;
|
|
28
|
+
private readonly osType;
|
|
29
|
+
private readonly appName;
|
|
30
|
+
private readonly appVersion;
|
|
31
|
+
private readonly osVersion;
|
|
32
|
+
private readonly phoneModel;
|
|
33
|
+
private readonly minIntervalMs;
|
|
34
|
+
private got;
|
|
35
|
+
private throttle;
|
|
36
|
+
/** Resolved domains from estimate_domain (eufy_security, etc). */
|
|
37
|
+
private domains;
|
|
38
|
+
private megaDomain;
|
|
39
|
+
/** One identity per cluster host (e.g. app-openapi-eu-pr.eufy.com). */
|
|
40
|
+
private identities;
|
|
41
|
+
/** eufy.com auth token (from passport/login on the new backend). */
|
|
42
|
+
private authToken?;
|
|
43
|
+
/** Unix seconds when authToken expires (from login `token_expires_at`). */
|
|
44
|
+
private tokenExpiresAt?;
|
|
45
|
+
private userId?;
|
|
46
|
+
constructor(opts: MegaApiOptions);
|
|
47
|
+
init(): Promise<void>;
|
|
48
|
+
private get gtoken();
|
|
49
|
+
/** Stable per-install device id (uuid-ish, 32 hex). Restored from a persisted session. */
|
|
50
|
+
private openudid;
|
|
51
|
+
/**
|
|
52
|
+
* Low-level signed/encrypted POST to a v6 host.
|
|
53
|
+
*
|
|
54
|
+
* X-Signature covers the ENCRYPTED VALUE (the base64 ciphertext), not the JSON wrapper: for
|
|
55
|
+
* key/exchange the body is `{"client_public_key":"<b64>"}` but only `<b64>` is signed; for
|
|
56
|
+
* regular requests the body IS the ciphertext, so signed value == body.
|
|
57
|
+
*
|
|
58
|
+
* No got-level retry: the timestamp/nonce/signature are computed once per call and the backend
|
|
59
|
+
* enforces a replay/timestamp window, so a got retry would resend a frozen signature and be
|
|
60
|
+
* rejected. Retry above this method (recomputing the signature) if needed.
|
|
61
|
+
*
|
|
62
|
+
* @param host full host (e.g. app-passport-eu-pr.eufy.com)
|
|
63
|
+
* @param path request path
|
|
64
|
+
* @param payload plaintext object (encrypted with the cluster identity sharedKey)
|
|
65
|
+
* @param identity cluster identity; if omitted, bootstrap mode (presetKey) is used
|
|
66
|
+
*/
|
|
67
|
+
private signedPost;
|
|
68
|
+
/** Decrypt a v6 response `data` field with a cluster identity sharedKey. */
|
|
69
|
+
decryptForCluster(identity: MegaIdentity, dataB64: string): string;
|
|
70
|
+
/** Resolve the region's mega domain + product domains. Body is cleartext JSON. */
|
|
71
|
+
estimateDomain(): Promise<Record<string, string>>;
|
|
72
|
+
/**
|
|
73
|
+
* Perform a key/exchange against a cluster's openapi host and cache the identity.
|
|
74
|
+
* @param openapiHost e.g. app-openapi-eu-pr.eufy.com
|
|
75
|
+
*/
|
|
76
|
+
keyExchange(openapiHost: string): Promise<MegaIdentity>;
|
|
77
|
+
/**
|
|
78
|
+
* Region cluster host for a service, e.g. "passport" → app-passport-eu-pr.eufy.com.
|
|
79
|
+
*
|
|
80
|
+
* Derived from the `domain` returned by {@link estimateDomain} (the server decides the region,
|
|
81
|
+
* the client does not guess it) by replacing the `mega` prefix with `app-{service}` — the same
|
|
82
|
+
* transform the app does in MegaAppDomain.createByMega. Falls back to a us/eu guess only when
|
|
83
|
+
* estimate_domain has not run yet (e.g. the bootstrap key/exchange before login).
|
|
84
|
+
*/
|
|
85
|
+
clusterHost(service: string): string;
|
|
86
|
+
getDomains(): Record<string, string>;
|
|
87
|
+
getIdentity(host: string): MegaIdentity | undefined;
|
|
88
|
+
setAuth(authToken: string, userId: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Export the full session so a later run can resume WITHOUT a fresh login/2FA.
|
|
91
|
+
*
|
|
92
|
+
* Mirrors the legacy HTTPApi's `persistentData`: once authenticated you ARE authenticated —
|
|
93
|
+
* persist the token + user_id + the per-cluster ECDH identities (keyIdent/sharedKey) + the
|
|
94
|
+
* stable device id. As long as the token hasn't expired, `restoreSession()` lets every signed
|
|
95
|
+
* call go straight through (no estimate_domain / key/exchange / login replay).
|
|
96
|
+
*/
|
|
97
|
+
exportSession(loginHash?: string): MegaSession;
|
|
98
|
+
/** Restore a session previously produced by {@link exportSession}. */
|
|
99
|
+
restoreSession(s: MegaSession): void;
|
|
100
|
+
/**
|
|
101
|
+
* True if we hold a non-expired auth token (60s safety margin) → no login replay needed.
|
|
102
|
+
* An unknown expiry is treated as invalid (forces a relogin) rather than valid-forever.
|
|
103
|
+
*/
|
|
104
|
+
hasValidSession(): boolean;
|
|
105
|
+
/** Generic signed/encrypted call once an identity exists for the host's cluster. */
|
|
106
|
+
call(host: string, path: string, payload: unknown): Promise<MegaResult>;
|
|
107
|
+
/** {@link call} + decrypt the `data` field. Throws on non-zero code. */
|
|
108
|
+
callDecrypted(service: string, path: string, payload?: unknown): Promise<unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* MQTT connection info for the v6 backend (`devicemanage/get_user_mqtt_info`).
|
|
111
|
+
* This is how events are pushed in v6 — broker endpoint + credentials + topics, returned
|
|
112
|
+
* dynamically (unlike the legacy static `security-mqtt-eu.eufylife.com`). Decrypted.
|
|
113
|
+
*/
|
|
114
|
+
getUserMqttInfo(): Promise<MegaUserMqttInfo>;
|
|
115
|
+
/**
|
|
116
|
+
* Build the full AWS IoT MQTT connection config for the v6 event channel. Fetches the per-user
|
|
117
|
+
* certificate/credentials and combines them with the client identity so a consumer can connect
|
|
118
|
+
* without touching MegaHTTPApi internals.
|
|
119
|
+
*
|
|
120
|
+
* NOTE: SCAFFOLDING — there is no MQTT subscriber consuming this yet. The v6 AWS IoT channel
|
|
121
|
+
* only serves devices flagged `is_support_mqtt`; ordinary cameras/sensors are delivered over
|
|
122
|
+
* FCM ({@link registerPushToken}). This builder is shipped ahead of a follow-up MQTT-consumer
|
|
123
|
+
* change so that change won't need to touch MegaHTTPApi. It is NOT a live event path today.
|
|
124
|
+
*
|
|
125
|
+
* The `clientId` format is mandatory for the AWS IoT policy (decompiled `createClientId`):
|
|
126
|
+
* `android-{app_name}-{user_id}-{openudid}`. Topics carry `PN`/`SN` placeholders to fill per
|
|
127
|
+
* device. Requires a valid session.
|
|
128
|
+
*/
|
|
129
|
+
getMqttConnectConfig(): Promise<MegaMqttConnectConfig>;
|
|
130
|
+
/**
|
|
131
|
+
* Register an FCM push token on the v6 backend (`push/register_push_token`).
|
|
132
|
+
*
|
|
133
|
+
* Body (decompiled `PushManager.uploadToken`): `{token, is_notification_enable, voip_token}`.
|
|
134
|
+
* There is NO platform/type field in the body — the backend routes FCM vs APNs purely by the
|
|
135
|
+
* `os-type` header + the `x-key-ident` identity this request is signed under. So this MUST run
|
|
136
|
+
* on an `os-type: android` identity (the default here) for events to be delivered over FCM.
|
|
137
|
+
*/
|
|
138
|
+
registerPushToken(fcmToken: string): Promise<MegaResult>;
|
|
139
|
+
/** Encrypt the login password exactly like the legacy HTTPApi (ECDH vs LOGIN_SERVER_PUBLIC_KEY). */
|
|
140
|
+
private encryptLoginPassword;
|
|
141
|
+
/** Validate the email exists on the new backend (the app does this before login). */
|
|
142
|
+
validateEmail(email: string): Promise<MegaResult>;
|
|
143
|
+
/** The 2FA session id, shared between login (which triggers 2FA) and loginAfterTFA. */
|
|
144
|
+
private loginId?;
|
|
145
|
+
/**
|
|
146
|
+
* Obtain a `login_id` (2FA session id). The app calls `passport/get_login_id` before login and
|
|
147
|
+
* passes the SAME login_id to both the initial login and the post-2FA login. (Decompiled flow.)
|
|
148
|
+
*/
|
|
149
|
+
getLoginId(): Promise<string>;
|
|
150
|
+
/**
|
|
151
|
+
* Login against the v6 passport backend.
|
|
152
|
+
*
|
|
153
|
+
* The body sends `login_id: ""`: `get_login_id`/`getTouchId` is for biometric (TouchID) login
|
|
154
|
+
* only and must not be called in the email+2FA flow. On success the backend returns code:0 even
|
|
155
|
+
* when 2FA is still pending — the real state is in `fa_info.step` (26052), and the returned token
|
|
156
|
+
* is provisional but still used to authenticate the subsequent sendVerifyCode + final login.
|
|
157
|
+
*
|
|
158
|
+
* @param verifyCode email 2FA code (omit on first call; pass it on retry after code 26052)
|
|
159
|
+
* @param captcha picture-captcha answer (pass it after code 100032/100033 — see {@link generateCaptcha})
|
|
160
|
+
* @returns the full MegaResult. Codes the caller acts on: 26052 = email 2FA required;
|
|
161
|
+
* 100032 = captcha required, 100033 = captcha answer incorrect. On success, token+user stored.
|
|
162
|
+
*/
|
|
163
|
+
login(email: string, password: string, verifyCode?: string, captcha?: MegaCaptchaAnswer): Promise<MegaResult>;
|
|
164
|
+
/**
|
|
165
|
+
* Request the email 2FA code (after login indicates fa_info.step 26052).
|
|
166
|
+
*
|
|
167
|
+
* Exact payload confirmed by decompiling the Ijiami-packed `SendVerifyCodeRequest`
|
|
168
|
+
* (dumped from process memory): the 2FA-login send is `{message_type, biz_type, transaction}`
|
|
169
|
+
* with `biz_type = 1004` (BIZ_TYPE_TFA) and `message_type = 2` (TYPE_EMAIL). No captcha for send.
|
|
170
|
+
*/
|
|
171
|
+
sendVerifyCode(_email?: string): Promise<MegaResult>;
|
|
172
|
+
/**
|
|
173
|
+
* Fetch a picture captcha (after login returns 100032/100033). Body
|
|
174
|
+
* `{captcha_type, biz_type}` (decompiled CaptchaManager.getCaptcha); the response `item` is a
|
|
175
|
+
* base64 image the user must solve, then pass the answer + captcha_id back into {@link login}.
|
|
176
|
+
*/
|
|
177
|
+
generateCaptcha(): Promise<MegaCaptcha>;
|
|
178
|
+
/**
|
|
179
|
+
* Fetch the Tuya/Thingclips device list (`get_things_list`) and DECRYPT it.
|
|
180
|
+
* This is the probe to see whether a given device (e.g. Kitchen sensor) has been
|
|
181
|
+
* migrated server-side onto the Tuya backend.
|
|
182
|
+
*/
|
|
183
|
+
getThingsListDecrypted(productCodes?: string[]): Promise<unknown>;
|
|
184
|
+
/** Eufy-side device list (`house/get_devs_list`), decrypted. The non-Tuya inventory. */
|
|
185
|
+
getDevsListDecrypted(): Promise<unknown>;
|
|
186
|
+
}
|