@mega-yfue/eufy-sdk 0.6.0-beta.40 → 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.
@@ -21,6 +21,7 @@ export { unobservableMembers, unreflectedMembers } from "./members.js";
21
21
  export { Watermark, type WatermarkValue, NightVision, type NightVisionValue, RecordingQuality, type RecordingQualityName, RECORDING_QUALITY_TIERS, StreamingQuality, type StreamingQualityName, STREAMING_QUALITY_TIERS, resolveStreamingQuality, resolveStreamingQualityTier, resolveRecordingQuality, resolveRecordingQualityValue, resolveRecordingQualityTier, } from "./camera.js";
22
22
  export { WorkingMode, PowerSource, resolveWorkingMode, resolveWorkingModeValue, cameraPowerTier, WORKING_MODE_MAPS, type WorkingModeName, type PowerSourceName, } from "./battery.js";
23
23
  export { type DpCatalog, EMPTY_DP_CATALOG } from "./dp-catalog.js";
24
+ export type { ThingAction, ThingBundle, ThingCondition, ThingConditions, ThingDescription, ThingEvent, ThingMember, ThingProperty, } from "./thing-description.js";
24
25
  import type { PtzActions } from "./ptz.js";
25
26
  import type { LightActions } from "./light.js";
26
27
  import type { RgbColor, SmartLightActions } from "./smart-light.js";
