@apocaliss92/nodelink-js 0.4.4 → 0.4.6

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.cts CHANGED
@@ -1009,11 +1009,13 @@ interface ReolinkDayNightNotification {
1009
1009
  timestamp?: number;
1010
1010
  }
1011
1011
  type ReolinkEvent = ReolinkMotionNotification | ReolinkAiNotification | ReolinkVisitorNotification | ReolinkDayNightNotification;
1012
- type ReolinkSimpleEventType = "motion" | "doorbell" | "people" | "vehicle" | "animal" | "face" | "package" | "daynight" | "sleeping" | "awake" | "online" | "offline" | "other";
1012
+ type ReolinkSimpleEventType = "motion" | "doorbell" | "people" | "vehicle" | "animal" | "face" | "package" | "daynight" | "sleeping" | "awake" | "online" | "offline" | "battery" | "other";
1013
1013
  interface ReolinkSimpleEvent {
1014
1014
  type: ReolinkSimpleEventType;
1015
1015
  channel: number;
1016
1016
  timestamp: number;
1017
+ /** Present when type === "battery" — pushed by the camera via cmdId 252. */
1018
+ battery?: Partial<BatteryInfo>;
1017
1019
  }
1018
1020
  interface TwoWayAudioConfig {
1019
1021
  channel: number;
@@ -1935,6 +1937,11 @@ declare class BaichuanClient extends EventEmitter<{
1935
1937
  debug: [string, unknown?];
1936
1938
  event: [ReolinkEvent];
1937
1939
  channelInfo: [string];
1940
+ batteryPush: [BaichuanFrame];
1941
+ d2c_disc: [{
1942
+ host: string;
1943
+ atMs: number;
1944
+ }];
1938
1945
  }> {
1939
1946
  /**
1940
1947
  * Process-wide streaming activity registry.
@@ -1946,6 +1953,14 @@ declare class BaichuanClient extends EventEmitter<{
1946
1953
  * even if the current client instance is idle/disconnected.
1947
1954
  */
1948
1955
  private static readonly streamingRegistry;
1956
+ /**
1957
+ * Per-host D2C_DISC backoff state that persists across client instance recreation.
1958
+ *
1959
+ * Why: when a D2C_DISC kills a client, the socket pool destroys the old instance
1960
+ * and creates a new one. Instance-level backoff variables would reset to zero,
1961
+ * allowing immediate reconnection and perpetuating the storm.
1962
+ */
1963
+ private static readonly d2cDiscBackoff;
1949
1964
  /**
1950
1965
  * Global (process-wide) CoverPreview serialization.
1951
1966
  *
@@ -2419,6 +2434,7 @@ declare class BcUdpStream extends EventEmitter<{
2419
2434
  private resendTimer;
2420
2435
  private hbTimer;
2421
2436
  private discoveryTid;
2437
+ private discoveryTimers;
2422
2438
  private acceptSent;
2423
2439
  private lastAcceptAtMs;
2424
2440
  private ackScheduled;
@@ -3640,6 +3656,15 @@ declare class BaichuanVideoStream extends EventEmitter<{
3640
3656
  private lastPpsH265;
3641
3657
  private lastPrependedParamSetsH265;
3642
3658
  private aesStreamDecryptor;
3659
+ /**
3660
+ * Pending startup error stashed when emitSafeError is called before any
3661
+ * "error" listener is registered (e.g. camera returns 400 during start()).
3662
+ * The rfc4571-server's waitForKeyframe can consume this immediately instead
3663
+ * of waiting for the full keyframe timeout.
3664
+ */
3665
+ private _pendingStartupError;
3666
+ /** Consume and clear any pending startup error. */
3667
+ consumePendingStartupError(): Error | undefined;
3643
3668
  private emitSafeError;
3644
3669
  private lastMediaAtMs;
3645
3670
  private watchdogTimer;
@@ -4338,6 +4363,19 @@ declare class ReolinkBaichuanApi {
4338
4363
  private readonly aiAlarmCandidateTypesCache;
4339
4364
  private rtspServers;
4340
4365
  private readonly activeVideoMsgNums;
4366
+ /** Timestamp of the most recent D2C_DISC from any client for this device. */
4367
+ private lastD2cDiscAtMs;
4368
+ /** Sliding window of recent D2C_DISC timestamps for storm detection. */
4369
+ private readonly d2cDiscTimestamps;
4370
+ /** Immediate cooldown (ms) applied to socket pool on every D2C_DISC.
4371
+ * Prevents reconnect attempts while the camera is transitioning to sleep. */
4372
+ private static readonly D2C_DISC_IMMEDIATE_COOLDOWN_MS;
4373
+ /** Number of D2C_DISCs within the storm window to trigger extended cooldown. */
4374
+ private static readonly D2C_DISC_STORM_THRESHOLD;
4375
+ /** Sliding window size (ms) for storm detection. */
4376
+ private static readonly D2C_DISC_STORM_WINDOW_MS;
4377
+ /** Extended cooldown (ms) applied to socket pool when a D2C_DISC storm is detected. */
4378
+ private static readonly D2C_DISC_STORM_COOLDOWN_MS;
4341
4379
  private readonly nvrChannelsSummaryCache;
4342
4380
  /**
4343
4381
  * Cached device capabilities per channel.
@@ -4477,6 +4515,18 @@ declare class ReolinkBaichuanApi {
4477
4515
  * which indicates subStream (e.g., RecS03_, RecS_).
4478
4516
  */
4479
4517
  private determineStreamTypeFromFileName;
4518
+ /**
4519
+ * Stream profiles that the device explicitly rejected (response_code 400).
4520
+ * Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
4521
+ * it is excluded from `buildVideoStreamOptions()` results and no further
4522
+ * start attempts are made until the API instance is recreated.
4523
+ */
4524
+ private readonly _rejectedStreamProfiles;
4525
+ /**
4526
+ * Check whether a stream profile was rejected by the device at runtime
4527
+ * (e.g. ext returned response_code 400).
4528
+ */
4529
+ isStreamProfileRejected(channel: number, profile: StreamProfile): boolean;
4480
4530
  /**
4481
4531
  * Cache for buildVideoStreamOptions.
4482
4532
  *
@@ -4512,6 +4562,12 @@ declare class ReolinkBaichuanApi {
4512
4562
  * @returns The socket pool tag to use
4513
4563
  */
4514
4564
  private resolveSocketTag;
4565
+ /**
4566
+ * Attach a D2C_DISC listener to a BaichuanClient so that the API-level
4567
+ * grace period and storm detection are updated regardless of which
4568
+ * pool socket receives the disconnect.
4569
+ */
4570
+ private attachD2cDiscListener;
4515
4571
  /**
4516
4572
  * Acquire a socket from the pool by tag.
4517
4573
  * Creates a new socket if needed, or reuses an existing one.
@@ -5657,6 +5713,15 @@ declare class ReolinkBaichuanApi {
5657
5713
  zoomToFactor(zoomFactor: number, channel?: number): Promise<void>;
5658
5714
  zoomToFactor(channel: number, zoomFactor: number): Promise<void>;
5659
5715
  private parseBatteryInfoXml;
5716
+ /**
5717
+ * Called when any BaichuanClient for this device receives a D2C_DISC.
5718
+ *
5719
+ * Two-tier response:
5720
+ * 1. **Immediate**: every D2C_DISC applies a short socket pool cooldown
5721
+ * (10 s) to prevent reconnect attempts while the camera transitions to sleep.
5722
+ * 2. **Storm**: ≥3 D2C_DISCs within 60 s triggers extended cooldown (120 s).
5723
+ */
5724
+ private notifyD2cDisc;
5660
5725
  /**
5661
5726
  * Best-effort sleeping inference for battery/BCUDP cameras.
5662
5727
  *
package/dist/index.d.ts CHANGED
@@ -396,6 +396,11 @@ export declare class BaichuanClient extends EventEmitter<{
396
396
  debug: [string, unknown?];
397
397
  event: [ReolinkEvent];
398
398
  channelInfo: [string];
399
+ batteryPush: [BaichuanFrame];
400
+ d2c_disc: [{
401
+ host: string;
402
+ atMs: number;
403
+ }];
399
404
  }> {
400
405
  /**
401
406
  * Process-wide streaming activity registry.
@@ -407,6 +412,14 @@ export declare class BaichuanClient extends EventEmitter<{
407
412
  * even if the current client instance is idle/disconnected.
408
413
  */
409
414
  private static readonly streamingRegistry;
415
+ /**
416
+ * Per-host D2C_DISC backoff state that persists across client instance recreation.
417
+ *
418
+ * Why: when a D2C_DISC kills a client, the socket pool destroys the old instance
419
+ * and creates a new one. Instance-level backoff variables would reset to zero,
420
+ * allowing immediate reconnection and perpetuating the storm.
421
+ */
422
+ private static readonly d2cDiscBackoff;
410
423
  /**
411
424
  * Global (process-wide) CoverPreview serialization.
412
425
  *
@@ -1634,6 +1647,15 @@ export declare class BaichuanVideoStream extends EventEmitter<{
1634
1647
  private lastPpsH265;
1635
1648
  private lastPrependedParamSetsH265;
1636
1649
  private aesStreamDecryptor;
1650
+ /**
1651
+ * Pending startup error stashed when emitSafeError is called before any
1652
+ * "error" listener is registered (e.g. camera returns 400 during start()).
1653
+ * The rfc4571-server's waitForKeyframe can consume this immediately instead
1654
+ * of waiting for the full keyframe timeout.
1655
+ */
1656
+ private _pendingStartupError;
1657
+ /** Consume and clear any pending startup error. */
1658
+ consumePendingStartupError(): Error | undefined;
1637
1659
  private emitSafeError;
1638
1660
  private lastMediaAtMs;
1639
1661
  private watchdogTimer;
@@ -2324,6 +2346,7 @@ export declare class BcUdpStream extends EventEmitter<{
2324
2346
  private resendTimer;
2325
2347
  private hbTimer;
2326
2348
  private discoveryTid;
2349
+ private discoveryTimers;
2327
2350
  private acceptSent;
2328
2351
  private lastAcceptAtMs;
2329
2352
  private ackScheduled;
@@ -5183,6 +5206,19 @@ export declare class ReolinkBaichuanApi {
5183
5206
  private readonly aiAlarmCandidateTypesCache;
5184
5207
  private rtspServers;
5185
5208
  private readonly activeVideoMsgNums;
5209
+ /** Timestamp of the most recent D2C_DISC from any client for this device. */
5210
+ private lastD2cDiscAtMs;
5211
+ /** Sliding window of recent D2C_DISC timestamps for storm detection. */
5212
+ private readonly d2cDiscTimestamps;
5213
+ /** Immediate cooldown (ms) applied to socket pool on every D2C_DISC.
5214
+ * Prevents reconnect attempts while the camera is transitioning to sleep. */
5215
+ private static readonly D2C_DISC_IMMEDIATE_COOLDOWN_MS;
5216
+ /** Number of D2C_DISCs within the storm window to trigger extended cooldown. */
5217
+ private static readonly D2C_DISC_STORM_THRESHOLD;
5218
+ /** Sliding window size (ms) for storm detection. */
5219
+ private static readonly D2C_DISC_STORM_WINDOW_MS;
5220
+ /** Extended cooldown (ms) applied to socket pool when a D2C_DISC storm is detected. */
5221
+ private static readonly D2C_DISC_STORM_COOLDOWN_MS;
5186
5222
  private readonly nvrChannelsSummaryCache;
5187
5223
  /**
5188
5224
  * Cached device capabilities per channel.
@@ -5322,6 +5358,18 @@ export declare class ReolinkBaichuanApi {
5322
5358
  * which indicates subStream (e.g., RecS03_, RecS_).
5323
5359
  */
5324
5360
  private determineStreamTypeFromFileName;
5361
+ /**
5362
+ * Stream profiles that the device explicitly rejected (response_code 400).
5363
+ * Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
5364
+ * it is excluded from `buildVideoStreamOptions()` results and no further
5365
+ * start attempts are made until the API instance is recreated.
5366
+ */
5367
+ private readonly _rejectedStreamProfiles;
5368
+ /**
5369
+ * Check whether a stream profile was rejected by the device at runtime
5370
+ * (e.g. ext returned response_code 400).
5371
+ */
5372
+ isStreamProfileRejected(channel: number, profile: StreamProfile): boolean;
5325
5373
  /**
5326
5374
  * Cache for buildVideoStreamOptions.
5327
5375
  *
@@ -5357,6 +5405,12 @@ export declare class ReolinkBaichuanApi {
5357
5405
  * @returns The socket pool tag to use
5358
5406
  */
5359
5407
  private resolveSocketTag;
5408
+ /**
5409
+ * Attach a D2C_DISC listener to a BaichuanClient so that the API-level
5410
+ * grace period and storm detection are updated regardless of which
5411
+ * pool socket receives the disconnect.
5412
+ */
5413
+ private attachD2cDiscListener;
5360
5414
  /**
5361
5415
  * Acquire a socket from the pool by tag.
5362
5416
  * Creates a new socket if needed, or reuses an existing one.
@@ -6502,6 +6556,15 @@ export declare class ReolinkBaichuanApi {
6502
6556
  zoomToFactor(zoomFactor: number, channel?: number): Promise<void>;
6503
6557
  zoomToFactor(channel: number, zoomFactor: number): Promise<void>;
6504
6558
  private parseBatteryInfoXml;
6559
+ /**
6560
+ * Called when any BaichuanClient for this device receives a D2C_DISC.
6561
+ *
6562
+ * Two-tier response:
6563
+ * 1. **Immediate**: every D2C_DISC applies a short socket pool cooldown
6564
+ * (10 s) to prevent reconnect attempts while the camera transitions to sleep.
6565
+ * 2. **Storm**: ≥3 D2C_DISCs within 60 s triggers extended cooldown (120 s).
6566
+ */
6567
+ private notifyD2cDisc;
6505
6568
  /**
6506
6569
  * Best-effort sleeping inference for battery/BCUDP cameras.
6507
6570
  *
@@ -8361,9 +8424,11 @@ export declare interface ReolinkSimpleEvent {
8361
8424
  type: ReolinkSimpleEventType;
8362
8425
  channel: number;
8363
8426
  timestamp: number;
8427
+ /** Present when type === "battery" — pushed by the camera via cmdId 252. */
8428
+ battery?: Partial<BatteryInfo>;
8364
8429
  }
8365
8430
 
8366
- export declare type ReolinkSimpleEventType = "motion" | "doorbell" | "people" | "vehicle" | "animal" | "face" | "package" | "daynight" | "sleeping" | "awake" | "online" | "offline" | "other";
8431
+ export declare type ReolinkSimpleEventType = "motion" | "doorbell" | "people" | "vehicle" | "animal" | "face" | "package" | "daynight" | "sleeping" | "awake" | "online" | "offline" | "battery" | "other";
8367
8432
 
8368
8433
  /**
8369
8434
  * Complete media stream options with all available metadata.
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  parseSupportXml,
44
44
  setGlobalLogger,
45
45
  xmlIndicatesFloodlight
46
- } from "./chunk-UHFJPQA4.js";
46
+ } from "./chunk-F2Y5U3YP.js";
47
47
  import {
48
48
  AesStreamDecryptor,
49
49
  BC_AES_IV,
@@ -223,7 +223,7 @@ import {
223
223
  testChannelStreams,
224
224
  xmlEscape,
225
225
  zipDirectory
226
- } from "./chunk-DEOMUWBN.js";
226
+ } from "./chunk-TR3V5FTO.js";
227
227
 
228
228
  // src/reolink/baichuan/HlsSessionManager.ts
229
229
  var withTimeout = async (p, ms, label) => {
@@ -3466,6 +3466,11 @@ async function createRfc4571TcpServerInternal(options) {
3466
3466
  "videoAccessUnit",
3467
3467
  onAu
3468
3468
  );
3469
+ const pendingErr = videoStream.consumePendingStartupError?.();
3470
+ if (pendingErr) {
3471
+ cleanup();
3472
+ reject(pendingErr);
3473
+ }
3469
3474
  });
3470
3475
  }
3471
3476
  };
@@ -3477,24 +3482,32 @@ async function createRfc4571TcpServerInternal(options) {
3477
3482
  await videoStream.stop();
3478
3483
  } catch {
3479
3484
  }
3480
- if (closeApiOnTeardown) {
3481
- await Promise.allSettled(
3482
- Array.from(apisToClose).map(async (a) => {
3485
+ if (dedicatedSession) {
3486
+ try {
3487
+ await dedicatedSession.release();
3488
+ } catch {
3489
+ }
3490
+ }
3491
+ if (!dedicatedSession) {
3492
+ if (closeApiOnTeardown) {
3493
+ await Promise.allSettled(
3494
+ Array.from(apisToClose).map(async (a) => {
3495
+ try {
3496
+ await a.close();
3497
+ } catch {
3498
+ }
3499
+ })
3500
+ );
3501
+ } else {
3502
+ const graceMs = isComposite ? 5e3 : 0;
3503
+ for (const a of Array.from(apisToClose)) {
3483
3504
  try {
3484
- await a.close();
3505
+ a?.client?.requestIdleDisconnectSoon?.(
3506
+ "rfc4571_teardown",
3507
+ graceMs
3508
+ );
3485
3509
  } catch {
3486
3510
  }
3487
- })
3488
- );
3489
- } else {
3490
- const graceMs = isComposite ? 5e3 : 0;
3491
- for (const a of Array.from(apisToClose)) {
3492
- try {
3493
- a?.client?.requestIdleDisconnectSoon?.(
3494
- "rfc4571_teardown",
3495
- graceMs
3496
- );
3497
- } catch {
3498
3511
  }
3499
3512
  }
3500
3513
  }
@@ -3750,7 +3763,7 @@ async function createRfc4571TcpServerInternal(options) {
3750
3763
  } catch {
3751
3764
  }
3752
3765
  }
3753
- if (closeApiOnTeardown) {
3766
+ if (closeApiOnTeardown && !dedicatedSession) {
3754
3767
  await Promise.allSettled(
3755
3768
  Array.from(apisToClose).map(async (a) => {
3756
3769
  try {