@apocaliss92/scrypted-reolink-native 0.5.27 → 0.5.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/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/camera.ts +89 -17
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -807,6 +807,13 @@ export class ReolinkCamera
|
|
|
807
807
|
private batteryUpdateTimer: NodeJS.Timeout | undefined;
|
|
808
808
|
private periodicStarted = false;
|
|
809
809
|
private statusPollTimer: NodeJS.Timeout | undefined;
|
|
810
|
+
// Off-handle for the global email-push bus subscription. Tied to
|
|
811
|
+
// the camera (Scrypted device) lifecycle, NOT to the Baichuan api
|
|
812
|
+
// — the plugin destroys the api on every idle_disconnect (battery
|
|
813
|
+
// cams every ~30s), and an api-scoped bridge would be dead in
|
|
814
|
+
// that window. The camera-scoped subscription stays alive across
|
|
815
|
+
// every reconnect cycle so SMTP motion always finds a listener.
|
|
816
|
+
private emailPushBusOff?: () => void;
|
|
810
817
|
|
|
811
818
|
// Cooldown timestamps to prevent alignAuxDevicesState from overwriting recently set states
|
|
812
819
|
// Camera can take 10+ seconds to reflect state changes in its API response
|
|
@@ -1557,13 +1564,14 @@ export class ReolinkCamera
|
|
|
1557
1564
|
// throw new Error("IP Address is required for TCP devices");
|
|
1558
1565
|
// }
|
|
1559
1566
|
|
|
1560
|
-
//
|
|
1561
|
-
//
|
|
1562
|
-
//
|
|
1563
|
-
//
|
|
1564
|
-
// the
|
|
1565
|
-
//
|
|
1566
|
-
|
|
1567
|
+
// The lib's `emailPushCameraId` auto-bridge would die every time
|
|
1568
|
+
// the plugin calls `cleanupBaichuanApi` (every idle_disconnect on
|
|
1569
|
+
// battery cams — every ~30s of idle). In that window SMTP motion
|
|
1570
|
+
// events emitted on the global bus have no listener and are lost.
|
|
1571
|
+
// Instead, the camera subscribes to the bus directly in `init()`
|
|
1572
|
+
// via `subscribeToEmailPushBus()` — that subscription is tied to
|
|
1573
|
+
// the camera (Scrypted device) lifecycle, not to the api object,
|
|
1574
|
+
// so it survives every api recreate / idle disconnect / wake.
|
|
1567
1575
|
|
|
1568
1576
|
return {
|
|
1569
1577
|
host: ipAddress,
|
|
@@ -1575,9 +1583,6 @@ export class ReolinkCamera
|
|
|
1575
1583
|
udpDiscoveryMethod: discoveryMethod,
|
|
1576
1584
|
// NOTE: idleDisconnect is NOT set here - the library handles it internally
|
|
1577
1585
|
// based on the battery status detected during connection
|
|
1578
|
-
...(eligibleForEmailPush && this.nativeId
|
|
1579
|
-
? { emailPushCameraId: this.nativeId }
|
|
1580
|
-
: {}),
|
|
1581
1586
|
};
|
|
1582
1587
|
}
|
|
1583
1588
|
|
|
@@ -2756,6 +2761,74 @@ export class ReolinkCamera
|
|
|
2756
2761
|
}
|
|
2757
2762
|
}
|
|
2758
2763
|
|
|
2764
|
+
/**
|
|
2765
|
+
* Subscribe to the lib's global email-push bus and translate every
|
|
2766
|
+
* matching SMTP delivery into a synthetic `ReolinkSimpleEvent` fed
|
|
2767
|
+
* to this camera's `onSimpleEvent` — same code path used by native
|
|
2768
|
+
* Baichuan push so `motionDetected` flips uniformly.
|
|
2769
|
+
*
|
|
2770
|
+
* Subscription lifetime is tied to the camera (init → release), NOT
|
|
2771
|
+
* to the Baichuan api: the plugin destroys the api on every
|
|
2772
|
+
* idle_disconnect (battery cams sleep every ~30s of idle), and a
|
|
2773
|
+
* lib-level api auto-bridge would be dead in that window. The
|
|
2774
|
+
* camera-level subscription stays alive across every reconnect so
|
|
2775
|
+
* SMTP motion always finds a listener and lands on `onSimpleEvent`.
|
|
2776
|
+
*/
|
|
2777
|
+
private async subscribeToEmailPushBus(): Promise<void> {
|
|
2778
|
+
if (this.emailPushBusOff) return;
|
|
2779
|
+
if (this.isOnNvr || this.multiFocalDevice) return;
|
|
2780
|
+
const ownNativeId = this.nativeId ?? "";
|
|
2781
|
+
if (!ownNativeId) return;
|
|
2782
|
+
const logger = this.getBaichuanLogger();
|
|
2783
|
+
try {
|
|
2784
|
+
const { onEmailPushEvent, mapEmailPushInferredType } = await import(
|
|
2785
|
+
"@apocaliss92/nodelink-js"
|
|
2786
|
+
);
|
|
2787
|
+
this.emailPushBusOff = onEmailPushEvent((event) => {
|
|
2788
|
+
if (event.cameraId !== ownNativeId) return;
|
|
2789
|
+
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
2790
|
+
const mapped = mapEmailPushInferredType(event.inferredType);
|
|
2791
|
+
logger.log(
|
|
2792
|
+
`E-mail Push event received (type=${event.inferredType}) → dispatching ${mapped}`,
|
|
2793
|
+
);
|
|
2794
|
+
try {
|
|
2795
|
+
this.onSimpleEvent({
|
|
2796
|
+
type: mapped,
|
|
2797
|
+
channel,
|
|
2798
|
+
timestamp: event.receivedAtMs,
|
|
2799
|
+
});
|
|
2800
|
+
// Fan out a generic motion for AI sub-types so motion-only
|
|
2801
|
+
// consumers still flip — mirrors lib's per-api behaviour.
|
|
2802
|
+
if (mapped !== "motion" && mapped !== "doorbell") {
|
|
2803
|
+
this.onSimpleEvent({
|
|
2804
|
+
type: "motion",
|
|
2805
|
+
channel,
|
|
2806
|
+
timestamp: event.receivedAtMs,
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
} catch (e) {
|
|
2810
|
+
logger.warn(
|
|
2811
|
+
`E-mail Push: onSimpleEvent dispatch threw: ${e?.message || String(e)}`,
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
logger.log("E-mail Push: subscribed to global bus");
|
|
2816
|
+
} catch (e) {
|
|
2817
|
+
logger.warn(
|
|
2818
|
+
`E-mail Push: bus subscribe failed: ${e?.message || String(e)}`,
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
private unsubscribeFromEmailPushBus(): void {
|
|
2824
|
+
if (this.emailPushBusOff) {
|
|
2825
|
+
try {
|
|
2826
|
+
this.emailPushBusOff();
|
|
2827
|
+
} catch {}
|
|
2828
|
+
this.emailPushBusOff = undefined;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2759
2832
|
/**
|
|
2760
2833
|
* Refresh the `lastPicture` cache by fetching a live snapshot via
|
|
2761
2834
|
* the Baichuan API. Used on motion events for battery cameras: the
|
|
@@ -2949,8 +3022,7 @@ export class ReolinkCamera
|
|
|
2949
3022
|
this.statusPollTimer && clearInterval(this.statusPollTimer);
|
|
2950
3023
|
this.sleepCheckTimer && clearInterval(this.sleepCheckTimer);
|
|
2951
3024
|
this.batteryUpdateTimer && clearInterval(this.batteryUpdateTimer);
|
|
2952
|
-
|
|
2953
|
-
// inside `resetBaichuanClient` — no separate teardown needed.
|
|
3025
|
+
this.unsubscribeFromEmailPushBus();
|
|
2954
3026
|
this.resetBaichuanClient();
|
|
2955
3027
|
this.plugin.camerasMap.delete(this.id);
|
|
2956
3028
|
}
|
|
@@ -3620,11 +3692,11 @@ export class ReolinkCamera
|
|
|
3620
3692
|
|
|
3621
3693
|
this.startPeriodicTasks();
|
|
3622
3694
|
|
|
3623
|
-
//
|
|
3624
|
-
//
|
|
3625
|
-
//
|
|
3626
|
-
// `
|
|
3627
|
-
|
|
3695
|
+
// Subscribe to the lib's email-push bus BEFORE ensureClient so
|
|
3696
|
+
// SMTP motion is never lost during the api's connection window.
|
|
3697
|
+
// Lifetime is tied to the camera (released in `release()`) so it
|
|
3698
|
+
// survives every `cleanupBaichuanApi` cycle on battery cams.
|
|
3699
|
+
await this.subscribeToEmailPushBus();
|
|
3628
3700
|
|
|
3629
3701
|
await this.ensureClient();
|
|
3630
3702
|
|