@mega-yfue/eufy-sdk 0.5.0-beta.66 → 0.5.0-beta.67
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 +10 -7
- package/dist/index.js +270 -41
- package/dist/index.js.map +4 -4
- package/dist/model/device-family.d.ts +2 -0
- package/dist/transport/rtc/call-live.d.ts +41 -0
- package/dist/transport/rtc/command-router.d.ts +13 -0
- package/dist/transport/rtc/peer.d.ts +19 -1
- package/dist/transport/rtc/scall-sdp.d.ts +8 -0
- package/dist/transport/rtc/session.d.ts +5 -0
- package/package.json +1 -1
|
@@ -416,7 +416,7 @@ export declare class EufyMega extends EventEmitter {
|
|
|
416
416
|
/**
|
|
417
417
|
* The station a device's commands go through: the device itself, or the HomeBase the registry links it
|
|
418
418
|
* to. Answers the target record, the station record and serial, the station's `device_type`, and the
|
|
419
|
-
* `t9000` / `t8n00` family flags.
|
|
419
|
+
* `t9000` / `t8n00` / `t8223` family flags.
|
|
420
420
|
*/
|
|
421
421
|
private stationOf;
|
|
422
422
|
/** The vendor DeviceType of the station a device hangs off; `undefined` for a station itself. */
|
|
@@ -443,18 +443,21 @@ export declare class EufyMega extends EventEmitter {
|
|
|
443
443
|
* kind and stays transport-agnostic; only the facade sees both sides and decides here. A T9000 or T8N00
|
|
444
444
|
* station and the devices attached to it go over RTC, with an attached device's command refused
|
|
445
445
|
* unless its channel resolves back to it on the station. T8N00 uses full SDP and admits direct and
|
|
446
|
-
* relay candidates; T9000 retains compact SDP and relay-only candidates.
|
|
446
|
+
* relay candidates; T9000 retains compact SDP and relay-only candidates. A T8223 doorbell's route serves
|
|
447
|
+
* media only, and the RTC router refuses a command on it. Everything else is P2P.
|
|
447
448
|
*/
|
|
448
449
|
private routeCommand;
|
|
449
450
|
/**
|
|
450
|
-
* The RTC route for a T9000 or T8N00 station or a device attached to one,
|
|
451
|
-
* classification; `undefined` for any other device. A T8N00
|
|
452
|
-
* `
|
|
451
|
+
* The RTC route for a T9000 or T8N00 station or a device attached to one, or a T8223 doorbell that is its
|
|
452
|
+
* own station, decided by the station's model classification; `undefined` for any other device. A T8N00
|
|
453
|
+
* route carries its native `call` signalling; a T8223 route does too, and streams its video on a media
|
|
454
|
+
* track. The admin id is the station's `member.admin_user_id`, else the logged-in user.
|
|
453
455
|
*/
|
|
454
456
|
private rtcRouteFor;
|
|
455
457
|
/**
|
|
456
|
-
* The RTC media provider for a
|
|
457
|
-
* the station; `undefined` otherwise, including a camera behind
|
|
458
|
+
* The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
|
|
459
|
+
* `device_channel` resolves back to it on the station; `undefined` otherwise, including a camera behind
|
|
460
|
+
* a T8N00, whose route carries commands only.
|
|
458
461
|
*/
|
|
459
462
|
private rtcMediaFor;
|
|
460
463
|
/**
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// dist/client/eufy-mega.js
|
|
8
|
-
import { EventEmitter as
|
|
8
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
9
9
|
|
|
10
10
|
// dist/transport/http/mega-client.js
|
|
11
11
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -3655,6 +3655,7 @@ var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
|
|
|
3655
3655
|
var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
|
|
3656
3656
|
var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
|
|
3657
3657
|
var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
|
|
3658
|
+
var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
|
|
3658
3659
|
var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
|
|
3659
3660
|
var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
|
|
3660
3661
|
|
|
@@ -23366,11 +23367,139 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23366
23367
|
}
|
|
23367
23368
|
};
|
|
23368
23369
|
|
|
23370
|
+
// dist/transport/rtc/call-live.js
|
|
23371
|
+
import { EventEmitter as EventEmitter8 } from "node:events";
|
|
23372
|
+
var START_CODE = Buffer.from([0, 0, 0, 1]);
|
|
23373
|
+
var H264Depacketizer = class {
|
|
23374
|
+
onUnit;
|
|
23375
|
+
nals = [];
|
|
23376
|
+
fu;
|
|
23377
|
+
lastSeq = -1;
|
|
23378
|
+
broken = false;
|
|
23379
|
+
constructor(onUnit) {
|
|
23380
|
+
this.onUnit = onUnit;
|
|
23381
|
+
}
|
|
23382
|
+
push(packet) {
|
|
23383
|
+
if (packet.length < 12 || packet[0] >> 6 !== 2)
|
|
23384
|
+
return;
|
|
23385
|
+
const pt = packet[1] & 127;
|
|
23386
|
+
if (pt >= 72 && pt <= 79)
|
|
23387
|
+
return;
|
|
23388
|
+
const marker = (packet[1] & 128) !== 0;
|
|
23389
|
+
const seq = packet.readUInt16BE(2);
|
|
23390
|
+
const timestamp = packet.readUInt32BE(4);
|
|
23391
|
+
if (this.lastSeq >= 0 && seq !== (this.lastSeq + 1 & 65535))
|
|
23392
|
+
this.broken = true;
|
|
23393
|
+
this.lastSeq = seq;
|
|
23394
|
+
const cc = packet[0] & 15;
|
|
23395
|
+
let off = 12 + cc * 4;
|
|
23396
|
+
if (packet[0] & 16) {
|
|
23397
|
+
if (packet.length < off + 4)
|
|
23398
|
+
return;
|
|
23399
|
+
off += 4 + packet.readUInt16BE(off + 2) * 4;
|
|
23400
|
+
}
|
|
23401
|
+
let end = packet.length;
|
|
23402
|
+
if (packet[0] & 32)
|
|
23403
|
+
end -= packet[packet.length - 1];
|
|
23404
|
+
if (end <= off)
|
|
23405
|
+
return;
|
|
23406
|
+
const p = packet.subarray(off, end);
|
|
23407
|
+
const type = p[0] & 31;
|
|
23408
|
+
if (type >= 1 && type <= 23) {
|
|
23409
|
+
this.nals.push(Buffer.from(p));
|
|
23410
|
+
} else if (type === 24) {
|
|
23411
|
+
let o = 1;
|
|
23412
|
+
while (o + 2 <= p.length) {
|
|
23413
|
+
const len = p.readUInt16BE(o);
|
|
23414
|
+
if (o + 2 + len > p.length)
|
|
23415
|
+
break;
|
|
23416
|
+
this.nals.push(Buffer.from(p.subarray(o + 2, o + 2 + len)));
|
|
23417
|
+
o += 2 + len;
|
|
23418
|
+
}
|
|
23419
|
+
} else if (type === 28 && p.length > 2) {
|
|
23420
|
+
const start = (p[1] & 128) !== 0;
|
|
23421
|
+
const stop = (p[1] & 64) !== 0;
|
|
23422
|
+
if (start)
|
|
23423
|
+
this.fu = [Buffer.from([p[0] & 224 | p[1] & 31])];
|
|
23424
|
+
if (this.fu)
|
|
23425
|
+
this.fu.push(Buffer.from(p.subarray(2)));
|
|
23426
|
+
else
|
|
23427
|
+
this.broken = true;
|
|
23428
|
+
if (stop && this.fu) {
|
|
23429
|
+
this.nals.push(Buffer.concat(this.fu));
|
|
23430
|
+
this.fu = void 0;
|
|
23431
|
+
}
|
|
23432
|
+
}
|
|
23433
|
+
if (marker) {
|
|
23434
|
+
if (!this.broken && !this.fu && this.nals.length) {
|
|
23435
|
+
const parts = [];
|
|
23436
|
+
for (const n of this.nals)
|
|
23437
|
+
parts.push(START_CODE, n);
|
|
23438
|
+
this.onUnit(Buffer.concat(parts), timestamp);
|
|
23439
|
+
}
|
|
23440
|
+
this.nals = [];
|
|
23441
|
+
this.fu = void 0;
|
|
23442
|
+
this.broken = false;
|
|
23443
|
+
}
|
|
23444
|
+
}
|
|
23445
|
+
};
|
|
23446
|
+
var RtcCallLiveStream = class extends EventEmitter8 {
|
|
23447
|
+
session;
|
|
23448
|
+
logger;
|
|
23449
|
+
running = false;
|
|
23450
|
+
depacketizer = new H264Depacketizer((unit, ts) => this.onUnit(unit, ts));
|
|
23451
|
+
/** `session` is a session on a {@link RtcPeerOptions.mediaTrack} peer, not yet connected. */
|
|
23452
|
+
constructor(session, logger) {
|
|
23453
|
+
super();
|
|
23454
|
+
this.session = session;
|
|
23455
|
+
this.logger = logger ?? noopLogger;
|
|
23456
|
+
}
|
|
23457
|
+
start() {
|
|
23458
|
+
if (this.running)
|
|
23459
|
+
return this;
|
|
23460
|
+
this.running = true;
|
|
23461
|
+
const { session } = this;
|
|
23462
|
+
session.on("rtp", (packet) => this.depacketizer.push(packet));
|
|
23463
|
+
session.on("close", () => this.fail(new Error("rtc call: session closed")));
|
|
23464
|
+
session.on("error", (e) => this.fail(e));
|
|
23465
|
+
session.connect().catch((e) => this.fail(e instanceof Error ? e : new Error(String(e))));
|
|
23466
|
+
return this;
|
|
23467
|
+
}
|
|
23468
|
+
stop() {
|
|
23469
|
+
if (!this.running)
|
|
23470
|
+
return;
|
|
23471
|
+
this.teardown();
|
|
23472
|
+
this.emit("stop");
|
|
23473
|
+
}
|
|
23474
|
+
onUnit(unit, timestamp) {
|
|
23475
|
+
const frame = {
|
|
23476
|
+
codec: "h264",
|
|
23477
|
+
data: unit,
|
|
23478
|
+
keyframe: hasIdr(unit, "h264"),
|
|
23479
|
+
width: 0,
|
|
23480
|
+
height: 0,
|
|
23481
|
+
sourceTimestamp: timestamp
|
|
23482
|
+
};
|
|
23483
|
+
this.emit("video", frame);
|
|
23484
|
+
}
|
|
23485
|
+
fail(err) {
|
|
23486
|
+
if (!this.running)
|
|
23487
|
+
return;
|
|
23488
|
+
this.logger.warn(`[rtc-call] ${err.message}`);
|
|
23489
|
+
this.teardown();
|
|
23490
|
+
this.emit("error", err);
|
|
23491
|
+
}
|
|
23492
|
+
teardown() {
|
|
23493
|
+
this.running = false;
|
|
23494
|
+
this.session.close();
|
|
23495
|
+
}
|
|
23496
|
+
};
|
|
23497
|
+
|
|
23369
23498
|
// dist/transport/rtc/session.js
|
|
23370
|
-
import { EventEmitter as
|
|
23499
|
+
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
23371
23500
|
|
|
23372
23501
|
// dist/transport/rtc/peer.js
|
|
23373
|
-
import { EventEmitter as
|
|
23502
|
+
import { EventEmitter as EventEmitter9 } from "node:events";
|
|
23374
23503
|
|
|
23375
23504
|
// dist/transport/rtc/ptcs-framer.js
|
|
23376
23505
|
var MAGIC3 = Buffer.from("PTCS", "ascii");
|
|
@@ -23621,6 +23750,17 @@ function forceDtlsRole(sdp, role) {
|
|
|
23621
23750
|
function toWireCandidate(candidate) {
|
|
23622
23751
|
return candidate.trim().replace(/^a=/, "");
|
|
23623
23752
|
}
|
|
23753
|
+
function midOf(sdp, kind) {
|
|
23754
|
+
const section = sdp.split(/(?=^m=)/m).find((s) => s.startsWith(`m=${kind} `));
|
|
23755
|
+
return section?.match(/^a=mid:([^\r\n]+)/m)?.[1]?.trim();
|
|
23756
|
+
}
|
|
23757
|
+
function rejectDataSection(sdp) {
|
|
23758
|
+
const appMid = midOf(sdp, "application");
|
|
23759
|
+
const out = sdp.replace(/^m=application \d+/m, "m=application 0");
|
|
23760
|
+
if (appMid === void 0)
|
|
23761
|
+
return out;
|
|
23762
|
+
return out.replace(/^(a=group:BUNDLE)([^\r\n]*)/m, (_, head, mids) => [head, ...mids.split(" ").filter((t) => t && t !== appMid)].join(" "));
|
|
23763
|
+
}
|
|
23624
23764
|
|
|
23625
23765
|
// dist/transport/rtc/peer.js
|
|
23626
23766
|
var DATA_CHANNEL_LABELS = ["WebrtcDataChannel", "audio", "idr", "video", "notify", "download"];
|
|
@@ -23655,7 +23795,7 @@ async function loadNativePeerFactory() {
|
|
|
23655
23795
|
return (name, config) => new ndc.PeerConnection(name, config);
|
|
23656
23796
|
}
|
|
23657
23797
|
var ANSWER_TIMEOUT_MS = 15e3;
|
|
23658
|
-
var RtcPeer = class extends
|
|
23798
|
+
var RtcPeer = class extends EventEmitter9 {
|
|
23659
23799
|
opts;
|
|
23660
23800
|
pc;
|
|
23661
23801
|
channels = /* @__PURE__ */ new Map();
|
|
@@ -23666,6 +23806,8 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23666
23806
|
remoteMid = HUB_SDP_MID;
|
|
23667
23807
|
handlingOffer = false;
|
|
23668
23808
|
channelsCreated = false;
|
|
23809
|
+
videoMid;
|
|
23810
|
+
videoTrack;
|
|
23669
23811
|
gatheringDone = false;
|
|
23670
23812
|
/** Set by the framer's wire callback when the native channel refused a packet mid-send. */
|
|
23671
23813
|
wireSendFailed = false;
|
|
@@ -23715,6 +23857,8 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23715
23857
|
this.emit("connectionState", state);
|
|
23716
23858
|
});
|
|
23717
23859
|
pc.onDataChannel((dc) => this.wireChannel(dc.getLabel(), dc));
|
|
23860
|
+
if (this.opts.mediaTrack)
|
|
23861
|
+
pc.onTrack((track) => this.wireVideoTrack(track));
|
|
23718
23862
|
}
|
|
23719
23863
|
/**
|
|
23720
23864
|
* The hub offered: apply the offer, declare the channels, return the answer SDP to signal back.
|
|
@@ -23724,7 +23868,9 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23724
23868
|
* only what is announced. The offer goes in before the channels are declared: `createDataChannel` on
|
|
23725
23869
|
* a peer without a remote description starts its own negotiation and leaves it in `have-local-offer`.
|
|
23726
23870
|
* The data channels need no m-line of their own; the offer's SCTP m-line carries them. Only an ANSWER
|
|
23727
|
-
* is taken as the local description: one read in `have-local-offer` is an offer.
|
|
23871
|
+
* is taken as the local description: one read in `have-local-offer` is an offer. A
|
|
23872
|
+
* {@link RtcPeerOptions.mediaTrack} peer applies the offer with its application section rejected,
|
|
23873
|
+
* declares no channels, and adds remote candidates on the offer's first (bundle-tagged) mid.
|
|
23728
23874
|
*/
|
|
23729
23875
|
async handleRemoteOffer(offerSdp) {
|
|
23730
23876
|
const pc = this.pc;
|
|
@@ -23734,9 +23880,12 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23734
23880
|
throw new Error("RTC peer already handling an offer");
|
|
23735
23881
|
this.handlingOffer = true;
|
|
23736
23882
|
try {
|
|
23737
|
-
const
|
|
23738
|
-
const
|
|
23739
|
-
|
|
23883
|
+
const media = this.opts.mediaTrack === true;
|
|
23884
|
+
const pinned = forceDtlsRole(offerSdp, "passive");
|
|
23885
|
+
const offer = media ? rejectDataSection(pinned) : pinned;
|
|
23886
|
+
this.remoteMid = media ? offer.match(/^a=mid:([^\r\n]+)/m)?.[1]?.trim() ?? HUB_SDP_MID : midOf(offer, "application") ?? HUB_SDP_MID;
|
|
23887
|
+
if (media)
|
|
23888
|
+
this.videoMid = midOf(offer, "video");
|
|
23740
23889
|
const answerWait = new Promise((resolve, reject) => {
|
|
23741
23890
|
const timer = setTimeout(() => {
|
|
23742
23891
|
this.localAnswer = void 0;
|
|
@@ -23746,7 +23895,8 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23746
23895
|
});
|
|
23747
23896
|
pc.setRemoteDescription(offer, "offer");
|
|
23748
23897
|
this.remoteSet = true;
|
|
23749
|
-
|
|
23898
|
+
if (!media)
|
|
23899
|
+
this.createChannels();
|
|
23750
23900
|
const local = pc.localDescription();
|
|
23751
23901
|
let answer = String(local?.type ?? "").toLowerCase() === "answer" ? local?.sdp ?? "" : "";
|
|
23752
23902
|
if (!answer)
|
|
@@ -23815,6 +23965,7 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23815
23965
|
this.pc = void 0;
|
|
23816
23966
|
this.channels.clear();
|
|
23817
23967
|
this.channelsCreated = false;
|
|
23968
|
+
this.videoTrack = void 0;
|
|
23818
23969
|
this.remoteSet = false;
|
|
23819
23970
|
this.pending.length = 0;
|
|
23820
23971
|
}
|
|
@@ -23878,6 +24029,14 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23878
24029
|
this.framer?.recvPacket(buf);
|
|
23879
24030
|
});
|
|
23880
24031
|
}
|
|
24032
|
+
/** Take the offer's video m-line as the one track whose RTP is emitted; any other track is left alone. */
|
|
24033
|
+
wireVideoTrack(track) {
|
|
24034
|
+
if (this.videoTrack || this.videoMid === void 0 || track.mid() !== this.videoMid)
|
|
24035
|
+
return;
|
|
24036
|
+
this.videoTrack = track;
|
|
24037
|
+
this.logger.debug(`[rtc] video track ${this.videoMid}`);
|
|
24038
|
+
track.onMessage((msg) => this.emit("rtp", Buffer.isBuffer(msg) ? msg : Buffer.from(msg)));
|
|
24039
|
+
}
|
|
23881
24040
|
addNow(candidate) {
|
|
23882
24041
|
try {
|
|
23883
24042
|
this.pc?.addRemoteCandidate(candidate, this.remoteMid);
|
|
@@ -23893,7 +24052,7 @@ var RtcPeer = class extends EventEmitter8 {
|
|
|
23893
24052
|
};
|
|
23894
24053
|
|
|
23895
24054
|
// dist/transport/rtc/signaling.js
|
|
23896
|
-
import { EventEmitter as
|
|
24055
|
+
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
23897
24056
|
import { createHmac as createHmac3, randomUUID as randomUUID2 } from "node:crypto";
|
|
23898
24057
|
var RTC_WS_PATH = "/v1/rtc/ws/join?reqtype=nvr";
|
|
23899
24058
|
var RTC_SIGN_PATH = "/v1/smart/nvr/ws/sign";
|
|
@@ -23909,7 +24068,7 @@ var defaultSocket = (url, init) => new WebSocket(url, {
|
|
|
23909
24068
|
protocols: init.protocols,
|
|
23910
24069
|
headers: { Origin: init.origin }
|
|
23911
24070
|
});
|
|
23912
|
-
var RtcSignalingClient = class extends
|
|
24071
|
+
var RtcSignalingClient = class extends EventEmitter10 {
|
|
23913
24072
|
opts;
|
|
23914
24073
|
ws;
|
|
23915
24074
|
sign;
|
|
@@ -24134,7 +24293,7 @@ var RtcSignalingClient = class extends EventEmitter9 {
|
|
|
24134
24293
|
|
|
24135
24294
|
// dist/transport/rtc/session.js
|
|
24136
24295
|
var AUTH_TIMEOUT_MS = 15e3;
|
|
24137
|
-
var RtcSession = class extends
|
|
24296
|
+
var RtcSession = class extends EventEmitter11 {
|
|
24138
24297
|
opts;
|
|
24139
24298
|
signaling;
|
|
24140
24299
|
peer;
|
|
@@ -24153,6 +24312,7 @@ var RtcSession = class extends EventEmitter10 {
|
|
|
24153
24312
|
this.logger = opts.logger ?? noopLogger;
|
|
24154
24313
|
this.signaling = (opts.createSignaling ?? ((o) => new RtcSignalingClient(o)))(opts);
|
|
24155
24314
|
this.peer = (opts.createPeer ?? ((o) => new RtcPeer(o)))({ logger: this.logger, ...opts.peer });
|
|
24315
|
+
const mediaTrack = opts.peer?.mediaTrack === true;
|
|
24156
24316
|
this.signaling.on("message", (inner) => {
|
|
24157
24317
|
this.chain = this.chain.then(() => this.onSignaling(inner)).catch((e) => {
|
|
24158
24318
|
this.emit("error", e instanceof Error ? e : new Error(String(e)));
|
|
@@ -24180,13 +24340,14 @@ var RtcSession = class extends EventEmitter10 {
|
|
|
24180
24340
|
this.announceClose();
|
|
24181
24341
|
});
|
|
24182
24342
|
this.peer.on("connectionState", (state) => {
|
|
24183
|
-
if ((state === "failed" || state === "closed") && this.connected) {
|
|
24343
|
+
if ((state === "failed" || state === "closed") && (this.connected || mediaTrack)) {
|
|
24184
24344
|
this.connected = false;
|
|
24185
24345
|
this.announceClose();
|
|
24186
24346
|
}
|
|
24187
24347
|
});
|
|
24188
24348
|
this.peer.on("error", (e) => this.emit("error", e));
|
|
24189
24349
|
this.peer.on("keepaliveEcho", () => this.emit("keepaliveEcho"));
|
|
24350
|
+
this.peer.on("rtp", (packet) => this.emit("rtp", packet));
|
|
24190
24351
|
this.peer.on("data", (frame, linkType) => {
|
|
24191
24352
|
if (linkType === PortalLinkType.LIVE)
|
|
24192
24353
|
this.emit("mediaData", frame);
|
|
@@ -24383,6 +24544,7 @@ var RtcCommandRouter = class {
|
|
|
24383
24544
|
sessions = /* @__PURE__ */ new Map();
|
|
24384
24545
|
lives = /* @__PURE__ */ new Map();
|
|
24385
24546
|
warm = /* @__PURE__ */ new Map();
|
|
24547
|
+
callSources = /* @__PURE__ */ new Map();
|
|
24386
24548
|
constructor(deps) {
|
|
24387
24549
|
this.deps = deps;
|
|
24388
24550
|
}
|
|
@@ -24390,6 +24552,8 @@ var RtcCommandRouter = class {
|
|
|
24390
24552
|
if (cmd.kind !== "set-payload" && cmd.kind !== "set-json") {
|
|
24391
24553
|
throw new Error(`rtc: ${cmd.kind} is not routable over the RTC control channel (only set-payload or set-json)`);
|
|
24392
24554
|
}
|
|
24555
|
+
if (route.mediaTrack)
|
|
24556
|
+
throw new Error(`rtc: ${route.stationSn} serves media only and takes no portal command`);
|
|
24393
24557
|
const identity = this.deps.identity();
|
|
24394
24558
|
if (!identity)
|
|
24395
24559
|
throw new Error(`rtc: not logged in, cannot drive ${route.stationSn}`);
|
|
@@ -24424,11 +24588,56 @@ var RtcCommandRouter = class {
|
|
|
24424
24588
|
}
|
|
24425
24589
|
};
|
|
24426
24590
|
}
|
|
24591
|
+
/**
|
|
24592
|
+
* Live view of a station on a {@link RtcRoute.mediaTrack} route: a live feed, a readable feed and a live
|
|
24593
|
+
* still, from one shared source per station whose every warm-up places a fresh `call` on its own
|
|
24594
|
+
* session. Recording has no wire here and is refused.
|
|
24595
|
+
*/
|
|
24596
|
+
trackMediaProviderFor(route) {
|
|
24597
|
+
const sn = route.stationSn;
|
|
24598
|
+
const sourceFor = (hints = {}) => {
|
|
24599
|
+
let source = this.callSources.get(sn);
|
|
24600
|
+
if (!source) {
|
|
24601
|
+
source = new SharedLiveSource({
|
|
24602
|
+
makeStream: () => {
|
|
24603
|
+
const identity = this.deps.identity();
|
|
24604
|
+
if (!identity)
|
|
24605
|
+
throw new Error(`rtc: not logged in, cannot call ${sn}`);
|
|
24606
|
+
const session = this.newSession(route, route.adminUserId || identity.userId, identity);
|
|
24607
|
+
return new RtcCallLiveStream(session, this.deps.logger);
|
|
24608
|
+
},
|
|
24609
|
+
label: sn,
|
|
24610
|
+
logger: this.deps.logger,
|
|
24611
|
+
powered: hints.powered,
|
|
24612
|
+
preBufferSeconds: hints.preBufferSeconds,
|
|
24613
|
+
warmTimeoutMs: 45e3
|
|
24614
|
+
});
|
|
24615
|
+
this.callSources.set(sn, source);
|
|
24616
|
+
}
|
|
24617
|
+
return source;
|
|
24618
|
+
};
|
|
24619
|
+
return {
|
|
24620
|
+
live: async (opts) => attachUnlessAborted(sourceFor(opts), opts?.signal),
|
|
24621
|
+
openReadable: async (opts) => openReadableFromConsumer(attachUnlessAborted(sourceFor(opts), opts?.signal), opts),
|
|
24622
|
+
snapshotLive: (opts) => captureSnapshotFromShared(sourceFor(opts), {
|
|
24623
|
+
...opts,
|
|
24624
|
+
logger: this.deps.logger,
|
|
24625
|
+
ffmpegPath: this.deps.ffmpegPath
|
|
24626
|
+
}),
|
|
24627
|
+
record: async () => {
|
|
24628
|
+
throw new Error(`record is not available for ${sn} over a portal call`);
|
|
24629
|
+
}
|
|
24630
|
+
};
|
|
24631
|
+
}
|
|
24427
24632
|
/**
|
|
24428
24633
|
* Tear down every station session (logout / shutdown). Each live source is disposed before its session
|
|
24429
24634
|
* closes, so its consumers end cleanly and its stop still rides the session.
|
|
24430
24635
|
*/
|
|
24431
24636
|
close() {
|
|
24637
|
+
for (const [sn, src] of this.callSources) {
|
|
24638
|
+
this.callSources.delete(sn);
|
|
24639
|
+
src.dispose();
|
|
24640
|
+
}
|
|
24432
24641
|
for (const [sn, live] of this.lives) {
|
|
24433
24642
|
this.lives.delete(sn);
|
|
24434
24643
|
live.source.dispose();
|
|
@@ -24558,6 +24767,19 @@ var RtcCommandRouter = class {
|
|
|
24558
24767
|
}
|
|
24559
24768
|
});
|
|
24560
24769
|
}
|
|
24770
|
+
newSession(route, adminUserId, identity) {
|
|
24771
|
+
return (this.deps.createSession ?? ((o) => new RtcSession(o)))({
|
|
24772
|
+
authToken: identity.authToken,
|
|
24773
|
+
gtoken: identity.gtoken,
|
|
24774
|
+
stationSn: route.stationSn,
|
|
24775
|
+
adminUserId,
|
|
24776
|
+
shard: this.deps.shard(),
|
|
24777
|
+
country: this.deps.country ?? "US",
|
|
24778
|
+
logger: this.deps.logger,
|
|
24779
|
+
signalingMode: route.signalingMode,
|
|
24780
|
+
peer: { logger: this.deps.logger, iceTransportPolicy: route.iceTransportPolicy, mediaTrack: route.mediaTrack }
|
|
24781
|
+
});
|
|
24782
|
+
}
|
|
24561
24783
|
async stationSession(route, adminUserId, identity) {
|
|
24562
24784
|
const sn = route.stationSn;
|
|
24563
24785
|
const existing = this.sessions.get(sn);
|
|
@@ -24569,17 +24791,7 @@ var RtcCommandRouter = class {
|
|
|
24569
24791
|
}
|
|
24570
24792
|
this.drop(sn, existing);
|
|
24571
24793
|
}
|
|
24572
|
-
const session =
|
|
24573
|
-
authToken: identity.authToken,
|
|
24574
|
-
gtoken: identity.gtoken,
|
|
24575
|
-
stationSn: sn,
|
|
24576
|
-
adminUserId,
|
|
24577
|
-
shard: this.deps.shard(),
|
|
24578
|
-
country: this.deps.country ?? "US",
|
|
24579
|
-
logger: this.deps.logger,
|
|
24580
|
-
signalingMode: route.signalingMode,
|
|
24581
|
-
peer: { logger: this.deps.logger, iceTransportPolicy: route.iceTransportPolicy }
|
|
24582
|
-
});
|
|
24794
|
+
const session = this.newSession(route, adminUserId, identity);
|
|
24583
24795
|
const ready = this.bringUp(sn, session);
|
|
24584
24796
|
const st = { session, seg: new SegmentCounter(), ready, queue: Promise.resolve(), leases: 0 };
|
|
24585
24797
|
session.on("error", (e) => this.deps.onError?.(e));
|
|
@@ -26390,7 +26602,7 @@ async function resolveLightEffect(mega, lightId) {
|
|
|
26390
26602
|
}
|
|
26391
26603
|
|
|
26392
26604
|
// dist/transport/push/push-client.js
|
|
26393
|
-
import { EventEmitter as
|
|
26605
|
+
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
26394
26606
|
import tls2 from "node:tls";
|
|
26395
26607
|
|
|
26396
26608
|
// dist/transport/push/proto.js
|
|
@@ -26645,7 +26857,7 @@ var MessageTag;
|
|
|
26645
26857
|
})(MessageTag || (MessageTag = {}));
|
|
26646
26858
|
|
|
26647
26859
|
// dist/transport/push/parser.js
|
|
26648
|
-
import { EventEmitter as
|
|
26860
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
26649
26861
|
var State;
|
|
26650
26862
|
(function(State2) {
|
|
26651
26863
|
State2[State2["VERSION_TAG_AND_SIZE"] = 0] = "VERSION_TAG_AND_SIZE";
|
|
@@ -26663,7 +26875,7 @@ var TAG_TO_TYPE = {
|
|
|
26663
26875
|
[MessageTag.DataMessageStanza]: "mcs_proto.DataMessageStanza",
|
|
26664
26876
|
[MessageTag.StreamErrorStanza]: "mcs_proto.StreamErrorStanza"
|
|
26665
26877
|
};
|
|
26666
|
-
var McsParser = class extends
|
|
26878
|
+
var McsParser = class extends EventEmitter12 {
|
|
26667
26879
|
data = Buffer.alloc(0);
|
|
26668
26880
|
state = State.VERSION_TAG_AND_SIZE;
|
|
26669
26881
|
messageTag = 0;
|
|
@@ -26841,7 +27053,7 @@ function normalizePushEvent(raw) {
|
|
|
26841
27053
|
raw
|
|
26842
27054
|
};
|
|
26843
27055
|
}
|
|
26844
|
-
var PushClient = class _PushClient extends
|
|
27056
|
+
var PushClient = class _PushClient extends EventEmitter13 {
|
|
26845
27057
|
creds;
|
|
26846
27058
|
logger;
|
|
26847
27059
|
/** Consecutive MCS login rejections tolerated (self-healing propagation) before surfacing an error. */
|
|
@@ -28048,7 +28260,7 @@ function readinessSnapshot(readiness, state = readiness.state) {
|
|
|
28048
28260
|
wiredP2p: Object.freeze({ ...readiness.wiredP2p })
|
|
28049
28261
|
});
|
|
28050
28262
|
}
|
|
28051
|
-
var EufyMega = class extends
|
|
28263
|
+
var EufyMega = class extends EventEmitter14 {
|
|
28052
28264
|
opts;
|
|
28053
28265
|
mega;
|
|
28054
28266
|
/**
|
|
@@ -28915,7 +29127,7 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28915
29127
|
/**
|
|
28916
29128
|
* The station a device's commands go through: the device itself, or the HomeBase the registry links it
|
|
28917
29129
|
* to. Answers the target record, the station record and serial, the station's `device_type`, and the
|
|
28918
|
-
* `t9000` / `t8n00` family flags.
|
|
29130
|
+
* `t9000` / `t8n00` / `t8223` family flags.
|
|
28919
29131
|
*/
|
|
28920
29132
|
stationOf(sn) {
|
|
28921
29133
|
const devices = this.registry.list();
|
|
@@ -28925,7 +29137,16 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28925
29137
|
const raw = station?.raw ?? {};
|
|
28926
29138
|
const deviceType = typeof raw.device_type === "number" ? raw.device_type : void 0;
|
|
28927
29139
|
const family = { deviceType, model: station?.model };
|
|
28928
|
-
return {
|
|
29140
|
+
return {
|
|
29141
|
+
target,
|
|
29142
|
+
station,
|
|
29143
|
+
stationSn,
|
|
29144
|
+
raw,
|
|
29145
|
+
deviceType,
|
|
29146
|
+
t9000: isStation9000(family),
|
|
29147
|
+
t8n00: isNvrT8N00(family),
|
|
29148
|
+
t8223: isDoorbellT8223(family)
|
|
29149
|
+
};
|
|
28929
29150
|
}
|
|
28930
29151
|
/** The vendor DeviceType of the station a device hangs off; `undefined` for a station itself. */
|
|
28931
29152
|
stationDeviceTypeOf(sn) {
|
|
@@ -28984,7 +29205,8 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28984
29205
|
* kind and stays transport-agnostic; only the facade sees both sides and decides here. A T9000 or T8N00
|
|
28985
29206
|
* station and the devices attached to it go over RTC, with an attached device's command refused
|
|
28986
29207
|
* unless its channel resolves back to it on the station. T8N00 uses full SDP and admits direct and
|
|
28987
|
-
* relay candidates; T9000 retains compact SDP and relay-only candidates.
|
|
29208
|
+
* relay candidates; T9000 retains compact SDP and relay-only candidates. A T8223 doorbell's route serves
|
|
29209
|
+
* media only, and the RTC router refuses a command on it. Everything else is P2P.
|
|
28988
29210
|
*/
|
|
28989
29211
|
routeCommand(sn, cmd) {
|
|
28990
29212
|
if (cmd.kind === "ff09-actuate" || cmd.kind === "ff09-autolock" || cmd.kind === "ff09-setting-toggle") {
|
|
@@ -29013,25 +29235,32 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
29013
29235
|
return this.p2p.dispatchCommand(sn, cmd);
|
|
29014
29236
|
}
|
|
29015
29237
|
/**
|
|
29016
|
-
* The RTC route for a T9000 or T8N00 station or a device attached to one,
|
|
29017
|
-
* classification; `undefined` for any other device. A T8N00
|
|
29018
|
-
* `
|
|
29238
|
+
* The RTC route for a T9000 or T8N00 station or a device attached to one, or a T8223 doorbell that is its
|
|
29239
|
+
* own station, decided by the station's model classification; `undefined` for any other device. A T8N00
|
|
29240
|
+
* route carries its native `call` signalling; a T8223 route does too, and streams its video on a media
|
|
29241
|
+
* track. The admin id is the station's `member.admin_user_id`, else the logged-in user.
|
|
29019
29242
|
*/
|
|
29020
29243
|
rtcRouteFor(sn) {
|
|
29021
|
-
const { target, station, stationSn, raw, t9000, t8n00: native } = this.stationOf(sn);
|
|
29022
|
-
|
|
29244
|
+
const { target, station, stationSn, raw, t9000, t8n00: native, t8223 } = this.stationOf(sn);
|
|
29245
|
+
const doorbell = t8223 && stationSn === sn;
|
|
29246
|
+
if (!target || !station || !(t9000 || native || doorbell))
|
|
29023
29247
|
return void 0;
|
|
29024
29248
|
const member = raw.member?.admin_user_id;
|
|
29025
29249
|
const adminUserId = typeof member === "string" && member ? member : void 0;
|
|
29026
29250
|
const base = { stationSn, adminUserId, attached: stationSn !== sn };
|
|
29251
|
+
if (doorbell)
|
|
29252
|
+
return { ...base, signalingMode: "call", iceTransportPolicy: "all", mediaTrack: true };
|
|
29027
29253
|
return native ? { ...base, signalingMode: "call", iceTransportPolicy: "all" } : base;
|
|
29028
29254
|
}
|
|
29029
29255
|
/**
|
|
29030
|
-
* The RTC media provider for a
|
|
29031
|
-
* the station; `undefined` otherwise, including a camera behind
|
|
29256
|
+
* The RTC media provider for a station on a media-track route, or a camera attached to a T9000 whose
|
|
29257
|
+
* `device_channel` resolves back to it on the station; `undefined` otherwise, including a camera behind
|
|
29258
|
+
* a T8N00, whose route carries commands only.
|
|
29032
29259
|
*/
|
|
29033
29260
|
rtcMediaFor(sn) {
|
|
29034
29261
|
const route = this.rtcRouteFor(sn);
|
|
29262
|
+
if (route?.mediaTrack)
|
|
29263
|
+
return this.rtc.trackMediaProviderFor(route);
|
|
29035
29264
|
if (!route?.attached || route.signalingMode === "call")
|
|
29036
29265
|
return void 0;
|
|
29037
29266
|
const raw = this.registry.list().find((d) => d.sn === sn)?.raw ?? {};
|
|
@@ -30672,7 +30901,7 @@ var SolixClient = class _SolixClient {
|
|
|
30672
30901
|
};
|
|
30673
30902
|
|
|
30674
30903
|
// dist/transport/mqtt/solix-mqtt.js
|
|
30675
|
-
import { EventEmitter as
|
|
30904
|
+
import { EventEmitter as EventEmitter15 } from "node:events";
|
|
30676
30905
|
var SOLIX_METER_FIELD_NAMES = {
|
|
30677
30906
|
168: "meterPowerL1",
|
|
30678
30907
|
// Anker Modbus: primary_phase_1_active_power
|
|
@@ -30850,7 +31079,7 @@ function addSolarbankScalars(frame, out) {
|
|
|
30850
31079
|
out.gridExportLimit = df.readUInt16LE(5);
|
|
30851
31080
|
}
|
|
30852
31081
|
}
|
|
30853
|
-
var SolixMqtt = class extends
|
|
31082
|
+
var SolixMqtt = class extends EventEmitter15 {
|
|
30854
31083
|
transport;
|
|
30855
31084
|
appName;
|
|
30856
31085
|
userId;
|