@chromahq/react 1.0.9 → 1.0.11

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,21 +1,24 @@
1
- type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
1
+ type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error' | 'reconnecting';
2
2
  interface Bridge {
3
3
  send: <Req = unknown, Res = unknown>(key: string, payload?: Req, timeoutDuration?: number) => Promise<Res>;
4
4
  broadcast: (key: string, payload: any) => void;
5
5
  on: (key: string, handler: (payload: any) => void) => void;
6
6
  off: (key: string, handler: (payload: any) => void) => void;
7
7
  isConnected: boolean;
8
+ ping: () => Promise<boolean>;
8
9
  }
9
10
  interface BridgeContextValue {
10
11
  bridge: Bridge | null;
11
12
  status: ConnectionStatus;
12
13
  error: Error | null;
13
14
  reconnect: () => void;
15
+ isServiceWorkerAlive: boolean;
14
16
  }
15
17
  interface Props {
16
18
  children: React.ReactNode;
17
19
  retryAfter?: number;
18
20
  maxRetries?: number;
21
+ pingInterval?: number;
19
22
  onConnectionChange?: (status: ConnectionStatus) => void;
20
23
  onError?: (error: Error) => void;
21
24
  }
package/dist/index.js CHANGED
@@ -4,14 +4,16 @@ import { createContext, useState, useRef, useCallback, useEffect, useMemo, useCo
4
4
  const BridgeContext = createContext(null);
5
5
  const BridgeProvider = ({
6
6
  children,
7
- retryAfter = 1500,
8
- maxRetries = 5,
7
+ retryAfter = 1e3,
8
+ maxRetries = 10,
9
+ pingInterval = 5e3,
9
10
  onConnectionChange,
10
11
  onError
11
12
  }) => {
12
13
  const [bridge, setBridge] = useState(null);
13
14
  const [status, setStatus] = useState("connecting");
14
15
  const [error, setError] = useState(null);
16
+ const [isServiceWorkerAlive, setIsServiceWorkerAlive] = useState(false);
15
17
  const portRef = useRef(null);
16
18
  const pendingRef = useRef(
17
19
  /* @__PURE__ */ new Map()
@@ -22,6 +24,8 @@ const BridgeProvider = ({
22
24
  const retryCountRef = useRef(0);
23
25
  const isConnectingRef = useRef(false);
24
26
  const errorCheckIntervalRef = useRef(null);
27
+ const pingIntervalRef = useRef(null);
28
+ const consecutiveTimeoutsRef = useRef(0);
25
29
  const updateStatus = useCallback(
26
30
  (newStatus) => {
27
31
  setStatus(newStatus);
@@ -46,6 +50,10 @@ const BridgeProvider = ({
46
50
  clearInterval(errorCheckIntervalRef.current);
47
51
  errorCheckIntervalRef.current = null;
48
52
  }
53
+ if (pingIntervalRef.current) {
54
+ clearInterval(pingIntervalRef.current);
55
+ pingIntervalRef.current = null;
56
+ }
49
57
  if (portRef.current) {
50
58
  try {
51
59
  portRef.current.disconnect();
@@ -53,11 +61,13 @@ const BridgeProvider = ({
53
61
  }
54
62
  portRef.current = null;
55
63
  }
56
- pendingRef.current.forEach(({ reject }) => {
64
+ pendingRef.current.forEach(({ reject, timeout }) => {
65
+ clearTimeout(timeout);
57
66
  reject(new Error("Bridge disconnected"));
58
67
  });
59
68
  pendingRef.current.clear();
60
69
  eventListenersRef.current.clear();
70
+ setIsServiceWorkerAlive(false);
61
71
  setBridge(null);
62
72
  isConnectingRef.current = false;
63
73
  }, []);
@@ -110,11 +120,13 @@ const BridgeProvider = ({
110
120
  }, 100);
111
121
  port.onMessage.addListener((msg) => {
112
122
  if (msg.id && pendingRef.current.has(msg.id)) {
113
- const { resolve, reject } = pendingRef.current.get(msg.id);
123
+ const pending = pendingRef.current.get(msg.id);
124
+ clearTimeout(pending.timeout);
125
+ consecutiveTimeoutsRef.current = 0;
114
126
  if (msg.error) {
115
- reject(new Error(msg.error));
127
+ pending.reject(new Error(msg.error));
116
128
  } else {
117
- resolve(msg.data);
129
+ pending.resolve(msg.data);
118
130
  }
119
131
  pendingRef.current.delete(msg.id);
120
132
  } else if (msg.type === "broadcast" && msg.key) {
@@ -156,13 +168,29 @@ const BridgeProvider = ({
156
168
  return;
157
169
  }
158
170
  const id = `msg${uidRef.current++}`;
159
- pendingRef.current.set(id, { resolve, reject });
160
171
  const timeout = setTimeout(() => {
161
172
  if (pendingRef.current.has(id)) {
162
173
  pendingRef.current.delete(id);
163
- reject(new Error("Request timeout"));
174
+ consecutiveTimeoutsRef.current++;
175
+ if (consecutiveTimeoutsRef.current >= 2) {
176
+ console.warn(
177
+ "[Bridge] Multiple timeouts detected, service worker may be unresponsive. Reconnecting..."
178
+ );
179
+ setIsServiceWorkerAlive(false);
180
+ updateStatus("reconnecting");
181
+ cleanup();
182
+ retryCountRef.current = 0;
183
+ isConnectingRef.current = false;
184
+ setTimeout(connect, 500);
185
+ }
186
+ reject(
187
+ new Error(
188
+ `Request timed out after ${timeoutDuration} ms for key: ${key} with id: ${id}`
189
+ )
190
+ );
164
191
  }
165
192
  }, timeoutDuration);
193
+ pendingRef.current.set(id, { resolve, reject, timeout });
166
194
  try {
167
195
  portRef.current.postMessage({ id, key, payload });
168
196
  setTimeout(() => {
@@ -171,7 +199,8 @@ const BridgeProvider = ({
171
199
  console.warn("[Bridge] Async runtime error after postMessage:", errorMessage);
172
200
  chrome.runtime.lastError;
173
201
  if (pendingRef.current.has(id)) {
174
- clearTimeout(timeout);
202
+ const pending = pendingRef.current.get(id);
203
+ if (pending) clearTimeout(pending.timeout);
175
204
  pendingRef.current.delete(id);
176
205
  reject(new Error(errorMessage || "Async send failed"));
177
206
  }
@@ -181,7 +210,8 @@ const BridgeProvider = ({
181
210
  throw new Error(chrome.runtime.lastError.message || "Failed to send message");
182
211
  }
183
212
  } catch (e) {
184
- clearTimeout(timeout);
213
+ const pending = pendingRef.current.get(id);
214
+ if (pending) clearTimeout(pending.timeout);
185
215
  pendingRef.current.delete(id);
186
216
  if (chrome.runtime.lastError) {
187
217
  console.warn(
@@ -222,13 +252,35 @@ const BridgeProvider = ({
222
252
  }
223
253
  }
224
254
  },
255
+ ping: async () => {
256
+ try {
257
+ await bridgeInstance.send("__ping__", void 0, 2e3);
258
+ return true;
259
+ } catch {
260
+ return false;
261
+ }
262
+ },
225
263
  isConnected: true
226
264
  };
227
265
  setBridge(bridgeInstance);
228
266
  updateStatus("connected");
267
+ setIsServiceWorkerAlive(true);
229
268
  setError(null);
230
269
  retryCountRef.current = 0;
270
+ consecutiveTimeoutsRef.current = 0;
231
271
  isConnectingRef.current = false;
272
+ if (pingIntervalRef.current) {
273
+ clearInterval(pingIntervalRef.current);
274
+ }
275
+ pingIntervalRef.current = setInterval(async () => {
276
+ if (bridgeInstance && portRef.current) {
277
+ const alive = await bridgeInstance.ping();
278
+ setIsServiceWorkerAlive(alive);
279
+ if (!alive) {
280
+ console.warn("[Bridge] Service worker ping failed, may be unresponsive");
281
+ }
282
+ }
283
+ }, pingInterval);
232
284
  } catch (e) {
233
285
  isConnectingRef.current = false;
234
286
  const error2 = e instanceof Error ? e : new Error("Connection failed");
@@ -238,9 +290,10 @@ const BridgeProvider = ({
238
290
  reconnectTimeoutRef.current = setTimeout(connect, retryAfter);
239
291
  }
240
292
  }
241
- }, [retryAfter, maxRetries, handleError, updateStatus, cleanup]);
293
+ }, [retryAfter, maxRetries, pingInterval, handleError, updateStatus, cleanup]);
242
294
  const reconnect = useCallback(() => {
243
295
  retryCountRef.current = 0;
296
+ consecutiveTimeoutsRef.current = 0;
244
297
  updateStatus("connecting");
245
298
  connect();
246
299
  }, [connect, updateStatus]);
@@ -253,9 +306,10 @@ const BridgeProvider = ({
253
306
  bridge,
254
307
  status,
255
308
  error,
256
- reconnect
309
+ reconnect,
310
+ isServiceWorkerAlive
257
311
  }),
258
- [bridge, status, error, reconnect]
312
+ [bridge, status, error, reconnect, isServiceWorkerAlive]
259
313
  );
260
314
  return /* @__PURE__ */ jsx(BridgeContext.Provider, { value: contextValue, children });
261
315
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chromahq/react",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "React bindings for the Chroma Chrome extension framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",