@mega-yfue/eufy-sdk 0.4.0-beta.10 → 0.4.0-beta.12

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
@@ -1070,6 +1070,401 @@ function normalizePushImage(data, p2pDid) {
1070
1070
  return decodeImageV1(data, p2pDid) ?? data;
1071
1071
  }
1072
1072
 
1073
+ // dist/core/types.js
1074
+ var PRINTER_CATEGORY_RE = /ankermake|eufymake|fdm|3d_?print/i;
1075
+ function classifyDevice(raw) {
1076
+ const category = raw.category ?? "";
1077
+ const hasP2p = typeof raw.p2p_did === "string" && raw.p2p_did.length > 0;
1078
+ if (category === "eufy_security" || hasP2p)
1079
+ return { category, api: "mega", realtime: "p2p" };
1080
+ if (PRINTER_CATEGORY_RE.test(category))
1081
+ return { category, api: "ankermake", realtime: "ankermake-mqtt" };
1082
+ return { category, api: "mega", realtime: "smqtt" };
1083
+ }
1084
+
1085
+ // dist/core/contracts.js
1086
+ var StoredSnapshotUnavailableError = class extends Error {
1087
+ reason;
1088
+ constructor(reason, message, options) {
1089
+ super(message, options);
1090
+ this.reason = reason;
1091
+ this.name = "StoredSnapshotUnavailableError";
1092
+ }
1093
+ };
1094
+ var RETRYABLE_LIVE_SNAPSHOT_REASONS = [
1095
+ "no-keyframe",
1096
+ "source-failed",
1097
+ "undecodable-burst"
1098
+ ];
1099
+ var LiveSnapshotUnavailableError = class extends Error {
1100
+ reason;
1101
+ /** Whether another attempt could plausibly succeed without the host changing anything. */
1102
+ retryable;
1103
+ constructor(reason, message, options) {
1104
+ super(message, options);
1105
+ this.reason = reason;
1106
+ this.name = "LiveSnapshotUnavailableError";
1107
+ this.retryable = RETRYABLE_LIVE_SNAPSHOT_REASONS.includes(reason);
1108
+ }
1109
+ };
1110
+ var CameraDisabledError = class extends Error {
1111
+ camera;
1112
+ constructor(camera, options) {
1113
+ super(`camera${camera ? ` ${camera}` : ""} is disabled \u2014 no live stream possible`, options);
1114
+ this.camera = camera;
1115
+ this.name = "CameraDisabledError";
1116
+ }
1117
+ };
1118
+ var StationKeyUnavailableError = class extends Error {
1119
+ stationSn;
1120
+ /** Always true: the negotiation is per connection, so a later one may still produce a key. */
1121
+ retryable = true;
1122
+ constructor(stationSn, options) {
1123
+ super(`station ${stationSn} did not provide its session key, so nothing that requires one could be sent`, options);
1124
+ this.stationSn = stationSn;
1125
+ this.name = "StationKeyUnavailableError";
1126
+ }
1127
+ };
1128
+ var StationUnreachableError = class extends Error {
1129
+ stationSn;
1130
+ waitedMs;
1131
+ /** Always true: a station unreachable now may answer on a later attempt. */
1132
+ retryable = true;
1133
+ constructor(stationSn, waitedMs, options) {
1134
+ super(`station ${stationSn}'s P2P session did not connect within ${waitedMs}ms, so nothing could be sent to it`, options);
1135
+ this.stationSn = stationSn;
1136
+ this.waitedMs = waitedMs;
1137
+ this.name = "StationUnreachableError";
1138
+ }
1139
+ };
1140
+ var DeviceChannelUnresolvedError = class extends Error {
1141
+ sn;
1142
+ stationSn;
1143
+ constructor(sn, stationSn, options) {
1144
+ super(`${sn} has no usable channel on station ${stationSn}, so nothing was sent to it`, options);
1145
+ this.sn = sn;
1146
+ this.stationSn = stationSn;
1147
+ this.name = "DeviceChannelUnresolvedError";
1148
+ }
1149
+ };
1150
+ var LiveStreamStartError = class extends Error {
1151
+ reason;
1152
+ stage;
1153
+ timeoutMs;
1154
+ attempts;
1155
+ constructor(failure) {
1156
+ super(`live stream failed to start (${failure.reason} at ${failure.stage} after ${failure.attempts} attempts; ${failure.timeoutMs}ms deadline)`, failure.cause === void 0 ? void 0 : { cause: failure.cause });
1157
+ this.name = "LiveStreamStartError";
1158
+ this.reason = failure.reason;
1159
+ this.stage = failure.stage;
1160
+ this.timeoutMs = failure.timeoutMs;
1161
+ this.attempts = failure.attempts;
1162
+ }
1163
+ };
1164
+ var StateConvergenceError = class extends Error {
1165
+ sn;
1166
+ property;
1167
+ param;
1168
+ expected;
1169
+ observed;
1170
+ timeoutMs;
1171
+ constructor(failure) {
1172
+ super(`device ${failure.sn} did not report ${failure.property} as ${String(failure.expected)} within ${failure.timeoutMs}ms (param ${failure.param} read ${String(failure.observed)})`);
1173
+ this.name = "StateConvergenceError";
1174
+ this.sn = failure.sn;
1175
+ this.property = failure.property;
1176
+ this.param = failure.param;
1177
+ this.expected = failure.expected;
1178
+ this.observed = failure.observed;
1179
+ this.timeoutMs = failure.timeoutMs;
1180
+ }
1181
+ };
1182
+ var COMMAND_OBSERVATION = /* @__PURE__ */ Symbol("command-observation");
1183
+ function observeCommand(command, observation) {
1184
+ return Object.defineProperty(command, COMMAND_OBSERVATION, { configurable: true, value: observation });
1185
+ }
1186
+ function commandObservation(command) {
1187
+ return command[COMMAND_OBSERVATION];
1188
+ }
1189
+
1190
+ // dist/core/raw-dp-writer.js
1191
+ var WIRE = { VARINT: 0, LENGTH_DELIMITED: 2 };
1192
+ function encodeVarint(value) {
1193
+ if (!Number.isSafeInteger(value) || value < 0) {
1194
+ throw new Error(`varint: ${value} is not a non-negative safe integer`);
1195
+ }
1196
+ const out = [];
1197
+ let v = value;
1198
+ while (v > 127) {
1199
+ out.push(v % 128 + 128);
1200
+ v = Math.floor(v / 128);
1201
+ }
1202
+ out.push(v);
1203
+ return out;
1204
+ }
1205
+ function zigzag(value) {
1206
+ if (!Number.isSafeInteger(value))
1207
+ throw new Error(`zigzag: ${value} is not a safe integer`);
1208
+ return value < 0 ? -2 * value - 1 : 2 * value;
1209
+ }
1210
+ var RawDpWriter = class _RawDpWriter {
1211
+ body = [];
1212
+ /** A varint-valued field (`int32`, `uint32`, `bool`, an enum member). */
1213
+ int(field, value) {
1214
+ this.body.push(...encodeVarint(tag(field, WIRE.VARINT)), ...encodeVarint(value));
1215
+ return this;
1216
+ }
1217
+ /** A signed varint field (`sint32`, `sint64`), ZigZag-encoded — see {@link zigzag}. */
1218
+ sint(field, value) {
1219
+ return this.int(field, zigzag(value));
1220
+ }
1221
+ /** A boolean field, on protobuf's own terms: `1` or `0` in a varint. */
1222
+ bool(field, value) {
1223
+ return this.int(field, value ? 1 : 0);
1224
+ }
1225
+ /** A length-delimited field carrying raw bytes — a string, a blob, or a message built elsewhere. */
1226
+ bytes(field, value) {
1227
+ const bytes2 = value instanceof Uint8Array ? Array.from(value) : [...value];
1228
+ this.body.push(...encodeVarint(tag(field, WIRE.LENGTH_DELIMITED)), ...encodeVarint(bytes2.length), ...bytes2);
1229
+ return this;
1230
+ }
1231
+ /**
1232
+ * A nested sub-message, built by `build` into a writer of its own.
1233
+ *
1234
+ * The sub-message is emitted even when `build` writes nothing into it. That is not an oversight: an
1235
+ * EMPTY sub-message is how these protocols say "this subsystem exists and is in its zero state", as
1236
+ * opposed to saying nothing about it, and the readers on the other side of this file depend on the
1237
+ * distinction. Omitting the call is what says nothing about it.
1238
+ */
1239
+ sub(field, build) {
1240
+ const inner = new _RawDpWriter();
1241
+ build(inner);
1242
+ return this.bytes(field, inner.toBytes());
1243
+ }
1244
+ /** The message body on its own, unframed — for nesting into another writer. */
1245
+ toBytes() {
1246
+ return [...this.body];
1247
+ }
1248
+ /**
1249
+ * The finished DP value: `varint(bodyLength) ++ body`, base64-encoded.
1250
+ *
1251
+ * This is the envelope `RawDpCodec.decode` unwraps, so a value produced here and read back through the
1252
+ * codec is a genuine round-trip rather than two implementations of the same guess.
1253
+ */
1254
+ finish() {
1255
+ return Buffer.from([...encodeVarint(this.body.length), ...this.body]).toString("base64");
1256
+ }
1257
+ };
1258
+ function tag(field, wire) {
1259
+ if (!Number.isSafeInteger(field) || field < 1)
1260
+ throw new Error(`field number: ${field} is not a positive integer`);
1261
+ return field * 8 + wire;
1262
+ }
1263
+ function rawDp(build) {
1264
+ const w = new RawDpWriter();
1265
+ build(w);
1266
+ return w.finish();
1267
+ }
1268
+
1269
+ // dist/core/raw-dp-hex.js
1270
+ function hexToRawDp(hex) {
1271
+ if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex))
1272
+ return void 0;
1273
+ return Buffer.from(hex, "hex").toString("base64");
1274
+ }
1275
+
1276
+ // dist/core/util.js
1277
+ var Timer = class {
1278
+ handle;
1279
+ /** (Re)arm to run `fn` after `ms`, cancelling any already-pending fire. */
1280
+ arm(ms, fn) {
1281
+ this.cancel();
1282
+ this.handle = setTimeout(() => {
1283
+ this.handle = void 0;
1284
+ fn();
1285
+ }, ms);
1286
+ this.handle.unref?.();
1287
+ }
1288
+ /** Cancel a pending fire. No-op if not armed. */
1289
+ cancel() {
1290
+ if (this.handle) {
1291
+ clearTimeout(this.handle);
1292
+ this.handle = void 0;
1293
+ }
1294
+ }
1295
+ /** Whether a fire is currently scheduled. */
1296
+ get pending() {
1297
+ return this.handle !== void 0;
1298
+ }
1299
+ };
1300
+ function assertNever(value) {
1301
+ throw new Error(`unhandled variant: ${JSON.stringify(value)}`);
1302
+ }
1303
+ function asBool(value) {
1304
+ if (typeof value === "string")
1305
+ return value === "1" || value.toLowerCase() === "true";
1306
+ return value === true || value === 1;
1307
+ }
1308
+ function clamp(value, min, max) {
1309
+ if (Number.isNaN(value))
1310
+ return min;
1311
+ return Math.max(min, Math.min(max, Math.round(value)));
1312
+ }
1313
+ function structuralEqual(a, b) {
1314
+ if (Object.is(a, b))
1315
+ return true;
1316
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
1317
+ return false;
1318
+ const aArr = Array.isArray(a);
1319
+ const bArr = Array.isArray(b);
1320
+ if (aArr !== bArr)
1321
+ return false;
1322
+ if (aArr && bArr) {
1323
+ if (a.length !== b.length)
1324
+ return false;
1325
+ for (let i = 0; i < a.length; i++)
1326
+ if (!structuralEqual(a[i], b[i]))
1327
+ return false;
1328
+ return true;
1329
+ }
1330
+ const aObj = a;
1331
+ const bObj = b;
1332
+ const aKeys = Object.keys(aObj);
1333
+ const bKeys = Object.keys(bObj);
1334
+ if (aKeys.length !== bKeys.length)
1335
+ return false;
1336
+ for (const k of aKeys) {
1337
+ if (!Object.prototype.hasOwnProperty.call(bObj, k))
1338
+ return false;
1339
+ if (!structuralEqual(aObj[k], bObj[k]))
1340
+ return false;
1341
+ }
1342
+ return true;
1343
+ }
1344
+ function coerceEnumValue(enumObj, value) {
1345
+ const n = Number(value);
1346
+ return Number.isInteger(n) && Object.values(enumObj).includes(n) ? n : void 0;
1347
+ }
1348
+ function enumValues(enumObj) {
1349
+ return Object.values(enumObj).join("/");
1350
+ }
1351
+ function enumLabels(enumObj) {
1352
+ return Object.fromEntries(Object.entries(enumObj).map(([name, value]) => [value, name]));
1353
+ }
1354
+ function jsonObject(text2) {
1355
+ if (typeof text2 !== "string")
1356
+ return void 0;
1357
+ try {
1358
+ const v = JSON.parse(text2);
1359
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
1360
+ } catch {
1361
+ return void 0;
1362
+ }
1363
+ }
1364
+ function u16le(n) {
1365
+ const b = Buffer.alloc(2);
1366
+ b.writeUInt16LE(n & 65535);
1367
+ return b;
1368
+ }
1369
+ function u16be(n) {
1370
+ const b = Buffer.alloc(2);
1371
+ b.writeUInt16BE(n & 65535);
1372
+ return b;
1373
+ }
1374
+ function u32le(n) {
1375
+ const b = Buffer.alloc(4);
1376
+ b.writeUInt32LE(n >>> 0);
1377
+ return b;
1378
+ }
1379
+ function u32be(n) {
1380
+ const b = Buffer.alloc(4);
1381
+ b.writeUInt32BE(n >>> 0);
1382
+ return b;
1383
+ }
1384
+
1385
+ // dist/core/lz4-block.js
1386
+ var MAX_OUTPUT = 64 * 1024 * 1024;
1387
+ function lz4BlockDecompress(block, size) {
1388
+ if (!Number.isSafeInteger(size) || size < 0 || size > MAX_OUTPUT)
1389
+ return void 0;
1390
+ const out = Buffer.alloc(size);
1391
+ const end = block.length;
1392
+ let pos = 0;
1393
+ let written = 0;
1394
+ const extend2 = (base) => {
1395
+ let len = base;
1396
+ if (base !== 15)
1397
+ return len;
1398
+ for (; ; ) {
1399
+ if (pos >= end)
1400
+ return void 0;
1401
+ const extra = block[pos++];
1402
+ len += extra;
1403
+ if (extra !== 255)
1404
+ return len;
1405
+ }
1406
+ };
1407
+ while (pos < end) {
1408
+ const token = block[pos++];
1409
+ const literals = extend2(token >> 4);
1410
+ if (literals === void 0)
1411
+ return void 0;
1412
+ if (pos + literals > end || written + literals > size)
1413
+ return void 0;
1414
+ for (let i = 0; i < literals; i++)
1415
+ out[written++] = block[pos++];
1416
+ if (pos === end)
1417
+ break;
1418
+ if (pos + 2 > end)
1419
+ return void 0;
1420
+ const offset = block[pos] | block[pos + 1] << 8;
1421
+ pos += 2;
1422
+ if (offset === 0 || offset > written)
1423
+ return void 0;
1424
+ const match = extend2(token & 15);
1425
+ if (match === void 0)
1426
+ return void 0;
1427
+ const length = match + 4;
1428
+ if (written + length > size)
1429
+ return void 0;
1430
+ let from = written - offset;
1431
+ for (let i = 0; i < length; i++)
1432
+ out[written++] = out[from++];
1433
+ }
1434
+ return written === size ? out : void 0;
1435
+ }
1436
+
1437
+ // dist/transport/http/passport.js
1438
+ function loginCredentials(email, password, country) {
1439
+ const { clientPublicKeyHex, encryptedPassword } = encryptLoginPassword(password);
1440
+ return { email, password: encryptedPassword, ab: country, client_secret_info: { public_key: clientPublicKeyHex } };
1441
+ }
1442
+ function readLoginReply(data) {
1443
+ const userId = data.ap_cloud_user_id ?? data.user_id ?? data.userId;
1444
+ const authToken = data.auth_token ?? data.token;
1445
+ if (!userId || !authToken)
1446
+ return void 0;
1447
+ return {
1448
+ userId,
1449
+ accountUserId: data.user_id ?? data.userId,
1450
+ authToken,
1451
+ geoKey: data.geo_key,
1452
+ tokenExpiresAt: Number(data.token_expires_at ?? 0) || 0,
1453
+ twoFactorPending: !!(data.fa_info ?? {}).info
1454
+ };
1455
+ }
1456
+ function signedHeaders(entry, encBody) {
1457
+ const ts = nowSec();
1458
+ const once = genId();
1459
+ return {
1460
+ "x-encryption-info": "algo_ecdh",
1461
+ "x-key-ident": entry.keyIdent,
1462
+ "x-request-ts": ts,
1463
+ "x-request-once": once,
1464
+ "x-signature": signRequest(entry.shareKey, ts, once, encBody)
1465
+ };
1466
+ }
1467
+
1073
1468
  // dist/transport/http/mega-client.js
