@mega-yfue/eufy-sdk 0.6.0-beta.41 → 0.6.0-beta.42

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
@@ -47,10 +47,10 @@ function decryptBody(b64, shareKeyHex) {
47
47
  return Buffer.concat([decipher.update(raw.subarray(16)), decipher.final()]);
48
48
  }
49
49
  function signRequest(shareKeyHex, ts, once, encBodyB64) {
50
- const parts = [ts, once];
50
+ const parts2 = [ts, once];
51
51
  if (encBodyB64)
52
- parts.push(encBodyB64);
53
- return createHmac("sha256", signKey(shareKeyHex)).update(parts.join("+"), "utf-8").digest("hex");
52
+ parts2.push(encBodyB64);
53
+ return createHmac("sha256", signKey(shareKeyHex)).update(parts2.join("+"), "utf-8").digest("hex");
54
54
  }
55
55
  function prepareKeyExchange(localKeyHex = EUFY_MEGA_LOCAL_KEY_HEX) {
56
56
  const localKey = Buffer.from(localKeyHex, "hex");
@@ -411,8 +411,8 @@ function randomPhoneModel(seed) {
411
411
  const rnd = rng(seed);
412
412
  const pick = (a) => a[Math.floor(rnd() * a.length)];
413
413
  const brand = pick(Object.keys(PHONE_MODELS));
414
- const parts = PHONE_MODELS[brand];
415
- return `${brand}${pick(parts.first)}${pick(parts.second)}`.trim();
414
+ const parts2 = PHONE_MODELS[brand];
415
+ return `${brand}${pick(parts2.first)}${pick(parts2.second)}`.trim();
416
416
  }
417
417
  function randomUserAgent(seed, model) {
418
418
  const rnd = rng(seed);
@@ -1052,10 +1052,10 @@ function getIdSuffix(p2pDid) {
1052
1052
  const match = p2pDid.match(/^[A-Z]+-(\d+)-[A-Z]+$/);
1053
1053
  if (match?.length === 2) {
1054
1054
  const num1 = Number.parseInt(match[1][0]);
1055
- const num2 = Number.parseInt(match[1][1]);
1055
+ const num22 = Number.parseInt(match[1][1]);
1056
1056
  const num3 = Number.parseInt(match[1][3]);
1057
1057
  const num4 = Number.parseInt(match[1][5]);
1058
- result = num1 + num2 + num3;
1058
+ result = num1 + num22 + num3;
1059
1059
  if (num3 < 5)
1060
1060
  result = result + num3;
1061
1061
  result = result + num4;
@@ -1735,6 +1735,10 @@ var MegaHttpClient = class {
1735
1735
  get regionShard() {
1736
1736
  return this.region;
1737
1737
  }
1738
+ /** The account's country code as configured (e.g. `"FR"`), the one logins are sent with. */
1739
+ get countryCode() {
1740
+ return this.cfg.countryCode;
1741
+ }
1738
1742
  /**
1739
1743
  * The shard the estimated domain names (`…-ie-…`/`…-ie.` → `ie-pr`), else {@link regionShard}. The
1740
1744
  * eu/us classification cannot name shards like `ie-pr`.
@@ -2069,6 +2073,21 @@ ${this.auth_?.authToken ?? ""}`;
2069
2073
  getProductDataPoint(code) {
2070
2074
  return this.post("things", "/app/things/get_product_data_point", { code });
2071
2075
  }
2076
+ /**
2077
+ * Fetch the **thing descriptions** of the given product codes from the mega `things` service: per
2078
+ * product, every named property with its type and offered values, its actions and events. The v6 app
2079
+ * fetches the same list for the products an account holds and reads each setting's options from it.
2080
+ * Product-level data, not account data. `codeTimeMap` names, per product code, the `update_time` of the
2081
+ * description already held: a product whose description has not changed since comes back with that
2082
+ * `update_time` and no properties, one that has (or is not named) comes back in full. Returns the raw
2083
+ * `data`.
2084
+ */
2085
+ getThingsList(productCodes, codeTimeMap = {}) {
2086
+ return this.post("things", "/app/things/get_things_list", {
2087
+ product_codes: productCodes,
2088
+ code_time_map: codeTimeMap
2089
+ });
2090
+ }
2072
2091
  /**
2073
2092
  * Fetch the **live param list** from the mega `devicemanage` service. Body keys off a
2074
2093
  * `device_sns` ARRAY (a bare `device_sn` 400s with "type mismatch"). Each returned entry is
@@ -2105,10 +2124,10 @@ ${this.auth_?.authToken ?? ""}`;
2105
2124
  * host it points at is unconfirmed, and this client's binary path (`downloadMediaResource`) is
2106
2125
  * host-allowlisted by design.
2107
2126
  */
2108
- getCleanRecords(deviceSn, num2 = 20, page = 1) {
2127
+ getCleanRecords(deviceSn, num3 = 20, page = 1) {
2109
2128
  return this.post("clean", "/app/clean/get_device_clean_record_list", {
2110
2129
  device_sn: deviceSn,
2111
- num: num2,
2130
+ num: num3,
2112
2131
  page
2113
2132
  });
2114
2133
  }
@@ -2123,11 +2142,11 @@ ${this.auth_?.authToken ?? ""}`;
2123
2142
  * APK, and the extracted `.so` set contains no clean-native module. Handing over bytes a caller can
2124
2143
  * take elsewhere is the honest surface; a decode here would be invention.
2125
2144
  */
2126
- getDeviceMapList(deviceSn, channelId = 0, num2 = 1, page = 1, lastOffset = 0) {
2145
+ getDeviceMapList(deviceSn, channelId = 0, num3 = 1, page = 1, lastOffset = 0) {
2127
2146
  return this.post("clean", "/app/clean/get_device_map_list", {
2128
2147
  device_sn: deviceSn,
2129
2148
  channel_id: channelId,
2130
- num: num2,
2149
+ num: num3,
2131
2150
  page,
2132
2151
  last_offset: lastOffset
2133
2152
  });
@@ -2650,15 +2669,15 @@ function subscribeTopics(device) {
2650
2669
  return [secureTopic(device, "res")];
2651
2670
  }
2652
2671
  function parseSecureTopic(topic) {
2653
- const parts = topic.split("/");
2654
- if (parts.length < 5)
2672
+ const parts2 = topic.split("/");
2673
+ if (parts2.length < 5)
2655
2674
  return void 0;
2656
- const [root, category, model, sn] = parts;
2675
+ const [root, category, model, sn] = parts2;
2657
2676
  if (root !== "cmd" && root !== "synq" && root !== "biz" && root !== "dt")
2658
2677
  return void 0;
2659
2678
  if (!category || !model || !sn)
2660
2679
  return void 0;
2661
- return { root, category, model, sn, tail: parts.slice(4).join("/") };
2680
+ return { root, category, model, sn, tail: parts2.slice(4).join("/") };
2662
2681
  }
2663
2682
  function solixDeviceTopics(appName, productCode, deviceSn) {
2664
2683
  const dt = `dt/${appName}/${productCode}/${deviceSn}`;
@@ -3403,8 +3422,17 @@ function provided(needs, build, description, requiredCapabilities) {
3403
3422
  function hasRequiredCapabilities(member, capabilities) {
3404
3423
  return !member.requiredCapabilities?.some((capability) => !capabilities?.has(capability));
3405
3424
  }
3425
+ function thingEnum(m, ctx) {
3426
+ const named = m.enumValues;
3427
+ const property2 = named && m.thing?.map((id) => ctx.things?.properties[id]).find(Boolean);
3428
+ if (!named || !property2)
3429
+ return void 0;
3430
+ const wire = property2.type === "bool" ? [0, 1] : property2.members.map((x) => Number(x.wireValue ?? NaN));
3431
+ const options = Object.fromEntries(wire.filter((v) => v in named).map((v) => [v, named[v]]));
3432
+ return Object.keys(options).length ? options : void 0;
3433
+ }
3406
3434
  function resolvedEnum(m, ctx) {
3407
- const dynamic = ctx ? m.enumValuesFor?.(ctx) : void 0;
3435
+ const dynamic = ctx ? m.enumValuesFor?.(ctx) ?? thingEnum(m, ctx) : void 0;
3408
3436
  return dynamic ? { values: dynamic, dynamic: true } : { values: m.enumValues, dynamic: false };
3409
3437
  }
3410
3438
  function propertiesOf(members, ctx) {
@@ -3525,6 +3553,46 @@ function wasReported(m, from, ctx) {
3525
3553
  function installs(m, ctx) {
3526
3554
  return reports(m.requires, ctx) && (!m.requiresRead || reads(m, ctx)) && (!m.available || m.available(ctx));
3527
3555
  }
3556
+ function kindRoutable(kind, ctx) {
3557
+ return !ctx.refusedCommandKinds?.has(kind);
3558
+ }
3559
+ function probeValues(m, ctx) {
3560
+ if (m.type === "bool")
3561
+ return [false, true];
3562
+ const { values } = resolvedEnum(m, ctx);
3563
+ const keys = values ? Object.keys(values).map(Number) : void 0;
3564
+ if (m.type === "string")
3565
+ return keys && values ? keys.map((k) => values[k]) : [""];
3566
+ if (m.type === "enum")
3567
+ return keys ?? [0];
3568
+ return keys ?? [m.min ?? 0];
3569
+ }
3570
+ function writeRoutable(m, ctx) {
3571
+ if (!ctx.refusedCommandKinds)
3572
+ return true;
3573
+ for (const value of probeValues(m, ctx)) {
3574
+ let kind;
3575
+ try {
3576
+ kind = m.write?.(value, ctx)?.kind;
3577
+ } catch {
3578
+ return true;
3579
+ }
3580
+ if (kind === void 0 || kindRoutable(kind, ctx))
3581
+ return true;
3582
+ }
3583
+ return false;
3584
+ }
3585
+ function actionRoutable(m, ctx) {
3586
+ if (!ctx.refusedCommandKinds)
3587
+ return true;
3588
+ let kind;
3589
+ try {
3590
+ kind = m.action(ctx).kind;
3591
+ } catch {
3592
+ return true;
3593
+ }
3594
+ return kindRoutable(kind, ctx);
3595
+ }
3528
3596
  function bindMembers(members, deps) {
3529
3597
  const { ctx, sink, read, rawDp: rawDp2 } = deps;
3530
3598
  const out = {};
@@ -3550,7 +3618,7 @@ function bindMembers(members, deps) {
3550
3618
  continue;
3551
3619
  }
3552
3620
  if ("action" in m) {
3553
- if (installs(m, ctx))
3621
+ if (installs(m, ctx) && actionRoutable(m, ctx))
3554
3622
  out[name] = describe(m.description, () => sink.dispatch(m.action(ctx)), {});
3555
3623
  continue;
3556
3624
  }
@@ -3567,7 +3635,7 @@ function bindMembers(members, deps) {
3567
3635
  const get = decode ? () => decode(raw(), rawDp2, ctx) : () => narrow(m.type, read, prop, domain);
3568
3636
  Object.defineProperty(out, name, { get, enumerable: true, configurable: true });
3569
3637
  }
3570
- if (!m.write || m.unverified || !installs(m, ctx))
3638
+ if (!m.write || m.unverified || !installs(m, ctx) || !writeRoutable(m, ctx))
3571
3639
  continue;
3572
3640
  if (m.writeOnly)
3573
3641
  unobservable.push(name);
@@ -3683,73 +3751,6 @@ var VIDEO = {
3683
3751
  detection: { evidenceParams: [1056], codecs: ["camera"] }
3684
3752
  };
3685
3753
 
3686
- // dist/model/device-family.js
3687
- var INDOOR_CAMERA_TYPES = /* @__PURE__ */ new Set([
3688
- DeviceType.INDOOR_CAMERA,
3689
- DeviceType.INDOOR_CAMERA_1080,
3690
- DeviceType.INDOOR_PT_CAMERA,
3691
- DeviceType.INDOOR_PT_CAMERA_1080,
3692
- DeviceType.INDOOR_OUTDOOR_CAMERA_1080P,
3693
- DeviceType.INDOOR_OUTDOOR_CAMERA_1080P_NO_LIGHT,
3694
- DeviceType.INDOOR_OUTDOOR_CAMERA_2K,
3695
- DeviceType.INDOOR_COST_DOWN_CAMERA,
3696
- DeviceType.INDOOR_PT_CAMERA_S350,
3697
- DeviceType.INDOOR_PT_CAMERA_E30,
3698
- DeviceType.INDOOR_PT_CAMERA_C210,
3699
- DeviceType.INDOOR_PT_CAMERA_C220,
3700
- DeviceType.INDOOR_PT_CAMERA_C220_V2
3701
- ]);
3702
- var INDOOR_PT_S350_TYPES = /* @__PURE__ */ new Set([
3703
- DeviceType.INDOOR_PT_CAMERA_S350,
3704
- DeviceType.INDOOR_PT_CAMERA_E30,
3705
- DeviceType.INDOOR_PT_CAMERA_C210,
3706
- DeviceType.INDOOR_PT_CAMERA_C220,
3707
- DeviceType.INDOOR_PT_CAMERA_C220_V2
3708
- ]);
3709
- var OUTDOOR_PT_TYPES = /* @__PURE__ */ new Set([
3710
- DeviceType.OUTDOOR_PT_CAMERA,
3711
- DeviceType.SOLO_CAMERA_E30,
3712
- DeviceType.CAMERA_S4,
3713
- DeviceType.SOLOCAM_E42,
3714
- DeviceType.CAMERA_4G_S330
3715
- ]);
3716
- var FLOODLIGHT_TYPES = /* @__PURE__ */ new Set([
3717
- DeviceType.FLOODLIGHT,
3718
- DeviceType.FLOODLIGHT_CAMERA_8422,
3719
- DeviceType.FLOODLIGHT_CAMERA_8423,
3720
- DeviceType.FLOODLIGHT_CAMERA_8424,
3721
- DeviceType.FLOODLIGHT_CAMERA_8425,
3722
- DeviceType.FLOODLIGHT_CAMERA_8426
3723
- ]);
3724
- var WALL_LIGHT_TYPES = /* @__PURE__ */ new Set([
3725
- DeviceType.WALL_LIGHT_CAM,
3726
- DeviceType.WALL_LIGHT_CAM_81A0,
3727
- DeviceType.WALL_LIGHT_CAM_T86C1
3728
- ]);
3729
- var HOMEBASE_TYPES = /* @__PURE__ */ new Set([
3730
- DeviceType.STATION,
3731
- DeviceType.HB3,
3732
- DeviceType.STATION_9000,
3733
- DeviceType.MINIBASE_CHIME,
3734
- DeviceType.HOMEBASE_MINI
3735
- ]);
3736
- var has = (set, t) => t !== void 0 && set.has(t);
3737
- var isIndoorCamera = (ctx) => has(INDOOR_CAMERA_TYPES, ctx.deviceType);
3738
- var isIndoorCamMini = (ctx) => ctx.deviceType === DeviceType.INDOOR_COST_DOWN_CAMERA;
3739
- var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
3740
- var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
3741
- var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
3742
- var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
3743
- var NVR_MEDIA_CAMERA_MODELS = /* @__PURE__ */ new Set(["T8E00", "T8P00", "T8P10", "T8162", "T8410", "T8214"]);
3744
- var isNvrMediaCamera = (ctx) => ctx.model !== void 0 && NVR_MEDIA_CAMERA_MODELS.has(ctx.model);
3745
- var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
3746
- var isNvrFullHdCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
3747
- var isCallDoorbell = (ctx) => ctx.model === "T8223" || ctx.model === "T8224";
3748
- var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
3749
- var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
3750
- var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
3751
- var isClassicWifiLock = (ctx) => ctx.deviceType === DeviceType.LOCK_WIFI && typeof ctx.serial === "string" && ctx.serial.startsWith("T8520") && ctx.serial.charAt(6) === "2";
3752
-
3753
3754
  // dist/model/capabilities/rtsp.js
3754
3755
  var RTSP_PARAM = {
3755
3756
  /**
@@ -3805,7 +3806,7 @@ var RtspRecordingMode = {
3805
3806
  Continuous: 1
3806
3807
  };
3807
3808
  function publishable(ctx) {
3808
- return !isStation9000({ deviceType: ctx.stationDeviceType });
3809
+ return kindRoutable("set-param", ctx);
3809
3810
  }
3810
3811
  function publishCommand(on, ctx) {
3811
3812
  return setScalar(RTSP_PARAM.STREAM_SWITCH, on ? 1 : 0, ctx);
@@ -3831,8 +3832,7 @@ var RTSP_MEMBERS = {
3831
3832
  provenance: "apk",
3832
3833
  description: "Whether this camera's stream is published over RTSP (1145 NAS_STREAM_SWITHC). Name from the apk; the WRITE is verified live on a HomeBase-attached doorbell and two indoor cameras, both directions (DESCRIBE 404\u2192200 with a real SDP, and back).",
3833
3834
  write: (v, ctx) => publishCommand(asBool(v), ctx),
3834
- aliases: { publish: true, withdraw: false },
3835
- available: publishable
3835
+ aliases: { publish: true, withdraw: false }
3836
3836
  },
3837
3837
  /**
3838
3838
  * The device-reported RTSP URL — the full `rtsp://user:pass@host/path`, carrying the credentials the
@@ -4781,8 +4781,8 @@ function decodePowerSource(raw) {
4781
4781
  return String(raw);
4782
4782
  }
4783
4783
  }
4784
- const num2 = Number(raw);
4785
- return Number.isFinite(num2) ? num2 : String(raw);
4784
+ const num3 = Number(raw);
4785
+ return Number.isFinite(num3) ? num3 : String(raw);
4786
4786
  }
4787
4787
  function recordSetting(param, value, ctx) {
4788
4788
  return setStationScalar(param, value, ctx.channel);
@@ -5283,6 +5283,73 @@ var CONTACT = {
5283
5283
  }
5284
5284
  };
5285
5285
 
5286
+ // dist/model/device-family.js
5287
+ var INDOOR_CAMERA_TYPES = /* @__PURE__ */ new Set([
5288
+ DeviceType.INDOOR_CAMERA,
5289
+ DeviceType.INDOOR_CAMERA_1080,
5290
+ DeviceType.INDOOR_PT_CAMERA,
5291
+ DeviceType.INDOOR_PT_CAMERA_1080,
5292
+ DeviceType.INDOOR_OUTDOOR_CAMERA_1080P,
5293
+ DeviceType.INDOOR_OUTDOOR_CAMERA_1080P_NO_LIGHT,
5294
+ DeviceType.INDOOR_OUTDOOR_CAMERA_2K,
5295
+ DeviceType.INDOOR_COST_DOWN_CAMERA,
5296
+ DeviceType.INDOOR_PT_CAMERA_S350,
5297
+ DeviceType.INDOOR_PT_CAMERA_E30,
5298
+ DeviceType.INDOOR_PT_CAMERA_C210,
5299
+ DeviceType.INDOOR_PT_CAMERA_C220,
5300
+ DeviceType.INDOOR_PT_CAMERA_C220_V2
5301
+ ]);
5302
+ var INDOOR_PT_S350_TYPES = /* @__PURE__ */ new Set([
5303
+ DeviceType.INDOOR_PT_CAMERA_S350,
5304
+ DeviceType.INDOOR_PT_CAMERA_E30,
5305
+ DeviceType.INDOOR_PT_CAMERA_C210,
5306
+ DeviceType.INDOOR_PT_CAMERA_C220,
5307
+ DeviceType.INDOOR_PT_CAMERA_C220_V2
5308
+ ]);
5309
+ var OUTDOOR_PT_TYPES = /* @__PURE__ */ new Set([
5310
+ DeviceType.OUTDOOR_PT_CAMERA,
5311
+ DeviceType.SOLO_CAMERA_E30,
5312
+ DeviceType.CAMERA_S4,
5313
+ DeviceType.SOLOCAM_E42,
5314
+ DeviceType.CAMERA_4G_S330
5315
+ ]);
5316
+ var FLOODLIGHT_TYPES = /* @__PURE__ */ new Set([
5317
+ DeviceType.FLOODLIGHT,
5318
+ DeviceType.FLOODLIGHT_CAMERA_8422,
5319
+ DeviceType.FLOODLIGHT_CAMERA_8423,
5320
+ DeviceType.FLOODLIGHT_CAMERA_8424,
5321
+ DeviceType.FLOODLIGHT_CAMERA_8425,
5322
+ DeviceType.FLOODLIGHT_CAMERA_8426
5323
+ ]);
5324
+ var WALL_LIGHT_TYPES = /* @__PURE__ */ new Set([
5325
+ DeviceType.WALL_LIGHT_CAM,
5326
+ DeviceType.WALL_LIGHT_CAM_81A0,
5327
+ DeviceType.WALL_LIGHT_CAM_T86C1
5328
+ ]);
5329
+ var HOMEBASE_TYPES = /* @__PURE__ */ new Set([
5330
+ DeviceType.STATION,
5331
+ DeviceType.HB3,
5332
+ DeviceType.STATION_9000,
5333
+ DeviceType.MINIBASE_CHIME,
5334
+ DeviceType.HOMEBASE_MINI
5335
+ ]);
5336
+ var has = (set, t) => t !== void 0 && set.has(t);
5337
+ var isIndoorCamera = (ctx) => has(INDOOR_CAMERA_TYPES, ctx.deviceType);
5338
+ var isIndoorCamMini = (ctx) => ctx.deviceType === DeviceType.INDOOR_COST_DOWN_CAMERA;
5339
+ var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
5340
+ var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
5341
+ var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
5342
+ var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
5343
+ var NVR_MEDIA_CAMERA_MODELS = /* @__PURE__ */ new Set(["T8E00", "T8P00", "T8P10", "T8162", "T8410", "T8214"]);
5344
+ var isNvrMediaCamera = (ctx) => ctx.model !== void 0 && NVR_MEDIA_CAMERA_MODELS.has(ctx.model);
5345
+ var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
5346
+ var isNvrFullHdCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
5347
+ var isCallDoorbell = (ctx) => ctx.model === "T8223" || ctx.model === "T8224";
5348
+ var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
5349
+ var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
5350
+ var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
5351
+ var isClassicWifiLock = (ctx) => ctx.deviceType === DeviceType.LOCK_WIFI && typeof ctx.serial === "string" && ctx.serial.startsWith("T8520") && ctx.serial.charAt(6) === "2";
5352
+
5286
5353
  // dist/model/capabilities/audio.js
5287
5354
  var AUDIO_CMD = {
5288
5355
  /**
@@ -6057,10 +6124,10 @@ function isT8442(ctx) {
6057
6124
  function usesHomeBasePowerPayload(ctx) {
6058
6125
  if (!isT8410HomeBase3(ctx) || !ctx.firmwareVersion || !/^\d+(?:\.\d+){2,3}$/.test(ctx.firmwareVersion))
6059
6126
  return false;
6060
- const version = ctx.firmwareVersion.split(".").map(Number);
6127
+ const version2 = ctx.firmwareVersion.split(".").map(Number);
6061
6128
  const minimum = [2, 3, 1, 0];
6062
6129
  for (let i = 0; i < minimum.length; i++) {
6063
- const difference = (version[i] ?? 0) - minimum[i];
6130
+ const difference = (version2[i] ?? 0) - minimum[i];
6064
6131
  if (difference !== 0)
6065
6132
  return difference > 0;
6066
6133
  }
@@ -7213,17 +7280,17 @@ var SMART_LIGHT = {
7213
7280
  const params = reportFrom(signal);
7214
7281
  if (!params)
7215
7282
  return null;
7216
- const num2 = (id) => params[id] === void 0 ? void 0 : Number(params[id]);
7283
+ const num3 = (id) => params[id] === void 0 ? void 0 : Number(params[id]);
7217
7284
  const bool = (id) => params[id] === void 0 ? void 0 : params[id] === "1";
7218
7285
  return {
7219
7286
  event: "smartLightState",
7220
7287
  payload: {
7221
7288
  power: bool(LIGHT_PARAM.POWER),
7222
- brightness: num2(LIGHT_PARAM.BRIGHTNESS),
7223
- lightLength: num2(LIGHT_PARAM.LENGTH),
7224
- effectId: num2(LIGHT_PARAM.EFFECT_ID),
7289
+ brightness: num3(LIGHT_PARAM.BRIGHTNESS),
7290
+ lightLength: num3(LIGHT_PARAM.LENGTH),
7291
+ effectId: num3(LIGHT_PARAM.EFFECT_ID),
7225
7292
  colorGradient: bool(LIGHT_PARAM.COLOR_GRADIENT),
7226
- cloudEffectId: num2(LIGHT_PARAM.CLOUD_EFFECT_ID)
7293
+ cloudEffectId: num3(LIGHT_PARAM.CLOUD_EFFECT_ID)
7227
7294
  }
7228
7295
  };
7229
7296
  },
@@ -9244,11 +9311,11 @@ function decodeDoNotDisturbTime(raw, codec, field) {
9244
9311
  const point = codec.nested(window.value)?.find((f) => f.field === field);
9245
9312
  if (point?.kind !== "bytes")
9246
9313
  return void 0;
9247
- const parts = codec.nested(point.value);
9248
- if (!parts)
9314
+ const parts2 = codec.nested(point.value);
9315
+ if (!parts2)
9249
9316
  return void 0;
9250
9317
  const at = (n) => {
9251
- const hit = parts.find((f) => f.field === n);
9318
+ const hit = parts2.find((f) => f.field === n);
9252
9319
  return hit?.kind === "int" ? Number(hit.value) : 0;
9253
9320
  };
9254
9321
  return `${String(at(UNDISTURBED_FIELD.HOUR)).padStart(2, "0")}:${String(at(UNDISTURBED_FIELD.MINUTE)).padStart(2, "0")}`;
@@ -11242,12 +11309,12 @@ function parseDpCatalog(raw) {
11242
11309
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
11243
11310
  return EMPTY_DP_CATALOG;
11244
11311
  const r = raw;
11245
- const list = r.data_point_list;
11246
- if (!Array.isArray(list) || list.length === 0)
11312
+ const list2 = r.data_point_list;
11313
+ if (!Array.isArray(list2) || list2.length === 0)
11247
11314
  return EMPTY_DP_CATALOG;
11248
11315
  let found = 0;
11249
11316
  const enumRanges = /* @__PURE__ */ new Map();
11250
- for (const entry of list) {
11317
+ for (const entry of list2) {
11251
11318
  if (!entry || typeof entry !== "object" || Array.isArray(entry))
11252
11319
  continue;
11253
11320
  const e = entry;
@@ -11552,14 +11619,14 @@ function mergeProperties(caps, ctx) {
11552
11619
  return merged;
11553
11620
  }
11554
11621
  function hintHaystack(rec) {
11555
- const parts = [rec.model, rec.category];
11622
+ const parts2 = [rec.model, rec.category];
11556
11623
  const loose = rec;
11557
11624
  for (const key of ["name", "device_name", "alias", "product_name"]) {
11558
11625
  const v = loose[key];
11559
11626
  if (typeof v === "string")
11560
- parts.push(v);
11627
+ parts2.push(v);
11561
11628
  }
11562
- return parts.filter((p) => typeof p === "string").join(" ");
11629
+ return parts2.filter((p) => typeof p === "string").join(" ");
11563
11630
  }
11564
11631
  var CODEC_LINE = {
11565
11632
  station: "security",
@@ -11603,7 +11670,7 @@ function detectCapabilities(rec, codec) {
11603
11670
  matched = d.deviceTypes.includes(rec.deviceType);
11604
11671
  }
11605
11672
  if (!matched && d.modelHints && haystack.length > 0) {
11606
- matched = d.modelHints.some((re) => re.test(haystack));
11673
+ matched = d.modelHints.some((re2) => re2.test(haystack));
11607
11674
  }
11608
11675
  if (!matched && d.codecs && codec !== void 0) {
11609
11676
  matched = d.codecs.includes(codec);
@@ -11790,9 +11857,9 @@ function memberCommand(m, action, value, ctx) {
11790
11857
  for (const [name, member] of Object.entries(m.members ?? {})) {
11791
11858
  if (!("type" in member))
11792
11859
  continue;
11793
- const property = member.property ?? name;
11860
+ const property2 = member.property ?? name;
11794
11861
  const aliased = member.aliases?.[action];
11795
- const claims = action === property || member.intentNames?.includes(action) === true;
11862
+ const claims = action === property2 || member.intentNames?.includes(action) === true;
11796
11863
  if (!claims && aliased === void 0)
11797
11864
  continue;
11798
11865
  if (member.unverified) {
@@ -11800,7 +11867,10 @@ function memberCommand(m, action, value, ctx) {
11800
11867
  }
11801
11868
  if (!member.write || !installs(member, ctx))
11802
11869
  continue;
11803
- return memberWrite(name, member, claims ? value : aliased, ctx);
11870
+ const cmd = memberWrite(name, member, claims ? value : aliased, ctx);
11871
+ if (!kindRoutable(cmd.kind, ctx))
11872
+ continue;
11873
+ return cmd;
11804
11874
  }
11805
11875
  return void 0;
11806
11876
  }
@@ -12000,6 +12070,7 @@ function resolveProperties(rec, codec, capabilities) {
12000
12070
  category: rec.category,
12001
12071
  capabilities: new Set(capabilities),
12002
12072
  paramIds,
12073
+ things: rec.things,
12003
12074
  stationSerial: rec.parentSn
12004
12075
  });
12005
12076
  }
@@ -14335,11 +14406,11 @@ function parseCleanRecords(raw) {
14335
14406
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
14336
14407
  return EMPTY_CLEAN_RECORD_PAGE;
14337
14408
  const r = raw;
14338
- const list = r.clean_record_list;
14339
- if (!Array.isArray(list))
14409
+ const list2 = r.clean_record_list;
14410
+ if (!Array.isArray(list2))
14340
14411
  return EMPTY_CLEAN_RECORD_PAGE;
14341
14412
  const records = [];
14342
- for (const entry of list) {
14413
+ for (const entry of list2) {
14343
14414
  if (!entry || typeof entry !== "object" || Array.isArray(entry))
14344
14415
  continue;
14345
14416
  const e = entry;
@@ -14401,17 +14472,17 @@ function parseCleanRecordDetail(blob, codec) {
14401
14472
  const fields = codec.nested(Buffer.from(body));
14402
14473
  if (!fields)
14403
14474
  return void 0;
14404
- const num2 = (field) => {
14475
+ const num3 = (field) => {
14405
14476
  const found = fields.find((f) => f.field === field);
14406
14477
  return found?.kind === "int" ? Number(found.value) : 0;
14407
14478
  };
14408
14479
  const reason = fields.find((f) => f.field === DETAIL_FIELD.FINISH_REASON);
14409
14480
  return {
14410
- startTime: num2(DETAIL_FIELD.START_TIME),
14411
- endTime: num2(DETAIL_FIELD.END_TIME),
14412
- duration: num2(DETAIL_FIELD.DURATION),
14413
- area: num2(DETAIL_FIELD.AREA),
14414
- cleanType: num2(DETAIL_FIELD.CLEAN_TYPE),
14481
+ startTime: num3(DETAIL_FIELD.START_TIME),
14482
+ endTime: num3(DETAIL_FIELD.END_TIME),
14483
+ duration: num3(DETAIL_FIELD.DURATION),
14484
+ area: num3(DETAIL_FIELD.AREA),
14485
+ cleanType: num3(DETAIL_FIELD.CLEAN_TYPE),
14415
14486
  // An absent reason is the enum's zero member, which is `completed` — the ordinary way a run ends.
14416
14487
  finishReason: reason === void 0 ? CLEAN_FINISH_REASONS[0] : reason.kind === "int" ? CLEAN_FINISH_REASONS[Number(reason.value)] : void 0
14417
14488
  };
@@ -14904,6 +14975,7 @@ function inspectParams(rec, sn) {
14904
14975
  },
14905
14976
  suggestedRegistry: { model, entry, exists: !!MODEL_REGISTRY[model.toUpperCase()] },
14906
14977
  registrySnippet: buildRegistrySnippet(model, entry),
14978
+ things: rec.things,
14907
14979
  dictionarySnippet: buildDictSnippet(model, unknown)
14908
14980
  };
14909
14981
  }
@@ -15198,9 +15270,9 @@ function sceneNum(v) {
15198
15270
  return void 0;
15199
15271
  }
15200
15272
  function solarbankSceneReadings(scene) {
15201
- const list = scene.solarbank_info?.solarbank_list ?? [];
15273
+ const list2 = scene.solarbank_info?.solarbank_list ?? [];
15202
15274
  const out = [];
15203
- for (const sb of list) {
15275
+ for (const sb of list2) {
15204
15276
  const deviceSn = typeof sb.device_sn === "string" ? sb.device_sn : void 0;
15205
15277
  if (!deviceSn)
15206
15278
  continue;
@@ -16428,11 +16500,11 @@ var CMD_CAMERA_INFO = 1103;
16428
16500
  var GCM_AAD = Buffer.from("eufy security");
16429
16501
  var LEVEL2_SEQ_BASE = 16909056;
16430
16502
  function paramReport(payload) {
16431
- const list = payload?.params;
16432
- if (!Array.isArray(list))
16503
+ const list2 = payload?.params;
16504
+ if (!Array.isArray(list2))
16433
16505
  return void 0;
16434
16506
  const out = {};
16435
- for (const entry of list) {
16507
+ for (const entry of list2) {
16436
16508
  const p = entry;
16437
16509
  if (typeof p?.param_type !== "number" || p.param_value == null)
16438
16510
  continue;
@@ -16441,11 +16513,11 @@ function paramReport(payload) {
16441
16513
  return Object.keys(out).length ? out : void 0;
16442
16514
  }
16443
16515
  function channelReport(payload) {
16444
- const list = payload?.params;
16445
- if (!Array.isArray(list))
16516
+ const list2 = payload?.params;
16517
+ if (!Array.isArray(list2))
16446
16518
  return void 0;
16447
16519
  const out = {};
16448
- for (const entry of list) {
16520
+ for (const entry of list2) {
16449
16521
  const p = entry;
16450
16522
  if (typeof p?.dev_type !== "number" || typeof p.param_type !== "number" || p.param_value == null)
16451
16523
  continue;
@@ -18266,19 +18338,19 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
18266
18338
  if (!this.connectAddress)
18267
18339
  throw new Error("not connected");
18268
18340
  const channel = opts.channel ?? 0;
18269
- const keyed = opts.key !== void 0;
18341
+ const keyed2 = opts.key !== void 0;
18270
18342
  const inner = opts.rawValue ?? {
18271
- ...keyed ? { key: opts.key } : {},
18343
+ ...keyed2 ? { key: opts.key } : {},
18272
18344
  account_id: opts.accountId ?? "",
18273
18345
  cmd: subCmd,
18274
18346
  mChannel: channel,
18275
18347
  mValue3: 0,
18276
18348
  ...opts.omitPayload ? {} : { payload }
18277
18349
  };
18278
- const value = keyed ? JSON.stringify(inner).replace(/=/g, "\\u003d") : JSON.stringify(inner);
18350
+ const value = keyed2 ? JSON.stringify(inner).replace(/=/g, "\\u003d") : JSON.stringify(inner);
18279
18351
  const body = Buffer.concat([
18280
18352
  buildCommandHeader(this.seqNumber, opts.wrapCmd ?? CMD_SET_PAYLOAD),
18281
- buildStringCommandPayload(value, channel, keyed ? void 0 : this.level1Key, 1)
18353
+ buildStringCommandPayload(value, channel, keyed2 ? void 0 : this.level1Key, 1)
18282
18354
  ]);
18283
18355
  this.seqNumber = this.seqNumber + 1 & 65535;
18284
18356
  this.logger.debug(`[p2p] ${this.cfg.stationSn} sendSetPayload subCmd=${subCmd}`);
@@ -22091,15 +22163,15 @@ var Fmp4Muxer = class {
22091
22163
  avcC() {
22092
22164
  const ps = this.params;
22093
22165
  const sps = ps.sps[0] ?? Buffer.alloc(4);
22094
- const parts = [
22166
+ const parts2 = [
22095
22167
  Buffer.from([1, sps[1] ?? 66, sps[2] ?? 0, sps[3] ?? 30, 255, 224 | ps.sps.length])
22096
22168
  ];
22097
22169
  for (const s of ps.sps)
22098
- parts.push(u16(s.length), s);
22099
- parts.push(Buffer.from([ps.pps.length]));
22170
+ parts2.push(u16(s.length), s);
22171
+ parts2.push(Buffer.from([ps.pps.length]));
22100
22172
  for (const p of ps.pps)
22101
- parts.push(u16(p.length), p);
22102
- return Buffer.concat(parts);
22173
+ parts2.push(u16(p.length), p);
22174
+ return Buffer.concat(parts2);
22103
22175
  }
22104
22176
  hvcC() {
22105
22177
  const ps = this.params;
@@ -22143,10 +22215,10 @@ var Fmp4Muxer = class {
22143
22215
  const push = (nalType, nals) => {
22144
22216
  if (!nals.length)
22145
22217
  return;
22146
- const parts = [Buffer.from([128 | nalType]), u16(nals.length)];
22218
+ const parts2 = [Buffer.from([128 | nalType]), u16(nals.length)];
22147
22219
  for (const n of nals)
22148
- parts.push(u16(n.length), n);
22149
- arrays.push(Buffer.concat(parts));
22220
+ parts2.push(u16(n.length), n);
22221
+ arrays.push(Buffer.concat(parts2));
22150
22222
  };
22151
22223
  push(32, ps.vps);
22152
22224
  push(33, ps.sps);
@@ -22188,11 +22260,11 @@ var Fmp4Muxer = class {
22188
22260
  */
22189
22261
  trun(samples) {
22190
22262
  const flags = 1793;
22191
- const parts = [u32(flags), u32(samples.length), u32(0)];
22263
+ const parts2 = [u32(flags), u32(samples.length), u32(0)];
22192
22264
  for (const s of samples) {
22193
- parts.push(u32(s.duration), u32(s.data.length), u32(s.keyframe ? 33554432 : 16842752));
22265
+ parts2.push(u32(s.duration), u32(s.data.length), u32(s.keyframe ? 33554432 : 16842752));
22194
22266
  }
22195
- return box("trun", Buffer.concat(parts));
22267
+ return box("trun", Buffer.concat(parts2));
22196
22268
  }
22197
22269
  hasAudioTrack() {
22198
22270
  return !!this.audioCodec && !this.audioDisabled;
@@ -24745,10 +24817,10 @@ var H264Depacketizer = class {
24745
24817
  }
24746
24818
  if (marker) {
24747
24819
  if (!this.broken && !this.fu && this.nals.length) {
24748
- const parts = [];
24820
+ const parts2 = [];
24749
24821
  for (const n of this.nals)
24750
- parts.push(START_CODE, n);
24751
- this.onUnit(Buffer.concat(parts), timestamp);
24822
+ parts2.push(START_CODE, n);
24823
+ this.onUnit(Buffer.concat(parts2), timestamp);
24752
24824
  }
24753
24825
  this.nals = [];
24754
24826
  this.fu = void 0;
@@ -24902,18 +24974,18 @@ var PtcsReassembler = class {
24902
24974
  if (p.lastIndex === void 0)
24903
24975
  return true;
24904
24976
  let total = 0;
24905
- const parts = [];
24977
+ const parts2 = [];
24906
24978
  for (let i = 0; i <= p.lastIndex; i++) {
24907
24979
  const c = p.chunks.get(i);
24908
24980
  if (!c)
24909
24981
  return true;
24910
- parts.push(c);
24982
+ parts2.push(c);
24911
24983
  total += c.length;
24912
24984
  }
24913
24985
  this.partials.delete(key);
24914
24986
  if (total !== p.frameLength)
24915
24987
  return false;
24916
- this.onFrame(Buffer.concat(parts, total), p.channel);
24988
+ this.onFrame(Buffer.concat(parts2, total), p.channel);
24917
24989
  return true;
24918
24990
  }
24919
24991
  /** Drop frames that stopped arriving; call periodically. */
@@ -25886,6 +25958,11 @@ var StationStreamBusyError = class extends Error {
25886
25958
  this.name = "StationStreamBusyError";
25887
25959
  }
25888
25960
  };
25961
+ var RTC_CONTROL_COMMAND_KINDS = /* @__PURE__ */ new Set([
25962
+ "set-payload",
25963
+ "set-json"
25964
+ ]);
25965
+ var isRtcControlCommand = (cmd) => RTC_CONTROL_COMMAND_KINDS.has(cmd.kind);
25889
25966
  var RtcCommandRouter = class {
25890
25967
  deps;
25891
25968
  sessions = /* @__PURE__ */ new Map();
@@ -25899,7 +25976,7 @@ var RtcCommandRouter = class {
25899
25976
  this.deps = deps;
25900
25977
  }
25901
25978
  async dispatchCommand(route, cmd) {
25902
- if (cmd.kind !== "set-payload" && cmd.kind !== "set-json") {
25979
+ if (!isRtcControlCommand(cmd)) {
25903
25980
  throw new Error(`rtc: ${cmd.kind} is not routable over the RTC control channel (only set-payload or set-json)`);
25904
25981
  }
25905
25982
  if (route.mediaTrack)
@@ -28462,10 +28539,10 @@ var McsParser = class extends EventEmitter12 {
28462
28539
  }
28463
28540
  }
28464
28541
  onVersion() {
28465
- const version = this.data.readInt8(0);
28542
+ const version2 = this.data.readInt8(0);
28466
28543
  this.data = this.data.subarray(1);
28467
- if (version < 38)
28468
- throw new Error(`MCS: wrong protocol version ${version}`);
28544
+ if (version2 < 38)
28545
+ throw new Error(`MCS: wrong protocol version ${version2}`);
28469
28546
  this.onTag();
28470
28547
  }
28471
28548
  onTag() {
@@ -29155,7 +29232,272 @@ function mediaFailureTag(error) {
29155
29232
  return Number.isInteger(status) && status >= 100 && status <= 599 ? { cause, status } : { cause };
29156
29233
  }
29157
29234
 
29235
+ // dist/model/capabilities/thing-description.js
29236
+ var APP_SDK_VERSION = "2.3.3.1";
29237
+ var SUFFIXED_STATION_PREFIXES = ["T8010", "T8020", "T8021", "T8023"];
29238
+ function re(pattern, value) {
29239
+ if (!pattern || !value)
29240
+ return true;
29241
+ if (pattern.length > 512 || value.length > 2048)
29242
+ return false;
29243
+ try {
29244
+ const insensitive = pattern.includes("(?i)");
29245
+ return new RegExp(pattern.replaceAll("(?i)", ""), insensitive ? "i" : "").test(value);
29246
+ } catch {
29247
+ return false;
29248
+ }
29249
+ }
29250
+ var parts = (v) => v.split(".");
29251
+ var intOf = (s) => {
29252
+ const t = (s ?? "0").replace(/[a-zA-Z]+$/, "");
29253
+ return /^[+-]?\d+$/.test(t) ? Number(t) : void 0;
29254
+ };
29255
+ var numericPrefix = (p) => {
29256
+ const i = p.findIndex((s) => intOf(s) === void 0);
29257
+ return i < 0 ? p.length : i;
29258
+ };
29259
+ function versionAbove(a, b) {
29260
+ const pa = parts(a);
29261
+ const pb = parts(b);
29262
+ const n = Math.max(numericPrefix(pa), numericPrefix(pb));
29263
+ for (let i = 0; i < n; i++) {
29264
+ const d = (intOf(pa[i]) ?? 0) - (intOf(pb[i]) ?? 0);
29265
+ if (d !== 0)
29266
+ return d > 0;
29267
+ }
29268
+ const ta = pa.slice(n);
29269
+ const tb = pb.slice(n);
29270
+ if (!ta.length || !tb.length)
29271
+ return ta.length > 0 && !tb.length;
29272
+ const lastInt = (t) => t.findLastIndex((s) => /^[+-]?\d+$/.test(s));
29273
+ const ia = lastInt(ta);
29274
+ const ib = lastInt(tb);
29275
+ const head = (t, i) => (i >= 0 ? t.slice(0, i) : t).join(".");
29276
+ if (head(ta, ia) !== head(tb, ib))
29277
+ return false;
29278
+ return (ia >= 0 ? Number(ta[ia]) : 0) > (ib >= 0 ? Number(tb[ib]) : 0);
29279
+ }
29280
+ function versionEqual(a, b) {
29281
+ const pa = parts(a);
29282
+ const pb = parts(b);
29283
+ const n = Math.max(numericPrefix(pa), numericPrefix(pb));
29284
+ for (let i = 0; i < n; i++)
29285
+ if ((intOf(pa[i]) ?? 0) !== (intOf(pb[i]) ?? 0))
29286
+ return false;
29287
+ return pa.slice(n).join(".") === pb.slice(n).join(".");
29288
+ }
29289
+ function version(current, qualifier, target) {
29290
+ switch (qualifier) {
29291
+ case "<":
29292
+ return versionAbove(target, current);
29293
+ case "<=":
29294
+ return versionAbove(target, current) || versionEqual(current, target);
29295
+ case "=":
29296
+ return versionEqual(current, target);
29297
+ case "!=":
29298
+ return !versionEqual(current, target);
29299
+ case ">=":
29300
+ return versionAbove(current, target) || versionEqual(current, target);
29301
+ case ">":
29302
+ return versionAbove(current, target);
29303
+ case "RE":
29304
+ return re(target, current);
29305
+ default:
29306
+ return false;
29307
+ }
29308
+ }
29309
+ function compare(current, qualifier, target) {
29310
+ if (qualifier === "RE")
29311
+ return re(target, current);
29312
+ switch (qualifier) {
29313
+ case "=":
29314
+ return current === target;
29315
+ case "!=":
29316
+ return current !== target;
29317
+ case "<":
29318
+ return current < target;
29319
+ case "<=":
29320
+ return current <= target;
29321
+ case ">=":
29322
+ return current >= target;
29323
+ case ">":
29324
+ return current > target;
29325
+ default:
29326
+ return false;
29327
+ }
29328
+ }
29329
+ function cameraInfo(facts, qualifier, value) {
29330
+ let spec;
29331
+ try {
29332
+ spec = JSON.parse(value);
29333
+ } catch {
29334
+ return false;
29335
+ }
29336
+ const reported = typeof spec?.param === "number" ? facts.params?.[spec.param] : void 0;
29337
+ if (reported === void 0)
29338
+ return false;
29339
+ if (qualifier === "equals")
29340
+ return typeof spec.value === "string" && spec.value === reported;
29341
+ if (qualifier === "RE")
29342
+ return typeof spec.regex === "string" && re(spec.regex, reported);
29343
+ return false;
29344
+ }
29345
+ function condition(c, facts) {
29346
+ const station = facts.stationSerial ?? facts.serial ?? "";
29347
+ switch (c.type) {
29348
+ case "device_version":
29349
+ return version(facts.firmwareVersion ?? "", c.qualifier, c.value);
29350
+ case "parent_version": {
29351
+ const v = facts.stationFirmwareVersion ?? "";
29352
+ const clean = SUFFIXED_STATION_PREFIXES.some((p) => station.startsWith(p)) ? v.replace(/[^0-9.][^.]*/g, "") : v;
29353
+ return version(clean, c.qualifier, c.value);
29354
+ }
29355
+ case "parent_sn":
29356
+ return re(c.value, station);
29357
+ case "device_sn":
29358
+ return re(c.value, facts.serial ?? "");
29359
+ case "user_country":
29360
+ return re(c.value, facts.country ?? "");
29361
+ case "user_domain":
29362
+ return re(c.value, "");
29363
+ case "sdk_version":
29364
+ return version(APP_SDK_VERSION, c.qualifier, c.value);
29365
+ case "associated_field":
29366
+ case "parent_associated_field":
29367
+ return compare("", c.qualifier, c.value);
29368
+ case "user_permission":
29369
+ return c.qualifier === "in";
29370
+ case "camera_info":
29371
+ return cameraInfo(facts, c.qualifier, c.value);
29372
+ default:
29373
+ return false;
29374
+ }
29375
+ }
29376
+ function conditionsPass(list2, facts) {
29377
+ return list2.length === 0 || list2.some((entry) => entry.every((c) => condition(c, facts)));
29378
+ }
29379
+ var bare = (identifier) => {
29380
+ const i = identifier.toLowerCase().indexOf("__v");
29381
+ return i > 0 ? identifier.slice(0, i) : identifier;
29382
+ };
29383
+ function resolveThing(td, facts) {
29384
+ const properties = /* @__PURE__ */ Object.create(null);
29385
+ const keys = Object.keys(td.properties);
29386
+ for (const base of new Set(keys.map(bare))) {
29387
+ let chosen = base;
29388
+ let most = -1;
29389
+ for (const key of keys) {
29390
+ if (key === base || bare(key) !== base)
29391
+ continue;
29392
+ const p2 = td.properties[key];
29393
+ if (p2.readConditions.length === 0 || !conditionsPass(p2.readConditions, facts))
29394
+ continue;
29395
+ const count2 = p2.readConditions.reduce((n, entry) => n + entry.length, 0);
29396
+ if (count2 > most)
29397
+ [chosen, most] = [key, count2];
29398
+ }
29399
+ const p = td.properties[chosen];
29400
+ if (!p || !conditionsPass(p.availableConditions, facts))
29401
+ continue;
29402
+ properties[base] = conditionsPass(p.writeConditions, facts) ? p : { ...p, accessMode: "R" };
29403
+ }
29404
+ const available = (entries) => {
29405
+ const out = /* @__PURE__ */ Object.create(null);
29406
+ for (const [k, v] of Object.entries(entries))
29407
+ if (conditionsPass(v.availableConditions, facts))
29408
+ out[k] = v;
29409
+ return out;
29410
+ };
29411
+ return { ...td, properties, actions: available(td.actions), events: available(td.events) };
29412
+ }
29413
+ var str3 = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
29414
+ var optStr = (v) => v == null || v === "" ? void 0 : str3(v);
29415
+ var num2 = (v) => {
29416
+ const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
29417
+ return Number.isFinite(n) ? n : void 0;
29418
+ };
29419
+ var list = (v) => Array.isArray(v) ? v.filter((x) => x && typeof x === "object") : [];
29420
+ function conditions(v) {
29421
+ return list(v?.condition_list).map((entry) => list(entry.conditions).map((c) => ({
29422
+ type: str3(c.type),
29423
+ qualifier: str3(c.qualifier),
29424
+ value: str3(c.value),
29425
+ ...c.prop_id ? { propId: str3(c.prop_id) } : {}
29426
+ })));
29427
+ }
29428
+ function keyed(entries, parse) {
29429
+ const out = /* @__PURE__ */ Object.create(null);
29430
+ for (const e of entries)
29431
+ if (typeof e.identifier === "string" && e.identifier)
29432
+ out[e.identifier] = parse(e);
29433
+ return out;
29434
+ }
29435
+ function property(p) {
29436
+ const specs = p.data_type?.specs ?? {};
29437
+ return {
29438
+ identifier: p.identifier,
29439
+ accessMode: str3(p.access_mode),
29440
+ type: str3(p.data_type?.type),
29441
+ members: list(specs.eunmList).map((m) => ({
29442
+ value: str3(m.value),
29443
+ ...m.associatedValue != null ? { wireValue: str3(m.associatedValue) } : {},
29444
+ desc: str3(m.desc),
29445
+ isDefault: m.isDefault === 1 || m.isDefault === true
29446
+ })),
29447
+ ...optStr(specs.defaultValue) !== void 0 ? { defaultValue: str3(specs.defaultValue) } : {},
29448
+ ...num2(specs.min) !== void 0 ? { min: num2(specs.min) } : {},
29449
+ ...num2(specs.max) !== void 0 ? { max: num2(specs.max) } : {},
29450
+ ...num2(specs.step) !== void 0 ? { step: num2(specs.step) } : {},
29451
+ ...optStr(specs.unit) !== void 0 ? { unit: str3(specs.unit) } : {},
29452
+ readConditions: conditions(p.read_conditions),
29453
+ writeConditions: conditions(p.write_conditions),
29454
+ availableConditions: conditions(p.available_conditions)
29455
+ };
29456
+ }
29457
+ function parseThingsList(raw) {
29458
+ const out = /* @__PURE__ */ Object.create(null);
29459
+ for (const t of list(raw?.things_list)) {
29460
+ const profile = t.profile ?? {};
29461
+ const code = profile.product_code;
29462
+ if (typeof code !== "string" || !code)
29463
+ continue;
29464
+ out[code] = {
29465
+ productCode: code,
29466
+ version: str3(t.version),
29467
+ updateTime: num2(t.update_time) ?? 0,
29468
+ largeVersion: num2(t.large_version) ?? 0,
29469
+ largeVersionUpdateTime: num2(t.large_version_update_time) ?? 0,
29470
+ bundle: {
29471
+ id: str3(profile.plugin_id),
29472
+ path: str3(profile.plugin_path),
29473
+ zipId: str3(profile.plugin_id_zip),
29474
+ zipPath: str3(profile.plugin_path_zip)
29475
+ },
29476
+ properties: keyed(list(t.properties), property),
29477
+ actions: keyed(list(t.actions), (a) => ({
29478
+ identifier: a.identifier,
29479
+ ...optStr(a.input_data?.type) ? { inputType: str3(a.input_data.type) } : {},
29480
+ ...optStr(a.output_data?.type) ? { outputType: str3(a.output_data.type) } : {},
29481
+ availableConditions: conditions(a.available_conditions)
29482
+ })),
29483
+ events: keyed(list(t.events), (e) => ({
29484
+ identifier: e.identifier,
29485
+ eventType: str3(e.event_type),
29486
+ ...optStr(e.output_data?.type) ? { outputType: str3(e.output_data.type) } : {},
29487
+ ...optStr(e.output_data?.specs?.defaultValue) ? { defaultValue: str3(e.output_data.specs.defaultValue) } : {},
29488
+ availableConditions: conditions(e.available_conditions)
29489
+ }))
29490
+ };
29491
+ }
29492
+ return out;
29493
+ }
29494
+
29158
29495
  // dist/client/device-registry.js
29496
+ function recordString(raw, key) {
29497
+ const nested = raw.deviceParams?.[key];
29498
+ const v = typeof nested === "string" && nested ? nested : raw[key];
29499
+ return typeof v === "string" && v ? v : void 0;
29500
+ }
29159
29501
  function mergeParams(raw, params, paramUpdatedAt) {
29160
29502
  for (const p of raw ?? []) {
29161
29503
  if (!p || p.param_type == null || p.param_value == null)
@@ -29226,6 +29568,8 @@ var DeviceRegistry = class {
29226
29568
  deviceCapsCache = /* @__PURE__ */ new Map();
29227
29569
  /** Per-serial cloud data points of an AIoT robot, joined beneath the params in {@link record} — see {@link loadCloudDps}. */
29228
29570
  cloudDps = /* @__PURE__ */ new Map();
29571
+ /** Per-product-code thing description — see {@link loadThings}; `undefined` for a code the cloud returned none for. */
29572
+ things = /* @__PURE__ */ new Map();
29229
29573
  /** Per-serial realtime state, keyed by param id in the device's own namespace (see {@link DeviceRecord.dpParams}). */
29230
29574
  dpParams = /* @__PURE__ */ new Map();
29231
29575
  /** Callers blocked in {@link awaitRealtimeState}, released by the device's first report. */
@@ -29339,6 +29683,7 @@ var DeviceRegistry = class {
29339
29683
  }
29340
29684
  }
29341
29685
  await this.loadCloudDps(seen);
29686
+ await this.loadThings(seen);
29342
29687
  this.lastRefreshPartial = partial;
29343
29688
  if (partial) {
29344
29689
  for (const prev of this.devices)
@@ -29465,6 +29810,45 @@ var DeviceRegistry = class {
29465
29810
  this.cloudDps.set(sn, dps);
29466
29811
  }
29467
29812
  }
29813
+ /**
29814
+ * The device's product description resolved for this device ({@link resolveThing}): against its serial
29815
+ * and firmware (`main_sw_version`), the station it is reached through and that station's firmware, the
29816
+ * account's country and its reported params.
29817
+ */
29818
+ thingsFor(dev, station, params) {
29819
+ const td = this.things.get(dev.model ?? "");
29820
+ if (!td)
29821
+ return void 0;
29822
+ const firmware = (d) => recordString(d?.raw ?? {}, "main_sw_version");
29823
+ return resolveThing(td, {
29824
+ serial: dev.sn,
29825
+ firmwareVersion: firmware(dev),
29826
+ stationSerial: station,
29827
+ stationFirmwareVersion: firmware(this.devices.find((d) => d.sn === station)),
29828
+ country: this.mega.countryCode,
29829
+ params
29830
+ });
29831
+ }
29832
+ /**
29833
+ * Fetch, once per start, the thing description of each product code the account has, in one
29834
+ * `get_things_list` call. A failed call is logged and asked again on the next refresh.
29835
+ */
29836
+ async loadThings(seen) {
29837
+ const codes = [...new Set([...seen.values()].map((d) => d.model ?? ""))].filter((c) => c && !this.things.has(c));
29838
+ if (!codes.length)
29839
+ return;
29840
+ let parsed;
29841
+ try {
29842
+ parsed = parseThingsList(await this.mega.getThingsList(codes));
29843
+ } catch (e) {
29844
+ if (e instanceof SessionExpiredError)
29845
+ throw e;
29846
+ this.logger.debug(`[registry] thing descriptions unavailable: ${e?.message ?? e}`);
29847
+ return;
29848
+ }
29849
+ for (const code of codes)
29850
+ this.things.set(code, parsed[code]);
29851
+ }
29468
29852
  /**
29469
29853
  * Resolve a serial to a {@link DeviceRecord} with current params: starts from the device-list params, then
29470
29854
  * overlays a fresh `get_device_param_list` when that call is available to this account. An AIoT robot's
@@ -29529,7 +29913,8 @@ var DeviceRegistry = class {
29529
29913
  parentSn: station === sn ? void 0 : station,
29530
29914
  params,
29531
29915
  paramUpdatedAt,
29532
- dpParams: this.dpParams.get(sn)
29916
+ dpParams: this.dpParams.get(sn),
29917
+ things: this.thingsFor(dev, station, params)
29533
29918
  };
29534
29919
  }
29535
29920
  /**
@@ -29798,11 +30183,6 @@ var PushRepeats = class {
29798
30183
  };
29799
30184
 
29800
30185
  // dist/client/eufy-mega.js
29801
- function recordString(raw, key) {
29802
- const nested = raw.deviceParams?.[key];
29803
- const v = typeof nested === "string" && nested ? nested : raw[key];
29804
- return typeof v === "string" && v ? v : void 0;
29805
- }
29806
30186
  function adminUserIdFrom(raw) {
29807
30187
  const member = (raw ?? {}).member;
29808
30188
  const id = member?.admin_user_id;
@@ -29823,6 +30203,16 @@ var PUSH_RETRY_MIN_MS = 3e4;
29823
30203
  var PUSH_RETRY_MAX_MS = 6e5;
29824
30204
  var STATION_REOPEN_MIN_MS = 5e3;
29825
30205
  var STATION_REOPEN_MAX_MS = 3e5;
30206
+ var PORTAL_COMMAND_KINDS = /* @__PURE__ */ new Set([
30207
+ "set-payload",
30208
+ "set-json",
30209
+ "set-param",
30210
+ "set-json-raw",
30211
+ "p2p-station-scalar",
30212
+ "p2p-int-string",
30213
+ "p2p-privacy-burst"
30214
+ ]);
30215
+ var RTC_CONTROL_REFUSED_KINDS = new Set([...PORTAL_COMMAND_KINDS].filter((kind) => !RTC_CONTROL_COMMAND_KINDS.has(kind)));
29826
30216
  var RealtimeStartupSupersededError = class extends Error {
29827
30217
  };
29828
30218
  var RefreshWindowClosedError = class extends Error {
@@ -31934,6 +32324,8 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31934
32324
  const parentRaw = parent?.raw;
31935
32325
  const resolved = resolveDevice(rec);
31936
32326
  const dpCatalog = (resolved.codec === "vacuum" || resolved.codec === "mower") && rec.model ? await this.fetchDpCatalog(rec.model) : void 0;
32327
+ const rtcRoute = this.rtcRouteFor(sn);
32328
+ const refusedCommandKinds = !rtcRoute ? void 0 : rtcRoute.mediaTrack ? PORTAL_COMMAND_KINDS : RTC_CONTROL_REFUSED_KINDS;
31937
32329
  return {
31938
32330
  channel,
31939
32331
  timezone: recordString(parentRaw ?? {}, "time_zone"),
@@ -31954,6 +32346,7 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31954
32346
  macAddress: recordString(raw, "wifi_mac"),
31955
32347
  updateAvailable: raw.needUpdate === true || raw.needUpdate === 1 || raw.needUpdate === "1",
31956
32348
  paramIds: new Set([...Object.keys(rec.params), ...Object.keys(rec.dpParams ?? {})].map(Number)),
32349
+ things: rec.things,
31957
32350
  capabilities: new Set(resolved.capabilities),
31958
32351
  // Member identity for ff09-* command devices (see core/contracts Ff09Identity).
31959
32352
  adminUserId: typeof member.admin_user_id === "string" ? member.admin_user_id : void 0,
@@ -31965,6 +32358,7 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31965
32358
  // Topology as the record states it: a parent that isn't the device itself means HomeBase-attached.
31966
32359
  homeBaseAttached: !!raw.parent_sn && raw.parent_sn !== dev.sn,
31967
32360
  stationDeviceType: this.stationDeviceTypeOf(sn),
32361
+ refusedCommandKinds,
31968
32362
  dpCatalog
31969
32363
  };
31970
32364
  }
@@ -32473,8 +32867,8 @@ var SolixClient = class _SolixClient {
32473
32867
  */
32474
32868
  async getSites() {
32475
32869
  const data = await this.authed("POST", SOLIX_ENDPOINTS.getSiteList, {});
32476
- const list = data?.site_list ?? [];
32477
- return list.filter((s) => typeof s?.site_id === "string" && s.site_id.length > 0);
32870
+ const list2 = data?.site_list ?? [];
32871
+ return list2.filter((s) => typeof s?.site_id === "string" && s.site_id.length > 0);
32478
32872
  }
32479
32873
  /** Per-user AWS-IoT MQTT credentials (cert/key/endpoint/thing) for the real-time device plane. */
32480
32874
  async getUserMqttInfo() {
@@ -32742,21 +33136,21 @@ function solixStateReadings(frame) {
32742
33136
  continue;
32743
33137
  const type = value[0];
32744
33138
  const pl = value.subarray(1);
32745
- let num2;
33139
+ let num3;
32746
33140
  if (type === 5 && pl.length >= 4)
32747
- num2 = pl.readFloatLE(0);
33141
+ num3 = pl.readFloatLE(0);
32748
33142
  else if (type === 2 && pl.length >= 2)
32749
- num2 = pl.readUInt16LE(0);
33143
+ num3 = pl.readUInt16LE(0);
32750
33144
  else if (type === 1 && pl.length >= 1)
32751
- num2 = pl[0];
33145
+ num3 = pl[0];
32752
33146
  else if (type === 3 && pl.length >= 2)
32753
- num2 = pl[1];
32754
- if (num2 === void 0)
33147
+ num3 = pl[1];
33148
+ if (num3 === void 0)
32755
33149
  continue;
32756
- out[`state_${tag2.toString(16)}`] = num2;
33150
+ out[`state_${tag2.toString(16)}`] = num3;
32757
33151
  const name = SOLIX_STATE_FIELD_NAMES[tag2];
32758
33152
  if (name)
32759
- out[name] = num2;
33153
+ out[name] = num3;
32760
33154
  }
32761
33155
  return out;
32762
33156
  }
@@ -33042,9 +33436,9 @@ var SolixMqtt = class extends EventEmitter15 {
33042
33436
  const frame = decodeSolixParamFrame(buf);
33043
33437
  if (!frame)
33044
33438
  return;
33045
- const parts = topic.split("/");
33046
- const productCode = parts[2] ?? "";
33047
- let deviceSn = frame.deviceSn ?? parts[3] ?? "";
33439
+ const parts2 = topic.split("/");
33440
+ const productCode = parts2[2] ?? "";
33441
+ let deviceSn = frame.deviceSn ?? parts2[3] ?? "";
33048
33442
  if (!this.watched.has(deviceSn)) {
33049
33443
  const ofProduct = [...this.watched.values()].filter((d) => d.product_code === productCode);
33050
33444
  if (ofProduct.length === 1)
@@ -33082,9 +33476,9 @@ var SolixMqtt = class extends EventEmitter15 {
33082
33476
  values.displayTimeoutIndex = a5[1];
33083
33477
  if (Object.keys(values).length === 0)
33084
33478
  return;
33085
- const parts = topic.split("/");
33086
- const productCode = parts[2] ?? "";
33087
- let deviceSn = frame.deviceSn ?? parts[3] ?? "";
33479
+ const parts2 = topic.split("/");
33480
+ const productCode = parts2[2] ?? "";
33481
+ let deviceSn = frame.deviceSn ?? parts2[3] ?? "";
33088
33482
  if (!this.watched.has(deviceSn)) {
33089
33483
  const ofProduct = [...this.watched.values()].filter((d) => d.product_code === productCode);
33090
33484
  if (ofProduct.length === 1)