@chromahq/react 1.0.40 → 1.0.42
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 +22 -0
- package/dist/index.js +147 -25
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { FC, ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
interface BridgeDiagnosticsSnapshot {
|
|
4
|
+
lastUpdatedAt: number;
|
|
5
|
+
portDisconnects: number;
|
|
6
|
+
lastDisconnectError?: string;
|
|
7
|
+
lastDisconnectAt?: number;
|
|
8
|
+
sendMessageFallbacks: number;
|
|
9
|
+
lastFallbackReason?: string;
|
|
10
|
+
lastFallbackAt?: number;
|
|
11
|
+
sendMessageFallbackErrors: number;
|
|
12
|
+
lastFallbackError?: string;
|
|
13
|
+
pingFailures: number;
|
|
14
|
+
lastPingFailureAt?: number;
|
|
15
|
+
}
|
|
16
|
+
interface BridgeDiagnosticsApi {
|
|
17
|
+
get: () => BridgeDiagnosticsSnapshot;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
declare global {
|
|
21
|
+
interface Window {
|
|
22
|
+
__CHROMA_BRIDGE_DIAGNOSTICS__?: BridgeDiagnosticsApi;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
3
25
|
type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error' | 'reconnecting';
|
|
4
26
|
interface Bridge {
|
|
5
27
|
send: <Req = unknown, Res = unknown>(key: string, payload?: Req, timeoutDuration?: number) => Promise<Res>;
|
package/dist/index.js
CHANGED
|
@@ -2,29 +2,92 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext, useSyncExternalStore } from 'react';
|
|
3
3
|
|
|
4
4
|
const BRIDGE_ENABLE_LOGS = true;
|
|
5
|
+
const DIRECT_MESSAGE_FLAG = "__CHROMA_BRIDGE_DIRECT_MESSAGE__";
|
|
6
|
+
const createInitialDiagnosticsState = () => ({
|
|
7
|
+
lastUpdatedAt: Date.now(),
|
|
8
|
+
portDisconnects: 0,
|
|
9
|
+
lastDisconnectError: void 0,
|
|
10
|
+
lastDisconnectAt: void 0,
|
|
11
|
+
sendMessageFallbacks: 0,
|
|
12
|
+
lastFallbackReason: void 0,
|
|
13
|
+
lastFallbackAt: void 0,
|
|
14
|
+
sendMessageFallbackErrors: 0,
|
|
15
|
+
lastFallbackError: void 0,
|
|
16
|
+
pingFailures: 0,
|
|
17
|
+
lastPingFailureAt: void 0
|
|
18
|
+
});
|
|
19
|
+
const bridgeDiagnosticsState = createInitialDiagnosticsState();
|
|
20
|
+
const updateDiagnosticsState = (mutator) => {
|
|
21
|
+
mutator(bridgeDiagnosticsState);
|
|
22
|
+
bridgeDiagnosticsState.lastUpdatedAt = Date.now();
|
|
23
|
+
};
|
|
24
|
+
const resetDiagnosticsState = () => {
|
|
25
|
+
Object.assign(bridgeDiagnosticsState, createInitialDiagnosticsState());
|
|
26
|
+
};
|
|
27
|
+
const attachDiagnosticsApi = () => {
|
|
28
|
+
if (typeof window === "undefined") {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
window.__CHROMA_BRIDGE_DIAGNOSTICS__ = {
|
|
32
|
+
get: () => ({ ...bridgeDiagnosticsState }),
|
|
33
|
+
reset: () => {
|
|
34
|
+
resetDiagnosticsState();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
attachDiagnosticsApi();
|
|
39
|
+
const recordDiagnostics = {
|
|
40
|
+
portDisconnect: (error) => {
|
|
41
|
+
updateDiagnosticsState((state) => {
|
|
42
|
+
state.portDisconnects += 1;
|
|
43
|
+
state.lastDisconnectError = error;
|
|
44
|
+
state.lastDisconnectAt = Date.now();
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
fallbackSent: (reason) => {
|
|
48
|
+
updateDiagnosticsState((state) => {
|
|
49
|
+
state.sendMessageFallbacks += 1;
|
|
50
|
+
state.lastFallbackReason = reason;
|
|
51
|
+
state.lastFallbackAt = Date.now();
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
fallbackError: (error) => {
|
|
55
|
+
updateDiagnosticsState((state) => {
|
|
56
|
+
state.sendMessageFallbackErrors += 1;
|
|
57
|
+
state.lastFallbackError = error;
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
pingFailure: () => {
|
|
61
|
+
updateDiagnosticsState((state) => {
|
|
62
|
+
state.pingFailures += 1;
|
|
63
|
+
state.lastPingFailureAt = Date.now();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
5
67
|
const CONFIG = {
|
|
6
68
|
RETRY_AFTER: 1e3,
|
|
7
69
|
MAX_RETRIES: 10,
|
|
8
|
-
PING_INTERVAL:
|
|
9
|
-
// Check every
|
|
70
|
+
PING_INTERVAL: 15e3,
|
|
71
|
+
// Check every 15s (tolerant of long operations)
|
|
10
72
|
MAX_RETRY_COOLDOWN: 3e4,
|
|
11
|
-
DEFAULT_TIMEOUT:
|
|
12
|
-
//
|
|
73
|
+
DEFAULT_TIMEOUT: 6e4,
|
|
74
|
+
// 60s default for slow operations
|
|
13
75
|
MAX_RETRY_DELAY: 3e4,
|
|
14
|
-
PING_TIMEOUT:
|
|
15
|
-
// Give SW
|
|
76
|
+
PING_TIMEOUT: 2e4,
|
|
77
|
+
// Give SW 20s to respond to ping
|
|
16
78
|
ERROR_CHECK_INTERVAL: 100,
|
|
17
79
|
MAX_ERROR_CHECKS: 10,
|
|
18
80
|
CONSECUTIVE_FAILURE_THRESHOLD: 5,
|
|
19
|
-
// Require 5 consecutive failures (
|
|
81
|
+
// Require 5 consecutive failures (75s total) before reconnecting
|
|
20
82
|
RECONNECT_DELAY: 100,
|
|
21
83
|
PORT_NAME: "chroma-bridge",
|
|
22
84
|
// Service worker restart retry settings (indefinite retries)
|
|
23
85
|
SW_RESTART_RETRY_DELAY: 500,
|
|
24
86
|
SW_RESTART_MAX_DELAY: 5e3,
|
|
25
87
|
// Threshold for counting timeouts toward reconnection (only count fast timeouts as failures)
|
|
26
|
-
|
|
27
|
-
|
|
88
|
+
// Requests with timeout > this value are considered intentional long operations
|
|
89
|
+
TIMEOUT_FAILURE_THRESHOLD_MS: 3e4
|
|
90
|
+
// Only count timeouts < 30s as potential SW issues
|
|
28
91
|
};
|
|
29
92
|
const calculateBackoffDelay = (attempt, baseDelay, maxDelay) => Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
30
93
|
const clearTimeoutSafe = (ref) => {
|
|
@@ -70,11 +133,71 @@ function createBridgeInstance(deps) {
|
|
|
70
133
|
};
|
|
71
134
|
const send = (key, payload, timeoutDuration = defaultTimeout) => {
|
|
72
135
|
return new Promise((resolve, reject) => {
|
|
73
|
-
if (!portRef.current) {
|
|
74
|
-
reject(new Error("Bridge disconnected"));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
136
|
const id = `msg_${++messageIdRef.current}`;
|
|
137
|
+
const finalizePendingWithError = (error) => {
|
|
138
|
+
const pending = pendingRequestsRef.current.get(id);
|
|
139
|
+
if (!pending) return;
|
|
140
|
+
clearTimeout(pending.timeout);
|
|
141
|
+
pendingRequestsRef.current.delete(id);
|
|
142
|
+
pending.reject(error instanceof Error ? error : new Error(error));
|
|
143
|
+
};
|
|
144
|
+
const triggerRuntimeFallback = (reason) => {
|
|
145
|
+
{
|
|
146
|
+
console.warn(`[Bridge] Falling back to runtime.sendMessage (${reason})`);
|
|
147
|
+
}
|
|
148
|
+
recordDiagnostics.fallbackSent(reason);
|
|
149
|
+
onReconnectNeeded();
|
|
150
|
+
if (!chrome?.runtime?.sendMessage) {
|
|
151
|
+
const message = "chrome.runtime.sendMessage not available";
|
|
152
|
+
recordDiagnostics.fallbackError(message);
|
|
153
|
+
finalizePendingWithError(message);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
chrome.runtime.sendMessage(
|
|
158
|
+
{
|
|
159
|
+
id,
|
|
160
|
+
key,
|
|
161
|
+
payload,
|
|
162
|
+
metadata: { transport: "direct", fallbackReason: reason },
|
|
163
|
+
[DIRECT_MESSAGE_FLAG]: true
|
|
164
|
+
},
|
|
165
|
+
(response) => {
|
|
166
|
+
const runtimeError = consumeRuntimeError();
|
|
167
|
+
const pending = pendingRequestsRef.current.get(id);
|
|
168
|
+
if (!pending) return;
|
|
169
|
+
if (runtimeError) {
|
|
170
|
+
recordDiagnostics.fallbackError(runtimeError);
|
|
171
|
+
clearTimeout(pending.timeout);
|
|
172
|
+
pendingRequestsRef.current.delete(id);
|
|
173
|
+
pending.reject(new Error(runtimeError));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!response) {
|
|
177
|
+
const message = "No response from service worker";
|
|
178
|
+
recordDiagnostics.fallbackError(message);
|
|
179
|
+
clearTimeout(pending.timeout);
|
|
180
|
+
pendingRequestsRef.current.delete(id);
|
|
181
|
+
pending.reject(new Error(message));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
clearTimeout(pending.timeout);
|
|
185
|
+
pendingRequestsRef.current.delete(id);
|
|
186
|
+
consecutiveTimeoutsRef.current = 0;
|
|
187
|
+
if (response.error) {
|
|
188
|
+
recordDiagnostics.fallbackError(response.error);
|
|
189
|
+
pending.reject(new Error(response.error));
|
|
190
|
+
} else {
|
|
191
|
+
pending.resolve(response.data);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const message = error instanceof Error ? error.message : "chrome.runtime.sendMessage failed";
|
|
197
|
+
recordDiagnostics.fallbackError(message);
|
|
198
|
+
finalizePendingWithError(error instanceof Error ? error : new Error(message));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
78
201
|
const timeout = setTimeout(() => {
|
|
79
202
|
if (!pendingRequestsRef.current.has(id)) return;
|
|
80
203
|
pendingRequestsRef.current.delete(id);
|
|
@@ -105,17 +228,16 @@ function createBridgeInstance(deps) {
|
|
|
105
228
|
timeout,
|
|
106
229
|
timeoutDuration
|
|
107
230
|
});
|
|
231
|
+
if (!portRef.current) {
|
|
232
|
+
triggerRuntimeFallback("port-unavailable");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
108
235
|
try {
|
|
109
236
|
portRef.current.postMessage({ id, key, payload });
|
|
110
237
|
setTimeout(() => {
|
|
111
238
|
const errorMessage = consumeRuntimeError();
|
|
112
239
|
if (errorMessage && pendingRequestsRef.current.has(id)) {
|
|
113
|
-
|
|
114
|
-
if (pending) {
|
|
115
|
-
clearTimeout(pending.timeout);
|
|
116
|
-
pendingRequestsRef.current.delete(id);
|
|
117
|
-
reject(new Error(errorMessage));
|
|
118
|
-
}
|
|
240
|
+
finalizePendingWithError(errorMessage);
|
|
119
241
|
}
|
|
120
242
|
}, 0);
|
|
121
243
|
const immediateError = consumeRuntimeError();
|
|
@@ -123,12 +245,10 @@ function createBridgeInstance(deps) {
|
|
|
123
245
|
throw new Error(immediateError);
|
|
124
246
|
}
|
|
125
247
|
} catch (e) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
clearTimeout(pending.timeout);
|
|
129
|
-
pendingRequestsRef.current.delete(id);
|
|
248
|
+
{
|
|
249
|
+
console.warn("[Bridge] Port send failed, attempting fallback", e);
|
|
130
250
|
}
|
|
131
|
-
|
|
251
|
+
triggerRuntimeFallback("port-postmessage-error");
|
|
132
252
|
}
|
|
133
253
|
});
|
|
134
254
|
};
|
|
@@ -234,6 +354,7 @@ function startHealthMonitor(deps) {
|
|
|
234
354
|
consecutivePingFailuresRef.current = 0;
|
|
235
355
|
return;
|
|
236
356
|
}
|
|
357
|
+
recordDiagnostics.pingFailure();
|
|
237
358
|
consecutivePingFailuresRef.current++;
|
|
238
359
|
{
|
|
239
360
|
console.warn(`[Bridge] Ping failed (${consecutivePingFailuresRef.current}x)`);
|
|
@@ -501,6 +622,7 @@ const BridgeProvider = ({
|
|
|
501
622
|
}
|
|
502
623
|
isConnectingRef.current = false;
|
|
503
624
|
const disconnectError = consumeRuntimeError();
|
|
625
|
+
recordDiagnostics.portDisconnect(disconnectError);
|
|
504
626
|
if (BRIDGE_ENABLE_LOGS) {
|
|
505
627
|
console.warn("[Bridge] Disconnect error:", disconnectError || "(none)");
|
|
506
628
|
console.warn("[Bridge] isMounted:", isMountedRef.current);
|
|
@@ -559,7 +681,7 @@ const BridgeProvider = ({
|
|
|
559
681
|
if (BRIDGE_ENABLE_LOGS) {
|
|
560
682
|
console.log("[Bridge] Grace period ended, timeout monitoring active");
|
|
561
683
|
}
|
|
562
|
-
},
|
|
684
|
+
}, 1e4);
|
|
563
685
|
eventListenersRef.current.get("bridge:connected")?.forEach((handler) => {
|
|
564
686
|
try {
|
|
565
687
|
handler({ timestamp: Date.now() });
|