@chromahq/react 1.0.46 → 1.0.48
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 +51 -1
- package/dist/index.js +264 -42
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -23,8 +23,58 @@ declare global {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error' | 'reconnecting';
|
|
26
|
+
/**
|
|
27
|
+
* Options for critical operations that need acknowledgment and deduplication.
|
|
28
|
+
*/
|
|
29
|
+
interface CriticalOperationOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Client-generated nonce for idempotency. If not provided, one will be generated.
|
|
32
|
+
* The SW should store processed nonces and reject duplicates.
|
|
33
|
+
*/
|
|
34
|
+
nonce?: string;
|
|
35
|
+
/**
|
|
36
|
+
* If true, the request will NOT be queued during disconnection - it will fail immediately.
|
|
37
|
+
* Use this for operations where you want explicit user retry rather than automatic retry.
|
|
38
|
+
* Default: false (requests are queued)
|
|
39
|
+
*/
|
|
40
|
+
noQueue?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Callback fired when SW acknowledges receipt of the request (before processing).
|
|
43
|
+
* Use this to update UI to show "processing" state.
|
|
44
|
+
*/
|
|
45
|
+
onAcknowledged?: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Result of a critical operation, including metadata about the request.
|
|
49
|
+
*/
|
|
50
|
+
interface CriticalOperationResult<T> {
|
|
51
|
+
data: T;
|
|
52
|
+
nonce: string;
|
|
53
|
+
acknowledged: boolean;
|
|
54
|
+
}
|
|
26
55
|
interface Bridge {
|
|
27
56
|
send: <Req = unknown, Res = unknown>(key: string, payload?: Req, timeoutDuration?: number) => Promise<Res>;
|
|
57
|
+
/**
|
|
58
|
+
* Send a critical operation that requires acknowledgment and idempotency.
|
|
59
|
+
* Use this for transfers, signing, and other non-idempotent operations.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const result = await bridge.sendCritical('transfer', {
|
|
64
|
+
* to: '0x...',
|
|
65
|
+
* amount: '1000000',
|
|
66
|
+
* }, {
|
|
67
|
+
* onAcknowledged: () => setStatus('processing'),
|
|
68
|
+
* noQueue: true, // Don't auto-retry transfers
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
/** Alias: clearer naming for nonce/idempotency semantics */
|
|
73
|
+
sendWithNonce: <Req = unknown, Res = unknown>(key: string, payload?: Req, options?: CriticalOperationOptions, timeoutDuration?: number) => Promise<CriticalOperationResult<Res>>;
|
|
74
|
+
/** Alias for callers that think in idempotency terms */
|
|
75
|
+
sendIdempotent: <Req = unknown, Res = unknown>(key: string, payload?: Req, options?: CriticalOperationOptions, timeoutDuration?: number) => Promise<CriticalOperationResult<Res>>;
|
|
76
|
+
/** Back-compat name (kept) */
|
|
77
|
+
sendCritical: <Req = unknown, Res = unknown>(key: string, payload?: Req, options?: CriticalOperationOptions, timeoutDuration?: number) => Promise<CriticalOperationResult<Res>>;
|
|
28
78
|
broadcast: (key: string, payload: unknown) => void;
|
|
29
79
|
on: (key: string, handler: (payload: unknown) => void) => void;
|
|
30
80
|
off: (key: string, handler: (payload: unknown) => void) => void;
|
|
@@ -291,4 +341,4 @@ declare function useServiceWorkerHealthSimple(options?: ServiceWorkerHealthOptio
|
|
|
291
341
|
};
|
|
292
342
|
|
|
293
343
|
export { BridgeProvider, ServiceWorkerHealthProvider, getHealthStatus, subscribeToHealth, useBridge, useBridgeQuery, useConnectionStatus, useServiceWorkerHealth, useServiceWorkerHealthSimple };
|
|
294
|
-
export type { HealthStatus, ServiceWorkerHealthContextValue };
|
|
344
|
+
export type { CriticalOperationOptions, CriticalOperationResult, HealthStatus, ServiceWorkerHealthContextValue };
|
package/dist/index.js
CHANGED
|
@@ -67,18 +67,18 @@ const recordDiagnostics = {
|
|
|
67
67
|
const CONFIG = {
|
|
68
68
|
RETRY_AFTER: 1e3,
|
|
69
69
|
MAX_RETRIES: 10,
|
|
70
|
-
PING_INTERVAL:
|
|
71
|
-
// Check every
|
|
70
|
+
PING_INTERVAL: 5e3,
|
|
71
|
+
// Check every 5s for faster SW down detection
|
|
72
72
|
MAX_RETRY_COOLDOWN: 3e4,
|
|
73
73
|
DEFAULT_TIMEOUT: 6e4,
|
|
74
74
|
// 60s default for slow operations
|
|
75
75
|
MAX_RETRY_DELAY: 3e4,
|
|
76
|
-
PING_TIMEOUT:
|
|
77
|
-
// Give SW
|
|
76
|
+
PING_TIMEOUT: 8e3,
|
|
77
|
+
// Give SW 8s to respond to ping (reduced from 20s)
|
|
78
78
|
ERROR_CHECK_INTERVAL: 100,
|
|
79
79
|
MAX_ERROR_CHECKS: 10,
|
|
80
|
-
CONSECUTIVE_FAILURE_THRESHOLD:
|
|
81
|
-
// Require
|
|
80
|
+
CONSECUTIVE_FAILURE_THRESHOLD: 2,
|
|
81
|
+
// Require 2 consecutive failures (~10s total) before reconnecting
|
|
82
82
|
RECONNECT_DELAY: 100,
|
|
83
83
|
PORT_NAME: "chroma-bridge",
|
|
84
84
|
// Service worker restart retry settings (indefinite retries)
|
|
@@ -100,8 +100,17 @@ const CONFIG = {
|
|
|
100
100
|
QUEUE_DRAIN_DELAY: 50,
|
|
101
101
|
// Delay between processing queued requests
|
|
102
102
|
// Optimistic health - don't surface unhealthy state immediately
|
|
103
|
-
HEALTH_GRACE_PERIOD_MS:
|
|
104
|
-
// Wait this long before showing unhealthy
|
|
103
|
+
HEALTH_GRACE_PERIOD_MS: 1e3,
|
|
104
|
+
// Wait this long before showing unhealthy (reduced from 3s)
|
|
105
|
+
// Critical operation settings
|
|
106
|
+
CRITICAL_OP_TIMEOUT: 12e4
|
|
107
|
+
// 2 minutes for critical operations (transfers, signing)
|
|
108
|
+
};
|
|
109
|
+
const generateNonce = () => {
|
|
110
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
111
|
+
return crypto.randomUUID();
|
|
112
|
+
}
|
|
113
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
105
114
|
};
|
|
106
115
|
const calculateBackoffDelay = (attempt, baseDelay, maxDelay) => Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
107
116
|
const clearTimeoutSafe = (ref) => {
|
|
@@ -318,7 +327,9 @@ function createBridgeInstance(deps) {
|
|
|
318
327
|
resolve,
|
|
319
328
|
reject,
|
|
320
329
|
timeout,
|
|
321
|
-
timeoutDuration
|
|
330
|
+
timeoutDuration,
|
|
331
|
+
key,
|
|
332
|
+
payload
|
|
322
333
|
});
|
|
323
334
|
if (!isPortValid()) {
|
|
324
335
|
if (isReconnectingRef.current) {
|
|
@@ -452,7 +463,67 @@ function createBridgeInstance(deps) {
|
|
|
452
463
|
}
|
|
453
464
|
return false;
|
|
454
465
|
}
|
|
455
|
-
}
|
|
466
|
+
},
|
|
467
|
+
sendCritical: async (key, payload, options, timeoutDuration = CONFIG.CRITICAL_OP_TIMEOUT) => {
|
|
468
|
+
const nonce = options?.nonce || generateNonce();
|
|
469
|
+
const noQueue = options?.noQueue ?? false;
|
|
470
|
+
if (noQueue && !isPortValid()) {
|
|
471
|
+
throw new Error("Not connected. Please try again.");
|
|
472
|
+
}
|
|
473
|
+
const criticalPayload = {
|
|
474
|
+
__critical__: true,
|
|
475
|
+
__nonce__: nonce,
|
|
476
|
+
__timestamp__: Date.now(),
|
|
477
|
+
data: payload
|
|
478
|
+
};
|
|
479
|
+
let acknowledged = false;
|
|
480
|
+
const ackKey = `__ack__:${nonce}`;
|
|
481
|
+
const ackPromise = new Promise((resolveAck) => {
|
|
482
|
+
const ackHandler = () => {
|
|
483
|
+
acknowledged = true;
|
|
484
|
+
options?.onAcknowledged?.();
|
|
485
|
+
resolveAck();
|
|
486
|
+
off(ackKey, ackHandler);
|
|
487
|
+
};
|
|
488
|
+
on(ackKey, ackHandler);
|
|
489
|
+
setTimeout(() => {
|
|
490
|
+
off(ackKey, ackHandler);
|
|
491
|
+
resolveAck();
|
|
492
|
+
}, 5e3);
|
|
493
|
+
});
|
|
494
|
+
{
|
|
495
|
+
console.log(`[Bridge] Sending critical operation: ${key} (nonce: ${nonce})`);
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const [data] = await Promise.all([
|
|
499
|
+
send(key, criticalPayload, timeoutDuration),
|
|
500
|
+
ackPromise
|
|
501
|
+
]);
|
|
502
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
503
|
+
console.log(
|
|
504
|
+
`[Bridge] Critical operation completed: ${key} (nonce: ${nonce}, acked: ${acknowledged})`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
data,
|
|
509
|
+
nonce,
|
|
510
|
+
acknowledged
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
{
|
|
514
|
+
console.error(
|
|
515
|
+
`[Bridge] Critical operation failed: ${key} (nonce: ${nonce}, acked: ${acknowledged})`,
|
|
516
|
+
error
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
520
|
+
err.nonce = nonce;
|
|
521
|
+
err.acknowledged = acknowledged;
|
|
522
|
+
throw err;
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
sendWithNonce: async (key, payload, options, timeoutDuration) => bridge.sendCritical(key, payload, options, timeoutDuration),
|
|
526
|
+
sendIdempotent: async (key, payload, options, timeoutDuration) => bridge.sendCritical(key, payload, options, timeoutDuration)
|
|
456
527
|
};
|
|
457
528
|
return bridge;
|
|
458
529
|
}
|
|
@@ -613,22 +684,32 @@ const BridgeProvider = ({
|
|
|
613
684
|
}
|
|
614
685
|
portDisconnectedRef.current = false;
|
|
615
686
|
if (preserveQueue) {
|
|
616
|
-
pendingRequestsRef.current.
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
687
|
+
const pendingCount = pendingRequestsRef.current.size;
|
|
688
|
+
if (pendingCount > 0 && BRIDGE_ENABLE_LOGS) {
|
|
689
|
+
console.log(
|
|
690
|
+
`[Bridge] Preserving ${pendingCount} pending requests for retry after reconnect`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
pendingRequestsRef.current.forEach(
|
|
694
|
+
({ resolve, reject, timeout, timeoutDuration, key, payload }, id) => {
|
|
695
|
+
clearTimeout(timeout);
|
|
696
|
+
requestQueueRef.current.push({
|
|
697
|
+
id,
|
|
698
|
+
key,
|
|
699
|
+
payload,
|
|
700
|
+
// Preserve the original payload for retry
|
|
701
|
+
timeoutDuration,
|
|
702
|
+
resolve,
|
|
703
|
+
reject,
|
|
704
|
+
retryCount: 0,
|
|
705
|
+
maxRetries: CONFIG.REQUEST_MAX_RETRIES,
|
|
706
|
+
queuedAt: Date.now()
|
|
707
|
+
});
|
|
708
|
+
{
|
|
709
|
+
console.log(`[Bridge] Queued pending request for retry: ${key}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
);
|
|
632
713
|
pendingRequestsRef.current.clear();
|
|
633
714
|
} else {
|
|
634
715
|
pendingRequestsRef.current.forEach(({ reject, timeout }) => {
|
|
@@ -667,7 +748,14 @@ const BridgeProvider = ({
|
|
|
667
748
|
return;
|
|
668
749
|
}
|
|
669
750
|
if (message.type === "broadcast" && message.key) {
|
|
670
|
-
eventListenersRef.current.get(message.key)
|
|
751
|
+
const listeners = eventListenersRef.current.get(message.key);
|
|
752
|
+
const listenerCount = listeners?.size ?? 0;
|
|
753
|
+
{
|
|
754
|
+
console.log(
|
|
755
|
+
`[Bridge] \u{1F4E1} Received broadcast: ${message.key}, dispatching to ${listenerCount} listeners`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
listeners?.forEach((handler) => {
|
|
671
759
|
try {
|
|
672
760
|
handler(message.payload);
|
|
673
761
|
} catch (err) {
|
|
@@ -725,6 +813,9 @@ const BridgeProvider = ({
|
|
|
725
813
|
);
|
|
726
814
|
}
|
|
727
815
|
bridgeRef.current.send(request.key, request.payload, request.timeoutDuration - queuedDuration).then((data) => {
|
|
816
|
+
{
|
|
817
|
+
console.log(`[Bridge] \u2705 Queued request succeeded: ${request.key}`);
|
|
818
|
+
}
|
|
728
819
|
if (request.idempotencyKey) {
|
|
729
820
|
activeIdempotencyKeysRef.current.delete(request.idempotencyKey);
|
|
730
821
|
}
|
|
@@ -750,6 +841,12 @@ const BridgeProvider = ({
|
|
|
750
841
|
}, retryDelay);
|
|
751
842
|
return;
|
|
752
843
|
}
|
|
844
|
+
{
|
|
845
|
+
console.error(
|
|
846
|
+
`[Bridge] \u274C Queued request failed after ${request.maxRetries} retries: ${request.key}`,
|
|
847
|
+
error2
|
|
848
|
+
);
|
|
849
|
+
}
|
|
753
850
|
if (request.idempotencyKey) {
|
|
754
851
|
activeIdempotencyKeysRef.current.delete(request.idempotencyKey);
|
|
755
852
|
}
|
|
@@ -948,11 +1045,145 @@ const BridgeProvider = ({
|
|
|
948
1045
|
swRestartRetryCountRef.current = 0;
|
|
949
1046
|
consecutiveTimeoutsRef.current = 0;
|
|
950
1047
|
isConnectingRef.current = false;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1048
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1049
|
+
const queueSize = requestQueueRef.current.length;
|
|
1050
|
+
const pendingSize = pendingRequestsRef.current.size;
|
|
1051
|
+
console.log(`[Bridge] \u2705 PORT CONNECTED | Queued: ${queueSize} | Pending: ${pendingSize}`);
|
|
1052
|
+
}
|
|
1053
|
+
const verifyConnection = (targetPort) => {
|
|
1054
|
+
const VERIFY_PING_TIMEOUT = 8e3;
|
|
1055
|
+
const MAX_VERIFY_RETRIES = 3;
|
|
1056
|
+
const VERIFY_RETRY_DELAY = 2e3;
|
|
1057
|
+
const attemptVerify = (attempt) => {
|
|
1058
|
+
if (portRef.current !== targetPort) {
|
|
1059
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1060
|
+
console.log(`[Bridge] Verification aborted - port changed (attempt ${attempt})`);
|
|
1061
|
+
}
|
|
1062
|
+
return Promise.resolve(false);
|
|
1063
|
+
}
|
|
1064
|
+
if (attempt > MAX_VERIFY_RETRIES) {
|
|
1065
|
+
return Promise.resolve(false);
|
|
1066
|
+
}
|
|
1067
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1068
|
+
console.log(
|
|
1069
|
+
`[Bridge] Verifying connection (attempt ${attempt}/${MAX_VERIFY_RETRIES})...`
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
const pingId = `verify_${Date.now()}_${attempt}`;
|
|
1073
|
+
return new Promise((resolve) => {
|
|
1074
|
+
if (portRef.current !== targetPort) {
|
|
1075
|
+
resolve(false);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const timeout = setTimeout(() => {
|
|
1079
|
+
pendingRequestsRef.current.delete(pingId);
|
|
1080
|
+
if (portRef.current !== targetPort) {
|
|
1081
|
+
resolve(false);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1085
|
+
console.warn(`[Bridge] Verification ping timeout (attempt ${attempt})`);
|
|
1086
|
+
}
|
|
1087
|
+
setTimeout(() => {
|
|
1088
|
+
attemptVerify(attempt + 1).then(resolve);
|
|
1089
|
+
}, VERIFY_RETRY_DELAY);
|
|
1090
|
+
}, VERIFY_PING_TIMEOUT);
|
|
1091
|
+
pendingRequestsRef.current.set(pingId, {
|
|
1092
|
+
resolve: () => {
|
|
1093
|
+
clearTimeout(timeout);
|
|
1094
|
+
if (portRef.current !== targetPort) {
|
|
1095
|
+
resolve(false);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1099
|
+
console.log("[Bridge] \u2705 VERIFIED - SW is responding");
|
|
1100
|
+
}
|
|
1101
|
+
resolve(true);
|
|
1102
|
+
},
|
|
1103
|
+
reject: () => {
|
|
1104
|
+
clearTimeout(timeout);
|
|
1105
|
+
if (portRef.current !== targetPort) {
|
|
1106
|
+
resolve(false);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1110
|
+
console.warn(`[Bridge] Verification ping error (attempt ${attempt})`);
|
|
1111
|
+
}
|
|
1112
|
+
setTimeout(() => {
|
|
1113
|
+
attemptVerify(attempt + 1).then(resolve);
|
|
1114
|
+
}, VERIFY_RETRY_DELAY);
|
|
1115
|
+
},
|
|
1116
|
+
timeout,
|
|
1117
|
+
timeoutDuration: VERIFY_PING_TIMEOUT,
|
|
1118
|
+
key: "__ping__",
|
|
1119
|
+
payload: void 0
|
|
1120
|
+
});
|
|
1121
|
+
try {
|
|
1122
|
+
targetPort.postMessage({ id: pingId, key: "__ping__", payload: void 0 });
|
|
1123
|
+
} catch (e) {
|
|
1124
|
+
clearTimeout(timeout);
|
|
1125
|
+
pendingRequestsRef.current.delete(pingId);
|
|
1126
|
+
if (portRef.current !== targetPort) {
|
|
1127
|
+
resolve(false);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1131
|
+
console.warn(`[Bridge] Verification postMessage error (attempt ${attempt}):`, e);
|
|
1132
|
+
}
|
|
1133
|
+
setTimeout(() => {
|
|
1134
|
+
attemptVerify(attempt + 1).then(resolve);
|
|
1135
|
+
}, VERIFY_RETRY_DELAY);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
};
|
|
1139
|
+
return attemptVerify(1);
|
|
1140
|
+
};
|
|
1141
|
+
const startVerification = () => {
|
|
1142
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1143
|
+
console.log("[Bridge] Starting verification after initial delay...");
|
|
954
1144
|
}
|
|
955
|
-
|
|
1145
|
+
verifyConnection(port).then((verified) => {
|
|
1146
|
+
if (!isMountedRef.current || portRef.current !== port) {
|
|
1147
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1148
|
+
console.log("[Bridge] Verification callback aborted - context changed");
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (!verified) {
|
|
1153
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1154
|
+
console.error("[Bridge] \u274C Connection verification failed - SW not responding");
|
|
1155
|
+
}
|
|
1156
|
+
isConnectingRef.current = false;
|
|
1157
|
+
isReconnectingRef.current = true;
|
|
1158
|
+
scheduleSwRestartReconnect(connect);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1162
|
+
const queueSize = requestQueueRef.current.length;
|
|
1163
|
+
console.log(
|
|
1164
|
+
`[Bridge] \u2705 RECONNECTED SUCCESSFULLY | Queue: ${queueSize} requests to drain`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
setTimeout(() => {
|
|
1168
|
+
if (isMountedRef.current && isConnectedRef.current) {
|
|
1169
|
+
drainRequestQueue();
|
|
1170
|
+
}
|
|
1171
|
+
}, 200);
|
|
1172
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1173
|
+
console.log("[Bridge] Emitting bridge:connected event to stores");
|
|
1174
|
+
}
|
|
1175
|
+
eventListenersRef.current.get("bridge:connected")?.forEach((handler) => {
|
|
1176
|
+
try {
|
|
1177
|
+
handler({ timestamp: Date.now() });
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
if (BRIDGE_ENABLE_LOGS) {
|
|
1180
|
+
console.warn("[Bridge] bridge:connected handler error:", err);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
};
|
|
1186
|
+
setTimeout(startVerification, 2e3);
|
|
956
1187
|
reconnectionGracePeriodRef.current = true;
|
|
957
1188
|
setTimeout(() => {
|
|
958
1189
|
reconnectionGracePeriodRef.current = false;
|
|
@@ -960,15 +1191,6 @@ const BridgeProvider = ({
|
|
|
960
1191
|
console.log("[Bridge] Grace period ended, timeout monitoring active");
|
|
961
1192
|
}
|
|
962
1193
|
}, 1e4);
|
|
963
|
-
eventListenersRef.current.get("bridge:connected")?.forEach((handler) => {
|
|
964
|
-
try {
|
|
965
|
-
handler({ timestamp: Date.now() });
|
|
966
|
-
} catch (err) {
|
|
967
|
-
if (BRIDGE_ENABLE_LOGS) {
|
|
968
|
-
console.warn("[Bridge] bridge:connected handler error:", err);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
});
|
|
972
1194
|
const rejectAllPendingRequests = (message) => {
|
|
973
1195
|
pendingRequestsRef.current.forEach(({ reject, timeout, timeoutDuration }, id) => {
|
|
974
1196
|
if (timeoutDuration <= timeoutFailureThreshold) {
|
|
@@ -1259,7 +1481,7 @@ function broadcastHealthChange(status, isHealthy) {
|
|
|
1259
1481
|
});
|
|
1260
1482
|
}
|
|
1261
1483
|
const ServiceWorkerHealthContext = createContext(null);
|
|
1262
|
-
const HEALTH_GRACE_PERIOD_MS =
|
|
1484
|
+
const HEALTH_GRACE_PERIOD_MS = 1e3;
|
|
1263
1485
|
const ServiceWorkerHealthProvider = ({
|
|
1264
1486
|
children,
|
|
1265
1487
|
onHealthChange
|
|
@@ -1289,7 +1511,7 @@ const ServiceWorkerHealthProvider = ({
|
|
|
1289
1511
|
clearTimeout(graceTimeoutRef.current);
|
|
1290
1512
|
graceTimeoutRef.current = null;
|
|
1291
1513
|
}
|
|
1292
|
-
} else if (
|
|
1514
|
+
} else if (!unhealthyStartedAt) {
|
|
1293
1515
|
setUnhealthyStartedAt(Date.now());
|
|
1294
1516
|
graceTimeoutRef.current = setTimeout(() => {
|
|
1295
1517
|
setGraceExpired(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chromahq/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "React bindings for the Chroma Chrome extension framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,16 +41,20 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
43
43
|
"@rollup/plugin-typescript": "^12.1.3",
|
|
44
|
+
"@testing-library/react": "^16.0.0",
|
|
44
45
|
"@types/react": "^18.2.7",
|
|
46
|
+
"jsdom": "^25.0.0",
|
|
45
47
|
"rollup": "^4.8.0",
|
|
46
48
|
"rollup-plugin-dts": "^6.1.0",
|
|
47
49
|
"rollup-plugin-esbuild": "^6.1.0",
|
|
48
50
|
"typescript": "^5.6.0",
|
|
49
51
|
"react": "^19.1.0",
|
|
50
|
-
"react-dom": "^19.1.0"
|
|
52
|
+
"react-dom": "^19.1.0",
|
|
53
|
+
"vitest": "^3.2.4"
|
|
51
54
|
},
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "rollup -c",
|
|
54
|
-
"dev": "rollup -c --watch"
|
|
57
|
+
"dev": "rollup -c --watch",
|
|
58
|
+
"test": "vitest run"
|
|
55
59
|
}
|
|
56
60
|
}
|