diff --git a/package.json b/package.json index 1e2e87e6..c734d9f4 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,13 @@ "appcenter-analytics": "^4.4.5", "appcenter-crashes": "^4.4.5", "axios": "1.2.1", + "dayjs": "^1.11.9", "fuzzysort": "2.0.4", "lottie-react-native": "5.1.4", "react": "18.1.0", "react-hook-form": "7.40.0", "react-native": "0.70.6", + "react-native-blob-util": "0.17.3", "react-native-call-log": "2.1.2", "react-native-code-push": "7.1.0", "react-native-contacts": "7.0.5", @@ -74,11 +76,10 @@ "react-native-toast-message": "2.1.5", "react-native-vector-icons": "9.2.0", "react-native-version-number": "0.3.6", + "react-native-webview": "12.0.2", "react-redux": "8.0.5", "redux": "4.2.0", - "redux-persist": "6.0.0", - "react-native-webview": "12.0.2", - "react-native-blob-util": "0.17.3" + "redux-persist": "6.0.0" }, "devDependencies": { "@babel/core": "7.12.9", @@ -102,6 +103,7 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "17.0.0", "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier-react": "0.0.24", "eslint-config-standard-with-typescript": "^34.0.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-jsx-a11y": "6.6.1", @@ -117,7 +119,6 @@ "miragejs": "0.1.47", "prettier": "^2.8.7", "react-test-renderer": "18.1.0", - "eslint-config-prettier-react": "0.0.24", "typescript": "4.8.3" }, "jest": { diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 2d21f51e..9954e7df 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -1,22 +1,24 @@ -import { ReactNode, useEffect, useRef } from 'react'; -import { NativeEventSubscription } from 'react-native'; +import { type ReactNode, useEffect, useRef } from 'react'; +import { type NativeEventSubscription, AppState, type AppStateStatus } from 'react-native'; +import dayJs from 'dayjs'; +import dayjs from 'dayjs'; import UnstoppableService, { - IForegroundTask, + type IForegroundTask, } from '../services/foregroundServices/foreground.service'; import useIsOnline from '../hooks/useIsOnline'; -import { getSyncTime, sendLocationToServer } from '../hooks/capturingApi'; +import { getSyncTime, sendLocationAndActivenessToServer } from '../hooks/capturingApi'; import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions'; import { setDeviceGeolocation, setIsTimeSynced } from '../reducer/foregroundServiceSlice'; import { CaptureGeolocation } from '../components/form/services/geoLocation.service'; -import { AppState, AppStateStatus } from 'react-native'; 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 { setAppForegroundTimestamp, setAppBackgroundTimestamp } from '../reducer/metadataSlice'; import { - ISyncCaseIdPayload, - ISyncedCases, + type ISyncCaseIdPayload, + type ISyncedCases, SyncStatus, fetchCasesToSync, getCasesSyncStatus, @@ -25,14 +27,16 @@ import { 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 { setIsActiveUser, setLockData } from '../reducer/userSlice'; import { getConfigData } from '../action/configActions'; +import { AppStates } from '../types/appStates'; 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 { @@ -46,13 +50,16 @@ const TrackingComponent: React.FC = ({ children }) => { const dispatch = useAppDispatch(); const appState = useRef(AppState.currentState); const user = useAppSelector((state) => state.user); + const foregroundTimestamp = useAppSelector((state) => state.metadata.appForegroundTimestamp); + const backgroundTimestamp = useAppSelector((state) => state.metadata.appBackgroundTimestamp); + const isActive = useAppSelector((state) => state.user.user.isActive); const { referenceId, pendingList = [], pinnedList = [], } = useAppSelector((state) => ({ - referenceId: state.user.user?.referenceId!!, + referenceId: state.user.user?.referenceId!, pendingList: state.allCases.pendingList, pinnedList: state.allCases.pinnedList, })); @@ -71,8 +78,8 @@ const TrackingComponent: React.FC = ({ children }) => { const handleSendGeolocation = async () => { try { - const location = await CaptureGeolocation.fetchLocation(Date.now() + '', 0, appState.current); - if (location) { + const location = await CaptureGeolocation.fetchLocation(`${Date.now()}`, 0, appState.current); + if (location != null) { dispatch( setDeviceGeolocation({ latitude: location.latitude, @@ -81,7 +88,7 @@ const TrackingComponent: React.FC = ({ children }) => { }) ); if (user.isLoggedIn) { - await sendLocationToServer(location); + await sendLocationAndActivenessToServer(location, isActive); } } } catch (e: any) { @@ -121,6 +128,15 @@ const TrackingComponent: React.FC = ({ children }) => { } }; + const handleUpdateActiveness = async () => { + const foregroundTime = foregroundTimestamp; + const backgroundTime = backgroundTimestamp; + const diff = dayjs(foregroundTime).diff(dayjs(backgroundTime), 'minute'); + if (diff <= 10) { + setIsActiveUser(true); + } + }; + const tasks: IForegroundTask[] = [ { taskId: FOREGROUND_TASKS.TIME_SYNC, @@ -140,6 +156,18 @@ const TrackingComponent: React.FC = ({ children }) => { delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 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: 10 * MILLISECONDS_IN_A_MINUTE, // 10 minutes + onLoop: true, + }, ]; if (IS_DATA_SYNC_REQUIRED) { @@ -153,11 +181,16 @@ const TrackingComponent: React.FC = ({ children }) => { const handleAppStateChange = async (nextAppState: AppStateStatus) => { // App comes to foreground from background - if (nextAppState === 'active') { + console.log('next app state', nextAppState); + if (nextAppState === AppStates.ACTIVE) { + setAppForegroundTimestamp(dayjs()); handleGetCaseSyncStatus(); dispatch(getConfigData()); UnstoppableService.start(tasks); } + if (nextAppState === 'background') { + setAppBackgroundTimestamp(dayjs()); + } appState.current = nextAppState; }; @@ -183,10 +216,8 @@ const TrackingComponent: React.FC = ({ children }) => { if (isOnline) { appStateSubscription = AppState.addEventListener('change', handleAppStateChange); UnstoppableService.start(tasks); - } else { - if (UnstoppableService.isRunning()) { - UnstoppableService.stopAll(); - } + } else if (UnstoppableService.isRunning()) { + UnstoppableService.stopAll(); } return () => { appStateSubscription?.remove(); diff --git a/src/hooks/capturingApi.ts b/src/hooks/capturingApi.ts index f4109d94..daaf0fb7 100644 --- a/src/hooks/capturingApi.ts +++ b/src/hooks/capturingApi.ts @@ -1,7 +1,10 @@ import { GeoCoordinates } from 'react-native-geolocation-service'; import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper'; -export const sendLocationToServer = async (location: GeoCoordinates) => { +export const sendLocationAndActivenessToServer = async ( + location: GeoCoordinates, + isActive: boolean +) => { try { const response = await axiosInstance.post( getApiUrl(ApiKeys.SEND_LOCATION), diff --git a/src/reducer/commonSlice.ts b/src/reducer/commonSlice.ts index 20ea8e76..511c6d9a 100644 --- a/src/reducer/commonSlice.ts +++ b/src/reducer/commonSlice.ts @@ -12,6 +12,11 @@ export interface User { sessionToken: string; } +export interface AppState { + appForegroundTimestamp: string; + appBackgroundTimestamp: string; +} + export interface CommonState { userData: User; clickstreamEvents: IClickstreamEvents[]; diff --git a/src/reducer/metadataSlice.ts b/src/reducer/metadataSlice.ts index 71ec5d8d..3b75b2a4 100644 --- a/src/reducer/metadataSlice.ts +++ b/src/reducer/metadataSlice.ts @@ -1,4 +1,5 @@ import { createSlice } from '@reduxjs/toolkit'; + export interface UninstallInformation { last_operational_time: any; reinstall_endpoint: string; @@ -8,12 +9,16 @@ interface IMetadata { isOnline: boolean; forceUninstall: Record; isWifiOrCellularOn: boolean; + appForegroundTimestamp: string; + appBackgroundTimestamp: string; } const initialState = { isOnline: true, forceUninstall: {}, isWifiOrCellularOn: true, + appForegroundTimestamp: '', + appBackgroundTimestamp: '', } as IMetadata; const MetadataSlice = createSlice({ @@ -29,9 +34,21 @@ const MetadataSlice = createSlice({ setIsWifiOrCellularOn: (state, action) => { state.isWifiOrCellularOn = action.payload; }, + setAppForegroundTimestamp: (state, action) => { + state.appForegroundTimestamp = action.payload; + }, + setAppBackgroundTimestamp: (state, action) => { + state.appBackgroundTimestamp = action.payload; + }, }, }); -export const { setIsOnline, setForceUninstallData, setIsWifiOrCellularOn } = MetadataSlice.actions; +export const { + setIsOnline, + setForceUninstallData, + setIsWifiOrCellularOn, + setAppForegroundTimestamp, + setAppBackgroundTimestamp, +} = MetadataSlice.actions; export default MetadataSlice.reducer; diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index b7fe08af..5b335e18 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -16,11 +16,13 @@ interface IUserDetails { referenceId: string; phoneNumber: string; realms: string[]; - roles: Array; + roles: IUserRole[]; groups: string[]; name: string; createdAt: string; updatedAt: string; + // check weather user is recently active on app or not + isActive: boolean; } export interface IUser { @@ -86,9 +88,12 @@ export const userSlice = createSlice({ state.lock = action.payload; } }, + setIsActiveUser: (state, action) => { + state.user.isActive = action.payload; + }, }, }); -export const { setAuthData, setDeviceId, setLockData } = userSlice.actions; +export const { setAuthData, setDeviceId, setLockData, setIsActiveUser } = userSlice.actions; export default userSlice.reducer; diff --git a/src/types/appStates.ts b/src/types/appStates.ts new file mode 100644 index 00000000..54f1a10a --- /dev/null +++ b/src/types/appStates.ts @@ -0,0 +1,4 @@ +export enum AppStates { + ACTIVE = 'active', + BACKGROUND = 'background', +} diff --git a/yarn.lock b/yarn.lock index bf83f2c3..09dbde4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3399,6 +3399,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dayjs@^1.11.9: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" + integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== + dayjs@^1.8.15: version "1.11.7" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"