1074
1469
  function parseMaybeJson(text2) {
1075
1470
  if (text2.length === 0)
@@ -1437,17 +1832,11 @@ ${this.auth_?.authToken ?? ""}`;
1437
1832
  const order = preferred === "text/plain" ? ["text/plain", "application/json"] : ["application/json", "text/plain"];
1438
1833
  let last;
1439
1834
  for (const contentType of order) {
1440
- const ts = nowSec();
1441
- const once = genId();
1442
1835
  const headers = {
1443
1836
  ...this.baseHeaders(),
1444
1837
  "content-type": contentType,
1445
- "x-encryption-info": "algo_ecdh",
1838
+ ...signedHeaders(entry, encBody),
1446
1839
  "x-replay-info": "replay",
1447
- "x-key-ident": entry.keyIdent,
1448
- "x-request-ts": ts,
1449
- "x-request-once": once,
1450
- "x-signature": signRequest(entry.shareKey, ts, once, encBody),
1451
1840
  // Per-call header overrides (e.g. app-name=eufy_security to request a security-scoped
1452
1841
  // MQTT cert on an existing eufy_mega session — see EufyMega.getUserMqttInfo).
1453
1842
  ...headerOverrides
@@ -1746,13 +2135,7 @@ ${this.auth_?.authToken ?? ""}`;
1746
2135
  }
