@mega-yfue/eufy-sdk 0.5.0-beta.7 → 0.5.0-beta.9
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/client/eufy-mega.d.ts +13 -4
- package/dist/index.js +54 -17
- package/dist/index.js.map +2 -2
- package/dist/model/registry.d.ts +3 -2
- package/dist/model/types.d.ts +6 -0
- package/dist/transport/p2p/command-router.d.ts +8 -5
- package/dist/transport/p2p/p2p-session.d.ts +5 -0
- package/package.json +1 -1
|
@@ -234,6 +234,11 @@ export declare class EufyMega extends EventEmitter {
|
|
|
234
234
|
*
|
|
235
235
|
* The evidence set is widened, never replaced: the ids come back through the cloud record, and a
|
|
236
236
|
* record that omits a realtime-only id would otherwise un-know it and re-trigger on the next report.
|
|
237
|
+
*
|
|
238
|
+
* The device is re-resolved against the fresh record before binding. A property gated on a
|
|
239
|
+
* realtime-only param enters the schema only once that param is evidence, and the value that made it
|
|
240
|
+
* evidence was stored before the property existed, so the record's realtime params are applied again
|
|
241
|
+
* to land it under the property's name.
|
|
237
242
|
*/
|
|
238
243
|
private rebindReads;
|
|
239
244
|
/** The distinct credential scopes the current MQTT roster needs — no devices on a scope, no connection. */
|
|
@@ -835,11 +840,14 @@ export declare class EufyMega extends EventEmitter {
|
|
|
835
840
|
*/
|
|
836
841
|
setProperty(sn: string, name: string, value: boolean | number | string): Promise<void>;
|
|
837
842
|
/**
|
|
838
|
-
* Restart a HomeBase.
|
|
843
|
+
* Restart a station: a HomeBase, or a camera-family device that is its own station.
|
|
839
844
|
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
845
|
+
* The restart is `RESTART_HUB` on the station's broadcast channel, so it reaches whatever owns the
|
|
846
|
+
* P2P session. A standalone camera or doorbell owns its own session and is the station it restarts.
|
|
847
|
+
* A camera attached to a HomeBase is not — the same frame would restart its HomeBase — so its serial
|
|
848
|
+
* throws, as does any non-camera serial, rather than doing nothing. The device drops its connection
|
|
849
|
+
* and returns after a minute or two; a HomeBase takes everything behind it offline meanwhile.
|
|
850
|
+
* Verified on real hardware against a HomeBase and a standalone indoor camera.
|
|
843
851
|
*/
|
|
844
852
|
reboot(sn: string): Promise<void>;
|
|
845
853
|
/**
|
|
@@ -857,6 +865,7 @@ export declare class EufyMega extends EventEmitter {
|
|
|
857
865
|
* exactly the reads it has.
|
|
858
866
|
*/
|
|
859
867
|
private fetchDpCatalog;
|
|
868
|
+
/** The {@link CommandContext} for a device, built from `known` when that record is already in hand. */
|
|
860
869
|
private commandContext;
|
|
861
870
|
/**
|
|
862
871
|
* **Generic P2P request/reply query** — sends a `SET_PAYLOAD` sub-command and resolves with the
|
package/dist/index.js
CHANGED
|
@@ -11179,8 +11179,10 @@ function resolveDevice(rec) {
|
|
|
11179
11179
|
}
|
|
11180
11180
|
function resolveProperties(rec, codec, capabilities) {
|
|
11181
11181
|
const paramIds = /* @__PURE__ */ new Set();
|
|
11182
|
-
|
|
11183
|
-
|
|
11182
|
+
for (const reported of [rec.params, rec.dpParams]) {
|
|
11183
|
+
if (!reported || typeof reported !== "object")
|
|
11184
|
+
continue;
|
|
11185
|
+
for (const k of Object.keys(reported)) {
|
|
11184
11186
|
const n = Number(k);
|
|
11185
11187
|
if (Number.isFinite(n))
|
|
11186
11188
|
paramIds.add(n);
|
|
@@ -16249,6 +16251,21 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
|
|
|
16249
16251
|
this.send(this.connectAddress, RequestMessageType.DATA, data);
|
|
16250
16252
|
return true;
|
|
16251
16253
|
}
|
|
16254
|
+
/**
|
|
16255
|
+
* The level-1 twin of {@link sendRawLevel2Bytes}: the plaintext `payload` sealed AES-128-ECB under the
|
|
16256
|
+
* level-1 key, signCode 1 — the seal of the level-1 media start. Returns `false` when not connected.
|
|
16257
|
+
*/
|
|
16258
|
+
sendRawLevel1Bytes(payload, channel, outerCmd) {
|
|
16259
|
+
if (!this.connectAddress)
|
|
16260
|
+
return false;
|
|
16261
|
+
const data = Buffer.concat([
|
|
16262
|
+
buildCommandHeader(this.seqNumber, outerCmd),
|
|
16263
|
+
buildRawCommandPayload(encryptP2PData(paddingP2PData(payload), this.level1Key), channel, 1)
|
|
16264
|
+
]);
|
|
16265
|
+
this.seqNumber = this.seqNumber + 1 & 65535;
|
|
16266
|
+
this.send(this.connectAddress, RequestMessageType.DATA, data);
|
|
16267
|
+
return true;
|
|
16268
|
+
}
|
|
16252
16269
|
/**
|
|
16253
16270
|
* Stop the realtime media stream (`CMD_STOP_REALTIME_MEDIA`, 1004) on a camera `channel`, in the shape
|
|
16254
16271
|
* its runtime topology takes, as {@link startLiveMedia} selects its start:
|
|
@@ -21197,14 +21214,20 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21197
21214
|
}
|
|
21198
21215
|
}
|
|
21199
21216
|
/**
|
|
21200
|
-
* Restart
|
|
21201
|
-
*
|
|
21202
|
-
*
|
|
21203
|
-
*
|
|
21204
|
-
*
|
|
21217
|
+
* Restart the station that owns `sn`'s P2P session. `RESTART_HUB` (1034) is a station-scalar on the
|
|
21218
|
+
* broadcast channel 255 whose body is `[u32 value][account_id padded]`, value `0`. ✅ Wire-confirmed
|
|
21219
|
+
* byte-exact as a level-2 frame from a capture of the app's own HomeBase Restart (2026-08-03).
|
|
21220
|
+
*
|
|
21221
|
+
* The seal follows the session, as {@link sendBySessionLevel} defines it: a keyed session takes the
|
|
21222
|
+
* level-2 frame, a keyless one — a standalone camera, which never negotiates a key — the same body
|
|
21223
|
+
* sealed level-1. Both are replayed {@link DIRECT_CMD_SENDS}× at 200ms, as every other unacknowledged
|
|
21224
|
+
* control here is.
|
|
21205
21225
|
*/
|
|
21206
21226
|
async rebootStation(sn) {
|
|
21207
|
-
await this.
|
|
21227
|
+
await this.sendBySessionLevel(sn, {
|
|
21228
|
+
l2: (resolved) => this.replayLevel2Send(sn, `reboot ${sn}`, ({ session, accountId }) => session.sendRawLevel2Bytes(buildDirectBinaryBody(0, accountId), 255, P2P_ENVELOPE.RESTART_HUB, 8), resolved),
|
|
21229
|
+
l1: (resolved) => this.replayLevel2Send(sn, `reboot ${sn}`, ({ session, accountId }) => session.sendRawLevel1Bytes(buildDirectBinaryBody(0, accountId), 255, P2P_ENVELOPE.RESTART_HUB), resolved)
|
|
21230
|
+
});
|
|
21208
21231
|
}
|
|
21209
21232
|
/**
|
|
21210
21233
|
* A {@link MediaProvider} bound to one serial — resolves the session then calls `p2p/media`.
|
|
@@ -25983,13 +26006,22 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
25983
26006
|
*
|
|
25984
26007
|
* The evidence set is widened, never replaced: the ids come back through the cloud record, and a
|
|
25985
26008
|
* record that omits a realtime-only id would otherwise un-know it and re-trigger on the next report.
|
|
26009
|
+
*
|
|
26010
|
+
* The device is re-resolved against the fresh record before binding. A property gated on a
|
|
26011
|
+
* realtime-only param enters the schema only once that param is evidence, and the value that made it
|
|
26012
|
+
* evidence was stored before the property existed, so the record's realtime params are applied again
|
|
26013
|
+
* to land it under the property's name.
|
|
25986
26014
|
*/
|
|
25987
26015
|
async rebindReads(sn) {
|
|
25988
26016
|
const dev = this.liveDevices.get(sn)?.deref();
|
|
25989
26017
|
if (!dev)
|
|
25990
26018
|
return;
|
|
25991
26019
|
try {
|
|
25992
|
-
const
|
|
26020
|
+
const rec = await this.registry.record(sn);
|
|
26021
|
+
dev.reresolve(rec);
|
|
26022
|
+
if (rec.dpParams)
|
|
26023
|
+
this.applyAndAnnounce(dev, rec.dpParams);
|
|
26024
|
+
const ctx = await this.commandContext(sn, rec);
|
|
25993
26025
|
dev.bindActions(ctx, this.commandSinkFor(sn), this.mediaProviderFor(sn), this.ff09SettingsReaderFor(sn, ctx), rawDpCodec);
|
|
25994
26026
|
this.boundParamIds.set(sn, /* @__PURE__ */ new Set([...this.boundParamIds.get(sn) ?? [], ...ctx.paramIds]));
|
|
25995
26027
|
this.emit("deviceState", this.deviceState(sn));
|
|
@@ -27310,16 +27342,20 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
27310
27342
|
await this.commandSinkFor(sn).dispatch(cmd);
|
|
27311
27343
|
}
|
|
27312
27344
|
/**
|
|
27313
|
-
* Restart a HomeBase.
|
|
27345
|
+
* Restart a station: a HomeBase, or a camera-family device that is its own station.
|
|
27314
27346
|
*
|
|
27315
|
-
*
|
|
27316
|
-
*
|
|
27317
|
-
*
|
|
27347
|
+
* The restart is `RESTART_HUB` on the station's broadcast channel, so it reaches whatever owns the
|
|
27348
|
+
* P2P session. A standalone camera or doorbell owns its own session and is the station it restarts.
|
|
27349
|
+
* A camera attached to a HomeBase is not — the same frame would restart its HomeBase — so its serial
|
|
27350
|
+
* throws, as does any non-camera serial, rather than doing nothing. The device drops its connection
|
|
27351
|
+
* and returns after a minute or two; a HomeBase takes everything behind it offline meanwhile.
|
|
27352
|
+
* Verified on real hardware against a HomeBase and a standalone indoor camera.
|
|
27318
27353
|
*/
|
|
27319
27354
|
async reboot(sn) {
|
|
27320
27355
|
const ctx = await this.commandContext(sn);
|
|
27321
|
-
|
|
27322
|
-
|
|
27356
|
+
const standaloneCamera = ctx.codec === "camera" && this.p2p.stationKeyOf(sn) === sn;
|
|
27357
|
+
if (!isHomeBase({ deviceType: ctx.deviceType, model: ctx.model }) && !standaloneCamera) {
|
|
27358
|
+
throw new Error(`reboot: ${sn} is neither a HomeBase nor a standalone camera (deviceType ${ctx.deviceType ?? "?"}, model ${ctx.model ?? "?"}) \u2014 restart addresses the station that owns the P2P session`);
|
|
27323
27359
|
}
|
|
27324
27360
|
await this.p2p.rebootStation(sn);
|
|
27325
27361
|
}
|
|
@@ -27351,8 +27387,9 @@ var EufyMega = class extends EventEmitter9 {
|
|
|
27351
27387
|
return EMPTY_DP_CATALOG;
|
|
27352
27388
|
}
|
|
27353
27389
|
}
|
|
27354
|
-
|
|
27355
|
-
|
|
27390
|
+
/** The {@link CommandContext} for a device, built from `known` when that record is already in hand. */
|
|
27391
|
+
async commandContext(sn, known) {
|
|
27392
|
+
const rec = known ?? await this.registry.record(sn);
|
|
27356
27393
|
const dev = this.registry.require(sn);
|
|
27357
27394
|
const raw = dev.raw ?? {};
|
|
27358
27395
|
const rawChannel = raw.device_channel;
|