2023-08-30 15:51:58 +05:30
|
|
|
import { type ReactNode, useEffect, useRef, useCallback } from 'react';
|
2023-08-01 17:43:25 +05:30
|
|
|
import { type NativeEventSubscription, AppState, type AppStateStatus } from 'react-native';
|
|
|
|
|
import dayJs from 'dayjs';
|
2023-08-02 13:32:18 +05:30
|
|
|
import { setItem, getItem } from '../components/utlis/storageHelper';
|
2023-08-30 15:51:58 +05:30
|
|
|
import CosmosForegroundService, {
|
2023-08-01 17:43:25 +05:30
|
|
|
type IForegroundTask,
|
2023-04-18 01:27:28 +05:30
|
|
|
} from '../services/foregroundServices/foreground.service';
|
|
|
|
|
import useIsOnline from '../hooks/useIsOnline';
|
2023-08-08 11:04:32 +05:30
|
|
|
import {
|
|
|
|
|
IGeolocationPayload,
|
|
|
|
|
getSyncTime,
|
|
|
|
|
sendLocationAndActivenessToServer,
|
|
|
|
|
} from '../hooks/capturingApi';
|
2023-04-18 01:27:28 +05:30
|
|
|
import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions';
|
2023-08-06 13:45:15 +05:30
|
|
|
import { setDeviceGeolocationsBuffer, setIsTimeSynced } from '../reducer/foregroundServiceSlice';
|
2023-04-18 01:27:28 +05:30
|
|
|
import { CaptureGeolocation } from '../components/form/services/geoLocation.service';
|
|
|
|
|
import { logError } from '../components/utlis/errorUtils';
|
2023-05-10 12:50:20 +05:30
|
|
|
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';
|
2023-05-17 20:28:49 +05:30
|
|
|
import {
|
2023-08-01 17:43:25 +05:30
|
|
|
type ISyncCaseIdPayload,
|
|
|
|
|
type ISyncedCases,
|
2023-05-17 20:28:49 +05:30
|
|
|
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';
|
2023-06-20 12:41:47 +05:30
|
|
|
import { setLockData } from '../reducer/userSlice';
|
|
|
|
|
import { getConfigData } from '../action/configActions';
|
2023-08-01 17:43:25 +05:30
|
|
|
import { AppStates } from '../types/appStates';
|
2023-08-02 13:32:18 +05:30
|
|
|
import { StorageKeys } from '../types/storageKeys';
|
2023-08-31 00:34:50 +05:30
|
|
|
import { toast } from '../../RN-UI-LIB/src/components/toast';
|
2023-04-18 01:27:28 +05:30
|
|
|
|
|
|
|
|
export enum FOREGROUND_TASKS {
|
|
|
|
|
GEOLOCATION = 'GEOLOCATION',
|
|
|
|
|
TIME_SYNC = 'TIME_SYNC',
|
2023-05-10 12:50:20 +05:30
|
|
|
DATA_SYNC = 'DATA_SYNC',
|
2023-05-17 20:28:49 +05:30
|
|
|
FIRESTORE_FALLBACK = 'FIRESTORE_FALLBACK',
|
2023-08-01 17:43:25 +05:30
|
|
|
UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS',
|
2023-04-18 01:27:28 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ITrackingComponent {
|
|
|
|
|
children?: ReactNode;
|
|
|
|
|
}
|
2023-06-13 18:53:13 +05:30
|
|
|
|
2023-06-06 20:05:01 +05:30
|
|
|
let LAST_SYNC_STATUS = 'SKIP';
|
2023-08-02 13:32:18 +05:30
|
|
|
const ACTIVITY_TIME_ON_APP = 5; // 5 seconds
|
|
|
|
|
const ACTIVITY_TIME_WINDOW = 10; // 10 minutes
|
2023-04-18 01:27:28 +05:30
|
|
|
|
|
|
|
|
const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
|
|
|
|
const isOnline = useIsOnline();
|
|
|
|
|
const dispatch = useAppDispatch();
|
|
|
|
|
const appState = useRef(AppState.currentState);
|
|
|
|
|
|
2023-05-19 17:52:28 +05:30
|
|
|
const {
|
|
|
|
|
referenceId,
|
|
|
|
|
pendingList = [],
|
|
|
|
|
pinnedList = [],
|
2023-08-06 13:45:15 +05:30
|
|
|
geolocations = [],
|
2023-05-19 17:52:28 +05:30
|
|
|
} = useAppSelector((state) => ({
|
2023-08-01 17:43:25 +05:30
|
|
|
referenceId: state.user.user?.referenceId!,
|
2023-05-19 17:52:28 +05:30
|
|
|
pendingList: state.allCases.pendingList,
|
|
|
|
|
pinnedList: state.allCases.pinnedList,
|
2023-08-06 13:45:15 +05:30
|
|
|
geolocations: state.foregroundService.deviceGeolocationsBuffer,
|
2023-05-19 17:52:28 +05:30
|
|
|
}));
|
2023-05-17 20:28:49 +05:30
|
|
|
|
2023-04-18 01:27:28 +05:30
|
|
|
const handleTimeSync = async () => {
|
|
|
|
|
try {
|
2023-08-08 18:45:08 +05:30
|
|
|
if (!isOnline) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-04-18 01:27:28 +05:30
|
|
|
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.');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-31 00:34:50 +05:30
|
|
|
const handleSendGeolocation = async () => {
|
2023-04-18 01:27:28 +05:30
|
|
|
try {
|
2023-05-11 17:23:40 +05:30
|
|
|
const location = await CaptureGeolocation.fetchLocation(Date.now() + '', 0, appState.current);
|
2023-08-06 13:45:15 +05:30
|
|
|
if (!location) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-31 00:34:50 +05:30
|
|
|
toast({ type: 'info', text1: `Location: ${location.latitude}, ${location.longitude}` });
|
2023-08-08 11:04:32 +05:30
|
|
|
const isActiveOnApp: string | boolean = (await getItem(StorageKeys.IS_USER_ACTIVE)) || false;
|
2023-08-06 13:45:15 +05:30
|
|
|
const geolocation: IGeolocationPayload = {
|
|
|
|
|
latitude: location.latitude,
|
|
|
|
|
longitude: location.longitude,
|
|
|
|
|
accuracy: location.accuracy,
|
|
|
|
|
timestamp: Date.now(),
|
2023-08-08 11:04:32 +05:30
|
|
|
isActiveOnApp: Boolean(isActiveOnApp),
|
2023-08-06 13:45:15 +05:30
|
|
|
};
|
2023-08-30 15:51:58 +05:30
|
|
|
dispatch(sendLocationAndActivenessToServer([geolocation]));
|
2023-04-18 01:27:28 +05:30
|
|
|
} catch (e: any) {
|
|
|
|
|
logError(e, 'Error during background location sending.');
|
|
|
|
|
}
|
2023-08-31 00:34:50 +05:30
|
|
|
};
|
2023-04-18 01:27:28 +05:30
|
|
|
|
2023-08-08 18:45:08 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isOnline) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (geolocations.length) {
|
2023-08-30 15:51:58 +05:30
|
|
|
dispatch(sendLocationAndActivenessToServer(geolocations, true));
|
2023-08-08 18:45:08 +05:30
|
|
|
}
|
|
|
|
|
}, [geolocations, isOnline]);
|
|
|
|
|
|
2023-05-17 20:28:49 +05:30
|
|
|
const handleGetCaseSyncStatus = async () => {
|
|
|
|
|
try {
|
2023-08-08 18:45:08 +05:30
|
|
|
if (!isOnline) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-04 16:32:01 +05:30
|
|
|
const { syncStatus, visitPlanStatus } = (await getCasesSyncStatus(referenceId)) ?? {};
|
2023-06-07 10:11:18 +05:30
|
|
|
if (syncStatus) {
|
2023-06-07 10:13:03 +05:30
|
|
|
// Keep track of the last status received
|
2023-06-07 10:11:18 +05:30
|
|
|
LAST_SYNC_STATUS = syncStatus;
|
|
|
|
|
}
|
2023-05-17 20:28:49 +05:30
|
|
|
if (syncStatus === SyncStatus.SEND_CASES) {
|
|
|
|
|
const cases = getSyncCaseIds([...pendingList, ...pinnedList]);
|
|
|
|
|
const payload: ISyncCaseIdPayload = {
|
|
|
|
|
agentId: referenceId,
|
|
|
|
|
cases,
|
|
|
|
|
};
|
|
|
|
|
sendSyncCaseIds(payload);
|
2023-05-19 17:52:28 +05:30
|
|
|
} else if (syncStatus === SyncStatus.FETCH_CASES) {
|
2023-05-17 20:28:49 +05:30
|
|
|
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
|
2023-05-19 12:14:27 +05:30
|
|
|
if (updatedDetails?.cases?.length) {
|
|
|
|
|
dispatch(syncCasesByFallback(updatedDetails));
|
|
|
|
|
}
|
2023-05-17 20:28:49 +05:30
|
|
|
}
|
2023-06-04 16:32:01 +05:30
|
|
|
if (visitPlanStatus) {
|
|
|
|
|
dispatch(
|
|
|
|
|
setLockData({
|
|
|
|
|
visitPlanStatus,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-05-17 20:28:49 +05:30
|
|
|
} catch (e) {
|
|
|
|
|
logError(e as Error, 'Error during fetching case sync status');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-01 17:43:25 +05:30
|
|
|
const handleUpdateActiveness = async () => {
|
2023-08-04 07:43:35 +05:30
|
|
|
if (AppState.currentState === AppStates.ACTIVE) {
|
|
|
|
|
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-02 13:32:18 +05:30
|
|
|
const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
|
|
|
|
|
const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
|
2023-08-04 13:25:05 +05:30
|
|
|
const foregroundTime = dayJs(foregroundTimestamp);
|
|
|
|
|
const backgroundTime = dayJs(backgroundTimestamp);
|
2023-08-02 13:32:18 +05:30
|
|
|
const diffBetweenBackgroundAndForegroundTime = dayJs(backgroundTime).diff(
|
|
|
|
|
foregroundTime,
|
|
|
|
|
'seconds'
|
|
|
|
|
);
|
|
|
|
|
const diffBetweenCurrentTimeAndForegroundTime =
|
2023-08-04 13:25:05 +05:30
|
|
|
dayJs().diff(foregroundTime, 'minutes') < 0 ? 0 : dayJs().diff(foregroundTime, 'minutes');
|
2023-08-02 13:32:18 +05:30
|
|
|
const isForegroundTimeWithInRange =
|
|
|
|
|
diffBetweenCurrentTimeAndForegroundTime <= ACTIVITY_TIME_WINDOW;
|
2023-08-04 13:25:05 +05:30
|
|
|
const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp);
|
2023-08-04 07:43:35 +05:30
|
|
|
|
2023-08-02 13:32:18 +05:30
|
|
|
if (isForegroundTimeWithInRange) {
|
|
|
|
|
if (
|
|
|
|
|
isForegroundTimeAfterBackground ||
|
|
|
|
|
diffBetweenBackgroundAndForegroundTime >= ACTIVITY_TIME_ON_APP
|
|
|
|
|
) {
|
|
|
|
|
await setItem(StorageKeys.IS_USER_ACTIVE, 'true');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
|
2023-08-01 17:43:25 +05:30
|
|
|
}
|
2023-08-02 13:32:18 +05:30
|
|
|
await setItem(StorageKeys.IS_USER_ACTIVE, 'false');
|
|
|
|
|
return;
|
2023-08-01 17:43:25 +05:30
|
|
|
};
|
|
|
|
|
|
2023-04-18 01:27:28 +05:30
|
|
|
const tasks: IForegroundTask[] = [
|
|
|
|
|
{
|
|
|
|
|
taskId: FOREGROUND_TASKS.TIME_SYNC,
|
|
|
|
|
task: handleTimeSync,
|
2023-05-17 20:28:49 +05:30
|
|
|
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes,
|
2023-04-18 01:27:28 +05:30
|
|
|
onLoop: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
taskId: FOREGROUND_TASKS.GEOLOCATION,
|
|
|
|
|
task: handleSendGeolocation,
|
2023-08-31 00:34:50 +05:30
|
|
|
delay: 3000, // 3 minutes
|
2023-05-17 20:28:49 +05:30
|
|
|
onLoop: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK,
|
|
|
|
|
task: handleGetCaseSyncStatus,
|
2023-06-05 17:21:30 +05:30
|
|
|
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
|
2023-04-18 01:27:28 +05:30
|
|
|
onLoop: true,
|
|
|
|
|
},
|
2023-08-01 17:43:25 +05:30
|
|
|
{
|
|
|
|
|
taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVENESS,
|
|
|
|
|
task: handleUpdateActiveness,
|
2023-08-02 13:32:18 +05:30
|
|
|
delay: ACTIVITY_TIME_WINDOW * MILLISECONDS_IN_A_MINUTE, // 10 minutes
|
2023-08-01 17:43:25 +05:30
|
|
|
onLoop: true,
|
|
|
|
|
},
|
2023-04-18 01:27:28 +05:30
|
|
|
];
|
2023-05-10 12:50:20 +05:30
|
|
|
|
2023-08-08 18:45:08 +05:30
|
|
|
const handleDataSync = () => {
|
|
|
|
|
if (!isOnline) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
dataSyncService();
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-10 12:50:20 +05:30
|
|
|
if (IS_DATA_SYNC_REQUIRED) {
|
|
|
|
|
tasks.push({
|
|
|
|
|
taskId: FOREGROUND_TASKS.DATA_SYNC,
|
2023-08-08 18:45:08 +05:30
|
|
|
task: handleDataSync,
|
2023-05-10 12:50:20 +05:30
|
|
|
delay: DATA_SYNC_TIME_INTERVAL,
|
|
|
|
|
onLoop: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-18 01:27:28 +05:30
|
|
|
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
|
|
|
|
|
// App comes to foreground from background
|
2023-08-04 13:25:05 +05:30
|
|
|
const now = dayJs().toString();
|
2023-08-01 17:43:25 +05:30
|
|
|
if (nextAppState === AppStates.ACTIVE) {
|
2023-08-02 13:32:18 +05:30
|
|
|
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
|
2023-06-04 16:32:01 +05:30
|
|
|
handleGetCaseSyncStatus();
|
2023-06-20 12:41:47 +05:30
|
|
|
dispatch(getConfigData());
|
2023-08-30 15:51:58 +05:30
|
|
|
CosmosForegroundService.start(tasks);
|
2023-04-18 01:27:28 +05:30
|
|
|
}
|
2023-08-02 13:32:18 +05:30
|
|
|
if (nextAppState === AppStates.BACKGROUND) {
|
|
|
|
|
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
|
2023-08-01 17:43:25 +05:30
|
|
|
}
|
2023-04-18 01:27:28 +05:30
|
|
|
appState.current = nextAppState;
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-06 20:05:01 +05:30
|
|
|
// Fetch cases on login initially and set data
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
(async () => {
|
|
|
|
|
if (!referenceId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await handleGetCaseSyncStatus();
|
2023-06-20 15:50:22 +05:30
|
|
|
dispatch(getConfigData());
|
2023-06-06 20:05:01 +05:30
|
|
|
if (LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) {
|
|
|
|
|
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
|
|
|
|
|
if (updatedDetails?.cases?.length) {
|
|
|
|
|
dispatch(syncCasesByFallback(updatedDetails));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2023-04-18 01:27:28 +05:30
|
|
|
useEffect(() => {
|
2023-06-20 15:50:22 +05:30
|
|
|
let appStateSubscription: NativeEventSubscription;
|
2023-08-30 15:51:58 +05:30
|
|
|
CosmosForegroundService.isRunning().then((isFGSRunning) => {
|
2023-08-30 11:57:55 +05:30
|
|
|
if (!isFGSRunning) {
|
|
|
|
|
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
|
2023-08-30 15:51:58 +05:30
|
|
|
CosmosForegroundService.start(tasks);
|
2023-08-30 11:57:55 +05:30
|
|
|
}
|
|
|
|
|
});
|
2023-06-20 15:50:22 +05:30
|
|
|
return () => {
|
|
|
|
|
appStateSubscription?.remove();
|
|
|
|
|
};
|
2023-08-08 18:45:08 +05:30
|
|
|
}, []);
|
2023-04-18 01:27:28 +05:30
|
|
|
|
2023-05-10 12:50:20 +05:30
|
|
|
useIsLocationEnabled();
|
|
|
|
|
|
2023-04-18 01:27:28 +05:30
|
|
|
return <>{children}</>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default TrackingComponent;
|