1747
2136
  /** Build the /passport/login body (verify_code empty unless 2FA). */
1748
2137
  loginBody(opts) {
1749
- const { clientPublicKeyHex, encryptedPassword } = encryptLoginPassword(this.cfg.password);
1750
- const base = {
1751
- email: this.cfg.email,
1752
- password: encryptedPassword,
1753
- ab: this.cfg.countryCode,
1754
- client_secret_info: { public_key: clientPublicKeyHex }
1755
- };
2138
+ const base = loginCredentials(this.cfg.email, this.cfg.password, this.cfg.countryCode);
1756
2139
  if (opts.verifyCode)
1757
2140
  return { ...base, verify_code: opts.verifyCode, login_id: "" };
1758
2141
  if (opts.captchaId)
@@ -1990,15 +2373,12 @@ ${this.auth_?.authToken ?? ""}`;
1990
2373
  if (cap.length)
1991
2374
  this.logger.debug("[mega] captcha/fa:", JSON.stringify(Object.fromEntries(cap.map((k) => [k, res[k]]))));
1992
2375
  }
1993
- const userId = res.ap_cloud_user_id ?? res.user_id ?? res.userId;
1994
- const accountUserId = res.user_id ?? res.userId;
1995
- const authToken = res.auth_token ?? res.token;
1996
- if (!userId || !authToken)
2376
+ const reply = readLoginReply(res);
2377
+ if (!reply)
1997
2378
  throw new Error(`login returned no session: ${JSON.stringify(res).slice(0, 200)}`);
1998
- const faInfo = res.fa_info ?? {};
1999
- const needs2fa = !isVerify && !!faInfo.info;
2000
- if (needs2fa) {
2001
- this.auth_ = { userId, accountUserId, authToken, geoKey: res.geo_key };
2379
+ const { userId, accountUserId, authToken, geoKey } = reply;
2380
+ if (!isVerify && reply.twoFactorPending) {
2381
+ this.auth_ = { userId, accountUserId, authToken, geoKey };
2002
2382
  this.pendingCaptchaId = void 0;
2003
2383
  this.pending2fa = true;
2004
2384
  await this.sendVerifyCode(messageType);
@@ -2006,8 +2386,8 @@ ${this.auth_?.authToken ?? ""}`;
2006
2386
  }
