From 463c0d493f0c5bb33c1dbca2d09058d8c2dbad62 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:36:48 -0800 Subject: [PATCH 1/8] Create actions/handlers to trigger API Checks for network connectivity --- app/src/UI/Header/Header.tsx | 2 +- app/src/constants/misc.ts | 2 ++ .../state/actions/network/NetworkActions.ts | 3 +- app/src/state/reducers/network.ts | 2 +- app/src/state/sagas/network.ts | 29 ++++++++++++++++++- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/src/UI/Header/Header.tsx b/app/src/UI/Header/Header.tsx index db5f7a9f4..1314e289e 100644 --- a/app/src/UI/Header/Header.tsx +++ b/app/src/UI/Header/Header.tsx @@ -377,7 +377,7 @@ const LoginOrOutMemo = React.memo(() => { const NetworkStateControl: React.FC = () => { const handleNetworkStateChange = () => { - dispatch(connected ? NetworkActions.offline() : NetworkActions.online()); + dispatch(connected ? NetworkActions.offline() : NetworkActions.checkMobileNetworkStatus()); }; const { connected } = useSelector((state) => state.Network); const dispatch = useDispatch(); diff --git a/app/src/constants/misc.ts b/app/src/constants/misc.ts index bd8e38583..0bd5efb6a 100644 --- a/app/src/constants/misc.ts +++ b/app/src/constants/misc.ts @@ -11,3 +11,5 @@ export const MediumDateTimeFormat = 'MMMM D, YYYY, H:mm a'; //January 5, 2020, 3 export const LongDateFormat = 'dddd, MMMM D, YYYY, H:mm a'; //Monday, January 5, 2020, 3:30 pm export const LongDateTimeFormat = 'dddd, MMMM D, YYYY, H:mm a'; //Monday, January 5, 2020, 3:30 pm + +export const HEALTH_ENDPOINT = '/api/misc/version'; diff --git a/app/src/state/actions/network/NetworkActions.ts b/app/src/state/actions/network/NetworkActions.ts index 582d59fe1..f8d07f651 100644 --- a/app/src/state/actions/network/NetworkActions.ts +++ b/app/src/state/actions/network/NetworkActions.ts @@ -2,8 +2,9 @@ import { createAction } from '@reduxjs/toolkit'; class NetworkActions { private static readonly PREFIX = 'NetworkActions'; - static readonly online = createAction(`${this.PREFIX}/online`); static readonly offline = createAction(`${this.PREFIX}/offline`); + static readonly checkMobileNetworkStatus = createAction(`${this.PREFIX}/checkMobileNetworkStatus`); + static readonly userLostConnection = createAction(`${this.PREFIX}/userLostConnection`); } export default NetworkActions; diff --git a/app/src/state/reducers/network.ts b/app/src/state/reducers/network.ts index ed2521638..f9268e7ba 100644 --- a/app/src/state/reducers/network.ts +++ b/app/src/state/reducers/network.ts @@ -15,7 +15,7 @@ function createNetworkReducer(initialStatus: Network) { return createNextState(state, (draftState: Draft) => { if (NetworkActions.online.match(action)) { draftState.connected = true; - } else if (NetworkActions.offline.match(action)) { + } else if (NetworkActions.offline.match(action) || NetworkActions.userLostConnection.match(action)) { draftState.connected = false; } }); diff --git a/app/src/state/sagas/network.ts b/app/src/state/sagas/network.ts index 8f7132147..7e2aeb62b 100644 --- a/app/src/state/sagas/network.ts +++ b/app/src/state/sagas/network.ts @@ -1,7 +1,11 @@ import networkAlertMessages from 'constants/alerts/networkAlerts'; +import { HEALTH_ENDPOINT } from 'constants/misc'; import { all, put, select, takeEvery } from 'redux-saga/effects'; import Alerts from 'state/actions/alerts/Alerts'; import NetworkActions from 'state/actions/network/NetworkActions'; +import { MOBILE } from 'state/build-time-config'; +import { selectConfiguration } from 'state/reducers/configuration'; +import { selectNetworkConnected } from 'state/reducers/network'; import { OfflineActivitySyncState, selectOfflineActivity } from 'state/reducers/offlineActivity'; function* handle_NETWORK_GO_OFFLINE() { @@ -20,10 +24,33 @@ function* handle_NETWORK_GO_ONLINE() { } } +function* handle_CHECK_MOBILE_NETWORK_STATUS() { + if (!MOBILE) { + return; + } + const currentOnlineStatus = yield select(selectNetworkConnected); + const configuration = yield select(selectConfiguration); + + const networkCheckPassed = yield fetch(configuration.API_BASE + HEALTH_ENDPOINT) + .then((res) => res.status === 200) + .catch(() => false); + + if (!networkCheckPassed && !currentOnlineStatus) { + yield put(Alerts.create(networkAlertMessages.attemptToReconnectFailed)); + } else if (!networkCheckPassed) { + yield put(NetworkActions.userLostConnection()); + yield put(Alerts.create(networkAlertMessages.userLostConnection)); + } else if (networkCheckPassed && !currentOnlineStatus) { + // Only fire online event if we are not already online + yield put(NetworkActions.online()); + } +} + function* networkSaga() { yield all([ takeEvery(NetworkActions.offline, handle_NETWORK_GO_OFFLINE), - takeEvery(NetworkActions.online, handle_NETWORK_GO_ONLINE) + takeEvery(NetworkActions.online, handle_NETWORK_GO_ONLINE), + takeEvery(NetworkActions.checkMobileNetworkStatus, handle_CHECK_MOBILE_NETWORK_STATUS) ]); } From ad0c77448cca97dee5f2ff3aaa8fc41ca9d3d2b9 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:09:46 -0800 Subject: [PATCH 2/8] Check network status at app load --- app/src/state/store.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/state/store.ts b/app/src/state/store.ts index f11f88de1..f987cf1f8 100644 --- a/app/src/state/store.ts +++ b/app/src/state/store.ts @@ -1,4 +1,4 @@ -import { configureStore, ThunkDispatch } from '@reduxjs/toolkit'; +import { configureStore } from '@reduxjs/toolkit'; import createSagaMiddleware from 'redux-saga'; import { createLogger } from 'redux-logger'; import { createBrowserHistory } from 'history'; @@ -19,6 +19,7 @@ import userSettingsSaga from './sagas/userSettings'; import { createSagaCrashHandler } from './sagas/error_handler'; import { AppConfig } from './config'; import { DEBUG } from './build-time-config'; +import NetworkActions from './actions/network/NetworkActions'; const historySingleton = createBrowserHistory(); @@ -79,6 +80,7 @@ export function setupStore(configuration: AppConfig) { sagaMiddleware.run(emailTemplatesSaga); sagaMiddleware.run(networkSaga); + store.dispatch(NetworkActions.checkMobileNetworkStatus()); store.dispatch({ type: AUTH_INITIALIZE_REQUEST }); historySingleton.listen((location) => { From da52b2c85f1c23063806af3d2831948dfb3e2bb6 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:42:43 -0800 Subject: [PATCH 3/8] update NetworkActions.ts --- app/src/state/actions/network/NetworkActions.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/state/actions/network/NetworkActions.ts b/app/src/state/actions/network/NetworkActions.ts index f8d07f651..360567b6b 100644 --- a/app/src/state/actions/network/NetworkActions.ts +++ b/app/src/state/actions/network/NetworkActions.ts @@ -4,7 +4,13 @@ class NetworkActions { private static readonly PREFIX = 'NetworkActions'; static readonly online = createAction(`${this.PREFIX}/online`); static readonly offline = createAction(`${this.PREFIX}/offline`); - static readonly checkMobileNetworkStatus = createAction(`${this.PREFIX}/checkMobileNetworkStatus`); + static readonly checkMobileNetworkStatus = createAction( + `${this.PREFIX}/checkMobileNetworkStatus`, + (cancel: boolean = false) => ({ payload: cancel }) + ); static readonly userLostConnection = createAction(`${this.PREFIX}/userLostConnection`); + static readonly attemptToReconnectFailed = createAction(`${this.PREFIX}/attemptToReconnectFailed`); + static readonly automaticReconnectFailed = createAction(`${this.PREFIX}/automaticReconnectFailed`); + static readonly checkInitConnection = createAction(`${this.PREFIX}/checkInitConnection`); } export default NetworkActions; From 98a3e2f83bf251bc8c766cabd40a09bdeb880a03 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:43:53 -0800 Subject: [PATCH 4/8] Simplify alertsAndPrompts handling of new entries, add new Alert --- app/src/constants/alerts/networkAlerts.ts | 6 ++++++ app/src/state/reducers/alertsAndPrompts.ts | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/constants/alerts/networkAlerts.ts b/app/src/constants/alerts/networkAlerts.ts index 33d96089d..cea80956a 100644 --- a/app/src/constants/alerts/networkAlerts.ts +++ b/app/src/constants/alerts/networkAlerts.ts @@ -30,6 +30,12 @@ const networkAlertMessages: Record = { severity: AlertSeverity.Error, subject: AlertSubjects.Network, autoClose: 10 + }, + automaticReconnectFailed: { + content: 'We were unable to bring you back online. Please try again later.', + severity: AlertSeverity.Error, + subject: AlertSubjects.Network, + autoClose: 10 } }; diff --git a/app/src/state/reducers/alertsAndPrompts.ts b/app/src/state/reducers/alertsAndPrompts.ts index 38ce9e25b..6d7acf03e 100644 --- a/app/src/state/reducers/alertsAndPrompts.ts +++ b/app/src/state/reducers/alertsAndPrompts.ts @@ -6,6 +6,8 @@ import Prompt from 'state/actions/prompts/Prompt'; import { PromptAction } from 'interfaces/prompt-interfaces'; import RecordCache from 'state/actions/cache/RecordCache'; import cacheAlertMessages from 'constants/alerts/cacheAlerts'; +import NetworkActions from 'state/actions/network/NetworkActions'; +import networkAlertMessages from 'constants/alerts/networkAlerts'; interface AlertsAndPromptsState { alerts: AlertMessage[]; @@ -20,6 +22,12 @@ const initialState: AlertsAndPromptsState = { const filterDuplicates = (key: keyof AlertMessage, matchValue: any, state: AlertMessage[]): any[] => state.filter((entry) => entry[key] !== matchValue); +const addAlert = (state: AlertMessage[], alert: AlertMessage): AlertMessage[] => [...state, { ...alert, id: nanoid() }]; +const addPrompt = (state: PromptAction[], prompt: PromptAction): PromptAction[] => [ + ...state, + { ...prompt, id: nanoid() } +]; + export function createAlertsAndPromptsReducer( configuration: AppConfig ): (AlertsAndPromptsState, AnyAction) => AlertsAndPromptsState { @@ -43,15 +51,15 @@ export function createAlertsAndPromptsReducer( draftState.prompts = []; } else if (RegExp(Prompt.NEW_PROMPT).exec(action.type)) { const newPrompt: PromptAction = action.payload; - draftState.prompts = [...state.prompts, { ...newPrompt, id: nanoid() }]; + draftState.prompts = addPrompt(state.prompts, newPrompt); } else if (RecordCache.requestCaching.fulfilled.match(action)) { - draftState.alerts = [...state.alerts, { ...cacheAlertMessages.recordsetCacheSuccess, id: nanoid() }]; + draftState.alerts = addAlert(state.alerts, cacheAlertMessages.recordsetCacheSuccess); } else if (RecordCache.requestCaching.rejected.match(action)) { - draftState.alerts = [...state.alerts, { ...cacheAlertMessages.recordsetCacheFailed, id: nanoid() }]; + draftState.alerts = addAlert(state.alerts, cacheAlertMessages.recordsetCacheFailed); } else if (RecordCache.deleteCache.rejected.match(action)) { - draftState.alerts = [...state.alerts, { ...cacheAlertMessages.recordsetDeleteCacheFailed, id: nanoid() }]; + draftState.alerts = addAlert(state.alerts, cacheAlertMessages.recordsetDeleteCacheFailed); } else if (RecordCache.deleteCache.fulfilled.match(action)) { - draftState.alerts = [...state.alerts, { ...cacheAlertMessages.recordsetDeleteCacheSuccess, id: nanoid() }]; + draftState.alerts = addAlert(state.alerts, cacheAlertMessages.recordsetDeleteCacheSuccess); } }); }; From 0c93e6cbc1f8d42c0403dad3de4bbe614facab16 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:45:07 -0800 Subject: [PATCH 5/8] Add workflows for monitoring network connections --- app/src/state/sagas/network.ts | 132 ++++++++++++++++++++++++++++----- app/src/state/store.ts | 2 +- 2 files changed, 116 insertions(+), 18 deletions(-) diff --git a/app/src/state/sagas/network.ts b/app/src/state/sagas/network.ts index 7e2aeb62b..90f8501ae 100644 --- a/app/src/state/sagas/network.ts +++ b/app/src/state/sagas/network.ts @@ -1,6 +1,7 @@ +import { PayloadAction } from '@reduxjs/toolkit'; +import { all, cancelled, delay, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; import networkAlertMessages from 'constants/alerts/networkAlerts'; import { HEALTH_ENDPOINT } from 'constants/misc'; -import { all, put, select, takeEvery } from 'redux-saga/effects'; import Alerts from 'state/actions/alerts/Alerts'; import NetworkActions from 'state/actions/network/NetworkActions'; import { MOBILE } from 'state/build-time-config'; @@ -8,10 +9,95 @@ import { selectConfiguration } from 'state/reducers/configuration'; import { selectNetworkConnected } from 'state/reducers/network'; import { OfflineActivitySyncState, selectOfflineActivity } from 'state/reducers/offlineActivity'; +function* handle_ATTEMPT_TO_RECONNECT_FAILED() { + yield put(Alerts.create(networkAlertMessages.attemptToReconnectFailed)); +} + +function* handle_AUTOMATIC_RECONNECT_FAILED() { + yield put(Alerts.create(networkAlertMessages.automaticReconnectFailed)); +} + +/** + * @desc Rolling function that targets the API to determine our online status. + * On failure to reach the API, attempts up to 5 times before determining we cannot proceed + * In event of this, disconnect the user and alert them of the incident. + */ +function* handle_CHECK_MOBILE_NETWORK_STATUS(cancel: PayloadAction) { + if (!MOBILE || cancel.payload) { + return; + } + const MAX_ATTEMPTS = 5; + const SECONDS_BETWEEN_CHECKS = 20; + const SECONDS_BETWEEN_ATTEMPTS = 5; + + while (true) { + const currentOnlineStatus = yield select(selectNetworkConnected); + const configuration = yield select(selectConfiguration); + let attempts: number = 0; + let networkCheckPassed: boolean = false; + + do { + networkCheckPassed = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); + if (!networkCheckPassed) { + attempts++; + yield delay(SECONDS_BETWEEN_ATTEMPTS * 1000); + } + + if (yield cancelled()) { + return; + } + } while (!networkCheckPassed && attempts < MAX_ATTEMPTS); + + if (!networkCheckPassed && !currentOnlineStatus) { + yield put(NetworkActions.attemptToReconnectFailed()); + } else if (!networkCheckPassed) { + yield put(NetworkActions.userLostConnection()); + return; + } else if (networkCheckPassed && !currentOnlineStatus) { + // Only fire online event if we are not already online + yield put(NetworkActions.online()); + } + yield delay(SECONDS_BETWEEN_CHECKS * 1000); + } +} + +/** + * @desc Targets the API and checks for successful response. + * @param url Path to API Health check + * @returns Connection to API Succeeded + */ +const canConnectToNetwork = async (url: string): Promise => { + return await fetch(url) + .then((res) => res.status === 200) + .catch(() => false); +}; + +/** + * Initial Network Connectivity Check, determines to begin application online or offline + */ +function* handle_CHECK_INIT_CONNECTION() { + if (!MOBILE) { + return; + } + const configuration = yield select(selectConfiguration); + if (yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT)) { + yield put(NetworkActions.online()); + } else { + yield put(NetworkActions.offline()); + } +} +/** + * @desc Handler for User manually going offline. Fires a cancellation event to stop rolling api checks. + */ function* handle_NETWORK_GO_OFFLINE() { yield put(Alerts.create(networkAlertMessages.userWentOffline)); + yield put(NetworkActions.checkMobileNetworkStatus(true)); } +/** + * @desc When user comes online, check for any existing unsychronized Activities. + * Restart the rolling Network status checks. + */ function* handle_NETWORK_GO_ONLINE() { const { serializedActivities } = yield select(selectOfflineActivity); const userHasUnsynchronizedActivities = Object.keys(serializedActivities).some( @@ -22,35 +108,47 @@ function* handle_NETWORK_GO_ONLINE() { } else { yield put(Alerts.create(networkAlertMessages.userWentOnline)); } + yield put(NetworkActions.checkMobileNetworkStatus()); } -function* handle_CHECK_MOBILE_NETWORK_STATUS() { - if (!MOBILE) { - return; - } - const currentOnlineStatus = yield select(selectNetworkConnected); +/** + * @desc Attempt to establish connection with the API. Abandons after ~3 minutes of disconnection. + * When this event fires, it cancels the rolling API checks + */ +function* handle_USER_LOST_CONNECTION() { + const MAX_RECONNECT_ATTEMPTS = 18; + const SECONDS_BETWEEN_ATTEMPTS = 10; + const configuration = yield select(selectConfiguration); + let attempts: number = 0; + let networkCheckPassed: boolean = false; - const networkCheckPassed = yield fetch(configuration.API_BASE + HEALTH_ENDPOINT) - .then((res) => res.status === 200) - .catch(() => false); + yield put(Alerts.create(networkAlertMessages.userLostConnection)); - if (!networkCheckPassed && !currentOnlineStatus) { - yield put(Alerts.create(networkAlertMessages.attemptToReconnectFailed)); - } else if (!networkCheckPassed) { - yield put(NetworkActions.userLostConnection()); - yield put(Alerts.create(networkAlertMessages.userLostConnection)); - } else if (networkCheckPassed && !currentOnlineStatus) { - // Only fire online event if we are not already online + do { + networkCheckPassed = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); + if (!networkCheckPassed) { + attempts++; + yield delay(SECONDS_BETWEEN_ATTEMPTS * 1000); + } + } while (!networkCheckPassed && attempts < MAX_RECONNECT_ATTEMPTS); + + if (networkCheckPassed) { yield put(NetworkActions.online()); + } else { + yield put(NetworkActions.automaticReconnectFailed()); } } function* networkSaga() { yield all([ + takeEvery(NetworkActions.attemptToReconnectFailed, handle_ATTEMPT_TO_RECONNECT_FAILED), + takeEvery(NetworkActions.automaticReconnectFailed, handle_AUTOMATIC_RECONNECT_FAILED), + takeEvery(NetworkActions.checkInitConnection, handle_CHECK_INIT_CONNECTION), + takeLatest(NetworkActions.checkMobileNetworkStatus, handle_CHECK_MOBILE_NETWORK_STATUS), takeEvery(NetworkActions.offline, handle_NETWORK_GO_OFFLINE), takeEvery(NetworkActions.online, handle_NETWORK_GO_ONLINE), - takeEvery(NetworkActions.checkMobileNetworkStatus, handle_CHECK_MOBILE_NETWORK_STATUS) + takeLatest(NetworkActions.userLostConnection, handle_USER_LOST_CONNECTION) ]); } diff --git a/app/src/state/store.ts b/app/src/state/store.ts index f987cf1f8..27418a3a1 100644 --- a/app/src/state/store.ts +++ b/app/src/state/store.ts @@ -80,7 +80,7 @@ export function setupStore(configuration: AppConfig) { sagaMiddleware.run(emailTemplatesSaga); sagaMiddleware.run(networkSaga); - store.dispatch(NetworkActions.checkMobileNetworkStatus()); + store.dispatch(NetworkActions.checkInitConnection()); store.dispatch({ type: AUTH_INITIALIZE_REQUEST }); historySingleton.listen((location) => { From 484461979d3a25bd44387d7c97d9cb742582e0a3 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:04:52 -0800 Subject: [PATCH 6/8] Add manualReconnect path, simplify the rest --- app/src/UI/Header/Header.tsx | 2 +- .../state/actions/network/NetworkActions.ts | 2 +- app/src/state/sagas/network.ts | 50 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/UI/Header/Header.tsx b/app/src/UI/Header/Header.tsx index 1314e289e..cd28bfdc4 100644 --- a/app/src/UI/Header/Header.tsx +++ b/app/src/UI/Header/Header.tsx @@ -377,7 +377,7 @@ const LoginOrOutMemo = React.memo(() => { const NetworkStateControl: React.FC = () => { const handleNetworkStateChange = () => { - dispatch(connected ? NetworkActions.offline() : NetworkActions.checkMobileNetworkStatus()); + dispatch(connected ? NetworkActions.offline() : NetworkActions.manualReconnect()); }; const { connected } = useSelector((state) => state.Network); const dispatch = useDispatch(); diff --git a/app/src/state/actions/network/NetworkActions.ts b/app/src/state/actions/network/NetworkActions.ts index 360567b6b..d1ff7fa51 100644 --- a/app/src/state/actions/network/NetworkActions.ts +++ b/app/src/state/actions/network/NetworkActions.ts @@ -9,7 +9,7 @@ class NetworkActions { (cancel: boolean = false) => ({ payload: cancel }) ); static readonly userLostConnection = createAction(`${this.PREFIX}/userLostConnection`); - static readonly attemptToReconnectFailed = createAction(`${this.PREFIX}/attemptToReconnectFailed`); + static readonly manualReconnect = createAction(`${this.PREFIX}/manualReconnect`); static readonly automaticReconnectFailed = createAction(`${this.PREFIX}/automaticReconnectFailed`); static readonly checkInitConnection = createAction(`${this.PREFIX}/checkInitConnection`); } diff --git a/app/src/state/sagas/network.ts b/app/src/state/sagas/network.ts index 90f8501ae..4b3e8360e 100644 --- a/app/src/state/sagas/network.ts +++ b/app/src/state/sagas/network.ts @@ -6,13 +6,19 @@ import Alerts from 'state/actions/alerts/Alerts'; import NetworkActions from 'state/actions/network/NetworkActions'; import { MOBILE } from 'state/build-time-config'; import { selectConfiguration } from 'state/reducers/configuration'; -import { selectNetworkConnected } from 'state/reducers/network'; import { OfflineActivitySyncState, selectOfflineActivity } from 'state/reducers/offlineActivity'; -function* handle_ATTEMPT_TO_RECONNECT_FAILED() { - yield put(Alerts.create(networkAlertMessages.attemptToReconnectFailed)); +/** + * @desc Handler for a Manual Reconnect attempt by user + */ +function* handle_MANUAL_RECONNECT() { + const configuration = yield select(selectConfiguration); + if (yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT)) { + yield put(NetworkActions.online()); + } else { + yield put(Alerts.create(networkAlertMessages.attemptToReconnectFailed)); + } } - function* handle_AUTOMATIC_RECONNECT_FAILED() { yield put(Alerts.create(networkAlertMessages.automaticReconnectFailed)); } @@ -31,14 +37,13 @@ function* handle_CHECK_MOBILE_NETWORK_STATUS(cancel: PayloadAction) { const SECONDS_BETWEEN_ATTEMPTS = 5; while (true) { - const currentOnlineStatus = yield select(selectNetworkConnected); const configuration = yield select(selectConfiguration); let attempts: number = 0; - let networkCheckPassed: boolean = false; + let canConnect: boolean = false; do { - networkCheckPassed = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); - if (!networkCheckPassed) { + canConnect = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); + if (!canConnect) { attempts++; yield delay(SECONDS_BETWEEN_ATTEMPTS * 1000); } @@ -46,16 +51,11 @@ function* handle_CHECK_MOBILE_NETWORK_STATUS(cancel: PayloadAction) { if (yield cancelled()) { return; } - } while (!networkCheckPassed && attempts < MAX_ATTEMPTS); + } while (!canConnect && attempts < MAX_ATTEMPTS); - if (!networkCheckPassed && !currentOnlineStatus) { - yield put(NetworkActions.attemptToReconnectFailed()); - } else if (!networkCheckPassed) { + if (!canConnect) { yield put(NetworkActions.userLostConnection()); return; - } else if (networkCheckPassed && !currentOnlineStatus) { - // Only fire online event if we are not already online - yield put(NetworkActions.online()); } yield delay(SECONDS_BETWEEN_CHECKS * 1000); } @@ -68,7 +68,7 @@ function* handle_CHECK_MOBILE_NETWORK_STATUS(cancel: PayloadAction) { */ const canConnectToNetwork = async (url: string): Promise => { return await fetch(url) - .then((res) => res.status === 200) + .then((res) => res.ok) .catch(() => false); }; @@ -115,25 +115,25 @@ function* handle_NETWORK_GO_ONLINE() { * @desc Attempt to establish connection with the API. Abandons after ~3 minutes of disconnection. * When this event fires, it cancels the rolling API checks */ -function* handle_USER_LOST_CONNECTION() { +function* handle_ATTEMPT_AUTOMATIC_RECONNECT() { const MAX_RECONNECT_ATTEMPTS = 18; const SECONDS_BETWEEN_ATTEMPTS = 10; const configuration = yield select(selectConfiguration); - let attempts: number = 0; - let networkCheckPassed: boolean = false; + let attempts = 0; + let canReconnect: boolean; yield put(Alerts.create(networkAlertMessages.userLostConnection)); do { - networkCheckPassed = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); - if (!networkCheckPassed) { + canReconnect = yield canConnectToNetwork(configuration.API_BASE + HEALTH_ENDPOINT); + if (!canReconnect) { attempts++; yield delay(SECONDS_BETWEEN_ATTEMPTS * 1000); } - } while (!networkCheckPassed && attempts < MAX_RECONNECT_ATTEMPTS); + } while (!canReconnect && attempts < MAX_RECONNECT_ATTEMPTS); - if (networkCheckPassed) { + if (canReconnect) { yield put(NetworkActions.online()); } else { yield put(NetworkActions.automaticReconnectFailed()); @@ -142,13 +142,13 @@ function* handle_USER_LOST_CONNECTION() { function* networkSaga() { yield all([ - takeEvery(NetworkActions.attemptToReconnectFailed, handle_ATTEMPT_TO_RECONNECT_FAILED), + takeEvery(NetworkActions.manualReconnect, handle_MANUAL_RECONNECT), takeEvery(NetworkActions.automaticReconnectFailed, handle_AUTOMATIC_RECONNECT_FAILED), takeEvery(NetworkActions.checkInitConnection, handle_CHECK_INIT_CONNECTION), takeLatest(NetworkActions.checkMobileNetworkStatus, handle_CHECK_MOBILE_NETWORK_STATUS), takeEvery(NetworkActions.offline, handle_NETWORK_GO_OFFLINE), takeEvery(NetworkActions.online, handle_NETWORK_GO_ONLINE), - takeLatest(NetworkActions.userLostConnection, handle_USER_LOST_CONNECTION) + takeLatest(NetworkActions.userLostConnection, handle_ATTEMPT_AUTOMATIC_RECONNECT) ]); } From cdb73f7fc64c01407079bb34128f84b762df9bcf Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:14:47 -0800 Subject: [PATCH 7/8] Cleanup unused imports --- app/src/state/reducers/alertsAndPrompts.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/state/reducers/alertsAndPrompts.ts b/app/src/state/reducers/alertsAndPrompts.ts index 6d7acf03e..666fec052 100644 --- a/app/src/state/reducers/alertsAndPrompts.ts +++ b/app/src/state/reducers/alertsAndPrompts.ts @@ -6,8 +6,6 @@ import Prompt from 'state/actions/prompts/Prompt'; import { PromptAction } from 'interfaces/prompt-interfaces'; import RecordCache from 'state/actions/cache/RecordCache'; import cacheAlertMessages from 'constants/alerts/cacheAlerts'; -import NetworkActions from 'state/actions/network/NetworkActions'; -import networkAlertMessages from 'constants/alerts/networkAlerts'; interface AlertsAndPromptsState { alerts: AlertMessage[]; From 36327e456a6072c87c96a43740cdd7e6c527b2df Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:36:42 -0800 Subject: [PATCH 8/8] Move configuration yield out of while loop --- app/src/state/sagas/network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/state/sagas/network.ts b/app/src/state/sagas/network.ts index 4b3e8360e..d9fab94a4 100644 --- a/app/src/state/sagas/network.ts +++ b/app/src/state/sagas/network.ts @@ -35,9 +35,9 @@ function* handle_CHECK_MOBILE_NETWORK_STATUS(cancel: PayloadAction) { const MAX_ATTEMPTS = 5; const SECONDS_BETWEEN_CHECKS = 20; const SECONDS_BETWEEN_ATTEMPTS = 5; + const configuration = yield select(selectConfiguration); while (true) { - const configuration = yield select(selectConfiguration); let attempts: number = 0; let canConnect: boolean = false;