mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-29 16:06:31 +08:00
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:
parent
532e096a21
commit
a011d12b74
6 changed files with 159 additions and 1 deletions
|
@ -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(),
|
||||
}
|
||||
},
|
||||
})
|
|
@ -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,
|
||||
]
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -114,6 +114,7 @@ class Actions {
|
|||
static longPollProcessedDeltas = ActionScopeWorkWindow;
|
||||
static willMakeAPIRequest = ActionScopeWorkWindow;
|
||||
static didMakeAPIRequest = ActionScopeWorkWindow;
|
||||
static checkOnlineStatus = ActionScopeWindow;
|
||||
|
||||
|
||||
static wakeLocalSyncWorkerForAccount = ActionScopeGlobal;
|
||||
|
|
111
src/flux/stores/online-status-store.es6
Normal file
111
src/flux/stores/online-status-store.es6
Normal 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()
|
|
@ -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');
|
||||
|
|
Loading…
Add table
Reference in a new issue