2007
2387
  this.pendingCaptchaId = void 0;
2008
2388
  this.pending2fa = false;
2009
- this.auth_ = { userId, accountUserId, authToken, geoKey: res.geo_key };
2010
- this.tokenExpiresAt = Number(res.token_expires_at ?? 0) || 0;
2389
+ this.auth_ = { userId, accountUserId, authToken, geoKey };
2390
+ this.tokenExpiresAt = reply.tokenExpiresAt;
2011
2391
  this.sessionKey = void 0;
2012
2392
  await this.ensureSessionKey();
2013
2393
  this.persist();
@@ -2323,115 +2703,6 @@ function mqttUuidFrom(installId) {
2323
2703
  return createHash4("sha256").update(installId).digest("hex").slice(0, 16);
2324
2704
  }
2325
2705
 
2326
- // dist/core/util.js
2327
- var Timer = class {
2328
- handle;
2329
- /** (Re)arm to run `fn` after `ms`, cancelling any already-pending fire. */
2330
- arm(ms, fn) {
2331
- this.cancel();
2332
- this.handle = setTimeout(() => {
2333
- this.handle = void 0;
2334
- fn();
2335
- }, ms);
2336
- this.handle.unref?.();
2337
- }
2338
- /** Cancel a pending fire. No-op if not armed. */
2339
- cancel() {
2340
- if (this.handle) {
2341
- clearTimeout(this.handle);
2342
- this.handle = void 0;
2343
- }
2344
- }
2345
- /** Whether a fire is currently scheduled. */
2346
- get pending() {
2347
- return this.handle !== void 0;
2348
- }
2349
- };
2350
- function assertNever(value) {
2351
- throw new Error(`unhandled variant: ${JSON.stringify(value)}`);
2352
- }
2353
- function asBool(value) {
2354
- if (typeof value === "string")
2355
- return value === "1" || value.toLowerCase() === "true";
2356
- return value === true || value === 1;
2357
- }
2358
- function clamp(value, min, max) {
2359
- if (Number.isNaN(value))
2360
- return min;
2361
- return Math.max(min, Math.min(max, Math.round(value)));
2362
- }
2363
- function structuralEqual(a, b) {
2364
- if (Object.is(a, b))
2365
- return true;
2366
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
2367
- return false;
2368
- const aArr = Array.isArray(a);
2369
- const bArr = Array.isArray(b);
2370
- if (aArr !== bArr)
2371
- return false;
2372
- if (aArr && bArr) {
2373
- if (a.length !== b.length)
2374
- return false;
2375
- for (let i = 0; i < a.length; i++)
2376
- if (!structuralEqual(a[i], b[i]))
2377
- return false;
2378
- return true;
2379
- }
2380
- const aObj = a;
2381
- const bObj = b;
2382
- const aKeys = Object.keys(aObj);
2383
- const bKeys = Object.keys(bObj);
2384
- if (aKeys.length !== bKeys.length)
2385
- return false;
2386
- for (const k of aKeys) {
2387
- if (!Object.prototype.hasOwnProperty.call(bObj, k))
2388
- return false;
2389
- if (!structuralEqual(aObj[k], bObj[k]))
2390
- return false;
2391
- }
2392
- return true;
2393
- }
2394
- function coerceEnumValue(enumObj, value) {
2395
- const n = Number(value);
2396
- return Number.isInteger(n) && Object.values(enumObj).includes(n) ? n : void 0;
2397
- }
2398
- function enumValues(enumObj) {
2399
- return Object.values(enumObj).join("/");
2400
- }
2401
- function enumLabels(enumObj) {
2402
- return Object.fromEntries(Object.entries(enumObj).map(([name, value]) => [value, name]));
2403
- }
2404
- function jsonObject(text2) {
2405
- if (typeof text2 !== "string")
2406
- return void 0;
2407
- try {
2408
- const v = JSON.parse(text2);
2409
- return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
2410
- } catch {
2411
- return void 0;
2412
- }
2413
- }
2414
- function u16le(n) {
2415
- const b = Buffer.alloc(2);
2416
- b.writeUInt16LE(n & 65535);
2417
- return b;
2418
- }
2419
- function u16be(n) {
2420
- const b = Buffer.alloc(2);
2421
- b.writeUInt16BE(n & 65535);
2422
- return b;
2423
- }
2424
- function u32le(n) {
2425
- const b = Buffer.alloc(4);
2426
- b.writeUInt32LE(n >>> 0);
2427
- return b;
2428
- }
2429
- function u32be(n) {
2430
- const b = Buffer.alloc(4);
2431
- b.writeUInt32BE(n >>> 0);
2432
- return b;
2433
- }
2434
-
2435
2706
  // dist/transport/mqtt/availability.js
