@chromahq/react 1.0.41 → 1.0.43

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 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,6 +2,68 @@ 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,
@@ -71,11 +133,71 @@ function createBridgeInstance(deps) {
71
133
  };
72
134
  const send = (key, payload, timeoutDuration = defaultTimeout) => {
73
135
  return new Promise((resolve, reject) => {
74
- if (!portRef.current) {
75
- reject(new Error("Bridge disconnected"));
76
- return;
77
- }
78
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
+ };
79
201
  const timeout = setTimeout(() => {
80
202
  if (!pendingRequestsRef.current.has(id)) return;
81
203
  pendingRequestsRef.current.delete(id);
@@ -106,17 +228,16 @@ function createBridgeInstance(deps) {
106
228
  timeout,
107
229
  timeoutDuration
108
230
  });
231
+ if (!portRef.current) {
232
+ triggerRuntimeFallback("port-unavailable");
233
+ return;
234
+ }
109
235
  try {
110
236
  portRef.current.postMessage({ id, key, payload });
111
237
  setTimeout(() => {
112
238
  const errorMessage = consumeRuntimeError();
113
239
  if (errorMessage && pendingRequestsRef.current.has(id)) {
114
- const pending = pendingRequestsRef.current.get(id);
115
- if (pending) {
116
- clearTimeout(pending.timeout);
117
- pendingRequestsRef.current.delete(id);
118
- reject(new Error(errorMessage));
119
- }
240
+ finalizePendingWithError(errorMessage);
120
241
  }
121
242
  }, 0);
122
243
  const immediateError = consumeRuntimeError();
@@ -124,12 +245,10 @@ function createBridgeInstance(deps) {
124
245
  throw new Error(immediateError);
125
246
  }
126
247
  } catch (e) {
127
- const pending = pendingRequestsRef.current.get(id);
128
- if (pending) {
129
- clearTimeout(pending.timeout);
130
- pendingRequestsRef.current.delete(id);
248
+ {
249
+ console.warn("[Bridge] Port send failed, attempting fallback", e);
131
250
  }
132
- reject(e instanceof Error ? e : new Error("Send failed"));
251
+ triggerRuntimeFallback("port-postmessage-error");
133
252
  }
134
253
  });
135
254
  };
@@ -235,6 +354,7 @@ function startHealthMonitor(deps) {
235
354
  consecutivePingFailuresRef.current = 0;
236
355
  return;
237
356
  }
357
+ recordDiagnostics.pingFailure();
238
358
  consecutivePingFailuresRef.current++;
239
359
  {
240
360
  console.warn(`[Bridge] Ping failed (${consecutivePingFailuresRef.current}x)`);
@@ -502,6 +622,7 @@ const BridgeProvider = ({
502
622
  }
503
623
  isConnectingRef.current = false;
504
624
  const disconnectError = consumeRuntimeError();
625
+ recordDiagnostics.portDisconnect(disconnectError);
505
626
  if (BRIDGE_ENABLE_LOGS) {
506
627
  console.warn("[Bridge] Disconnect error:", disconnectError || "(none)");
507
628
  console.warn("[Bridge] isMounted:", isMountedRef.current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chromahq/react",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "React bindings for the Chroma Chrome extension framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",