@mega-yfue/eufy-sdk 0.5.0-beta.25 → 0.5.0-beta.27
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 +21 -0
- package/dist/core/contracts.d.ts +2 -0
- package/dist/index.js +421 -67
- package/dist/index.js.map +4 -4
- package/dist/transport/abortable.d.ts +8 -0
- package/dist/transport/p2p/command-router.d.ts +0 -20
- package/dist/transport/p2p/shared-live-source.d.ts +9 -0
- package/dist/transport/p2p/video.d.ts +2 -0
- package/dist/transport/rtc/command-router.d.ts +39 -5
- package/dist/transport/rtc/live.d.ts +69 -0
- package/dist/transport/rtc/peer.d.ts +5 -0
- package/dist/transport/rtc/portal-packet.d.ts +6 -0
- package/dist/transport/rtc/ptcs-framer.d.ts +3 -2
- package/dist/transport/rtc/session.d.ts +5 -1
- package/package.json +1 -1
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 EventEmitter13 } from "node:events";
|
|
9
9
|
|
|
10
10
|
// dist/transport/http/mega-client.js
|
|
11
11
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -14514,6 +14514,19 @@ function decodeMapFrame(frame, codec) {
|
|
|
14514
14514
|
return READERS.get(frame.channelId)?.(frame.payload, codec);
|
|
14515
14515
|
}
|
|
14516
14516
|
|
|
14517
|
+
// dist/transport/abortable.js
|
|
14518
|
+
function abortable(work, signal) {
|
|
14519
|
+
if (!signal)
|
|
14520
|
+
return work;
|
|
14521
|
+
signal.throwIfAborted();
|
|
14522
|
+
return Promise.race([
|
|
14523
|
+
work,
|
|
14524
|
+
new Promise((_resolve, reject) => {
|
|
14525
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
14526
|
+
})
|
|
14527
|
+
]);
|
|
14528
|
+
}
|
|
14529
|
+
|
|
14517
14530
|
// dist/transport/p2p/command-router.js
|
|
14518
14531
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
14519
14532
|
|
|
@@ -17751,7 +17764,13 @@ function beginsAccessUnit(body) {
|
|
|
17751
17764
|
return body[2] === 1 || body[2] === 0 && body[3] === 1;
|
|
17752
17765
|
}
|
|
17753
17766
|
function unitOf(header, data) {
|
|
17754
|
-
return {
|
|
17767
|
+
return {
|
|
17768
|
+
sourceTimestamp: header.timestamp,
|
|
17769
|
+
keyframe: header.keyframe,
|
|
17770
|
+
width: header.width,
|
|
17771
|
+
height: header.height,
|
|
17772
|
+
data
|
|
17773
|
+
};
|
|
17755
17774
|
}
|
|
17756
17775
|
|
|
17757
17776
|
// dist/transport/p2p/annexb.js
|
|
@@ -17854,6 +17873,18 @@ function startCodeBegin(buf, payloadOffset) {
|
|
|
17854
17873
|
return threeByte - 1;
|
|
17855
17874
|
return threeByte;
|
|
17856
17875
|
}
|
|
17876
|
+
function hasIdr(buf, codec) {
|
|
17877
|
+
for (const p of nalStarts(buf)) {
|
|
17878
|
+
const b = buf[p];
|
|
17879
|
+
if (codec === "h265") {
|
|
17880
|
+
const t = b >> 1 & 63;
|
|
17881
|
+
if (t === 19 || t === 20)
|
|
17882
|
+
return true;
|
|
17883
|
+
} else if ((b & 31) === 5)
|
|
17884
|
+
return true;
|
|
17885
|
+
}
|
|
17886
|
+
return false;
|
|
17887
|
+
}
|
|
17857
17888
|
var MAX_CODED_DIMENSION = 32768;
|
|
17858
17889
|
var H264_CHROMA_PROFILES = /* @__PURE__ */ new Set([100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135, 144]);
|
|
17859
17890
|
var CHROMA_SUBSAMPLING = {
|
|
@@ -18280,6 +18311,7 @@ var LiveStream = class extends EventEmitter3 {
|
|
|
18280
18311
|
this.lastCodec = sniffAnnexbCodec(unit.data) ?? this.lastCodec;
|
|
18281
18312
|
this.settleKeepalive();
|
|
18282
18313
|
this.emit("video", {
|
|
18314
|
+
sourceTimestamp: unit.sourceTimestamp,
|
|
18283
18315
|
keyframe: unit.keyframe,
|
|
18284
18316
|
width: unit.width,
|
|
18285
18317
|
height: unit.height,
|
|
@@ -19623,6 +19655,14 @@ var SharedLiveSource = class {
|
|
|
19623
19655
|
this.opts.onIdle?.();
|
|
19624
19656
|
}
|
|
19625
19657
|
};
|
|
19658
|
+
function attachUnlessAborted(source, signal) {
|
|
19659
|
+
const consumer = source.attach();
|
|
19660
|
+
if (signal?.aborted) {
|
|
19661
|
+
consumer.detach();
|
|
19662
|
+
signal.throwIfAborted();
|
|
19663
|
+
}
|
|
19664
|
+
return consumer;
|
|
19665
|
+
}
|
|
19626
19666
|
|
|
19627
19667
|
// dist/transport/p2p/session-manager.js
|
|
19628
19668
|
var SessionSupersededError = class extends Error {
|
|
@@ -20979,17 +21019,6 @@ function stationChannels(devices) {
|
|
|
20979
21019
|
|
|
20980
21020
|
// dist/transport/p2p/command-router.js
|
|
20981
21021
|
var DIRECT_CMD_SENDS = 5;
|
|
20982
|
-
function abortable(work, signal) {
|
|
20983
|
-
if (!signal)
|
|
20984
|
-
return work;
|
|
20985
|
-
signal.throwIfAborted();
|
|
20986
|
-
return Promise.race([
|
|
20987
|
-
work,
|
|
20988
|
-
new Promise((_resolve, reject) => {
|
|
20989
|
-
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
20990
|
-
})
|
|
20991
|
-
]);
|
|
20992
|
-
}
|
|
20993
21022
|
var LEVEL2_GRACE_MS = 25e3;
|
|
20994
21023
|
var LEVEL2_SETTLE_MS = 8e3;
|
|
20995
21024
|
var P2P_STATION_WAITS = {
|
|
@@ -21481,11 +21510,11 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21481
21510
|
},
|
|
21482
21511
|
live: async (opts) => {
|
|
21483
21512
|
const source = await this.sharedLiveSourceFor(sn, opts, true);
|
|
21484
|
-
return
|
|
21513
|
+
return attachUnlessAborted(source, opts?.signal);
|
|
21485
21514
|
},
|
|
21486
21515
|
openReadable: async (opts) => {
|
|
21487
21516
|
const source = await this.sharedLiveSourceFor(sn, opts ?? {}, true);
|
|
21488
|
-
return openReadableFromConsumer(
|
|
21517
|
+
return openReadableFromConsumer(attachUnlessAborted(source, opts?.signal), opts);
|
|
21489
21518
|
},
|
|
21490
21519
|
recordFragments: (opts) => this.recordFragments(sn, opts),
|
|
21491
21520
|
talkback: (opts) => this.openTalkback(sn, opts),
|
|
@@ -21853,22 +21882,6 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21853
21882
|
* A still with no live sibling re-asserts, so a tile refreshing on a quiet station is
|
|
21854
21883
|
* unaffected.
|
|
21855
21884
|
*/
|
|
21856
|
-
/**
|
|
21857
|
-
* Attach a consumer, unless the caller has already abandoned the call.
|
|
21858
|
-
*
|
|
21859
|
-
* The acquisition it just waited through can outlast the caller's interest, and a consumer attached for
|
|
21860
|
-
* somebody who has gone keeps the pull warm for nobody. Detaching immediately gives the pull back, which
|
|
21861
|
-
* lets it linger and fall away if this was the only thing holding it, and leaves it untouched if it was
|
|
21862
|
-
* not.
|
|
21863
|
-
*/
|
|
21864
|
-
attachUnlessAborted(source, signal) {
|
|
21865
|
-
const consumer = source.attach();
|
|
21866
|
-
if (signal?.aborted) {
|
|
21867
|
-
consumer.detach();
|
|
21868
|
-
signal.throwIfAborted();
|
|
21869
|
-
}
|
|
21870
|
-
return consumer;
|
|
21871
|
-
}
|
|
21872
21885
|
/**
|
|
21873
21886
|
* Dispose one cached live source and forget it, so the next acquisition builds a fresh one. Its claim
|
|
21874
21887
|
* on a session goes with it, and a media session opened for this source alone is closed: nothing else
|
|
@@ -22638,20 +22651,21 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22638
22651
|
}
|
|
22639
22652
|
};
|
|
22640
22653
|
|
|
22641
|
-
// dist/transport/rtc/
|
|
22642
|
-
import { EventEmitter as EventEmitter9 } from "node:events";
|
|
22643
|
-
|
|
22644
|
-
// dist/transport/rtc/peer.js
|
|
22654
|
+
// dist/transport/rtc/live.js
|
|
22645
22655
|
import { EventEmitter as EventEmitter7 } from "node:events";
|
|
22646
22656
|
|
|
22647
22657
|
// dist/transport/rtc/portal-packet.js
|
|
22648
22658
|
var MAGIC2 = Buffer.from("XZYH", "ascii");
|
|
22649
22659
|
var PORTAL_HEADER_LENGTH = 16;
|
|
22660
|
+
var PORTAL_CMD_SET_PAYLOAD = 1350;
|
|
22661
|
+
var PORTAL_STATION_CHANNEL = 255;
|
|
22650
22662
|
var PortalLinkType = {
|
|
22651
22663
|
/** Command channel: requests out, acknowledgements back. */
|
|
22652
22664
|
COMMAND: 1,
|
|
22653
22665
|
/** Station-originated frames: pushes, notify payloads, control results. */
|
|
22654
|
-
NOTIFY: 3
|
|
22666
|
+
NOTIFY: 3,
|
|
22667
|
+
/** Live video. */
|
|
22668
|
+
LIVE: 5
|
|
22655
22669
|
};
|
|
22656
22670
|
function encodeLe(value, width) {
|
|
22657
22671
|
const buf = Buffer.alloc(width);
|
|
@@ -22743,6 +22757,159 @@ var SegmentCounter = class {
|
|
|
22743
22757
|
}
|
|
22744
22758
|
};
|
|
22745
22759
|
|
|
22760
|
+
// dist/transport/rtc/live.js
|
|
22761
|
+
var LIVE_START = 1003;
|
|
22762
|
+
var LIVE_STOP = 1004;
|
|
22763
|
+
var LIVE_MEDIA = 1300;
|
|
22764
|
+
var LIVE_MEDIA_CHANNEL = 101;
|
|
22765
|
+
var LIVE_STREAM_ID = 1;
|
|
22766
|
+
var KEEPALIVE_MS = 29e3;
|
|
22767
|
+
var KEEPALIVE = Buffer.from("0009000010000000000000006300000000000000585a5948730400000000000000000002", "hex");
|
|
22768
|
+
var MEDIA_HEADER_LENGTH = 22;
|
|
22769
|
+
var STREAM_TYPE_HEVC = 1;
|
|
22770
|
+
function parseMediaBody(body) {
|
|
22771
|
+
if (body.length < MEDIA_HEADER_LENGTH || body[5] !== STREAM_TYPE_HEVC)
|
|
22772
|
+
return void 0;
|
|
22773
|
+
const length = body.readUInt32LE(0);
|
|
22774
|
+
if (body.length < MEDIA_HEADER_LENGTH + length)
|
|
22775
|
+
return void 0;
|
|
22776
|
+
return {
|
|
22777
|
+
data: body.subarray(MEDIA_HEADER_LENGTH, MEDIA_HEADER_LENGTH + length),
|
|
22778
|
+
width: body.readUInt16LE(10),
|
|
22779
|
+
height: body.readUInt16LE(12)
|
|
22780
|
+
};
|
|
22781
|
+
}
|
|
22782
|
+
var RtcLiveStream = class extends EventEmitter7 {
|
|
22783
|
+
opts;
|
|
22784
|
+
running = false;
|
|
22785
|
+
startSegment = -1;
|
|
22786
|
+
keepalive;
|
|
22787
|
+
width = 0;
|
|
22788
|
+
height = 0;
|
|
22789
|
+
onMedia = (frame) => this.onMediaFrame(frame);
|
|
22790
|
+
onCommand = (frame, linkType) => this.onCommandFrame(frame, linkType);
|
|
22791
|
+
onClose = () => this.fail(new Error(`rtc live ${this.tag}: session closed`));
|
|
22792
|
+
constructor(opts) {
|
|
22793
|
+
super();
|
|
22794
|
+
this.opts = opts;
|
|
22795
|
+
}
|
|
22796
|
+
get tag() {
|
|
22797
|
+
return `${this.opts.stationSn}#${this.opts.channel}`;
|
|
22798
|
+
}
|
|
22799
|
+
start() {
|
|
22800
|
+
if (this.running)
|
|
22801
|
+
return this;
|
|
22802
|
+
const { session, seg, accountId } = this.opts;
|
|
22803
|
+
this.running = true;
|
|
22804
|
+
session.on("mediaData", this.onMedia);
|
|
22805
|
+
session.on("commandData", this.onCommand);
|
|
22806
|
+
session.on("close", this.onClose);
|
|
22807
|
+
this.startSegment = seg.next();
|
|
22808
|
+
const sent = session.sendCommand(buildPortalPacket({
|
|
22809
|
+
commandId: PORTAL_CMD_SET_PAYLOAD,
|
|
22810
|
+
channel: PORTAL_STATION_CHANNEL,
|
|
22811
|
+
segment: this.startSegment,
|
|
22812
|
+
isResponse: LIVE_STREAM_ID,
|
|
22813
|
+
payload: {
|
|
22814
|
+
account_id: accountId,
|
|
22815
|
+
cmd: LIVE_START,
|
|
22816
|
+
payload: {
|
|
22817
|
+
ClientOS: "WEB",
|
|
22818
|
+
entrytype: 0,
|
|
22819
|
+
camera_type: 0,
|
|
22820
|
+
key: "",
|
|
22821
|
+
msg_id: 116,
|
|
22822
|
+
audio_chn: -1,
|
|
22823
|
+
streamtype: 2,
|
|
22824
|
+
stitch_mode: 1,
|
|
22825
|
+
chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
|
|
22826
|
+
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
22827
|
+
station_video_type: 6,
|
|
22828
|
+
play_id: 1
|
|
22829
|
+
}
|
|
22830
|
+
}
|
|
22831
|
+
}));
|
|
22832
|
+
if (!sent) {
|
|
22833
|
+
queueMicrotask(() => this.fail(new Error(`rtc live ${this.tag}: command channel not open`)));
|
|
22834
|
+
return this;
|
|
22835
|
+
}
|
|
22836
|
+
this.keepalive = setInterval(() => {
|
|
22837
|
+
if (!session.sendRaw(KEEPALIVE))
|
|
22838
|
+
this.opts.logger?.warn?.(`[rtc] ${this.tag} keepalive not sent`);
|
|
22839
|
+
}, KEEPALIVE_MS);
|
|
22840
|
+
this.keepalive.unref?.();
|
|
22841
|
+
this.opts.logger?.debug?.(`[rtc] ${this.tag} live start sent`);
|
|
22842
|
+
return this;
|
|
22843
|
+
}
|
|
22844
|
+
stop() {
|
|
22845
|
+
if (!this.running)
|
|
22846
|
+
return;
|
|
22847
|
+
this.teardown();
|
|
22848
|
+
const { session, seg, accountId } = this.opts;
|
|
22849
|
+
session.sendCommand(buildPortalPacket({
|
|
22850
|
+
commandId: PORTAL_CMD_SET_PAYLOAD,
|
|
22851
|
+
channel: PORTAL_STATION_CHANNEL,
|
|
22852
|
+
segment: seg.next(),
|
|
22853
|
+
isResponse: LIVE_STREAM_ID,
|
|
22854
|
+
payload: { account_id: accountId, cmd: LIVE_STOP, payload: {} }
|
|
22855
|
+
}));
|
|
22856
|
+
this.emit("stop");
|
|
22857
|
+
}
|
|
22858
|
+
teardown() {
|
|
22859
|
+
this.running = false;
|
|
22860
|
+
if (this.keepalive)
|
|
22861
|
+
clearInterval(this.keepalive);
|
|
22862
|
+
this.keepalive = void 0;
|
|
22863
|
+
const { session } = this.opts;
|
|
22864
|
+
session.off("mediaData", this.onMedia);
|
|
22865
|
+
session.off("commandData", this.onCommand);
|
|
22866
|
+
session.off("close", this.onClose);
|
|
22867
|
+
}
|
|
22868
|
+
fail(err) {
|
|
22869
|
+
if (!this.running)
|
|
22870
|
+
return;
|
|
22871
|
+
this.teardown();
|
|
22872
|
+
this.emit("error", err);
|
|
22873
|
+
}
|
|
22874
|
+
/** The start's ACK: a non-zero result code ends the pull. */
|
|
22875
|
+
onCommandFrame(frame, linkType) {
|
|
22876
|
+
const p = parsePortalPacket(frame, linkType);
|
|
22877
|
+
if (!p?.isResponse || p.segment !== this.startSegment || p.commandId !== PORTAL_CMD_SET_PAYLOAD)
|
|
22878
|
+
return;
|
|
22879
|
+
if (p.errCode !== 0)
|
|
22880
|
+
this.fail(new Error(`rtc live ${this.tag}: start refused (err ${p.errCode})`));
|
|
22881
|
+
}
|
|
22882
|
+
onMediaFrame(frame) {
|
|
22883
|
+
const h = parsePortalHeader(frame);
|
|
22884
|
+
if (!h || h.commandId !== LIVE_MEDIA || h.channel !== LIVE_MEDIA_CHANNEL)
|
|
22885
|
+
return;
|
|
22886
|
+
if (h.paramLength > frame.length - PORTAL_HEADER_LENGTH)
|
|
22887
|
+
return;
|
|
22888
|
+
const media = parseMediaBody(frame.subarray(PORTAL_HEADER_LENGTH, PORTAL_HEADER_LENGTH + h.paramLength));
|
|
22889
|
+
if (!media)
|
|
22890
|
+
return;
|
|
22891
|
+
const { data, width, height } = media;
|
|
22892
|
+
if (width && height) {
|
|
22893
|
+
this.width = width;
|
|
22894
|
+
this.height = height;
|
|
22895
|
+
}
|
|
22896
|
+
const out = {
|
|
22897
|
+
codec: "h265",
|
|
22898
|
+
data,
|
|
22899
|
+
keyframe: hasIdr(data, "h265"),
|
|
22900
|
+
width: this.width,
|
|
22901
|
+
height: this.height
|
|
22902
|
+
};
|
|
22903
|
+
this.emit("video", out);
|
|
22904
|
+
}
|
|
22905
|
+
};
|
|
22906
|
+
|
|
22907
|
+
// dist/transport/rtc/session.js
|
|
22908
|
+
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
22909
|
+
|
|
22910
|
+
// dist/transport/rtc/peer.js
|
|
22911
|
+
import { EventEmitter as EventEmitter8 } from "node:events";
|
|
22912
|
+
|
|
22746
22913
|
// dist/transport/rtc/ptcs-framer.js
|
|
22747
22914
|
var MAGIC3 = Buffer.from("PTCS", "ascii");
|
|
22748
22915
|
var PTCS_HEADER_LENGTH = 28;
|
|
@@ -22752,10 +22919,15 @@ var FLAG_LAST = 1024;
|
|
|
22752
22919
|
var LENGTH_MASK = 1023;
|
|
22753
22920
|
var PtcsChannel = {
|
|
22754
22921
|
COMMAND: 0,
|
|
22755
|
-
NOTIFY: 2
|
|
22922
|
+
NOTIFY: 2,
|
|
22923
|
+
LIVE: 5
|
|
22756
22924
|
};
|
|
22757
22925
|
function linkTypeForChannel(channel) {
|
|
22758
|
-
|
|
22926
|
+
if (channel === PtcsChannel.NOTIFY)
|
|
22927
|
+
return PortalLinkType.NOTIFY;
|
|
22928
|
+
if (channel === PtcsChannel.LIVE)
|
|
22929
|
+
return PortalLinkType.LIVE;
|
|
22930
|
+
return PortalLinkType.COMMAND;
|
|
22759
22931
|
}
|
|
22760
22932
|
var PTCS_STALE_MS = 15e3;
|
|
22761
22933
|
function parsePtcsHeader(packet) {
|
|
@@ -23021,7 +23193,7 @@ async function loadNativePeerFactory() {
|
|
|
23021
23193
|
return (name, config) => new ndc.PeerConnection(name, config);
|
|
23022
23194
|
}
|
|
23023
23195
|
var ANSWER_TIMEOUT_MS = 15e3;
|
|
23024
|
-
var RtcPeer = class extends
|
|
23196
|
+
var RtcPeer = class extends EventEmitter8 {
|
|
23025
23197
|
opts;
|
|
23026
23198
|
pc;
|
|
23027
23199
|
channels = /* @__PURE__ */ new Map();
|
|
@@ -23138,6 +23310,16 @@ var RtcPeer = class extends EventEmitter7 {
|
|
|
23138
23310
|
}
|
|
23139
23311
|
this.addNow(candidate);
|
|
23140
23312
|
}
|
|
23313
|
+
/**
|
|
23314
|
+
* Send bytes on the command channel as they are, without PTCS framing; false when it isn't open. The
|
|
23315
|
+
* portal's data-channel keepalive rides the wire unframed.
|
|
23316
|
+
*/
|
|
23317
|
+
sendRaw(bytes2) {
|
|
23318
|
+
const dc = this.channels.get(COMMAND_CHANNEL);
|
|
23319
|
+
if (!dc?.isOpen())
|
|
23320
|
+
return false;
|
|
23321
|
+
return dc.sendMessageBinary(bytes2);
|
|
23322
|
+
}
|
|
23141
23323
|
/**
|
|
23142
23324
|
* Send one portal packet on the command channel, through the framer. False when the channel is not
|
|
23143
23325
|
* usable or the native send refused a wire packet, so a dropped frame does not read as success.
|
|
@@ -23244,7 +23426,7 @@ var RtcPeer = class extends EventEmitter7 {
|
|
|
23244
23426
|
};
|
|
23245
23427
|
|
|
23246
23428
|
// dist/transport/rtc/signaling.js
|
|
23247
|
-
import { EventEmitter as
|
|
23429
|
+
import { EventEmitter as EventEmitter9 } from "node:events";
|
|
23248
23430
|
import { createHmac as createHmac3, randomUUID as randomUUID2 } from "node:crypto";
|
|
23249
23431
|
var RTC_WS_PATH = "/v1/rtc/ws/join?reqtype=nvr";
|
|
23250
23432
|
var RTC_SIGN_PATH = "/v1/smart/nvr/ws/sign";
|
|
@@ -23260,7 +23442,7 @@ var defaultSocket = (url, init) => new WebSocket(url, {
|
|
|
23260
23442
|
protocols: init.protocols,
|
|
23261
23443
|
headers: { Origin: init.origin }
|
|
23262
23444
|
});
|
|
23263
|
-
var RtcSignalingClient = class extends
|
|
23445
|
+
var RtcSignalingClient = class extends EventEmitter9 {
|
|
23264
23446
|
opts;
|
|
23265
23447
|
ws;
|
|
23266
23448
|
sign;
|
|
@@ -23485,7 +23667,7 @@ var RtcSignalingClient = class extends EventEmitter8 {
|
|
|
23485
23667
|
|
|
23486
23668
|
// dist/transport/rtc/session.js
|
|
23487
23669
|
var AUTH_TIMEOUT_MS = 15e3;
|
|
23488
|
-
var RtcSession = class extends
|
|
23670
|
+
var RtcSession = class extends EventEmitter10 {
|
|
23489
23671
|
opts;
|
|
23490
23672
|
signaling;
|
|
23491
23673
|
peer;
|
|
@@ -23537,7 +23719,12 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23537
23719
|
}
|
|
23538
23720
|
});
|
|
23539
23721
|
this.peer.on("error", (e) => this.emit("error", e));
|
|
23540
|
-
this.peer.on("data", (frame, linkType) =>
|
|
23722
|
+
this.peer.on("data", (frame, linkType) => {
|
|
23723
|
+
if (linkType === PortalLinkType.LIVE)
|
|
23724
|
+
this.emit("mediaData", frame);
|
|
23725
|
+
else
|
|
23726
|
+
this.emit("commandData", frame, linkType);
|
|
23727
|
+
});
|
|
23541
23728
|
}
|
|
23542
23729
|
get isConnected() {
|
|
23543
23730
|
return this.connected;
|
|
@@ -23562,6 +23749,10 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23562
23749
|
sendCommand(portalPacket) {
|
|
23563
23750
|
return this.peer.sendCommand(portalPacket);
|
|
23564
23751
|
}
|
|
23752
|
+
/** Send unframed bytes on the command channel; false when it isn't open. */
|
|
23753
|
+
sendRaw(bytes2) {
|
|
23754
|
+
return this.peer.sendRaw(bytes2);
|
|
23755
|
+
}
|
|
23565
23756
|
/**
|
|
23566
23757
|
* Hang up and tear both sides down; `close` fires once, here if nothing announced it before. A
|
|
23567
23758
|
* `connect()` still waiting for the signalling auth is rejected and its timer stopped.
|
|
@@ -23708,16 +23899,21 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23708
23899
|
};
|
|
23709
23900
|
|
|
23710
23901
|
// dist/transport/rtc/command-router.js
|
|
23711
|
-
var PORTAL_CMD_SET_PAYLOAD = 1350;
|
|
23712
23902
|
var PORTAL_CMD_CONTROL_PAYLOAD = 1700;
|
|
23713
23903
|
var PORTAL_CMD_NOTIFY_PAYLOAD = 1351;
|
|
23714
|
-
var PORTAL_STATION_CHANNEL = 255;
|
|
23715
23904
|
var ACK_TIMEOUT_MS = 8e3;
|
|
23716
23905
|
var CONNECT_TIMEOUT_MS2 = 12e3;
|
|
23717
23906
|
var IDLE_CLOSE_MS = 6e4;
|
|
23907
|
+
var StationStreamBusyError = class extends Error {
|
|
23908
|
+
constructor(stationSn, servingSn) {
|
|
23909
|
+
super(`rtc live: ${stationSn} is serving ${servingSn}; the station streams one camera at a time`);
|
|
23910
|
+
this.name = "StationStreamBusyError";
|
|
23911
|
+
}
|
|
23912
|
+
};
|
|
23718
23913
|
var RtcCommandRouter = class {
|
|
23719
23914
|
deps;
|
|
23720
23915
|
sessions = /* @__PURE__ */ new Map();
|
|
23916
|
+
lives = /* @__PURE__ */ new Map();
|
|
23721
23917
|
constructor(deps) {
|
|
23722
23918
|
this.deps = deps;
|
|
23723
23919
|
}
|
|
@@ -23741,11 +23937,112 @@ var RtcCommandRouter = class {
|
|
|
23741
23937
|
st.queue = run.catch(() => void 0);
|
|
23742
23938
|
return run;
|
|
23743
23939
|
}
|
|
23744
|
-
/**
|
|
23940
|
+
/**
|
|
23941
|
+
* The media of a camera on a T9000: live video, a readable feed and a live still over the station's
|
|
23942
|
+
* session. Recording has no wire here and is refused; the optional members it has no wire for are absent.
|
|
23943
|
+
*/
|
|
23944
|
+
mediaProviderFor(route) {
|
|
23945
|
+
return {
|
|
23946
|
+
live: (opts) => this.withLiveSource(route, opts ?? {}, (source) => attachUnlessAborted(source, opts?.signal)),
|
|
23947
|
+
openReadable: (opts) => this.withLiveSource(route, opts ?? {}, (source) => openReadableFromConsumer(attachUnlessAborted(source, opts?.signal), opts)),
|
|
23948
|
+
snapshotLive: (opts) => this.withLiveSource(route, opts ?? {}, (source) => captureSnapshotFromShared(source, { ...opts, logger: this.deps.logger, ffmpegPath: this.deps.ffmpegPath })).catch((e) => {
|
|
23949
|
+
if (!(e instanceof StationStreamBusyError))
|
|
23950
|
+
throw e;
|
|
23951
|
+
throw new LiveSnapshotUnavailableError("source-failed", e.message, { cause: e });
|
|
23952
|
+
}),
|
|
23953
|
+
record: async () => {
|
|
23954
|
+
throw new Error(`record is not available for ${route.cameraSn} over the T9000 control channel`);
|
|
23955
|
+
}
|
|
23956
|
+
};
|
|
23957
|
+
}
|
|
23958
|
+
/**
|
|
23959
|
+
* Tear down every station session (logout / shutdown). Each live source is disposed before its session
|
|
23960
|
+
* closes, so its consumers end cleanly and its stop still rides the session.
|
|
23961
|
+
*/
|
|
23745
23962
|
close() {
|
|
23963
|
+
for (const [sn, live] of this.lives) {
|
|
23964
|
+
this.lives.delete(sn);
|
|
23965
|
+
live.source.dispose();
|
|
23966
|
+
}
|
|
23746
23967
|
for (const [sn, st] of this.sessions)
|
|
23747
23968
|
this.drop(sn, st);
|
|
23748
23969
|
}
|
|
23970
|
+
/**
|
|
23971
|
+
* Hand `use` the station's live source for this camera, in the same turn the source is created or reused,
|
|
23972
|
+
* so a consumer it attaches is counted before any other caller looks.
|
|
23973
|
+
*
|
|
23974
|
+
* The station streams one camera at a time. Another camera whose source has consumers refuses this one,
|
|
23975
|
+
* before the session is awaited and again after; one with none (never attached or lingering)
|
|
23976
|
+
* holds nothing a viewer needs, so it is disposed and the slot taken. The first caller's
|
|
23977
|
+
* {@link SharedSourceHints} configure a new source.
|
|
23978
|
+
*/
|
|
23979
|
+
async withLiveSource(route, opts, use) {
|
|
23980
|
+
opts.signal?.throwIfAborted();
|
|
23981
|
+
this.refuseIfWatched(route);
|
|
23982
|
+
const identity = this.deps.identity();
|
|
23983
|
+
if (!identity)
|
|
23984
|
+
throw new Error(`rtc: not logged in, cannot open ${route.stationSn}`);
|
|
23985
|
+
const accountId = route.adminUserId || identity.userId;
|
|
23986
|
+
const st = await abortable(this.stationSession(route, accountId, identity), opts.signal);
|
|
23987
|
+
this.refuseIfWatched(route);
|
|
23988
|
+
const current = this.lives.get(route.stationSn);
|
|
23989
|
+
if (current && current.cameraSn !== route.cameraSn) {
|
|
23990
|
+
this.deps.logger?.debug?.(`[rtc] ${route.stationSn} releasing the unwatched source of ${current.cameraSn} for ${route.cameraSn}`);
|
|
23991
|
+
this.lives.delete(route.stationSn);
|
|
23992
|
+
current.source.dispose();
|
|
23993
|
+
}
|
|
23994
|
+
let live = this.lives.get(route.stationSn);
|
|
23995
|
+
if (!live) {
|
|
23996
|
+
live = { cameraSn: route.cameraSn, source: this.newLiveSource(route, st, accountId, opts) };
|
|
23997
|
+
this.lives.set(route.stationSn, live);
|
|
23998
|
+
}
|
|
23999
|
+
return await use(live.source);
|
|
24000
|
+
}
|
|
24001
|
+
/** Refuse a camera while another camera on its station has consumers. */
|
|
24002
|
+
refuseIfWatched(route) {
|
|
24003
|
+
const current = this.lives.get(route.stationSn);
|
|
24004
|
+
if (current && current.cameraSn !== route.cameraSn && current.source.consumerCount > 0) {
|
|
24005
|
+
throw new StationStreamBusyError(route.stationSn, current.cameraSn);
|
|
24006
|
+
}
|
|
24007
|
+
}
|
|
24008
|
+
newLiveSource(route, st, accountId, hints) {
|
|
24009
|
+
let leased = false;
|
|
24010
|
+
const release = () => {
|
|
24011
|
+
if (!leased)
|
|
24012
|
+
return;
|
|
24013
|
+
leased = false;
|
|
24014
|
+
st.leases = Math.max(0, st.leases - 1);
|
|
24015
|
+
this.touch(route.stationSn, st);
|
|
24016
|
+
};
|
|
24017
|
+
const source = new SharedLiveSource({
|
|
24018
|
+
makeStream: () => new RtcLiveStream({
|
|
24019
|
+
session: st.session,
|
|
24020
|
+
seg: st.seg,
|
|
24021
|
+
stationSn: route.stationSn,
|
|
24022
|
+
channel: route.channel,
|
|
24023
|
+
accountId,
|
|
24024
|
+
logger: this.deps.logger
|
|
24025
|
+
}),
|
|
24026
|
+
label: `${route.stationSn}#${route.channel}`,
|
|
24027
|
+
logger: this.deps.logger,
|
|
24028
|
+
powered: hints.powered,
|
|
24029
|
+
preBufferSeconds: hints.preBufferSeconds,
|
|
24030
|
+
onActive: () => {
|
|
24031
|
+
if (leased)
|
|
24032
|
+
return;
|
|
24033
|
+
leased = true;
|
|
24034
|
+
st.leases++;
|
|
24035
|
+
if (st.idle)
|
|
24036
|
+
clearTimeout(st.idle);
|
|
24037
|
+
},
|
|
24038
|
+
onStopped: () => {
|
|
24039
|
+
release();
|
|
24040
|
+
if (this.lives.get(route.stationSn)?.source === source)
|
|
24041
|
+
this.lives.delete(route.stationSn);
|
|
24042
|
+
}
|
|
24043
|
+
});
|
|
24044
|
+
return source;
|
|
24045
|
+
}
|
|
23749
24046
|
/**
|
|
23750
24047
|
* Completes on the command ACK or a correlated control-result notification. The notification must
|
|
23751
24048
|
* match the request channel, parameter and segment and contain a structured payload. Receipt does
|
|
@@ -23815,7 +24112,7 @@ var RtcCommandRouter = class {
|
|
|
23815
24112
|
peer: { logger: this.deps.logger, iceTransportPolicy: route.iceTransportPolicy }
|
|
23816
24113
|
});
|
|
23817
24114
|
const ready = this.bringUp(sn, session);
|
|
23818
|
-
const st = { session, seg: new SegmentCounter(), ready, queue: Promise.resolve() };
|
|
24115
|
+
const st = { session, seg: new SegmentCounter(), ready, queue: Promise.resolve(), leases: 0 };
|
|
23819
24116
|
session.on("error", (e) => this.deps.onError?.(e));
|
|
23820
24117
|
session.on("close", () => {
|
|
23821
24118
|
if (this.sessions.get(sn) === st)
|
|
@@ -23869,6 +24166,8 @@ var RtcCommandRouter = class {
|
|
|
23869
24166
|
touch(sn, st) {
|
|
23870
24167
|
if (st.idle)
|
|
23871
24168
|
clearTimeout(st.idle);
|
|
24169
|
+
if (st.leases > 0)
|
|
24170
|
+
return;
|
|
23872
24171
|
st.idle = setTimeout(() => this.drop(sn, st), IDLE_CLOSE_MS);
|
|
23873
24172
|
st.idle.unref?.();
|
|
23874
24173
|
}
|
|
@@ -25498,7 +25797,7 @@ async function resolveLightEffect(mega, lightId) {
|
|
|
25498
25797
|
}
|
|
25499
25798
|
|
|
25500
25799
|
// dist/transport/push/push-client.js
|
|
25501
|
-
import { EventEmitter as
|
|
25800
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
25502
25801
|
import tls2 from "node:tls";
|
|
25503
25802
|
|
|
25504
25803
|
// dist/transport/push/proto.js
|
|
@@ -25753,7 +26052,7 @@ var MessageTag;
|
|
|
25753
26052
|
})(MessageTag || (MessageTag = {}));
|
|
25754
26053
|
|
|
25755
26054
|
// dist/transport/push/parser.js
|
|
25756
|
-
import { EventEmitter as
|
|
26055
|
+
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
25757
26056
|
var State;
|
|
25758
26057
|
(function(State2) {
|
|
25759
26058
|
State2[State2["VERSION_TAG_AND_SIZE"] = 0] = "VERSION_TAG_AND_SIZE";
|
|
@@ -25771,7 +26070,7 @@ var TAG_TO_TYPE = {
|
|
|
25771
26070
|
[MessageTag.DataMessageStanza]: "mcs_proto.DataMessageStanza",
|
|
25772
26071
|
[MessageTag.StreamErrorStanza]: "mcs_proto.StreamErrorStanza"
|
|
25773
26072
|
};
|
|
25774
|
-
var McsParser = class extends
|
|
26073
|
+
var McsParser = class extends EventEmitter11 {
|
|
25775
26074
|
data = Buffer.alloc(0);
|
|
25776
26075
|
state = State.VERSION_TAG_AND_SIZE;
|
|
25777
26076
|
messageTag = 0;
|
|
@@ -25949,7 +26248,7 @@ function normalizePushEvent(raw) {
|
|
|
25949
26248
|
raw
|
|
25950
26249
|
};
|
|
25951
26250
|
}
|
|
25952
|
-
var PushClient = class _PushClient extends
|
|
26251
|
+
var PushClient = class _PushClient extends EventEmitter12 {
|
|
25953
26252
|
creds;
|
|
25954
26253
|
logger;
|
|
25955
26254
|
/** Consecutive MCS login rejections tolerated (self-healing propagation) before surfacing an error. */
|
|
@@ -27136,6 +27435,8 @@ function tuyaDevIdFrom(raw) {
|
|
|
27136
27435
|
var DEFAULT_POLL_MS = 6e5;
|
|
27137
27436
|
var DEFAULT_PREWARM_TIERS = ["wired", "battery"];
|
|
27138
27437
|
var SESSION_RECYCLE_WAIT_MS = 5e3;
|
|
27438
|
+
var PUSH_RETRY_MIN_MS = 3e4;
|
|
27439
|
+
var PUSH_RETRY_MAX_MS = 6e5;
|
|
27139
27440
|
var RealtimeStartupSupersededError = class extends Error {
|
|
27140
27441
|
};
|
|
27141
27442
|
var RefreshWindowClosedError = class extends Error {
|
|
@@ -27151,7 +27452,7 @@ function readinessSnapshot(readiness, state = readiness.state) {
|
|
|
27151
27452
|
wiredP2p: Object.freeze({ ...readiness.wiredP2p })
|
|
27152
27453
|
});
|
|
27153
27454
|
}
|
|
27154
|
-
var EufyMega = class extends
|
|
27455
|
+
var EufyMega = class extends EventEmitter13 {
|
|
27155
27456
|
opts;
|
|
27156
27457
|
mega;
|
|
27157
27458
|
/**
|
|
@@ -27234,6 +27535,8 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27234
27535
|
mapStores = /* @__PURE__ */ new Map();
|
|
27235
27536
|
/** Re-armed after each cloud-param poll; cancelled by {@link disconnect}. */
|
|
27236
27537
|
pollTimer = new Timer();
|
|
27538
|
+
/** Armed while a failed push bring-up waits to be retried; cancelled by {@link disconnect}. */
|
|
27539
|
+
pushRetryTimer = new Timer();
|
|
27237
27540
|
constructor(opts) {
|
|
27238
27541
|
super();
|
|
27239
27542
|
this.opts = opts;
|
|
@@ -27278,6 +27581,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27278
27581
|
shard: () => this.mega.rtcShard,
|
|
27279
27582
|
country: opts.countryCode,
|
|
27280
27583
|
logger: opts.logger,
|
|
27584
|
+
ffmpegPath: opts.ffmpegPath,
|
|
27281
27585
|
onError: (e) => this.reportError(e)
|
|
27282
27586
|
});
|
|
27283
27587
|
this.mqtt = new MqttCommandRouter({
|
|
@@ -27989,7 +28293,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27989
28293
|
* current. With nothing retained the refusal stands.
|
|
27990
28294
|
*/
|
|
27991
28295
|
mediaProviderFor(sn) {
|
|
27992
|
-
const media = this.p2p.mediaProviderFor(sn);
|
|
28296
|
+
const media = this.rtcMediaFor(sn) ?? this.p2p.mediaProviderFor(sn);
|
|
27993
28297
|
const cache = this.storedImages;
|
|
27994
28298
|
if (!cache)
|
|
27995
28299
|
return media;
|
|
@@ -28049,24 +28353,48 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
28049
28353
|
return this.tuya.dispatchCommand(sn, cmd);
|
|
28050
28354
|
return this.mqtt.dispatchCommand(sn, cmd);
|
|
28051
28355
|
}
|
|
28356
|
+
const route = this.rtcRouteFor(sn);
|
|
28357
|
+
if (route) {
|
|
28358
|
+
if (route.attached && this.registry.serialForFrame(route.stationSn, cmd.channel) !== sn)
|
|
28359
|
+
return Promise.reject(new Error("RTC command requires an unambiguous attached-device channel"));
|
|
28360
|
+
return this.rtc.dispatchCommand(route, cmd);
|
|
28361
|
+
}
|
|
28362
|
+
return this.p2p.dispatchCommand(sn, cmd);
|
|
28363
|
+
}
|
|
28364
|
+
/**
|
|
28365
|
+
* The RTC route for a T9000 or T8N00 station or a device attached to one, decided by the station's model
|
|
28366
|
+
* classification; `undefined` for any other device. A T8N00 route carries its native `call` signalling. The admin id is the station's
|
|
28367
|
+
* `member.admin_user_id`, else the logged-in user.
|
|
28368
|
+
*/
|
|
28369
|
+
rtcRouteFor(sn) {
|
|
28052
28370
|
const devices = this.registry.list();
|
|
28053
28371
|
const target = devices.find((d) => d.sn === sn);
|
|
28054
28372
|
const stationSn = target?.stationSn || sn;
|
|
28055
28373
|
const station = devices.find((d) => d.sn === stationSn);
|
|
28056
|
-
const
|
|
28057
|
-
const deviceType = typeof
|
|
28374
|
+
const raw = station?.raw ?? {};
|
|
28375
|
+
const deviceType = typeof raw.device_type === "number" ? raw.device_type : void 0;
|
|
28058
28376
|
const family = { deviceType, model: station?.model };
|
|
28059
28377
|
const native = isNvrT8N00(family);
|
|
28060
|
-
if (target
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
28067
|
-
|
|
28068
|
-
|
|
28069
|
-
|
|
28378
|
+
if (!target || !station || !(isStation9000(family) || native))
|
|
28379
|
+
return void 0;
|
|
28380
|
+
const member = raw.member?.admin_user_id;
|
|
28381
|
+
const adminUserId = typeof member === "string" && member ? member : void 0;
|
|
28382
|
+
const base = { stationSn, adminUserId, attached: stationSn !== sn };
|
|
28383
|
+
return native ? { ...base, signalingMode: "call", iceTransportPolicy: "all" } : base;
|
|
28384
|
+
}
|
|
28385
|
+
/**
|
|
28386
|
+
* The RTC media provider for a camera attached to a T9000 whose `device_channel` resolves back to it on
|
|
28387
|
+
* the station; `undefined` otherwise, including a camera behind a T8N00, whose route carries commands only.
|
|
28388
|
+
*/
|
|
28389
|
+
rtcMediaFor(sn) {
|
|
28390
|
+
const route = this.rtcRouteFor(sn);
|
|
28391
|
+
if (!route?.attached || route.signalingMode === "call")
|
|
28392
|
+
return void 0;
|
|
28393
|
+
const raw = this.registry.list().find((d) => d.sn === sn)?.raw ?? {};
|
|
28394
|
+
const channel = typeof raw.device_channel === "number" ? raw.device_channel : void 0;
|
|
28395
|
+
if (channel === void 0 || this.registry.serialForFrame(route.stationSn, channel) !== sn)
|
|
28396
|
+
return void 0;
|
|
28397
|
+
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
28070
28398
|
}
|
|
28071
28399
|
/**
|
|
28072
28400
|
* List + classify devices across all houses (mega API). Each device is tagged with its API backend
|
|
@@ -28300,13 +28628,38 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
28300
28628
|
this.schedulePoll();
|
|
28301
28629
|
const failures = readiness.push.failed + readiness.mqtt.failed + readiness.wiredP2p.failed;
|
|
28302
28630
|
this.settleRealtimeGeneration(generation, failures || startupFailed ? "partial" : "ready");
|
|
28631
|
+
if (readiness.push.failed)
|
|
28632
|
+
this.retryPush(generation);
|
|
28303
28633
|
} catch (e) {
|
|
28304
28634
|
this.reportError(e);
|
|
28305
28635
|
readiness.push.failed += readiness.push.pending;
|
|
28306
28636
|
readiness.push.pending = 0;
|
|
28307
28637
|
this.settleRealtimeGeneration(generation, epoch === this.realtimeEpoch ? "partial" : "superseded");
|
|
28638
|
+
if (epoch === this.realtimeEpoch)
|
|
28639
|
+
this.retryPush(generation);
|
|
28308
28640
|
}
|
|
28309
28641
|
}
|
|
28642
|
+
/**
|
|
28643
|
+
* Bring push up again after the generation's startup attempt failed, backing off between attempts
|
|
28644
|
+
* until one connects, the generation is superseded, or it is aborted by {@link disconnect}.
|
|
28645
|
+
*
|
|
28646
|
+
* The MCS socket reconnects on its own only once it has connected. The settled
|
|
28647
|
+
* {@link RealtimeReadiness} is a startup snapshot and is not revised when a retry connects.
|
|
28648
|
+
*/
|
|
28649
|
+
retryPush(generation, delayMs = PUSH_RETRY_MIN_MS) {
|
|
28650
|
+
this.pushRetryTimer.arm(delayMs, () => {
|
|
28651
|
+
this.startPush(generation.abort.signal).then((client) => {
|
|
28652
|
+
if (generation.epoch !== this.realtimeEpoch)
|
|
28653
|
+
return client.close();
|
|
28654
|
+
this.pushClient = client;
|
|
28655
|
+
}, (error) => {
|
|
28656
|
+
if (generation.epoch !== this.realtimeEpoch)
|
|
28657
|
+
return;
|
|
28658
|
+
this.reportError(error);
|
|
28659
|
+
this.retryPush(generation, Math.min(delayMs * 2, PUSH_RETRY_MAX_MS));
|
|
28660
|
+
});
|
|
28661
|
+
});
|
|
28662
|
+
}
|
|
28310
28663
|
/** Resolve a generation with an immutable count snapshot; later transport completions are ignored. */
|
|
28311
28664
|
settleRealtimeGeneration(generation, state) {
|
|
28312
28665
|
if (generation.settled)
|
|
@@ -29162,6 +29515,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
29162
29515
|
*/
|
|
29163
29516
|
async teardownRealtime() {
|
|
29164
29517
|
this.pollTimer.cancel();
|
|
29518
|
+
this.pushRetryTimer.cancel();
|
|
29165
29519
|
this.lastStateAnnounced.clear();
|
|
29166
29520
|
await this.closeMqttTransports();
|
|
29167
29521
|
this.rtc.close();
|
|
@@ -29669,7 +30023,7 @@ var SolixClient = class _SolixClient {
|
|
|
29669
30023
|
};
|
|
29670
30024
|
|
|
29671
30025
|
// dist/transport/mqtt/solix-mqtt.js
|
|
29672
|
-
import { EventEmitter as
|
|
30026
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
29673
30027
|
var SOLIX_METER_FIELD_NAMES = {
|
|
29674
30028
|
168: "meterPowerL1",
|
|
29675
30029
|
// Anker Modbus: primary_phase_1_active_power
|
|
@@ -29847,7 +30201,7 @@ function addSolarbankScalars(frame, out) {
|
|
|
29847
30201
|
out.gridExportLimit = df.readUInt16LE(5);
|
|
29848
30202
|
}
|
|
29849
30203
|
}
|
|
29850
|
-
var SolixMqtt = class extends
|
|
30204
|
+
var SolixMqtt = class extends EventEmitter14 {
|
|
29851
30205
|
transport;
|
|
29852
30206
|
appName;
|
|
29853
30207
|
userId;
|