diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js index 908c0a8db8a1..10f12bac71d0 100644 --- a/src/components/TestToolMenu.js +++ b/src/components/TestToolMenu.js @@ -47,10 +47,18 @@ const TestToolMenu = props => ( /> + {/* When toggled the app will be forced offline. */} + + Network.setShouldForceOffline(!props.network.shouldForceOffline)} + /> + + {/* When toggled all network requests will fail. */} Network.setShouldFailAllRequests(!props.network.shouldFailAllRequests)} /> diff --git a/src/components/networkPropTypes.js b/src/components/networkPropTypes.js index ecfcc3b601cb..40cbf990d81f 100644 --- a/src/components/networkPropTypes.js +++ b/src/components/networkPropTypes.js @@ -4,6 +4,9 @@ export default PropTypes.shape({ /** Is the network currently offline or not */ isOffline: PropTypes.bool, + /** Should the network be forced offline */ + shouldForceOffline: PropTypes.bool, + /** Whether we should fail all network requests */ shouldFailAllRequests: PropTypes.bool, }); diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index 4c8fe2ee6b30..60971fe1b0b3 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -13,9 +13,16 @@ Onyx.connect({ }); let shouldFailAllRequests = false; +let shouldForceOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, - callback: val => shouldFailAllRequests = (val && _.isBoolean(val.shouldFailAllRequests)) ? val.shouldFailAllRequests : false, + callback: (network) => { + if (!network) { + return; + } + shouldFailAllRequests = Boolean(network.shouldFailAllRequests); + shouldForceOffline = Boolean(network.shouldForceOffline); + }, }); // We use the AbortController API to terminate pending request in `cancelPendingRequests` @@ -40,7 +47,7 @@ function processHTTPRequest(url, method = 'get', body = null, canCancel = true) }) .then((response) => { // Test mode where all requests will succeed in the server, but fail to return a response - if (shouldFailAllRequests) { + if (shouldFailAllRequests || shouldForceOffline) { throw new HttpsError({ message: CONST.ERROR.FAILED_TO_FETCH, }); diff --git a/src/libs/Network/NetworkStore.js b/src/libs/Network/NetworkStore.js index 67b57f966974..6d4a9dd8856f 100644 --- a/src/libs/Network/NetworkStore.js +++ b/src/libs/Network/NetworkStore.js @@ -80,7 +80,7 @@ Onyx.connect({ triggerReconnectCallback(); } - offline = network.isOffline; + offline = Boolean(network.shouldForceOffline) || network.isOffline; }, }); diff --git a/src/libs/NetworkConnection.js b/src/libs/NetworkConnection.js index 972c3dcd5179..d6c7932f2a47 100644 --- a/src/libs/NetworkConnection.js +++ b/src/libs/NetworkConnection.js @@ -1,10 +1,12 @@ import _ from 'underscore'; +import Onyx from 'react-native-onyx'; import NetInfo from '@react-native-community/netinfo'; import AppStateMonitor from './AppStateMonitor'; import Log from './Log'; import * as NetworkActions from './actions/Network'; import CONFIG from '../CONFIG'; import CONST from '../CONST'; +import ONYXKEYS from '../ONYXKEYS'; let isOffline = false; let hasPendingNetworkCheck = false; @@ -39,6 +41,29 @@ function setOfflineStatus(isCurrentlyOffline) { isOffline = isCurrentlyOffline; } +// Update the offline status in response to changes in shouldForceOffline +let shouldForceOffline = false; +Onyx.connect({ + key: ONYXKEYS.NETWORK, + callback: (network) => { + if (!network) { + return; + } + const currentShouldForceOffline = Boolean(network.shouldForceOffline); + if (currentShouldForceOffline === shouldForceOffline) { + return; + } + shouldForceOffline = currentShouldForceOffline; + if (shouldForceOffline) { + setOfflineStatus(true); + } else { + // If we are no longer forcing offline fetch the NetInfo to set isOffline appropriately + NetInfo.fetch() + .then(state => setOfflineStatus(state.isInternetReachable === false)); + } + }, +}); + /** * Set up the event listener for NetInfo to tell whether the user has * internet connectivity or not. This is more reliable than the Pusher @@ -65,6 +90,10 @@ function subscribeToNetInfo() { // whether a user has internet connectivity or not. NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, state); + if (shouldForceOffline) { + Log.info('[NetworkConnection] Not setting offline status because shouldForceOffline = true'); + return; + } setOfflineStatus(state.isInternetReachable === false); }); } diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index 5fd1e7fa6d26..2483a0c5e3f3 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -1,8 +1,21 @@ +import Onyx from 'react-native-onyx'; import _ from 'underscore'; +import ONYXKEYS from '../../ONYXKEYS'; import Pusher from './library'; import TYPE from './EventType'; import Log from '../Log'; +let shouldForceOffline = false; +Onyx.connect({ + key: ONYXKEYS.NETWORK, + callback: (network) => { + if (!network) { + return; + } + shouldForceOffline = Boolean(network.shouldForceOffline); + }, +}); + let socket; const socketEventCallbacks = []; let customAuthorizer; @@ -112,6 +125,11 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { const chunkedDataEvents = {}; const callback = (eventData) => { + if (shouldForceOffline) { + Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true'); + return; + } + let data; try { data = _.isObject(eventData) ? eventData : JSON.parse(eventData); diff --git a/src/libs/actions/Network.js b/src/libs/actions/Network.js index 54d732c96e9a..43f6303d20db 100644 --- a/src/libs/actions/Network.js +++ b/src/libs/actions/Network.js @@ -8,6 +8,14 @@ function setIsOffline(isOffline) { Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } +/** + * + * @param {Boolean} shouldForceOffline + */ +function setShouldForceOffline(shouldForceOffline) { + Onyx.merge(ONYXKEYS.NETWORK, {shouldForceOffline}); +} + /** * Test tool that will fail all network requests when enabled * @param {Boolean} shouldFailAllRequests @@ -18,5 +26,6 @@ function setShouldFailAllRequests(shouldFailAllRequests) { export { setIsOffline, + setShouldForceOffline, setShouldFailAllRequests, }; diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js index d221ba62f6be..9dc63952b5dd 100644 --- a/src/libs/actions/SignInRedirect.js +++ b/src/libs/actions/SignInRedirect.js @@ -19,9 +19,16 @@ Onyx.connect({ }); let currentIsOffline; +let currentShouldForceOffline; Onyx.connect({ key: ONYXKEYS.NETWORK, - callback: val => currentIsOffline = val.isOffline, + callback: (network) => { + if (!network) { + return; + } + currentIsOffline = network.isOffline; + currentShouldForceOffline = Boolean(network.shouldForceOffline); + }, }); /** @@ -31,6 +38,7 @@ function clearStorageAndRedirect(errorMessage) { const activeClients = currentActiveClients; const preferredLocale = currentPreferredLocale; const isOffline = currentIsOffline; + const shouldForceOffline = currentShouldForceOffline; // Clearing storage discards the authToken. This causes a redirect to the SignIn screen Onyx.clear() @@ -41,7 +49,10 @@ function clearStorageAndRedirect(errorMessage) { if (activeClients && activeClients.length > 0) { Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients); } - if (isOffline) { + + // After signing out, set ourselves as offline if we were offline before logging out and we are not forcing it. + // If we are forcing offline, ignore it while signed out, otherwise it would require a refresh because there's no way to toggle the switch to go back online while signed out. + if (isOffline && !shouldForceOffline) { Onyx.set(ONYXKEYS.NETWORK, {isOffline}); }