react-realtime-hooks 1.0.2 → 1.0.3

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.js CHANGED
@@ -402,6 +402,7 @@ var useHeartbeat = (options) => {
402
402
  const startOnMount = options.startOnMount ?? true;
403
403
  const intervalRef = useRef(createManagedInterval());
404
404
  const timeoutRef = useRef(createManagedTimeout());
405
+ const generationRef = useRef(0);
405
406
  const [state, setState] = useState(
406
407
  () => createInitialState2(enabled && startOnMount)
407
408
  );
@@ -428,8 +429,7 @@ var useHeartbeat = (options) => {
428
429
  handleTimeout();
429
430
  }, options.timeoutMs);
430
431
  });
431
- const runBeat = useEffectEvent(() => {
432
- const performedAt = Date.now();
432
+ const handleBeatSuccess = useEffectEvent((performedAt) => {
433
433
  commitState((current) => ({
434
434
  ...current,
435
435
  hasTimedOut: false,
@@ -437,7 +437,35 @@ var useHeartbeat = (options) => {
437
437
  }));
438
438
  scheduleTimeout();
439
439
  options.onBeat?.();
440
- void options.beat?.();
440
+ });
441
+ const handleBeatError = useEffectEvent((error) => {
442
+ timeoutRef.current.cancel();
443
+ options.onError?.(error);
444
+ });
445
+ const runBeat = useEffectEvent(() => {
446
+ const generation = generationRef.current;
447
+ const completeBeat = (result) => {
448
+ if (generation !== generationRef.current || result === false) {
449
+ return;
450
+ }
451
+ handleBeatSuccess(Date.now());
452
+ };
453
+ const failBeat = (error) => {
454
+ if (generation !== generationRef.current) {
455
+ return;
456
+ }
457
+ handleBeatError(error);
458
+ };
459
+ try {
460
+ const result = options.beat?.();
461
+ if (result !== null && typeof result === "object" && "then" in result) {
462
+ void Promise.resolve(result).then(completeBeat, failBeat);
463
+ return;
464
+ }
465
+ completeBeat(result);
466
+ } catch (error) {
467
+ failBeat(error);
468
+ }
441
469
  });
442
470
  const start = () => {
443
471
  if (!enabled) {
@@ -455,6 +483,7 @@ var useHeartbeat = (options) => {
455
483
  }));
456
484
  };
457
485
  const stop = () => {
486
+ generationRef.current += 1;
458
487
  intervalRef.current.cancel();
459
488
  timeoutRef.current.cancel();
460
489
  commitState((current) => ({
@@ -500,6 +529,7 @@ var useHeartbeat = (options) => {
500
529
  return void 0;
501
530
  }, [enabled, options.intervalMs, startOnMount]);
502
531
  useEffect(() => () => {
532
+ generationRef.current += 1;
503
533
  intervalRef.current.cancel();
504
534
  timeoutRef.current.cancel();
505
535
  }, []);
@@ -605,6 +635,7 @@ var toProtocolsDependency = (protocols) => {
605
635
  return Array.isArray(protocols) ? protocols.join("|") : protocols;
606
636
  };
607
637
  var toHeartbeatConfig = (heartbeat) => heartbeat === void 0 || heartbeat === false ? null : heartbeat;
638
+ var isSocketActive = (socket) => socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;
608
639
  var useWebSocket = (options) => {
609
640
  const connect = options.connect ?? true;
610
641
  const supported = isWebSocketSupported();
@@ -616,6 +647,7 @@ var useWebSocket = (options) => {
616
647
  const manualOpenRef = useRef(false);
617
648
  const skipCloseReconnectRef = useRef(false);
618
649
  const suppressReconnectRef = useRef(false);
650
+ const pendingCloseActionRef = useRef(null);
619
651
  const terminalErrorRef = useRef(null);
620
652
  const [openNonce, setOpenNonce] = useState(0);
621
653
  const [state, setState] = useState(
@@ -634,6 +666,7 @@ var useWebSocket = (options) => {
634
666
  const heartbeatConfig = toHeartbeatConfig(
635
667
  options.heartbeat
636
668
  );
669
+ const defaultHeartbeatAction = options.reconnect === false ? "close" : "reconnect";
637
670
  const heartbeatHookOptions = heartbeatConfig === null ? {
638
671
  enabled: false,
639
672
  intervalMs: 1e3,
@@ -669,6 +702,27 @@ var useWebSocket = (options) => {
669
702
  if (heartbeatConfig !== null && heartbeatConfig.onTimeout !== void 0) {
670
703
  heartbeatHookOptions.onTimeout = heartbeatConfig.onTimeout;
671
704
  }
705
+ if (heartbeatConfig !== null) {
706
+ const onTimeout = heartbeatHookOptions.onTimeout;
707
+ heartbeatHookOptions.onTimeout = () => {
708
+ applyHeartbeatAction(
709
+ heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
710
+ new Event("heartbeat-timeout"),
711
+ "heartbeat-timeout"
712
+ );
713
+ onTimeout?.();
714
+ };
715
+ const onError = heartbeatConfig.onError;
716
+ heartbeatHookOptions.onError = (error) => {
717
+ const event = error instanceof Event ? error : new Event("heartbeat-error");
718
+ applyHeartbeatAction(
719
+ heartbeatConfig.errorAction ?? heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
720
+ event,
721
+ "error"
722
+ );
723
+ onError?.(error);
724
+ };
725
+ }
672
726
  const heartbeat = useHeartbeat(
673
727
  heartbeatHookOptions
674
728
  );
@@ -684,10 +738,46 @@ var useWebSocket = (options) => {
684
738
  }
685
739
  socketRef.current = null;
686
740
  socketKeyRef.current = null;
687
- if (socket2.readyState === WebSocket.OPEN || socket2.readyState === WebSocket.CONNECTING) {
741
+ if (isSocketActive(socket2)) {
688
742
  socket2.close(code, reason);
689
743
  }
690
744
  });
745
+ const applyHeartbeatAction = useEffectEvent(
746
+ (action, error, reconnectTrigger) => {
747
+ heartbeat.stop();
748
+ if (action === "none") {
749
+ commitState((current) => ({
750
+ ...current,
751
+ lastChangedAt: Date.now(),
752
+ lastError: error
753
+ }));
754
+ return;
755
+ }
756
+ const shouldReconnect = action === "reconnect" && reconnectEnabled && (options.shouldReconnect?.(error) ?? true);
757
+ manualOpenRef.current = false;
758
+ terminalErrorRef.current = shouldReconnect ? null : error;
759
+ const socket2 = socketRef.current;
760
+ if (socket2 === null || !isSocketActive(socket2)) {
761
+ commitState((current) => ({
762
+ ...current,
763
+ lastChangedAt: Date.now(),
764
+ lastError: error,
765
+ status: shouldReconnect ? "reconnecting" : "error"
766
+ }));
767
+ if (shouldReconnect) {
768
+ reconnect.schedule(reconnectTrigger);
769
+ }
770
+ return;
771
+ }
772
+ pendingCloseActionRef.current = {
773
+ error,
774
+ reconnectTrigger: shouldReconnect ? reconnectTrigger : null
775
+ };
776
+ skipCloseReconnectRef.current = true;
777
+ suppressReconnectRef.current = true;
778
+ closeSocket();
779
+ }
780
+ );
691
781
  const parseMessage = useEffectEvent((event) => {
692
782
  const parser = options.parseMessage ?? defaultParseMessage;
693
783
  return parser(event);
@@ -755,9 +845,26 @@ var useWebSocket = (options) => {
755
845
  socketKeyRef.current = null;
756
846
  heartbeat.stop();
757
847
  updateBufferedAmount();
848
+ const pendingCloseAction = pendingCloseActionRef.current;
849
+ pendingCloseActionRef.current = null;
758
850
  const terminalError = terminalErrorRef.current;
759
851
  const skipCloseReconnect = skipCloseReconnectRef.current;
760
852
  skipCloseReconnectRef.current = false;
853
+ if (pendingCloseAction !== null) {
854
+ suppressReconnectRef.current = false;
855
+ commitState((current) => ({
856
+ ...current,
857
+ lastChangedAt: Date.now(),
858
+ lastCloseEvent: event,
859
+ lastError: pendingCloseAction.error ?? current.lastError,
860
+ status: pendingCloseAction.reconnectTrigger === null ? "error" : "reconnecting"
861
+ }));
862
+ options.onClose?.(event);
863
+ if (pendingCloseAction.reconnectTrigger !== null) {
864
+ reconnect.schedule(pendingCloseAction.reconnectTrigger);
865
+ }
866
+ return;
867
+ }
761
868
  if (terminalError !== null) {
762
869
  suppressReconnectRef.current = false;
763
870
  commitState((current) => ({
@@ -916,7 +1023,7 @@ var useWebSocket = (options) => {
916
1023
  if (socket2 === null) {
917
1024
  return;
918
1025
  }
919
- if (socket2.readyState === WebSocket.OPEN || socket2.readyState === WebSocket.CONNECTING) {
1026
+ if (isSocketActive(socket2)) {
920
1027
  socket2.close();
921
1028
  }
922
1029
  }, []);