@mega-yfue/eufy-sdk 0.5.0-beta.45 → 0.5.0-beta.46
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 +5 -0
- package/dist/index.js +115 -5
- package/dist/index.js.map +3 -3
- package/dist/transport/rtc/command-router.d.ts +19 -0
- package/dist/transport/rtc/peer.d.ts +4 -1
- package/dist/transport/rtc/portal-keepalive.d.ts +11 -0
- package/dist/transport/rtc/session.d.ts +2 -0
- package/package.json +1 -1
|
@@ -711,6 +711,11 @@ export declare class EufyMega extends EventEmitter {
|
|
|
711
711
|
* — an already-open session resolves immediately.
|
|
712
712
|
*/
|
|
713
713
|
connectStation(deviceSn: string, signal?: AbortSignal): Promise<void>;
|
|
714
|
+
/**
|
|
715
|
+
* Keep a T9000 station's RTC command session open and warm, so a command fires without a cold reconnect.
|
|
716
|
+
* Answers the hold's disposer; throws for any other station, and while logged out.
|
|
717
|
+
*/
|
|
718
|
+
keepStationWarm(stationSn: string): () => void;
|
|
714
719
|
/** Eagerly open P2P sessions for WIRED stations only (persistent — they don't drain). Battery
|
|
715
720
|
* stations stay closed until an on-demand open. Best-effort per station. */
|
|
716
721
|
private warmWiredP2P;
|
package/dist/index.js
CHANGED
|
@@ -22724,6 +22724,10 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22724
22724
|
// dist/transport/rtc/live.js
|
|
22725
22725
|
import { EventEmitter as EventEmitter7 } from "node:events";
|
|
22726
22726
|
|
|
22727
|
+
// dist/transport/rtc/portal-keepalive.js
|
|
22728
|
+
var PORTAL_KEEPALIVE = Buffer.from("0009000010000000000000006300000000000000585a5948730400000000000000000002", "hex");
|
|
22729
|
+
var PORTAL_KEEPALIVE_MS = 29e3;
|
|
22730
|
+
|
|
22727
22731
|
// dist/transport/rtc/portal-packet.js
|
|
22728
22732
|
var MAGIC2 = Buffer.from("XZYH", "ascii");
|
|
22729
22733
|
var PORTAL_HEADER_LENGTH = 16;
|
|
@@ -22833,8 +22837,6 @@ var LIVE_STOP = 1004;
|
|
|
22833
22837
|
var LIVE_MEDIA = 1300;
|
|
22834
22838
|
var LIVE_MEDIA_CHANNEL = 101;
|
|
22835
22839
|
var LIVE_STREAM_ID = 1;
|
|
22836
|
-
var KEEPALIVE_MS = 29e3;
|
|
22837
|
-
var KEEPALIVE = Buffer.from("0009000010000000000000006300000000000000585a5948730400000000000000000002", "hex");
|
|
22838
22840
|
var MEDIA_HEADER_LENGTH = 22;
|
|
22839
22841
|
var STREAM_TYPE_HEVC = 1;
|
|
22840
22842
|
function parseMediaBody(body) {
|
|
@@ -22904,9 +22906,9 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
22904
22906
|
return this;
|
|
22905
22907
|
}
|
|
22906
22908
|
this.keepalive = setInterval(() => {
|
|
22907
|
-
if (!session.sendRaw(
|
|
22909
|
+
if (!session.sendRaw(PORTAL_KEEPALIVE))
|
|
22908
22910
|
this.opts.logger?.warn?.(`[rtc] ${this.tag} keepalive not sent`);
|
|
22909
|
-
},
|
|
22911
|
+
}, PORTAL_KEEPALIVE_MS);
|
|
22910
22912
|
this.keepalive.unref?.();
|
|
22911
22913
|
this.opts.logger?.debug?.(`[rtc] ${this.tag} live start sent`);
|
|
22912
22914
|
return this;
|
|
@@ -23440,7 +23442,8 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23440
23442
|
}
|
|
23441
23443
|
/**
|
|
23442
23444
|
* Wire one data channel. Every channel feeds the framer once the command channel has opened it; a
|
|
23443
|
-
* message before that has nothing to reassemble into and is dropped.
|
|
23445
|
+
* message before that has nothing to reassemble into and is dropped. The hub's echo of the portal
|
|
23446
|
+
* keepalive on the command channel is unframed and surfaces as `keepaliveEcho` instead.
|
|
23444
23447
|
*/
|
|
23445
23448
|
wireChannel(label, dc) {
|
|
23446
23449
|
if (this.channels.has(label))
|
|
@@ -23478,6 +23481,10 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23478
23481
|
if (n <= 3 || n % 100 === 0) {
|
|
23479
23482
|
this.logger.debug(`[rtc] wire ${label} #${n} ${buf.length}B ${buf.subarray(0, 8).toString("hex")}`);
|
|
23480
23483
|
}
|
|
23484
|
+
if (label === COMMAND_CHANNEL && buf.equals(PORTAL_KEEPALIVE)) {
|
|
23485
|
+
this.emit("keepaliveEcho");
|
|
23486
|
+
return;
|
|
23487
|
+
}
|
|
23481
23488
|
this.framer?.recvPacket(buf);
|
|
23482
23489
|
});
|
|
23483
23490
|
}
|
|
@@ -23789,6 +23796,7 @@ var RtcSession = class extends EventEmitter10 {
|
|
|
23789
23796
|
}
|
|
23790
23797
|
});
|
|
23791
23798
|
this.peer.on("error", (e) => this.emit("error", e));
|
|
23799
|
+
this.peer.on("keepaliveEcho", () => this.emit("keepaliveEcho"));
|
|
23792
23800
|
this.peer.on("data", (frame, linkType) => {
|
|
23793
23801
|
if (linkType === PortalLinkType.LIVE)
|
|
23794
23802
|
this.emit("mediaData", frame);
|
|
@@ -23984,6 +23992,7 @@ var RtcCommandRouter = class {
|
|
|
23984
23992
|
deps;
|
|
23985
23993
|
sessions = /* @__PURE__ */ new Map();
|
|
23986
23994
|
lives = /* @__PURE__ */ new Map();
|
|
23995
|
+
warm = /* @__PURE__ */ new Map();
|
|
23987
23996
|
constructor(deps) {
|
|
23988
23997
|
this.deps = deps;
|
|
23989
23998
|
}
|
|
@@ -24233,6 +24242,96 @@ var RtcCommandRouter = class {
|
|
|
24233
24242
|
Promise.resolve().then(() => session.connect()).catch((e) => finish(e instanceof Error ? e : new Error(String(e))));
|
|
24234
24243
|
});
|
|
24235
24244
|
}
|
|
24245
|
+
/**
|
|
24246
|
+
* Hold a station's command session open: one hold per station, its idle-close ({@link IDLE_CLOSE_MS})
|
|
24247
|
+
* suppressed, {@link PORTAL_KEEPALIVE} sent every {@link PORTAL_KEEPALIVE_MS}, and the session dropped
|
|
24248
|
+
* on a tick that finds the previous keepalive unechoed. A closed session ends the hold.
|
|
24249
|
+
*
|
|
24250
|
+
* Answers an idempotent disposer that releases the hold. Throws when no account is logged in.
|
|
24251
|
+
*/
|
|
24252
|
+
keepWarm(route) {
|
|
24253
|
+
const sn = route.stationSn;
|
|
24254
|
+
const existing = this.warm.get(sn);
|
|
24255
|
+
if (existing)
|
|
24256
|
+
return existing.dispose;
|
|
24257
|
+
const identity = this.deps.identity();
|
|
24258
|
+
if (!identity)
|
|
24259
|
+
throw new Error(`rtc: not logged in, cannot warm ${sn}`);
|
|
24260
|
+
const hold = {
|
|
24261
|
+
awaitingEcho: false,
|
|
24262
|
+
dispose: () => {
|
|
24263
|
+
if (this.warm.get(sn) !== hold)
|
|
24264
|
+
return;
|
|
24265
|
+
this.warm.delete(sn);
|
|
24266
|
+
const st = hold.watch?.st;
|
|
24267
|
+
this.unwatch(hold);
|
|
24268
|
+
if (st) {
|
|
24269
|
+
st.leases = Math.max(0, st.leases - 1);
|
|
24270
|
+
this.touch(sn, st);
|
|
24271
|
+
}
|
|
24272
|
+
}
|
|
24273
|
+
};
|
|
24274
|
+
this.warm.set(sn, hold);
|
|
24275
|
+
void this.warmUp(sn, route, identity, hold);
|
|
24276
|
+
return hold.dispose;
|
|
24277
|
+
}
|
|
24278
|
+
/**
|
|
24279
|
+
* Bring a station session up for a {@link keepWarm} hold and start its keepalive. A bring-up that fails,
|
|
24280
|
+
* or a hold disposed before it completes, clears the hold and arms no timer. Each tick first checks
|
|
24281
|
+
* that the hub echoed the previous keepalive, and drops the session when it did not. The keepalive
|
|
24282
|
+
* stops when the session closes, which also clears the hold so the next command or `keepWarm`
|
|
24283
|
+
* rebuilds cleanly.
|
|
24284
|
+
*/
|
|
24285
|
+
async warmUp(sn, route, identity, hold) {
|
|
24286
|
+
const adminUserId = route.adminUserId || identity.userId;
|
|
24287
|
+
let st;
|
|
24288
|
+
try {
|
|
24289
|
+
st = await this.stationSession(route, adminUserId, identity);
|
|
24290
|
+
} catch (e) {
|
|
24291
|
+
if (this.warm.get(sn) === hold)
|
|
24292
|
+
this.warm.delete(sn);
|
|
24293
|
+
this.deps.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
24294
|
+
return;
|
|
24295
|
+
}
|
|
24296
|
+
if (this.warm.get(sn) !== hold) {
|
|
24297
|
+
this.touch(sn, st);
|
|
24298
|
+
return;
|
|
24299
|
+
}
|
|
24300
|
+
st.leases++;
|
|
24301
|
+
this.touch(sn, st);
|
|
24302
|
+
const onClose = () => {
|
|
24303
|
+
this.unwatch(hold);
|
|
24304
|
+
if (this.warm.get(sn) === hold)
|
|
24305
|
+
this.warm.delete(sn);
|
|
24306
|
+
};
|
|
24307
|
+
const onEcho = () => {
|
|
24308
|
+
hold.awaitingEcho = false;
|
|
24309
|
+
};
|
|
24310
|
+
hold.watch = { st, onClose, onEcho };
|
|
24311
|
+
st.session.once("close", onClose);
|
|
24312
|
+
st.session.on("keepaliveEcho", onEcho);
|
|
24313
|
+
hold.keepalive = setInterval(() => {
|
|
24314
|
+
if (hold.awaitingEcho) {
|
|
24315
|
+
this.deps.logger?.warn?.(`[rtc] ${sn} warm keepalive went unechoed, dropping the session`);
|
|
24316
|
+
this.drop(sn, st);
|
|
24317
|
+
return;
|
|
24318
|
+
}
|
|
24319
|
+
hold.awaitingEcho = true;
|
|
24320
|
+
st.session.sendRaw(PORTAL_KEEPALIVE);
|
|
24321
|
+
}, PORTAL_KEEPALIVE_MS);
|
|
24322
|
+
hold.keepalive.unref?.();
|
|
24323
|
+
}
|
|
24324
|
+
/** Stop a hold's keepalive and remove its listeners from the session it watches. */
|
|
24325
|
+
unwatch(hold) {
|
|
24326
|
+
if (hold.keepalive)
|
|
24327
|
+
clearInterval(hold.keepalive);
|
|
24328
|
+
hold.keepalive = void 0;
|
|
24329
|
+
if (hold.watch) {
|
|
24330
|
+
hold.watch.st.session.off("close", hold.watch.onClose);
|
|
24331
|
+
hold.watch.st.session.off("keepaliveEcho", hold.watch.onEcho);
|
|
24332
|
+
}
|
|
24333
|
+
hold.watch = void 0;
|
|
24334
|
+
}
|
|
24236
24335
|
touch(sn, st) {
|
|
24237
24336
|
if (st.idle)
|
|
24238
24337
|
clearTimeout(st.idle);
|
|
@@ -28993,6 +29092,17 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28993
29092
|
await this.getDevices();
|
|
28994
29093
|
await this.p2p.ensureStation(this.p2p.stationKeyOf(deviceSn), signal);
|
|
28995
29094
|
}
|
|
29095
|
+
/**
|
|
29096
|
+
* Keep a T9000 station's RTC command session open and warm, so a command fires without a cold reconnect.
|
|
29097
|
+
* Answers the hold's disposer; throws for any other station, and while logged out.
|
|
29098
|
+
*/
|
|
29099
|
+
keepStationWarm(stationSn) {
|
|
29100
|
+
const { t9000 } = this.stationOf(stationSn);
|
|
29101
|
+
const route = this.rtcRouteFor(stationSn);
|
|
29102
|
+
if (!t9000 || !route)
|
|
29103
|
+
throw new Error(`rtc: ${stationSn} has no warm command session`);
|
|
29104
|
+
return this.rtc.keepWarm(route);
|
|
29105
|
+
}
|
|
28996
29106
|
/** Eagerly open P2P sessions for WIRED stations only (persistent — they don't drain). Battery
|
|
28997
29107
|
* stations stay closed until an on-demand open. Best-effort per station. */
|
|
28998
29108
|
async warmWiredP2P(readiness = createPlaneReadiness(), signal) {
|