@apocaliss92/nodelink-js 0.4.24 → 0.4.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/README.md +4 -2
- package/dist/{chunk-JYHK2ZSH.js → chunk-NQ7D5TLR.js} +241 -27
- package/dist/chunk-NQ7D5TLR.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +201 -26
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +569 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -6
- package/dist/index.d.ts +269 -5
- package/dist/index.js +290 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-JYHK2ZSH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
NVR_HUB_EXACT_TYPES,
|
|
14
14
|
NVR_HUB_MODEL_PATTERNS,
|
|
15
15
|
ReolinkBaichuanApi,
|
|
16
|
+
_resetEmailPushBusForTests,
|
|
16
17
|
abilitiesHasAny,
|
|
17
18
|
asLogger,
|
|
18
19
|
autoDetectDeviceType,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
discoverViaTcpPortScan,
|
|
36
37
|
discoverViaUdpBroadcast,
|
|
37
38
|
discoverViaUdpDirect,
|
|
39
|
+
emitEmailPushEvent,
|
|
38
40
|
encodeHeader,
|
|
39
41
|
encodeMotionSensitivityListXml,
|
|
40
42
|
encodeShelterCoord,
|
|
@@ -43,21 +45,27 @@ import {
|
|
|
43
45
|
extractCanvasFromShelterXml,
|
|
44
46
|
flattenAbilitiesForChannel,
|
|
45
47
|
getConstructedVideoStreamOptions,
|
|
48
|
+
getEmailPushCameraResolver,
|
|
46
49
|
getGlobalLogger,
|
|
50
|
+
getLastEmailPushEvent,
|
|
51
|
+
getRecentEmailPushEvents,
|
|
47
52
|
getSupportItemForChannel,
|
|
48
53
|
getVideoStream,
|
|
49
54
|
isDualLenseModel,
|
|
50
55
|
isNvrHubModel,
|
|
51
56
|
isTcpFailureThatShouldFallbackToUdp,
|
|
57
|
+
mapEmailPushInferredType,
|
|
52
58
|
maskUid,
|
|
53
59
|
normalizeUid,
|
|
60
|
+
onEmailPushEvent,
|
|
54
61
|
parseSupportXml,
|
|
55
62
|
patchAiDetectCfgXml,
|
|
56
63
|
patchMotionSensitivityListXml,
|
|
57
64
|
patchShelterXml,
|
|
65
|
+
setEmailPushCameraResolver,
|
|
58
66
|
setGlobalLogger,
|
|
59
67
|
xmlIndicatesFloodlight
|
|
60
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-NQ7D5TLR.js";
|
|
61
69
|
import {
|
|
62
70
|
ReolinkCgiApi,
|
|
63
71
|
ReolinkHttpClient,
|
|
@@ -6003,9 +6011,9 @@ var BaichuanMjpegServer = class extends EventEmitter5 {
|
|
|
6003
6011
|
this.started = true;
|
|
6004
6012
|
const port = this.options.port ?? 8080;
|
|
6005
6013
|
const host = this.options.host ?? "0.0.0.0";
|
|
6006
|
-
const
|
|
6014
|
+
const path3 = this.options.path ?? "/mjpeg";
|
|
6007
6015
|
this.httpServer = http5.createServer((req, res) => {
|
|
6008
|
-
this.handleRequest(req, res,
|
|
6016
|
+
this.handleRequest(req, res, path3);
|
|
6009
6017
|
});
|
|
6010
6018
|
return new Promise((resolve, reject) => {
|
|
6011
6019
|
this.httpServer.on("error", (err) => {
|
|
@@ -6015,9 +6023,9 @@ var BaichuanMjpegServer = class extends EventEmitter5 {
|
|
|
6015
6023
|
this.httpServer.listen(port, host, () => {
|
|
6016
6024
|
this.log(
|
|
6017
6025
|
"info",
|
|
6018
|
-
`MJPEG server started on http://${host}:${port}${
|
|
6026
|
+
`MJPEG server started on http://${host}:${port}${path3}`
|
|
6019
6027
|
);
|
|
6020
|
-
this.emit("started", { host, port, path:
|
|
6028
|
+
this.emit("started", { host, port, path: path3 });
|
|
6021
6029
|
resolve();
|
|
6022
6030
|
});
|
|
6023
6031
|
});
|
|
@@ -8274,6 +8282,272 @@ function base64DecodeToBytes(b64) {
|
|
|
8274
8282
|
}
|
|
8275
8283
|
return out.subarray(0, outIdx);
|
|
8276
8284
|
}
|
|
8285
|
+
|
|
8286
|
+
// src/emailPush/server.ts
|
|
8287
|
+
import { SMTPServer } from "smtp-server";
|
|
8288
|
+
import { simpleParser } from "mailparser";
|
|
8289
|
+
|
|
8290
|
+
// src/emailPush/tls.ts
|
|
8291
|
+
import fs2 from "fs";
|
|
8292
|
+
import path2 from "path";
|
|
8293
|
+
async function loadEmailPushTls(params) {
|
|
8294
|
+
const certPath = path2.join(params.dir, "cert.pem");
|
|
8295
|
+
const keyPath = path2.join(params.dir, "key.pem");
|
|
8296
|
+
const warn = params.warn ?? ((m) => console.warn(`[email-push-tls] ${m}`));
|
|
8297
|
+
if (!fs2.existsSync(certPath) || !fs2.existsSync(keyPath)) {
|
|
8298
|
+
warn(
|
|
8299
|
+
`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.`
|
|
8300
|
+
);
|
|
8301
|
+
return void 0;
|
|
8302
|
+
}
|
|
8303
|
+
return {
|
|
8304
|
+
cert: fs2.readFileSync(certPath),
|
|
8305
|
+
key: fs2.readFileSync(keyPath)
|
|
8306
|
+
};
|
|
8307
|
+
}
|
|
8308
|
+
|
|
8309
|
+
// src/emailPush/server.ts
|
|
8310
|
+
async function defaultLoadTls(dir, warn) {
|
|
8311
|
+
return loadEmailPushTls({ dir, warn });
|
|
8312
|
+
}
|
|
8313
|
+
function classifyMessage(parsed) {
|
|
8314
|
+
const haystack = `${parsed.subject ?? ""} ${parsed.text ?? ""}`.toLowerCase();
|
|
8315
|
+
if (/person|people|human/.test(haystack)) return "people";
|
|
8316
|
+
if (/vehicle|car|truck/.test(haystack)) return "vehicle";
|
|
8317
|
+
if (/dog[_\s-]?cat|pet|animal/.test(haystack)) return "animal";
|
|
8318
|
+
if (/face/.test(haystack)) return "face";
|
|
8319
|
+
if (/package|parcel/.test(haystack)) return "package";
|
|
8320
|
+
if (/doorbell|ring(?:ing)?\s+button/.test(haystack)) return "doorbell";
|
|
8321
|
+
if (/motion|alarm|alert|detect/.test(haystack)) return "motion";
|
|
8322
|
+
return "other";
|
|
8323
|
+
}
|
|
8324
|
+
function getCameraEmailAddress(cameraId, domain) {
|
|
8325
|
+
return `cam-${cameraId}@${domain}`;
|
|
8326
|
+
}
|
|
8327
|
+
function createEmailPushServer(params) {
|
|
8328
|
+
const log = {
|
|
8329
|
+
debug: params.logger?.debug ?? (() => {
|
|
8330
|
+
}),
|
|
8331
|
+
info: params.logger?.info ?? (() => {
|
|
8332
|
+
}),
|
|
8333
|
+
warn: params.logger?.warn ?? (() => {
|
|
8334
|
+
}),
|
|
8335
|
+
error: params.logger?.error ?? (() => {
|
|
8336
|
+
})
|
|
8337
|
+
};
|
|
8338
|
+
let config = params.config;
|
|
8339
|
+
let server;
|
|
8340
|
+
let status = buildInitialStatus(config);
|
|
8341
|
+
function parseRecipient(rcpt) {
|
|
8342
|
+
const [local, domain] = rcpt.toLowerCase().split("@");
|
|
8343
|
+
if (!local || !domain) return void 0;
|
|
8344
|
+
return { local, domain };
|
|
8345
|
+
}
|
|
8346
|
+
function resolveCameraIdFromRecipient(rcpt) {
|
|
8347
|
+
const parsed = parseRecipient(rcpt);
|
|
8348
|
+
if (!parsed) return void 0;
|
|
8349
|
+
if (parsed.domain !== config.domain.toLowerCase()) return void 0;
|
|
8350
|
+
const match = parsed.local.match(/^cam-(.+)$/);
|
|
8351
|
+
if (!match || !match[1]) return void 0;
|
|
8352
|
+
return params.cameraResolver(match[1]);
|
|
8353
|
+
}
|
|
8354
|
+
async function handleIncomingMessage(cameraId, recipient, raw) {
|
|
8355
|
+
let parsed;
|
|
8356
|
+
try {
|
|
8357
|
+
parsed = await simpleParser(raw);
|
|
8358
|
+
} catch (err) {
|
|
8359
|
+
log.warn(
|
|
8360
|
+
`Failed to parse mail for ${cameraId}: ${err instanceof Error ? err.message : err}`
|
|
8361
|
+
);
|
|
8362
|
+
status.messagesRejected++;
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
8365
|
+
const receivedAtMs = Date.now();
|
|
8366
|
+
const event = {
|
|
8367
|
+
cameraId,
|
|
8368
|
+
recipient,
|
|
8369
|
+
inferredType: classifyMessage(parsed),
|
|
8370
|
+
receivedAtMs,
|
|
8371
|
+
subject: parsed.subject ?? "",
|
|
8372
|
+
from: typeof parsed.from === "object" && parsed.from !== null && "text" in parsed.from ? String(parsed.from.text) : "",
|
|
8373
|
+
bodyExcerpt: (parsed.text ?? "").slice(0, 500)
|
|
8374
|
+
};
|
|
8375
|
+
status.messagesAccepted++;
|
|
8376
|
+
log.info(
|
|
8377
|
+
`Email push for camera=${cameraId} type=${event.inferredType} subject="${event.subject.slice(0, 80)}"`
|
|
8378
|
+
);
|
|
8379
|
+
emitEmailPushEvent(event);
|
|
8380
|
+
}
|
|
8381
|
+
async function start() {
|
|
8382
|
+
if (server) {
|
|
8383
|
+
log.debug("startEmailPushServer called but server already running");
|
|
8384
|
+
return;
|
|
8385
|
+
}
|
|
8386
|
+
const tlsOptions = config.tls ? await (params.loadTls ?? defaultLoadTls)(
|
|
8387
|
+
config.tlsDir ?? "./email-push-tls",
|
|
8388
|
+
(m) => log.warn(m)
|
|
8389
|
+
) : void 0;
|
|
8390
|
+
status = buildInitialStatus(config);
|
|
8391
|
+
const next = new SMTPServer({
|
|
8392
|
+
authOptional: !config.requireAuth,
|
|
8393
|
+
disabledCommands: config.requireAuth ? [] : ["AUTH"],
|
|
8394
|
+
allowInsecureAuth: !config.tls,
|
|
8395
|
+
size: config.maxMessageBytes,
|
|
8396
|
+
logger: {
|
|
8397
|
+
info: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
8398
|
+
debug: (...args) => log.debug(`[smtp] ${args.map((a) => String(a)).join(" ")}`),
|
|
8399
|
+
error: (...args) => log.warn(`[smtp] ${args.map((a) => String(a)).join(" ")}`)
|
|
8400
|
+
},
|
|
8401
|
+
...tlsOptions ? { secure: false, ...tlsOptions } : {},
|
|
8402
|
+
onConnect(session, callback) {
|
|
8403
|
+
log.info(
|
|
8404
|
+
`SMTP connect from ${session.remoteAddress} (sessionId=${session.id})`
|
|
8405
|
+
);
|
|
8406
|
+
callback();
|
|
8407
|
+
},
|
|
8408
|
+
onClose(session) {
|
|
8409
|
+
log.debug(
|
|
8410
|
+
`SMTP close ${session.remoteAddress} (sessionId=${session.id})`
|
|
8411
|
+
);
|
|
8412
|
+
},
|
|
8413
|
+
onMailFrom(address, session, callback) {
|
|
8414
|
+
log.info(
|
|
8415
|
+
`SMTP MAIL FROM ${address.address} (sessionId=${session.id})`
|
|
8416
|
+
);
|
|
8417
|
+
callback();
|
|
8418
|
+
},
|
|
8419
|
+
onAuth(auth, session, callback) {
|
|
8420
|
+
const expectedUser = config.authUsername;
|
|
8421
|
+
const expectedPass = config.authPassword;
|
|
8422
|
+
if (!expectedUser || !expectedPass) {
|
|
8423
|
+
log.warn(
|
|
8424
|
+
`SMTP AUTH rejected from ${session.remoteAddress} (sessionId=${session.id}): server has no credentials configured`
|
|
8425
|
+
);
|
|
8426
|
+
return callback(new Error("Email push auth not configured"));
|
|
8427
|
+
}
|
|
8428
|
+
const stripDomain = (u) => {
|
|
8429
|
+
const at = u.lastIndexOf("@");
|
|
8430
|
+
if (at < 0) return u;
|
|
8431
|
+
const local = u.slice(0, at);
|
|
8432
|
+
const domain = u.slice(at + 1).toLowerCase();
|
|
8433
|
+
return domain === config.domain.toLowerCase() ? local : u;
|
|
8434
|
+
};
|
|
8435
|
+
const triedUserNorm = stripDomain(auth.username ?? "");
|
|
8436
|
+
const expectedUserNorm = stripDomain(expectedUser);
|
|
8437
|
+
if (triedUserNorm === expectedUserNorm && auth.password === expectedPass) {
|
|
8438
|
+
log.info(
|
|
8439
|
+
`SMTP AUTH ok method=${auth.method} user=${auth.username} (sessionId=${session.id})`
|
|
8440
|
+
);
|
|
8441
|
+
return callback(null, { user: auth.username });
|
|
8442
|
+
}
|
|
8443
|
+
log.warn(
|
|
8444
|
+
`SMTP AUTH FAIL method=${auth.method} from=${session.remoteAddress} triedUser="${auth.username}" expectedUser="${expectedUser}" triedPasswordLen=${auth.password?.length ?? 0} (sessionId=${session.id})`
|
|
8445
|
+
);
|
|
8446
|
+
return callback(new Error("Invalid username or password"));
|
|
8447
|
+
},
|
|
8448
|
+
onRcptTo(address, _session, callback) {
|
|
8449
|
+
const cameraId = resolveCameraIdFromRecipient(address.address);
|
|
8450
|
+
if (!cameraId) {
|
|
8451
|
+
status.messagesRejected++;
|
|
8452
|
+
return callback(
|
|
8453
|
+
new Error(
|
|
8454
|
+
`Unknown recipient ${address.address} (not registered)`
|
|
8455
|
+
)
|
|
8456
|
+
);
|
|
8457
|
+
}
|
|
8458
|
+
callback();
|
|
8459
|
+
},
|
|
8460
|
+
onData(stream, session, callback) {
|
|
8461
|
+
const chunks = [];
|
|
8462
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
8463
|
+
stream.on("end", () => {
|
|
8464
|
+
const recipients = session.envelope.rcptTo?.map((r) => r.address) ?? [];
|
|
8465
|
+
const buffer = Buffer.concat(chunks);
|
|
8466
|
+
const matched = recipients.map((r) => ({
|
|
8467
|
+
recipient: r,
|
|
8468
|
+
cameraId: resolveCameraIdFromRecipient(r)
|
|
8469
|
+
})).filter(
|
|
8470
|
+
(m) => Boolean(m.cameraId)
|
|
8471
|
+
);
|
|
8472
|
+
if (matched.length === 0) {
|
|
8473
|
+
status.messagesRejected++;
|
|
8474
|
+
return callback(new Error("No recognised recipients"));
|
|
8475
|
+
}
|
|
8476
|
+
Promise.all(
|
|
8477
|
+
matched.map(
|
|
8478
|
+
(m) => handleIncomingMessage(m.cameraId, m.recipient, buffer)
|
|
8479
|
+
)
|
|
8480
|
+
).then(() => callback()).catch((err) => {
|
|
8481
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8482
|
+
log.error(`Email push pipeline error: ${msg}`);
|
|
8483
|
+
status.lastErrorMessage = msg;
|
|
8484
|
+
callback(new Error(msg));
|
|
8485
|
+
});
|
|
8486
|
+
});
|
|
8487
|
+
stream.on("error", (err) => {
|
|
8488
|
+
log.warn(`SMTP stream error: ${err.message}`);
|
|
8489
|
+
callback(err);
|
|
8490
|
+
});
|
|
8491
|
+
}
|
|
8492
|
+
});
|
|
8493
|
+
next.on("error", (err) => {
|
|
8494
|
+
status.lastErrorMessage = err.message;
|
|
8495
|
+
log.error(`Email push server error: ${err.message}`);
|
|
8496
|
+
});
|
|
8497
|
+
await new Promise((resolve, reject) => {
|
|
8498
|
+
next.listen(config.port, config.bindHost, () => {
|
|
8499
|
+
status.running = true;
|
|
8500
|
+
status.startedAtMs = Date.now();
|
|
8501
|
+
log.info(
|
|
8502
|
+
`Email push SMTP listening on ${config.bindHost}:${config.port} (domain=${config.domain}, auth=${config.requireAuth}, tls=${config.tls})`
|
|
8503
|
+
);
|
|
8504
|
+
resolve();
|
|
8505
|
+
});
|
|
8506
|
+
next.once("error", reject);
|
|
8507
|
+
});
|
|
8508
|
+
server = next;
|
|
8509
|
+
}
|
|
8510
|
+
async function stop() {
|
|
8511
|
+
if (!server) return;
|
|
8512
|
+
const active = server;
|
|
8513
|
+
server = void 0;
|
|
8514
|
+
await new Promise((resolve) => {
|
|
8515
|
+
active.close(() => resolve());
|
|
8516
|
+
});
|
|
8517
|
+
status.running = false;
|
|
8518
|
+
log.info("Email push SMTP server stopped");
|
|
8519
|
+
}
|
|
8520
|
+
async function restart() {
|
|
8521
|
+
await stop();
|
|
8522
|
+
await start();
|
|
8523
|
+
}
|
|
8524
|
+
return {
|
|
8525
|
+
start,
|
|
8526
|
+
stop,
|
|
8527
|
+
restart,
|
|
8528
|
+
getStatus() {
|
|
8529
|
+
return { ...status };
|
|
8530
|
+
},
|
|
8531
|
+
updateConfig(next) {
|
|
8532
|
+
config = next;
|
|
8533
|
+
}
|
|
8534
|
+
};
|
|
8535
|
+
}
|
|
8536
|
+
function buildInitialStatus(config) {
|
|
8537
|
+
return {
|
|
8538
|
+
enabled: true,
|
|
8539
|
+
running: false,
|
|
8540
|
+
port: config.port,
|
|
8541
|
+
bindHost: config.bindHost,
|
|
8542
|
+
domain: config.domain,
|
|
8543
|
+
requireAuth: config.requireAuth,
|
|
8544
|
+
tls: config.tls,
|
|
8545
|
+
messagesAccepted: 0,
|
|
8546
|
+
messagesRejected: 0,
|
|
8547
|
+
startedAtMs: void 0,
|
|
8548
|
+
lastErrorMessage: void 0
|
|
8549
|
+
};
|
|
8550
|
+
}
|
|
8277
8551
|
export {
|
|
8278
8552
|
AesStreamDecryptor,
|
|
8279
8553
|
AutodiscoveryClient,
|
|
@@ -8446,6 +8720,7 @@ export {
|
|
|
8446
8720
|
ReolinkCgiApi,
|
|
8447
8721
|
ReolinkHttpClient,
|
|
8448
8722
|
Rfc4571Muxer,
|
|
8723
|
+
_resetEmailPushBusForTests,
|
|
8449
8724
|
abilitiesHasAny,
|
|
8450
8725
|
aesDecrypt,
|
|
8451
8726
|
aesEncrypt,
|
|
@@ -8491,6 +8766,7 @@ export {
|
|
|
8491
8766
|
createBaichuanEndpointsServer,
|
|
8492
8767
|
createDebugGateLogger,
|
|
8493
8768
|
createDiagnosticsBundle,
|
|
8769
|
+
createEmailPushServer,
|
|
8494
8770
|
createLogger,
|
|
8495
8771
|
createMjpegBoundary,
|
|
8496
8772
|
createNativeStream,
|
|
@@ -8518,6 +8794,7 @@ export {
|
|
|
8518
8794
|
discoverViaTcpPortScan,
|
|
8519
8795
|
discoverViaUdpBroadcast,
|
|
8520
8796
|
discoverViaUdpDirect,
|
|
8797
|
+
emitEmailPushEvent,
|
|
8521
8798
|
encodeHeader,
|
|
8522
8799
|
encodeMotionScopeBitmap,
|
|
8523
8800
|
encodeMotionSensitivityListXml,
|
|
@@ -8534,10 +8811,14 @@ export {
|
|
|
8534
8811
|
flattenAbilitiesForChannel,
|
|
8535
8812
|
formatMjpegFrame,
|
|
8536
8813
|
fullCoverageScope,
|
|
8814
|
+
getCameraEmailAddress,
|
|
8537
8815
|
getConstructedVideoStreamOptions,
|
|
8816
|
+
getEmailPushCameraResolver,
|
|
8538
8817
|
getGlobalLogger,
|
|
8539
8818
|
getH265NalType,
|
|
8819
|
+
getLastEmailPushEvent,
|
|
8540
8820
|
getMjpegContentType,
|
|
8821
|
+
getRecentEmailPushEvents,
|
|
8541
8822
|
getSupportItemForChannel,
|
|
8542
8823
|
getVideoStream,
|
|
8543
8824
|
getVideoclipClientInfo,
|
|
@@ -8552,12 +8833,15 @@ export {
|
|
|
8552
8833
|
isTcpFailureThatShouldFallbackToUdp,
|
|
8553
8834
|
isValidH264AnnexBAccessUnit,
|
|
8554
8835
|
isValidH265AnnexBAccessUnit,
|
|
8836
|
+
loadEmailPushTls,
|
|
8837
|
+
mapEmailPushInferredType,
|
|
8555
8838
|
maskUid,
|
|
8556
8839
|
md5HexUpper,
|
|
8557
8840
|
md5StrModern,
|
|
8558
8841
|
normalizeDayNightMode,
|
|
8559
8842
|
normalizeOpenClose,
|
|
8560
8843
|
normalizeUid,
|
|
8844
|
+
onEmailPushEvent,
|
|
8561
8845
|
packetizeAacAdtsFrame,
|
|
8562
8846
|
packetizeAacRawFrame,
|
|
8563
8847
|
packetizeH264,
|
|
@@ -8575,6 +8859,7 @@ export {
|
|
|
8575
8859
|
runMultifocalDiagnosticsConsecutively,
|
|
8576
8860
|
sampleStreams,
|
|
8577
8861
|
sanitizeFixtureData,
|
|
8862
|
+
setEmailPushCameraResolver,
|
|
8578
8863
|
setGlobalLogger,
|
|
8579
8864
|
splitAnnexBToNalPayloads,
|
|
8580
8865
|
splitAnnexBToNals,
|