@apocaliss92/nodelink-js 0.4.26 → 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/dist/index.d.ts CHANGED
@@ -2978,6 +2978,8 @@ export declare function buildStartZoomFocusXml(channelId: number, movePos: numbe
2978
2978
  */
2979
2979
  export declare function buildWhiteLedStateXml(channelId: number, state: number): string;
2980
2980
 
2981
+ declare type CameraResolver = (recipient: string) => string | undefined;
2982
+
2981
2983
  /**
2982
2984
  * Capture all relevant API responses from a single device (or NVR channel)
2983
2985
  * and write them as JSON/XML fixtures into `outDir`.
@@ -4079,6 +4081,27 @@ export declare function createDiagnosticsBundle(params: {
4079
4081
  diagnosticsPath: string;
4080
4082
  }>;
4081
4083
 
4084
+ export declare function createEmailPushServer(params: CreateEmailPushServerParams): EmailPushServerInstance;
4085
+
4086
+ export declare interface CreateEmailPushServerParams {
4087
+ config: EmailPushServerConfig;
4088
+ /**
4089
+ * Resolver mapping the `cam-<id>` localpart (the part before `@domain`)
4090
+ * to the consumer's internal camera identifier. Return `undefined` to
4091
+ * reject the recipient at RCPT TO.
4092
+ */
4093
+ cameraResolver: (localCameraId: string) => string | undefined;
4094
+ /** Optional logger; defaults to no-op. */
4095
+ logger?: EmailPushLogger;
4096
+ /**
4097
+ * Optional TLS loader override. Default uses `loadEmailPushTls` which
4098
+ * reads `cert.pem` + `key.pem` from `config.tlsDir`. Consumers (e.g.
4099
+ * Scrypted) can pass their own loader to source the material from a
4100
+ * different store.
4101
+ */
4102
+ loadTls?: (dir: string, warn: (m: string) => void) => Promise<EmailPushTlsOptions | undefined>;
4103
+ }
4104
+
4082
4105
  export declare function createLogger(options?: {
4083
4106
  base?: LoggerLike;
4084
4107
  level?: LogLevel;
@@ -4773,6 +4796,115 @@ export declare interface EmailConfig {
4773
4796
  /** Patch payload accepted by SetEmail. All fields optional — only supplied ones are written. */
4774
4797
  export declare type EmailConfigPatch = Partial<Omit<EmailConfig, "senderMaxLen" | "pwdMaxLen" | "emailAttachAbility">>;
4775
4798
 
4799
+ export declare interface EmailPushEvent {
4800
+ cameraId: string;
4801
+ /** Original recipient address that matched this camera. */
4802
+ recipient: string;
4803
+ inferredType: EmailPushInferredType;
4804
+ /** Reception timestamp on the manager (ms since epoch). */
4805
+ receivedAtMs: number;
4806
+ subject: string;
4807
+ from: string;
4808
+ /** Raw body text excerpt (max 500 chars). */
4809
+ bodyExcerpt: string;
4810
+ }
4811
+
4812
+ /**
4813
+ * Email push event bus.
4814
+ *
4815
+ * Stateless in-memory dispatcher. Lives in the library so any consumer
4816
+ * (the manager app, the Scrypted plugin, integration tests) can subscribe
4817
+ * and emit through the same surface without dragging in the
4818
+ * `smtp-server` / `mailparser` native deps.
4819
+ *
4820
+ * The bus is intentionally:
4821
+ * - **Process-local** — every consumer has its own bus instance (the
4822
+ * module-level state below). No cross-process or cross-import sharing.
4823
+ * - **Bounded** — keeps the last `MAX_GLOBAL_EVENTS` (default 300) for
4824
+ * the "recent emails" panel, and a single-event reference per camera
4825
+ * for the verifyDelivery race short-circuit. Footprint is O(cameras)
4826
+ * plus O(MAX_GLOBAL_EVENTS).
4827
+ * - **Attachment-free** — only metadata (subject, from, body excerpt,
4828
+ * classified type) is retained; the actual JPEG/MP4 payloads from the
4829
+ * camera are never persisted by the bus.
4830
+ */
4831
+ /** Trigger classification produced by parsing the camera's email body. */
4832
+ export declare type EmailPushInferredType = "motion" | "people" | "vehicle" | "animal" | "face" | "package" | "doorbell" | "other";
4833
+
4834
+ export declare interface EmailPushLogger {
4835
+ debug?: (msg: string) => void;
4836
+ info?: (msg: string) => void;
4837
+ warn?: (msg: string) => void;
4838
+ error?: (msg: string) => void;
4839
+ }
4840
+
4841
+ export declare interface EmailPushServerConfig {
4842
+ /** SMTP listen port. */
4843
+ port: number;
4844
+ /** Bind host (`0.0.0.0` to accept on LAN). */
4845
+ bindHost: string;
4846
+ /** Virtual mail domain — used to extract the camera id from RCPT TO. */
4847
+ domain: string;
4848
+ /** Require AUTH PLAIN / AUTH LOGIN. */
4849
+ requireAuth: boolean;
4850
+ /** Expected AUTH username (stored bare, e.g. `nodelink-abcd`). */
4851
+ authUsername: string;
4852
+ /** Expected AUTH password. */
4853
+ authPassword: string;
4854
+ /** Enable STARTTLS using cert+key under `tlsDir`. */
4855
+ tls: boolean;
4856
+ /** Directory holding cert.pem + key.pem when `tls: true`. */
4857
+ tlsDir?: string;
4858
+ /** Maximum accepted message size in bytes. */
4859
+ maxMessageBytes: number;
4860
+ }
4861
+
4862
+ export declare interface EmailPushServerInstance {
4863
+ start(): Promise<void>;
4864
+ stop(): Promise<void>;
4865
+ restart(): Promise<void>;
4866
+ getStatus(): EmailPushServerStatus;
4867
+ /**
4868
+ * Replace the current config; the next `start` / `restart` picks it up.
4869
+ * Hot updates of config without restart are not supported on purpose —
4870
+ * SMTP server bind options need a fresh `listen()`.
4871
+ */
4872
+ updateConfig(next: EmailPushServerConfig): void;
4873
+ }
4874
+
4875
+ export declare interface EmailPushServerStatus {
4876
+ enabled: boolean;
4877
+ running: boolean;
4878
+ port: number;
4879
+ bindHost: string;
4880
+ domain: string;
4881
+ requireAuth: boolean;
4882
+ tls: boolean;
4883
+ /** Cumulative messages accepted since start. */
4884
+ messagesAccepted: number;
4885
+ /** Cumulative messages rejected (unknown recipient, parse fail, etc.). */
4886
+ messagesRejected: number;
4887
+ startedAtMs: number | undefined;
4888
+ lastErrorMessage: string | undefined;
4889
+ }
4890
+
4891
+ /**
4892
+ * Optional STARTTLS support for the email-push SMTP server.
4893
+ *
4894
+ * The library does NOT auto-generate a self-signed cert: doing so portably
4895
+ * across Node versions requires a heavy dependency (`selfsigned` /
4896
+ * `node-forge`), and Reolink cameras typically accept self-signed certs
4897
+ * only when the user has consciously opted into TLS.
4898
+ *
4899
+ * Consumers point `loadEmailPushTls` at a directory containing `cert.pem`
4900
+ * and `key.pem`. When either is missing the function returns `undefined`
4901
+ * and logs through the provided logger (or `console.warn` by default).
4902
+ */
4903
+ export declare interface EmailPushTlsOptions {
4904
+ cert: Buffer;
4905
+ key: Buffer;
4906
+ }
4907
+
4776
4908
  /** Email schedule configuration (cmdId=216/217). */
4777
4909
  export declare interface EmailTaskConfig {
4778
4910
  channelId: number;
@@ -4799,6 +4931,9 @@ export declare interface EmailTaskScheduleItem {
4799
4931
  /** Whether the alert body carries the standard human-readable text. */
4800
4932
  export declare type EmailTextType = "withText" | "noText";
4801
4933
 
4934
+ /** Internal: SMTP server uses this to dispatch an event into the bus. */
4935
+ export declare function emitEmailPushEvent(event: EmailPushEvent): void;
4936
+
4802
4937
  /**
4803
4938
  * Encoding configuration (getEnc response).
4804
4939
  * cmdId=56 (GetEnc) — payload is wrapped in `Compression`, not `Enc`.
@@ -4959,6 +5094,8 @@ export declare interface EncStreamPatch {
4959
5094
  */
4960
5095
  export declare function ensureXmlHeader(xml: string): string;
4961
5096
 
5097
+ declare type EventHandler = (event: EmailPushEvent) => void;
5098
+
4962
5099
  export declare interface Events {
4963
5100
  channel?: number;
4964
5101
  ai?: AIState;
@@ -5076,6 +5213,9 @@ export declare interface FtpTaskConfig {
5076
5213
  */
5077
5214
  export declare function fullCoverageScope(columns: number, rows: number, width?: number, height?: number): MotionZoneScope;
5078
5215
 
5216
+ /** Build the recipient address assigned to a given camera. */
5217
+ export declare function getCameraEmailAddress(cameraId: string, domain: string): string;
5218
+
5079
5219
  /**
5080
5220
  * Get constructed video stream options for all available profiles.
5081
5221
  *
@@ -5083,6 +5223,8 @@ export declare function fullCoverageScope(columns: number, rows: number, width?:
5083
5223
  */
5084
5224
  export declare function getConstructedVideoStreamOptions(channel: number, api: ReolinkBaichuanApi, rtspHost: string, rtspPort?: number, rtspUsername?: string, rtspPassword?: string): Promise<ResponseMediaStreamOptions[]>;
5085
5225
 
5226
+ export declare function getEmailPushCameraResolver(): CameraResolver;
5227
+
5086
5228
  export declare function getGlobalLogger(): Logger_2;
5087
5229
 
5088
5230
  /**
@@ -5091,8 +5233,23 @@ export declare function getGlobalLogger(): Logger_2;
5091
5233
  */
5092
5234
  export declare function getH265NalType(nalPayload: Buffer): number | null;
5093
5235
 
5236
+ /**
5237
+ * Read the most recent event observed for a camera. Returns `undefined`
5238
+ * when no email-push event has been received since the consumer started.
5239
+ */
5240
+ export declare function getLastEmailPushEvent(cameraId: string): EmailPushEvent | undefined;
5241
+
5094
5242
  export declare function getMjpegContentType(boundary: string): string;
5095
5243
 
5244
+ /**
5245
+ * Return a snapshot of the most recent email-push events across all
5246
+ * cameras (most recent first). Capped at `MAX_GLOBAL_EVENTS` (300).
5247
+ *
5248
+ * `limit` is clamped to the buffer cap so callers can ask for a smaller
5249
+ * page without juggling the storage limit.
5250
+ */
5251
+ export declare function getRecentEmailPushEvents(limit?: number): EmailPushEvent[];
5252
+
5096
5253
  /**
5097
5254
  * Result of getRecordingVideo() - a fully muxed MP4 with stats.
5098
5255
  */
@@ -5770,6 +5927,19 @@ export declare type LastSleepProbe = {
5770
5927
  status: SleepStatus;
5771
5928
  };
5772
5929
 
5930
+ /**
5931
+ * Read the user-provided cert/key pair. Returns `undefined` (and logs a
5932
+ * warning) when either file is missing.
5933
+ */
5934
+ export declare function loadEmailPushTls(params: LoadTlsParams): Promise<EmailPushTlsOptions | undefined>;
5935
+
5936
+ export declare interface LoadTlsParams {
5937
+ /** Directory holding `cert.pem` + `key.pem`. */
5938
+ dir: string;
5939
+ /** Optional logger; defaults to console-based warn. */
5940
+ warn?: (msg: string) => void;
5941
+ }
5942
+
5773
5943
  export declare type Logger = Logger_2;
5774
5944
 
5775
5945
  declare interface Logger_2 {
@@ -5796,6 +5966,15 @@ export declare type LoginResponseValue = {
5796
5966
 
5797
5967
  export declare type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
5798
5968
 
5969
+ /**
5970
+ * Map the email-push classifier output onto the lib's broader
5971
+ * ReolinkSimpleEvent type union. Shared by `ReolinkBaichuanApi
5972
+ * .subscribeEmailPushEvents` (the per-camera bridge) and any consumer
5973
+ * that wants the same mapping at the global bus level (e.g. the
5974
+ * manager-app's events-manager) so both code paths stay in sync.
5975
+ */
5976
+ export declare function mapEmailPushInferredType(inferred: EmailPushInferredType): "motion" | "doorbell" | "people" | "vehicle" | "animal" | "face" | "package";
5977
+
5799
5978
  /**
5800
5979
  * Privacy mask configuration (`getMask`). The `Shelter` block always
5801
5980
  * contains `enable` + `maxNum` + `shelterList`; tracked-shelter sub-
@@ -6168,6 +6347,12 @@ export declare type NvrChannelsSummaryCacheEntry = {
6168
6347
  devices: ReolinkBaichuanDeviceSummary[];
6169
6348
  };
6170
6349
 
6350
+ /**
6351
+ * Register a handler invoked for every accepted email-push event. Returns
6352
+ * an `off` function.
6353
+ */
6354
+ export declare function onEmailPushEvent(handler: EventHandler): () => void;
6355
+
6171
6356
  /**
6172
6357
  * Online user list response.
6173
6358
  */
@@ -6556,7 +6741,7 @@ export declare class ReolinkBaichuanApi {
6556
6741
  * general socket is created, logged in, and all event/push/guard listeners
6557
6742
  * are re-attached automatically.
6558
6743
  *
6559
- * This is a **no-op** when the API is already {@link isReady}.
6744
+ * This is a **no-op** when the API is already ready (see `isReadyState()`).
6560
6745
  *
6561
6746
  * @throws If `close()` was called — the API is permanently closed and a new
6562
6747
  * instance must be created.
@@ -6570,7 +6755,7 @@ export declare class ReolinkBaichuanApi {
6570
6755
  /**
6571
6756
  * Attach event, push, channelInfo, and guard listeners to the current
6572
6757
  * "general" client. Called from the constructor and from
6573
- * {@link reconnectGeneralSocket}.
6758
+ * `reconnectGeneralSocket()`.
6574
6759
  */
6575
6760
  private setupGeneralClientListeners;
6576
6761
  /**
@@ -7093,6 +7278,27 @@ export declare class ReolinkBaichuanApi {
7093
7278
  * Called on every socket close event.
7094
7279
  */
7095
7280
  private maybeRebootOnEconnresetStorm;
7281
+ /**
7282
+ * Bind this API instance to the global email-push bus so that incoming
7283
+ * SMTP-delivered motion / AI events for the matching camera surface on
7284
+ * this instance's standard `onSimpleEvent` channel. The consumer keeps
7285
+ * a single subscription (`onSimpleEvent`) and gets both the native
7286
+ * Baichuan push and the email-push transport on the same stream.
7287
+ *
7288
+ * - `cameraId` shorthand: match events with `event.cameraId === cameraId`.
7289
+ * - `match`: arbitrary predicate (e.g. when the consumer uses a
7290
+ * nickname-based mapping or wants to handle multiple recipients).
7291
+ *
7292
+ * Returns an `off()` handle. Safe to call repeatedly — each call
7293
+ * registers its own listener.
7294
+ */
7295
+ subscribeEmailPushEvents(params: {
7296
+ cameraId: string;
7297
+ channel?: number;
7298
+ } | {
7299
+ match: (event: EmailPushEvent) => boolean;
7300
+ channel?: number;
7301
+ }): () => void;
7096
7302
  /**
7097
7303
  * Subscribe to minimal high-level events.
7098
7304
  * The API manages Baichuan subscribe/unsubscribe automatically.
@@ -7109,7 +7315,7 @@ export declare class ReolinkBaichuanApi {
7109
7315
  * Subscribe to per-frame detection events sourced from the BcMedia
7110
7316
  * `additionalHeader` block on active video streams.
7111
7317
  *
7112
- * Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
7318
+ * Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
7113
7319
  * one event fires for every I-frame / P-frame that carries an overlay block.
7114
7320
  * Coordinates are reported in normalized [0, 1] fractions of the source
7115
7321
  * frame, so the same box renders correctly on mainStream, subStream, and
@@ -7128,7 +7334,7 @@ export declare class ReolinkBaichuanApi {
7128
7334
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
7129
7335
  * with class label and confidence) without managing a video stream yourself.
7130
7336
  *
7131
- * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
7337
+ * Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
7132
7338
  * `(channel, profile)` tuple the API ensures the corresponding video stream
7133
7339
  * is running (the pool socket may already be shared with a regular consumer),
7134
7340
  * forwards every box-bearing `additionalHeader` to your callback, and tears
@@ -8745,7 +8951,7 @@ export declare class ReolinkBaichuanApi {
8745
8951
  * Field meaning per stream:
8746
8952
  * - `audio` — 0/1 toggle
8747
8953
  * - `width`/`height` — resolution in pixels. Must be one of the
8748
- * resolutions returned by {@link getStreamInfoList}.
8954
+ * resolutions returned by `getStreamInfoList()`.
8749
8955
  * - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
8750
8956
  * - `frameRate` — fps. Must match the table from `getStreamInfoList`.
8751
8957
  * - `videoEncType` — `"h264"` or `"h265"`
@@ -10848,6 +11054,9 @@ export declare interface ReplayHttpServerOptions {
10848
11054
  isNvr?: boolean;
10849
11055
  }
10850
11056
 
11057
+ /** Test hook: drop all subscribers, recent events, and reset the resolver. */
11058
+ export declare function _resetEmailPushBusForTests(): void;
11059
+
10851
11060
  /**
10852
11061
  * ResponseMediaStreamOptions - Stream profile metadata
10853
11062
  */
@@ -11220,6 +11429,13 @@ export declare function sampleStreams(opts: StreamSamplingOptions): Promise<void
11220
11429
  */
11221
11430
  export declare function sanitizeFixtureData(value: unknown): unknown;
11222
11431
 
11432
+ /**
11433
+ * Set the callback that maps a recipient address (e.g.
11434
+ * `cam-abc@nodelink.local`) to a known cameraId. Returning `undefined`
11435
+ * rejects the recipient at RCPT TO.
11436
+ */
11437
+ export declare function setEmailPushCameraResolver(resolver: CameraResolver): void;
11438
+
11223
11439
  export declare function setGlobalLogger(logger: LoggerLike | Logger_2 | undefined): void;
11224
11440
 
11225
11441
  /**