diff --git a/src/CONST.js b/src/CONST.js index 779a6edd1a6f..0d0e151adfc8 100644 --- a/src/CONST.js +++ b/src/CONST.js @@ -78,6 +78,11 @@ const CONST = { TIMEZONE: 'timeZone', }, DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, + APP_STATE: { + ACTIVE: 'active', + BACKGROUND: 'background', + INACTIVE: 'inactive', + }, // at least 8 characters, 1 capital letter, 1 lowercase number, 1 number PASSWORD_COMPLEXITY_REGEX_STRING: '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}$', diff --git a/src/libs/AppStateMonitor/index.js b/src/libs/AppStateMonitor/index.js new file mode 100644 index 000000000000..63f2f448ac35 --- /dev/null +++ b/src/libs/AppStateMonitor/index.js @@ -0,0 +1,35 @@ +import {AppState} from 'react-native'; +import CONST from '../../CONST'; +import shouldReportActivity from './shouldReportActivity'; + +let appState = CONST.APP_STATE.ACTIVE; + +/** + * Listener that will only fire the callback when the user has become active. + * + * @param {Function} callback + * @returns {Function} to unsubscribe + */ +function addBecameActiveListener(callback) { + /** + * @param {String} state + */ + function appStateChangeCallback(state) { + if ( + shouldReportActivity + && (appState === CONST.APP_STATE.INACTIVE || appState === CONST.APP_STATE.BACKGROUND) + && state === CONST.APP_STATE.ACTIVE + ) { + callback(); + } + appState = state; + } + AppState.addEventListener('change', appStateChangeCallback); + return () => { + AppState.removeEventListener('change', appStateChangeCallback); + }; +} + +export default { + addBecameActiveListener, +}; diff --git a/src/libs/AppStateMonitor/shouldReportActivity/index.js b/src/libs/AppStateMonitor/shouldReportActivity/index.js new file mode 100644 index 000000000000..05939ab7eb8d --- /dev/null +++ b/src/libs/AppStateMonitor/shouldReportActivity/index.js @@ -0,0 +1,4 @@ +// We only need to report when the app becomes active on native since web maintains most of it's network functions while +// in the "background" and the concept is not quite the same on mobile. We avoid setting this to true for web since +// the event would fire much more frequently than it does on native causing performance issues. +export default false; diff --git a/src/libs/AppStateMonitor/shouldReportActivity/index.native.js b/src/libs/AppStateMonitor/shouldReportActivity/index.native.js new file mode 100644 index 000000000000..ff3177babdde --- /dev/null +++ b/src/libs/AppStateMonitor/shouldReportActivity/index.native.js @@ -0,0 +1 @@ +export default true; diff --git a/src/libs/NetworkConnection.js b/src/libs/NetworkConnection.js index d37559dcb5cf..f98512acf8b8 100644 --- a/src/libs/NetworkConnection.js +++ b/src/libs/NetworkConnection.js @@ -1,16 +1,16 @@ import _ from 'underscore'; -import {AppState} from 'react-native'; import Onyx from 'react-native-onyx'; import NetInfo from './NetInfo'; import ONYXKEYS from '../ONYXKEYS'; +import SleepTimer from './SleepTimer'; +import AppStateMonitor from './AppStateMonitor'; // NetInfo.addEventListener() returns a function used to unsubscribe the // listener so we must create a reference to it and call it in stopListeningForReconnect() let unsubscribeFromNetInfo; -let sleepTimer; -let lastTime; +let unsubscribeFromSleepTimer; +let unsubscribeFromAppState; let isOffline = false; -let listeningForAppStateChanges = false; let logInfo = () => {}; // Holds all of the callbacks that need to be triggered when the network reconnects @@ -42,10 +42,6 @@ function setOfflineStatus(isCurrentlyOffline) { isOffline = isCurrentlyOffline; } -function logAppStateChange(state) { - logInfo('[NetworkConnection] AppState change event', true, {state}); -} - /** * Set up the event listener for NetInfo to tell whether the user has * internet connectivity or not. This is more reliable than the Pusher @@ -54,10 +50,9 @@ function logAppStateChange(state) { function listenForReconnect() { logInfo('[NetworkConnection] listenForReconnect called', true); - if (!listeningForAppStateChanges) { - AppState.addEventListener('change', logAppStateChange); - listeningForAppStateChanges = true; - } + unsubscribeFromAppState = AppStateMonitor.addBecameActiveListener(() => { + triggerReconnectionCallbacks('app became active'); + }); // Subscribe to the state change event via NetInfo so we can update // whether a user has internet connectivity or not. @@ -68,18 +63,11 @@ function listenForReconnect() { // When a device is put to sleep, NetInfo is not always able to detect // when connectivity has been lost. As a failsafe we will capture the time - // every two seconds and if the last time recorded is greater than 4 seconds + // every two seconds and if the last time recorded goes past a threshold // we know that the computer has been asleep. - lastTime = (new Date()).getTime(); - clearInterval(sleepTimer); - sleepTimer = setInterval(() => { - const currentTime = (new Date()).getTime(); - const isSkewed = currentTime > (lastTime + 4000); - if (isSkewed) { - triggerReconnectionCallbacks('sleep timer clock skewed'); - } - lastTime = currentTime; - }, 2000); + unsubscribeFromSleepTimer = SleepTimer.addClockSkewListener(() => ( + triggerReconnectionCallbacks('timer clock skewed') + )); } /** @@ -87,15 +75,17 @@ function listenForReconnect() { */ function stopListeningForReconnect() { logInfo('[NetworkConnection] stopListeningForReconnect called', true); - clearInterval(sleepTimer); - sleepTimer = null; if (unsubscribeFromNetInfo) { unsubscribeFromNetInfo(); unsubscribeFromNetInfo = undefined; } - if (listeningForAppStateChanges) { - AppState.removeEventListener('change', logAppStateChange); - listeningForAppStateChanges = false; + if (unsubscribeFromSleepTimer) { + unsubscribeFromSleepTimer(); + unsubscribeFromSleepTimer = undefined; + } + if (unsubscribeFromAppState) { + unsubscribeFromAppState(); + unsubscribeFromAppState = undefined; } } diff --git a/src/libs/SleepTimer/index.js b/src/libs/SleepTimer/index.js new file mode 100644 index 000000000000..75c722304e2f --- /dev/null +++ b/src/libs/SleepTimer/index.js @@ -0,0 +1,37 @@ +/** + * The Timer module is used on web/desktop to detect when a computer has gone to sleep. We don't use this on native + * mobile since it does not work reliably and fires at inappropriate times. + */ +let sleepTimer; +let lastTime; + +/** + * Adds a listener for detecting when laptop screens have closed or desktop computers put to sleep. Not reliable on + * native platforms. + * + * @param {Function} onClockSkewCallback function to call when the + * @returns {Fuction} that when called clears the timer + */ +function addClockSkewListener(onClockSkewCallback) { + clearInterval(sleepTimer); + sleepTimer = setInterval(() => { + const currentTime = (new Date()).getTime(); + const isSkewed = currentTime > (lastTime + 8000); + lastTime = currentTime; + + if (!isSkewed) { + return; + } + + onClockSkewCallback(); + }, 2000); + + return () => { + clearInterval(sleepTimer); + sleepTimer = null; + }; +} + +export default { + addClockSkewListener, +}; diff --git a/src/libs/SleepTimer/index.native.js b/src/libs/SleepTimer/index.native.js new file mode 100644 index 000000000000..96dcb1988f45 --- /dev/null +++ b/src/libs/SleepTimer/index.native.js @@ -0,0 +1,8 @@ +/** + * We don't want the clock skew listener to run on native as it only helps us on desktop/web when a laptop is closed + * and reopened. This method of detecting timing variance to see if we are inactive doesn't work well on native mobile + * platforms. These platforms should use AppState instead to determine whether they must catch up on missing data. + */ +export default { + addClockSkewListener: () => () => {}, +};