mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-11-10 00:11:34 +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 UnstableChannelNotification from "./items/unstable-channel-notif";
|
||||||
import DevModeNotification from "./items/dev-mode-notif";
|
import DevModeNotification from "./items/dev-mode-notif";
|
||||||
import DisabledMailRulesNotification from "./items/disabled-mail-rules-notif";
|
import DisabledMailRulesNotification from "./items/disabled-mail-rules-notif";
|
||||||
|
import OfflineNotification from "./items/offline-notification";
|
||||||
import UpdateNotification from "./items/update-notification";
|
import UpdateNotification from "./items/update-notification";
|
||||||
|
|
||||||
const notifications = [
|
const notifications = [
|
||||||
|
|
@ -17,6 +18,7 @@ const notifications = [
|
||||||
UnstableChannelNotification,
|
UnstableChannelNotification,
|
||||||
DevModeNotification,
|
DevModeNotification,
|
||||||
DisabledMailRulesNotification,
|
DisabledMailRulesNotification,
|
||||||
|
OfflineNotification,
|
||||||
UpdateNotification,
|
UpdateNotification,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,11 @@
|
||||||
"guid": "0.0.10",
|
"guid": "0.0.10",
|
||||||
"imap-provider-settings": "nylas/imap-provider-settings",
|
"imap-provider-settings": "nylas/imap-provider-settings",
|
||||||
"immutable": "3.7.5",
|
"immutable": "3.7.5",
|
||||||
|
"is-online": "6.1.0",
|
||||||
"jasmine-json": "~0.0",
|
"jasmine-json": "~0.0",
|
||||||
"jasmine-react-helpers": "^0.2",
|
"jasmine-react-helpers": "^0.2",
|
||||||
"jasmine-tagged": "^1.1.2",
|
|
||||||
"jasmine-reporters": "1.x.x",
|
"jasmine-reporters": "1.x.x",
|
||||||
|
"jasmine-tagged": "^1.1.2",
|
||||||
"jsx-transform": "^2.3.0",
|
"jsx-transform": "^2.3.0",
|
||||||
"juice": "^1.4",
|
"juice": "^1.4",
|
||||||
"kbpgp": "^2.0.52",
|
"kbpgp": "^2.0.52",
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ class Actions {
|
||||||
static longPollProcessedDeltas = ActionScopeWorkWindow;
|
static longPollProcessedDeltas = ActionScopeWorkWindow;
|
||||||
static willMakeAPIRequest = ActionScopeWorkWindow;
|
static willMakeAPIRequest = ActionScopeWorkWindow;
|
||||||
static didMakeAPIRequest = ActionScopeWorkWindow;
|
static didMakeAPIRequest = ActionScopeWorkWindow;
|
||||||
|
static checkOnlineStatus = ActionScopeWindow;
|
||||||
|
|
||||||
|
|
||||||
static wakeLocalSyncWorkerForAccount = ActionScopeGlobal;
|
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(`FeatureUsageStore`, 'feature-usage-store');
|
||||||
lazyLoadAndRegisterStore(`ThreadCountsStore`, 'thread-counts-store');
|
lazyLoadAndRegisterStore(`ThreadCountsStore`, 'thread-counts-store');
|
||||||
lazyLoadAndRegisterStore(`FileDownloadStore`, 'file-download-store');
|
lazyLoadAndRegisterStore(`FileDownloadStore`, 'file-download-store');
|
||||||
|
lazyLoadAndRegisterStore(`OnlineStatusStore`, 'online-status-store');
|
||||||
lazyLoadAndRegisterStore(`UpdateChannelStore`, 'update-channel-store');
|
lazyLoadAndRegisterStore(`UpdateChannelStore`, 'update-channel-store');
|
||||||
lazyLoadAndRegisterStore(`PreferencesUIStore`, 'preferences-ui-store');
|
lazyLoadAndRegisterStore(`PreferencesUIStore`, 'preferences-ui-store');
|
||||||
lazyLoadAndRegisterStore(`FocusedContentStore`, 'focused-content-store');
|
lazyLoadAndRegisterStore(`FocusedContentStore`, 'focused-content-store');
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue