@chromahq/react 1.0.28 → 1.0.29

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
@@ -124,8 +124,9 @@ declare const useConnectionStatus: (store?: StoreReadyMethods) => ConnectionStat
124
124
  * </ServiceWorkerHealthProvider>
125
125
  * </BridgeProvider>
126
126
  *
127
- * // In any component, use the hook:
128
- * const { isHealthy, isRecovering } = useServiceWorkerHealth();
127
+ * // In any component, use the hook (optionally pass your store instance):
128
+ * import { appStore } from './stores/app';
129
+ * const { isHealthy, isRecovering } = useServiceWorkerHealth({ store: appStore });
129
130
  *
130
131
  * if (!isHealthy) {
131
132
  * return <Spinner message="Reconnecting..." />;
@@ -148,6 +149,10 @@ interface ServiceWorkerHealthContextValue {
148
149
  /** Force a reconnection attempt */
149
150
  forceReconnect: () => void;
150
151
  }
152
+ interface ServiceWorkerHealthResult extends ServiceWorkerHealthContextValue {
153
+ /** Optional readiness state for attached store */
154
+ storeReady: boolean;
155
+ }
151
156
  interface ServiceWorkerHealthProviderProps {
152
157
  children: ReactNode;
153
158
  /**
@@ -156,6 +161,9 @@ interface ServiceWorkerHealthProviderProps {
156
161
  */
157
162
  onHealthChange?: (status: HealthStatus, isHealthy: boolean) => void;
158
163
  }
164
+ interface ServiceWorkerHealthOptions {
165
+ store?: StoreReadyMethods;
166
+ }
159
167
  type HealthSubscriber = (status: HealthStatus, isHealthy: boolean) => void;
160
168
  /**
161
169
  * Subscribe to health status changes from outside React.
@@ -192,7 +200,7 @@ declare const ServiceWorkerHealthProvider: FC<ServiceWorkerHealthProviderProps>;
192
200
  * @example
193
201
  * ```tsx
194
202
  * function App() {
195
- * const { isHealthy, isRecovering } = useServiceWorkerHealth();
203
+ * const { isHealthy, isRecovering } = useServiceWorkerHealth({ store: appStore });
196
204
  *
197
205
  * if (!isHealthy) {
198
206
  * return (
@@ -207,7 +215,7 @@ declare const ServiceWorkerHealthProvider: FC<ServiceWorkerHealthProviderProps>;
207
215
  * }
208
216
  * ```
209
217
  */
210
- declare function useServiceWorkerHealth(): ServiceWorkerHealthContextValue;
218
+ declare function useServiceWorkerHealth(options?: ServiceWorkerHealthOptions): ServiceWorkerHealthResult;
211
219
  /**
212
220
  * Lightweight hook that directly consumes BridgeContext without needing
213
221
  * ServiceWorkerHealthProvider. Use this for simple cases where you don't
@@ -222,11 +230,12 @@ declare function useServiceWorkerHealth(): ServiceWorkerHealthContextValue;
222
230
  * }
223
231
  * ```
224
232
  */
225
- declare function useServiceWorkerHealthSimple(): {
233
+ declare function useServiceWorkerHealthSimple(options?: ServiceWorkerHealthOptions): {
226
234
  isHealthy: boolean;
227
235
  isRecovering: boolean;
228
236
  isLoading: boolean;
229
237
  reconnect: () => void;
238
+ storeReady: boolean;
230
239
  };
231
240
 
232
241
  export { BridgeProvider, ServiceWorkerHealthProvider, getHealthStatus, subscribeToHealth, useBridge, useBridgeQuery, useConnectionStatus, useServiceWorkerHealth, useServiceWorkerHealthSimple };
package/dist/index.js CHANGED
@@ -5,15 +5,17 @@ const BRIDGE_ENABLE_LOGS = true;
5
5
  const CONFIG = {
6
6
  RETRY_AFTER: 1e3,
7
7
  MAX_RETRIES: 10,
8
- PING_INTERVAL: 2e3,
9
- // Check every 2s for faster SW death detection
8
+ PING_INTERVAL: 3e3,
9
+ // Check every 3s (balance between responsiveness and false positives)
10
10
  MAX_RETRY_COOLDOWN: 3e4,
11
11
  DEFAULT_TIMEOUT: 1e4,
12
12
  MAX_RETRY_DELAY: 3e4,
13
- PING_TIMEOUT: 2e3,
13
+ PING_TIMEOUT: 5e3,
14
+ // Give SW 5s to respond (handles busy periods)
14
15
  ERROR_CHECK_INTERVAL: 100,
15
16
  MAX_ERROR_CHECKS: 10,
16
- CONSECUTIVE_FAILURE_THRESHOLD: 2,
17
+ CONSECUTIVE_FAILURE_THRESHOLD: 3,
18
+ // Require 3 consecutive failures (9s total) before reconnecting
17
19
  RECONNECT_DELAY: 100,
18
20
  PORT_NAME: "chroma-bridge",
19
21
  // Service worker restart retry settings (indefinite retries)
@@ -275,7 +277,8 @@ const BridgeProvider = ({
275
277
  [onError, updateStatus]
276
278
  );
277
279
  const cleanup = useCallback((emitDisconnect = true) => {
278
- if (emitDisconnect) {
280
+ const wasConnected = isConnectedRef.current || portRef.current !== null;
281
+ if (emitDisconnect && wasConnected) {
279
282
  eventListenersRef.current.get("bridge:disconnected")?.forEach((handler) => {
280
283
  try {
281
284
  handler(void 0);
@@ -411,7 +414,7 @@ const BridgeProvider = ({
411
414
  return;
412
415
  }
413
416
  isConnectingRef.current = true;
414
- cleanup();
417
+ cleanup(false);
415
418
  if (!chrome?.runtime?.connect) {
416
419
  handleError(new Error("Chrome runtime not available"));
417
420
  isConnectingRef.current = false;
@@ -438,7 +441,7 @@ const BridgeProvider = ({
438
441
  if (BRIDGE_ENABLE_LOGS) {
439
442
  console.warn("[Bridge] Service worker not ready (may be restarting)...");
440
443
  }
441
- cleanup();
444
+ cleanup(false);
442
445
  isConnectingRef.current = false;
443
446
  scheduleSwRestartReconnect(connect);
444
447
  }
@@ -481,7 +484,7 @@ const BridgeProvider = ({
481
484
  clearTimeoutSafe(triggerReconnectTimeoutRef);
482
485
  triggerReconnectTimeoutRef.current = setTimeout(() => {
483
486
  if (!isMountedRef.current) return;
484
- cleanup();
487
+ cleanup(false);
485
488
  connect();
486
489
  }, CONFIG.RECONNECT_DELAY);
487
490
  };
@@ -762,6 +765,17 @@ const useConnectionStatus = (store) => {
762
765
  };
763
766
  };
764
767
 
768
+ const noopSubscribe = () => () => {
769
+ };
770
+ const alwaysTrue = () => true;
771
+ const ssrFallback = () => false;
772
+ function useStoreReady(store) {
773
+ return useSyncExternalStore(
774
+ store?.onReady ?? noopSubscribe,
775
+ store?.isReady ?? alwaysTrue,
776
+ ssrFallback
777
+ );
778
+ }
765
779
  const globalSubscribers = /* @__PURE__ */ new Set();
766
780
  function subscribeToHealth(callback) {
767
781
  globalSubscribers.add(callback);
@@ -832,24 +846,33 @@ const ServiceWorkerHealthProvider = ({
832
846
  );
833
847
  return /* @__PURE__ */ jsx(ServiceWorkerHealthContext.Provider, { value, children });
834
848
  };
835
- function useServiceWorkerHealth() {
849
+ function useServiceWorkerHealth(options) {
836
850
  const context = useContext(ServiceWorkerHealthContext);
837
851
  if (!context) {
838
852
  throw new Error(
839
853
  "useServiceWorkerHealth must be used within a ServiceWorkerHealthProvider. Wrap your app with <ServiceWorkerHealthProvider> inside <BridgeProvider>."
840
854
  );
841
855
  }
842
- return context;
856
+ const storeReady = useStoreReady(options?.store);
857
+ const combinedHealthy = context.isHealthy && (options?.store ? storeReady : true);
858
+ const combinedLoading = context.isLoading || (options?.store ? !storeReady : false);
859
+ return {
860
+ ...context,
861
+ isHealthy: combinedHealthy,
862
+ isLoading: combinedLoading,
863
+ storeReady
864
+ };
843
865
  }
844
- function useServiceWorkerHealthSimple() {
866
+ function useServiceWorkerHealthSimple(options) {
845
867
  const bridgeContext = useContext(BridgeContext);
846
- const isHealthy = bridgeContext?.status === "connected" && bridgeContext?.isServiceWorkerAlive === true;
868
+ const storeReady = useStoreReady(options?.store);
869
+ const isHealthy = bridgeContext?.status === "connected" && bridgeContext?.isServiceWorkerAlive === true && (options?.store ? storeReady : true);
847
870
  const isRecovering = bridgeContext?.status === "reconnecting" || bridgeContext?.status === "connecting";
848
- const isLoading = !isHealthy && (isRecovering || !bridgeContext);
871
+ const isLoading = !isHealthy && (isRecovering || !bridgeContext) || (options?.store ? !storeReady : false);
849
872
  const reconnect = useCallback(() => {
850
873
  bridgeContext?.reconnect();
851
874
  }, [bridgeContext]);
852
- return { isHealthy, isRecovering, isLoading, reconnect };
875
+ return { isHealthy, isRecovering, isLoading, reconnect, storeReady };
853
876
  }
854
877
 
855
878
  export { BridgeProvider, ServiceWorkerHealthProvider, getHealthStatus, subscribeToHealth, useBridge, useBridgeMutation, useBridgeQuery, useConnectionStatus, useServiceWorkerHealth, useServiceWorkerHealthSimple };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chromahq/react",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "React bindings for the Chroma Chrome extension framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",