@apocaliss92/nodelink-js 0.4.32 → 0.4.33

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 CHANGED
@@ -49,14 +49,46 @@ Battery cameras (Argus, Go, …) can't reliably keep a TCP/ONVIF push subscripti
49
49
  - **Auto** — manager: **Email Push** tab in the camera modal → *Auto-configure*. Scrypted: open the camera's Settings → **E-mail Push** group → *Auto-configure from Email Push Server*. Both call `setupEmailPushToManager` under the hood.
50
50
  - **API** — `await api.setupEmailPushToManager({ managerHost, managerPort, recipientLocalPart, domain, authUsername, authPassword, triggerTypes, attachmentType }, channel)`. The lib auto-wraps a bare username as `<user@domain>` so MAIL FROM stays RFC 5321 compliant.
51
51
  - **Manual** — fill the Reolink app form: server = manager LAN IP, port = `2525`, sender = `authUsername`, password = `authPassword`, TLS off, receiver = `cam-<id>@<domain>`.
52
- 4. On motion, the camera sends an e-mail. The intake parses it, classifies the trigger (`MD` / `people` / `vehicle`), and emits an `EmailPushEvent` on the shared bus. Snapshots are kept in memory only (no disk persistence) they're forwarded to whatever per-event handler the consumer wires (MQTT image entities in the manager, `motionDetected` flip in the Scrypted plugin).
52
+ 4. On motion, the camera sends an e-mail. The intake parses it, classifies the trigger (`MD` / `people` / `vehicle`), and emits an `EmailPushEvent` on the shared bus. From there it lands on `api.onSimpleEvent` automaticallysee "Unified event stream" below.
53
53
 
54
54
  See [documentation/baichuan-api/email.md](./documentation/baichuan-api/email.md) for the full Baichuan API surface and [documentation/baichuan-api/time.md](./documentation/baichuan-api/time.md) for the related NTP / DST / system clock setters.
55
55
 
56
+ ### Unified event stream (since 0.4.32)
57
+
58
+ Construct the api with `emailPushCameraId` (and optionally `emailPushChannel`) and the library wires the SMTP bus into the api's internal `simpleEventListeners` for you. Every consumer registered via `api.onSimpleEvent(...)` then receives native Baichuan push **and** SMTP-delivered motion through the same stream — no separate `onEmailPushEvent` subscription needed. The bridge survives TCP transient disconnects (it's a pure JS fan-out, not a network operation) and is released automatically by `close()`.
59
+
60
+ ```ts
61
+ import { ReolinkBaichuanApi } from "@apocaliss92/nodelink-js";
62
+
63
+ const api = new ReolinkBaichuanApi({
64
+ host: "192.168.1.100",
65
+ username: "admin",
66
+ password: "secret",
67
+ transport: "udp",
68
+ uid: "REOLINK-UID-HERE",
69
+
70
+ // Auto-bridge SMTP motion into api.onSimpleEvent. Match the same
71
+ // cameraId your `createEmailPushServer({ cameraResolver })` returns
72
+ // (typically the camera's nativeId / stable identifier).
73
+ emailPushCameraId: "my-battery-cam",
74
+ emailPushChannel: 0, // optional, default 0
75
+ });
76
+
77
+ await api.login();
78
+ await api.onSimpleEvent((event) => {
79
+ // Fires for both native Baichuan push AND SMTP motion.
80
+ console.log(event.type, "on ch", event.channel, "@", event.timestamp);
81
+ });
82
+ ```
83
+
84
+ For single-owner consumers that already manage their own bridge (e.g. a custom resolver scheme), the lower-level `api.subscribeEmailPushEvents({ cameraId | match, channel })` is still exposed.
85
+
56
86
  **Library entry points**:
57
87
 
58
88
  - `createEmailPushServer({ config, cameraResolver, logger, loadTls? })` — factory returning `{ start, stop, restart, updateConfig, getStatus }`
