@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 +139 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -45
- package/dist/index.d.ts +140 -45
- package/dist/index.js +137 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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,
|