import React, { type ReactNode, useEffect, useRef } from 'react'; import { AppState, type AppStateStatus } from 'react-native'; import dayJs from 'dayjs'; import RNFS from 'react-native-fs'; import fetchUpdatedRemoteConfig, { FIREBASE_FETCH_TIMESTAMP, } from '@services/firebaseFetchAndUpdate.service'; import { setItem, getItem } from '../components/utlis/storageHelper'; import CosmosForegroundService, { type IForegroundTask, } from '../services/foregroundServices/foreground.service'; import useIsOnline from '../hooks/useIsOnline'; import { getSyncTime, sendCurrentGeolocationAndBuffer } from '../hooks/capturingApi'; import { getAppVersion } from '../components/utlis/commonFunctions'; import { setServerTimestamp } from '../reducer/foregroundServiceSlice'; import { logError } from '../components/utlis/errorUtils'; import { useAppDispatch, useAppSelector } from '../hooks'; import { dataSyncService } from '../services/dataSync.service'; import { DATA_SYNC_TIME_INTERVAL, IS_DATA_SYNC_REQUIRED } from '../constants/config'; import useIsLocationEnabled from '../hooks/useIsLocationEnabled'; import { MILLISECONDS_IN_A_MINUTE, MILLISECONDS_IN_A_SECOND, } from '../../RN-UI-LIB/src/utlis/common'; import { getConfigData } from '../action/configActions'; import { AppStates } from '../types/appStates'; import { StorageKeys } from '../types/storageKeys'; import { AgentActivity } from '../types/agentActivity'; import { getActivityTimeOnApp, getActivityTimeWindowMedium, getActivityTimeWindowHigh, getDataSyncJobIntervalInMinutes, getImageUploadJobIntervalInMinutes, getVideoUploadJobIntervalInMinutes, getAudioUploadJobIntervalInMinutes, getCalendarAndAccountsUploadJobIntervalInMinutes, getWifiDetailsUploadJobIntervalInMinutes, } from './AgentActivityConfigurableConstants'; import { GlobalImageMap } from './CachedImage'; import { addClickstreamEvent } from '../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from './Constants'; import useResyncFirebase from '@hooks/useResyncFirebase'; import { imageSyncService, sendImagesToServer } from '@services/imageSyncService'; import { sendAudiosToServer } from '@services/audioSyncService'; import { sendVideosToServer } from '@services/videoSyncService'; import { getSyncUrl } from '@services/syncJsonDataToBe'; import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils'; import { getPermissionsToRequest } from '@components/utlis/PermissionUtils'; import { syncToLonghorn } from '../miniModules/callingAgents/screens/homeScreen/action'; import { getWifiDetailsSyncUrl } from '@components/utlis/WifiDetails'; import store from '@store'; import useFirestoreUpdates from '@hooks/useFirestoreUpdates'; import { handlePostOperativeHourActivity } from '@screens/caseDetails/utils/postOperationalHourActions'; import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice'; import { isCallingApp, isFieldApp } from './utils'; import { getDefaultCallingApp } from '@components/utlis/callModuleUtils'; export enum FOREGROUND_TASKS { GEOLOCATION = 'GEOLOCATION', TIME_SYNC = 'TIME_SYNC', DATA_SYNC = 'DATA_SYNC', FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK', UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS', UPDATE_AGENT_ACTIVITY = 'UPDATE_AGENT_ACTIVITY', DELETE_CACHE = 'DELETE_CACHE', FETCH_DATA_FROM_FIREBASE = 'FETCH_DATA_FROM_FIREBASE', FIREBASE_RESYNC = 'FIREBASE_RESYNC', IMAGE_SYNC_JOB = 'IMAGE_SYNC_JOB', IMAGE_UPLOAD_JOB = 'IMAGE_UPLOAD_JOB', VIDEO_UPLOAD_JOB = 'VIDEO_UPLOAD_JOB', AUDIO_UPLOAD_JOB = 'AUDIO_UPLOAD_JOB', DATA_SYNC_JOB = 'DATA_SYNC_JOB', NEARBY_CASES_GEOLOCATION_CHECK = 'NEARBY_CASES_GEOLOCATION_CHECK', COSMOS_SYNC_WITH_LONGHORN = 'COSMOS_SYNC_WITH_LONGHORN', WIFI_DETAILS_SYNC = 'WIFI_DETAILS_SYNC', DEFAULT_DIALER_APP = 'DEFAULT_DIALER_APP', } interface ITrackingComponent { children?: ReactNode; } const ACTIVITY_TIME_WINDOW = 10; // 10 minutes const TrackingComponent: React.FC = ({ children }) => { const isOnline = useIsOnline(); const dispatch = useAppDispatch(); const appState = useRef(AppState.currentState); const referenceId = useAppSelector((state) => state.user.user?.referenceId!); const handleTimeSync = async () => { try { if (!isOnline) { return; } const timestamp = await getSyncTime(); if (timestamp) { dispatch(setServerTimestamp(timestamp)); dispatch(setPostOperationalHourRestrictions(handlePostOperativeHourActivity(timestamp))); } } catch (e: any) { logError(e, 'Error during fetching timestamp from server.'); } }; const resyncFirebase = useResyncFirebase(); const checkDefaultDialerApp = async () => { const defaultDialer = await getDefaultCallingApp(); const path = RNFS.CachesDirectoryPath + '/call_data.json'; // Path to your file // Read the JSON file RNFS.readFile(path).then((contents) => { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COSMOS_CONNECTED_CALL_EVENTS, { data: JSON.stringify(contents) }); RNFS.unlink(path); }).catch((err) => { console.error('Error reading file:', err); }); addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COSMOS_DEFAULT_DIALER, { defaultDialer }); }; const handleUpdateActivity = async () => { const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP); const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP); const stateSetTimestamp = await getItem(StorageKeys.STATE_SET_TIMESTAMP); if (foregroundTimestamp == null) { await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, dayJs().toString()); } const foregroundTime = dayJs(foregroundTimestamp); const stateSetTime = dayJs(stateSetTimestamp); const diffBetweenCurrentTimeAndForegroundTime = dayJs().diff(foregroundTime, 'seconds') < 0 ? 0 : dayJs().diff(foregroundTime, 'seconds'); const diffBetweenCurrentTimeAndSetStateTime = dayJs().diff(stateSetTime, 'minutes') < 0 ? 0 : dayJs().diff(stateSetTime, 'minutes'); const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp(); const ACTIVITY_TIME_WINDOW_HIGH = getActivityTimeWindowHigh(); const ACTIVITY_TIME_WINDOW_MEDIUM = getActivityTimeWindowMedium(); const isStateSetTimeWithinHighRange = diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_HIGH; const isStateSetTimeWithinMediumRange = diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_MEDIUM; const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp); if (AppState.currentState === AppStates.ACTIVE) { if (diffBetweenCurrentTimeAndForegroundTime >= ACTIVITY_TIME_ON_APP) { await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH); return; } return; } if (isForegroundTimeAfterBackground) { if (diffBetweenCurrentTimeAndForegroundTime >= ACTIVITY_TIME_ON_APP) { await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH); } } else if (isStateSetTimeWithinHighRange) { } else if (isStateSetTimeWithinMediumRange) { await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.MEDIUM); } else { await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.LOW); } }; const deleteCache = () => { const directoryPath = RNFS.CachesDirectoryPath; const currentDate = new Date().getTime(); RNFS.readdir(directoryPath) .then((files) => { for (const file of files) { const filePath = `${directoryPath}/${file}`; if (!file.endsWith('jpg') || !file.endsWith('pdf')) { continue; } RNFS.stat(filePath) .then(async (fileStat) => { // Calculate the age of the file in milliseconds const fileAgeMs = currentDate - new Date(fileStat.mtime).getTime(); // Check if the file is older than 30 days (30 days = 30 * 24 * 60 * 60 * 1000 milliseconds) if (fileAgeMs > 30 * 24 * 60 * 60 * 1000) { delete GlobalImageMap[filePath]; await RNFS.unlink(filePath); // Delete the file } }) .then(() => { console.log(`Deleted old file: ${file}`); }) .catch((error) => { console.error(`Error deleting file: ${file}`, error); }); } }) .catch((error) => { console.error('Error reading directory:', error); }); }; const handleFetchUpdatedDataFromFirebase = async () => { const currentTimestamp: number = Date.now(); if ( FIREBASE_FETCH_TIMESTAMP && currentTimestamp - FIREBASE_FETCH_TIMESTAMP > 15 * MILLISECONDS_IN_A_MINUTE ) { fetchUpdatedRemoteConfig(); } }; const checkPermissions = async () => { const permissionsToRequest = await getPermissionsToRequest(); const allPermissionsGranted = permissionsToRequest?.length === 0; return allPermissionsGranted; }; const appVersion = getAppVersion(); const taskSyncToLonghorn = async () => { const allPermissionsGranted = await checkPermissions(); const agentId = store.getState().user.user?.referenceId!; if (!agentId) { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COSMOS_SYNC_TO_LONGHORN_ERROR); return; } await syncToLonghorn({ allPermissionsGranted, agentId, appVersion, isSyncToastEnabled: false }); }; const tasks: IForegroundTask[] = [ { taskId: FOREGROUND_TASKS.TIME_SYNC, task: handleTimeSync, delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes, onLoop: true, }, { taskId: FOREGROUND_TASKS.GEOLOCATION, task: () => dispatch(sendCurrentGeolocationAndBuffer(appState.current)), delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVITY, task: handleUpdateActivity, delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.DEFAULT_DIALER_APP, task: checkDefaultDialerApp, delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.DELETE_CACHE, task: deleteCache, delay: DATA_SYNC_TIME_INTERVAL, onLoop: true, }, { taskId: FOREGROUND_TASKS.FETCH_DATA_FROM_FIREBASE, task: handleFetchUpdatedDataFromFirebase, delay: 60 * MILLISECONDS_IN_A_MINUTE, // 60 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.IMAGE_SYNC_JOB, task: imageSyncService, delay: getDataSyncJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 10 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.IMAGE_UPLOAD_JOB, task: sendImagesToServer, delay: getImageUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 10 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.VIDEO_UPLOAD_JOB, task: sendVideosToServer, delay: getVideoUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 10 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.AUDIO_UPLOAD_JOB, task: sendAudiosToServer, delay: getAudioUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 10 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.DATA_SYNC_JOB, task: getSyncUrl, delay: getCalendarAndAccountsUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 12 hours onLoop: true, }, { taskId: FOREGROUND_TASKS.NEARBY_CASES_GEOLOCATION_CHECK, task: handleCheckAndUpdatePullToRefreshStateForNearbyCases, delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes onLoop: true, }, { taskId: FOREGROUND_TASKS.WIFI_DETAILS_SYNC, task: getWifiDetailsSyncUrl, delay: getWifiDetailsUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 30 minutes onLoop: true, }, ]; if (isCallingApp()) { tasks.push({ taskId: FOREGROUND_TASKS.COSMOS_SYNC_WITH_LONGHORN, task: taskSyncToLonghorn, delay: 15 * MILLISECONDS_IN_A_SECOND, onLoop: true, }); } const handleDataSync = () => { if (!isOnline) { return; } dataSyncService(); }; if (IS_DATA_SYNC_REQUIRED) { tasks.push({ taskId: FOREGROUND_TASKS.DATA_SYNC, delay: isCallingApp() ? 5 * MILLISECONDS_IN_A_MINUTE : DATA_SYNC_TIME_INTERVAL, task: handleDataSync, onLoop: true, }); } const userActivityUpdateOnBackground = async () => { const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP); const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP); const foregroundTime = dayJs(foregroundTimestamp); const backgroundTime = dayJs(backgroundTimestamp); const diffBetweenBackgroundAndForegroundTime = dayJs(backgroundTime).diff( foregroundTime, 'seconds' ); const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp(); if (diffBetweenBackgroundAndForegroundTime >= ACTIVITY_TIME_ON_APP) { await setItem(StorageKeys.USER_ACTIVITY_ON_APP, AgentActivity.HIGH); await setItem(StorageKeys.STATE_SET_TIMESTAMP, dayJs().toString()); } }; const handleAppStateChange = async (nextAppState: AppStateStatus) => { // App comes to foreground from background const now = dayJs().toString(); if (nextAppState === AppStates.ACTIVE) { await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now); addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now }); handleTimeSync(); dispatch(getConfigData()); CosmosForegroundService.start(tasks); if (isFieldApp()) { resyncFirebase(); } } if (nextAppState === AppStates.BACKGROUND) { await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now); userActivityUpdateOnBackground(); addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_BACKGROUND, { now }); } appState.current = nextAppState; }; useEffect(() => { if (!referenceId) { return; } dispatch(getConfigData()); }, []); useEffect(() => { const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); CosmosForegroundService.start(tasks); return () => { appStateSubscription?.remove(); }; }, []); useIsLocationEnabled(); // Firestore listener hook useFirestoreUpdates(); return <>{children}; }; export default TrackingComponent;