All files / apps/host/src/hooks/notifications useRefreshNotifications.ts

96.42% Statements 54/56
89.65% Branches 26/29
100% Functions 11/11
100% Lines 50/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                    3x             8x 8x 8x 8x 8x 8x 8x   8x 8x   8x 6x     8x 6x 6x     8x 6x     6x 5x     5x     4x   4x 2x 2x   2x       4x   4x 4x       8x 6x 4x 4x 4x 4x   4x 3x       6x       8x 8x 2x 2x           8x 6x 1x   1x 1x     1x       6x 6x   6x      
import { useCallback, useEffect, useRef } from 'react';
 
import { useIsFocused } from '@react-navigation/native';
 
import { extractNotificationData } from '@/services/notification/helpers';
import { notificationService } from '@/services/notification/notificationService';
 
/**
 * Handles data refresh triggered by notifications (foreground, tap, or app resume).
 */
export interface UseRefreshNotificationsParams {
  onRefresh: () => void;
  filterTicketId?: string;
  isRefetching?: boolean;
  refreshTimeStamp?: number;
  notificationId?: string;
}
 
export const useRefreshNotifications = ({
  onRefresh,
  filterTicketId,
  isRefetching,
  refreshTimeStamp,
  notificationId,
}: UseRefreshNotificationsParams) => {
  const onRefreshRef = useRef(onRefresh);
  const isRefetchingRef = useRef(isRefetching);
  const lastRefreshTimeRef = useRef(0);
  const lastProcessedIdRef = useRef<string | undefined>(undefined);
  const lastProcessedStampRef = useRef<number | undefined>(undefined);
  const lastProcessedVersionRef = useRef(notificationService.getRefreshVersion());
  const hasMountedRef = useRef(false);
 
  const isFocused = useIsFocused();
  const isFocusedRef = useRef(isFocused);
 
  useEffect(() => {
    isFocusedRef.current = isFocused;
  }, [isFocused]);
 
  useEffect(() => {
    onRefreshRef.current = onRefresh;
    isRefetchingRef.current = isRefetching;
  }, [onRefresh, isRefetching]);
 
  const triggerRefresh = useCallback((id?: string, isDirectArrival = false) => {
    const now = Date.now();
 
    // 1. DEDUPLICATION
    if (!isDirectArrival && id && notificationService.isNotificationProcessed(id)) return;
    Iif (id && id === lastProcessedIdRef.current) return;
 
    // 2. DEBOUNCE (500ms safety window)
    if (now - lastRefreshTimeRef.current < 500) return;
 
    // 3. REFETCHING GUARD
    Iif (isRefetchingRef.current) return;
 
    if (id) {
      notificationService.markNotificationAsProcessed(id);
      lastProcessedIdRef.current = id;
    } else {
      notificationService.markAllDisplayedAsProcessed();
    }
 
    // Update local version to current global state
    lastProcessedVersionRef.current = notificationService.getRefreshVersion();
 
    lastRefreshTimeRef.current = now;
    onRefreshRef.current();
  }, []);
 
  // Case 1: Foreground notification arrivals
  useEffect(() => {
    const unsubscribe = notificationService.addMessageListener(data => {
      const notificationData = extractNotificationData(data);
      const msgId = (data as any)?._messageId;
      const notificationTicketId = notificationData?.ticketId;
      const isMatchingTicket = !filterTicketId || notificationTicketId === filterTicketId;
 
      if (isMatchingTicket) {
        triggerRefresh(msgId, true);
      }
    });
 
    return () => unsubscribe();
  }, [filterTicketId, triggerRefresh]);
 
  // Case 2: Navigation-driven refreshes (via tap/params)
  useEffect(() => {
    if (refreshTimeStamp && refreshTimeStamp !== lastProcessedStampRef.current) {
      lastProcessedStampRef.current = refreshTimeStamp;
      triggerRefresh(notificationId);
    }
  }, [refreshTimeStamp, notificationId, triggerRefresh]);
 
  // Case 3: Reactive Version Monitor
  // Subscribes to global version changes to catch background-sync arrivals and foreground updates.
  useEffect(() => {
    const unsubscribe = notificationService.addVersionListener(currentGlobalVersion => {
      const isNew = currentGlobalVersion > lastProcessedVersionRef.current;
 
      Eif (isNew && isFocusedRef.current && hasMountedRef.current) {
        triggerRefresh();
      }
 
      lastProcessedVersionRef.current = currentGlobalVersion;
    });
 
    // Ensure local state is in sync on mount
    lastProcessedVersionRef.current = notificationService.getRefreshVersion();
    hasMountedRef.current = true;
 
    return () => unsubscribe();
  }, [triggerRefresh]);
};