@apocaliss92/nodelink-js 0.4.26 → 0.4.29
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/README.md +4 -2
- package/dist/{chunk-F3XCYKYT.js → chunk-NQ7D5TLR.js} +145 -26
- package/dist/chunk-NQ7D5TLR.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +105 -25
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +473 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -6
- package/dist/index.d.ts +221 -5
- package/dist/index.js +290 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-F3XCYKYT.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -3293,10 +3293,10 @@ function buildRtspPath(channel, stream) {
|
|
|
3293
3293
|
}
|
|
3294
3294
|
function buildRtspUrl(params) {
|
|
3295
3295
|
const port = params.port ?? 554;
|
|
3296
|
-
const
|
|
3296
|
+
const path7 = buildRtspPath(params.channel, params.stream);
|
|
3297
3297
|
const user = encodeURIComponent(params.username);
|
|
3298
3298
|
const pass = encodeURIComponent(params.password);
|
|
3299
|
-
return `rtsp://${user}:${pass}@${params.host}:${port}${
|
|
3299
|
+
return `rtsp://${user}:${pass}@${params.host}:${port}${path7}`;
|
|
3300
3300
|
}
|
|
3301
3301
|
var init_urls = __esm({
|
|
3302
3302
|
"src/rtsp/urls.ts"() {
|
|
@@ -4845,8 +4845,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
4845
4845
|
for (const app of rtmpApps) {
|
|
4846
4846
|
for (const streamName of streams) {
|
|
4847
4847
|
const streamType = streamName.includes("sub") || streamName === "sub" || streamName === "mobile" ? 1 : 0;
|
|
4848
|
-
const
|
|
4849
|
-
const u = new URL(`rtmp://${params.host}:1935${
|
|
4848
|
+
const path7 = `/${app}/channel${params.channel}_${streamName}.bcs`;
|
|
4849
|
+
const u = new URL(`rtmp://${params.host}:1935${path7}`);
|
|
4850
4850
|
u.searchParams.set("channel", params.channel.toString());
|
|
4851
4851
|
u.searchParams.set("stream", streamType.toString());
|
|
4852
4852
|
u.searchParams.set("user", params.username);
|
|
@@ -8334,6 +8334,7 @@ __export(index_exports, {
|
|
|
8334
8334
|
ReolinkCgiApi: () => ReolinkCgiApi,
|
|
8335
8335
|
ReolinkHttpClient: () => ReolinkHttpClient,
|
|
8336
8336
|
Rfc4571Muxer: () => Rfc4571Muxer,
|
|
8337
|
+
_resetEmailPushBusForTests: () => _resetEmailPushBusForTests,
|
|
8337
8338
|
abilitiesHasAny: () => abilitiesHasAny,
|
|
8338
8339
|
aesDecrypt: () => aesDecrypt,
|
|
8339
8340
|
aesEncrypt: () => aesEncrypt,
|
|
@@ -8379,6 +8380,7 @@ __export(index_exports, {
|
|
|
8379
8380
|
createBaichuanEndpointsServer: () => createBaichuanEndpointsServer,
|
|
8380
8381
|
createDebugGateLogger: () => createDebugGateLogger,
|
|
8381
8382
|
createDiagnosticsBundle: () => createDiagnosticsBundle,
|
|
8383
|
+
createEmailPushServer: () => createEmailPushServer,
|
|
8382
8384
|
createLogger: () => createLogger,
|
|
8383
8385
|
createMjpegBoundary: () => createMjpegBoundary,
|
|
8384
8386
|
createNativeStream: () => createNativeStream,
|
|
@@ -8406,6 +8408,7 @@ __export(index_exports, {
|
|
|
8406
8408
|
discoverViaTcpPortScan: () => discoverViaTcpPortScan,
|
|
8407
8409
|
discoverViaUdpBroadcast: () => discoverViaUdpBroadcast,
|
|
8408
8410
|
discoverViaUdpDirect: () => discoverViaUdpDirect,
|
|
8411
|
+
emitEmailPushEvent: () => emitEmailPushEvent,
|
|
8409
8412
|
encodeHeader: () => encodeHeader,
|
|
8410
8413
|
encodeMotionScopeBitmap: () => encodeMotionScopeBitmap,
|
|
8411
8414
|
encodeMotionSensitivityListXml: () => encodeMotionSensitivityListXml,
|
|
@@ -8422,10 +8425,14 @@ __export(index_exports, {
|
|
|
8422
8425
|
flattenAbilitiesForChannel: () => flattenAbilitiesForChannel,
|
|
8423
8426
|
formatMjpegFrame: () => formatMjpegFrame,
|
|
8424
8427
|
fullCoverageScope: () => fullCoverageScope,
|
|
8428
|
+
getCameraEmailAddress: () => getCameraEmailAddress,
|
|
8425
8429
|
getConstructedVideoStreamOptions: () => getConstructedVideoStreamOptions,
|
|
8430
|
+
getEmailPushCameraResolver: () => getEmailPushCameraResolver,
|
|
8426
8431
|
getGlobalLogger: () => getGlobalLogger,
|
|
8427
8432
|
getH265NalType: () => getH265NalType,
|
|
8433
|
+
getLastEmailPushEvent: () => getLastEmailPushEvent,
|
|
8428
8434
|
getMjpegContentType: () => getMjpegContentType,
|
|
8435
|
+
getRecentEmailPushEvents: () => getRecentEmailPushEvents,
|
|
8429
8436
|
getSupportItemForChannel: () => getSupportItemForChannel,
|
|
8430
8437
|
getVideoStream: () => getVideoStream,
|
|
8431
8438
|
getVideoclipClientInfo: () => getVideoclipClientInfo,
|
|
@@ -8440,12 +8447,15 @@ __export(index_exports, {
|
|
|
8440
8447
|
isTcpFailureThatShouldFallbackToUdp: () => isTcpFailureThatShouldFallbackToUdp,
|
|
8441
8448
|
isValidH264AnnexBAccessUnit: () => isValidH264AnnexBAccessUnit,
|
|
8442
8449
|
isValidH265AnnexBAccessUnit: () => isValidH265AnnexBAccessUnit,
|
|
8450
|
+
loadEmailPushTls: () => loadEmailPushTls,
|
|
8451
|
+
mapEmailPushInferredType: () => mapEmailPushInferredType,
|
|
8443
8452
|
maskUid: () => maskUid,
|
|
8444
8453
|
md5HexUpper: () => md5HexUpper,
|
|
8445
8454
|
md5StrModern: () => md5StrModern,
|
|
8446
8455
|
normalizeDayNightMode: () => normalizeDayNightMode,
|
|
8447
8456
|
normalizeOpenClose: () => normalizeOpenClose,
|
|
8448
8457
|
normalizeUid: () => normalizeUid,
|
|
8458
|
+
onEmailPushEvent: () => onEmailPushEvent,
|
|
8449
8459
|
packetizeAacAdtsFrame: () => packetizeAacAdtsFrame,
|
|
8450
8460
|
packetizeAacRawFrame: () => packetizeAacRawFrame,
|
|
8451
8461
|
packetizeH264: () => packetizeH264,
|
|
@@ -8463,6 +8473,7 @@ __export(index_exports, {
|
|
|
8463
8473
|
runMultifocalDiagnosticsConsecutively: () => runMultifocalDiagnosticsConsecutively,
|
|
8464
8474
|
sampleStreams: () => sampleStreams,
|
|
8465
8475
|
sanitizeFixtureData: () => sanitizeFixtureData,
|
|
8476
|
+
setEmailPushCameraResolver: () => setEmailPushCameraResolver,
|
|
8466
8477
|
setGlobalLogger: () => setGlobalLogger,
|
|
8467
8478
|
splitAnnexBToNalPayloads: () => splitAnnexBToNalPayloads,
|
|
8468
8479
|
splitAnnexBToNals: () => splitAnnexBToNals,
|
|
@@ -13646,6 +13657,21 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13646
13657
|
let audioSampleRate = null;
|
|
13647
13658
|
let streamStarted = false;
|
|
13648
13659
|
let closed = false;
|
|
13660
|
+
const signal = options?.signal;
|
|
13661
|
+
let sleepResolve = null;
|
|
13662
|
+
let sleepTimer = null;
|
|
13663
|
+
const clearSleepTimer = () => {
|
|
13664
|
+
if (sleepTimer) {
|
|
13665
|
+
clearTimeout(sleepTimer);
|
|
13666
|
+
sleepTimer = null;
|
|
13667
|
+
}
|
|
13668
|
+
};
|
|
13669
|
+
const handleAbort = () => {
|
|
13670
|
+
clearSleepTimer();
|
|
13671
|
+
const r = sleepResolve;
|
|
13672
|
+
sleepResolve = null;
|
|
13673
|
+
r?.();
|
|
13674
|
+
};
|
|
13649
13675
|
const onError = (_error) => {
|
|
13650
13676
|
closed = true;
|
|
13651
13677
|
api.logger?.warn?.(
|
|
@@ -13743,7 +13769,13 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13743
13769
|
}
|
|
13744
13770
|
});
|
|
13745
13771
|
streamStarted = true;
|
|
13746
|
-
|
|
13772
|
+
if (signal) {
|
|
13773
|
+
if (signal.aborted) {
|
|
13774
|
+
closed = true;
|
|
13775
|
+
} else {
|
|
13776
|
+
signal.addEventListener("abort", handleAbort);
|
|
13777
|
+
}
|
|
13778
|
+
}
|
|
13747
13779
|
while (!closed && !signal?.aborted) {
|
|
13748
13780
|
if (frameQueue.length > 0) {
|
|
13749
13781
|
const frame = frameQueue.shift();
|
|
@@ -13751,31 +13783,30 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
13751
13783
|
} else {
|
|
13752
13784
|
await new Promise((resolve) => {
|
|
13753
13785
|
frameResolve = resolve;
|
|
13754
|
-
|
|
13786
|
+
sleepResolve = resolve;
|
|
13787
|
+
sleepTimer = setTimeout(() => {
|
|
13788
|
+
sleepTimer = null;
|
|
13789
|
+
if (sleepResolve === resolve) sleepResolve = null;
|
|
13755
13790
|
if (frameResolve === resolve) {
|
|
13756
13791
|
frameResolve = null;
|
|
13757
13792
|
resolve();
|
|
13758
13793
|
}
|
|
13759
13794
|
}, 1e3);
|
|
13760
|
-
if (signal) {
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
};
|
|
13766
|
-
if (signal.aborted) {
|
|
13767
|
-
clearTimeout(timer);
|
|
13768
|
-
frameResolve = null;
|
|
13769
|
-
resolve();
|
|
13770
|
-
} else {
|
|
13771
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
13772
|
-
}
|
|
13795
|
+
if (signal?.aborted) {
|
|
13796
|
+
clearSleepTimer();
|
|
13797
|
+
sleepResolve = null;
|
|
13798
|
+
frameResolve = null;
|
|
13799
|
+
resolve();
|
|
13773
13800
|
}
|
|
13774
13801
|
});
|
|
13802
|
+
sleepResolve = null;
|
|
13803
|
+
clearSleepTimer();
|
|
13775
13804
|
}
|
|
13776
13805
|
}
|
|
13777
13806
|
} finally {
|
|
13778
13807
|
closed = true;
|
|
13808
|
+
if (signal) signal.removeEventListener("abort", handleAbort);
|
|
13809
|
+
clearSleepTimer();
|
|
13779
13810
|
try {
|
|
13780
13811
|
await videoStream.stop();
|
|
13781
13812
|
} catch {
|
|
@@ -15809,12 +15840,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
15809
15840
|
if (frame.videoType === "H264" || frame.videoType === "H265") {
|
|
15810
15841
|
this.setFlowVideoType(frame.videoType, "native stream");
|
|
15811
15842
|
}
|
|
15812
|
-
this.flow.
|
|
15813
|
-
|
|
15814
|
-
|
|
15843
|
+
if (!this.flow.getFmtp().hasParamSets) {
|
|
15844
|
+
this.flow.extractParameterSets(frame.data);
|
|
15845
|
+
}
|
|
15846
|
+
if (this.flow.getFmtp().hasParamSets) {
|
|
15815
15847
|
this.markFirstFrameReceived();
|
|
15816
15848
|
}
|
|
15817
|
-
const isKeyframe = this.isRawFrameKeyframe(frame);
|
|
15849
|
+
const isKeyframe = typeof frame.isKeyframe === "boolean" ? frame.isKeyframe : this.isRawFrameKeyframe(frame);
|
|
15818
15850
|
this.prebuffer.push({
|
|
15819
15851
|
frame: { ...frame, data: Buffer.from(frame.data) },
|
|
15820
15852
|
time: Date.now(),
|
|
@@ -19668,6 +19700,60 @@ var parseSirenStatusListPushXml = (xml) => {
|
|
|
19668
19700
|
return out;
|
|
19669
19701
|
};
|
|
19670
19702
|
|
|
19703
|
+
// src/emailPush/bus.ts
|
|
19704
|
+
var import_node_events5 = require("events");
|
|
19705
|
+
var emitter = new import_node_events5.EventEmitter();
|
|
19706
|
+
var cameraResolver = () => void 0;
|
|
19707
|
+
var lastEventByCamera = /* @__PURE__ */ new Map();
|
|
19708
|
+
var MAX_GLOBAL_EVENTS = 300;
|
|
19709
|
+
var globalRecentEvents = [];
|
|
19710
|
+
function setEmailPushCameraResolver(resolver) {
|
|
19711
|
+
cameraResolver = resolver;
|
|
19712
|
+
}
|
|
19713
|
+
function getEmailPushCameraResolver() {
|
|
19714
|
+
return cameraResolver;
|
|
19715
|
+
}
|
|
19716
|
+
function onEmailPushEvent(handler) {
|
|
19717
|
+
emitter.on("event", handler);
|
|
19718
|
+
return () => emitter.off("event", handler);
|
|
19719
|
+
}
|
|
19720
|
+
function emitEmailPushEvent(event) {
|
|
19721
|
+
lastEventByCamera.set(event.cameraId, event);
|
|
19722
|
+
globalRecentEvents.unshift(event);
|
|
19723
|
+
if (globalRecentEvents.length > MAX_GLOBAL_EVENTS) {
|
|
19724
|
+
globalRecentEvents.length = MAX_GLOBAL_EVENTS;
|
|
19725
|
+
}
|
|
19726
|
+
emitter.emit("event", event);
|
|
19727
|
+
}
|
|
19728
|
+
function getLastEmailPushEvent(cameraId) {
|
|
19729
|
+
return lastEventByCamera.get(cameraId);
|
|
19730
|
+
}
|
|
19731
|
+
function getRecentEmailPushEvents(limit = MAX_GLOBAL_EVENTS) {
|
|
19732
|
+
const clamped = Math.max(0, Math.min(limit, MAX_GLOBAL_EVENTS));
|
|
19733
|
+
return globalRecentEvents.slice(0, clamped);
|
|
19734
|
+
}
|
|
19735
|
+
function mapEmailPushInferredType(inferred) {
|
|
19736
|
+
switch (inferred) {
|
|
19737
|
+
case "motion":
|
|
19738
|
+
case "doorbell":
|
|
19739
|
+
case "people":
|
|
19740
|
+
case "vehicle":
|
|
19741
|
+
case "animal":
|
|
19742
|
+
case "face":
|
|
19743
|
+
case "package":
|
|
19744
|
+
return inferred;
|
|
19745
|
+
case "other":
|
|
19746
|
+
default:
|
|
19747
|
+
return "motion";
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
function _resetEmailPushBusForTests() {
|
|
19751
|
+
emitter.removeAllListeners();
|
|
19752
|
+
cameraResolver = () => void 0;
|
|
19753
|
+
lastEventByCamera.clear();
|
|
19754
|
+
globalRecentEvents.length = 0;
|
|
19755
|
+
}
|
|
19756
|
+
|
|
19671
19757
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
19672
19758
|
var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
|
|
19673
19759
|
"Reolink Duo PoE",
|
|
@@ -19813,7 +19899,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19813
19899
|
* general socket is created, logged in, and all event/push/guard listeners
|
|
19814
19900
|
* are re-attached automatically.
|
|
19815
19901
|
*
|
|
19816
|
-
* This is a **no-op** when the API is already
|
|
19902
|
+
* This is a **no-op** when the API is already ready (see `isReadyState()`).
|
|
19817
19903
|
*
|
|
19818
19904
|
* @throws If `close()` was called — the API is permanently closed and a new
|
|
19819
19905
|
* instance must be created.
|
|
@@ -19894,7 +19980,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19894
19980
|
/**
|
|
19895
19981
|
* Attach event, push, channelInfo, and guard listeners to the current
|
|
19896
19982
|
* "general" client. Called from the constructor and from
|
|
19897
|
-
*
|
|
19983
|
+
* `reconnectGeneralSocket()`.
|
|
19898
19984
|
*/
|
|
19899
19985
|
setupGeneralClientListeners() {
|
|
19900
19986
|
const client = this.client;
|
|
@@ -21483,6 +21569,40 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21483
21569
|
this.econnresetStormRebootInFlight = void 0;
|
|
21484
21570
|
});
|
|
21485
21571
|
}
|
|
21572
|
+
/**
|
|
21573
|
+
* Bind this API instance to the global email-push bus so that incoming
|
|
21574
|
+
* SMTP-delivered motion / AI events for the matching camera surface on
|
|
21575
|
+
* this instance's standard `onSimpleEvent` channel. The consumer keeps
|
|
21576
|
+
* a single subscription (`onSimpleEvent`) and gets both the native
|
|
21577
|
+
* Baichuan push and the email-push transport on the same stream.
|
|
21578
|
+
*
|
|
21579
|
+
* - `cameraId` shorthand: match events with `event.cameraId === cameraId`.
|
|
21580
|
+
* - `match`: arbitrary predicate (e.g. when the consumer uses a
|
|
21581
|
+
* nickname-based mapping or wants to handle multiple recipients).
|
|
21582
|
+
*
|
|
21583
|
+
* Returns an `off()` handle. Safe to call repeatedly — each call
|
|
21584
|
+
* registers its own listener.
|
|
21585
|
+
*/
|
|
21586
|
+
subscribeEmailPushEvents(params) {
|
|
21587
|
+
const channel = params.channel ?? 0;
|
|
21588
|
+
const matches = "match" in params ? params.match : (event) => event.cameraId === params.cameraId;
|
|
21589
|
+
const off = onEmailPushEvent((event) => {
|
|
21590
|
+
if (!matches(event)) return;
|
|
21591
|
+
this.dispatchSimpleEvent({
|
|
21592
|
+
type: mapEmailPushInferredType(event.inferredType),
|
|
21593
|
+
channel,
|
|
21594
|
+
timestamp: event.receivedAtMs
|
|
21595
|
+
});
|
|
21596
|
+
if (event.inferredType !== "motion" && event.inferredType !== "doorbell" && event.inferredType !== "other") {
|
|
21597
|
+
this.dispatchSimpleEvent({
|
|
21598
|
+
type: "motion",
|
|
21599
|
+
channel,
|
|
21600
|
+
timestamp: event.receivedAtMs
|
|
21601
|
+
});
|
|
21602
|
+
}
|
|
21603
|
+
});
|
|
21604
|
+
return off;
|
|
21605
|
+
}
|
|
21486
21606
|
/**
|
|
21487
21607
|
* Subscribe to minimal high-level events.
|
|
21488
21608
|
* The API manages Baichuan subscribe/unsubscribe automatically.
|
|
@@ -21541,7 +21661,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21541
21661
|
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
21542
21662
|
* `additionalHeader` block on active video streams.
|
|
21543
21663
|
*
|
|
21544
|
-
* Mirrors
|
|
21664
|
+
* Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
|
|
21545
21665
|
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
21546
21666
|
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
21547
21667
|
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
@@ -21568,7 +21688,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
21568
21688
|
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
21569
21689
|
* with class label and confidence) without managing a video stream yourself.
|
|
21570
21690
|
*
|
|
21571
|
-
* Mirrors
|
|
21691
|
+
* Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
|
|
21572
21692
|
* `(channel, profile)` tuple the API ensures the corresponding video stream
|
|
21573
21693
|
* is running (the pool socket may already be shared with a regular consumer),
|
|
21574
21694
|
* forwards every box-bearing `additionalHeader` to your callback, and tears
|
|
@@ -25388,24 +25508,24 @@ ${stderr}`)
|
|
|
25388
25508
|
async muxToMp4(params) {
|
|
25389
25509
|
const { spawn: spawn13 } = await import("child_process");
|
|
25390
25510
|
const { randomUUID: randomUUID3 } = await import("crypto");
|
|
25391
|
-
const
|
|
25511
|
+
const fs7 = await import("fs/promises");
|
|
25392
25512
|
const os2 = await import("os");
|
|
25393
|
-
const
|
|
25513
|
+
const path7 = await import("path");
|
|
25394
25514
|
const ffmpeg = params.ffmpegPath ?? "ffmpeg";
|
|
25395
25515
|
const tmpDir = os2.tmpdir();
|
|
25396
25516
|
const id = randomUUID3();
|
|
25397
25517
|
const videoFormat = params.videoCodec === "H265" ? "hevc" : "h264";
|
|
25398
|
-
const videoPath =
|
|
25399
|
-
const outputPath =
|
|
25518
|
+
const videoPath = path7.join(tmpDir, `reolink-${id}.${videoFormat}`);
|
|
25519
|
+
const outputPath = path7.join(tmpDir, `reolink-${id}.mp4`);
|
|
25400
25520
|
let audioPath = null;
|
|
25401
25521
|
if (params.audioData && params.audioData.length > 0 && params.audioCodec) {
|
|
25402
25522
|
const audioExt = params.audioCodec === "Aac" ? "aac" : "raw";
|
|
25403
|
-
audioPath =
|
|
25523
|
+
audioPath = path7.join(tmpDir, `reolink-${id}.${audioExt}`);
|
|
25404
25524
|
}
|
|
25405
25525
|
try {
|
|
25406
|
-
await
|
|
25526
|
+
await fs7.writeFile(videoPath, params.videoData);
|
|
25407
25527
|
if (audioPath && params.audioData) {
|
|
25408
|
-
await
|
|
25528
|
+
await fs7.writeFile(audioPath, params.audioData);
|
|
25409
25529
|
}
|
|
25410
25530
|
const args = ["-hide_banner", "-loglevel", "error", "-y"];
|
|
25411
25531
|
if (params.fps > 0) {
|
|
@@ -25458,13 +25578,13 @@ ${stderr}`)
|
|
|
25458
25578
|
}
|
|
25459
25579
|
});
|
|
25460
25580
|
});
|
|
25461
|
-
return await
|
|
25581
|
+
return await fs7.readFile(outputPath);
|
|
25462
25582
|
} finally {
|
|
25463
|
-
await
|
|
25583
|
+
await fs7.unlink(videoPath).catch(() => {
|
|
25464
25584
|
});
|
|
25465
|
-
if (audioPath) await
|
|
25585
|
+
if (audioPath) await fs7.unlink(audioPath).catch(() => {
|
|
25466
25586
|
});
|
|
25467
|
-
await
|
|
25587
|
+
await fs7.unlink(outputPath).catch(() => {
|
|
25468
25588
|
});
|
|
25469
25589
|
}
|
|
25470
25590
|
}
|
|
@@ -28412,7 +28532,7 @@ ${xml}`
|
|
|
28412
28532
|
* Field meaning per stream:
|
|
28413
28533
|
* - `audio` — 0/1 toggle
|
|
28414
28534
|
* - `width`/`height` — resolution in pixels. Must be one of the
|
|
28415
|
-
* resolutions returned by
|
|
28535
|
+
* resolutions returned by `getStreamInfoList()`.
|
|
28416
28536
|
* - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
|
|
28417
28537
|
* - `frameRate` — fps. Must match the table from `getStreamInfoList`.
|
|
28418
28538
|
* - `videoEncType` — `"h264"` or `"h265"`
|
|
@@ -29801,10 +29921,12 @@ ${xml}`
|
|
|
29801
29921
|
const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
|
|
29802
29922
|
const attachmentType = params.attachmentType ?? "picture";
|
|
29803
29923
|
const interval = params.interval ?? 30;
|
|
29924
|
+
const rawUser = params.authUsername ?? recipient;
|
|
29925
|
+
const wireUser = rawUser.includes("@") ? rawUser : `${rawUser}@${domain}`;
|
|
29804
29926
|
const emailPatch = {
|
|
29805
29927
|
smtpServer: params.managerHost,
|
|
29806
29928
|
smtpPort: port,
|
|
29807
|
-
userName:
|
|
29929
|
+
userName: wireUser,
|
|
29808
29930
|
password: params.authPassword ?? "",
|
|
29809
29931
|
address1: recipient,
|
|
29810
29932
|
address2: "",
|
|
@@ -30816,16 +30938,16 @@ ${scheduleItems}
|
|
|
30816
30938
|
const logger = params.logger ?? this.logger;
|
|
30817
30939
|
const hlsSegmentDuration = params.hlsSegmentDuration ?? 4;
|
|
30818
30940
|
const os2 = await import("os");
|
|
30819
|
-
const
|
|
30820
|
-
const
|
|
30941
|
+
const path7 = await import("path");
|
|
30942
|
+
const fs7 = await import("fs/promises");
|
|
30821
30943
|
const crypto3 = await import("crypto");
|
|
30822
|
-
const tempDir =
|
|
30944
|
+
const tempDir = path7.join(
|
|
30823
30945
|
os2.tmpdir(),
|
|
30824
30946
|
`reolink-hls-${crypto3.randomBytes(8).toString("hex")}`
|
|
30825
30947
|
);
|
|
30826
|
-
await
|
|
30827
|
-
const playlistPath =
|
|
30828
|
-
const segmentPattern =
|
|
30948
|
+
await fs7.mkdir(tempDir, { recursive: true });
|
|
30949
|
+
const playlistPath = path7.join(tempDir, "playlist.m3u8");
|
|
30950
|
+
const segmentPattern = path7.join(tempDir, "segment_%03d.ts");
|
|
30829
30951
|
const parsed = parseRecordingFileName(params.fileName);
|
|
30830
30952
|
const durationMs = parsed?.durationMs ?? 3e5;
|
|
30831
30953
|
const fps = parsed?.framerate && parsed.framerate > 0 ? parsed.framerate : 15;
|
|
@@ -30862,13 +30984,13 @@ ${scheduleItems}
|
|
|
30862
30984
|
const segments = /* @__PURE__ */ new Map();
|
|
30863
30985
|
const startSegmentWatcher = () => {
|
|
30864
30986
|
if (segmentWatcher || !readyResolve) return;
|
|
30865
|
-
const firstSegmentPath =
|
|
30987
|
+
const firstSegmentPath = path7.join(tempDir, "segment_000.ts");
|
|
30866
30988
|
let checkCount = 0;
|
|
30867
30989
|
const maxChecks = Math.ceil((hlsSegmentDuration + 2) * 10);
|
|
30868
30990
|
segmentWatcher = setInterval(async () => {
|
|
30869
30991
|
checkCount++;
|
|
30870
30992
|
try {
|
|
30871
|
-
const stats = await
|
|
30993
|
+
const stats = await fs7.stat(firstSegmentPath);
|
|
30872
30994
|
if (stats.size > 256) {
|
|
30873
30995
|
if (segmentWatcher) {
|
|
30874
30996
|
clearInterval(segmentWatcher);
|
|
@@ -31010,12 +31132,12 @@ ${scheduleItems}
|
|
|
31010
31132
|
]);
|
|
31011
31133
|
setTimeout(async () => {
|
|
31012
31134
|
try {
|
|
31013
|
-
const files = await
|
|
31135
|
+
const files = await fs7.readdir(tempDir);
|
|
31014
31136
|
for (const file of files) {
|
|
31015
|
-
await
|
|
31137
|
+
await fs7.unlink(path7.join(tempDir, file)).catch(() => {
|
|
31016
31138
|
});
|
|
31017
31139
|
}
|
|
31018
|
-
await
|
|
31140
|
+
await fs7.rmdir(tempDir).catch(() => {
|
|
31019
31141
|
});
|
|
31020
31142
|
} catch {
|
|
31021
31143
|
}
|
|
@@ -31091,7 +31213,7 @@ ${scheduleItems}
|
|
|
31091
31213
|
}
|
|
31092
31214
|
try {
|
|
31093
31215
|
const { readFileSync } = require("fs");
|
|
31094
|
-
const segmentPath =
|
|
31216
|
+
const segmentPath = path7.join(tempDir, name);
|
|
31095
31217
|
const data = readFileSync(segmentPath);
|
|
31096
31218
|
segments.set(name, data);
|
|
31097
31219
|
return data;
|
|
@@ -34264,7 +34386,7 @@ init_BaichuanVideoStream();
|
|
|
34264
34386
|
// src/multifocal/compositeStream.ts
|
|
34265
34387
|
var import_node_child_process7 = require("child_process");
|
|
34266
34388
|
var import_node_crypto5 = require("crypto");
|
|
34267
|
-
var
|
|
34389
|
+
var import_node_events6 = require("events");
|
|
34268
34390
|
function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pipHeight, margin) {
|
|
34269
34391
|
const pipW = Math.floor(pipWidth);
|
|
34270
34392
|
const pipH = Math.floor(pipHeight);
|
|
@@ -34292,7 +34414,7 @@ function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pip
|
|
|
34292
34414
|
return { x: m, y: m };
|
|
34293
34415
|
}
|
|
34294
34416
|
}
|
|
34295
|
-
var CompositeStream = class extends
|
|
34417
|
+
var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
34296
34418
|
options;
|
|
34297
34419
|
widerStream = null;
|
|
34298
34420
|
teleStream = null;
|
|
@@ -36715,7 +36837,7 @@ async function createReplayHttpServer(options) {
|
|
|
36715
36837
|
init_BaichuanVideoStream();
|
|
36716
36838
|
|
|
36717
36839
|
// src/baichuan/stream/Go2rtcTcpServer.ts
|
|
36718
|
-
var
|
|
36840
|
+
var import_node_events7 = require("events");
|
|
36719
36841
|
var net4 = __toESM(require("net"), 1);
|
|
36720
36842
|
init_H264Converter();
|
|
36721
36843
|
init_H265Converter();
|
|
@@ -36827,7 +36949,7 @@ var NativeStreamFanout2 = class {
|
|
|
36827
36949
|
this.pumpPromise = null;
|
|
36828
36950
|
}
|
|
36829
36951
|
};
|
|
36830
|
-
var Go2rtcTcpServer = class _Go2rtcTcpServer extends
|
|
36952
|
+
var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events7.EventEmitter {
|
|
36831
36953
|
api;
|
|
36832
36954
|
channel;
|
|
36833
36955
|
profile;
|
|
@@ -37518,7 +37640,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends import_node_events6.EventEm
|
|
|
37518
37640
|
};
|
|
37519
37641
|
|
|
37520
37642
|
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
37521
|
-
var
|
|
37643
|
+
var import_node_events8 = require("events");
|
|
37522
37644
|
var import_node_child_process9 = require("child_process");
|
|
37523
37645
|
var http4 = __toESM(require("http"), 1);
|
|
37524
37646
|
var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
|
|
@@ -37565,7 +37687,7 @@ function isH264KeyframeFromAnnexB(annexB) {
|
|
|
37565
37687
|
}
|
|
37566
37688
|
return false;
|
|
37567
37689
|
}
|
|
37568
|
-
var BaichuanHttpStreamServer = class extends
|
|
37690
|
+
var BaichuanHttpStreamServer = class extends import_node_events8.EventEmitter {
|
|
37569
37691
|
videoStream;
|
|
37570
37692
|
listenPort;
|
|
37571
37693
|
path;
|
|
@@ -37837,15 +37959,15 @@ var BaichuanHttpStreamServer = class extends import_node_events7.EventEmitter {
|
|
|
37837
37959
|
};
|
|
37838
37960
|
|
|
37839
37961
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
37840
|
-
var
|
|
37962
|
+
var import_node_events10 = require("events");
|
|
37841
37963
|
var http5 = __toESM(require("http"), 1);
|
|
37842
37964
|
|
|
37843
37965
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
37844
|
-
var
|
|
37966
|
+
var import_node_events9 = require("events");
|
|
37845
37967
|
var import_node_child_process10 = require("child_process");
|
|
37846
37968
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
37847
37969
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
37848
|
-
var MjpegTransformer = class extends
|
|
37970
|
+
var MjpegTransformer = class extends import_node_events9.EventEmitter {
|
|
37849
37971
|
options;
|
|
37850
37972
|
ffmpeg = null;
|
|
37851
37973
|
started = false;
|
|
@@ -38044,7 +38166,7 @@ Content-Length: ${frame.length}\r
|
|
|
38044
38166
|
// src/baichuan/stream/BaichuanMjpegServer.ts
|
|
38045
38167
|
init_H264Converter();
|
|
38046
38168
|
init_H265Converter();
|
|
38047
|
-
var BaichuanMjpegServer = class extends
|
|
38169
|
+
var BaichuanMjpegServer = class extends import_node_events10.EventEmitter {
|
|
38048
38170
|
options;
|
|
38049
38171
|
clients = /* @__PURE__ */ new Map();
|
|
38050
38172
|
httpServer = null;
|
|
@@ -38066,9 +38188,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
38066
38188
|
this.started = true;
|
|
38067
38189
|
const port = this.options.port ?? 8080;
|
|
38068
38190
|
const host = this.options.host ?? "0.0.0.0";
|
|
38069
|
-
const
|
|
38191
|
+
const path7 = this.options.path ?? "/mjpeg";
|
|
38070
38192
|
this.httpServer = http5.createServer((req, res) => {
|
|
38071
|
-
this.handleRequest(req, res,
|
|
38193
|
+
this.handleRequest(req, res, path7);
|
|
38072
38194
|
});
|
|
38073
38195
|
return new Promise((resolve, reject) => {
|
|
38074
38196
|
this.httpServer.on("error", (err) => {
|
|
@@ -38078,9 +38200,9 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
38078
38200
|
this.httpServer.listen(port, host, () => {
|
|
38079
38201
|
this.log(
|
|
38080
38202
|
"info",
|
|
38081
|
-
`MJPEG server started on http://${host}:${port}${
|
|
38203
|
+
`MJPEG server started on http://${host}:${port}${path7}`
|
|
38082
38204
|
);
|
|
38083
|
-
this.emit("started", { host, port, path:
|
|
38205
|
+
this.emit("started", { host, port, path: path7 });
|
|
38084
38206
|
resolve();
|
|
38085
38207
|
});
|
|
38086
38208
|
});
|
|
@@ -38325,14 +38447,14 @@ var BaichuanMjpegServer = class extends import_node_events9.EventEmitter {
|
|
|
38325
38447
|
};
|
|
38326
38448
|
|
|
38327
38449
|
// src/baichuan/stream/BaichuanWebRTCServer.ts
|
|
38328
|
-
var
|
|
38450
|
+
var import_node_events12 = require("events");
|
|
38329
38451
|
init_BcMediaAnnexBDecoder();
|
|
38330
38452
|
|
|
38331
38453
|
// src/baichuan/stream/AacToOpusTranscoder.ts
|
|
38332
38454
|
var import_node_child_process11 = require("child_process");
|
|
38333
38455
|
var import_node_dgram3 = require("dgram");
|
|
38334
|
-
var
|
|
38335
|
-
var AacToOpusTranscoder = class extends
|
|
38456
|
+
var import_node_events11 = require("events");
|
|
38457
|
+
var AacToOpusTranscoder = class extends import_node_events11.EventEmitter {
|
|
38336
38458
|
opts;
|
|
38337
38459
|
socket = null;
|
|
38338
38460
|
ffmpeg = null;
|
|
@@ -38549,7 +38671,7 @@ function getH264NalType(nalUnit) {
|
|
|
38549
38671
|
function getH265NalType2(nalUnit) {
|
|
38550
38672
|
return nalUnit[0] >> 1 & 63;
|
|
38551
38673
|
}
|
|
38552
|
-
var BaichuanWebRTCServer = class extends
|
|
38674
|
+
var BaichuanWebRTCServer = class extends import_node_events12.EventEmitter {
|
|
38553
38675
|
options;
|
|
38554
38676
|
sessions = /* @__PURE__ */ new Map();
|
|
38555
38677
|
sessionIdCounter = 0;
|
|
@@ -39541,7 +39663,7 @@ Error: ${err}`
|
|
|
39541
39663
|
};
|
|
39542
39664
|
|
|
39543
39665
|
// src/baichuan/stream/BaichuanHlsServer.ts
|
|
39544
|
-
var
|
|
39666
|
+
var import_node_events13 = require("events");
|
|
39545
39667
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
39546
39668
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
39547
39669
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
@@ -39621,7 +39743,7 @@ function getNalTypes(codec, annexB) {
|
|
|
39621
39743
|
}
|
|
39622
39744
|
});
|
|
39623
39745
|
}
|
|
39624
|
-
var BaichuanHlsServer = class extends
|
|
39746
|
+
var BaichuanHlsServer = class extends import_node_events13.EventEmitter {
|
|
39625
39747
|
api;
|
|
39626
39748
|
channel;
|
|
39627
39749
|
profile;
|
|
@@ -40645,10 +40767,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40645
40767
|
}
|
|
40646
40768
|
|
|
40647
40769
|
// src/multifocal/compositeRtspServer.ts
|
|
40648
|
-
var
|
|
40770
|
+
var import_node_events14 = require("events");
|
|
40649
40771
|
var import_node_child_process13 = require("child_process");
|
|
40650
40772
|
var net5 = __toESM(require("net"), 1);
|
|
40651
|
-
var CompositeRtspServer = class extends
|
|
40773
|
+
var CompositeRtspServer = class extends import_node_events14.EventEmitter {
|
|
40652
40774
|
options;
|
|
40653
40775
|
compositeStream = null;
|
|
40654
40776
|
rtspServer = null;
|
|
@@ -40941,6 +41063,272 @@ function base64DecodeToBytes(b64) {
|
|
|
40941
41063
|
}
|
|
40942
41064
|
return out.subarray(0, outIdx);
|
|
40943
41065
|
}
|
|
41066
|
+
|
|
41067
|
+
// src/emailPush/server.ts
|
|
41068
|
+
var import_smtp_server = require("smtp-server");
|
|
41069
|
+
var import_mailparser = require("mailparser");
|
|
41070
|
+
|
|
41071
|
+
// src/emailPush/tls.ts
|
|
41072
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
41073
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
41074
|
+
async function loadEmailPushTls(params) {
|
|
41075
|
+
const certPath = import_node_path4.default.join(params.dir, "cert.pem");
|
|
41076
|
+
const keyPath = import_node_path4.default.join(params.dir, "key.pem");
|
|
41077
|
+
const warn = params.warn ?? ((m) => console.warn(`[email-push-tls] ${m}`));
|
|
41078
|
+
if (!import_node_fs2.default.existsSync(certPath) || !import_node_fs2.default.existsSync(keyPath)) {
|
|
41079
|
+
warn(
|
|
41080
|
+
`Email-push TLS requested but ${certPath} or ${keyPath} is missing. Place a PEM cert+key under that directory to enable STARTTLS, or disable TLS in the consumer config to silence this warning.`
|
|
41081
|
+
);
|
|
41082
|
+
return void 0;
|
|
41083
|
+
}
|
|
41084
|
+
return {
|
|
41085
|
+
cert: import_node_fs2.default.readFileSync(certPath),
|
|
41086
|
+
key: import_node_fs2.default.readFileSync(keyPath)
|
|
41087
|
+
};
|
|
41088
|
+
}
|
|
41089
|
+
|
|
41090
|
+
// src/emailPush/server.ts
|
|
41091
|
+
async function defaultLoadTls(dir, warn) {
|
|
41092
|
+
return loadEmailPushTls({ dir, warn });
|
|
41093
|
+
}
|
|
41094
|
+
function classifyMessage(parsed) {
|
|
41095
|
+
const haystack = `${parsed.subject ?? ""} ${parsed.text ?? ""}`.toLowerCase();
|
|
41096
|
+
if (/person|people|human/.test(haystack)) return "people";
|
|
41097
|
+
if (/vehicle|car|truck/.test(haystack)) return "vehicle";
|
|
41098
|
+
if (/dog[_\s-]?cat|pet|animal/.test(haystack)) return "animal";
|
|
41099
|
+
if (/face/.test(haystack)) return "face";
|
|
41100
|
+
if (/package|parcel/.test(haystack)) return "package";
|
|
41101
|
+
if (/doorbell|ring(?:ing)?\s+button/.test(haystack)) return "doorbell";
|
|
41102
|
+
if (/motion|alarm|alert|detect/.test(haystack)) return "motion";
|
|
41103
|
+
return "other";
|
|
41104
|
+
}
|
|
41105
|
+
function getCameraEmailAddress(cameraId, domain) {
|
|
41106
|
+
return `cam-${cameraId}@${domain}`;
|
|
41107
|
+
}
|
|
41108
|
+
function createEmailPushServer(params) {
|
|
41109
|
+
const log = {
|
|
41110
|
+
debug: params.logger?.debug ?? (() => {
|
|
41111
|
+
}),
|
|
41112
|
+
info: params.logger?.info ?? (() => {
|
|
41113
|
+
}),
|
|
41114
|
+
warn: params.logger?.warn ?? (() => {
|
|
41115
|
+
}),
|
|
41116
|
+
error: params.logger?.error ?? (() => {
|
|
41117
|
+
})
|
|
41118
|
+
};
|
|
41119
|
+
let config = params.config;
|
|
41120
|
+
let server;
|
|
41121
|
+
let status = buildInitialStatus(config);
|
|
41122
|
+
function parseRecipient(rcpt) {
|
|
41123
|
+
const [local, domain] = rcpt.toLowerCase().split("@");
|
|
41124
|
+
if (!local || !domain) return void 0;
|
|
41125
|
+
return { local, domain };
|
|
41126
|
+
}
|
|
41127
|
+
function resolveCameraIdFromRecipient(rcpt) {
|
|
41128
|
+
const parsed = parseRecipient(rcpt);
|
|
41129
|
+
if (!parsed) return void 0;
|
|
41130
|
+
if (parsed.domain !== config.domain.toLowerCase()) return void 0;
|
|
41131
|
+
const match = parsed.local.match(/^cam-(.+)$/);
|
|
41132
|
+
if (!match || !match[1]) return void 0;
|
|
41133
|
+
return params.cameraResolver(match[1]);
|
|
41134
|
+
}
|
|
41135
|
+
async function handleIncomingMessage(cameraId, recipient, raw) {
|
|
41136
|
+
let parsed;
|
|
41137
|
+
try {
|
|
41138
|
+
parsed = await (0, import_mailparser.simpleParser)(raw);
|
|
41139
|
+
} catch (err) {
|
|
41140
|
+
log.warn(
|
|
41141
|
+
`Failed to parse mail for ${cameraId}: ${err instanceof Error ? err.message : err}`
|
|
41142
|
+
);
|
|
41143
|
+
status.messagesRejected++;
|
|
41144
|
+
return;
|
|
41145
|
+
}
|
|
41146
|
+
const receivedAtMs = Date.now();
|
|
41147
|
+
const event = {
|
|
41148
|
+
cameraId,
|
|
41149
|
+
recipient,
|
|
41150
|
+
inferredType: classifyMessage(parsed),
|
|
41151
|
+
receivedAtMs,
|
|
41152
|
+
subject: parsed.subject ?? "",
|
|
41153
|
+
from: typeof parsed.from === "object" && parsed.from !== null && "text" in parsed.from ? String(parsed.from.text) : "",
|
|
41154
|
+
bodyExcerpt: (parsed.text ?? "").slice(0, 500)
|
|
41155
|
+
};
|
|
41156
|
+
status.messagesAccepted++;
|
|
41157
|
+
log.info(
|
|
41158
|
+
`Email push for camera=${cameraId} type=${event.inferredType} subject="${event.subject.slice(0, 80)}"`
|
|
41159
|
+
);
|
|
41160
|
+
emitEmailPushEvent(event);
|
|
41161
|
+
}
|
|
41162
|
+
async function start() {
|
|
41163
|
+
if (server) {
|
|
41164
|
+
log.debug("startEmailPushServer called but server already running");
|
|
41165
|
+
return;
|
|
41166
|
+
}
|
|
41167
|
+
const tlsOptions = config.tls ? await (params.loadTls ?? defaultLoadTls)(
|
|
41168
|
+
config.tlsDir ?? "./email-push-tls",
|
|
41169
|
+
(m) => log.warn(m)
|
|
41170
|
+
) : void 0;
|
|
41171
|
+
status = buildInitialStatus(config);
|
|
41172
|
+
const next = new import_smtp_server.SMTPServer({
|
|
41173
|
+
authOptional: !config.requireAuth,
|
|
41174
|
+
disabledCommands: config.requireAuth ? [] : ["AUTH"],
|
|
41175
|
+
allowInsecureAuth: !config.tls,
|
|
41176
|
+
size: config.maxMessageBytes,
|
|
41177
|
+
logger: {
|
|
41178
|
+
info: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
41179
|
+
debug: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
41180
|
+
error: (...args) => log.warn(`[smtp] ${args.map((a) => String(a)).join(" ")}`)
|
|
41181
|
+
},
|
|
41182
|
+
...tlsOptions ? { secure: false, ...tlsOptions } : {},
|
|
41183
|
+
onConnect(session, callback) {
|
|
41184
|
+
log.info(
|
|
41185
|
+
`SMTP connect from ${session.remoteAddress} (sessionId=${session.id})`
|
|
41186
|
+
);
|
|
41187
|
+
callback();
|
|
41188
|
+
},
|
|
41189
|
+
onClose(session) {
|
|
41190
|
+
log.debug(
|
|
41191
|
+
`SMTP close ${session.remoteAddress} (sessionId=${session.id})`
|
|
41192
|
+
);
|
|
41193
|
+
},
|
|
41194
|
+
onMailFrom(address, session, callback) {
|
|
41195
|
+
log.info(
|
|
41196
|
+
`SMTP MAIL FROM ${address.address} (sessionId=${session.id})`
|
|
41197
|
+
);
|
|
41198
|
+
callback();
|
|
41199
|
+
},
|
|
41200
|
+
onAuth(auth, session, callback) {
|
|
41201
|
+
const expectedUser = config.authUsername;
|
|
41202
|
+
const expectedPass = config.authPassword;
|
|
41203
|
+
if (!expectedUser || !expectedPass) {
|
|
41204
|
+
log.warn(
|
|
41205
|
+
`SMTP AUTH rejected from ${session.remoteAddress} (sessionId=${session.id}): server has no credentials configured`
|
|
41206
|
+
);
|
|
41207
|
+
return callback(new Error("Email push auth not configured"));
|
|
41208
|
+
}
|
|
41209
|
+
const stripDomain = (u) => {
|
|
41210
|
+
const at = u.lastIndexOf("@");
|
|
41211
|
+
if (at < 0) return u;
|
|
41212
|
+
const local = u.slice(0, at);
|
|
41213
|
+
const domain = u.slice(at + 1).toLowerCase();
|
|
41214
|
+
return domain === config.domain.toLowerCase() ? local : u;
|
|
41215
|
+
};
|
|
41216
|
+
const triedUserNorm = stripDomain(auth.username ?? "");
|
|
41217
|
+
const expectedUserNorm = stripDomain(expectedUser);
|
|
41218
|
+
if (triedUserNorm === expectedUserNorm && auth.password === expectedPass) {
|
|
41219
|
+
log.info(
|
|
41220
|
+
`SMTP AUTH ok method=${auth.method} user=${auth.username} (sessionId=${session.id})`
|
|
41221
|
+
);
|
|
41222
|
+
return callback(null, { user: auth.username });
|
|
41223
|
+
}
|
|
41224
|
+
log.warn(
|
|
41225
|
+
`SMTP AUTH FAIL method=${auth.method} from=${session.remoteAddress} triedUser="${auth.username}" expectedUser="${expectedUser}" triedPasswordLen=${auth.password?.length ?? 0} (sessionId=${session.id})`
|
|
41226
|
+
);
|
|
41227
|
+
return callback(new Error("Invalid username or password"));
|
|
41228
|
+
},
|
|
41229
|
+
onRcptTo(address, _session, callback) {
|
|
41230
|
+
const cameraId = resolveCameraIdFromRecipient(address.address);
|
|
41231
|
+
if (!cameraId) {
|
|
41232
|
+
status.messagesRejected++;
|
|
41233
|
+
return callback(
|
|
41234
|
+
new Error(
|
|
41235
|
+
`Unknown recipient ${address.address} (not registered)`
|
|
41236
|
+
)
|
|
41237
|
+
);
|
|
41238
|
+
}
|
|
41239
|
+
callback();
|
|
41240
|
+
},
|
|
41241
|
+
onData(stream, session, callback) {
|
|
41242
|
+
const chunks = [];
|
|
41243
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
41244
|
+
stream.on("end", () => {
|
|
41245
|
+
const recipients = session.envelope.rcptTo?.map((r) => r.address) ?? [];
|
|
41246
|
+
const buffer = Buffer.concat(chunks);
|
|
41247
|
+
const matched = recipients.map((r) => ({
|
|
41248
|
+
recipient: r,
|
|
41249
|
+
cameraId: resolveCameraIdFromRecipient(r)
|
|
41250
|
+
})).filter(
|
|
41251
|
+
(m) => Boolean(m.cameraId)
|
|
41252
|
+
);
|
|
41253
|
+
if (matched.length === 0) {
|
|
41254
|
+
status.messagesRejected++;
|
|
41255
|
+
return callback(new Error("No recognised recipients"));
|
|
41256
|
+
}
|
|
41257
|
+
Promise.all(
|
|
41258
|
+
matched.map(
|
|
41259
|
+
(m) => handleIncomingMessage(m.cameraId, m.recipient, buffer)
|
|
41260
|
+
)
|
|
41261
|
+
).then(() => callback()).catch((err) => {
|
|
41262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41263
|
+
log.error(`Email push pipeline error: ${msg}`);
|
|
41264
|
+
status.lastErrorMessage = msg;
|
|
41265
|
+
callback(new Error(msg));
|
|
41266
|
+
});
|
|
41267
|
+
});
|
|
41268
|
+
stream.on("error", (err) => {
|
|
41269
|
+
log.warn(`SMTP stream error: ${err.message}`);
|
|
41270
|
+
callback(err);
|
|
41271
|
+
});
|
|
41272
|
+
}
|
|
41273
|
+
});
|
|
41274
|
+
next.on("error", (err) => {
|
|
41275
|
+
status.lastErrorMessage = err.message;
|
|
41276
|
+
log.error(`Email push server error: ${err.message}`);
|
|
41277
|
+
});
|
|
41278
|
+
await new Promise((resolve, reject) => {
|
|
41279
|
+
next.listen(config.port, config.bindHost, () => {
|
|
41280
|
+
status.running = true;
|
|
41281
|
+
status.startedAtMs = Date.now();
|
|
41282
|
+
log.info(
|
|
41283
|
+
`Email push SMTP listening on ${config.bindHost}:${config.port} (domain=${config.domain}, auth=${config.requireAuth}, tls=${config.tls})`
|
|
41284
|
+
);
|
|
41285
|
+
resolve();
|
|
41286
|
+
});
|
|
41287
|
+
next.once("error", reject);
|
|
41288
|
+
});
|
|
41289
|
+
server = next;
|
|
41290
|
+
}
|
|
41291
|
+
async function stop() {
|
|
41292
|
+
if (!server) return;
|
|
41293
|
+
const active = server;
|
|
41294
|
+
server = void 0;
|
|
41295
|
+
await new Promise((resolve) => {
|
|
41296
|
+
active.close(() => resolve());
|
|
41297
|
+
});
|
|
41298
|
+
status.running = false;
|
|
41299
|
+
log.info("Email push SMTP server stopped");
|
|
41300
|
+
}
|
|
41301
|
+
async function restart() {
|
|
41302
|
+
await stop();
|
|
41303
|
+
await start();
|
|
41304
|
+
}
|
|
41305
|
+
return {
|
|
41306
|
+
start,
|
|
41307
|
+
stop,
|
|
41308
|
+
restart,
|
|
41309
|
+
getStatus() {
|
|
41310
|
+
return { ...status };
|
|
41311
|
+
},
|
|
41312
|
+
updateConfig(next) {
|
|
41313
|
+
config = next;
|
|
41314
|
+
}
|
|
41315
|
+
};
|
|
41316
|
+
}
|
|
41317
|
+
function buildInitialStatus(config) {
|
|
41318
|
+
return {
|
|
41319
|
+
enabled: true,
|
|
41320
|
+
running: false,
|
|
41321
|
+
port: config.port,
|
|
41322
|
+
bindHost: config.bindHost,
|
|
41323
|
+
domain: config.domain,
|
|
41324
|
+
requireAuth: config.requireAuth,
|
|
41325
|
+
tls: config.tls,
|
|
41326
|
+
messagesAccepted: 0,
|
|
41327
|
+
messagesRejected: 0,
|
|
41328
|
+
startedAtMs: void 0,
|
|
41329
|
+
lastErrorMessage: void 0
|
|
41330
|
+
};
|
|
41331
|
+
}
|
|
40944
41332
|
// Annotate the CommonJS export names for ESM import in node:
|
|
40945
41333
|
0 && (module.exports = {
|
|
40946
41334
|
AesStreamDecryptor,
|
|
@@ -41114,6 +41502,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41114
41502
|
ReolinkCgiApi,
|
|
41115
41503
|
ReolinkHttpClient,
|
|
41116
41504
|
Rfc4571Muxer,
|
|
41505
|
+
_resetEmailPushBusForTests,
|
|
41117
41506
|
abilitiesHasAny,
|
|
41118
41507
|
aesDecrypt,
|
|
41119
41508
|
aesEncrypt,
|
|
@@ -41159,6 +41548,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41159
41548
|
createBaichuanEndpointsServer,
|
|
41160
41549
|
createDebugGateLogger,
|
|
41161
41550
|
createDiagnosticsBundle,
|
|
41551
|
+
createEmailPushServer,
|
|
41162
41552
|
createLogger,
|
|
41163
41553
|
createMjpegBoundary,
|
|
41164
41554
|
createNativeStream,
|
|
@@ -41186,6 +41576,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41186
41576
|
discoverViaTcpPortScan,
|
|
41187
41577
|
discoverViaUdpBroadcast,
|
|
41188
41578
|
discoverViaUdpDirect,
|
|
41579
|
+
emitEmailPushEvent,
|
|
41189
41580
|
encodeHeader,
|
|
41190
41581
|
encodeMotionScopeBitmap,
|
|
41191
41582
|
encodeMotionSensitivityListXml,
|
|
@@ -41202,10 +41593,14 @@ function base64DecodeToBytes(b64) {
|
|
|
41202
41593
|
flattenAbilitiesForChannel,
|
|
41203
41594
|
formatMjpegFrame,
|
|
41204
41595
|
fullCoverageScope,
|
|
41596
|
+
getCameraEmailAddress,
|
|
41205
41597
|
getConstructedVideoStreamOptions,
|
|
41598
|
+
getEmailPushCameraResolver,
|
|
41206
41599
|
getGlobalLogger,
|
|
41207
41600
|
getH265NalType,
|
|
41601
|
+
getLastEmailPushEvent,
|
|
41208
41602
|
getMjpegContentType,
|
|
41603
|
+
getRecentEmailPushEvents,
|
|
41209
41604
|
getSupportItemForChannel,
|
|
41210
41605
|
getVideoStream,
|
|
41211
41606
|
getVideoclipClientInfo,
|
|
@@ -41220,12 +41615,15 @@ function base64DecodeToBytes(b64) {
|
|
|
41220
41615
|
isTcpFailureThatShouldFallbackToUdp,
|
|
41221
41616
|
isValidH264AnnexBAccessUnit,
|
|
41222
41617
|
isValidH265AnnexBAccessUnit,
|
|
41618
|
+
loadEmailPushTls,
|
|
41619
|
+
mapEmailPushInferredType,
|
|
41223
41620
|
maskUid,
|
|
41224
41621
|
md5HexUpper,
|
|
41225
41622
|
md5StrModern,
|
|
41226
41623
|
normalizeDayNightMode,
|
|
41227
41624
|
normalizeOpenClose,
|
|
41228
41625
|
normalizeUid,
|
|
41626
|
+
onEmailPushEvent,
|
|
41229
41627
|
packetizeAacAdtsFrame,
|
|
41230
41628
|
packetizeAacRawFrame,
|
|
41231
41629
|
packetizeH264,
|
|
@@ -41243,6 +41641,7 @@ function base64DecodeToBytes(b64) {
|
|
|
41243
41641
|
runMultifocalDiagnosticsConsecutively,
|
|
41244
41642
|
sampleStreams,
|
|
41245
41643
|
sanitizeFixtureData,
|
|
41644
|
+
setEmailPushCameraResolver,
|
|
41246
41645
|
setGlobalLogger,
|
|
41247
41646
|
splitAnnexBToNalPayloads,
|
|
41248
41647
|
splitAnnexBToNals,
|