diff --git a/app/actions/storage/index.js b/app/actions/storage/index.js index 44e28b517..2feaba06a 100644 --- a/app/actions/storage/index.js +++ b/app/actions/storage/index.js @@ -12,13 +12,14 @@ export function loadStorage() { try { const data = JSON.parse(await AsyncStorage.getItem('storage')); - const {token, url, currentTeamId, ...otherStorage} = data; + const {token, url, serverVersion, currentTeamId, ...otherStorage} = data; const credentials = {token, url}; const currentChannelId = otherStorage[currentTeamId] ? otherStorage[currentTeamId].currentChannelId : ''; const actions = [ {type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: credentials}, + {type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion}, {type: TeamsTypes.SELECT_TEAM, data: currentTeamId}, {type: ChannelTypes.SELECT_CHANNEL, data: currentChannelId} ]; diff --git a/app/actions/views/root.js b/app/actions/views/root.js index 361230c06..bae2bfd92 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -3,10 +3,11 @@ import {NavigationTypes} from 'app/constants'; import Routes from 'app/navigation/routes'; +import {updateStorage} from 'app/actions/storage'; import Client from 'service/client'; +import {getClientConfig, getLicenseConfig, setServerVersion} from 'service/actions/general'; import {loadMe} from 'service/actions/users'; -import {getClientConfig, getLicenseConfig} from 'service/actions/general'; export function goToSelectServer() { return async (dispatch, getState) => { @@ -23,13 +24,21 @@ export function setStoreFromLocalData(data) { Client.setToken(data.token); Client.setUrl(data.url); - await getClientConfig()(dispatch, getState); - await getLicenseConfig()(dispatch, getState); return loadMe()(dispatch, getState); }; } +export function loadConfigAndLicense(serverVersion) { + return async (dispatch, getState) => { + getClientConfig()(dispatch, getState); + getLicenseConfig()(dispatch, getState); + setServerVersion(serverVersion)(dispatch, getState); + await updateStorage(null, {serverVersion}); + }; +} + export default { goToSelectServer, + loadConfigAndLicense, setStoreFromLocalData }; diff --git a/app/components/root/root.js b/app/components/root/root.js index d389eb369..6f89e3483 100644 --- a/app/components/root/root.js +++ b/app/components/root/root.js @@ -3,14 +3,17 @@ import React from 'react'; import {AppState} from 'react-native'; -import {getTranslations} from 'service/i18n'; import {IntlProvider} from 'react-intl'; +import {Constants} from 'service/constants'; +import {getTranslations} from 'service/i18n'; +import EventEmitter from 'service/utils/event_emitter'; export default class Root extends React.Component { static propTypes = { children: React.PropTypes.node, locale: React.PropTypes.string.isRequired, actions: React.PropTypes.shape({ + loadConfigAndLicense: React.PropTypes.func.isRequired, setAppState: React.PropTypes.func }).isRequired }; @@ -25,16 +28,22 @@ export default class Root extends React.Component { componentDidMount() { AppState.addEventListener('change', this.handleAppStateChange); + EventEmitter.on(Constants.CONFIG_CHANGED, this.handleConfigChanged); } componentWillUnmount() { AppState.removeEventListener('change', this.handleAppStateChange); + EventEmitter.off(Constants.CONFIG_CHANGED, this.handleConfigChanged); } handleAppStateChange(appState) { this.props.actions.setAppState(appState === 'active'); } + handleConfigChanged = (serverVersion) => { + this.props.actions.loadConfigAndLicense(serverVersion); + }; + render() { const locale = this.props.locale; diff --git a/app/components/root/root_container.js b/app/components/root/root_container.js index 8ab3c33bc..3e17153af 100644 --- a/app/components/root/root_container.js +++ b/app/components/root/root_container.js @@ -6,6 +6,7 @@ import {connect} from 'react-redux'; import Config from 'assets/config.json'; +import {loadConfigAndLicense} from 'app/actions/views/root'; import {setAppState} from 'service/actions/general'; import Root from './root'; @@ -28,6 +29,7 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ + loadConfigAndLicense, setAppState }, dispatch) }; diff --git a/app/scenes/login/login.js b/app/scenes/login/login.js index e6cbdf70f..abff9f8a5 100644 --- a/app/scenes/login/login.js +++ b/app/scenes/login/login.js @@ -32,8 +32,6 @@ class Login extends Component { handleSuccessfulLogin: React.PropTypes.func.isRequired, checkMfa: React.PropTypes.func.isRequired, login: React.PropTypes.func.isRequired, - getClientConfig: React.PropTypes.func.isRequired, - getLicenseConfig: React.PropTypes.func.isRequired, goToMfa: React.PropTypes.func.isRequired, goToLoadTeam: React.PropTypes.func.isRequired }).isRequired, @@ -55,11 +53,6 @@ class Login extends Component { }; } - componentWillMount() { - this.props.actions.getClientConfig(); - this.props.actions.getLicenseConfig(); - } - componentWillReceiveProps(nextProps) { if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) { this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToLoadTeam); @@ -193,7 +186,7 @@ class Login extends Component { }; } return error.message; - } + }; loginRef = (ref) => { this.loginId = ref; diff --git a/app/scenes/login/login_container.js b/app/scenes/login/login_container.js index a085afd76..66bb58b56 100644 --- a/app/scenes/login/login_container.js +++ b/app/scenes/login/login_container.js @@ -5,7 +5,6 @@ import {bindActionCreators} from 'redux'; import navigationSceneConnect from '../navigationSceneConnect'; -import {getClientConfig, getLicenseConfig} from 'service/actions/general'; import LoginActions from 'app/actions/views/login'; import {goToMfa, goToLoadTeam} from 'app/actions/navigation'; import {checkMfa, login} from 'service/actions/users'; @@ -33,8 +32,6 @@ function mapDispatchToProps(dispatch) { ...LoginActions, checkMfa, login, - getClientConfig, - getLicenseConfig, goToMfa, goToLoadTeam }, dispatch) diff --git a/service/actions/general.js b/service/actions/general.js index 7335649c2..92de72be1 100644 --- a/service/actions/general.js +++ b/service/actions/general.js @@ -73,10 +73,17 @@ export function setAppState(state) { }; } +export function setServerVersion(serverVersion) { + return async (dispatch, getState) => { + dispatch({type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion}, getState); + }; +} + export default { getPing, getClientConfig, getLicenseConfig, logClientError, - setAppState + setAppState, + setServerVersion }; diff --git a/service/actions/websocket.js b/service/actions/websocket.js index c017220c3..b09f2c92a 100644 --- a/service/actions/websocket.js +++ b/service/actions/websocket.js @@ -142,6 +142,9 @@ function handleEvent(msg, dispatch, getState) { case WebsocketEvents.TYPING: handleUserTypingEvent(msg, dispatch, getState); break; + case WebsocketEvents.HELLO: + handleHelloEvent(msg); + break; } } @@ -358,6 +361,14 @@ function handleStatusChangedEvent(msg, dispatch, getState) { }, getState); } +function handleHelloEvent(msg) { + const serverVersion = msg.data.server_version; + if (Client.serverVersion !== serverVersion) { + Client.serverVersion = serverVersion; + EventEmitter.emit(Constants.CONFIG_CHANGED, serverVersion); + } +} + const typingUsers = {}; function handleUserTypingEvent(msg, dispatch, getState) { const state = getState(); diff --git a/service/client/client.js b/service/client/client.js index b3caa1ad6..6f03bf7b2 100644 --- a/service/client/client.js +++ b/service/client/client.js @@ -1,11 +1,15 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import EventEmitter from 'service/utils/event_emitter'; +import {Constants} from 'service/constants'; + const HEADER_AUTH = 'Authorization'; const HEADER_BEARER = 'BEARER'; const HEADER_CONTENT_TYPE = 'Content-Type'; const HEADER_REQUESTED_WITH = 'X-Requested-With'; const HEADER_TOKEN = 'Token'; +const HEADER_X_VERSION_ID = 'X-Version-Id'; const CONTENT_TYPE_JSON = 'application/json'; @@ -15,6 +19,7 @@ export default class Client { this.token = ''; this.url = ''; this.urlVersion = '/api/v3'; + this.serverVersion = ''; this.translations = { connectionError: 'There appears to be a problem with your internet connection.', @@ -38,6 +43,10 @@ export default class Client { this.token = token; } + getServerVersion() { + return this.serverVersion; + } + getUrlVersion() { return this.urlVersion; } @@ -708,6 +717,14 @@ export default class Client { data = await response.text(); } + if (headers.has(HEADER_X_VERSION_ID)) { + const serverVersion = headers.get(HEADER_X_VERSION_ID); + if (this.serverVersion !== serverVersion) { + this.serverVersion = serverVersion; + EventEmitter.emit(Constants.CONFIG_CHANGED, serverVersion); + } + } + if (response.ok) { return { response, diff --git a/service/constants/constants.js b/service/constants/constants.js index 591c5779f..af1815cff 100644 --- a/service/constants/constants.js +++ b/service/constants/constants.js @@ -2,6 +2,8 @@ // See License.txt for license information. const Constants = { + CONFIG_CHANGED: 'config_changed', + POST_CHUNK_SIZE: 60, PROFILE_CHUNK_SIZE: 100, CHANNELS_CHUNK_SIZE: 50, diff --git a/service/constants/general.js b/service/constants/general.js index 5200c997f..53c877ae2 100644 --- a/service/constants/general.js +++ b/service/constants/general.js @@ -12,6 +12,8 @@ const GeneralTypes = keyMirror({ PING_SUCCESS: null, PING_FAILURE: null, + RECEIVED_SERVER_VERSION: null, + CLIENT_CONFIG_REQUEST: null, CLIENT_CONFIG_SUCCESS: null, CLIENT_CONFIG_FAILURE: null, diff --git a/service/reducers/entities/general.js b/service/reducers/entities/general.js index b564b47f2..aa3967d84 100644 --- a/service/reducers/entities/general.js +++ b/service/reducers/entities/general.js @@ -48,9 +48,21 @@ function credentials(state = {}, action) { } } +function serverVersion(state = '', action) { + switch (action.type) { + case GeneralTypes.RECEIVED_SERVER_VERSION: + return action.data; + case UsersTypes.LOGOUT_SUCCESS: + return ''; + default: + return state; + } +} + export default combineReducers({ appState, credentials, config, - license + license, + serverVersion }); diff --git a/test/service/actions/general.test.js b/test/service/actions/general.test.js index 7652ce78b..4ebf26d4b 100644 --- a/test/service/actions/general.test.js +++ b/test/service/actions/general.test.js @@ -75,4 +75,12 @@ describe('Actions.General', () => { // Check a few basic fields since they may change over time assert.notStrictEqual(licenseConfig.IsLicensed, undefined); }); + + it('setServerVersion', async () => { + const version = '3.7.0'; + await Actions.setServerVersion(version)(store.dispatch, store.getState); + + const {serverVersion} = store.getState().entities.general; + assert.deepEqual(serverVersion, version); + }); });