@mega-yfue/eufy-sdk 0.5.0-beta.70 → 0.5.0-beta.72
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/core/contracts.d.ts +1 -0
- package/dist/index.js +102 -20
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/lock.d.ts +21 -7
- package/dist/model/capabilities/types.d.ts +4 -0
- package/dist/model/device-family.d.ts +2 -0
- package/dist/transport/p2p/command-router.d.ts +1 -0
- package/dist/transport/rtc/command-router.d.ts +3 -3
- package/dist/transport/rtc/session.d.ts +6 -2
- package/package.json +1 -1
package/dist/core/contracts.d.ts
CHANGED
|
@@ -883,6 +883,7 @@ export interface MediaProvider {
|
|
|
883
883
|
*/
|
|
884
884
|
p2pQuery?(subCmd: number, opts?: {
|
|
885
885
|
timeoutMs?: number;
|
|
886
|
+
payload?: Record<string, unknown>;
|
|
886
887
|
}): Promise<Record<string, unknown>>;
|
|
887
888
|
/**
|
|
888
889
|
* Generic control-payload request/reply query: send a `CONTROL_PAYLOAD` (1700) `{commandType,data}`
|
package/dist/index.js
CHANGED
|
@@ -3658,6 +3658,7 @@ var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
|
|
|
3658
3658
|
var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
|
|
3659
3659
|
var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
|
|
3660
3660
|
var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
|
|
3661
|
+
var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
|
|
3661
3662
|
|
|
3662
3663
|
// dist/model/capabilities/rtsp.js
|
|
3663
3664
|
var RTSP_PARAM = {
|
|
@@ -7434,9 +7435,24 @@ var LOCK_SETTING_ID = {
|
|
|
7434
7435
|
/** One-touch rear-lock toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
|
|
7435
7436
|
ONE_TOUCH_REAR_LOCK: 11
|
|
7436
7437
|
};
|
|
7438
|
+
var VIDEO_LOCK_CMD = { ACTUATE: 1961, STATUS: 1955, NOTIFY: 1351 };
|
|
7437
7439
|
function actuate(engage, ctx, sink) {
|
|
7438
|
-
if (!ctx.adminUserId || !ctx.shortUserId) {
|
|
7439
|
-
return Promise.reject(new Error(`lock: missing member identity (admin_user_id/short_user_id) [${describeDevice(ctx)}]`));
|
|
7440
|
+
if (!ctx.adminUserId || !ctx.shortUserId || isVideoSmartLock(ctx) && !ctx.accountName) {
|
|
7441
|
+
return Promise.reject(new Error(`lock: missing member identity (admin_user_id/short_user_id, and user name on video locks) [${describeDevice(ctx)}]`));
|
|
7442
|
+
}
|
|
7443
|
+
if (isVideoSmartLock(ctx)) {
|
|
7444
|
+
return sink.dispatch({
|
|
7445
|
+
kind: "set-payload",
|
|
7446
|
+
cmd: VIDEO_LOCK_CMD.ACTUATE,
|
|
7447
|
+
channel: ctx.channel,
|
|
7448
|
+
mValue3: 0,
|
|
7449
|
+
payload: {
|
|
7450
|
+
shortUserId: ctx.shortUserId,
|
|
7451
|
+
slOperation: engage ? 1 : 0,
|
|
7452
|
+
userId: ctx.adminUserId,
|
|
7453
|
+
userName: ctx.accountName
|
|
7454
|
+
}
|
|
7455
|
+
});
|
|
7440
7456
|
}
|
|
7441
7457
|
return sink.dispatch({
|
|
7442
7458
|
kind: "ff09-actuate",
|
|
@@ -7459,7 +7475,31 @@ function settingToggle(settingId, name, enabled, ctx, sink) {
|
|
|
7459
7475
|
value: enabled
|
|
7460
7476
|
});
|
|
7461
7477
|
}
|
|
7462
|
-
var overP2p = (ctx) => ctx.hasP2p === true;
|
|
7478
|
+
var overP2p = (ctx) => ctx.hasP2p === true && !isVideoSmartLock(ctx);
|
|
7479
|
+
function actuationAvailable(ctx) {
|
|
7480
|
+
return !isVideoSmartLock(ctx) || ctx.stationSerial?.startsWith("T8030") === true;
|
|
7481
|
+
}
|
|
7482
|
+
async function refreshStatus(query, timezone) {
|
|
7483
|
+
const payload = await query(VIDEO_LOCK_CMD.STATUS, {
|
|
7484
|
+
payload: { timezone }
|
|
7485
|
+
});
|
|
7486
|
+
if (payload.code !== 0)
|
|
7487
|
+
throw new Error(`lock.refreshStatus: device result ${String(payload.code)}`);
|
|
7488
|
+
return nativeState(payload.slState);
|
|
7489
|
+
}
|
|
7490
|
+
function nativeState(raw) {
|
|
7491
|
+
if (raw === "3")
|
|
7492
|
+
return false;
|
|
7493
|
+
if (raw === "4")
|
|
7494
|
+
return true;
|
|
7495
|
+
return void 0;
|
|
7496
|
+
}
|
|
7497
|
+
function nativeReport(signal) {
|
|
7498
|
+
if (signal.source !== "p2p-frame" || !isVideoSmartLock(signal) || signal.commandId !== VIDEO_LOCK_CMD.NOTIFY || signal.json?.cmd !== VIDEO_LOCK_CMD.STATUS)
|
|
7499
|
+
return void 0;
|
|
7500
|
+
const report = signal.json.payload;
|
|
7501
|
+
return report?.code === 0 ? report : void 0;
|
|
7502
|
+
}
|
|
7463
7503
|
var LOCK_MEMBERS = {
|
|
7464
7504
|
/**
|
|
7465
7505
|
* The lock's own state, verified on param 6000: "4" = locked, "3" = unlocked.
|
|
@@ -7501,10 +7541,10 @@ var LOCK_MEMBERS = {
|
|
|
7501
7541
|
provenance: "verified",
|
|
7502
7542
|
description: "Lock signal strength (verified: param 1141 = RSSI)."
|
|
7503
7543
|
},
|
|
7504
|
-
/** Lock the deadbolt/door.
|
|
7505
|
-
lock: method(({ ctx, sink }) => () => actuate(true, ctx, sink), "Lock the deadbolt/door."),
|
|
7506
|
-
/** Unlock the deadbolt/door.
|
|
7507
|
-
unlock: method(({ ctx, sink }) => () => actuate(false, ctx, sink), "Unlock the deadbolt/door."),
|
|
7544
|
+
/** Lock the deadbolt/door. Resolves after sending; does not confirm physical state. */
|
|
7545
|
+
lock: method(({ ctx, sink }) => () => actuate(true, ctx, sink), "Lock the deadbolt/door.", actuationAvailable),
|
|
7546
|
+
/** Unlock the deadbolt/door. Resolves after sending; does not confirm physical state. */
|
|
7547
|
+
unlock: method(({ ctx, sink }) => () => actuate(false, ctx, sink), "Unlock the deadbolt/door.", actuationAvailable),
|
|
7508
7548
|
/**
|
|
7509
7549
|
* Read-modify-write the auto-lock setting: the transport GETs the device's current settings, changes
|
|
7510
7550
|
* only `enabled` (+ `delaySeconds` if given — otherwise the current delay is preserved), and writes the
|
|
@@ -7525,7 +7565,7 @@ var LOCK_MEMBERS = {
|
|
|
7525
7565
|
enabled,
|
|
7526
7566
|
delaySeconds
|
|
7527
7567
|
});
|
|
7528
|
-
}, "Read-modify-write the auto-lock setting, preserving the fields it does not change."),
|
|
7568
|
+
}, "Read-modify-write the auto-lock setting, preserving the fields it does not change.", (ctx) => !isVideoSmartLock(ctx)),
|
|
7529
7569
|
/**
|
|
7530
7570
|
* Toggle Rain Mode — a pure blind write, unlike `setAutoLock`: the compact frame carries only this one
|
|
7531
7571
|
* field, so there is no GET pass and it is fire-and-forget. Confirmed on-device end-to-end in both
|
|
@@ -7623,6 +7663,15 @@ var LOCK_MEMBERS = {
|
|
|
7623
7663
|
description: "One-touch rear locking (setting-id 11). Wire shape confirmed in the app's own JS, NOT captured.",
|
|
7624
7664
|
write: (v, ctx) => settingToggleCommand(LOCK_SETTING_ID.ONE_TOUCH_REAR_LOCK, v, ctx)
|
|
7625
7665
|
},
|
|
7666
|
+
/** Read the current device-reported state; undefined means no settled locked/unlocked state. */
|
|
7667
|
+
refreshStatus: {
|
|
7668
|
+
...provided("media", (media, { ctx }) => {
|
|
7669
|
+
const query = media.p2pQuery?.bind(media);
|
|
7670
|
+
const timezone = ctx.timezone;
|
|
7671
|
+
return isVideoSmartLock(ctx) && timezone && query ? () => refreshStatus(query, timezone) : void 0;
|
|
7672
|
+
}, "Read the current lock state from the device."),
|
|
7673
|
+
answers: true
|
|
7674
|
+
},
|
|
7626
7675
|
/**
|
|
7627
7676
|
* Read the device's current auto-lock settings via a live `GET_SETTINGS` round-trip — the SAME read
|
|
7628
7677
|
* `setAutoLock` does internally, exposed standalone with no write attached. Works over BOTH transports.
|
|
@@ -7632,7 +7681,7 @@ var LOCK_MEMBERS = {
|
|
|
7632
7681
|
* though it takes no arguments — offering it as one would run a round-trip and discard the answer.
|
|
7633
7682
|
*/
|
|
7634
7683
|
getAutoLockState: {
|
|
7635
|
-
...provided("ff09Settings", (f) => () => f.getAutoLockState(), "Read the current auto-lock settings via a live round-trip."),
|
|
7684
|
+
...provided("ff09Settings", (f, { ctx }) => isVideoSmartLock(ctx) ? void 0 : () => f.getAutoLockState(), "Read the current auto-lock settings via a live round-trip."),
|
|
7636
7685
|
answers: true
|
|
7637
7686
|
}
|
|
7638
7687
|
};
|
|
@@ -7659,6 +7708,17 @@ var LOCK = {
|
|
|
7659
7708
|
description: "Smart-lock locked/unlocked state and battery.",
|
|
7660
7709
|
members: LOCK_MEMBERS,
|
|
7661
7710
|
properties: propertiesOf(LOCK_MEMBERS),
|
|
7711
|
+
decodeState(signal) {
|
|
7712
|
+
const report = nativeReport(signal);
|
|
7713
|
+
if (!report)
|
|
7714
|
+
return null;
|
|
7715
|
+
const params = {};
|
|
7716
|
+
if (nativeState(report.slState) !== void 0)
|
|
7717
|
+
params[6e3] = String(report.slState);
|
|
7718
|
+
if (report.slBattery !== void 0)
|
|
7719
|
+
params[6001] = String(report.slBattery);
|
|
7720
|
+
return { params };
|
|
7721
|
+
},
|
|
7662
7722
|
// Lock state is reported via param 6000 (verified: 4=locked, 3=unlocked); the model name (lock/safe) is a
|
|
7663
7723
|
// signal, and every lock-codec device has the lock capability as its baseline.
|
|
7664
7724
|
detection: { evidenceParams: [6e3], modelHints: [/lock/i, /safe/i], codecs: ["lock"] },
|
|
@@ -22616,7 +22676,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22616
22676
|
}, timeoutMs);
|
|
22617
22677
|
const onData = (f) => {
|
|
22618
22678
|
const j = f.json;
|
|
22619
|
-
if (j?.cmd === subCmd && j.payload && typeof j.payload === "object") {
|
|
22679
|
+
if (f.channel === channel && f.commandId === CommandType.CMD_NOTIFY_PAYLOAD && j?.cmd === subCmd && j.payload && typeof j.payload === "object") {
|
|
22620
22680
|
cleanup();
|
|
22621
22681
|
resolve(j.payload);
|
|
22622
22682
|
}
|
|
@@ -22627,7 +22687,11 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22627
22687
|
};
|
|
22628
22688
|
session.on("data", onData);
|
|
22629
22689
|
try {
|
|
22630
|
-
|
|
22690
|
+
if (opts.payload) {
|
|
22691
|
+
if (!session.sendControlLevel2(subCmd, channel, accountId, opts.payload, 0))
|
|
22692
|
+
throw new Error("p2p query send failed");
|
|
22693
|
+
} else
|
|
22694
|
+
session.sendSetPayload(subCmd, {}, { accountId, channel });
|
|
22631
22695
|
} catch (e) {
|
|
22632
22696
|
cleanup();
|
|
22633
22697
|
reject(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -24489,6 +24553,10 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
24489
24553
|
* `connect()` still waiting for the signalling auth is rejected and its timer stopped.
|
|
24490
24554
|
*/
|
|
24491
24555
|
close() {
|
|
24556
|
+
this.shutdown();
|
|
24557
|
+
}
|
|
24558
|
+
/** {@link RtcSession.close}, with the failure that ended the session handed to `close` when one did. */
|
|
24559
|
+
shutdown(err) {
|
|
24492
24560
|
if (this.closed)
|
|
24493
24561
|
return;
|
|
24494
24562
|
this.closed = true;
|
|
@@ -24501,7 +24569,7 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
24501
24569
|
this.signaling.close();
|
|
24502
24570
|
this.peer.close();
|
|
24503
24571
|
this.settleAuth(new Error("RTC session closed while waiting for signalling auth"));
|
|
24504
|
-
this.announceClose();
|
|
24572
|
+
this.announceClose(err);
|
|
24505
24573
|
}
|
|
24506
24574
|
/**
|
|
24507
24575
|
* Trickle one local candidate, or the empty end-of-candidates. The native peer can report one after the
|
|
@@ -24513,11 +24581,11 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
24513
24581
|
return;
|
|
24514
24582
|
this.signaling.sendInfoCandidate(candidate);
|
|
24515
24583
|
}
|
|
24516
|
-
announceClose() {
|
|
24584
|
+
announceClose(err) {
|
|
24517
24585
|
if (this.closeAnnounced)
|
|
24518
24586
|
return;
|
|
24519
24587
|
this.closeAnnounced = true;
|
|
24520
|
-
this.emit("close");
|
|
24588
|
+
this.emit("close", err);
|
|
24521
24589
|
}
|
|
24522
24590
|
waitForAuth() {
|
|
24523
24591
|
if (this.authOk)
|
|
@@ -24577,7 +24645,14 @@ var RtcSession = class extends EventEmitter11 {
|
|
|
24577
24645
|
const status = payload.status;
|
|
24578
24646
|
if (status === 100 && payload.turn) {
|
|
24579
24647
|
this.turn = payload.turn;
|
|
24580
|
-
|
|
24648
|
+
try {
|
|
24649
|
+
await this.peer.init(payload.turn);
|
|
24650
|
+
} catch (e) {
|
|
24651
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
24652
|
+
this.emit("error", err);
|
|
24653
|
+
this.shutdown(err);
|
|
24654
|
+
return;
|
|
24655
|
+
}
|
|
24581
24656
|
return;
|
|
24582
24657
|
}
|
|
24583
24658
|
if (status === 200) {
|
|
@@ -24913,9 +24988,9 @@ var RtcCommandRouter = class {
|
|
|
24913
24988
|
}
|
|
24914
24989
|
/**
|
|
24915
24990
|
* One bounded bring-up: the session is up when its command channel opens, and it fails, with the
|
|
24916
|
-
* session closed and every listener gone, when `connect()` throws, the session closes first
|
|
24917
|
-
*
|
|
24918
|
-
* rejecting unheard.
|
|
24991
|
+
* session closed and every listener gone, when `connect()` throws, the session closes first (rejecting
|
|
24992
|
+
* with the failure that closed it, when it carries one), or {@link CONNECT_TIMEOUT_MS} passes. A single
|
|
24993
|
+
* promise, so an early failure cannot leave a second one rejecting unheard.
|
|
24919
24994
|
*/
|
|
24920
24995
|
bringUp(sn, session) {
|
|
24921
24996
|
return new Promise((resolve, reject) => {
|
|
@@ -24939,7 +25014,7 @@ var RtcCommandRouter = class {
|
|
|
24939
25014
|
}
|
|
24940
25015
|
};
|
|
24941
25016
|
const onConnected = () => finish();
|
|
24942
|
-
const onClose = () => finish(new Error(`rtc: ${sn} session closed before the command channel opened`));
|
|
25017
|
+
const onClose = (err) => finish(err ?? new Error(`rtc: ${sn} session closed before the command channel opened`));
|
|
24943
25018
|
const timer = setTimeout(() => finish(new Error(`rtc: ${sn} did not come up within ${CONNECT_TIMEOUT_MS2}ms`)), CONNECT_TIMEOUT_MS2);
|
|
24944
25019
|
session.once("connected", onConnected);
|
|
24945
25020
|
session.once("close", onClose);
|
|
@@ -29021,7 +29096,11 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29021
29096
|
this.emit("p2p", f);
|
|
29022
29097
|
const caps = this.registry.capabilitiesForFrame(stationSn, f.channel);
|
|
29023
29098
|
const deviceSn = this.registry.serialForFrame(stationSn, f.channel);
|
|
29024
|
-
const signal = {
|
|
29099
|
+
const signal = {
|
|
29100
|
+
source: "p2p-frame",
|
|
29101
|
+
...f,
|
|
29102
|
+
deviceType: deviceSn ? this.registry.require(deviceSn).raw?.device_type : void 0
|
|
29103
|
+
};
|
|
29025
29104
|
if (f.params)
|
|
29026
29105
|
this.applyRealtimeState(deviceSn, f.params);
|
|
29027
29106
|
this.applyRealtimeReport(deviceSn, decodeState(signal, caps));
|
|
@@ -30289,10 +30368,13 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
30289
30368
|
const rawChannel = raw.device_channel;
|
|
30290
30369
|
const channel = typeof rawChannel === "number" ? rawChannel : 0;
|
|
30291
30370
|
const member = raw.member ?? {};
|
|
30371
|
+
const parent = this.registry.list().find((d) => d.sn === rec.parentSn);
|
|
30372
|
+
const parentRaw = parent?.raw;
|
|
30292
30373
|
const resolved = resolveDevice(rec);
|
|
30293
30374
|
const dpCatalog = (resolved.codec === "vacuum" || resolved.codec === "mower") && rec.model ? await this.fetchDpCatalog(rec.model) : void 0;
|
|
30294
30375
|
return {
|
|
30295
30376
|
channel,
|
|
30377
|
+
timezone: recordString(parentRaw ?? {}, "time_zone"),
|
|
30296
30378
|
codec: resolved.codec,
|
|
30297
30379
|
deviceType: rec.deviceType,
|
|
30298
30380
|
model: rec.model,
|