diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/components/Account.js | 60 | ||||
| -rw-r--r-- | app/containers/AccountPage.js | 10 | ||||
| -rw-r--r-- | app/lib/app-visibility.js | 28 | ||||
| -rw-r--r-- | app/lib/backend.js | 19 | ||||
| -rw-r--r-- | app/main.js | 58 | ||||
| -rw-r--r-- | app/redux/account/actions.js | 20 | ||||
| -rw-r--r-- | app/redux/account/reducers.js | 7 | ||||
| -rw-r--r-- | app/redux/store.js | 24 |
8 files changed, 178 insertions, 48 deletions
diff --git a/app/components/Account.js b/app/components/Account.js index b4eef86712..8639d0fe42 100644 --- a/app/components/Account.js +++ b/app/components/Account.js @@ -1,28 +1,60 @@ // @flow import moment from 'moment'; -import React from 'react'; +import * as React from 'react'; import { Component, Text, View } from 'reactxp'; import { Button, RedButton, GreenButton, Label } from './styled'; import { Layout, Container } from './Layout'; import styles from './AccountStyles'; import Img from './Img'; import { formatAccount } from '../lib/formatters'; +import AppVisiblityObserver from '../lib/app-visibility'; -import type { AccountReduxState } from '../redux/account/reducers'; +import type { AccountToken } from '../lib/ipc-facade'; export type AccountProps = { - account: AccountReduxState, + accountToken: AccountToken, + accountExpiry: string, + updateAccountExpiry: () => Promise<void>, onLogout: () => void, onClose: () => void, onBuyMore: () => void, }; -export default class Account extends Component { - props: AccountProps; +export type AccountState = { + isRefreshingExpiry: boolean, +}; + +export default class Account extends Component<AccountProps, AccountState> { + state = { + isRefreshingExpiry: false, + }; + + _appVisibilityObserver: ?AppVisiblityObserver; + + _isMounted = false; + + componentDidMount() { + this._isMounted = true; + this._refreshAccountExpiry(); + + this._appVisibilityObserver = new AppVisiblityObserver((isVisible) => { + if (isVisible) { + this._refreshAccountExpiry(); + } + }); + } + + componentWillUnmount() { + this._isMounted = false; + + if (this._appVisibilityObserver) { + this._appVisibilityObserver.dispose(); + } + } render() { - const expiry = moment(this.props.account.expiry); - const formattedAccountToken = formatAccount(this.props.account.accountToken || ''); + const expiry = moment(this.props.accountExpiry); + const formattedAccountToken = formatAccount(this.props.accountToken || ''); const formattedExpiry = expiry.format('hA, D MMMM YYYY').toUpperCase(); const isOutOfTime = expiry.isSameOrBefore(moment()); @@ -81,4 +113,18 @@ export default class Account extends Component { </Layout> ); } + + async _refreshAccountExpiry() { + this.setState({ isRefreshingExpiry: true }); + + try { + await this.props.updateAccountExpiry(); + } catch (e) { + // TODO: Report the error to user + } + + if (this._isMounted) { + this.setState({ isRefreshingExpiry: false }); + } + } } diff --git a/app/containers/AccountPage.js b/app/containers/AccountPage.js index 210ad4aaeb..1c54167b50 100644 --- a/app/containers/AccountPage.js +++ b/app/containers/AccountPage.js @@ -11,13 +11,19 @@ import { openLink } from '../lib/platform'; import type { ReduxState, ReduxDispatch } from '../redux/store'; import type { SharedRouteProps } from '../routes'; -const mapStateToProps = (state: ReduxState) => state; +const mapStateToProps = (state: ReduxState) => ({ + accountToken: state.account.accountToken, + accountExpiry: state.account.expiry, +}); const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => { + const { backend } = props; const { push: pushHistory } = bindActionCreators({ push }, dispatch); const { logout } = bindActionCreators(accountActions, dispatch); + return { + updateAccountExpiry: () => backend.updateAccountExpiry(), onLogout: () => { - logout(props.backend); + logout(backend); }, onClose: () => { pushHistory('/settings'); diff --git a/app/lib/app-visibility.js b/app/lib/app-visibility.js new file mode 100644 index 0000000000..d599f6941b --- /dev/null +++ b/app/lib/app-visibility.js @@ -0,0 +1,28 @@ +// @flow +import { ipcRenderer } from 'electron'; + +type EventHandler = (boolean) => any; + +export default class AppVisiblityObserver { + _handler: EventHandler; + + constructor(handler: EventHandler) { + this._handler = handler; + + ipcRenderer.on('show-window', this._handleShowEvent).on('hide-window', this._handleHideEvent); + } + + dispose() { + ipcRenderer + .removeListener('show-window', this._handleShowEvent) + .removeListener('hide-window', this._handleHideEvent); + } + + _handleShowEvent = (_event) => { + this._handler(true); + }; + + _handleHideEvent = (_event) => { + this._handler(false); + }; +} diff --git a/app/lib/backend.js b/app/lib/backend.js index b043a9b10e..dfe4fdd2c3 100644 --- a/app/lib/backend.js +++ b/app/lib/backend.js @@ -365,6 +365,25 @@ export class Backend { } } + async updateAccountExpiry() { + const ipc = this._ipc; + const store = this._store; + + try { + await this._ensureAuthenticated(); + + const accountToken = await this._ipc.getAccount(); + if (!accountToken) { + throw new BackendError('NO_ACCOUNT'); + } + + const accountData = await ipc.getAccountData(accountToken); + store.dispatch(accountActions.updateAccountExpiry(accountData.expiry)); + } catch (e) { + log.error(`Failed to update account expiry: ${e.message}`); + } + } + async removeAccountFromHistory(accountToken: AccountToken) { try { await this._ensureAuthenticated(); diff --git a/app/main.js b/app/main.js index 2ac7f0a8d1..b6fefc519e 100644 --- a/app/main.js +++ b/app/main.js @@ -110,7 +110,8 @@ const ApplicationMain = { tray.on('click', () => windowController.toggle()); - this._registerIpcEvents(); + this._registerWindowIpcEvents(window); + this._registerIpcListeners(); this._setAppMenu(); this._addContextMenu(window); @@ -140,7 +141,13 @@ const ApplicationMain = { window.loadFile('build/index.html'); }, - _registerIpcEvents() { + _registerWindowIpcEvents(window: BrowserWindow) { + // Notify renderer when window visibility changes. + window.on('show', () => window.webContents.send('show-window')); + window.on('hide', () => window.webContents.send('hide-window')); + }, + + _registerIpcListeners() { ipcMain.on('on-browser-window-ready', () => { this._pollConnectionInfoFile(); }); @@ -406,32 +413,35 @@ const ApplicationMain = { ]; // add inspect element on right click menu - window.webContents.on('context-menu', (_e: Event, props: { x: number, y: number }) => { - let inspectTemplate = [ - { - label: 'Inspect element', - click() { - window.openDevTools({ mode: 'detach' }); - window.inspectElement(props.x, props.y); + window.webContents.on( + 'context-menu', + (_e: Event, props: { x: number, y: number, isEditable: boolean }) => { + let inspectTemplate = [ + { + label: 'Inspect element', + click() { + window.openDevTools({ mode: 'detach' }); + window.inspectElement(props.x, props.y); + }, }, - }, - ]; + ]; - if (props.isEditable) { - let inputMenu = menuTemplate; + if (props.isEditable) { + let inputMenu = menuTemplate; - // mixin 'inspect element' into standard menu when in development mode - if (process.env.NODE_ENV === 'development') { - inputMenu = menuTemplate.concat([{ type: 'separator' }], inspectTemplate); - } + // mixin 'inspect element' into standard menu when in development mode + if (process.env.NODE_ENV === 'development') { + inputMenu = menuTemplate.concat([{ type: 'separator' }], inspectTemplate); + } - Menu.buildFromTemplate(inputMenu).popup(window); - } else if (process.env.NODE_ENV === 'development') { - // display inspect element for all non-editable - // elements when in development mode - Menu.buildFromTemplate(inspectTemplate).popup(window); - } - }); + Menu.buildFromTemplate(inputMenu).popup(window); + } else if (process.env.NODE_ENV === 'development') { + // display inspect element for all non-editable + // elements when in development mode + Menu.buildFromTemplate(inspectTemplate).popup(window); + } + }, + ); }, _createTray(): Tray { diff --git a/app/redux/account/actions.js b/app/redux/account/actions.js index ca2bbdc7b6..fdb9f44281 100644 --- a/app/redux/account/actions.js +++ b/app/redux/account/actions.js @@ -10,7 +10,7 @@ type StartLoginAction = { type LoginSuccessfulAction = { type: 'LOGIN_SUCCESSFUL', - expiry: string, + expiry?: string, }; type LoginFailedAction = { @@ -36,6 +36,11 @@ type UpdateAccountHistoryAction = { accountHistory: Array<AccountToken>, }; +type UpdateAccountExpiryAction = { + type: 'UPDATE_ACCOUNT_EXPIRY', + expiry: string, +}; + export type AccountAction = | StartLoginAction | LoginSuccessfulAction @@ -43,7 +48,8 @@ export type AccountAction = | LoggedOutAction | ResetLoginErrorAction | UpdateAccountTokenAction - | UpdateAccountHistoryAction; + | UpdateAccountHistoryAction + | UpdateAccountExpiryAction; function startLogin(accountToken?: AccountToken): StartLoginAction { return { @@ -55,7 +61,7 @@ function startLogin(accountToken?: AccountToken): StartLoginAction { function loginSuccessful(expiry: string): LoginSuccessfulAction { return { type: 'LOGIN_SUCCESSFUL', - expiry: expiry, + expiry, }; } @@ -96,6 +102,13 @@ function updateAccountHistory(accountHistory: Array<AccountToken>): UpdateAccoun }; } +function updateAccountExpiry(expiry: string): UpdateAccountExpiryAction { + return { + type: 'UPDATE_ACCOUNT_EXPIRY', + expiry, + }; +} + const login = (backend: Backend, account: string) => () => backend.login(account); const logout = (backend: Backend) => () => backend.logout(); @@ -110,4 +123,5 @@ export default { resetLoginError, updateAccountToken, updateAccountHistory, + updateAccountExpiry, }; diff --git a/app/redux/account/reducers.js b/app/redux/account/reducers.js index fe10dce742..f3485028d4 100644 --- a/app/redux/account/reducers.js +++ b/app/redux/account/reducers.js @@ -85,6 +85,13 @@ export default function( accountHistory: action.accountHistory, }, }; + case 'UPDATE_ACCOUNT_EXPIRY': + return { + ...state, + ...{ + expiry: action.expiry, + }, + }; } return state; diff --git a/app/redux/store.js b/app/redux/store.js index 8d74b605f6..9e15b3a325 100644 --- a/app/redux/store.js +++ b/app/redux/store.js @@ -3,22 +3,22 @@ import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; import { routerMiddleware, routerReducer, push, replace } from 'react-router-redux'; import thunk from 'redux-thunk'; -import account from './account/reducers.js'; -import accountActions from './account/actions.js'; -import connection from './connection/reducers.js'; -import connectionActions from './connection/actions.js'; -import settings from './settings/reducers.js'; -import settingsActions from './settings/actions.js'; +import account from './account/reducers'; +import accountActions from './account/actions'; +import connection from './connection/reducers'; +import connectionActions from './connection/actions'; +import settings from './settings/reducers'; +import settingsActions from './settings/actions'; import type { Store } from 'redux'; import type { History } from 'history'; -import type { AccountReduxState } from './account/reducers.js'; -import type { ConnectionReduxState } from './connection/reducers.js'; -import type { SettingsReduxState } from './settings/reducers.js'; +import type { AccountReduxState } from './account/reducers'; +import type { ConnectionReduxState } from './connection/reducers'; +import type { SettingsReduxState } from './settings/reducers'; -import type { ConnectionAction } from './connection/actions.js'; -import type { AccountAction } from './account/actions.js'; -import type { SettingsAction } from './settings/actions.js'; +import type { ConnectionAction } from './connection/actions'; +import type { AccountAction } from './account/actions'; +import type { SettingsAction } from './settings/actions'; export type ReduxState = { account: AccountReduxState, |
