eufy-security-client 4.0.0-dev.33 → 4.0.0-dev.35

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 CHANGED
@@ -1,5 +1,37 @@
1
1
  # eufy-security-client
2
2
 
3
+ > [!CAUTION]
4
+ > # 🚨🚨🚨 LIBRARY DEPRECATION NOTICE 🚨🚨🚨
5
+ >
6
+ > ### ⚠️ Eufy is shutting down the legacy APIs this library is built on. ⚠️
7
+ >
8
+ > Eufy is in the middle of a large migration of their ecosystem. The newer **Eufy Mega**
9
+ > platform (the "5-in-1" app, covering Security / Clean / Lights / Care) is gradually
10
+ > becoming the only supported backend, and Eufy has **already started removing access to
11
+ > the legacy APIs** this library was originally built on. Until recently both worked in
12
+ > parallel — that is no longer guaranteed.
13
+ >
14
+ > **🔔 What this means for you:**
15
+ >
16
+ > - 🟢 A recent PR restores **push notifications** against the new v6 ("eufy_mega")
17
+ > backend, so push works again **for now**. This is a short-term stopgap.
18
+ > - 🟡 Other functionality that still depends on legacy endpoints may stop working
19
+ > **without warning** as Eufy continues the rollout. The current Eufy app no longer
20
+ > uses the legacy API at all.
21
+ > - 🔴 Once the legacy API is fully shut down, **this library will stop functioning** —
22
+ > no amount of patching here will change that.
23
+ >
24
+ > **🚧 What's next:**
25
+ >
26
+ > A new integration built around **Eufy Mega** is in active development (auto-discovery,
27
+ > less battery drain for P2P), designed from the ground up rather than bolted onto the
28
+ > Security-only structure, and coordinated across the Home Assistant, Homebridge and
29
+ > Homey communities so the new library works for everyone.
30
+ >
31
+ > ### 👉 Treat this release as a **temporary stopgap.** 👈
32
+ >
33
+ > *This notice will be updated as the migration progresses.*
34
+
3
35
  ![Logo](docs/_media/eufy-security-client.png)
4
36
 