2436
2707
  function parseStateInfoSignal(topic, raw) {
2437
2708
  const parsedTopic = parseSecureTopic(topic);
@@ -2556,13 +2827,6 @@ function parseAiotDpReport(raw) {
2556
2827
  return Object.keys(out).length ? out : void 0;
2557
2828
  }
2558
2829
 
2559
- // dist/core/raw-dp-hex.js
2560
- function hexToRawDp(hex) {
2561
- if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex))
2562
- return void 0;
2563
- return Buffer.from(hex, "hex").toString("base64");
2564
- }
2565
-
2566
2830
  // dist/transport/mqtt/biz-stream.js
2567
2831
  var BIZ_PROTOCOL = {
2568
2832
  /** Map-stream data: the frames this module parses. */
@@ -2735,28 +2999,16 @@ var DeviceType = {
2735
2999
  LOCK_85D0: 202,
2736
3000
  LOCK_85V0: 203,
2737
3001
  LOCK_85P0: 209,
2738
- NVR_S4_MAX: 300,
2739
- CAMERA_POE_S4: 301,
2740
- WALL_LIGHT_CAM_81A0: 10005,
2741
- INDOOR_8W11: 10006,
2742
- INDOOR_PT_CAMERA_C220: 10008,
2743
- INDOOR_PT_CAMERA_C210: 10009,
2744
- INDOOR_PT_CAMERA_C220_V2: 10010,
2745
- INDOOR_PT_CAMERA_C220_V3: 10011,
2746
- CAMERA_C35: 10035
2747
- };
2748
-
2749
- // dist/core/types.js
2750
- var PRINTER_CATEGORY_RE = /ankermake|eufymake|fdm|3d_?print/i;
2751
- function classifyDevice(raw) {
2752
- const category = raw.category ?? "";
2753
- const hasP2p = typeof raw.p2p_did === "string" && raw.p2p_did.length > 0;
2754
- if (category === "eufy_security" || hasP2p)
2755
- return { category, api: "mega", realtime: "p2p" };
2756
- if (PRINTER_CATEGORY_RE.test(category))
2757
- return { category, api: "ankermake", realtime: "ankermake-mqtt" };
2758
- return { category, api: "mega", realtime: "smqtt" };
2759
- }
3002
+ NVR_S4_MAX: 300,
3003
+ CAMERA_POE_S4: 301,
3004
+ WALL_LIGHT_CAM_81A0: 10005,
3005
+ INDOOR_8W11: 10006,
3006
+ INDOOR_PT_CAMERA_C220: 10008,
3007
+ INDOOR_PT_CAMERA_C210: 10009,
3008
+ INDOOR_PT_CAMERA_C220_V2: 10010,
3009
+ INDOOR_PT_CAMERA_C220_V3: 10011,
3010
+ CAMERA_C35: 10035
3011
+ };
2760
3012
 
2761
3013
  // dist/model/classify.js
