@camera.ui/transport 0.0.22 → 0.0.24

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.
@@ -8,7 +8,9 @@ export interface DegradedRecoveryOptions {
8
8
  readonly ensureAll: () => void;
9
9
  readonly probe: () => Promise<BackgroundProbeOutcome>;
10
10
  readonly graceMs?: number;
11
+ readonly isReachable?: () => boolean;
11
12
  readonly onEscalate?: (round: number) => void;
12
13
  readonly onOffline?: (reason: string) => void;
14
+ readonly onHold?: (reason: string) => void;
13
15
  }
14
16
  export declare function attachDegradedRecovery(options: DegradedRecoveryOptions): Detach;
@@ -6,5 +6,6 @@ export interface NetworkChangeOptions {
6
6
  readonly kernel: Kernel;
7
7
  readonly source: NetworkChangeSource;
8
8
  readonly onChange: (kernel: Kernel, event: Event) => void;
9
+ readonly debounceMs?: number;
9
10
  }
10
11
  export declare function attachNetworkChange(options: NetworkChangeOptions): Detach;
@@ -5,11 +5,13 @@ export type RefreshReason = 'proactive' | 'auth-error';
5
5
  export interface TokenLifecycleOptions {
6
6
  readonly kernel: Kernel;
7
7
  readonly transports: readonly Transport[];
8
- readonly refresh: (target: ConnectionTarget, reason: RefreshReason) => Promise<Tokens>;
9
8
  readonly graceMs?: number;
10
- readonly isTransientError?: (err: unknown) => boolean;
11
9
  readonly maxTransientRetries?: number;
12
10
  readonly transientRetryDelayMs?: number;
11
+ readonly maxServerErrorRetries?: number;
12
+ readonly refresh: (target: ConnectionTarget, reason: RefreshReason) => Promise<Tokens>;
13
+ readonly isTransientError?: (err: unknown) => boolean;
14
+ readonly isServerError?: (err: unknown) => boolean;
13
15
  readonly now?: () => number;
14
16
  readonly setTimer?: (cb: () => void, ms: number) => unknown;
15
17
  readonly clearTimer?: (handle: unknown) => void;
@@ -20,6 +22,7 @@ export interface TokenLifecycleOptions {
20
22
  readonly onRefreshSkipped?: (reason: RefreshReason, tokens: Tokens) => void;
21
23
  readonly onRefreshError?: (reason: RefreshReason, error: unknown, info: {
22
24
  transient: boolean;
25
+ serverError: boolean;
23
26
  retriesLeft: number;
24
27
  willRetry: boolean;
25
28
  }) => void;
package/dist/index.js CHANGED
@@ -495,6 +495,11 @@ function attachDegradedRecovery(options) {
495
495
  const outcome = await options.probe();
496
496
  if (detached || options.signal.current.kind !== "degraded") return;
497
497
  if (outcome === "failed") {
498
+ if (options.isReachable?.()) {
499
+ options.onHold?.("probe failed but a channel is up — holding");
500
+ arm();
501
+ return;
502
+ }
498
503
  options.onOffline?.("degraded: endpoint unreachable");
499
504
  options.kernel.dispatch({
500
505
  type: "PROBE_FAILED_ALL",
@@ -518,14 +523,28 @@ function attachDegradedRecovery(options) {
518
523
  //#endregion
519
524
  //#region src/effects/networkChange.ts
520
525
  function attachNetworkChange(options) {
526
+ const debounceMs = options.debounceMs ?? 0;
521
527
  let detached = false;
528
+ let timer = null;
522
529
  const handler = (event) => {
523
530
  if (detached) return;
524
- options.onChange(options.kernel, event);
531
+ if (debounceMs <= 0) {
532
+ options.onChange(options.kernel, event);
533
+ return;
534
+ }
535
+ if (timer !== null) clearTimeout(timer);
536
+ timer = setTimeout(() => {
537
+ timer = null;
538
+ if (!detached) options.onChange(options.kernel, event);
539
+ }, debounceMs);
525
540
  };
526
541
  options.source.addEventListener("change", handler);
527
542
  return () => {
528
543
  detached = true;
544
+ if (timer !== null) {
545
+ clearTimeout(timer);
546
+ timer = null;
547
+ }
529
548
  options.source.removeEventListener("change", handler);
530
549
  };
531
550
  }
@@ -817,9 +836,11 @@ var DEFAULT_TRANSIENT_RETRY_DELAY_MS = 2e3;
817
836
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
818
837
  function attachTokenLifecycle(options) {
819
838
  const graceMs = options.graceMs ?? DEFAULT_GRACE_MS;
820
- const isTransient = options.isTransientError ?? (() => false);
821
839
  const maxTransientRetries = options.maxTransientRetries ?? DEFAULT_MAX_TRANSIENT_RETRIES;
840
+ const maxServerErrorRetries = options.maxServerErrorRetries ?? maxTransientRetries;
822
841
  const transientRetryDelayMs = options.transientRetryDelayMs ?? DEFAULT_TRANSIENT_RETRY_DELAY_MS;
842
+ const isTransient = options.isTransientError ?? (() => false);
843
+ const isServerError = options.isServerError ?? (() => false);
823
844
  const now = options.now ?? (() => Date.now());
824
845
  const setTimer = options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
825
846
  const clearTimer = options.clearTimer ?? ((h) => clearTimeout(h));
@@ -907,11 +928,14 @@ function attachTokenLifecycle(options) {
907
928
  } catch (err) {
908
929
  if (detached) return;
909
930
  const transient = isTransient(err);
910
- if (transient && transientRetries < maxTransientRetries) {
931
+ const serverError = transient && isServerError(err);
932
+ const budget = serverError ? maxServerErrorRetries : maxTransientRetries;
933
+ if (transient && transientRetries < budget) {
911
934
  transientRetries++;
912
- const retriesLeft = maxTransientRetries - transientRetries;
935
+ const retriesLeft = budget - transientRetries;
913
936
  options.onRefreshError?.(reason, err, {
914
937
  transient: true,
938
+ serverError,
915
939
  retriesLeft,
916
940
  willRetry: true
917
941
  });
@@ -924,6 +948,7 @@ function attachTokenLifecycle(options) {
924
948
  transientRetries = 0;
925
949
  options.onRefreshError?.(reason, err, {
926
950
  transient,
951
+ serverError,
927
952
  retriesLeft: 0,
928
953
  willRetry: false
929
954
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camera.ui/transport",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "camera.ui transport layer — framework-agnostic connection kernel, reducer state, pluggable transports (HTTP/WS/Socket.IO/NATS), lifecycle effects and worker bridge",
5
5
  "author": "seydx (https://github.com/cameraui/clients)",
6
6
  "type": "module",