5
37
  [![node](https://img.shields.io/node/v/eufy-security-client.svg)](https://www.npmjs.com/package/eufy-security-client)
@@ -12,6 +12,8 @@ import { LogLevel } from "typescript-logging";
12
12
  export declare class EufySecurity extends TypedEmitter<EufySecurityEvents> {
13
13
  private config;
14
14
  private api;
15
+ /** All v6 ("eufy_mega") behaviour — transport, login, push, connect sequencing — is isolated here. */
16
+ private megaTransition;
15
17
  private houses;
16
18
  private stations;
17
19
  private devices;
@@ -70,7 +72,20 @@ export declare class EufySecurity extends TypedEmitter<EufySecurityEvents> {
70
72
  setCameraMaxLivestreamDuration(seconds: number): void;
71
73
  getCameraMaxLivestreamDuration(): number;
72
74
  registerPushNotifications(credentials?: Credentials, persistentIds?: string[]): Promise<void>;
75
+ /**
76
+ * Entry point for the consumer. The whole login sequence (v6-first, legacy best-effort, single
77
+ * app-ready signal at the end, serialisation, 2FA/captcha routing) lives in {@link MegaTransition};
78
+ * here we just delegate. Removing the transition layer makes this equivalent to {@link legacyConnect}.
79
+ */
73
80
  connect(options?: LoginOptions): Promise<void>;
81
+ /**
82
+ * The original (upstream) login: authenticate the legacy backend and trust the device on first
83
+ * 2FA. Kept verbatim and driven by {@link MegaTransition} as the best-effort second step; it no
84
+ * longer signals the app directly (that is now done once, at the end of the sequence).
85
+ */
86
+ private legacyConnect;
87
+ /** The narrow surface the v6 transition layer uses to talk back to us, as a closure object. */
88
+ private megaTransitionHost;
74
89
  getPushPersistentIds(): string[];
75
90
  private updateDeviceProperties;
76
91
  private onAPIClose;
@@ -42,7 +42,7 @@ const fs_1 = require("fs");
42
42
  const path = __importStar(require("path"));
43
43
  const util = __importStar(require("util"));
44
44
  const events_1 = __importDefault(require("events"));
45
- const api_1 = require("./http/api");
45
+ const megaTransition_1 = require("./http/megaTransition");
46
46
  const station_1 = require("./http/station");
47
47
  const types_1 = require("./http/types");
48
48
  const service_1 = require("./push/service");
@@ -61,6 +61,8 @@ const utils_3 = require("./p2p/utils");
61
61
  class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
62
62
  config;
63
63
  api;
64
+ /** All v6 ("eufy_mega") behaviour — transport, login, push, connect sequencing — is isolated here. */
65
+ megaTransition;
64
66
  houses = {};
65
67
  stations = {};
66
68
  devices = {};
@@ -236,17 +238,31 @@ class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
236
238
  this.persistentData.cloud_token_expiration = 0;
237
239
  this.persistentData.httpApi = undefined;
238
240
  }
239
- this.api = await api_1.HTTPApi.initialize(this.config.country, this.config.username, this.config.password, this.persistentData.httpApi);
241
+ // All v6 ("eufy_mega") behaviour is isolated in MegaTransition (login, push, connect
242
+ // sequencing). It talks back to us only through this narrow host surface and builds the live
243
+ // transport (the legacy HTTPApi today). Removing the transition layer reverts everything to the
244
+ // upstream legacy behaviour.
245
+ this.megaTransition = new megaTransition_1.MegaTransition(this.megaTransitionHost());
246
+ this.api = await this.megaTransition.createTransport(this.persistentData.httpApi);
240
247
  this.api.setLanguage(this.config.language);
241
248
  this.api.setPhoneModel(this.config.trustedDeviceName);
242
249
  this.api.on("houses", (houses) => this.handleHouses(houses));
243
250
  this.api.on("hubs", (hubs) => this.handleHubs(hubs));
244
251
  this.api.on("devices", (devices) => this.handleDevices(devices));
245
252
  this.api.on("close", () => this.onAPIClose());
246
- this.api.on("connect", () => this.onAPIConnect());
247
- this.api.on("captcha request", (id, captcha) => this.onCaptchaRequest(id, captcha));
253
+ // NOTE: the legacy login emitting "connect" no longer drives the app-ready signal directly —
254
+ // connect() sequences mega + legacy and calls onAPIConnect() once at the very end (see below).
255
+ this.api.on("connect", () => logging_1.rootMainLogger.debug("Legacy API connected"));
256
+ // The legacy login records itself as the pending challenge so the next code/captcha is routed to it.
257
+ this.api.on("captcha request", (id, captcha) => {
258
+ this.megaTransition.recordLegacyChallenge();
259
+ this.onCaptchaRequest(id, captcha);
260
+ });
248
261
  this.api.on("auth token invalidated", () => this.onAuthTokenInvalidated());
249
- this.api.on("tfa request", () => this.onTfaRequest());
262
+ this.api.on("tfa request", () => {
263
+ this.megaTransition.recordLegacyChallenge();
264
+ this.onTfaRequest();
265
+ });
250
266
  this.api.on("connection error", (error) => this.onAPIConnectionError(error));
251
267
  if (this.persistentData.cloud_token &&
252
268
  this.persistentData.cloud_token != "" &&
@@ -273,8 +289,11 @@ class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
273
289
  this.pushService.on("connect", async (token) => {
274
290
  this.pushCloudRegistered = await this.api.registerPushToken(token);
275
291
  this.pushCloudChecked = await this.api.checkPushToken();
292
+ const megaRegistered = await this.megaTransition.registerMegaPushToken(token);
276
293
  //TODO: Retry if failed with max retry to not lock account
277
- if (this.pushCloudRegistered && this.pushCloudChecked) {
294
+ // Push is "connected" if registration succeeded on EITHER backend: on a migrated account the
295
+ // legacy registration fails (no legacy session) but the v6 one carries the events.
296
+ if ((this.pushCloudRegistered && this.pushCloudChecked) || megaRegistered) {
278
297
  logging_1.rootMainLogger.info("Push notification connection successfully established");
279
298
  this.emit("push connect");
280
299
  }
@@ -870,7 +889,20 @@ class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
870
889
  this.pushService.setPersistentIds(persistentIds);
871
890
  this.pushService.open();
872
891
  }
892
+ /**
893
+ * Entry point for the consumer. The whole login sequence (v6-first, legacy best-effort, single
894
+ * app-ready signal at the end, serialisation, 2FA/captcha routing) lives in {@link MegaTransition};
895
+ * here we just delegate. Removing the transition layer makes this equivalent to {@link legacyConnect}.
896
+ */
873
897
  async connect(options) {
898
+ return this.megaTransition.connect(options);
899
+ }
900
+ /**
901
+ * The original (upstream) login: authenticate the legacy backend and trust the device on first
902
+ * 2FA. Kept verbatim and driven by {@link MegaTransition} as the best-effort second step; it no
903
+ * longer signals the app directly (that is now done once, at the end of the sequence).
904
+ */
905
+ async legacyConnect(options) {
874
906
  await this.api
875
907
  .login(options)
876
908
  .then(async () => {
@@ -891,6 +923,25 @@ class EufySecurity extends tiny_typed_emitter_1.TypedEmitter {
891
923
  logging_1.rootMainLogger.error("Connect Error", { error: (0, utils_1.getError)(error), options: options });
892
924
  });
893
925
  }
926
+ /** The narrow surface the v6 transition layer uses to talk back to us, as a closure object. */
927
+ megaTransitionHost() {
928
+ // `api` is a getter so the transition layer always sees the live transport, which we assign
929
+ // right after building the host (and could swap later); the other members are stable.
930
+ const eufy = this;
931
+ return {
932
+ config: this.config,
933
+ persistentData: this.persistentData,
934
+ get api() {
935
+ return eufy.api;
936
+ },
937
+ writePersistentData: () => this.writePersistentData(),
938
+ emitTfaRequest: () => this.onTfaRequest(),
939
+ emitCaptchaRequest: (id, captcha) => this.onCaptchaRequest(id, captcha),
940
+ legacyConnect: (options) => this.legacyConnect(options),
941
+ onAPIConnect: () => this.onAPIConnect(),
942
+ onConnectionError: (error) => this.onAPIConnectionError(error),
943
+ };
944
+ }
894
945
  getPushPersistentIds() {
895
946
  return this.pushService.getPersistentIds();
896
947
  }