@apocaliss92/nodedreame 1.12.1 → 1.12.3

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.cjs CHANGED
@@ -96,6 +96,8 @@ __export(index_exports, {
96
96
  getAliyunAuthCode: () => getAliyunAuthCode,
97
97
  getDeviceVideoProfile: () => getDeviceVideoProfile,
98
98
  getMowerCapabilities: () => getMowerCapabilities,
99
+ getTencentIdentity: () => getTencentIdentity,
100
+ getTencentP2PInfo: () => getTencentP2PInfo,
99
101
  getVacuumCapabilities: () => getVacuumCapabilities,
100
102
  getVideoAccessToken: () => getVideoAccessToken,
101
103
  getVideoFamilyId: () => getVideoFamilyId,
@@ -132,7 +134,7 @@ module.exports = __toCommonJS(index_exports);
132
134
 
133
135
  // src/support/version.ts
134
136
  var LIBRARY_NAME = "nodedreame";
135
- var LIBRARY_VERSION = "1.12.0";
137
+ var LIBRARY_VERSION = "1.12.3";
136
138
 
137
139
  // src/transport/errors.ts
138
140
  var DreameError = class extends Error {
@@ -2198,6 +2200,24 @@ var FamilyIdResponseSchema = import_zod3.z.object({
2198
2200
  msg: import_zod3.z.string().nullish(),
2199
2201
  data: import_zod3.z.object({ data: import_zod3.z.object({ familyId: import_zod3.z.string() }).passthrough() }).passthrough()
2200
2202
  }).passthrough();
2203
+ var TencentIdentityResponseSchema = import_zod3.z.object({
2204
+ code: import_zod3.z.number().optional(),
2205
+ msg: import_zod3.z.string().nullish(),
2206
+ data: import_zod3.z.object({
2207
+ data: import_zod3.z.object({
2208
+ productId: import_zod3.z.string(),
2209
+ deviceName: import_zod3.z.string(),
2210
+ deviceId: import_zod3.z.string().optional(),
2211
+ secretId: import_zod3.z.string().optional(),
2212
+ secretKey: import_zod3.z.string().optional()
2213
+ }).passthrough()
2214
+ }).passthrough()
2215
+ }).passthrough();
2216
+ var TencentP2PInfoResponseSchema = import_zod3.z.object({
2217
+ code: import_zod3.z.number().optional(),
2218
+ msg: import_zod3.z.string().nullish(),
2219
+ data: import_zod3.z.object({ data: import_zod3.z.object({ p2pInfo: import_zod3.z.string() }).passthrough() }).passthrough()
2220
+ }).passthrough();
2201
2221
  var DeviceVideoInfoSchema = import_zod3.z.object({
2202
2222
  model: import_zod3.z.string().optional(),
2203
2223
  displayName: import_zod3.z.string().optional(),
@@ -2318,6 +2338,37 @@ async function getVideoFamilyId(input) {
2318
2338
  });
2319
2339
  return FamilyIdResponseSchema.parse(raw).data.data.familyId;
2320
2340
  }
2341
+ async function getTencentIdentity(input) {
2342
+ const ctx = resolveCtx(input);
2343
+ const videoToken = input.videoToken ?? (await getVideoAccessToken({ ...input, ctx })).token;
2344
+ const raw = await httpPostJsonBody({
2345
+ ctx,
2346
+ path: `${P_THIRD_VIDEO}/tx/mgr/dev/getIdentity`,
2347
+ body: { accesstoken: videoToken, os: DEFAULT_OS, did: input.did },
2348
+ context: "tencent identity",
2349
+ ...passthrough(input)
2350
+ });
2351
+ const d = TencentIdentityResponseSchema.parse(raw).data.data;
2352
+ return {
2353
+ productId: d.productId,
2354
+ deviceName: d.deviceName,
2355
+ deviceId: d.deviceId ?? null,
2356
+ secretId: d.secretId ?? null,
2357
+ secretKey: d.secretKey ?? null
2358
+ };
2359
+ }
2360
+ async function getTencentP2PInfo(input) {
2361
+ const ctx = resolveCtx(input);
2362
+ const videoToken = input.videoToken ?? (await getVideoAccessToken({ ...input, ctx })).token;
2363
+ const raw = await httpPostJsonBody({
2364
+ ctx,
2365
+ path: `${P_THIRD_VIDEO}/tx/dev/getP2PInfo`,
2366
+ body: { accesstoken: videoToken, os: DEFAULT_OS, did: input.did },
2367
+ context: "tencent p2p info",
2368
+ ...passthrough(input)
2369
+ });
2370
+ return { p2pInfo: TencentP2PInfoResponseSchema.parse(raw).data.data.p2pInfo };
2371
+ }
2321
2372
  async function getDeviceVideoProfile(input) {
2322
2373
  const ctx = resolveCtx(input);
2323
2374
  const raw = await httpPostJsonBody({
@@ -2741,8 +2792,11 @@ function explainNoCameraChannel(profile) {
2741
2792
  if (!profile.online) {
2742
2793
  return `${who} is a camera but is OFFLINE. The video binding is published by the device itself, so there is nothing to read until it connects \u2014 this is not a provisioning fault.`;
2743
2794
  }
2744
- const on = profile.currentVendor ?? "none";
2745
2795
  const supports = profile.supportedVendors.length > 0 ? profile.supportedVendors.join(", ") : "none";
2796
+ if (profile.currentVendor === "tx") {
2797
+ return `${who} is a camera and online, but is provisioned on the TENCENT video vendor (vendor: tx; supports: ${supports}), and this library implements only the Aliyun LinkVisual path \u2014 the channel id it needs lives on Aliyun and a tx device has none. Re-pairing or re-opening the camera in the app will not change this.`;
2798
+ }
2799
+ const on = profile.currentVendor ?? "none";
2746
2800
  return `${who} is a camera and online, but has no video channel yet (vendor: ${on}; supports: ${supports}). The device has not bound to the video service \u2014 open its camera once in the Dreame app to create the binding, then retry.`;
2747
2801
  }
2748
2802
 
@@ -2831,6 +2885,34 @@ var VACUUM_ACTIONS = {
2831
2885
  autoEmpty: { siid: 15, aiid: 1 }
2832
2886
  };
2833
2887
 
2888
+ // src/video/monitor/vendor.ts
2889
+ function videoVendorSwitchParams(vendor) {
2890
+ return { vendor };
2891
+ }
2892
+ function parseVideoVendorStatus(raw) {
2893
+ const text = typeof raw === "string" ? raw : typeof raw === "object" && raw !== null ? null : null;
2894
+ const source = text === null ? raw : safeJson(text);
2895
+ if (typeof source !== "object" || source === null) {
2896
+ return { vendor: null, initStatus: null };
2897
+ }
2898
+ const v = Reflect.get(source, "vendor");
2899
+ const s = Reflect.get(source, "initStatus");
2900
+ return {
2901
+ vendor: v === "ali" || v === "tx" ? v : null,
2902
+ initStatus: typeof s === "number" ? s : null
2903
+ };
2904
+ }
2905
+ function safeJson(text) {
2906
+ try {
2907
+ return JSON.parse(text);
2908
+ } catch {
2909
+ return null;
2910
+ }
2911
+ }
2912
+ function vendorSwitchSettled(status, wanted) {
2913
+ return status.vendor === wanted && status.initStatus === 1;
2914
+ }
2915
+
2834
2916
  // src/video/monitor/protocol.ts
2835
2917
  var import_node_crypto4 = require("crypto");
2836
2918
  function makeMonitorSession(accountId, now = Date.now()) {
@@ -5487,6 +5569,59 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
5487
5569
  ...opts?.log ? { log: opts.log } : {}
5488
5570
  });
5489
5571
  }
5572
+ /**
5573
+ * What video backend this robot is on, and whether that backend's SDK is up.
5574
+ *
5575
+ * A CACHED read by default: the cloud shadow answers for a robot on its dock
5576
+ * without waking it, which is the whole point of asking before deciding how
5577
+ * to stream. Pass `{ live: true }` when the answer must be current — while a
5578
+ * switch settles, for instance.
5579
+ */
5580
+ async readVideoVendorStatus(opts) {
5581
+ const props = [{ siid: MONITOR_SIID, piid: MONITOR_PIID.VIDEO_VENDOR_STATUS }];
5582
+ const results = opts?.live === true ? await this.refreshProperties(props) : await this.refreshCachedProperties(props);
5583
+ return parseVideoVendorStatus(results[0]?.value);
5584
+ }
5585
+ /**
5586
+ * Move this robot onto a video backend, and WAIT until it is really there.
5587
+ *
5588
+ * The two backends are not interchangeable: measured on an X50 on
5589
+ * 2026-09-16, a robot on `tx` offers only Tencent's proprietary UDP P2P
5590
+ * plane (`getRtcInfo` answers 404), while the same robot on `ali` streams
5591
+ * through a plain RTMP relay this library implements end to end. So which
5592
+ * one it sits on decides whether it can be streamed at all.
5593
+ *
5594
+ * The device reports the NEW vendor before its SDK is up, which is why the
5595
+ * app polls rather than trusting the action's return — and why this resolves
5596
+ * on `initStatus === 1` and not a moment earlier. A half-switched robot
5597
+ * answers questions about a backend it cannot yet serve.
5598
+ *
5599
+ * Only meaningful on a dual-vendor device (`videoDynamicVendor`, with the
5600
+ * target in `defaultVendors`); a device that cannot move simply never
5601
+ * settles, and this reports that as a timeout rather than a silent success.
5602
+ */
5603
+ async setVideoVendor(vendor, opts) {
5604
+ const already = await this.readVideoVendorStatus({ live: true });
5605
+ if (vendorSwitchSettled(already, vendor)) {
5606
+ return already;
5607
+ }
5608
+ await this.callAction(MONITOR_SIID, MONITOR_AIID.VIDEO_VENDOR, [
5609
+ { piid: MONITOR_PIID.VIDEO_VENDOR_STATUS, value: videoVendorSwitchParams(vendor) }
5610
+ ]);
5611
+ const every = opts?.pollIntervalMs ?? 5e3;
5612
+ const deadline = Date.now() + (opts?.timeoutMs ?? every * 10);
5613
+ let last = already;
5614
+ while (Date.now() < deadline) {
5615
+ await new Promise((r) => setTimeout(r, every));
5616
+ last = await this.readVideoVendorStatus({ live: true });
5617
+ if (vendorSwitchSettled(last, vendor)) {
5618
+ return last;
5619
+ }
5620
+ }
5621
+ throw new DreameError(
5622
+ `video vendor did not settle on "${vendor}" within the timeout (last seen: vendor=${last.vendor ?? "unknown"}, initStatus=${String(last.initStatus)})`
5623
+ );
5624
+ }
5490
5625
  /** Props worth seeding on start() / polling — exported for the facade. */
5491
5626
  static DEFAULT_PROPS = [
5492
5627
  VACUUM_PROP.STATE,
@@ -8569,6 +8704,8 @@ var DreameCameraStream = class extends TypedEmitter {
8569
8704
  getAliyunAuthCode,
8570
8705
  getDeviceVideoProfile,
8571
8706
  getMowerCapabilities,
8707
+ getTencentIdentity,
8708
+ getTencentP2PInfo,
8572
8709
  getVacuumCapabilities,
8573
8710
  getVideoAccessToken,
8574
8711
  getVideoFamilyId,