Files
address-verification-app/src/common/TrackingComponent.tsx
2023-08-30 15:52:44 +05:30

277 lines
8.8 KiB
TypeScript

import { type ReactNode, useEffect, useRef, useCallback } from 'react';
import { type NativeEventSubscription, AppState, type AppStateStatus } from 'react-native';
import dayJs from 'dayjs';
import { setItem, getItem } from '../components/utlis/storageHelper';
import CosmosForegroundService, {
type IForegroundTask,
} from '../services/foregroundServices/foreground.service';
import useIsOnline from '../hooks/useIsOnline';
import {
IGeolocationPayload,
getSyncTime,
sendLocationAndActivenessToServer,
} from '../hooks/capturingApi';
import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions';
import { setDeviceGeolocationsBuffer, setIsTimeSynced } from '../reducer/foregroundServiceSlice';
import { CaptureGeolocation } from '../components/form/services/geoLocation.service';
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 {
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 } from '../../RN-UI-LIB/src/utlis/common';
import { setLockData } from '../reducer/userSlice';
import { getConfigData } from '../action/configActions';
import { AppStates } from '../types/appStates';
import { StorageKeys } from '../types/storageKeys';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
TIME_SYNC = 'TIME_SYNC',
DATA_SYNC = 'DATA_SYNC',
FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK',
UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS',
}
interface ITrackingComponent {
children?: ReactNode;
}
let LAST_SYNC_STATUS = 'SKIP';
const ACTIVITY_TIME_ON_APP = 5; // 5 seconds
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 {
referenceId,
pendingList = [],
pinnedList = [],
geolocations = [],
} = useAppSelector((state) => ({
referenceId: state.user.user?.referenceId!,
pendingList: state.allCases.pendingList,
pinnedList: state.allCases.pinnedList,
geolocations: state.foregroundService.deviceGeolocationsBuffer,
}));
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 handleSendGeolocation = useCallback(async () => {
try {
const location = await CaptureGeolocation.fetchLocation(Date.now() + '', 0, appState.current);
if (!location) {
return;
}
const isActiveOnApp: string | boolean = (await getItem(StorageKeys.IS_USER_ACTIVE)) || false;
const geolocation: IGeolocationPayload = {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
timestamp: Date.now(),
isActiveOnApp: Boolean(isActiveOnApp),
};
dispatch(sendLocationAndActivenessToServer([geolocation]));
} catch (e: any) {
logError(e, 'Error during background location sending.');
}
}, [dispatch, geolocations]);
useEffect(() => {
if (!isOnline) {
return;
}
if (geolocations.length) {
dispatch(sendLocationAndActivenessToServer(geolocations, true));
}
}, [geolocations, isOnline]);
const handleGetCaseSyncStatus = async () => {
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,
})
);
}
} catch (e) {
logError(e as Error, 'Error during fetching case sync status');
}
};
const handleUpdateActiveness = async () => {
if (AppState.currentState === AppStates.ACTIVE) {
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
return;
}
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 diffBetweenCurrentTimeAndForegroundTime =
dayJs().diff(foregroundTime, 'minutes') < 0 ? 0 : dayJs().diff(foregroundTime, 'minutes');
const isForegroundTimeWithInRange =
diffBetweenCurrentTimeAndForegroundTime <= ACTIVITY_TIME_WINDOW;
const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp);
if (isForegroundTimeWithInRange) {
if (
isForegroundTimeAfterBackground ||
diffBetweenBackgroundAndForegroundTime >= ACTIVITY_TIME_ON_APP
) {
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
return;
}
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
}
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
return;
};
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: handleSendGeolocation,
delay: 10000, // 3 minutes
onLoop: true,
},
{
taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK,
task: handleGetCaseSyncStatus,
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
onLoop: true,
},
{
taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVENESS,
task: handleUpdateActiveness,
delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 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 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);
handleGetCaseSyncStatus();
dispatch(getConfigData());
CosmosForegroundService.start(tasks);
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
}
appState.current = nextAppState;
};
// Fetch cases on login initially and set data
useEffect(() => {
(async () => {
if (!referenceId) {
return;
}
await handleGetCaseSyncStatus();
dispatch(getConfigData());
if (LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) {
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
if (updatedDetails?.cases?.length) {
dispatch(syncCasesByFallback(updatedDetails));
}
}
})();
}, []);
useEffect(() => {
let appStateSubscription: NativeEventSubscription;
CosmosForegroundService.isRunning().then((isFGSRunning) => {
if (!isFGSRunning) {
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
CosmosForegroundService.start(tasks);
}
});
return () => {
appStateSubscription?.remove();
};
}, []);
useIsLocationEnabled();
return <>{children}</>;
};
export default TrackingComponent;