From 07220a05d12ecba168cae625db81340d36682b47 Mon Sep 17 00:00:00 2001 From: Varnit Goyal Date: Thu, 4 Jan 2024 19:17:10 +0530 Subject: [PATCH] TP-51252 | resync firebase (#692) --- .eslintrc.json | 4 +- .gitignore | 3 +- android/app/build.gradle | 4 +- firebase.json | 4 +- package.json | 2 +- .../AgentActivityConfigurableConstants.ts | 13 + src/common/Constants.ts | 20 +- src/common/TrackingComponent.tsx | 99 +-- src/components/utlis/apiHelper.ts | 126 ++-- src/hooks/useResyncFirebase.ts | 196 ++++++ src/reducer/allCasesSlice.ts | 1 + src/reducer/metadataSlice.ts | 17 +- src/screens/auth/ProtectedRouter.tsx | 601 +++++++++--------- .../firebaseFetchAndUpdate.service.ts | 64 +- src/store/store.ts | 5 +- 15 files changed, 720 insertions(+), 439 deletions(-) create mode 100644 src/hooks/useResyncFirebase.ts diff --git a/.eslintrc.json b/.eslintrc.json index 46cfc8b8..6cff82a4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,6 +19,8 @@ "plugins": ["react", "react-native"], "rules": { "@typescript-eslint/strict-boolean-expressions": 0, - "@typescript-eslint/no-floating-promises": 0 + "@typescript-eslint/no-floating-promises": 0, + "@typescript-eslint/no-misused-promises": 0, + "react-native/split-platform-components": 0 } } diff --git a/.gitignore b/.gitignore index d639dd21..12ed79f2 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,5 @@ buck-out/ # Ruby / CocoaPods /ios/Pods/ /vendor/bundle/ -/android/app/src/main/res/values/strings.xml \ No newline at end of file +/android/app/src/main/res/values/strings.xml +.eslintcache \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index e3454053..4b308c70 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -133,8 +133,8 @@ def reactNativeArchitectures() { return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } -def VERSION_CODE = 115 -def VERSION_NAME = "2.6.6" +def VERSION_CODE = 117 +def VERSION_NAME = "2.6.9" android { ndkVersion rootProject.ext.ndkVersion diff --git a/firebase.json b/firebase.json index 006eb8f1..2d124d65 100644 --- a/firebase.json +++ b/firebase.json @@ -1,5 +1,7 @@ { "react-native": { - "crashlytics_debug_enabled": true + "crashlytics_debug_enabled": false, + "android_task_executor_maximum_pool_size": 20, + "android_task_executor_keep_alive_seconds": 5 } } diff --git a/package.json b/package.json index 5d3db15c..6294b9b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "AV_APP", - "version": "2.6.6", + "version": "2.6.9", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/common/AgentActivityConfigurableConstants.ts b/src/common/AgentActivityConfigurableConstants.ts index 015db2b6..a2b7dfee 100644 --- a/src/common/AgentActivityConfigurableConstants.ts +++ b/src/common/AgentActivityConfigurableConstants.ts @@ -1,11 +1,16 @@ let ACTIVITY_TIME_ON_APP: number = 5; //5 seconds let ACTIVITY_TIME_WINDOW_HIGH: number = 10; //10 minutes let ACTIVITY_TIME_WINDOW_MEDIUM: number = 30; //30 minutes +let ENABLE_FIRESTORE_RESYNC = false; +let FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = 15; export const getActivityTimeOnApp = () => ACTIVITY_TIME_ON_APP; export const getActivityTimeWindowHigh = () => ACTIVITY_TIME_WINDOW_HIGH; export const getActivityTimeWindowMedium = () => ACTIVITY_TIME_WINDOW_MEDIUM; +export const getEnableFirestoreResync = () => ENABLE_FIRESTORE_RESYNC; +export const getFirestoreResyncIntervalInMinutes = () => FIRESTORE_RESYNC_INTERVAL_IN_MINUTES; + export const setActivityTimeOnApp = (activityTimeOnApp: number) => { ACTIVITY_TIME_ON_APP = activityTimeOnApp; }; @@ -17,3 +22,11 @@ export const setActivityTimeWindowHigh = (activityTimeWindowHigh: number) => { export const setActivityTimeWindowMedium = (activityTimeWindowMedium: number) => { ACTIVITY_TIME_WINDOW_MEDIUM = activityTimeWindowMedium; }; + +export const setEnableFirestoreResync = (enableFirestoreResync: boolean) => { + ENABLE_FIRESTORE_RESYNC = enableFirestoreResync; +}; + +export const setFirestoreResyncIntervalInMinutes = (firestoreResyncIntervalInMinutes: number) => { + FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = firestoreResyncIntervalInMinutes; +}; \ No newline at end of file diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 29af2f50..bb6d8701 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -33,6 +33,11 @@ export enum FirestoreUpdateTypes { REMOVED = 'removed', } + +export enum SyncedSource { + FIREBASE='FIREBASE', + API='API', +} export const ClickstreamAPIToMonitor = { [API_URLS[ApiKeys.GENERATE_OTP]]: 'AV_LOGIN_SCREEN_SEND_OTP_API', [API_URLS[ApiKeys.VERIFY_OTP]]: 'AV_OTP_SCREEN_VERIFY_OTP_API', @@ -442,7 +447,7 @@ export const CLICKSTREAM_EVENT_NAMES = { FA_SHARE_FEEDBACK_CLICKED: { name: 'FA_SHARE_FEEDBACK_CLICKED', description: - 'When user clicks on share feedback on case details screen for any of the filled feedback', + 'When user clicks on share feedback on case details screen for any of the filled feedback', }, FA_SHARE_SUCCESSFUL: { name: 'FA_SHARE_SUCCESSFUL', @@ -702,6 +707,15 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_LEGAL_DOCUMENT_DOWNLOAD_FAILED', description: 'FA_LEGAL_DOCUMENT_DOWNLOAD_FAILED', }, + // firebase resync + FA_FIREBASE_RESYNC: { + name: 'FA_FIREBASE_RESYNC', + description: 'FA_FIREBASE_RESYNC', + }, + FA_FIREBASE_RESYNC_STARTED: { + name: 'FA_FIREBASE_RESYNC_STARTED', + description: 'FA_FIREBASE_RESYNC_STARTED', + }, } as const; export enum MimeType { @@ -732,7 +746,7 @@ export const VISIT_PLAN_HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER export const VISIT_PLAN_HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER_HEIGHT_MIN + 50; export const HEADER_SCROLL_DISTANCE = (HEADER_HEIGHT_MAX - HEADER_HEIGHT_MIN) * 2; export const HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS = - (HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS - HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS) * 2; + (HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS - HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS) * 2; export const LocalStorageKeys = { LOAN_ID_TO_VALUE: 'loanIdToValue', @@ -817,4 +831,4 @@ export const REQUEST_TO_UNBLOCK_FOR_IMPERSONATION = [ getApiUrl(ApiKeys.PAST_FEEDBACK), ]; -export const NAVI_AGENCY_CODE = '1000'; +export const NAVI_AGENCY_CODE = '1000'; \ No newline at end of file diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 22c2a759..04d7137b 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -33,7 +33,7 @@ import { } 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 {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'; @@ -42,11 +42,13 @@ import { AgentActivity } from '../types/agentActivity'; import { getActivityTimeOnApp, getActivityTimeWindowMedium, - getActivityTimeWindowHigh, + getActivityTimeWindowHigh, getEnableFirestoreResync, getFirestoreResyncIntervalInMinutes, } from './AgentActivityConfigurableConstants'; import { GlobalImageMap } from './CachedImage'; import { addClickstreamEvent } from '../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from './Constants'; +import useResyncFirebase from "@hooks/useResyncFirebase"; +import AsyncStorage from "@react-native-async-storage/async-storage"; export enum FOREGROUND_TASKS { GEOLOCATION = 'GEOLOCATION', @@ -57,6 +59,7 @@ export enum FOREGROUND_TASKS { UPDATE_AGENT_ACTIVITY = 'UPDATE_AGENT_ACTIVITY', DELETE_CACHE = 'DELETE_CACHE', FETCH_DATA_FROM_FIREBASE = 'FETCH_DATA_FROM_FIREBASE', + FIREBASE_RESYNC = 'FIREBASE_RESYNC' } interface ITrackingComponent { @@ -66,12 +69,14 @@ interface ITrackingComponent { let LAST_SYNC_STATUS = 'SKIP'; const ACTIVITY_TIME_WINDOW = 10; // 10 minutes + const TrackingComponent: React.FC = ({ 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 lastFirebaseResyncTimestamp = useAppSelector((state) => state?.metadata?.lastFirebaseResyncTimestamp); const { referenceId, @@ -108,7 +113,7 @@ const TrackingComponent: React.FC = ({ children }) => { } const isActiveOnApp: string | boolean = (await getItem(StorageKeys.IS_USER_ACTIVE)) || false; const userActivityonApp: string = - (await getItem(StorageKeys.USER_ACTIVITY_ON_APP)) || AgentActivity.LOW; + (await getItem(StorageKeys.USER_ACTIVITY_ON_APP)) || AgentActivity.LOW; const geolocation: IGeolocationPayload = { latitude: location.latitude, @@ -134,8 +139,11 @@ const TrackingComponent: React.FC = ({ children }) => { } }, [geolocations, isOnline]); + + const resyncFirebase = useResyncFirebase(); + const handleGetCaseSyncStatus = async () => { - if (caseSyncLock) { + if (caseSyncLock || getEnableFirestoreResync()) { return; } dispatch(setCaseSyncLock(true)); @@ -163,9 +171,9 @@ const TrackingComponent: React.FC = ({ children }) => { } if (visitPlanStatus) { dispatch( - setLockData({ - visitPlanStatus, - }) + setLockData({ + visitPlanStatus, + }) ); } dispatch(setCaseSyncLock(false)); @@ -189,18 +197,18 @@ const TrackingComponent: React.FC = ({ children }) => { const stateSetTime = dayJs(stateSetTimestamp); const diffBetweenCurrentTimeAndForegroundTime = - dayJs().diff(foregroundTime, 'seconds') < 0 ? 0 : dayJs().diff(foregroundTime, 'seconds'); + dayJs().diff(foregroundTime, 'seconds') < 0 ? 0 : dayJs().diff(foregroundTime, 'seconds'); const diffBetweenCurrentTimeAndSetStateTime = - dayJs().diff(stateSetTime, 'minutes') < 0 ? 0 : dayJs().diff(stateSetTime, 'minutes'); + 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; + diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_HIGH; const isStateSetTimeWithinMediumRange = - diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_MEDIUM; + diffBetweenCurrentTimeAndSetStateTime < ACTIVITY_TIME_WINDOW_MEDIUM; const isForegroundTimeAfterBackground = dayJs(foregroundTimestamp).isAfter(backgroundTimestamp); if (AppState.currentState === AppStates.ACTIVE) { @@ -228,42 +236,42 @@ const TrackingComponent: React.FC = ({ children }) => { 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; + .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); + }); } - 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); - }); + }) + .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 + FIREBASE_FETCH_TIMESTAMP && + currentTimestamp - FIREBASE_FETCH_TIMESTAMP > 15 * MILLISECONDS_IN_A_MINUTE ) { fetchUpdatedRemoteConfig(); } @@ -333,8 +341,8 @@ const TrackingComponent: React.FC = ({ children }) => { const foregroundTime = dayJs(foregroundTimestamp); const backgroundTime = dayJs(backgroundTimestamp); const diffBetweenBackgroundAndForegroundTime = dayJs(backgroundTime).diff( - foregroundTime, - 'seconds' + foregroundTime, + 'seconds' ); const ACTIVITY_TIME_ON_APP = getActivityTimeOnApp(); @@ -353,6 +361,7 @@ const TrackingComponent: React.FC = ({ children }) => { handleGetCaseSyncStatus(); dispatch(getConfigData()); CosmosForegroundService.start(tasks); + resyncFirebase(); } if (nextAppState === AppStates.BACKGROUND) { await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now); @@ -370,7 +379,7 @@ const TrackingComponent: React.FC = ({ children }) => { } await handleGetCaseSyncStatus(); dispatch(getConfigData()); - if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) { + if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES && !getEnableFirestoreResync()) { const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId); if (updatedDetails?.cases?.length) { dispatch(syncCasesByFallback(updatedDetails)); @@ -393,4 +402,4 @@ const TrackingComponent: React.FC = ({ children }) => { return <>{children}; }; -export default TrackingComponent; +export default TrackingComponent; \ No newline at end of file diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index f8e93e6f..0682bd4f 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -62,6 +62,8 @@ export enum ApiKeys { GET_PERFORMANCE_METRICS = 'GET_PERFORMANCE_METRICS', GET_CASH_COLLECTED = 'GET_CASH_COLLECTED', GET_TELEPHONE_NUMBERS = 'GET_TELEPHONE_NUMBERS', + FIRESTORE_INCONSISTENCY_INFO = 'FIRESTORE_INCONSISTENCY_INFO', + GET_CASE_DETAILS_FROM_API = 'GET_CASE_DETAILS_FROM_API', } export const API_URLS: Record = {} as Record; @@ -108,6 +110,10 @@ API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/user/role-info'; API_URLS[ApiKeys.GET_PERFORMANCE_METRICS] = '/allocation-cycle/agent-performance'; API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/allocation-cycle/cash-collected-split'; API_URLS[ApiKeys.GET_TELEPHONE_NUMBERS] = '/v2/collection-cases/telephones-view/{loanAccountNumber}'; +API_URLS[ApiKeys.FIRESTORE_INCONSISTENCY_INFO] = '/cases/sync-status'; +API_URLS[ApiKeys.GET_CASE_DETAILS_FROM_API] = '/collection-cases/minimal-collection-case-view/{caseId}'; + + export const API_STATUS_CODE = { OK: 200, @@ -134,9 +140,9 @@ export const getErrorMessage = (err: any) => { }; export function getApiUrl( - apiKey: ApiKeys, - params?: Record, - queryParams?: Record + apiKey: ApiKeys, + params?: Record, + queryParams?: Record ) { let apiUrl = API_URLS[apiKey]; @@ -206,68 +212,68 @@ axiosInstance.interceptors.request.use((request) => { }); axiosInstance.interceptors.response.use( - (response) => { - if (response.config.headers) { - const start = response.config.headers['request-start-time']; - const end = Date.now(); - const milliseconds = end - Number(start); - response.headers['request-duration'] = String(milliseconds); - sendApiToClickstreamEvent(response, milliseconds, true); - } - return response; - }, - (error) => { - if (axios.isCancel(error) && GLOBAL.IS_IMPERSONATED) { - toast({ - type: 'error', - text1: ToastMessages.NOT_ALLOWED_ERROR, - }); - } - const { config, response } = error; - alfredHandleSWWEvent(new Error(JSON.stringify(response ?? '{}'))); - logError(error as Error, config?.baseURL + config?.url); - const start = response?.config?.headers['request-start-time']; - const end = Date.now(); - const milliseconds = end - Number(start); - sendApiToClickstreamEvent(response, milliseconds, false); - const donotHandleErrorOnStatusCode = (config.headers.donotHandleErrorOnStatusCode || []).map( - Number - ); - if ( - config?.headers?.donotHandleError || - donotHandleErrorOnStatusCode.includes(error.response.status) - ) { - return Promise.reject(error); - } - if (!config || config.retry <= 1 || !errorsToRetry.includes(error.response.status)) { - const errorString = getErrorMessage(error); - if ( - !config.headers.donotHandleError && - (config.headers?.showInSpecificComponents - ? config.headers.showInSpecificComponents?.includes(getCurrentScreen().name) - : true) - ) { + (response) => { + if (response.config.headers) { + const start = response.config.headers['request-start-time']; + const end = Date.now(); + const milliseconds = end - Number(start); + response.headers['request-duration'] = String(milliseconds); + sendApiToClickstreamEvent(response, milliseconds, true); + } + return response; + }, + (error) => { + if (axios.isCancel(error) && GLOBAL.IS_IMPERSONATED) { toast({ type: 'error', - text1: typeof errorString === 'string' ? errorString : 'Oops! something went wrong', + text1: ToastMessages.NOT_ALLOWED_ERROR, }); } - - if ([API_STATUS_CODE.UNAUTHORIZED, API_STATUS_CODE.FORBIDDEN].includes(response.status)) { - // Reset user info - dispatch(handleLogout()); + const { config, response } = error; + alfredHandleSWWEvent(new Error(JSON.stringify(response ?? '{}'))); + logError(error as Error, config?.baseURL + config?.url); + const start = response?.config?.headers['request-start-time']; + const end = Date.now(); + const milliseconds = end - Number(start); + sendApiToClickstreamEvent(response, milliseconds, false); + const donotHandleErrorOnStatusCode = (config.headers.donotHandleErrorOnStatusCode || []).map( + Number + ); + if ( + config?.headers?.donotHandleError || + donotHandleErrorOnStatusCode.includes(error.response.status) + ) { + return Promise.reject(error); } + if (!config || config.retry <= 1 || !errorsToRetry.includes(error.response.status)) { + const errorString = getErrorMessage(error); + if ( + !config.headers.donotHandleError && + (config.headers?.showInSpecificComponents + ? config.headers.showInSpecificComponents?.includes(getCurrentScreen().name) + : true) + ) { + toast({ + type: 'error', + text1: typeof errorString === 'string' ? errorString : 'Oops! something went wrong', + }); + } - return Promise.reject(error); + if ([API_STATUS_CODE.UNAUTHORIZED, API_STATUS_CODE.FORBIDDEN].includes(response.status)) { + // Reset user info + dispatch(handleLogout()); + } + + return Promise.reject(error); + } + config.retry -= 1; + const delayRetryRequest = new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 500); + }); + return delayRetryRequest.then(() => axiosInstance(config)); } - config.retry -= 1; - const delayRetryRequest = new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 500); - }); - return delayRetryRequest.then(() => axiosInstance(config)); - } ); axiosInstance.defaults.headers.common['Content-Type'] = 'application/json'; @@ -275,9 +281,9 @@ axiosInstance.defaults.baseURL = BASE_AV_APP_URL; // TODO:: Ideally should happen through middlewares. export const registerNavigateAndDispatch = (dispatchParam: Dispatch) => - (dispatch = dispatchParam); + (dispatch = dispatchParam); export const isAxiosError = (err: Error) => { return axios.isAxiosError(err); }; -export default axiosInstance; +export default axiosInstance; \ No newline at end of file diff --git a/src/hooks/useResyncFirebase.ts b/src/hooks/useResyncFirebase.ts new file mode 100644 index 00000000..88f6493f --- /dev/null +++ b/src/hooks/useResyncFirebase.ts @@ -0,0 +1,196 @@ +import firestore from '@react-native-firebase/firestore'; +import {useAppDispatch, useAppSelector} from '@hooks'; +import {MY_CASE_ITEM} from '@reducers/userSlice'; +import store, {type RootState} from '@store'; +import {updateCaseDetailsFirestore} from '@reducers/allCasesSlice'; +import {CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes, SyncedSource} from '@common/Constants'; +import axiosInstance, {ApiKeys, getApiUrl} from '@utils/apiHelper'; +import {getSyncCaseIds} from '@utils/firebaseFallbackUtils'; +import {logError} from '@utils/errorUtils'; +import {addClickstreamEvent} from '@services/clickstreamEventService'; +import {GenericObject} from "@common/GenericTypes"; +import {setLastFirebaseResyncTimestamp} from "@reducers/metadataSlice"; +import {getSyncTime} from "@hooks/capturingApi"; +import dayjs from "dayjs"; +import dayJs from "dayjs"; +import { + getEnableFirestoreResync, + getFirestoreResyncIntervalInMinutes +} from "@common/AgentActivityConfigurableConstants"; +import AsyncStorage from "@react-native-async-storage/async-storage"; + +type CasesToFetchPayload = { + data: { + allocatedCaseIds: string[]; + deallocatedCaseIds: string[]; + updatedCaseIds: string[]; + }; +}; +const useResyncFirebase = () => { + + const { + user: { user, selectedAgent = MY_CASE_ITEM }, + } = useAppSelector((state: RootState) => ({ + user: state.user || {}, + })); + const refId = user?.referenceId || ''; + const dispatch = useAppDispatch(); + + const casesPath = `allocations/${refId}/cases`; + + + + + const _getCaseDetailsFromApi = (caseId: string)=>{ + const getCaseDetailsFromApiUrl = getApiUrl(ApiKeys.GET_CASE_DETAILS_FROM_API, {caseId: caseId}) + + return axiosInstance.get(getCaseDetailsFromApiUrl); + + } + + + const updateCaseInRedux = (updateType: FirestoreUpdateTypes, caseDetails : GenericObject)=>{ + dispatch( + updateCaseDetailsFirestore({ + caseUpdates: [ + { + updateType: updateType, + updatedCaseDetail: caseDetails, + }, + ], + isInitialLoad: false, + isVisitPlanLocked: false, + selectedAgent, + }) + ); + } + + return async (): Promise => { + console.log('firebase resync called'); + const now = dayJs().toString(); + const FIRST_DATE = new Date(1970, 1, 1); + const lastFirebaseResyncTimestamp = await AsyncStorage.getItem('lastFirebaseResyncTimestamp') || dayJs(FIRST_DATE).toString(); + const minutesSinceLastResync = dayJs(now).diff(dayJs(lastFirebaseResyncTimestamp), 'minutes'); + if(!getEnableFirestoreResync()) { + return ; + } + if(minutesSinceLastResync < getFirestoreResyncIntervalInMinutes()){ + return; + } + console.log('firebase resync started'); + + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC_STARTED ) + const getFirestoreInconsistencyUrl = getApiUrl(ApiKeys.FIRESTORE_INCONSISTENCY_INFO); + const casesList = store?.getState()?.allCases?.casesList || []; + const localCases = getSyncCaseIds(casesList); + const casesToFetch: CasesToFetchPayload = await axiosInstance.post( + getFirestoreInconsistencyUrl, + { + agentId: user?.referenceId, + cases: localCases, + }, + ); + + + const allocatedCases = casesToFetch?.data?.allocatedCaseIds; + const unallocatedCases = casesToFetch?.data?.deallocatedCaseIds; + const updatedCases = casesToFetch?.data?.updatedCaseIds; + + allocatedCases.forEach((caseId: string) => { + firestore() + .collection(casesPath) + .doc(caseId.toString()) + .get({ source: 'server' }) + .then((res) => { + const firebaseCase = res?.data() || {}; + dispatch( + updateCaseDetailsFirestore({ + caseUpdates: [ + { + updateType: FirestoreUpdateTypes.ADDED, + updatedCaseDetail: firebaseCase, + }, + ], + isInitialLoad: false, + isVisitPlanLocked: false, + selectedAgent, + }) + ); + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { added: caseId, syncedSource: SyncedSource.FIREBASE }); + }) + .catch((err) => { + logError(err as Error, 'Error fetching cases from firestore'); + void _getCaseDetailsFromApi(caseId).then((res : {data: GenericObject})=>{ + const caseDetails = res?.data ; + updateCaseInRedux(FirestoreUpdateTypes.ADDED, caseDetails); + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { + updated: caseId, + syncedSource: SyncedSource.API + }); + }) + + }); + }); + + unallocatedCases.forEach((caseId: string) => { + dispatch( + updateCaseDetailsFirestore({ + caseUpdates: [ + { + updateType: FirestoreUpdateTypes.REMOVED, + updatedCaseDetail: { + caseType: '', + caseReferenceId: caseId, + id: '', + pinRank: '', + caseViewCreatedAt: '', + }, + }, + ], + isInitialLoad: false, + isVisitPlanLocked: false, + selectedAgent, + }) + ); + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { deleted: caseId }); + }); + + updatedCases.forEach((caseId: string) => { + firestore() + .collection(casesPath) + .doc(caseId.toString()) + .get({ source: 'server' }) + .then((res) => { + const firebaseCase = res?.data() || {}; + console.log('firebase updated case', firebaseCase); + updateCaseInRedux(FirestoreUpdateTypes.MODIFIED, firebaseCase) + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { + updated: caseId, + syncedSource: SyncedSource.FIREBASE + }); + }) + .catch((err) => { + void _getCaseDetailsFromApi(caseId).then((res: {data: GenericObject})=>{ + const caseDetails = res?.data || {}; + updateCaseInRedux(FirestoreUpdateTypes.MODIFIED, caseDetails); + void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { + updated: caseId, + syncedSource: SyncedSource.API + }); + }) + logError(err as Error, 'Error fetching cases from firestore'); + console.log('cases err:', err); + }); + }); + + + dispatch(setLastFirebaseResyncTimestamp(dayjs().toString())) + await AsyncStorage.setItem('lastFirebaseResyncTimestamp', dayjs().toString()); + + + + await Promise.resolve(); + }; +}; + +export default useResyncFirebase; \ No newline at end of file diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index 8c193005..129588fc 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -301,6 +301,7 @@ const allCasesSlice = createSlice({ ...state.casesList[index], caseReferenceId: caseId, pinRank: pinRank || null, + caseViewCreatedAt: caseViewCreatedAt }; } let currentTask = null; diff --git a/src/reducer/metadataSlice.ts b/src/reducer/metadataSlice.ts index d099d428..51a4ffce 100644 --- a/src/reducer/metadataSlice.ts +++ b/src/reducer/metadataSlice.ts @@ -1,5 +1,8 @@ import { createSlice } from '@reduxjs/toolkit'; +import dayJs from 'dayjs'; + +const FIRST_DATE = new Date(1970, 1, 1); export interface UninstallInformation { last_operational_time: any; reinstall_endpoint: string; @@ -9,12 +12,14 @@ interface IMetadata { isOnline: boolean; forceUninstall: Record; isWifiOrCellularOn: boolean; + lastFirebaseResyncTimestamp: number; } const initialState = { isOnline: true, forceUninstall: {}, isWifiOrCellularOn: true, + lastFirebaseResyncTimestamp: dayJs(FIRST_DATE).toString(), } as IMetadata; const MetadataSlice = createSlice({ @@ -30,9 +35,17 @@ const MetadataSlice = createSlice({ setIsWifiOrCellularOn: (state, action) => { state.isWifiOrCellularOn = action.payload; }, + setLastFirebaseResyncTimestamp: (state, action) => { + state.lastFirebaseResyncTimestamp = action.payload; + }, }, }); -export const { setIsOnline, setForceUninstallData, setIsWifiOrCellularOn } = MetadataSlice.actions; +export const { + setIsOnline, + setForceUninstallData, + setIsWifiOrCellularOn, + setLastFirebaseResyncTimestamp, +} = MetadataSlice.actions; -export default MetadataSlice.reducer; +export default MetadataSlice.reducer; \ No newline at end of file diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index 10c51d6b..4beeef61 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -42,316 +42,331 @@ import PDFFullScreen from '../caseDetails/PDFFullScreen'; import ImageViewer from '../caseDetails/ImageViewer'; import NearbyCases from '@screens/allCases/NearbyCases'; import usePolling from "@hooks/usePolling"; +import {getFirestoreResyncIntervalInMinutes} from "@common/AgentActivityConfigurableConstants"; +import useResyncFirebase from "@hooks/useResyncFirebase"; const Stack = createNativeStackNavigator(); export enum PageRouteEnum { - PAYMENTS = 'registerPayments', - ADDRESS_GEO = 'addressGeolocation', - NEW_ADDRESS = 'newAddress', - COLLECTION_CASE_DETAIL = 'collectionCaseDetail', - EMI_SCHEDULE = 'EmiSchedule', - PAST_FEEDBACK_DETAIL = 'pastFeedbackDetail', - ADDITIONAL_ADDRESSES = 'additionalAddresses', - CASH_COLLECTED = 'cashCollected', - DASHBOARD_MAIN = 'dashboardMain', - FILTERED_CASES = 'filteredCases', - NEARBY_CASES = 'nearbyCases', - GEOLOCATION_OLD_FEEDBACKS = 'geolocationOldFeedbacks', + PAYMENTS = 'registerPayments', + ADDRESS_GEO = 'addressGeolocation', + NEW_ADDRESS = 'newAddress', + COLLECTION_CASE_DETAIL = 'collectionCaseDetail', + EMI_SCHEDULE = 'EmiSchedule', + PAST_FEEDBACK_DETAIL = 'pastFeedbackDetail', + ADDITIONAL_ADDRESSES = 'additionalAddresses', + CASH_COLLECTED = 'cashCollected', + DASHBOARD_MAIN = 'dashboardMain', + FILTERED_CASES = 'filteredCases', + NEARBY_CASES = 'nearbyCases', + GEOLOCATION_OLD_FEEDBACKS = 'geolocationOldFeedbacks', } const ProtectedRouter = () => { - const { notificationsWithActions } = useAppSelector((state) => state.notifications); + const { notificationsWithActions } = useAppSelector((state) => state.notifications); - const isOnline = useIsOnline(); - const dispatch = useAppDispatch(); - const [isLoading, setIsLoading] = useState(false); + const isOnline = useIsOnline(); + const dispatch = useAppDispatch(); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - if (isOnline) { - dispatch(getNotifications()); - } - }, [isOnline]); + useEffect(() => { + if (isOnline) { + dispatch(getNotifications()); + } + }, [isOnline]); - useEffect(() => { - if (isOnline) { - setIsLoading(true); - dispatch(getAgentDetail(() => setIsLoading(false))); - } - }, [isOnline]); + useEffect(() => { + if (isOnline) { + setIsLoading(true); + dispatch(getAgentDetail(() => setIsLoading(false))); + } + }, [isOnline]); - // Check the queue of read action notifications in case of offline - useEffect(() => { - if (isOnline && notificationsWithActions.length) { - dispatch(notificationAction(notificationsWithActions)); - } - }, [isOnline, notificationsWithActions]); + // Check the queue of read action notifications in case of offline + useEffect(() => { + if (isOnline && notificationsWithActions.length) { + dispatch(notificationAction(notificationsWithActions)); + } + }, [isOnline, notificationsWithActions]); - const avTemplate = useSelector( - (state: RootState) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE] - ); + const avTemplate = useSelector( + (state: RootState) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE] + ); - const collectionTemplate = useSelector( - (state: RootState) => state.case.templateData[CaseAllocationType.COLLECTION_CASE] - ); + const collectionTemplate = useSelector( + (state: RootState) => state.case.templateData[CaseAllocationType.COLLECTION_CASE] + ); - // This checks for unsubmitted feedbacks in case of offline and retry submitting them - interactionsHandler(); + // This checks for unsubmitted feedbacks in case of offline and retry submitting them + interactionsHandler(); - // Firestore listener hook - useFirestoreUpdates(); - // + // Firestore listener hook + useFirestoreUpdates(); - usePolling(() => { - CaptureGeolocation.watchLocation((location: DeviceLocation) => { - return dispatch(setDeviceGeolocation(location)); - }); - }, 3* MILLISECONDS_IN_A_MINUTE); + const resyncFirebase = useResyncFirebase(); - if (isLoading) return ; + const stopLocationPolling = usePolling(() => { + CaptureGeolocation.watchLocation((location: DeviceLocation) => { + return dispatch(setDeviceGeolocation(location)); + }); + }, 3* MILLISECONDS_IN_A_MINUTE); - return ( - - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'slide_from_right', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - null, - animationDuration: SCREEN_ANIMATION_DURATION, - animation: 'none', - }} - listeners={getScreenFocusListenerObj} - /> - {_map(avTemplate?.widget, (key) => ( - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - ))} - {_map(collectionTemplate?.widget, (key) => ( - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - ))} - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> - - ); + const stopFirebaseResyncPolling = usePolling(() => { + void resyncFirebase(); + + }, getFirestoreResyncIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE); + + useEffect(() => { + return () => { + stopLocationPolling(); + stopFirebaseResyncPolling(); + }; + }, []); + + if (isLoading) return ; + + return ( + + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'slide_from_right', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + /> + {_map(avTemplate?.widget, (key) => ( + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + ))} + {_map(collectionTemplate?.widget, (key) => ( + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + ))} + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> + + ); }; -export default ProtectedRouter; +export default ProtectedRouter; \ No newline at end of file diff --git a/src/services/firebaseFetchAndUpdate.service.ts b/src/services/firebaseFetchAndUpdate.service.ts index eddb8706..c4238181 100644 --- a/src/services/firebaseFetchAndUpdate.service.ts +++ b/src/services/firebaseFetchAndUpdate.service.ts @@ -3,6 +3,8 @@ import { setActivityTimeOnApp, setActivityTimeWindowHigh, setActivityTimeWindowMedium, + setEnableFirestoreResync, + setFirestoreResyncIntervalInMinutes, } from '../common/AgentActivityConfigurableConstants'; import { setBlacklistedAppsList } from './blacklistedApps.service'; @@ -11,32 +13,40 @@ export let FIREBASE_FETCH_TIMESTAMP: number; async function fetchUpdatedRemoteConfig() { await remoteConfig().fetch(FIREBASE_FETCH_TIME); //15 minutes remoteConfig() - .activate() - .then((fetchedRemotely) => { - if (fetchedRemotely) { - console.log('Configs were fetched.'); - } else { - console.log('No configs were fetched.'); - } - }) - .catch((error) => { - console.error(error); - }) - .finally(() => { - const ACTIVITY_TIME_ON_APP = remoteConfig().getValue('ACTIVITY_TIME_ON_APP').asNumber(); - const ACTIVITY_TIME_WINDOW_HIGH = remoteConfig() - .getValue('ACTIVITY_TIME_WINDOW_HIGH') - .asNumber(); - const ACTIVITY_TIME_WINDOW_MEDIUM = remoteConfig() - .getValue('ACTIVITY_TIME_WINDOW_MEDIUM') - .asNumber(); - const BLACKLISTED_APPS = remoteConfig().getValue('BLACKLISTED_APPS').asString(); - setActivityTimeOnApp(ACTIVITY_TIME_ON_APP); - setActivityTimeWindowHigh(ACTIVITY_TIME_WINDOW_HIGH); - setActivityTimeWindowMedium(ACTIVITY_TIME_WINDOW_MEDIUM); - setBlacklistedAppsList(BLACKLISTED_APPS); - FIREBASE_FETCH_TIMESTAMP = Date.now(); - }); + .activate() + .then((fetchedRemotely) => { + if (fetchedRemotely) { + console.log('Configs were fetched.'); + } else { + console.log('No configs were fetched.'); + } + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + const ACTIVITY_TIME_ON_APP = remoteConfig().getValue('ACTIVITY_TIME_ON_APP').asNumber(); + const ACTIVITY_TIME_WINDOW_HIGH = remoteConfig() + .getValue('ACTIVITY_TIME_WINDOW_HIGH') + .asNumber(); + const ACTIVITY_TIME_WINDOW_MEDIUM = remoteConfig() + .getValue('ACTIVITY_TIME_WINDOW_MEDIUM') + .asNumber(); + const BLACKLISTED_APPS = remoteConfig().getValue('BLACKLISTED_APPS').asString(); + const ENABLE_FIRESTORE_RESYNC = remoteConfig() + .getValue('ENABLE_FIRESTORE_RESYNC') + .asBoolean(); + const FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = remoteConfig() + .getValue('FIRESTORE_RESYNC_INTERVAL_IN_MINUTES') + .asNumber(); + setActivityTimeOnApp(ACTIVITY_TIME_ON_APP); + setActivityTimeWindowHigh(ACTIVITY_TIME_WINDOW_HIGH); + setActivityTimeWindowMedium(ACTIVITY_TIME_WINDOW_MEDIUM); + setBlacklistedAppsList(BLACKLISTED_APPS); + setEnableFirestoreResync(ENABLE_FIRESTORE_RESYNC); + setFirestoreResyncIntervalInMinutes(FIRESTORE_RESYNC_INTERVAL_IN_MINUTES); + FIREBASE_FETCH_TIMESTAMP = Date.now(); + }); } -export default fetchUpdatedRemoteConfig; +export default fetchUpdatedRemoteConfig; \ No newline at end of file diff --git a/src/store/store.ts b/src/store/store.ts index 4f605efa..f48483b6 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -93,9 +93,8 @@ const persistedReducer = persistReducer(persistConfig, rootReducer); const store = configureStore({ reducer: persistedReducer, middleware: getDefaultMiddleware({ - serializableCheck: { - ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], - }, + serializableCheck: false, + immutableCheck: false, }), });