@chromahq/react 1.0.35 → 1.0.39
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.ts +14 -12
- package/dist/index.js +62 -30
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ interface Bridge {
|
|
|
8
8
|
off: (key: string, handler: (payload: unknown) => void) => void;
|
|
9
9
|
isConnected: boolean;
|
|
10
10
|
ping: () => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Pause health checks for the specified duration.
|
|
13
|
+
* Use this before calling a message that triggers heavy/blocking operations in the SW.
|
|
14
|
+
* @param durationMs - How long to pause health checks in milliseconds
|
|
15
|
+
*/
|
|
16
|
+
pauseHealthChecks: (durationMs: number) => void;
|
|
11
17
|
}
|
|
12
18
|
interface BridgeContextValue {
|
|
13
19
|
bridge: Bridge | null;
|
|
@@ -26,23 +32,19 @@ interface BridgeProviderProps {
|
|
|
26
32
|
pingInterval?: number;
|
|
27
33
|
/** How long to wait before resetting retry count in ms. Default: 30000 */
|
|
28
34
|
maxRetryCooldown?: number;
|
|
29
|
-
/** Default timeout for messages in ms. Default:
|
|
35
|
+
/** Default timeout for messages in ms. Default: 30000 */
|
|
30
36
|
defaultTimeout?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Timeout threshold for counting failures toward reconnection.
|
|
39
|
+
* Only requests with timeouts <= this value are counted as potential SW issues.
|
|
40
|
+
* Requests with longer timeouts (intentional slow operations) won't trigger reconnection.
|
|
41
|
+
* Default: 15000 (15s)
|
|
42
|
+
*/
|
|
43
|
+
timeoutFailureThreshold?: number;
|
|
31
44
|
/** Callback when connection status changes */
|
|
32
45
|
onConnectionChange?: (status: ConnectionStatus) => void;
|
|
33
46
|
/** Callback when an error occurs */
|
|
34
47
|
onError?: (error: Error) => void;
|
|
35
|
-
/**
|
|
36
|
-
* Optional function to check if health checks should be paused.
|
|
37
|
-
* Return a timestamp (ms) until which health checks are paused, or null/0 if not paused.
|
|
38
|
-
* This allows service workers to pause health monitoring during heavy operations.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```tsx
|
|
42
|
-
* <BridgeProvider isHealthPaused={() => store.getState().healthPausedUntil}>
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
isHealthPausedUntil?: () => number | null | undefined;
|
|
46
48
|
}
|
|
47
49
|
declare const BridgeProvider: FC<BridgeProviderProps>;
|
|
48
50
|
|
package/dist/index.js
CHANGED
|
@@ -5,22 +5,26 @@ const BRIDGE_ENABLE_LOGS = true;
|
|
|
5
5
|
const CONFIG = {
|
|
6
6
|
RETRY_AFTER: 1e3,
|
|
7
7
|
MAX_RETRIES: 10,
|
|
8
|
-
PING_INTERVAL:
|
|
9
|
-
// Check every
|
|
8
|
+
PING_INTERVAL: 5e3,
|
|
9
|
+
// Check every 5s (reduced frequency to avoid false positives during heavy operations)
|
|
10
10
|
MAX_RETRY_COOLDOWN: 3e4,
|
|
11
|
-
DEFAULT_TIMEOUT:
|
|
11
|
+
DEFAULT_TIMEOUT: 3e4,
|
|
12
|
+
// Increased from 20s to 30s for slow operations
|
|
12
13
|
MAX_RETRY_DELAY: 3e4,
|
|
13
|
-
PING_TIMEOUT:
|
|
14
|
-
// Give SW
|
|
14
|
+
PING_TIMEOUT: 1e4,
|
|
15
|
+
// Give SW 10s to respond (handles busy periods like large storage reads)
|
|
15
16
|
ERROR_CHECK_INTERVAL: 100,
|
|
16
17
|
MAX_ERROR_CHECKS: 10,
|
|
17
|
-
CONSECUTIVE_FAILURE_THRESHOLD:
|
|
18
|
-
// Require
|
|
18
|
+
CONSECUTIVE_FAILURE_THRESHOLD: 5,
|
|
19
|
+
// Require 5 consecutive failures (25s total) before reconnecting
|
|
19
20
|
RECONNECT_DELAY: 100,
|
|
20
21
|
PORT_NAME: "chroma-bridge",
|
|
21
22
|
// Service worker restart retry settings (indefinite retries)
|
|
22
23
|
SW_RESTART_RETRY_DELAY: 500,
|
|
23
|
-
SW_RESTART_MAX_DELAY: 5e3
|
|
24
|
+
SW_RESTART_MAX_DELAY: 5e3,
|
|
25
|
+
// Threshold for counting timeouts toward reconnection (only count fast timeouts as failures)
|
|
26
|
+
TIMEOUT_FAILURE_THRESHOLD_MS: 15e3
|
|
27
|
+
// Only count timeouts < 15s as potential SW issues
|
|
24
28
|
};
|
|
25
29
|
const calculateBackoffDelay = (attempt, baseDelay, maxDelay) => Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
26
30
|
const clearTimeoutSafe = (ref) => {
|
|
@@ -50,15 +54,19 @@ function createBridgeInstance(deps) {
|
|
|
50
54
|
isConnectedRef,
|
|
51
55
|
consecutiveTimeoutsRef,
|
|
52
56
|
reconnectionGracePeriodRef,
|
|
57
|
+
healthPausedUntilRef,
|
|
53
58
|
defaultTimeout,
|
|
59
|
+
timeoutFailureThreshold,
|
|
54
60
|
onReconnectNeeded
|
|
55
61
|
} = deps;
|
|
56
62
|
const rejectAllPending = (message) => {
|
|
57
|
-
pendingRequestsRef.current.forEach(({ reject, timeout }) => {
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
pendingRequestsRef.current.forEach(({ reject, timeout, timeoutDuration }, id) => {
|
|
64
|
+
if (timeoutDuration <= timeoutFailureThreshold) {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
reject(new Error(message));
|
|
67
|
+
pendingRequestsRef.current.delete(id);
|
|
68
|
+
}
|
|
60
69
|
});
|
|
61
|
-
pendingRequestsRef.current.clear();
|
|
62
70
|
};
|
|
63
71
|
const send = (key, payload, timeoutDuration = defaultTimeout) => {
|
|
64
72
|
return new Promise((resolve, reject) => {
|
|
@@ -70,15 +78,16 @@ function createBridgeInstance(deps) {
|
|
|
70
78
|
const timeout = setTimeout(() => {
|
|
71
79
|
if (!pendingRequestsRef.current.has(id)) return;
|
|
72
80
|
pendingRequestsRef.current.delete(id);
|
|
73
|
-
|
|
81
|
+
const isShortTimeout = timeoutDuration <= timeoutFailureThreshold;
|
|
82
|
+
if (!reconnectionGracePeriodRef.current && isShortTimeout) {
|
|
74
83
|
consecutiveTimeoutsRef.current++;
|
|
75
84
|
}
|
|
76
85
|
{
|
|
77
86
|
console.warn(
|
|
78
|
-
`[Bridge] Request timed out: ${key} (${timeoutDuration}ms)${reconnectionGracePeriodRef.current ? " [grace period]" : ""}`
|
|
87
|
+
`[Bridge] Request timed out: ${key} (${timeoutDuration}ms)${reconnectionGracePeriodRef.current ? " [grace period]" : ""}${!isShortTimeout ? " [long operation, not counted toward reconnect]" : ""}`
|
|
79
88
|
);
|
|
80
89
|
}
|
|
81
|
-
if (!reconnectionGracePeriodRef.current && consecutiveTimeoutsRef.current >= CONFIG.CONSECUTIVE_FAILURE_THRESHOLD) {
|
|
90
|
+
if (!reconnectionGracePeriodRef.current && isShortTimeout && consecutiveTimeoutsRef.current >= CONFIG.CONSECUTIVE_FAILURE_THRESHOLD) {
|
|
82
91
|
{
|
|
83
92
|
console.warn(
|
|
84
93
|
`[Bridge] ${consecutiveTimeoutsRef.current} consecutive timeouts, reconnecting...`
|
|
@@ -93,7 +102,8 @@ function createBridgeInstance(deps) {
|
|
|
93
102
|
pendingRequestsRef.current.set(id, {
|
|
94
103
|
resolve,
|
|
95
104
|
reject,
|
|
96
|
-
timeout
|
|
105
|
+
timeout,
|
|
106
|
+
timeoutDuration
|
|
97
107
|
});
|
|
98
108
|
try {
|
|
99
109
|
portRef.current.postMessage({ id, key, payload });
|
|
@@ -167,6 +177,13 @@ function createBridgeInstance(deps) {
|
|
|
167
177
|
} catch {
|
|
168
178
|
return false;
|
|
169
179
|
}
|
|
180
|
+
},
|
|
181
|
+
pauseHealthChecks: (durationMs) => {
|
|
182
|
+
const pauseUntil = Date.now() + durationMs;
|
|
183
|
+
healthPausedUntilRef.current = pauseUntil;
|
|
184
|
+
{
|
|
185
|
+
console.log(`[Bridge] Health checks paused for ${Math.round(durationMs / 1e3)}s`);
|
|
186
|
+
}
|
|
170
187
|
}
|
|
171
188
|
};
|
|
172
189
|
return bridge;
|
|
@@ -176,11 +193,11 @@ function startHealthMonitor(deps) {
|
|
|
176
193
|
bridge,
|
|
177
194
|
pingIntervalRef,
|
|
178
195
|
consecutivePingFailuresRef,
|
|
196
|
+
healthPausedUntilRef,
|
|
179
197
|
pingInterval,
|
|
180
198
|
setIsServiceWorkerAlive,
|
|
181
199
|
onReconnectNeeded,
|
|
182
|
-
rejectAllPendingRequests
|
|
183
|
-
isHealthPausedUntil
|
|
200
|
+
rejectAllPendingRequests
|
|
184
201
|
} = deps;
|
|
185
202
|
clearIntervalSafe(pingIntervalRef);
|
|
186
203
|
consecutivePingFailuresRef.current = 0;
|
|
@@ -194,7 +211,7 @@ function startHealthMonitor(deps) {
|
|
|
194
211
|
}
|
|
195
212
|
return;
|
|
196
213
|
}
|
|
197
|
-
const pausedUntil =
|
|
214
|
+
const pausedUntil = healthPausedUntilRef.current;
|
|
198
215
|
if (pausedUntil && Date.now() < pausedUntil) {
|
|
199
216
|
{
|
|
200
217
|
const remainingMs = pausedUntil - Date.now();
|
|
@@ -207,7 +224,7 @@ function startHealthMonitor(deps) {
|
|
|
207
224
|
}
|
|
208
225
|
const alive = await bridge.ping();
|
|
209
226
|
if (!pingIntervalRef.current) return;
|
|
210
|
-
const pausedUntilAfterPing =
|
|
227
|
+
const pausedUntilAfterPing = healthPausedUntilRef.current;
|
|
211
228
|
if (pausedUntilAfterPing && Date.now() < pausedUntilAfterPing) {
|
|
212
229
|
consecutivePingFailuresRef.current = 0;
|
|
213
230
|
return;
|
|
@@ -240,9 +257,9 @@ const BridgeProvider = ({
|
|
|
240
257
|
pingInterval = CONFIG.PING_INTERVAL,
|
|
241
258
|
maxRetryCooldown = CONFIG.MAX_RETRY_COOLDOWN,
|
|
242
259
|
defaultTimeout = CONFIG.DEFAULT_TIMEOUT,
|
|
260
|
+
timeoutFailureThreshold = CONFIG.TIMEOUT_FAILURE_THRESHOLD_MS,
|
|
243
261
|
onConnectionChange,
|
|
244
|
-
onError
|
|
245
|
-
isHealthPausedUntil
|
|
262
|
+
onError
|
|
246
263
|
}) => {
|
|
247
264
|
const [bridge, setBridge] = useState(null);
|
|
248
265
|
const [status, setStatus] = useState("connecting");
|
|
@@ -260,6 +277,7 @@ const BridgeProvider = ({
|
|
|
260
277
|
const errorCheckIntervalRef = useRef(null);
|
|
261
278
|
const consecutivePingFailuresRef = useRef(0);
|
|
262
279
|
const consecutiveTimeoutsRef = useRef(0);
|
|
280
|
+
const healthPausedUntilRef = useRef(0);
|
|
263
281
|
const reconnectionGracePeriodRef = useRef(false);
|
|
264
282
|
const pendingRequestsRef = useRef(/* @__PURE__ */ new Map());
|
|
265
283
|
const eventListenersRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -323,6 +341,9 @@ const BridgeProvider = ({
|
|
|
323
341
|
reject(new Error("Bridge disconnected"));
|
|
324
342
|
});
|
|
325
343
|
pendingRequestsRef.current.clear();
|
|
344
|
+
if (!emitDisconnect) {
|
|
345
|
+
eventListenersRef.current.clear();
|
|
346
|
+
}
|
|
326
347
|
setIsServiceWorkerAlive(false);
|
|
327
348
|
setBridge(null);
|
|
328
349
|
isConnectingRef.current = false;
|
|
@@ -351,6 +372,10 @@ const BridgeProvider = ({
|
|
|
351
372
|
}
|
|
352
373
|
}
|
|
353
374
|
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (message.id && BRIDGE_ENABLE_LOGS) {
|
|
378
|
+
console.warn("[Bridge] Received response for unknown/expired request:", message.id);
|
|
354
379
|
}
|
|
355
380
|
}, []);
|
|
356
381
|
const scheduleReconnect = useCallback(
|
|
@@ -514,7 +539,9 @@ const BridgeProvider = ({
|
|
|
514
539
|
isConnectedRef,
|
|
515
540
|
consecutiveTimeoutsRef,
|
|
516
541
|
reconnectionGracePeriodRef,
|
|
542
|
+
healthPausedUntilRef,
|
|
517
543
|
defaultTimeout,
|
|
544
|
+
timeoutFailureThreshold,
|
|
518
545
|
onReconnectNeeded: triggerReconnect
|
|
519
546
|
});
|
|
520
547
|
setBridge(bridgeInstance);
|
|
@@ -543,22 +570,24 @@ const BridgeProvider = ({
|
|
|
543
570
|
}
|
|
544
571
|
});
|
|
545
572
|
const rejectAllPendingRequests = (message) => {
|
|
546
|
-
pendingRequestsRef.current.forEach(({ reject, timeout }) => {
|
|
547
|
-
|
|
548
|
-
|
|
573
|
+
pendingRequestsRef.current.forEach(({ reject, timeout, timeoutDuration }, id) => {
|
|
574
|
+
if (timeoutDuration <= timeoutFailureThreshold) {
|
|
575
|
+
clearTimeout(timeout);
|
|
576
|
+
reject(new Error(message));
|
|
577
|
+
pendingRequestsRef.current.delete(id);
|
|
578
|
+
}
|
|
549
579
|
});
|
|
550
|
-
pendingRequestsRef.current.clear();
|
|
551
580
|
consecutiveTimeoutsRef.current = 0;
|
|
552
581
|
};
|
|
553
582
|
startHealthMonitor({
|
|
554
583
|
bridge: bridgeInstance,
|
|
555
584
|
pingIntervalRef,
|
|
556
585
|
consecutivePingFailuresRef,
|
|
586
|
+
healthPausedUntilRef,
|
|
557
587
|
pingInterval,
|
|
558
588
|
setIsServiceWorkerAlive,
|
|
559
589
|
onReconnectNeeded: triggerReconnect,
|
|
560
|
-
rejectAllPendingRequests
|
|
561
|
-
isHealthPausedUntil
|
|
590
|
+
rejectAllPendingRequests
|
|
562
591
|
});
|
|
563
592
|
} catch (e) {
|
|
564
593
|
isConnectingRef.current = false;
|
|
@@ -574,8 +603,7 @@ const BridgeProvider = ({
|
|
|
574
603
|
scheduleSwRestartReconnect,
|
|
575
604
|
defaultTimeout,
|
|
576
605
|
updateStatus,
|
|
577
|
-
pingInterval
|
|
578
|
-
isHealthPausedUntil
|
|
606
|
+
pingInterval
|
|
579
607
|
]);
|
|
580
608
|
const reconnect = useCallback(() => {
|
|
581
609
|
retryCountRef.current = 0;
|
|
@@ -606,17 +634,21 @@ const BridgeProvider = ({
|
|
|
606
634
|
isConnectingRef.current = false;
|
|
607
635
|
connect();
|
|
608
636
|
} else if (currentStatus === "connected" && currentBridge) {
|
|
637
|
+
if (isConnectingRef.current) return;
|
|
609
638
|
currentBridge.ping().then((alive) => {
|
|
610
639
|
if (!isMountedRef.current) return;
|
|
640
|
+
if (isConnectingRef.current) return;
|
|
611
641
|
if (!alive) {
|
|
612
642
|
{
|
|
613
643
|
console.warn("[Bridge] Tab visible but unresponsive, reconnecting...");
|
|
614
644
|
}
|
|
615
645
|
retryCountRef.current = 0;
|
|
646
|
+
swRestartRetryCountRef.current = 0;
|
|
616
647
|
isConnectingRef.current = false;
|
|
617
648
|
cleanup();
|
|
618
649
|
connect();
|
|
619
650
|
}
|
|
651
|
+
}).catch(() => {
|
|
620
652
|
});
|
|
621
653
|
}
|
|
622
654
|
};
|