@mega-yfue/eufy-sdk 0.5.0-beta.26 → 0.5.0-beta.28
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 +29 -0
- package/dist/index.js +445 -70
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/types.d.ts +6 -0
- 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/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";
|
|
@@ -5877,8 +5877,9 @@ function isStationAlarmOutput(ctx) {
|
|
|
5877
5877
|
function isEvidencedStationAlarm(ctx) {
|
|
5878
5878
|
return isStationAlarmOutput(ctx) && HUB_ALARM_EVIDENCE.some((param) => ctx.paramIds.has(param));
|
|
5879
5879
|
}
|
|
5880
|
+
var S1_PRO_ALARM_MODELS = /* @__PURE__ */ new Set(["T8134"]);
|
|
5880
5881
|
function isEvidencedAttachedCamera(ctx) {
|
|
5881
|
-
return ctx.codec === "camera" && ctx.homeBaseAttached === true && hasReportedEasSwitch(ctx);
|
|
5882
|
+
return ctx.codec === "camera" && ctx.homeBaseAttached === true && hasReportedEasSwitch(ctx) && (!isStation9000({ deviceType: ctx.stationDeviceType }) || S1_PRO_ALARM_MODELS.has(ctx.model ?? ""));
|
|
5882
5883
|
}
|
|
5883
5884
|
function reportsParam(source, param) {
|
|
5884
5885
|
return source.paramIds?.has(param) === true || Object.hasOwn(source.params ?? {}, param);
|
|
@@ -5897,7 +5898,18 @@ function homeBaseAlarm(seconds, ctx) {
|
|
|
5897
5898
|
throw new Error("station-family HomeBase alarm output requires the acting account name");
|
|
5898
5899
|
return setPayload(SIREN_CMD.HOMEBASE_TONE, { time_out: seconds, user_name: ctx.accountName }, ctx, 0, STATION_CHANNEL);
|
|
5899
5900
|
}
|
|
5901
|
+
var PORTAL_MANUAL_ALARM_TYPE = 10;
|
|
5900
5902
|
function cameraAlarm(seconds, ctx) {
|
|
5903
|
+
if (isStation9000({ deviceType: ctx.stationDeviceType })) {
|
|
5904
|
+
if (!ctx.accountName)
|
|
5905
|
+
throw new Error("a camera alarm behind a HomeBase S1 Pro requires the acting account name");
|
|
5906
|
+
return setPayload(SIREN_CMD.CAMERA_TONE, {
|
|
5907
|
+
time_out: seconds,
|
|
5908
|
+
type: PORTAL_MANUAL_ALARM_TYPE,
|
|
5909
|
+
channel: ctx.channel,
|
|
5910
|
+
username: ctx.accountName
|
|
5911
|
+
}, ctx);
|
|
5912
|
+
}
|
|
5901
5913
|
return {
|
|
5902
5914
|
kind: "p2p-int-string",
|
|
5903
5915
|
cmd: SIREN_CMD.CAMERA_TONE,
|
|
@@ -14514,6 +14526,19 @@ function decodeMapFrame(frame, codec) {
|
|
|
14514
14526
|
return READERS.get(frame.channelId)?.(frame.payload, codec);
|
|
14515
14527
|
}
|
|
14516
14528
|
|
|
14529
|
+
// dist/transport/abortable.js
|
|
14530
|
+
function abortable(work, signal) {
|
|
14531
|
+
if (!signal)
|
|
14532
|
+
return work;
|
|
14533
|
+
signal.throwIfAborted();
|
|
14534
|
+
return Promise.race([
|
|
14535
|
+
work,
|
|
14536
|
+
new Promise((_resolve, reject) => {
|
|
14537
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
14538
|
+
})
|
|
14539
|
+
]);
|
|
14540
|
+
}
|
|
14541
|
+
|
|
14517
14542
|
// dist/transport/p2p/command-router.js
|
|
14518
14543
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
14519
14544
|
|
|
@@ -17860,6 +17885,18 @@ function startCodeBegin(buf, payloadOffset) {
|
|
|
17860
17885
|
return threeByte - 1;
|
|
17861
17886
|
return threeByte;
|
|
17862
17887
|
}
|
|
17888
|
+
function hasIdr(buf, codec) {
|
|
17889
|
+
for (const p of nalStarts(buf)) {
|
|
17890
|
+
const b = buf[p];
|
|
17891
|
+
if (codec === "h265") {
|
|
17892
|
+
const t = b >> 1 & 63;
|
|
17893
|
+
if (t === 19 || t === 20)
|
|
17894
|
+
return true;
|
|
17895
|
+
} else if ((b & 31) === 5)
|
|
17896
|
+
return true;
|
|
17897
|
+
}
|
|
17898
|
+
return false;
|
|
17899
|
+
}
|
|
17863
17900
|
var MAX_CODED_DIMENSION = 32768;
|
|
17864
17901
|
var H264_CHROMA_PROFILES = /* @__PURE__ */ new Set([100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135, 144]);
|
|
17865
17902
|
var CHROMA_SUBSAMPLING = {
|
|
@@ -19630,6 +19667,14 @@ var SharedLiveSource = class {
|
|
|
19630
19667
|
this.opts.onIdle?.();
|
|
19631
19668
|
}
|
|
19632
19669
|
};
|
|
19670
|
+
function attachUnlessAborted(source, signal) {
|
|
19671
|
+
const consumer = source.attach();
|
|
19672
|
+
if (signal?.aborted) {
|
|
19673
|
+
consumer.detach();
|
|
19674
|
+
signal.throwIfAborted();
|
|
19675
|
+
}
|
|
19676
|
+
return consumer;
|
|
19677
|
+
}
|
|
19633
19678
|
|
|
19634
19679
|
// dist/transport/p2p/session-manager.js
|
|
19635
19680
|
var SessionSupersededError = class extends Error {
|
|
@@ -20986,17 +21031,6 @@ function stationChannels(devices) {
|
|
|
20986
21031
|
|
|
20987
21032
|
// dist/transport/p2p/command-router.js
|
|
20988
21033
|
var DIRECT_CMD_SENDS = 5;
|
|
20989
|
-
function abortable(work, signal) {
|
|
20990
|
-
if (!signal)
|
|
20991
|
-
return work;
|
|
20992
|
-
signal.throwIfAborted();
|
|
20993
|
-
return Promise.race([
|
|
20994
|
-
work,
|
|
20995
|
-
new Promise((_resolve, reject) => {
|
|
20996
|
-
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
20997
|
-
})
|
|
20998
|
-
]);
|
|
20999
|
-
}
|
|
21000
21034
|
var LEVEL2_GRACE_MS = 25e3;
|
|
21001
21035
|
var LEVEL2_SETTLE_MS = 8e3;
|
|
21002
21036
|
var P2P_STATION_WAITS = {
|
|
@@ -21488,11 +21522,11 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21488
21522
|
},
|
|
21489
21523
|
live: async (opts) => {
|
|
21490
21524
|
const source = await this.sharedLiveSourceFor(sn, opts, true);
|
|
21491
|
-
return
|
|
21525
|
+
return attachUnlessAborted(source, opts?.signal);
|
|
21492
21526
|
},
|
|
21493
21527
|
openReadable: async (opts) => {
|
|
21494
21528
|
const source = await this.sharedLiveSourceFor(sn, opts ?? {}, true);
|
|
21495
|
-
return openReadableFromConsumer(
|
|
21529
|
+
return openReadableFromConsumer(attachUnlessAborted(source, opts?.signal), opts);
|
|
21496
21530
|
},
|
|
21497
21531
|
recordFragments: (opts) => this.recordFragments(sn, opts),
|
|
21498
21532
|
talkback: (opts) => this.openTalkback(sn, opts),
|
|
@@ -21860,22 +21894,6 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21860
21894
|
* A still with no live sibling re-asserts, so a tile refreshing on a quiet station is
|
|
21861
21895
|
* unaffected.
|
|
21862
21896
|
*/
|
|
21863
|
-
/**
|
|
21864
|
-
* Attach a consumer, unless the caller has already abandoned the call.
|
|
21865
|
-
*
|
|
21866
|
-
* The acquisition it just waited through can outlast the caller's interest, and a consumer attached for
|
|
21867
|
-
* somebody who has gone keeps the pull warm for nobody. Detaching immediately gives the pull back, which
|
|
21868
|
-
* lets it linger and fall away if this was the only thing holding it, and leaves it untouched if it was
|
|
21869
|
-
* not.
|
|
21870
|
-
*/
|
|
21871
|
-
attachUnlessAborted(source, signal) {
|
|
21872
|
-
const consumer = source.attach();
|
|
21873
|
-
if (signal?.aborted) {
|
|
21874
|
-
consumer.detach();
|
|
21875
|
-
signal.throwIfAborted();
|
|
21876
|
-
}
|
|
21877
|
-
return consumer;
|
|
21878
|
-
}
|
|
21879
21897
|
/**
|
|
21880
21898
|
* Dispose one cached live source and forget it, so the next acquisition builds a fresh one. Its claim
|
|
21881
21899
|
* on a session goes with it, and a media session opened for this source alone is closed: nothing else
|
|
@@ -22645,20 +22663,21 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
22645
22663
|
}
|
|
22646
22664
|
};
|
|
22647
22665
|
|
|
22648
|
-
// dist/transport/rtc/
|
|
22649
|
-
import { EventEmitter as EventEmitter9 } from "node:events";
|
|
22650
|
-
|
|
22651
|
-
// dist/transport/rtc/peer.js
|
|
22666
|
+
// dist/transport/rtc/live.js
|
|
22652
22667
|
import { EventEmitter as EventEmitter7 } from "node:events";
|
|
22653
22668
|
|
|
22654
22669
|
// dist/transport/rtc/portal-packet.js
|
|
22655
22670
|
var MAGIC2 = Buffer.from("XZYH", "ascii");
|
|
22656
22671
|
var PORTAL_HEADER_LENGTH = 16;
|
|
22672
|
+
var PORTAL_CMD_SET_PAYLOAD = 1350;
|
|
22673
|
+
var PORTAL_STATION_CHANNEL = 255;
|
|
22657
22674
|
var PortalLinkType = {
|
|
22658
22675
|
/** Command channel: requests out, acknowledgements back. */
|
|
22659
22676
|
COMMAND: 1,
|
|
22660
22677
|
/** Station-originated frames: pushes, notify payloads, control results. */
|
|
22661
|
-
NOTIFY: 3
|
|
22678
|
+
NOTIFY: 3,
|
|
22679
|
+
/** Live video. */
|
|
22680
|
+
LIVE: 5
|
|
22662
22681
|
};
|
|
22663
22682
|
function encodeLe(value, width) {
|
|
22664
22683
|
const buf = Buffer.alloc(width);
|
|
@@ -22750,6 +22769,159 @@ var SegmentCounter = class {
|
|
|
22750
22769
|
}
|
|
22751
22770
|
};
|
|
22752
22771
|
|
|
22772
|
+
// dist/transport/rtc/live.js
|
|
22773
|
+
var LIVE_START = 1003;
|
|
22774
|
+
var LIVE_STOP = 1004;
|
|
22775
|
+
var LIVE_MEDIA = 1300;
|
|
22776
|
+
var LIVE_MEDIA_CHANNEL = 101;
|
|
22777
|
+
var LIVE_STREAM_ID = 1;
|
|
22778
|
+
var KEEPALIVE_MS = 29e3;
|
|
22779
|
+
var KEEPALIVE = Buffer.from("0009000010000000000000006300000000000000585a5948730400000000000000000002", "hex");
|
|
22780
|
+
var MEDIA_HEADER_LENGTH = 22;
|
|
22781
|
+
var STREAM_TYPE_HEVC = 1;
|
|
22782
|
+
function parseMediaBody(body) {
|
|
22783
|
+
if (body.length < MEDIA_HEADER_LENGTH || body[5] !== STREAM_TYPE_HEVC)
|
|
22784
|
+
return void 0;
|
|
22785
|
+
const length = body.readUInt32LE(0);
|
|
22786
|
+
if (body.length < MEDIA_HEADER_LENGTH + length)
|
|
22787
|
+
return void 0;
|
|
22788
|
+
return {
|
|
22789
|
+
data: body.subarray(MEDIA_HEADER_LENGTH, MEDIA_HEADER_LENGTH + length),
|
|
22790
|
+
width: body.readUInt16LE(10),
|
|
22791
|
+
height: body.readUInt16LE(12)
|
|
22792
|
+
};
|
|
22793
|
+
}
|
|
22794
|
+
var RtcLiveStream = class extends EventEmitter7 {
|
|
22795
|
+
opts;
|
|
22796
|
+
running = false;
|
|
22797
|
+
startSegment = -1;
|
|
22798
|
+
keepalive;
|
|
22799
|
+
width = 0;
|
|
22800
|
+
height = 0;
|
|
22801
|
+
onMedia = (frame) => this.onMediaFrame(frame);
|
|
22802
|
+
onCommand = (frame, linkType) => this.onCommandFrame(frame, linkType);
|
|
22803
|
+
onClose = () => this.fail(new Error(`rtc live ${this.tag}: session closed`));
|
|
22804
|
+
constructor(opts) {
|
|
22805
|
+
super();
|
|
22806
|
+
this.opts = opts;
|
|
22807
|
+
}
|
|
22808
|
+
get tag() {
|
|
22809
|
+
return `${this.opts.stationSn}#${this.opts.channel}`;
|
|
22810
|
+
}
|
|
22811
|
+
start() {
|
|
22812
|
+
if (this.running)
|
|
22813
|
+
return this;
|
|
22814
|
+
const { session, seg, accountId } = this.opts;
|
|
22815
|
+
this.running = true;
|
|
22816
|
+
session.on("mediaData", this.onMedia);
|
|
22817
|
+
session.on("commandData", this.onCommand);
|
|
22818
|
+
session.on("close", this.onClose);
|
|
22819
|
+
this.startSegment = seg.next();
|
|
22820
|
+
const sent = session.sendCommand(buildPortalPacket({
|
|
22821
|
+
commandId: PORTAL_CMD_SET_PAYLOAD,
|
|
22822
|
+
channel: PORTAL_STATION_CHANNEL,
|
|
22823
|
+
segment: this.startSegment,
|
|
22824
|
+
isResponse: LIVE_STREAM_ID,
|
|
22825
|
+
payload: {
|
|
22826
|
+
account_id: accountId,
|
|
22827
|
+
cmd: LIVE_START,
|
|
22828
|
+
payload: {
|
|
22829
|
+
ClientOS: "WEB",
|
|
22830
|
+
entrytype: 0,
|
|
22831
|
+
camera_type: 0,
|
|
22832
|
+
key: "",
|
|
22833
|
+
msg_id: 116,
|
|
22834
|
+
audio_chn: -1,
|
|
22835
|
+
streamtype: 2,
|
|
22836
|
+
stitch_mode: 1,
|
|
22837
|
+
chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
|
|
22838
|
+
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
22839
|
+
station_video_type: 6,
|
|
22840
|
+
play_id: 1
|
|
22841
|
+
}
|
|
22842
|
+
}
|
|
22843
|
+
}));
|
|
22844
|
+
if (!sent) {
|
|
22845
|
+
queueMicrotask(() => this.fail(new Error(`rtc live ${this.tag}: command channel not open`)));
|
|
22846
|
+
return this;
|
|
22847
|
+
}
|
|
22848
|
+
this.keepalive = setInterval(() => {
|
|
22849
|
+
if (!session.sendRaw(KEEPALIVE))
|
|
22850
|
+
this.opts.logger?.warn?.(`[rtc] ${this.tag} keepalive not sent`);
|
|
22851
|
+
}, KEEPALIVE_MS);
|
|
22852
|
+
this.keepalive.unref?.();
|
|
22853
|
+
this.opts.logger?.debug?.(`[rtc] ${this.tag} live start sent`);
|
|
22854
|
+
return this;
|
|
22855
|
+
}
|
|
22856
|
+
stop() {
|
|
22857
|
+
if (!this.running)
|
|
22858
|
+
return;
|
|
22859
|
+
this.teardown();
|
|
22860
|
+
const { session, seg, accountId } = this.opts;
|
|
22861
|
+
session.sendCommand(buildPortalPacket({
|
|
22862
|
+
commandId: PORTAL_CMD_SET_PAYLOAD,
|
|
22863
|
+
channel: PORTAL_STATION_CHANNEL,
|
|
22864
|
+
segment: seg.next(),
|
|
22865
|
+
isResponse: LIVE_STREAM_ID,
|
|
22866
|
+
payload: { account_id: accountId, cmd: LIVE_STOP, payload: {} }
|
|
22867
|
+
}));
|
|
22868
|
+
this.emit("stop");
|
|
22869
|
+
}
|
|
22870
|
+
teardown() {
|
|
22871
|
+
this.running = false;
|
|
22872
|
+
if (this.keepalive)
|
|
22873
|
+
clearInterval(this.keepalive);
|
|
22874
|
+
this.keepalive = void 0;
|
|
22875
|
+
const { session } = this.opts;
|
|
22876
|
+
session.off("mediaData", this.onMedia);
|
|
22877
|
+
session.off("commandData", this.onCommand);
|
|
22878
|
+
session.off("close", this.onClose);
|
|
22879
|
+
}
|
|
22880
|
+
fail(err) {
|
|
22881
|
+
if (!this.running)
|
|
22882
|
+
return;
|
|
22883
|
+
this.teardown();
|
|
22884
|
+
this.emit("error", err);
|
|
22885
|
+
}
|
|
22886
|
+
/** The start's ACK: a non-zero result code ends the pull. */
|
|
22887
|
+
onCommandFrame(frame, linkType) {
|
|
22888
|
+
const p = parsePortalPacket(frame, linkType);
|
|
22889
|
+
if (!p?.isResponse || p.segment !== this.startSegment || p.commandId !== PORTAL_CMD_SET_PAYLOAD)
|
|
22890
|
+
return;
|
|
22891
|
+
if (p.errCode !== 0)
|
|
22892
|
+
this.fail(new Error(`rtc live ${this.tag}: start refused (err ${p.errCode})`));
|
|
22893
|
+
}
|
|
22894
|
+
onMediaFrame(frame) {
|
|
22895
|
+
const h = parsePortalHeader(frame);
|
|
22896
|
+
if (!h || h.commandId !== LIVE_MEDIA || h.channel !== LIVE_MEDIA_CHANNEL)
|
|
22897
|
+
return;
|
|
22898
|
+
if (h.paramLength > frame.length - PORTAL_HEADER_LENGTH)
|
|
22899
|
+
return;
|
|
22900
|
+
const media = parseMediaBody(frame.subarray(PORTAL_HEADER_LENGTH, PORTAL_HEADER_LENGTH + h.paramLength));
|
|
22901
|
+
if (!media)
|
|
22902
|
+
return;
|
|
22903
|
+
const { data, width, height } = media;
|
|
22904
|
+
if (width && height) {
|
|
22905
|
+
this.width = width;
|
|
22906
|
+
this.height = height;
|
|
22907
|
+
}
|
|
22908
|
+
const out = {
|
|
22909
|
+
codec: "h265",
|
|
22910
|
+
data,
|
|
22911
|
+
keyframe: hasIdr(data, "h265"),
|
|
22912
|
+
width: this.width,
|
|
22913
|
+
height: this.height
|
|
22914
|
+
};
|
|
22915
|
+
this.emit("video", out);
|
|
22916
|
+
}
|
|
22917
|
+
};
|
|
22918
|
+
|
|
22919
|
+
// dist/transport/rtc/session.js
|
|
22920
|
+
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
22921
|
+
|
|
22922
|
+
// dist/transport/rtc/peer.js
|
|
22923
|
+
import { EventEmitter as EventEmitter8 } from "node:events";
|
|
22924
|
+
|
|
22753
22925
|
// dist/transport/rtc/ptcs-framer.js
|
|
22754
22926
|
var MAGIC3 = Buffer.from("PTCS", "ascii");
|
|
22755
22927
|
var PTCS_HEADER_LENGTH = 28;
|
|
@@ -22759,10 +22931,15 @@ var FLAG_LAST = 1024;
|
|
|
22759
22931
|
var LENGTH_MASK = 1023;
|
|
22760
22932
|
var PtcsChannel = {
|
|
22761
22933
|
COMMAND: 0,
|
|
22762
|
-
NOTIFY: 2
|
|
22934
|
+
NOTIFY: 2,
|
|
22935
|
+
LIVE: 5
|
|
22763
22936
|
};
|
|
22764
22937
|
function linkTypeForChannel(channel) {
|
|
22765
|
-
|
|
22938
|
+
if (channel === PtcsChannel.NOTIFY)
|
|
22939
|
+
return PortalLinkType.NOTIFY;
|
|
22940
|
+
if (channel === PtcsChannel.LIVE)
|
|
22941
|
+
return PortalLinkType.LIVE;
|
|
22942
|
+
return PortalLinkType.COMMAND;
|
|
22766
22943
|
}
|
|
22767
22944
|
var PTCS_STALE_MS = 15e3;
|
|
22768
22945
|
function parsePtcsHeader(packet) {
|
|
@@ -23028,7 +23205,7 @@ async function loadNativePeerFactory() {
|
|
|
23028
23205
|
return (name, config) => new ndc.PeerConnection(name, config);
|
|
23029
23206
|
}
|
|
23030
23207
|
var ANSWER_TIMEOUT_MS = 15e3;
|
|
23031
|
-
var RtcPeer = class extends
|
|
23208
|
+
var RtcPeer = class extends EventEmitter8 {
|
|
23032
23209
|
opts;
|
|
23033
23210
|
pc;
|
|
23034
23211
|
channels = /* @__PURE__ */ new Map();
|
|
@@ -23145,6 +23322,16 @@ var RtcPeer = class extends EventEmitter7 {
|
|
|
23145
23322
|
}
|
|
23146
23323
|
this.addNow(candidate);
|
|
23147
23324
|
}
|
|
23325
|
+
/**
|
|
23326
|
+
* Send bytes on the command channel as they are, without PTCS framing; false when it isn't open. The
|
|
23327
|
+
* portal's data-channel keepalive rides the wire unframed.
|
|
23328
|
+
*/
|
|
23329
|
+
sendRaw(bytes2) {
|
|
23330
|
+
const dc = this.channels.get(COMMAND_CHANNEL);
|
|
23331
|
+
if (!dc?.isOpen())
|
|
23332
|
+
return false;
|
|
23333
|
+
return dc.sendMessageBinary(bytes2);
|
|
23334
|
+
}
|
|
23148
23335
|
/**
|
|
23149
23336
|
* Send one portal packet on the command channel, through the framer. False when the channel is not
|
|
23150
23337
|
* usable or the native send refused a wire packet, so a dropped frame does not read as success.
|
|
@@ -23251,7 +23438,7 @@ var RtcPeer = class extends EventEmitter7 {
|
|
|
23251
23438
|
};
|
|
23252
23439
|
|
|
23253
23440
|
// dist/transport/rtc/signaling.js
|
|
23254
|
-
import { EventEmitter as
|
|
23441
|
+
import { EventEmitter as EventEmitter9 } from "node:events";
|
|
23255
23442
|
import { createHmac as createHmac3, randomUUID as randomUUID2 } from "node:crypto";
|
|
23256
23443
|
var RTC_WS_PATH = "/v1/rtc/ws/join?reqtype=nvr";
|
|
23257
23444
|
var RTC_SIGN_PATH = "/v1/smart/nvr/ws/sign";
|
|
@@ -23267,7 +23454,7 @@ var defaultSocket = (url, init) => new WebSocket(url, {
|
|
|
23267
23454
|
protocols: init.protocols,
|
|
23268
23455
|
headers: { Origin: init.origin }
|
|
23269
23456
|
});
|
|
23270
|
-
var RtcSignalingClient = class extends
|
|
23457
|
+
var RtcSignalingClient = class extends EventEmitter9 {
|
|
23271
23458
|
opts;
|
|
23272
23459
|
ws;
|
|
23273
23460
|
sign;
|
|
@@ -23492,7 +23679,7 @@ var RtcSignalingClient = class extends EventEmitter8 {
|
|
|
23492
23679
|
|
|
23493
23680
|
// dist/transport/rtc/session.js
|
|
23494
23681
|
var AUTH_TIMEOUT_MS = 15e3;
|
|
23495
|
-
var RtcSession = class extends
|
|
23682
|
+
var RtcSession = class extends EventEmitter10 {
|
|
23496
23683
|
opts;
|
|
23497
23684
|
signaling;
|
|
23498
23685
|
peer;
|
|
@@ -23544,7 +23731,12 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23544
23731
|
}
|
|
23545
23732
|
});
|
|
23546
23733
|
this.peer.on("error", (e) => this.emit("error", e));
|
|
23547
|
-
this.peer.on("data", (frame, linkType) =>
|
|
23734
|
+
this.peer.on("data", (frame, linkType) => {
|
|
23735
|
+
if (linkType === PortalLinkType.LIVE)
|
|
23736
|
+
this.emit("mediaData", frame);
|
|
23737
|
+
else
|
|
23738
|
+
this.emit("commandData", frame, linkType);
|
|
23739
|
+
});
|
|
23548
23740
|
}
|
|
23549
23741
|
get isConnected() {
|
|
23550
23742
|
return this.connected;
|
|
@@ -23569,6 +23761,10 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23569
23761
|
sendCommand(portalPacket) {
|
|
23570
23762
|
return this.peer.sendCommand(portalPacket);
|
|
23571
23763
|
}
|
|
23764
|
+
/** Send unframed bytes on the command channel; false when it isn't open. */
|
|
23765
|
+
sendRaw(bytes2) {
|
|
23766
|
+
return this.peer.sendRaw(bytes2);
|
|
23767
|
+
}
|
|
23572
23768
|
/**
|
|
23573
23769
|
* Hang up and tear both sides down; `close` fires once, here if nothing announced it before. A
|
|
23574
23770
|
* `connect()` still waiting for the signalling auth is rejected and its timer stopped.
|
|
@@ -23715,16 +23911,21 @@ var RtcSession = class extends EventEmitter9 {
|
|
|
23715
23911
|
};
|
|
23716
23912
|
|
|
23717
23913
|
// dist/transport/rtc/command-router.js
|
|
23718
|
-
var PORTAL_CMD_SET_PAYLOAD = 1350;
|
|
23719
23914
|
var PORTAL_CMD_CONTROL_PAYLOAD = 1700;
|
|
23720
23915
|
var PORTAL_CMD_NOTIFY_PAYLOAD = 1351;
|
|
23721
|
-
var PORTAL_STATION_CHANNEL = 255;
|
|
23722
23916
|
var ACK_TIMEOUT_MS = 8e3;
|
|
23723
23917
|
var CONNECT_TIMEOUT_MS2 = 12e3;
|
|
23724
23918
|
var IDLE_CLOSE_MS = 6e4;
|
|
23919
|
+
var StationStreamBusyError = class extends Error {
|
|
23920
|
+
constructor(stationSn, servingSn) {
|
|
23921
|
+
super(`rtc live: ${stationSn} is serving ${servingSn}; the station streams one camera at a time`);
|
|
23922
|
+
this.name = "StationStreamBusyError";
|
|
23923
|
+
}
|
|
23924
|
+
};
|
|
23725
23925
|
var RtcCommandRouter = class {
|
|
23726
23926
|
deps;
|
|
23727
23927
|
sessions = /* @__PURE__ */ new Map();
|
|
23928
|
+
lives = /* @__PURE__ */ new Map();
|
|
23728
23929
|
constructor(deps) {
|
|
23729
23930
|
this.deps = deps;
|
|
23730
23931
|
}
|
|
@@ -23748,11 +23949,112 @@ var RtcCommandRouter = class {
|
|
|
23748
23949
|
st.queue = run.catch(() => void 0);
|
|
23749
23950
|
return run;
|
|
23750
23951
|
}
|
|
23751
|
-
/**
|
|
23952
|
+
/**
|
|
23953
|
+
* The media of a camera on a T9000: live video, a readable feed and a live still over the station's
|
|
23954
|
+
* session. Recording has no wire here and is refused; the optional members it has no wire for are absent.
|
|
23955
|
+
*/
|
|
23956
|
+
mediaProviderFor(route) {
|
|
23957
|
+
return {
|
|
23958
|
+
live: (opts) => this.withLiveSource(route, opts ?? {}, (source) => attachUnlessAborted(source, opts?.signal)),
|
|
23959
|
+
openReadable: (opts) => this.withLiveSource(route, opts ?? {}, (source) => openReadableFromConsumer(attachUnlessAborted(source, opts?.signal), opts)),
|
|
23960
|
+
snapshotLive: (opts) => this.withLiveSource(route, opts ?? {}, (source) => captureSnapshotFromShared(source, { ...opts, logger: this.deps.logger, ffmpegPath: this.deps.ffmpegPath })).catch((e) => {
|
|
23961
|
+
if (!(e instanceof StationStreamBusyError))
|
|
23962
|
+
throw e;
|
|
23963
|
+
throw new LiveSnapshotUnavailableError("source-failed", e.message, { cause: e });
|
|
23964
|
+
}),
|
|
23965
|
+
record: async () => {
|
|
23966
|
+
throw new Error(`record is not available for ${route.cameraSn} over the T9000 control channel`);
|
|
23967
|
+
}
|
|
23968
|
+
};
|
|
23969
|
+
}
|
|
23970
|
+
/**
|
|
23971
|
+
* Tear down every station session (logout / shutdown). Each live source is disposed before its session
|
|
23972
|
+
* closes, so its consumers end cleanly and its stop still rides the session.
|
|
23973
|
+
*/
|
|
23752
23974
|
close() {
|
|
23975
|
+
for (const [sn, live] of this.lives) {
|
|
23976
|
+
this.lives.delete(sn);
|
|
23977
|
+
live.source.dispose();
|
|
23978
|
+
}
|
|
23753
23979
|
for (const [sn, st] of this.sessions)
|
|
23754
23980
|
this.drop(sn, st);
|
|
23755
23981
|
}
|
|
23982
|
+
/**
|
|
23983
|
+
* Hand `use` the station's live source for this camera, in the same turn the source is created or reused,
|
|
23984
|
+
* so a consumer it attaches is counted before any other caller looks.
|
|
23985
|
+
*
|
|
23986
|
+
* The station streams one camera at a time. Another camera whose source has consumers refuses this one,
|
|
23987
|
+
* before the session is awaited and again after; one with none (never attached or lingering)
|
|
23988
|
+
* holds nothing a viewer needs, so it is disposed and the slot taken. The first caller's
|
|
23989
|
+
* {@link SharedSourceHints} configure a new source.
|
|
23990
|
+
*/
|
|
23991
|
+
async withLiveSource(route, opts, use) {
|
|
23992
|
+
opts.signal?.throwIfAborted();
|
|
23993
|
+
this.refuseIfWatched(route);
|
|
23994
|
+
const identity = this.deps.identity();
|
|
23995
|
+
if (!identity)
|
|
23996
|
+
throw new Error(`rtc: not logged in, cannot open ${route.stationSn}`);
|
|
23997
|
+
const accountId = route.adminUserId || identity.userId;
|
|
23998
|
+
const st = await abortable(this.stationSession(route, accountId, identity), opts.signal);
|
|
23999
|
+
this.refuseIfWatched(route);
|
|
24000
|
+
const current = this.lives.get(route.stationSn);
|
|
24001
|
+
if (current && current.cameraSn !== route.cameraSn) {
|
|
24002
|
+
this.deps.logger?.debug?.(`[rtc] ${route.stationSn} releasing the unwatched source of ${current.cameraSn} for ${route.cameraSn}`);
|
|
24003
|
+
this.lives.delete(route.stationSn);
|
|
24004
|
+
current.source.dispose();
|
|
24005
|
+
}
|
|
24006
|
+
let live = this.lives.get(route.stationSn);
|
|
24007
|
+
if (!live) {
|
|
24008
|
+
live = { cameraSn: route.cameraSn, source: this.newLiveSource(route, st, accountId, opts) };
|
|
24009
|
+
this.lives.set(route.stationSn, live);
|
|
24010
|
+
}
|
|
24011
|
+
return await use(live.source);
|
|
24012
|
+
}
|
|
24013
|
+
/** Refuse a camera while another camera on its station has consumers. */
|
|
24014
|
+
refuseIfWatched(route) {
|
|
24015
|
+
const current = this.lives.get(route.stationSn);
|
|
24016
|
+
if (current && current.cameraSn !== route.cameraSn && current.source.consumerCount > 0) {
|
|
24017
|
+
throw new StationStreamBusyError(route.stationSn, current.cameraSn);
|
|
24018
|
+
}
|
|
24019
|
+
}
|
|
24020
|
+
newLiveSource(route, st, accountId, hints) {
|
|
24021
|
+
let leased = false;
|
|
24022
|
+
const release = () => {
|
|
24023
|
+
if (!leased)
|
|
24024
|
+
return;
|
|
24025
|
+
leased = false;
|
|
24026
|
+
st.leases = Math.max(0, st.leases - 1);
|
|
24027
|
+
this.touch(route.stationSn, st);
|
|
24028
|
+
};
|
|
24029
|
+
const source = new SharedLiveSource({
|
|
24030
|
+
makeStream: () => new RtcLiveStream({
|
|
24031
|
+
session: st.session,
|
|
24032
|
+
seg: st.seg,
|
|
24033
|
+
stationSn: route.stationSn,
|
|
24034
|
+
channel: route.channel,
|
|
24035
|
+
accountId,
|
|
24036
|
+
logger: this.deps.logger
|
|
24037
|
+
}),
|
|
24038
|
+
label: `${route.stationSn}#${route.channel}`,
|
|
24039
|
+
logger: this.deps.logger,
|
|
24040
|
+
powered: hints.powered,
|
|
24041
|
+
preBufferSeconds: hints.preBufferSeconds,
|
|
24042
|
+
onActive: () => {
|
|
24043
|
+
if (leased)
|
|
24044
|
+
return;
|
|
24045
|
+
leased = true;
|
|
24046
|
+
st.leases++;
|
|
24047
|
+
if (st.idle)
|
|
24048
|
+
clearTimeout(st.idle);
|
|
24049
|
+
},
|
|
24050
|
+
onStopped: () => {
|
|
24051
|
+
release();
|
|
24052
|
+
if (this.lives.get(route.stationSn)?.source === source)
|
|
24053
|
+
this.lives.delete(route.stationSn);
|
|
24054
|
+
}
|
|
24055
|
+
});
|
|
24056
|
+
return source;
|
|
24057
|
+
}
|
|
23756
24058
|
/**
|
|
23757
24059
|
* Completes on the command ACK or a correlated control-result notification. The notification must
|
|
23758
24060
|
* match the request channel, parameter and segment and contain a structured payload. Receipt does
|
|
@@ -23822,7 +24124,7 @@ var RtcCommandRouter = class {
|
|
|
23822
24124
|
peer: { logger: this.deps.logger, iceTransportPolicy: route.iceTransportPolicy }
|
|
23823
24125
|
});
|
|
23824
24126
|
const ready = this.bringUp(sn, session);
|
|
23825
|
-
const st = { session, seg: new SegmentCounter(), ready, queue: Promise.resolve() };
|
|
24127
|
+
const st = { session, seg: new SegmentCounter(), ready, queue: Promise.resolve(), leases: 0 };
|
|
23826
24128
|
session.on("error", (e) => this.deps.onError?.(e));
|
|
23827
24129
|
session.on("close", () => {
|
|
23828
24130
|
if (this.sessions.get(sn) === st)
|
|
@@ -23876,6 +24178,8 @@ var RtcCommandRouter = class {
|
|
|
23876
24178
|
touch(sn, st) {
|
|
23877
24179
|
if (st.idle)
|
|
23878
24180
|
clearTimeout(st.idle);
|
|
24181
|
+
if (st.leases > 0)
|
|
24182
|
+
return;
|
|
23879
24183
|
st.idle = setTimeout(() => this.drop(sn, st), IDLE_CLOSE_MS);
|
|
23880
24184
|
st.idle.unref?.();
|
|
23881
24185
|
}
|
|
@@ -25505,7 +25809,7 @@ async function resolveLightEffect(mega, lightId) {
|
|
|
25505
25809
|
}
|
|
25506
25810
|
|
|
25507
25811
|
// dist/transport/push/push-client.js
|
|
25508
|
-
import { EventEmitter as
|
|
25812
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
25509
25813
|
import tls2 from "node:tls";
|
|
25510
25814
|
|
|
25511
25815
|
// dist/transport/push/proto.js
|
|
@@ -25760,7 +26064,7 @@ var MessageTag;
|
|
|
25760
26064
|
})(MessageTag || (MessageTag = {}));
|
|
25761
26065
|
|
|
25762
26066
|
// dist/transport/push/parser.js
|
|
25763
|
-
import { EventEmitter as
|
|
26067
|
+
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
25764
26068
|
var State;
|
|
25765
26069
|
(function(State2) {
|
|
25766
26070
|
State2[State2["VERSION_TAG_AND_SIZE"] = 0] = "VERSION_TAG_AND_SIZE";
|
|
@@ -25778,7 +26082,7 @@ var TAG_TO_TYPE = {
|
|
|
25778
26082
|
[MessageTag.DataMessageStanza]: "mcs_proto.DataMessageStanza",
|
|
25779
26083
|
[MessageTag.StreamErrorStanza]: "mcs_proto.StreamErrorStanza"
|
|
25780
26084
|
};
|
|
25781
|
-
var McsParser = class extends
|
|
26085
|
+
var McsParser = class extends EventEmitter11 {
|
|
25782
26086
|
data = Buffer.alloc(0);
|
|
25783
26087
|
state = State.VERSION_TAG_AND_SIZE;
|
|
25784
26088
|
messageTag = 0;
|
|
@@ -25956,7 +26260,7 @@ function normalizePushEvent(raw) {
|
|
|
25956
26260
|
raw
|
|
25957
26261
|
};
|
|
25958
26262
|
}
|
|
25959
|
-
var PushClient = class _PushClient extends
|
|
26263
|
+
var PushClient = class _PushClient extends EventEmitter12 {
|
|
25960
26264
|
creds;
|
|
25961
26265
|
logger;
|
|
25962
26266
|
/** Consecutive MCS login rejections tolerated (self-healing propagation) before surfacing an error. */
|
|
@@ -27143,6 +27447,8 @@ function tuyaDevIdFrom(raw) {
|
|
|
27143
27447
|
var DEFAULT_POLL_MS = 6e5;
|
|
27144
27448
|
var DEFAULT_PREWARM_TIERS = ["wired", "battery"];
|
|
27145
27449
|
var SESSION_RECYCLE_WAIT_MS = 5e3;
|
|
27450
|
+
var PUSH_RETRY_MIN_MS = 3e4;
|
|
27451
|
+
var PUSH_RETRY_MAX_MS = 6e5;
|
|
27146
27452
|
var RealtimeStartupSupersededError = class extends Error {
|
|
27147
27453
|
};
|
|
27148
27454
|
var RefreshWindowClosedError = class extends Error {
|
|
@@ -27158,7 +27464,7 @@ function readinessSnapshot(readiness, state = readiness.state) {
|
|
|
27158
27464
|
wiredP2p: Object.freeze({ ...readiness.wiredP2p })
|
|
27159
27465
|
});
|
|
27160
27466
|
}
|
|
27161
|
-
var EufyMega = class extends
|
|
27467
|
+
var EufyMega = class extends EventEmitter13 {
|
|
27162
27468
|
opts;
|
|
27163
27469
|
mega;
|
|
27164
27470
|
/**
|
|
@@ -27241,6 +27547,8 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27241
27547
|
mapStores = /* @__PURE__ */ new Map();
|
|
27242
27548
|
/** Re-armed after each cloud-param poll; cancelled by {@link disconnect}. */
|
|
27243
27549
|
pollTimer = new Timer();
|
|
27550
|
+
/** Armed while a failed push bring-up waits to be retried; cancelled by {@link disconnect}. */
|
|
27551
|
+
pushRetryTimer = new Timer();
|
|
27244
27552
|
constructor(opts) {
|
|
27245
27553
|
super();
|
|
27246
27554
|
this.opts = opts;
|
|
@@ -27285,6 +27593,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27285
27593
|
shard: () => this.mega.rtcShard,
|
|
27286
27594
|
country: opts.countryCode,
|
|
27287
27595
|
logger: opts.logger,
|
|
27596
|
+
ffmpegPath: opts.ffmpegPath,
|
|
27288
27597
|
onError: (e) => this.reportError(e)
|
|
27289
27598
|
});
|
|
27290
27599
|
this.mqtt = new MqttCommandRouter({
|
|
@@ -27986,6 +28295,28 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27986
28295
|
}
|
|
27987
28296
|
};
|
|
27988
28297
|
}
|
|
28298
|
+
/**
|
|
28299
|
+
* The station a device's commands go through: the device itself, or the HomeBase the registry links it
|
|
28300
|
+
* to. Answers the target record, the station record and serial, the station's `device_type`, and the
|
|
28301
|
+
* `t9000` / `t8n00` family flags.
|
|
28302
|
+
*/
|
|
28303
|
+
stationOf(sn) {
|
|
28304
|
+
const devices = this.registry.list();
|
|
28305
|
+
const target = devices.find((d) => d.sn === sn);
|
|
28306
|
+
const stationSn = target?.stationSn || sn;
|
|
28307
|
+
const station = devices.find((d) => d.sn === stationSn);
|
|
28308
|
+
const raw = station?.raw ?? {};
|
|
28309
|
+
const deviceType = typeof raw.device_type === "number" ? raw.device_type : void 0;
|
|
28310
|
+
const family = { deviceType, model: station?.model };
|
|
28311
|
+
return { target, station, stationSn, raw, deviceType, t9000: isStation9000(family), t8n00: isNvrT8N00(family) };
|
|
28312
|
+
}
|
|
28313
|
+
/** The vendor DeviceType of the station a device hangs off; `undefined` for a station itself. */
|
|
28314
|
+
stationDeviceTypeOf(sn) {
|
|
28315
|
+
const st = this.stationOf(sn);
|
|
28316
|
+
if (!st.station || st.stationSn === sn)
|
|
28317
|
+
return void 0;
|
|
28318
|
+
return st.deviceType;
|
|
28319
|
+
}
|
|
27989
28320
|
/**
|
|
27990
28321
|
* Combine explicit P2P media with the optional passive push-thumbnail provider.
|
|
27991
28322
|
*
|
|
@@ -27996,7 +28327,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
27996
28327
|
* current. With nothing retained the refusal stands.
|
|
27997
28328
|
*/
|
|
27998
28329
|
mediaProviderFor(sn) {
|
|
27999
|
-
const media = this.p2p.mediaProviderFor(sn);
|
|
28330
|
+
const media = this.rtcMediaFor(sn) ?? this.p2p.mediaProviderFor(sn);
|
|
28000
28331
|
const cache = this.storedImages;
|
|
28001
28332
|
if (!cache)
|
|
28002
28333
|
return media;
|
|
@@ -28056,25 +28387,42 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
28056
28387
|
return this.tuya.dispatchCommand(sn, cmd);
|
|
28057
28388
|
return this.mqtt.dispatchCommand(sn, cmd);
|
|
28058
28389
|
}
|
|
28059
|
-
const
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
const station = devices.find((d) => d.sn === stationSn);
|
|
28063
|
-
const stationRaw = station?.raw ?? {};
|
|
28064
|
-
const deviceType = typeof stationRaw.device_type === "number" ? stationRaw.device_type : void 0;
|
|
28065
|
-
const family = { deviceType, model: station?.model };
|
|
28066
|
-
const native = isNvrT8N00(family);
|
|
28067
|
-
if (target && station && (isStation9000(family) || native)) {
|
|
28068
|
-
const attached = stationSn !== sn;
|
|
28069
|
-
if (attached && this.registry.serialForFrame(stationSn, cmd.channel) !== sn)
|
|
28390
|
+
const route = this.rtcRouteFor(sn);
|
|
28391
|
+
if (route) {
|
|
28392
|
+
if (route.attached && this.registry.serialForFrame(route.stationSn, cmd.channel) !== sn)
|
|
28070
28393
|
return Promise.reject(new Error("RTC command requires an unambiguous attached-device channel"));
|
|
28071
|
-
|
|
28072
|
-
const adminUserId = typeof member === "string" && member ? member : void 0;
|
|
28073
|
-
const base = { stationSn, adminUserId, attached };
|
|
28074
|
-
return this.rtc.dispatchCommand(native ? { ...base, signalingMode: "call", iceTransportPolicy: "all" } : base, cmd);
|
|
28394
|
+
return this.rtc.dispatchCommand(route, cmd);
|
|
28075
28395
|
}
|
|
28076
28396
|
return this.p2p.dispatchCommand(sn, cmd);
|
|
28077
28397
|
}
|
|
28398
|
+
/**
|
|
28399
|
+
* The RTC route for a T9000 or T8N00 station or a device attached to one, decided by the station's model
|
|
28400
|
+
* classification; `undefined` for any other device. A T8N00 route carries its native `call` signalling. The admin id is the station's
|
|
28401
|
+
* `member.admin_user_id`, else the logged-in user.
|
|
28402
|
+
*/
|
|
28403
|
+
rtcRouteFor(sn) {
|
|
28404
|
+
const { target, station, stationSn, raw, t9000, t8n00: native } = this.stationOf(sn);
|
|
28405
|
+
if (!target || !station || !(t9000 || native))
|
|
28406
|
+
return void 0;
|
|
28407
|
+
const member = raw.member?.admin_user_id;
|
|
28408
|
+
const adminUserId = typeof member === "string" && member ? member : void 0;
|
|
28409
|
+
const base = { stationSn, adminUserId, attached: stationSn !== sn };
|
|
28410
|
+
return native ? { ...base, signalingMode: "call", iceTransportPolicy: "all" } : base;
|
|
28411
|
+
}
|
|
28412
|
+
/**
|
|
28413
|
+
* The RTC media provider for a camera attached to a T9000 whose `device_channel` resolves back to it on
|
|
28414
|
+
* the station; `undefined` otherwise, including a camera behind a T8N00, whose route carries commands only.
|
|
28415
|
+
*/
|
|
28416
|
+
rtcMediaFor(sn) {
|
|
28417
|
+
const route = this.rtcRouteFor(sn);
|
|
28418
|
+
if (!route?.attached || route.signalingMode === "call")
|
|
28419
|
+
return void 0;
|
|
28420
|
+
const raw = this.registry.list().find((d) => d.sn === sn)?.raw ?? {};
|
|
28421
|
+
const channel = typeof raw.device_channel === "number" ? raw.device_channel : void 0;
|
|
28422
|
+
if (channel === void 0 || this.registry.serialForFrame(route.stationSn, channel) !== sn)
|
|
28423
|
+
return void 0;
|
|
28424
|
+
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
28425
|
+
}
|
|
28078
28426
|
/**
|
|
28079
28427
|
* List + classify devices across all houses (mega API). Each device is tagged with its API backend
|
|
28080
28428
|
* + realtime transport. Camera/HomeBase records still appear here for inventory; driving them is
|
|
@@ -28307,13 +28655,38 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
28307
28655
|
this.schedulePoll();
|
|
28308
28656
|
const failures = readiness.push.failed + readiness.mqtt.failed + readiness.wiredP2p.failed;
|
|
28309
28657
|
this.settleRealtimeGeneration(generation, failures || startupFailed ? "partial" : "ready");
|
|
28658
|
+
if (readiness.push.failed)
|
|
28659
|
+
this.retryPush(generation);
|
|
28310
28660
|
} catch (e) {
|
|
28311
28661
|
this.reportError(e);
|
|
28312
28662
|
readiness.push.failed += readiness.push.pending;
|
|
28313
28663
|
readiness.push.pending = 0;
|
|
28314
28664
|
this.settleRealtimeGeneration(generation, epoch === this.realtimeEpoch ? "partial" : "superseded");
|
|
28665
|
+
if (epoch === this.realtimeEpoch)
|
|
28666
|
+
this.retryPush(generation);
|
|
28315
28667
|
}
|
|
28316
28668
|
}
|
|
28669
|
+
/**
|
|
28670
|
+
* Bring push up again after the generation's startup attempt failed, backing off between attempts
|
|
28671
|
+
* until one connects, the generation is superseded, or it is aborted by {@link disconnect}.
|
|
28672
|
+
*
|
|
28673
|
+
* The MCS socket reconnects on its own only once it has connected. The settled
|
|
28674
|
+
* {@link RealtimeReadiness} is a startup snapshot and is not revised when a retry connects.
|
|
28675
|
+
*/
|
|
28676
|
+
retryPush(generation, delayMs = PUSH_RETRY_MIN_MS) {
|
|
28677
|
+
this.pushRetryTimer.arm(delayMs, () => {
|
|
28678
|
+
this.startPush(generation.abort.signal).then((client) => {
|
|
28679
|
+
if (generation.epoch !== this.realtimeEpoch)
|
|
28680
|
+
return client.close();
|
|
28681
|
+
this.pushClient = client;
|
|
28682
|
+
}, (error) => {
|
|
28683
|
+
if (generation.epoch !== this.realtimeEpoch)
|
|
28684
|
+
return;
|
|
28685
|
+
this.reportError(error);
|
|
28686
|
+
this.retryPush(generation, Math.min(delayMs * 2, PUSH_RETRY_MAX_MS));
|
|
28687
|
+
});
|
|
28688
|
+
});
|
|
28689
|
+
}
|
|
28317
28690
|
/** Resolve a generation with an immutable count snapshot; later transport completions are ignored. */
|
|
28318
28691
|
settleRealtimeGeneration(generation, state) {
|
|
28319
28692
|
if (generation.settled)
|
|
@@ -28995,6 +29368,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
28995
29368
|
hasP2p: P2PCommandRouter.claimsDevice(dev),
|
|
28996
29369
|
// Topology as the record states it: a parent that isn't the device itself means HomeBase-attached.
|
|
28997
29370
|
homeBaseAttached: !!raw.parent_sn && raw.parent_sn !== dev.sn,
|
|
29371
|
+
stationDeviceType: this.stationDeviceTypeOf(sn),
|
|
28998
29372
|
dpCatalog
|
|
28999
29373
|
};
|
|
29000
29374
|
}
|
|
@@ -29169,6 +29543,7 @@ var EufyMega = class extends EventEmitter12 {
|
|
|
29169
29543
|
*/
|
|
29170
29544
|
async teardownRealtime() {
|
|
29171
29545
|
this.pollTimer.cancel();
|
|
29546
|
+
this.pushRetryTimer.cancel();
|
|
29172
29547
|
this.lastStateAnnounced.clear();
|
|
29173
29548
|
await this.closeMqttTransports();
|
|
29174
29549
|
this.rtc.close();
|
|
@@ -29676,7 +30051,7 @@ var SolixClient = class _SolixClient {
|
|
|
29676
30051
|
};
|
|
29677
30052
|
|
|
29678
30053
|
// dist/transport/mqtt/solix-mqtt.js
|
|
29679
|
-
import { EventEmitter as
|
|
30054
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
29680
30055
|
var SOLIX_METER_FIELD_NAMES = {
|
|
29681
30056
|
168: "meterPowerL1",
|
|
29682
30057
|
// Anker Modbus: primary_phase_1_active_power
|
|
@@ -29854,7 +30229,7 @@ function addSolarbankScalars(frame, out) {
|
|
|
29854
30229
|
out.gridExportLimit = df.readUInt16LE(5);
|
|
29855
30230
|
}
|
|
29856
30231
|
}
|
|
29857
|
-
var SolixMqtt = class extends
|
|
30232
|
+
var SolixMqtt = class extends EventEmitter14 {
|
|
29858
30233
|
transport;
|
|
29859
30234
|
appName;
|
|
29860
30235
|
userId;
|