feat(offline) Re add offline status notification

Summary:
This commit rewrites the offline status notification from scratch, using
the `is-online` module (https://github.com/sindresorhus/is-online). The
react component no longer manages all of the state internally, but
rather depends on a separate OnlineStatusStore that manages the online state
for that component.

The new online status system will:

- Check online status every 30 seconds
- If status switches to offline:
  - Show notification
  - Recheck online status using exponential backoff
  - Notification will show remaining seconds until next online status
  check (like slack) (upon initial inspection this seemed to have no cpu problems.)
- If status switches to online
  - Hide notification
  - Revert to checking online status every 30 seconds

Depends on D3919

Test Plan: manual

Reviewers: spang, mark, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D3920
This commit is contained in:
Juan Tejada 2017-02-15 00:31:27 -08:00
parent 532e096a21
commit a011d12b74
6 changed files with 159 additions and 1 deletions

View file

@ -0,0 +1,42 @@
import {OnlineStatusStore, React, Actions} from 'nylas-exports';
import {Notification, ListensToFluxStore} from 'nylas-component-kit';
function OfflineNotification({isOnline, retryingInSeconds}) {
if (isOnline) {
return false
}
const subtitle = retryingInSeconds ?
`Retrying in ${retryingInSeconds} second${retryingInSeconds > 1 ? 's' : ''}` :
`Retrying now...`;
return (
<Notification
className="offline"
title="Nylas Mail is offline"
subtitle={subtitle}
priority="5"
icon="volstead-offline.png"
actions={[{
id: 'try_now',
label: 'Try now',
fn: () => Actions.checkOnlineStatus(),
}]}
/>
)
}
OfflineNotification.displayName = 'OfflineNotification'
OfflineNotification.propTypes = {
isOnline: React.PropTypes.bool,
retryingInSeconds: React.PropTypes.number,
}
export default ListensToFluxStore(OfflineNotification, {
stores: [OnlineStatusStore],
getStateFromStores() {
return {
isOnline: OnlineStatusStore.isOnline(),
retryingInSeconds: OnlineStatusStore.retryingInSeconds(),
}
},
})

View file

@ -9,6 +9,7 @@ import DefaultClientNotification from "./items/default-client-notif";
import UnstableChannelNotification from "./items/unstable-channel-notif";
import DevModeNotification from "./items/dev-mode-notif";
import DisabledMailRulesNotification from "./items/disabled-mail-rules-notif";
import OfflineNotification from "./items/offline-notification";
import UpdateNotification from "./items/update-notification";
const notifications = [
@ -17,6 +18,7 @@ const notifications = [
UnstableChannelNotification,
DevModeNotification,
DisabledMailRulesNotification,
OfflineNotification,
UpdateNotification,
]

View file

@ -45,10 +45,11 @@
"guid": "0.0.10",
"imap-provider-settings": "nylas/imap-provider-settings",
"immutable": "3.7.5",
"is-online": "6.1.0",
"jasmine-json": "~0.0",
"jasmine-react-helpers": "^0.2",
"jasmine-tagged": "^1.1.2",
"jasmine-reporters": "1.x.x",
"jasmine-tagged": "^1.1.2",
"jsx-transform": "^2.3.0",
"juice": "^1.4",
"kbpgp": "^2.0.52",

View file

@ -114,6 +114,7 @@ class Actions {
static longPollProcessedDeltas = ActionScopeWorkWindow;
static willMakeAPIRequest = ActionScopeWorkWindow;
static didMakeAPIRequest = ActionScopeWorkWindow;
static checkOnlineStatus = ActionScopeWindow;
static wakeLocalSyncWorkerForAccount = ActionScopeGlobal;

View file

@ -0,0 +1,111 @@
import isOnline from 'is-online'
import NylasStore from 'nylas-store'
import Actions from '../actions'
import {ExponentialBackoffScheduler} from '../../services/backoff-schedulers'
const CHECK_ONLINE_INTERVAL = 30 * 1000
class OnlineStatusStore extends NylasStore {
constructor() {
super()
this._isOnline = true
this._retryingInSeconds = 0
this._countdownInterval = null
this._checkOnlineTimeout = null
this._backoffScheduler = new ExponentialBackoffScheduler({jitter: false})
this.setupEmitter()
if (NylasEnv.isMainWindow()) {
Actions.checkOnlineStatus.listen(() => this._checkOnlineStatus())
this._checkOnlineStatus()
}
}
isOnline() {
return this._isOnline
}
retryingInSeconds() {
return this._retryingInSeconds
}
async _setNextOnlineState() {
const nextIsOnline = await isOnline()
if (this._isOnline !== nextIsOnline) {
this._isOnline = nextIsOnline
this.trigger()
}
}
async _checkOnlineStatus() {
this._clearCheckOnlineInterval()
this._clearRetryCountdown()
// If we are currently offline, this trigger will show the `Retrying now...`
// message
this._retryingInSeconds = 0
this.trigger()
await this._setNextOnlineState()
if (!this._isOnline) {
this._checkOnlineStatusAfterBackoff()
} else {
this._backoffScheduler.reset()
this._checkOnlineTimeout = setTimeout(() => {
this._checkOnlineStatus()
}, CHECK_ONLINE_INTERVAL)
}
}
async _checkOnlineStatusAfterBackoff() {
const nextDelayMs = this._backoffScheduler.nextDelay()
try {
await this._countdownRetryingInSeconds(nextDelayMs)
this._checkOnlineStatus()
} catch (err) {
// This means the retry countdown was cleared, in which case we don't
// want to do anything
}
}
async _countdownRetryingInSeconds(nextDelayMs) {
this._retryingInSeconds = Math.ceil(nextDelayMs / 1000)
this.trigger()
return new Promise((resolve, reject) => {
this._clearRetryCountdown()
this._emitter.once('clear-retry-countdown', () => reject(new Error('Retry countdown cleared')))
this._countdownInterval = setInterval(() => {
this._retryingInSeconds = Math.max(0, this._retryingInSeconds - 1)
this.trigger()
if (this._retryingInSeconds === 0) {
this._clearCountdownInterval()
resolve()
}
}, 1000)
})
}
_clearCheckOnlineInterval() {
clearInterval(this._checkOnlineTimeout)
this._checkOnlineTimeout = null
}
_clearCountdownInterval() {
clearInterval(this._countdownInterval)
this._countdownInterval = null
}
_clearRetryCountdown() {
this._clearCountdownInterval()
this._emitter.emit('clear-retry-countdown')
}
}
export default new OnlineStatusStore()

View file

@ -157,6 +157,7 @@ lazyLoadAndRegisterStore(`SendActionsStore`, 'send-actions-store');
lazyLoadAndRegisterStore(`FeatureUsageStore`, 'feature-usage-store');
lazyLoadAndRegisterStore(`ThreadCountsStore`, 'thread-counts-store');
lazyLoadAndRegisterStore(`FileDownloadStore`, 'file-download-store');
lazyLoadAndRegisterStore(`OnlineStatusStore`, 'online-status-store');
lazyLoadAndRegisterStore(`UpdateChannelStore`, 'update-channel-store');
lazyLoadAndRegisterStore(`PreferencesUIStore`, 'preferences-ui-store');
lazyLoadAndRegisterStore(`FocusedContentStore`, 'focused-content-store');