@apocaliss92/scrypted-reolink-native 0.1.32 → 0.1.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/dist/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apocaliss92/scrypted-reolink-native",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
5
5
  "author": "@apocaliss92",
6
6
  "license": "Apache",
@@ -33,7 +33,8 @@
33
33
  "ScryptedDeviceCreator",
34
34
  "DeviceProvider",
35
35
  "DeviceCreator",
36
- "Settings"
36
+ "Settings",
37
+ "HttpRequestHandler"
37
38
  ]
38
39
  },
39
40
  "dependencies": {
@@ -208,14 +208,27 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
208
208
  * Ensure Baichuan client is connected and ready
209
209
  */
210
210
  async ensureBaichuanClient(): Promise<ReolinkBaichuanApi> {
211
+ // Prevent concurrent login storms - check promise first
212
+ if (this.ensureClientPromise) return await this.ensureClientPromise;
213
+
211
214
  // Reuse existing client if socket is still connected and logged in
212
- if (this.baichuanApi && this.baichuanApi.client.isSocketConnected() && this.baichuanApi.client.loggedIn) {
213
- return this.baichuanApi;
215
+ // Check this AFTER checking the promise to avoid race conditions
216
+ if (this.baichuanApi) {
217
+ const isConnected = this.baichuanApi.client.isSocketConnected();
218
+ const isLoggedIn = this.baichuanApi.client.loggedIn;
219
+
220
+ // Only reuse if both conditions are true
221
+ if (isConnected && isLoggedIn) {
222
+ return this.baichuanApi;
223
+ }
224
+
225
+ // If socket is not connected or not logged in, cleanup the stale client
226
+ // This prevents leaking connections when the socket appears connected but isn't
227
+ const logger = this.getBaichuanLogger();
228
+ logger.log(`Stale client detected: connected=${isConnected}, loggedIn=${isLoggedIn}, cleaning up`);
229
+ await this.cleanupBaichuanApi();
214
230
  }
215
231
 
216
- // Prevent concurrent login storms
217
- if (this.ensureClientPromise) return await this.ensureClientPromise;
218
-
219
232
  // Apply backoff to avoid aggressive reconnection after disconnection
220
233
  if (this.lastDisconnectTime > 0) {
221
234
  const timeSinceDisconnect = Date.now() - this.lastDisconnectTime;
@@ -309,6 +322,13 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
309
322
 
310
323
  // Close listener
311
324
  this.closeListener = async () => {
325
+ // Prevent multiple concurrent cleanup operations
326
+ if (!this.baichuanApi || this.baichuanApi !== api) {
327
+ // This close event is for a different/old client, ignore it
328
+ logger.debug('Close event for stale client, ignoring');
329
+ return;
330
+ }
331
+
312
332
  try {
313
333
  const wasConnected = api.client.isSocketConnected();
314
334
  const wasLoggedIn = api.client.loggedIn;
@@ -330,8 +350,16 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
330
350
 
331
351
  logger.log(`Socket closed, resetting client state for reconnection (last disconnect ${timeSinceLastDisconnect}ms ago)`);
332
352
 
333
- // Cleanup
334
- await this.cleanupBaichuanApi();
353
+ // Mark as disconnected immediately to prevent reuse
354
+ // This prevents race conditions where ensureBaichuanClient might check
355
+ // isSocketConnected() before cleanup completes
356
+ const currentApi = this.baichuanApi;
357
+ if (currentApi === api) {
358
+ // Only cleanup if this is still the current API instance
359
+ // This prevents cleanup of a new connection that was created
360
+ // while the old one was closing
361
+ await this.cleanupBaichuanApi();
362
+ }
335
363
 
336
364
  // Call custom close handler if provided
337
365
  if (callbacks.onClose) {
@@ -318,10 +318,4 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
318
318
  this.getBaichuanLogger().warn(`Baichuan client reset requested: ${message}`);
319
319
  }
320
320
  }
321
-
322
- protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
323
- const client = await this.ensureClient();
324
- return fn(client);
325
- }
326
-
327
321
  }
package/src/camera.ts CHANGED
@@ -103,24 +103,15 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
103
103
  await this.alignAuxDevicesState();
104
104
  }
105
105
 
106
- async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
107
- return null;
108
- }
109
-
110
106
  async processEvents(events: { motion?: boolean; objects?: string[] }) {
111
107
  const logger = this.getBaichuanLogger();
112
108
 
113
109
  if (!this.isEventDispatchEnabled()) return;
114
110
 
115
- if (this.storageSettings.values.dispatchEvents.includes('eventLogs')) {
111
+ if (this.isDebugEnabled()) {
116
112
  logger.debug(`Events received: ${JSON.stringify(events)}`);
117
113
  }
118
114
 
119
- // const debugEvents = this.storageSettings.values.debugEvents;
120
- // if (debugEvents) {
121
- // logger.debug(`Events received: ${JSON.stringify(events)}`);
122
- // }
123
-
124
115
  if (this.shouldDispatchMotion() && events.motion !== this.motionDetected) {
125
116
  if (events.motion) {
126
117
  this.motionDetected = true;
@@ -147,30 +138,4 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
147
138
  sdk.deviceManager.onDeviceEvent(this.nativeId, ScryptedInterface.ObjectDetector, od);
148
139
  }
149
140
  }
150
-
151
- protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
152
- const client = await this.ensureClient();
153
- return fn(client);
154
- }
155
-
156
- async getPictureOptions(): Promise<ResponsePictureOptions[]> {
157
- return [];
158
- }
159
-
160
- async getOtherSettings(): Promise<Setting[]> {
161
- return await this.getSettings();
162
- }
163
-
164
- showRtspUrlOverride() {
165
- return false;
166
- }
167
-
168
-
169
- async startIntercom(media: MediaObject): Promise<void> {
170
- await this.intercom.start(media);
171
- }
172
-
173
- stopIntercom(): Promise<void> {
174
- return this.intercom.stop();
175
- }
176
141
  }