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

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);
@@ -2552,15 +2823,8 @@ function parseAiotDpReport(raw) {
2552
2823
  if (value === null || value === void 0 || typeof value === "object")
2553
2824
  continue;
2554
2825
  out[id] = typeof value === "boolean" ? value ? "1" : "0" : String(value);
2555
- }
2556
- return Object.keys(out).length ? out : void 0;
2557
- }
2558
-
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");
2826
+ }
2827
+ return Object.keys(out).length ? out : void 0;
2564
2828
  }
2565
2829
 
2566
2830
  // dist/transport/mqtt/biz-stream.js
@@ -2746,18 +3010,6 @@ var DeviceType = {
2746
3010
  CAMERA_C35: 10035
2747
3011
  };
2748
3012
 
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
- }
2760
-
2761
3013
  // dist/model/classify.js
2762
3014
  var STATION_TYPES = /* @__PURE__ */ new Set([
2763
3015
  DeviceType.STATION,
@@ -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"];
@@ -27189,26 +27205,16 @@ var SolixClient = class _SolixClient {
27189
27205
  }
27190
27206
  /** Build the encrypted, signed `/passport/login` request body + headers for the negotiated key. */
27191
27207
  async postLogin(kx, verifyCode, limitedToken) {
27192
- const { clientPublicKeyHex, encryptedPassword } = encryptLoginPassword(this.password);
27193
27208
  const bodyObj = {
27194
- email: this.email,
27195
- password: encryptedPassword,
27196
- ab: this.country,
27197
- client_secret_info: { public_key: clientPublicKeyHex },
27209
+ ...loginCredentials(this.email, this.password, this.country),
27198
27210
  answer: "",
27199
27211
  captcha_id: "",
27200
27212
  verify_code: verifyCode ?? "",
27201
27213
  login_id: ""
27202
27214
  };
27203
27215
  const encBody = encryptBody(JSON.stringify(bodyObj), kx.shareKey);
27204
- const ts = nowSec();
27205
- const once = genId();
27206
27216
  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),
27217
+ ...signedHeaders(kx, encBody),
27212
27218
  ...limitedToken ? { "x-auth-token": limitedToken } : {}
27213
27219
  }));
27214
27220
  }
@@ -27223,13 +27229,12 @@ var SolixClient = class _SolixClient {
27223
27229
  * observed refusing the header, which is consistent with the two ids agreeing on the accounts seen.
27224
27230
  */
27225
27231
  classifyLogin(data, isVerify) {
27226
- const userId = data.ap_cloud_user_id ?? data.user_id;
27227
- const authToken = data.auth_token;
27228
- if (!userId || !authToken)
27232
+ const reply = readLoginReply(data);
27233
+ if (!reply)
27229
27234
  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 };
27235
+ const { userId, authToken } = reply;
27236
+ if (!isVerify && reply.twoFactorPending) {
27237
+ this.pending2fa = { limitedToken: authToken, userId, geoKey: reply.geoKey };
27233
27238
  return { status: "2fa", method: "code sent by the passport" };
27234
27239
  }
27235
27240
  this.pending2fa = void 0;
@@ -27238,7 +27243,7 @@ var SolixClient = class _SolixClient {
27238
27243
  userId,
27239
27244
  gtoken: gtoken(userId),
27240
27245
  apiHost: this.apiHost,
27241
- tokenExpiresAt: Number(data.token_expires_at ?? 0) || 0
27246
+ tokenExpiresAt: reply.tokenExpiresAt
27242
27247
  };
27243
27248
  this.persist();
27244
27249
  return { status: "ok", session: this.session_ };
@@ -27393,14 +27398,8 @@ var SolixClient = class _SolixClient {
27393
27398
  throw new Error("not authenticated \u2014 call login() first");
27394
27399
  const kx = await this.keyExchange();
27395
27400
  const encBody = encryptBody(JSON.stringify(payload), kx.shareKey);
27396
- const ts = nowSec();
27397
- const once = genId();
27398
27401
  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),
27402
+ ...signedHeaders(kx, encBody),
27404
27403
  "x-auth-token": this.session_.authToken,
27405
27404
  gtoken: this.session_.gtoken
27406
27405
  }));