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