59
- - `subscribeEmailPushEvents({ cameraId? | match?, channel? })` on a `ReolinkBaichuanApi` instance bridges per-camera SMTP events into the same `onSimpleEvent` stream native Baichuan push uses
89
+ - `new ReolinkBaichuanApi({ ..., emailPushCameraId, emailPushChannel? })` — auto-bridge into `onSimpleEvent` (recommended)
90
+ - `api.subscribeEmailPushEvents({ cameraId | match, channel? })` — manual per-api bridge with custom matcher
91
+ - `onEmailPushEvent(handler)` — raw global bus subscription (use when you need the full `EmailPushEvent` payload, not just the synthesised `ReolinkSimpleEvent`)
60
92
  - `getRecentEmailPushEvents(limit)` — bounded in-memory ring buffer of accepted deliveries
61
93
  - `setupEmailPushToManager(params, channel)` — orchestrator: `setEmail` + `setEmailTask` + optional `testEmail`
62
94
  - `getEmail`, `setEmail`, `testEmail`, `getEmailTask`, `setEmailTask` — low-level Baichuan accessors
@@ -11818,6 +11818,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
11818
11818
  // check every 10s
11819
11819
  simpleEventWatchdogSilenceThresholdMs = 5 * 6e4;
11820
11820
  // 5 min without events
11821
+ /**
11822
+ * Whether the silence-based resubscribe path of the watchdog is
11823
+ * enabled. On UDP (battery cameras) silence is the *normal* state
11824
+ * while the device sleeps — firing `ensureSimpleEventSubscribed`
11825
+ * every 5 minutes wakes the camera on every tick, drains the
11826
+ * battery, and is observably wrong because the cam emits a
11827
+ * sleep/awake push when it actually wakes for motion.
11828
+ *
11829
+ * Defaults: `false` on UDP, `true` on TCP / `auto`. The subscription-
11830
+ * failed recovery path (Case 2) stays active regardless — it only
11831
+ * runs when the connection is alive, doesn't wake anyone, and is
11832
+ * useful on every transport when the initial subscribe call lost
11833
+ * the response packet.
11834
+ */
11835
+ eventWatchdogSilenceResubscribeEnabled = true;
11821
11836
  statePollingInterval;
11822
11837
  udpSleepInferenceInterval;
11823
11838
  udpLastInferredSleepStateByChannel = /* @__PURE__ */ new Map();
@@ -12775,6 +12790,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
12775
12790
  } else {
12776
12791
  this.eventResubscribeEnabled = opts.transport !== "udp";
12777
12792
  }
12793
+ const explicitWatchdogResubscribe = opts.enableEventWatchdogSilenceResubscribe;
12794
+ if (typeof explicitWatchdogResubscribe === "boolean") {
12795
+ this.eventWatchdogSilenceResubscribeEnabled = explicitWatchdogResubscribe;
12796
+ } else {
12797
+ this.eventWatchdogSilenceResubscribeEnabled = opts.transport !== "udp";
12798
+ }
12778
12799
  const maxSessions = opts.maxDedicatedSessionsBeforeReboot;
12779
12800
  if (typeof maxSessions === "number" && Number.isFinite(maxSessions) && maxSessions > 0) {
12780
12801
  this.maxDedicatedSessionsBeforeReboot = Math.floor(maxSessions);
@@ -13597,6 +13618,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
13597
13618
  if (this.simpleEventSubscribed && this.simpleEventLastReceivedAt > 0) {
13598
13619
  const silence = now - this.simpleEventLastReceivedAt;
13599
13620
  if (silence < this.simpleEventWatchdogSilenceThresholdMs) return;
13621
+ if (!this.eventWatchdogSilenceResubscribeEnabled) {
13622
+ this.logger.debug?.(
13623
+ `[ReolinkBaichuanApi] event watchdog: silence-based resubscribe disabled (UDP / battery), skipping`,
13624
+ { host: this.host, silenceMs: silence }
13625
+ );
13626
+ this.simpleEventLastReceivedAt = now;
13627
+ return;
13628
+ }
13600
13629
  (this.logger.warn ?? this.logger.log).call(
13601
13630
  this.logger,
13602
13631
  `[ReolinkBaichuanApi] event watchdog: no events for ${Math.round(silence / 6e4)} min, forcing resubscribe`,
@@ -24753,4 +24782,4 @@ export {
24753
24782
  isTcpFailureThatShouldFallbackToUdp,
24754
24783
  autoDetectDeviceType
24755
24784
  };
24756
- //# sourceMappingURL=chunk-5Z7NVPV6.js.map
24785
+ //# sourceMappingURL=chunk-OZL6C2YJ.js.map