@@ -46,6 +46,14 @@ export interface ValueMember {
46
46
  * static `enumValues` (or none) in place.
47
47
  */
48
48
  enumValuesFor?: (ctx: AvailabilityContext) => Record<number, string> | undefined;
49
+ /**
50
+ * The product thing-description identifiers this value is, in the app's order of preference
51
+ * (`nightvision_type_new` before `nightvision_type`), each grounded in the products' settings bundles
52
+ * reading it from this member's param. For an enum member without an {@link enumValuesFor} answer, the
53
+ * first one the device's resolved description has gives the options: its members' wire values that
54
+ * {@link enumValues} names, or 0 and 1 for a `bool` property.
55
+ */
56
+ thing?: readonly string[];
49
57
  provenance?: PropertySpec["provenance"];
50
58
  invert?: boolean;
51
59
  /** Per-device read polarity; undefined retains the static default. */
@@ -536,9 +544,9 @@ export type Surface<M extends Members> = {
536
544
  /**
537
545
  * Resolve a member's enum options for a device, evaluating `enumValuesFor` at most once. Returns the
538
546
  * options and whether they came from the device context (`dynamic`), so the option set and the enum
539
- * `kind` both come from a single result. A context domain (`enumValuesFor(ctx)`) is
540
- * returned when present; absent that (or with no context) the static `enumValues`; a member with
541
- * neither yields no options.
547
+ * `kind` both come from a single result. A context domain — `enumValuesFor(ctx)`, else what the
548
+ * member's `thing` gives on the device — is returned when present; absent that (or with no context) the
549
+ * static `enumValues`; a member with neither yields no options.
542
550
  */
543
551
  export declare function resolvedEnum(m: ValueMember, ctx?: AvailabilityContext): {
544
552
  values?: Record<number, string>;
@@ -601,6 +609,13 @@ export declare function installs(m: {
601
609
  readAvailable?: ValueMember["readAvailable"];
602
610
  readAliases?: ValueMember["readAliases"];
603
611
  }, ctx: CommandContext): boolean;
612
+ /**
613
+ * Whether a command of this kind can route over the device's primary transport. The client decides what
614
+ * a transport refuses — `ctx.refusedCommandKinds`, the kinds the device's primary transport cannot carry
615
+ * — so the model keeps no copy of the routing table: a kind absent from the set (and the whole set, the
616
+ * P2P default `undefined`) routes.
617
+ */
618
+ export declare function kindRoutable(kind: Command["kind"], ctx: AvailabilityContext): boolean;
604
619
  /**
605
620
  * Bind the table: evidence-gated getters, derived setters, momentary actions.
606
621
  *
@@ -1,5 +1,5 @@
1
1
  import { type Surface } from "./members.js";
2
- import type { CapabilityModule, CommandContext } from "./types.js";
2
+ import type { AvailabilityContext, CapabilityModule, CommandContext } from "./types.js";
3
3
  import type { Command } from "../../core/contracts.js";
4
4
  /**
5
5
  * The P2P **feature-command ids** this motion capability drives. Capability-owned wire vocabulary
@@ -125,6 +125,13 @@ export declare const MOTION_CMD: {
125
125
  * as a `1700` control payload carrying an index rather than the `1350` envelope its siblings take.
126
126
  */
127
127
  readonly INDOOR_SENSITIVITY_INDEX: 6041;
128
+ /**
129
+ * Motion detection on-off on a T8410 (app `CMD_INDOOR_DET_SET_MOTION_DETECT_ENABLE`). `1700` control
130
+ * payload `{"commandType":6040,"data":{"enable":0,"quality":0,"status":0|1,"value":0}}`.
131
+ *
132
+ * ⚠️ Replay + readback confirmed on a standalone and a HomeBase 3-attached T8410, NOT byte-captured.
133
+ */
134
+ readonly INDOOR_MOTION_ENABLE: 6040;
128
135
  /**
129
136
  * Detection sensitivity a solo camera REPORTS (app `SET_MOTION_DETECTION_SENSITIVITY_SOLO`). It does
130
137
  * not accept writes on this id — the write goes to {@link MOTION_SENSITIVITY}, which is why a scale
@@ -187,6 +194,8 @@ export declare function decodeAiDetectType(value: number): AiDetectFlags;
187
194
  * `ai_detect_type` field of the JSON object form. Answers `undefined` for any other shape.
188
195
  */
189
196
  declare function decodeAiDetectTypeParam(raw: unknown): number | undefined;
197
+ /** A T8410, whose motion switch is {@link MOTION_CMD.INDOOR_MOTION_ENABLE} rather than {@link MOTION_CMD.CAMERA_PIR}. */
198
+ declare function isT8410(ctx: AvailabilityContext): boolean;
190
199
  /**
191
200
  * Decode a `radar_wd_switch` (2706, loitering) read the way the app does:
192
201
  * `typeof v === "object" ? v.radar_wd_switch : v`. The device may store it as a JSON object
@@ -227,7 +236,12 @@ export declare const MOTION_MEMBERS: {
227
236
  readonly type: "bool";
228
237
  readonly kind: "boolean";
229
238
  readonly provenance: "verified";
230
- readonly description: "Motion/PIR detection enabled (verified: param 1011 = CAMERA_PIR).";
239
+ readonly readAvailable: (ctx: AvailabilityContext) => boolean;
240
+ readonly readAliases: readonly [{
241
+ readonly paramType: 6040;
242
+ readonly available: typeof isT8410;
243
+ }];
244
+ readonly description: string;
231
245
  readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
232
246
  readonly writeAs: "setDetection";
233
247
  };
@@ -1,6 +1,6 @@
1
1
  import type { Command } from "../../core/contracts.js";
2
2
  import { type Surface } from "./members.js";
3
- import type { AvailabilityContext, CapabilityModule, CommandContext } from "./types.js";
3
+ import type { CapabilityModule, CommandContext } from "./types.js";
4
4
  /** The state params this capability owns — its OWN vocabulary, used by no other capability. */
5
5
  export declare const RTSP_PARAM: {
6
6
  /**
@@ -93,13 +93,6 @@ export type RtspRecordingModeValue = (typeof RtspRecordingMode)[keyof typeof Rts
93
93
  * and the served effect on each endpoint with an RTSP `DESCRIBE`: 401 = challenged, 200 = open.
94
94
  */
95
95
  export type RtspActions = Surface<typeof RTSP_MEMBERS>;
96
- /**
97
- * Whether the publish switch has a wire to this camera. A HomeBase S1 Pro (`STATION_9000`) carries an
98
- * attached camera's commands over its WebRTC control channel, which takes only the `1350` and `1700`
99
- * envelopes; the switch is a plain scalar, and no other frame for it is captured there. Gating the
100
- * `published` member drops its read with its setter, since one availability decision covers both.
101
- */
102
- declare function publishable(ctx: AvailabilityContext): boolean;
103
96
  /**
104
97
  * Every `rtsp` feature, declared once.
105
98
  *
@@ -134,7 +127,6 @@ export declare const RTSP_MEMBERS: {
134
127
  readonly publish: true;
135
128
  readonly withdraw: false;
136
129
  };
137
- readonly available: typeof publishable;
138
130
  };
139
131
  /**
140
132
  * The device-reported RTSP URL — the full `rtsp://user:pass@host/path`, carrying the credentials the
@@ -231,4 +223,3 @@ export declare const RTSP_MEMBERS: {
231
223
  * rather than a model table.
232
224
  */
233
225
  export declare const RTSP: CapabilityModule;
234
- export {};
@@ -0,0 +1,137 @@
1
+ /**
2
+ * A product's **thing description** — the per-product model the vendor's things service returns from
3
+ * `app/things/get_things_list` and the v6 app builds its device settings from.
4
+ *
5
+ * One description per product code. Its properties are the product's settings and states, keyed by a
6
+ * stable `identifier` (`nightvision_type`, `led_on_off`, …); its actions and events are keyed the same
7
+ * way. A property declares its access (`R` read-only, `RW` writable), its data type and, per type, its
8
+ * specs: an enum lists its members, an `int` its bounds, most types a default. Each enum member carries
9
+ * the app's UI value (`value`), its label (`desc`) and — for a member that maps onto the device's own
10
+ * wire — that wire value (`associatedValue`); a member without one is a named constant, not a mode.
11
+ *
12
+ * A property may exist in several **variants**: `identifier`, `identifier__v1`, `identifier__v2`, …, each
13
+ * gated by conditions on the device (`read_conditions`, `available_conditions`): its firmware
14
+ * (`device_version`), its station's serial and firmware (`parent_sn`, `parent_version`), its own serial
15
+ * (`device_sn`), another property's value (`associated_field`), the account's country (`user_country`)
16
+ * or the app's SDK version (`sdk_version`). A condition list is held here as data, exactly as listed.
17
+ *
18
+ * `profile` names the product's settings bundle: the app's own per-product code mapping each property
19
+ * to the commands it reads and writes. The vendor's member key is spelled `eunmList`; the parser reads
20
+ * that spelling.
21
+ *
22
+ * Everything is parsed defensively — a malformed entry is skipped, never thrown on — and held as plain
23
+ * null-prototype objects, so a description serialises as it is and a lookup never reaches a prototype.
24
+ *
25
+ * @module model/capabilities/thing-description
26
+ */
27
+ /** One condition of a condition list: `type` compared by `qualifier` against `value`. */
28
+ export interface ThingCondition {
29
+ /** What is compared: `device_version`, `parent_sn`, `parent_version`, `device_sn`, `associated_field`, `user_country`, `sdk_version`, … */
30
+ readonly type: string;
31
+ /** How: `=`, `<`, `<=`, `>=` (versions, values), `RE` (regular expression), `SE` (regular expression on the device serial). */
32
+ readonly qualifier: string;
33
+ readonly value: string;
34
+ /** For `associated_field`: the identifier of the property whose value is compared. */
35
+ readonly propId?: string;
36
+ }
37
+ /** A condition list as the description gives it: its entries, each a set of conditions. */
38
+ export type ThingConditions = readonly (readonly ThingCondition[])[];
39
+ /** One member of an enum property. */
40
+ export interface ThingMember {
41
+ /** The app's UI value for this member. */
42
+ readonly value: string;
43
+ /** The value on the device's own wire (`associatedValue`); absent for a named constant. */
44
+ readonly wireValue?: string;
45
+ readonly desc: string;
46
+ readonly isDefault: boolean;
47
+ }
48
+ /** One property of a thing description. */
49
+ export interface ThingProperty {
50
+ readonly identifier: string;
51
+ /** `R` read-only or `RW` writable, as declared. */
52
+ readonly accessMode: string;
53
+ /** `bool`, `int`, `string`, `text`, `enum`, `tenum`, `ienum`, … */
54
+ readonly type: string;
55
+ /** An enum's members in the listed order; empty for any other type. */
56
+ readonly members: readonly ThingMember[];
57
+ readonly defaultValue?: string;
58
+ readonly min?: number;
59
+ readonly max?: number;
60
+ readonly step?: number;
61
+ readonly unit?: string;
62
+ readonly readConditions: ThingConditions;
63
+ readonly writeConditions: ThingConditions;
64
+ readonly availableConditions: ThingConditions;
65
+ }
66
+ /** One action of a thing description; its input and output types, when declared. */
67
+ export interface ThingAction {
68
+ readonly identifier: string;
69
+ readonly inputType?: string;
70
+ readonly outputType?: string;
71
+ readonly availableConditions: ThingConditions;
72
+ }
73
+ /** One event of a thing description; its output type and default (often a list of param ids), when declared. */
74
+ export interface ThingEvent {
75
+ readonly identifier: string;
76
+ readonly eventType: string;
77
+ readonly outputType?: string;
78
+ readonly defaultValue?: string;
79
+ readonly availableConditions: ThingConditions;
80
+ }
81
+ /** A product's settings bundle, as `profile` names it. */
82
+ export interface ThingBundle {
83
+ readonly id: string;
84
+ /** URL of the bundle's script. */
85
+ readonly path: string;
86
+ readonly zipId: string;
87
+ /** URL of the bundle's archive. */
88
+ readonly zipPath: string;
89
+ }
90
+ /** A product's thing description. */
91
+ export interface ThingDescription {
92
+ readonly productCode: string;
93
+ readonly version: string;
94
+ /** Unix seconds of the last change, as the service states it — the value `code_time_map` sends back. */
95
+ readonly updateTime: number;
96
+ readonly largeVersion: number;
97
+ readonly largeVersionUpdateTime: number;
98
+ readonly bundle: ThingBundle;
99
+ readonly properties: Readonly<Record<string, ThingProperty>>;
100
+ readonly actions: Readonly<Record<string, ThingAction>>;
101
+ readonly events: Readonly<Record<string, ThingEvent>>;
102
+ }
103
+ /**
104
+ * What a device's conditions are evaluated against. `stationSerial` is the serial of the station the
105
+ * device is reached through — its own serial when it stands alone — and `stationFirmwareVersion` that
106
+ * station's firmware. An `associated_field` / `parent_associated_field` condition compares an empty
107
+ * string. `params` are the device's reported `param_type → value`, for `camera_info`.
108
+ */
109
+ export interface ThingFacts {
110
+ readonly serial?: string;
111
+ readonly firmwareVersion?: string;
112
+ readonly stationSerial?: string;
113
+ readonly stationFirmwareVersion?: string;
114
+ readonly country?: string;
115
+ readonly params?: Readonly<Record<number, string>>;
116
+ }
117
+ /**
118
+ * Whether a condition list passes for a device, as the app decides it: an empty list passes; otherwise
119
+ * any entry passing is enough, and an entry passes when every one of its conditions does.
120
+ */
121
+ export declare function conditionsPass(list: ThingConditions, facts: ThingFacts): boolean;
122
+ /**
123
+ * A product's description **as one device sees it**, resolved the way the v6 app resolves each lookup.
124
+ * Properties are keyed by their base identifier and hold the variant this device uses: among the
125
+ * `identifier__vN` whose read conditions pass (a variant without read conditions is never chosen), the
126
+ * one with the most conditions, the first listed on a tie, else the base `identifier`. The chosen
127
+ * property's `identifier` keeps the variant's name. A property whose available conditions fail is left
128
+ * out, as are actions and events whose available conditions fail. A property whose write conditions fail
129
+ * is held as `R`.
130
+ */
131
+ export declare function resolveThing(td: ThingDescription, facts: ThingFacts): ThingDescription;
132
+ /**
133
+ * Parse an `app/things/get_things_list` reply into each listed product's {@link ThingDescription}, keyed
134
+ * by `profile.product_code`. `raw` is the reply's `data` object, holding `things_list`. An entry without a
135
+ * product code is skipped; any other shape mismatch yields an empty result — never throws.
136
+ */
137
+ export declare function parseThingsList(raw: unknown): Readonly<Record<string, ThingDescription>>;
@@ -1,4 +1,5 @@
1
1
  import type { DpCatalog } from "./dp-catalog.js";
2
+ import type { ThingDescription } from "./thing-description.js";
2
3
  /**
3
4
  * Capability-module contract — the extended shape each `capabilities/<cap>.ts` file implements.
4
5
  *
@@ -238,6 +239,12 @@ export interface AvailabilityContext {
238
239
  * `undefined` as an empty set — `ctx.paramIds?.has(dp) ?? false`.
239
240
  */
240
241
  paramIds?: ReadonlySet<number>;
242
+ /**
243
+ * The product's thing description, when the cloud returned one for its product code: which values each
244
+ * named property offers on this product. Absent when not fetched — fall back to static defaults rather
245
+ * than treating the device as incapable.
246
+ */
247
+ things?: ThingDescription;
241
248
  /**
242
249
  * Whether the device hangs off a HomeBase (a `parent_sn` other than its own) rather than standing
243
250
  * alone. A DEVICE fact, not a transport one — the same class of routing evidence as {@link hasP2p} —
@@ -247,6 +254,8 @@ export interface AvailabilityContext {
247
254
  * cannot do what its name promises there.
248
255
  */
249
256
  homeBaseAttached?: boolean;
257
+ /** The command kinds this device's primary transport REFUSES (a setter emitting one is withheld); `undefined` when it refuses none (the P2P default). Set by the client, which owns the routing. */
258
+ refusedCommandKinds?: ReadonlySet<Command["kind"]>;
250
259
  /** Serial of the covering station, when the device record names one. */
251
260
  stationSerial?: string;
252
261
  /**
@@ -14,6 +14,7 @@
14
14
  * @module model/inspect
15
15
  */
16
16
  import type { CloudRecord, PropertySource, PropertyValueType, RegistryEntry, ResolvedDevice } from "./types.js";
17
+ import type { ThingDescription } from "./capabilities/thing-description.js";
17
18
  import { type ParamNamespace } from "./param-namespace.js";
18
19
  /** One reported param, cross-referenced against our dictionary. */
19
20
  export interface ParamInspection {
@@ -53,6 +54,8 @@ export interface DeviceInspection {
53
54
  registrySnippet: string;
54
55
  /** Paste-ready param-dictionary lines for the unknown params. */
55
56
  dictionarySnippet: string;
57
+ /** The product's thing description, as the record carries it. Absent when the cloud returned none for the product code. */
58
+ things?: ThingDescription;
56
59
  }
57
60
  /**
58
61
  * Inspect a cloud device record and produce the enrichment report. `rec.params` should be the
@@ -13,6 +13,7 @@
13
13
  *
14
14
  * This file is the integration contract: every other `src/model/*` module imports from here.
15
15
  */
16
+ import type { ThingDescription } from "./capabilities/thing-description.js";
16
17
  /**
17
18
  * Command-codec families (axis B2). One per genuinely-different wire protocol.
18
19
  *
@@ -300,6 +301,8 @@ export interface CloudRecord {
300
301
  * evidence an availability gate on such a param is decided from.
301
302
  */
302
303
  dpParams?: Record<number, string>;
304
+ /** The product's thing description, when the cloud returned one for {@link model}. */
305
+ things?: ThingDescription;
303
306
  }
304
307
  /** The fully-resolved device shape produced by the 3-tier resolver. */
305
308
  export interface ResolvedDevice {
@@ -284,6 +284,8 @@ export declare class MegaHttpClient {
284
284
  } | undefined;
285
285
  /** The active region shard (e.g. `"eu-pr"`, `"us-pr"`), set after {@link login} or a region override. */
286
286
  get regionShard(): RegionShard;
287
+ /** The account's country code as configured (e.g. `"FR"`), the one logins are sent with. */
288
+ get countryCode(): string;
287
289
  /**
288
290
  * The shard the estimated domain names (`…-ie-…`/`…-ie.` → `ie-pr`), else {@link regionShard}. The
289
291
  * eu/us classification cannot name shards like `ie-pr`.
@@ -390,6 +392,16 @@ export declare class MegaHttpClient {
390
392
  * can adapt to the (not-yet-pinned) field layout.
391
393
  */
392
394
  getProductDataPoint<T = unknown>(code: string): Promise<T>;
395
+ /**
396
+ * Fetch the **thing descriptions** of the given product codes from the mega `things` service: per
397
+ * product, every named property with its type and offered values, its actions and events. The v6 app
398
+ * fetches the same list for the products an account holds and reads each setting's options from it.
399
+ * Product-level data, not account data. `codeTimeMap` names, per product code, the `update_time` of the
400
+ * description already held: a product whose description has not changed since comes back with that
401
+ * `update_time` and no properties, one that has (or is not named) comes back in full. Returns the raw
402
+ * `data`.
403
+ */
404
+ getThingsList<T = unknown>(productCodes: readonly string[], codeTimeMap?: Readonly<Record<string, number>>): Promise<T>;
393
405
  /**
394
406
  * Fetch the **live param list** from the mega `devicemanage` service. Body keys off a
395
407
  * `device_sns` ARRAY (a bare `device_sn` 400s with "type mismatch"). Each returned entry is
@@ -71,6 +71,13 @@ export interface RtcCommandRouterDeps {
71
71
  ffmpegPath?: string;
72
72
  createSession?: (opts: RtcSessionOptions) => RtcSession;
73
73
  }
74
+ /**
75
+ * The portal command kinds the RTC control channel frames — `set-payload` over the `1350` envelope,
76
+ * `set-json` over the `1700` one, and nothing else. The single source for both {@link
77
+ * RtcCommandRouter.dispatchCommand}'s guard and the client's portal-reach, so the two cannot disagree
78
+ * on what the channel carries.
79
+ */
80
+ export declare const RTC_CONTROL_COMMAND_KINDS: ReadonlySet<Command["kind"]>;
74
81
  export declare class RtcCommandRouter {
75
82
  private readonly deps;
76
83
  private readonly sessions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.6.0-beta.40",
3
+ "version": "0.6.0-beta.42",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",
@@ -54,6 +54,7 @@
54
54
  "dev": "tsc -p tsconfig.build.json --watch",
55
55
  "typecheck": "tsc --noEmit",
56
56
  "typecheck:examples": "tsc -p examples/tsconfig.json",
57
+ "typecheck:scripts": "tsc -p scripts/tsconfig.json",
57
58
  "test": "vitest run --passWithNoTests",
58
59
  "test:watch": "vitest",
59
60
  "format": "prettier --write .",
@@ -65,7 +66,7 @@
65
66
  "guard:lines": "bash scripts/ci/guard-lines.sh",
66
67
  "check:esm": "node -e \"import('./dist/index.js').then(m=>console.log('ESM OK:',Object.keys(m).length,'exports'))\"",
67
68
  "check:snippets": "node scripts/ci/check-doc-snippets.mjs",
68
- "verify": "npm run format:check && npm run typecheck && npm run guard:decorrelation && npm run guard:lines && npm run guard:docrefs && npm run guard:consumer-agnostic && npm run guard:capability-ownership && npm run build && npm run check:esm && npm run check:snippets && npm run typecheck:examples && npm test",
69
+ "verify": "npm run format:check && npm run typecheck && npm run guard:decorrelation && npm run guard:lines && npm run guard:docrefs && npm run guard:consumer-agnostic && npm run guard:capability-ownership && npm run build && npm run check:esm && npm run check:snippets && npm run typecheck:examples && npm run typecheck:scripts && npm test",
69
70
  "release": "bash scripts/release.sh"
70
71
  },
71
72
  "engines": {