2762
3014
  var STATION_TYPES = /* @__PURE__ */ new Set([
@@ -2896,111 +3148,6 @@ function inferName(rec) {
2896
3148
  return void 0;
2897
3149
  }
2898
3150
 
2899
- // dist/core/contracts.js
2900
- var StoredSnapshotUnavailableError = class extends Error {
2901
- reason;
2902
- constructor(reason, message, options) {
2903
- super(message, options);
2904
- this.reason = reason;
2905
- this.name = "StoredSnapshotUnavailableError";
2906
- }
2907
- };
2908
- var RETRYABLE_LIVE_SNAPSHOT_REASONS = [
2909
- "no-keyframe",
2910
- "source-failed",
2911
- "undecodable-burst"
2912
- ];
2913
- var LiveSnapshotUnavailableError = class extends Error {
2914
- reason;
2915
- /** Whether another attempt could plausibly succeed without the host changing anything. */
2916
- retryable;
2917
- constructor(reason, message, options) {
2918
- super(message, options);
2919
- this.reason = reason;
2920
- this.name = "LiveSnapshotUnavailableError";
2921
- this.retryable = RETRYABLE_LIVE_SNAPSHOT_REASONS.includes(reason);
2922
- }
2923
- };
2924
- var CameraDisabledError = class extends Error {
2925
- camera;
2926
- constructor(camera, options) {
2927
- super(`camera${camera ? ` ${camera}` : ""} is disabled \u2014 no live stream possible`, options);
2928
- this.camera = camera;
2929
- this.name = "CameraDisabledError";
2930
- }
2931
- };
2932
- var StationKeyUnavailableError = class extends Error {
2933
- stationSn;
2934
- /** Always true: the negotiation is per connection, so a later one may still produce a key. */
2935
- retryable = true;
2936
- constructor(stationSn, options) {
2937
- super(`station ${stationSn} did not provide its session key, so nothing that requires one could be sent`, options);
2938
- this.stationSn = stationSn;
2939
- this.name = "StationKeyUnavailableError";
2940
- }
2941
- };
2942
- var StationUnreachableError = class extends Error {
2943
- stationSn;
2944
- waitedMs;
2945
- /** Always true: a station unreachable now may answer on a later attempt. */
2946
- retryable = true;
2947
- constructor(stationSn, waitedMs, options) {
2948
- super(`station ${stationSn}'s P2P session did not connect within ${waitedMs}ms, so nothing could be sent to it`, options);
2949
- this.stationSn = stationSn;
2950
- this.waitedMs = waitedMs;
2951
- this.name = "StationUnreachableError";
2952
- }
2953
- };
2954
- var DeviceChannelUnresolvedError = class extends Error {
2955
- sn;
2956
- stationSn;
2957
- constructor(sn, stationSn, options) {
2958
- super(`${sn} has no usable channel on station ${stationSn}, so nothing was sent to it`, options);
2959
- this.sn = sn;
2960
- this.stationSn = stationSn;
2961
- this.name = "DeviceChannelUnresolvedError";
2962
- }
2963
- };
2964
- var LiveStreamStartError = class extends Error {
2965
- reason;
2966
- stage;
2967
- timeoutMs;
2968
- attempts;
2969
- constructor(failure) {
2970
- super(`live stream failed to start (${failure.reason} at ${failure.stage} after ${failure.attempts} attempts; ${failure.timeoutMs}ms deadline)`, failure.cause === void 0 ? void 0 : { cause: failure.cause });
2971
- this.name = "LiveStreamStartError";
2972
- this.reason = failure.reason;
2973
- this.stage = failure.stage;
2974
- this.timeoutMs = failure.timeoutMs;
2975
- this.attempts = failure.attempts;
2976
- }
2977
- };
2978
- var StateConvergenceError = class extends Error {
2979
- sn;
2980
- property;
2981
- param;
2982
- expected;
2983
- observed;
2984
- timeoutMs;
2985
- constructor(failure) {
2986
- super(`device ${failure.sn} did not report ${failure.property} as ${String(failure.expected)} within ${failure.timeoutMs}ms (param ${failure.param} read ${String(failure.observed)})`);
2987
- this.name = "StateConvergenceError";
2988
- this.sn = failure.sn;
2989
- this.property = failure.property;
2990
- this.param = failure.param;
2991
- this.expected = failure.expected;
2992
- this.observed = failure.observed;
2993
- this.timeoutMs = failure.timeoutMs;
2994
- }
2995
- };
2996
- var COMMAND_OBSERVATION = /* @__PURE__ */ Symbol("command-observation");
2997
- function observeCommand(command, observation) {
2998
- return Object.defineProperty(command, COMMAND_OBSERVATION, { configurable: true, value: observation });
2999
- }
3000
- function commandObservation(command) {
3001
- return command[COMMAND_OBSERVATION];
3002
- }
3003
-
3004
3151
  // dist/model/capabilities/access.js
3005
3152
  function setScalar(param, value, ctx, form = "auto") {
3006
3153
  return { kind: "set-param", param, value, form, channel: ctx.channel };
@@ -7581,85 +7728,6 @@ var STORAGE = {
7581
7728
  detection: { codecs: ["station"] }
7582
7729
  };
7583
7730
 
7584
- // dist/core/raw-dp-writer.js
7585
- var WIRE = { VARINT: 0, LENGTH_DELIMITED: 2 };
7586
- function encodeVarint(value) {
7587
- if (!Number.isSafeInteger(value) || value < 0) {
7588
- throw new Error(`varint: ${value} is not a non-negative safe integer`);
7589
- }
7590
- const out = [];
7591
- let v = value;
7592
- while (v > 127) {
7593
- out.push(v % 128 + 128);
7594
- v = Math.floor(v / 128);
7595
- }
7596
- out.push(v);
7597
- return out;
7598
- }
7599
- function zigzag(value) {
7600
- if (!Number.isSafeInteger(value))
7601
- throw new Error(`zigzag: ${value} is not a safe integer`);
7602
- return value < 0 ? -2 * value - 1 : 2 * value;
7603
- }
7604
- var RawDpWriter = class _RawDpWriter {
7605
- body = [];
7606
- /** A varint-valued field (`int32`, `uint32`, `bool`, an enum member). */
7607
- int(field, value) {
7608
- this.body.push(...encodeVarint(tag(field, WIRE.VARINT)), ...encodeVarint(value));
7609
- return this;
7610
- }
7611
- /** A signed varint field (`sint32`, `sint64`), ZigZag-encoded — see {@link zigzag}. */
7612
- sint(field, value) {
7613
- return this.int(field, zigzag(value));
7614
- }
7615
- /** A boolean field, on protobuf's own terms: `1` or `0` in a varint. */
7616
- bool(field, value) {
7617
- return this.int(field, value ? 1 : 0);
7618
- }
7619
- /** A length-delimited field carrying raw bytes — a string, a blob, or a message built elsewhere. */
7620
- bytes(field, value) {
7621
- const bytes2 = value instanceof Uint8Array ? Array.from(value) : [...value];
7622
- this.body.push(...encodeVarint(tag(field, WIRE.LENGTH_DELIMITED)), ...encodeVarint(bytes2.length), ...bytes2);
7623
- return this;
7624
- }
7625
- /**
7626
- * A nested sub-message, built by `build` into a writer of its own.
7627
- *
7628
- * The sub-message is emitted even when `build` writes nothing into it. That is not an oversight: an
7629
- * EMPTY sub-message is how these protocols say "this subsystem exists and is in its zero state", as
7630
- * opposed to saying nothing about it, and the readers on the other side of this file depend on the
7631
- * distinction. Omitting the call is what says nothing about it.
7632
- */
7633
- sub(field, build) {
7634
- const inner = new _RawDpWriter();
7635
- build(inner);
7636
- return this.bytes(field, inner.toBytes());
7637
- }
7638
- /** The message body on its own, unframed — for nesting into another writer. */
7639
- toBytes() {
7640
- return [...this.body];
7641
- }
7642
- /**
7643
- * The finished DP value: `varint(bodyLength) ++ body`, base64-encoded.
7644
- *
7645
- * This is the envelope `RawDpCodec.decode` unwraps, so a value produced here and read back through the
7646
- * codec is a genuine round-trip rather than two implementations of the same guess.
7647
- */
7648
- finish() {
7649
- return Buffer.from([...encodeVarint(this.body.length), ...this.body]).toString("base64");
7650
- }
7651
- };
7652
- function tag(field, wire) {
7653
- if (!Number.isSafeInteger(field) || field < 1)
7654
- throw new Error(`field number: ${field} is not a positive integer`);
7655
- return field * 8 + wire;
7656
- }
7657
- function rawDp(build) {
7658
- const w = new RawDpWriter();
7659
- build(w);
7660
- return w.finish();
7661
- }
7662
-
7663
7731
  // dist/model/capabilities/vacuum-dock.js
7664
7732
  var VACUUM_DOCK_DP = 173;
7665
7733
  var VACUUM_DOCK_INFO_DP = 169;
@@ -13364,58 +13432,6 @@ function parseCleanRecordDetail(blob, codec) {
13364
13432
  };
13365
13433
  }
13366
13434
 
13367
- // dist/core/lz4-block.js
13368
- var MAX_OUTPUT = 64 * 1024 * 1024;
13369
- function lz4BlockDecompress(block, size) {
13370
- if (!Number.isSafeInteger(size) || size < 0 || size > MAX_OUTPUT)
13371
- return void 0;
13372
- const out = Buffer.alloc(size);
13373
- const end = block.length;
13374
- let pos = 0;
13375
- let written = 0;
13376
- const extend2 = (base) => {
13377
- let len = base;
13378
- if (base !== 15)
13379
- return len;
13380
- for (; ; ) {
13381
- if (pos >= end)
13382
- return void 0;
13383
- const extra = block[pos++];
13384
- len += extra;
13385
- if (extra !== 255)
13386
- return len;
13387
- }
13388
- };
13389
- while (pos < end) {
13390
- const token = block[pos++];
13391
- const literals = extend2(token >> 4);
13392
- if (literals === void 0)
13393
- return void 0;
13394
- if (pos + literals > end || written + literals > size)
13395
- return void 0;
13396
- for (let i = 0; i < literals; i++)
13397
- out[written++] = block[pos++];
13398
- if (pos === end)
13399
- break;
13400
- if (pos + 2 > end)
13401
- return void 0;
13402
- const offset = block[pos] | block[pos + 1] << 8;
13403
- pos += 2;
13404
- if (offset === 0 || offset > written)
13405
- return void 0;
13406
- const match = extend2(token & 15);
13407
- if (match === void 0)
13408
- return void 0;
13409
- const length = match + 4;
13410
- if (written + length > size)
13411
- return void 0;
13412
- let from = written - offset;
13413
- for (let i = 0; i < length; i++)
13414
- out[written++] = out[from++];
13415
- }
13416
- return written === size ? out : void 0;
13417
- }
13418
-
13419
13435
  // dist/model/vacuum-map.js
13420
13436
  var DOCK_KINDS = ["charger", "station"];
13421
13437
  var MAP_QUALITIES = ["incomplete", "rough", "effective", "listFull"];
@@ -15214,6 +15230,7 @@ var CMD_DATABASE_IMAGE = 1308;
15214
15230
  var CMD_DATABASE = 1306;
15215
15231
  var DB_QUERY = { FULL_TABLE: 1e4, COMBINATION_WITH_AI: 10011 };
15216
15232
  var GCM_AAD = Buffer.from("eufy security");
15233
+ var LEVEL2_SEQ_BASE = 16909056;
15217
15234
  function paramReport(payload) {
15218
15235
  const list = payload?.params;
15219
15236
  if (!Array.isArray(list))
@@ -16318,11 +16335,6 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16318
16335
  this.seqNumber = this.seqNumber + 1 & 65535;
16319
16336
  this.send(this.connectAddress, RequestMessageType.DATA, data);
16320
16337
  }
16321
- /**
16322
- * Encrypt a level-2 command body (signCode 8): `tag(16) ‖ nonce(12) ‖ [seq,03,02,01](4) ‖
16323
- * ciphertext`, AES-256-GCM under the negotiated session key, AAD "eufy security". Inverse of
16324
- * `decryptLevel2`. The 4-byte sub-header is cleartext (skipped on decrypt); `seq` is a counter.
16325
- */
16326
16338
  /**
16327
16339
  * Decode a `CMD_VIDEO_FRAME` (1300) payload into clean Annex-B H.264 (the 22-byte frame header
16328
16340
  * stripped). Reversed from the V6 app + live H.264 captures: the 22-byte header is
@@ -16389,6 +16401,11 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16389
16401
  }
16390
16402
  return this.rsaModulusHex;
16391
16403
  }
16404
+ /**
16405
+ * Encrypt a level-2 command body (signCode 8): `tag(16) ‖ nonce(12) ‖ seq(4) ‖ ciphertext`,
16406
+ * AES-256-GCM under the negotiated session key, AAD "eufy security". Inverse of `decryptLevel2`.
16407
+ * The 4-byte sub-header is cleartext (skipped on decrypt); `seq` counts up from {@link LEVEL2_SEQ_BASE}.
16408
+ */
16392
16409
  encryptLevel2(plaintext) {
16393
16410
  if (!this.level2Key)
16394
16411
  return void 0;
@@ -16396,8 +16413,8 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16396
16413
  const c = createCipheriv3("aes-256-gcm", this.level2Key, nonce);
16397
16414
  c.setAAD(GCM_AAD);
16398
16415
  const ct = Buffer.concat([c.update(plaintext), c.final()]);
16399
- const sub2 = Buffer.from([this.level2Seq & 255, 3, 2, 1]);
16400
- this.level2Seq = this.level2Seq + 1 & 255;
16416
+ const sub2 = Buffer.alloc(4);
16417
+ sub2.writeUInt32LE(LEVEL2_SEQ_BASE + this.level2Seq++ >>> 0);
16401
16418
  return Buffer.concat([c.getAuthTag(), nonce, sub2, ct]);
16402
16419
  }
16403
16420
  /** Send a no-arg command frame (e.g. CMD_GATEWAYINFO) on a channel (default: the station channel). */
@@ -27189,26 +27206,16 @@ var SolixClient = class _SolixClient {
27189
27206
  }
27190
27207
  /** Build the encrypted, signed `/passport/login` request body + headers for the negotiated key. */
27191
27208
  async postLogin(kx, verifyCode, limitedToken) {
27192
- const { clientPublicKeyHex, encryptedPassword } = encryptLoginPassword(this.password);
27193
27209
  const bodyObj = {
27194
- email: this.email,
27195
- password: encryptedPassword,
27196
- ab: this.country,
27197
- client_secret_info: { public_key: clientPublicKeyHex },
27210
+ ...loginCredentials(this.email, this.password, this.country),
27198
27211
  answer: "",
27199
27212
  captcha_id: "",
27200
27213
  verify_code: verifyCode ?? "",
27201
27214
  login_id: ""
27202
27215
  };
27203
27216
  const encBody = encryptBody(JSON.stringify(bodyObj), kx.shareKey);
27204
- const ts = nowSec();
27205
- const once = genId();
27206
27217
  return this.post(this.apiHost, SOLIX_ENDPOINTS.login, encBody, this.authHeaders({
27207
- "x-encryption-info": "algo_ecdh",
27208
- "x-key-ident": kx.keyIdent,
27209
- "x-request-ts": ts,
27210
- "x-request-once": once,
27211
- "x-signature": signRequest(kx.shareKey, ts, once, encBody),
27218
+ ...signedHeaders(kx, encBody),
27212
27219
  ...limitedToken ? { "x-auth-token": limitedToken } : {}
27213
27220
  }));
27214
27221
  }
@@ -27223,13 +27230,12 @@ var SolixClient = class _SolixClient {
27223
27230
  * observed refusing the header, which is consistent with the two ids agreeing on the accounts seen.
27224
27231
  */
27225
27232
  classifyLogin(data, isVerify) {
27226
- const userId = data.ap_cloud_user_id ?? data.user_id;
27227
- const authToken = data.auth_token;
27228
- if (!userId || !authToken)
27233
+ const reply = readLoginReply(data);
27234
+ if (!reply)
27229
27235
  throw new Error(`Solix login returned no session: ${JSON.stringify(data).slice(0, 160)}`);
27230
- const faInfo = data.fa_info ?? {};
27231
- if (!isVerify && faInfo.info) {
27232
- this.pending2fa = { limitedToken: authToken, userId, geoKey: data.geo_key };
27236
+ const { userId, authToken } = reply;
27237
+ if (!isVerify && reply.twoFactorPending) {
27238
+ this.pending2fa = { limitedToken: authToken, userId, geoKey: reply.geoKey };
27233
27239
  return { status: "2fa", method: "code sent by the passport" };
27234
27240
  }
27235
27241
  this.pending2fa = void 0;
@@ -27238,7 +27244,7 @@ var SolixClient = class _SolixClient {
27238
27244
  userId,
27239
27245
  gtoken: gtoken(userId),
27240
27246
  apiHost: this.apiHost,
27241
- tokenExpiresAt: Number(data.token_expires_at ?? 0) || 0
27247
+ tokenExpiresAt: reply.tokenExpiresAt
27242
27248
  };
27243
27249
  this.persist();
27244
27250
  return { status: "ok", session: this.session_ };
@@ -27393,14 +27399,8 @@ var SolixClient = class _SolixClient {
27393
27399
  throw new Error("not authenticated \u2014 call login() first");
27394
27400
  const kx = await this.keyExchange();
27395
27401
  const encBody = encryptBody(JSON.stringify(payload), kx.shareKey);
27396
- const ts = nowSec();
27397
- const once = genId();
27398
27402
  const env = await this.post(this.apiHost, path, encBody, this.baseHeaders({
27399
- "x-encryption-info": "algo_ecdh",
27400
- "x-key-ident": kx.keyIdent,
27401
- "x-request-ts": ts,
27402
- "x-request-once": once,
27403
- "x-signature": signRequest(kx.shareKey, ts, once, encBody),
27403
+ ...signedHeaders(kx, encBody),
27404
27404
  "x-auth-token": this.session_.authToken,
27405
27405
  gtoken: this.session_.gtoken
27406
27406
  }));