@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/dist/index.d.ts CHANGED
@@ -1075,6 +1075,19 @@ export declare class BaichuanEventEmitter {
1075
1075
  isSubscribed(): boolean;
1076
1076
  }
1077
1077
 
1078
+ /**
1079
+ * Floodlight status push (cmd_id 291 / MSG_ID_FLOODLIGHT_STATUS_LIST).
1080
+ * Emitted by the camera whenever the floodlight transitions on/off
1081
+ * (including the auto-off after the FloodlightManual duration expires).
1082
+ * `<status>0|1</status>` carries the actual current ON/OFF — this is the
1083
+ * only reliable source for the current manual state, since cmd 289 only
1084
+ * returns the FloodlightTask config.
1085
+ */
1086
+ export declare type BaichuanFloodlightStatusPush = {
1087
+ /** True when the floodlight is currently emitting light. */
1088
+ status: boolean;
1089
+ };
1090
+
1078
1091
  export declare type BaichuanFrame = {
1079
1092
  header: BaichuanHeader;
1080
1093
  /** Raw body bytes, exactly `bodyLen` bytes (extension+payload, if any). */
@@ -1747,6 +1760,22 @@ export declare type BaichuanSettingsPushCacheEntry = {
1747
1760
  dingdongList?: BaichuanCachedPush<BaichuanDingdongListPush>;
1748
1761
  sleepStatus?: BaichuanCachedPush<BaichuanSleepStatusPush>;
1749
1762
  coordinatePointList?: BaichuanCachedPush<BaichuanCoordinatePointListPush>;
1763
+ floodlightStatus?: BaichuanCachedPush<BaichuanFloodlightStatusPush>;
1764
+ sirenStatus?: BaichuanCachedPush<BaichuanSirenStatusPush>;
1765
+ };
1766
+
1767
+ /**
1768
+ * Siren status push (cmd_id 547 / MSG_ID_AUDIO_ALARM).
1769
+ * Emitted when the siren starts or stops playing. The Reolink firmware
1770
+ * stops the siren automatically after its built-in playback duration,
1771
+ * so polling cmd 547 by itself can race the auto-off; the push catches
1772
+ * the actual transitions.
1773
+ */
1774
+ export declare type BaichuanSirenStatusPush = {
1775
+ /** True when the siren is currently audible. */
1776
+ status: boolean;
1777
+ /** Whether the camera reports the siren as actively playing (some firmwares emit only this). */
1778
+ playing?: boolean;
1750
1779
  };
1751
1780
 
1752
1781
  export declare type BaichuanSleepState = {
@@ -2949,6 +2978,8 @@ export declare function buildStartZoomFocusXml(channelId: number, movePos: numbe
2949
2978
  */
2950
2979
  export declare function buildWhiteLedStateXml(channelId: number, state: number): string;
2951
2980
 
2981
+ declare type CameraResolver = (recipient: string) => string | undefined;
2982
+
2952
2983
  /**
2953
2984
  * Capture all relevant API responses from a single device (or NVR channel)
2954
2985
  * and write them as JSON/XML fixtures into `outDir`.
@@ -4050,6 +4081,27 @@ export declare function createDiagnosticsBundle(params: {
4050
4081
  diagnosticsPath: string;
4051
4082
  }>;
4052
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
+
4053
4105
  export declare function createLogger(options?: {
4054
4106
  base?: LoggerLike;
4055
4107
  level?: LogLevel;
@@ -4744,6 +4796,115 @@ export declare interface EmailConfig {
4744
4796
  /** Patch payload accepted by SetEmail. All fields optional — only supplied ones are written. */
4745
4797
  export declare type EmailConfigPatch = Partial<Omit<EmailConfig, "senderMaxLen" | "pwdMaxLen" | "emailAttachAbility">>;
4746
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
+
4747
4908
  /** Email schedule configuration (cmdId=216/217). */
4748
4909
  export declare interface EmailTaskConfig {
4749
4910
  channelId: number;
@@ -4770,6 +4931,9 @@ export declare interface EmailTaskScheduleItem {
4770
4931
  /** Whether the alert body carries the standard human-readable text. */
4771
4932
  export declare type EmailTextType = "withText" | "noText";
4772
4933
 
4934
+ /** Internal: SMTP server uses this to dispatch an event into the bus. */
4935
+ export declare function emitEmailPushEvent(event: EmailPushEvent): void;
4936
+
4773
4937
  /**
4774
4938
  * Encoding configuration (getEnc response).
4775
4939
  * cmdId=56 (GetEnc) — payload is wrapped in `Compression`, not `Enc`.
@@ -4930,6 +5094,8 @@ export declare interface EncStreamPatch {
4930
5094
  */
4931
5095
  export declare function ensureXmlHeader(xml: string): string;
4932
5096
 
5097
+ declare type EventHandler = (event: EmailPushEvent) => void;
5098
+
4933
5099
  export declare interface Events {
4934
5100
  channel?: number;
4935
5101
  ai?: AIState;
@@ -5047,6 +5213,9 @@ export declare interface FtpTaskConfig {
5047
5213
  */
5048
5214
  export declare function fullCoverageScope(columns: number, rows: number, width?: number, height?: number): MotionZoneScope;
5049
5215
 
5216
+ /** Build the recipient address assigned to a given camera. */
5217
+ export declare function getCameraEmailAddress(cameraId: string, domain: string): string;
5218
+
5050
5219
  /**
5051
5220
  * Get constructed video stream options for all available profiles.
5052
5221
  *
@@ -5054,6 +5223,8 @@ export declare function fullCoverageScope(columns: number, rows: number, width?:
5054
5223
  */
5055
5224
  export declare function getConstructedVideoStreamOptions(channel: number, api: ReolinkBaichuanApi, rtspHost: string, rtspPort?: number, rtspUsername?: string, rtspPassword?: string): Promise<ResponseMediaStreamOptions[]>;
5056
5225
 
5226
+ export declare function getEmailPushCameraResolver(): CameraResolver;
5227
+
5057
5228
  export declare function getGlobalLogger(): Logger_2;
5058
5229
 
5059
5230
  /**
@@ -5062,8 +5233,23 @@ export declare function getGlobalLogger(): Logger_2;
5062
5233
  */
5063
5234
  export declare function getH265NalType(nalPayload: Buffer): number | null;
5064
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
+
5065
5242
  export declare function getMjpegContentType(boundary: string): string;
5066
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
+
5067
5253
  /**
5068
5254
  * Result of getRecordingVideo() - a fully muxed MP4 with stats.
5069
5255
  */
@@ -5741,6 +5927,19 @@ export declare type LastSleepProbe = {
5741
5927
  status: SleepStatus;
5742
5928
  };
5743
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
+
5744
5943
  export declare type Logger = Logger_2;
5745
5944
 
5746
5945
  declare interface Logger_2 {
@@ -5767,6 +5966,15 @@ export declare type LoginResponseValue = {
5767
5966
 
5768
5967
  export declare type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
5769
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
+
5770
5978
  /**
5771
5979
  * Privacy mask configuration (`getMask`). The `Shelter` block always
5772
5980
  * contains `enable` + `maxNum` + `shelterList`; tracked-shelter sub-
@@ -6139,6 +6347,12 @@ export declare type NvrChannelsSummaryCacheEntry = {
6139
6347
  devices: ReolinkBaichuanDeviceSummary[];
6140
6348
  };
6141
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
+
6142
6356
  /**
6143
6357
  * Online user list response.
6144
6358
  */
@@ -6527,7 +6741,7 @@ export declare class ReolinkBaichuanApi {
6527
6741
  * general socket is created, logged in, and all event/push/guard listeners
6528
6742
  * are re-attached automatically.
6529
6743
  *
6530
- * 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()`).
6531
6745
  *
6532
6746
  * @throws If `close()` was called — the API is permanently closed and a new
6533
6747
  * instance must be created.
@@ -6541,7 +6755,7 @@ export declare class ReolinkBaichuanApi {
6541
6755
  /**
6542
6756
  * Attach event, push, channelInfo, and guard listeners to the current
6543
6757
  * "general" client. Called from the constructor and from
6544
- * {@link reconnectGeneralSocket}.
6758
+ * `reconnectGeneralSocket()`.
6545
6759
  */
6546
6760
  private setupGeneralClientListeners;
6547
6761
  /**
@@ -7064,6 +7278,27 @@ export declare class ReolinkBaichuanApi {
7064
7278
  * Called on every socket close event.
7065
7279
  */
7066
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;
7067
7302
  /**
7068
7303
  * Subscribe to minimal high-level events.
7069
7304
  * The API manages Baichuan subscribe/unsubscribe automatically.
@@ -7080,7 +7315,7 @@ export declare class ReolinkBaichuanApi {
7080
7315
  * Subscribe to per-frame detection events sourced from the BcMedia
7081
7316
  * `additionalHeader` block on active video streams.
7082
7317
  *
7083
- * Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
7318
+ * Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
7084
7319
  * one event fires for every I-frame / P-frame that carries an overlay block.
7085
7320
  * Coordinates are reported in normalized [0, 1] fractions of the source
7086
7321
  * frame, so the same box renders correctly on mainStream, subStream, and
@@ -7099,7 +7334,7 @@ export declare class ReolinkBaichuanApi {
7099
7334
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
7100
7335
  * with class label and confidence) without managing a video stream yourself.
7101
7336
  *
7102
- * 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
7103
7338
  * `(channel, profile)` tuple the API ensures the corresponding video stream
7104
7339
  * is running (the pool socket may already be shared with a regular consumer),
7105
7340
  * forwards every box-bearing `additionalHeader` to your callback, and tears
@@ -8716,7 +8951,7 @@ export declare class ReolinkBaichuanApi {
8716
8951
  * Field meaning per stream:
8717
8952
  * - `audio` — 0/1 toggle
8718
8953
  * - `width`/`height` — resolution in pixels. Must be one of the
8719
- * resolutions returned by {@link getStreamInfoList}.
8954
+ * resolutions returned by `getStreamInfoList()`.
8720
8955
  * - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
8721
8956
  * - `frameRate` — fps. Must match the table from `getStreamInfoList`.
8722
8957
  * - `videoEncType` — `"h264"` or `"h265"`
@@ -9033,6 +9268,25 @@ export declare class ReolinkBaichuanApi {
9033
9268
  getDingdongListFromPushCache(channel?: number): BaichuanCachedPush<BaichuanDingdongListPush> | undefined;
9034
9269
  getSleepStatusFromPushCache(channel?: number): BaichuanCachedPush<BaichuanSleepStatusPush> | undefined;
9035
9270
  getCoordinatePointListFromPushCache(channel?: number): BaichuanCachedPush<BaichuanCoordinatePointListPush> | undefined;
9271
+ /**
9272
+ * Last cmd_id 291 (FloodlightStatusList) push observed for the channel.
9273
+ * The camera emits this whenever the floodlight transitions on/off,
9274
+ * including the auto-off after the FloodlightManual duration. This is
9275
+ * the only reliable source for the current manual state because cmd 289
9276
+ * only returns the FloodlightTask config.
9277
+ *
9278
+ * Returns undefined when no push has been received yet.
9279
+ */
9280
+ getCachedFloodlightStatus(channel?: number): BaichuanCachedPush<BaichuanFloodlightStatusPush> | undefined;
9281
+ /**
9282
+ * Last cmd_id 547 (SirenStatusList) push observed for the channel.
9283
+ * Captures the actual on/off transitions including the firmware's
9284
+ * built-in auto-off after the siren playback duration expires —
9285
+ * polling cmd 547 alone can race that auto-off.
9286
+ *
9287
+ * Returns undefined when no push has been received yet.
9288
+ */
9289
+ getCachedSirenStatus(channel?: number): BaichuanCachedPush<BaichuanSirenStatusPush> | undefined;
9036
9290
  private isNvrLikeDevice;
9037
9291
  private sendPcapDerivedSettingsGetXml;
9038
9292
  /**
@@ -10800,6 +11054,9 @@ export declare interface ReplayHttpServerOptions {
10800
11054
  isNvr?: boolean;
10801
11055
  }
10802
11056
 
11057
+ /** Test hook: drop all subscribers, recent events, and reset the resolver. */
11058
+ export declare function _resetEmailPushBusForTests(): void;
11059
+
10803
11060
  /**
10804
11061
  * ResponseMediaStreamOptions - Stream profile metadata
10805
11062
  */
@@ -11172,6 +11429,13 @@ export declare function sampleStreams(opts: StreamSamplingOptions): Promise<void
11172
11429
  */
11173
11430
  export declare function sanitizeFixtureData(value: unknown): unknown;
11174
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
+
11175
11439
  export declare function setGlobalLogger(logger: LoggerLike | Logger_2 | undefined): void;
11176
11440
 
11177
11441
  /**