@chromahq/react 1.0.10 → 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,17 +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);
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
+ }
163
186
  reject(
164
187
  new Error(
165
- `Request timed out after ${timeoutDuration} ms for key: ${key} with id: ${id} and payload: ${JSON.stringify(payload)}`
188
+ `Request timed out after ${timeoutDuration} ms for key: ${key} with id: ${id}`
166
189
  )
167
190
  );
168
191
  }
169
192
  }, timeoutDuration);
193
+ pendingRef.current.set(id, { resolve, reject, timeout });
170
194
  try {
171
195
  portRef.current.postMessage({ id, key, payload });
172
196
  setTimeout(() => {
@@ -175,7 +199,8 @@ const BridgeProvider = ({
175
199
  console.warn("[Bridge] Async runtime error after postMessage:", errorMessage);
176
200
  chrome.runtime.lastError;
177
201
  if (pendingRef.current.has(id)) {
178
- clearTimeout(timeout);
202
+ const pending = pendingRef.current.get(id);
203
+ if (pending) clearTimeout(pending.timeout);
179
204
  pendingRef.current.delete(id);
180
205
  reject(new Error(errorMessage || "Async send failed"));
181
206
  }
@@ -185,7 +210,8 @@ const BridgeProvider = ({
185
210
  throw new Error(chrome.runtime.lastError.message || "Failed to send message");
186
211
  }
187
212
  } catch (e) {
188
- clearTimeout(timeout);
213
+ const pending = pendingRef.current.get(id);
214
+ if (pending) clearTimeout(pending.timeout);
189
215
  pendingRef.current.delete(id);
190
216
  if (chrome.runtime.lastError) {
191
217
  console.warn(
@@ -226,13 +252,35 @@ const BridgeProvider = ({
226
252
  }
227
253
  }
228
254
  },
255
+ ping: async () => {
256
+ try {
257
+ await bridgeInstance.send("__ping__", void 0, 2e3);
258
+ return true;
259
+ } catch {
260
+ return false;
261
+ }
262
+ },
229
263
  isConnected: true
230
264
  };
231
265
  setBridge(bridgeInstance);
232
266
  updateStatus("connected");
267
+ setIsServiceWorkerAlive(true);
233
268
  setError(null);
234
269
  retryCountRef.current = 0;
270
+ consecutiveTimeoutsRef.current = 0;
235
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);
236
284
  } catch (e) {
237
285
  isConnectingRef.current = false;
238
286
  const error2 = e instanceof Error ? e : new Error("Connection failed");
@@ -242,9 +290,10 @@ const BridgeProvider = ({
242
290
  reconnectTimeoutRef.current = setTimeout(connect, retryAfter);
243
291
  }
244
292
  }
245
- }, [retryAfter, maxRetries, handleError, updateStatus, cleanup]);
293
+ }, [retryAfter, maxRetries, pingInterval, handleError, updateStatus, cleanup]);
246
294
  const reconnect = useCallback(() => {
247
295
  retryCountRef.current = 0;
296
+ consecutiveTimeoutsRef.current = 0;
248
297
  updateStatus("connecting");
249
298
  connect();
250
299
  }, [connect, updateStatus]);
@@ -257,9 +306,10 @@ const BridgeProvider = ({
257
306
  bridge,
258
307
  status,
259
308
  error,
260
- reconnect
309
+ reconnect,
310
+ isServiceWorkerAlive
261
311
  }),
262
- [bridge, status, error, reconnect]
312
+ [bridge, status, error, reconnect, isServiceWorkerAlive]
263
313
  );
264
314
  return /* @__PURE__ */ jsx(BridgeContext.Provider, { value: contextValue, children });
265
315
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chromahq/react",
3
- "version": "1.0.10",
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",