Files
address-verification-app/src/common/TrackingComponent.tsx
2024-07-09 19:25:23 +05:30

442 lines
15 KiB
TypeScript

import { type ReactNode, useEffect, useRef } from 'react';
import { type NativeEventSubscription, 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 {
isTimeDifferenceWithinRange,
setAsyncStorageItem,
} from '../components/utlis/commonFunctions';
import { setIsTimeSynced } 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,
MS_CLARITY_PROJECT_ID,
} from '../constants/config';
import useIsLocationEnabled from '../hooks/useIsLocationEnabled';
import {
type ISyncCaseIdPayload,
type ISyncedCases,
SyncStatus,
fetchCasesToSync,
getCasesSyncStatus,
sendSyncCaseIds,
} from '../action/firebaseFallbackActions';
import { getSyncCaseIds } from '../components/utlis/firebaseFallbackUtils';
import { syncCasesByFallback } from '../reducer/allCasesSlice';
import { MILLISECONDS_IN_A_MINUTE, noop } from '../../RN-UI-LIB/src/utlis/common';
import { setCaseSyncLock, setLockData } from '../reducer/userSlice';
import { getConfigData } from '../action/configActions';
import { AppStates } from '../types/appStates';
import { StorageKeys } from '../types/storageKeys';
import { AgentActivity } from '../types/agentActivity';
import {
getActivityTimeOnApp,
getActivityTimeWindowMedium,
getActivityTimeWindowHigh,
getEnableFirestoreResync,
getDataSyncJobIntervalInMinutes,
getImageUploadJobIntervalInMinutes,
getVideoUploadJobIntervalInMinutes,
getAudioUploadJobIntervalInMinutes,
getCalendarAndAccountsUploadJobIntervalInMinutes,
} from './AgentActivityConfigurableConstants';
import { GlobalImageMap } from './CachedImage';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './Constants';
import useResyncFirebase from '@hooks/useResyncFirebase';
import {
imageSyncService,
prepareImagesForUpload,
sendImagesToServer,
} from '@services/imageSyncService';
import { getImages } from '@components/utlis/ImageUtlis';
import getLitmusExperimentResult, {
LitmusExperimentName,
LitmusExperimentNameMap,
} from '@services/litmusExperiments.service';
import { GLOBAL } from '@constants/Global';
import { sendAudiosToServer } from '@services/audioSyncService';
import { sendVideosToServer } from '@services/videoSyncService';
import { getSyncUrl } from '@services/syncJsonDataToBe';
import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils';
import { initialize } from 'react-native-clarity';
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',
}
interface ITrackingComponent {
children?: ReactNode;
}
let LAST_SYNC_STATUS = 'SKIP';
const ACTIVITY_TIME_WINDOW = 10; // 10 minutes
const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const isOnline = useIsOnline();
const dispatch = useAppDispatch();
const appState = useRef(AppState.currentState);
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
const caseSyncLock = useAppSelector((state) => state?.user?.caseSyncLock);
const referenceId = useAppSelector((state) => state.user.user?.referenceId!);
const pendingList = useAppSelector((state) => state.allCases.pendingList);
const pinnedList = useAppSelector((state) => state.allCases.pinnedList);
const handleTimeSync = async () => {
try {
if (!isOnline) {
return;
}
const timestamp = await getSyncTime();
if (timestamp) {
const isTimeDifferenceLess = isTimeDifferenceWithinRange(timestamp, 5);
dispatch(setIsTimeSynced(isTimeDifferenceLess));
}
} catch (e: any) {
logError(e, 'Error during fetching timestamp from server.');
}
};
const resyncFirebase = useResyncFirebase();
const handleGetCaseSyncStatus = async () => {
if (caseSyncLock || getEnableFirestoreResync()) {
return;
}
dispatch(setCaseSyncLock(true));
try {
if (!isOnline) {
return;
}
const { syncStatus, visitPlanStatus } = (await getCasesSyncStatus(referenceId)) ?? {};
if (syncStatus) {
// Keep track of the last status received
LAST_SYNC_STATUS = syncStatus;
}
if (syncStatus === SyncStatus.SEND_CASES) {
const cases = getSyncCaseIds([...pendingList, ...pinnedList]);
const payload: ISyncCaseIdPayload = {
agentId: referenceId,
cases,
};
sendSyncCaseIds(payload);
} else if (syncStatus === SyncStatus.FETCH_CASES) {
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
if (updatedDetails?.cases?.length) {
dispatch(syncCasesByFallback(updatedDetails));
}
}
if (visitPlanStatus) {
dispatch(
setLockData({
visitPlanStatus,
})
);
}
dispatch(setCaseSyncLock(false));
} catch (e) {
logError(e as Error, 'Error during fetching case sync status');
}
};
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 backgroundTime = dayJs(backgroundTimestamp);
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 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.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,
},
];
if (!isTeamLead) {
tasks.push({
taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK,
task: handleGetCaseSyncStatus,
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
onLoop: true,
});
}
const handleDataSync = () => {
if (!isOnline) {
return;
}
dataSyncService();
};
if (IS_DATA_SYNC_REQUIRED) {
tasks.push({
taskId: FOREGROUND_TASKS.DATA_SYNC,
task: handleDataSync,
delay: DATA_SYNC_TIME_INTERVAL,
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 });
handleGetCaseSyncStatus();
handleTimeSync();
dispatch(getConfigData());
CosmosForegroundService.start(tasks);
resyncFirebase();
const msClarityResponse = await getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.MS_CLARITY],
{ 'x-customer-id': GLOBAL.AGENT_ID, deviceId: GLOBAL.DEVICE_ID }
);
const dataSyncResponse = await getLitmusExperimentResult(
LitmusExperimentNameMap[LitmusExperimentName.COSMOS_DATA_SYNC],
{ 'x-customer-id': GLOBAL.AGENT_ID }
);
if (MS_CLARITY_PROJECT_ID && !GLOBAL.MS_CLARITY_INITIALIZED && msClarityResponse) {
initialize(MS_CLARITY_PROJECT_ID);
GLOBAL.MS_CLARITY_INITIALIZED = true;
}
setAsyncStorageItem(LocalStorageKeys.IS_DATA_SYNC_ALLOWED, dataSyncResponse);
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
userActivityUpdateOnBackground();
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_BACKGROUND, { now });
}
appState.current = nextAppState;
};
// Fetch cases on login initially and set data
useEffect(() => {
(async () => {
if (!referenceId) {
return;
}
await handleGetCaseSyncStatus();
dispatch(getConfigData());
const isFirestoreResyncEnabled = getEnableFirestoreResync();
if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES && !isFirestoreResyncEnabled) {
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
if (updatedDetails?.cases?.length) {
dispatch(syncCasesByFallback(updatedDetails));
}
}
})();
}, []);
useEffect(() => {
let appStateSubscription: NativeEventSubscription;
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
CosmosForegroundService.start(tasks);
return () => {
appStateSubscription?.remove();
};
}, []);
useIsLocationEnabled();
return <>{children}</>;
};
export default TrackingComponent;