From 01792f2b54c6bddd359dd77a3128bc67d2750e6b Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 9 Jul 2018 18:33:13 -0400 Subject: [PATCH] Handle bad state (#1903) * Upgrade mattermost-redux * Check if url start with http before pinging * Remove required status prop in settings sidebar * Prevent the app to get into a bad state and if that happens try and recover --- app/app.js | 22 +++++++++---- .../sidebars/settings/settings_sidebar.js | 3 +- app/screens/channel/channel.js | 33 ++++++++++++++++--- app/screens/channel/index.js | 3 +- app/store/middleware.js | 15 +++++++++ app/utils/network.js | 2 +- assets/base/i18n/en.json | 2 ++ package-lock.json | 4 +-- package.json | 2 +- 9 files changed, 69 insertions(+), 17 deletions(-) diff --git a/app/app.js b/app/app.js index bd8de9c31..58fd2b263 100644 --- a/app/app.js +++ b/app/app.js @@ -91,12 +91,17 @@ export default class App { const [deviceToken, currentUserId] = usernameParsed; const [token, url] = passwordParsed; - this.deviceToken = deviceToken; - this.currentUserId = currentUserId; - this.token = token; - this.url = url; - Client4.setUrl(url); - Client4.setToken(token); + // if for any case the url and the token aren't valid proceed with re-hydration + if (url && url !== 'undefined' && token && token !== 'undefined') { + this.deviceToken = deviceToken; + this.currentUserId = currentUserId; + this.token = token; + this.url = url; + Client4.setUrl(url); + Client4.setToken(token); + } else { + this.waitForRehydration = true; + } } } } catch (error) { @@ -161,7 +166,10 @@ export default class App { this.url = url; } - setGenericPassword(username, password); + // Only save to keychain if the url and token are set + if (url && token) { + setGenericPassword(username, password); + } }; setStartupThemes = (toolbarBackground, toolbarTextColor, appBackground) => { diff --git a/app/components/sidebars/settings/settings_sidebar.js b/app/components/sidebars/settings/settings_sidebar.js index ceee93d13..f3957bdfa 100644 --- a/app/components/sidebars/settings/settings_sidebar.js +++ b/app/components/sidebars/settings/settings_sidebar.js @@ -41,12 +41,13 @@ export default class SettingsDrawer extends PureComponent { currentUser: PropTypes.object.isRequired, deviceWidth: PropTypes.number.isRequired, navigator: PropTypes.object, - status: PropTypes.string.isRequired, + status: PropTypes.string, theme: PropTypes.object.isRequired, }; static defaultProps = { currentUser: {}, + status: 'offline', }; static contextTypes = { diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 5f269e47b..a3cd26cbe 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -5,6 +5,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; import { + Alert, AppState, Dimensions, Platform, @@ -22,6 +23,7 @@ import KeyboardLayout from 'app/components/layout/keyboard_layout'; import OfflineIndicator from 'app/components/offline_indicator'; import SafeAreaView from 'app/components/safe_area_view'; import StatusBar from 'app/components/status_bar'; +import {ViewTypes} from 'app/constants'; import mattermostBucket from 'app/mattermost_bucket'; import {preventDoubleTap} from 'app/utils/tap'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -33,7 +35,6 @@ import LocalConfig from 'assets/config'; import ChannelNavBar from './channel_nav_bar'; import ChannelPostList from './channel_post_list'; -import {ViewTypes} from 'app/constants'; const { ANDROID_TOP_LANDSCAPE, ANDROID_TOP_PORTRAIT, @@ -50,6 +51,7 @@ export default class Channel extends PureComponent { connection: PropTypes.func.isRequired, loadChannelsIfNecessary: PropTypes.func.isRequired, loadProfilesAndTeamMembersForDMSidebar: PropTypes.func.isRequired, + logout: PropTypes.func.isRequired, selectDefaultTeam: PropTypes.func.isRequired, selectInitialChannel: PropTypes.func.isRequired, initWebSocket: PropTypes.func.isRequired, @@ -236,8 +238,12 @@ export default class Channel extends PureComponent { handleConnectionChange = (isConnected) => { const {connection} = this.props.actions; - this.handleWebSocket(isConnected); - connection(isConnected); + // Prevent for being called more than once. + if (this.isConnected !== isConnected) { + this.isConnected = isConnected; + this.handleWebSocket(isConnected); + connection(isConnected); + } }; handleLeaveTeam = () => { @@ -245,6 +251,7 @@ export default class Channel extends PureComponent { }; initializeWebSocket = async () => { + const {formatMessage} = this.context.intl; const {actions} = this.props; const {initWebSocket} = actions; const platform = Platform.OS; @@ -253,7 +260,25 @@ export default class Channel extends PureComponent { certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); } - initWebSocket(platform, null, null, null, {certificate}); + initWebSocket(platform, null, null, null, {certificate}).catch(() => { + // we should dispatch a failure and show the app as disconnected + Alert.alert( + formatMessage({id: 'mobile.authentication_error.title', defaultMessage: 'Authentication Error'}), + formatMessage({ + id: 'mobile.authentication_error.message', + defaultMessage: 'Mattermost has encountered an error. Please re-authenticate to start a new session.', + }), + [{ + text: formatMessage({ + id: 'navbar_dropdown.logout', + defaultMessage: 'Logout', + }), + onPress: actions.logout, + }], + {cancelable: false} + ); + this.props.actions.closeWebSocket(true); + }); }; loadChannels = (teamId) => { diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js index 84340c5b6..05fb3c482 100644 --- a/app/screens/channel/index.js +++ b/app/screens/channel/index.js @@ -4,7 +4,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users'; +import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates, logout} from 'mattermost-redux/actions/users'; import {init as initWebSocket, close as closeWebSocket} from 'mattermost-redux/actions/websocket'; import {RequestStatus} from 'mattermost-redux/constants'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; @@ -41,6 +41,7 @@ function mapDispatchToProps(dispatch) { connection, loadChannelsIfNecessary, loadProfilesAndTeamMembersForDMSidebar, + logout, selectDefaultTeam, selectInitialChannel, initWebSocket, diff --git a/app/store/middleware.js b/app/store/middleware.js index 1321a1678..50ef90bb8 100644 --- a/app/store/middleware.js +++ b/app/store/middleware.js @@ -111,6 +111,20 @@ function resetStateForNewVersion(action) { lastTeamId = payload.views.team.lastTeamId; } + const currentChannelId = lastChannelForTeam[lastTeamId] && lastChannelForTeam[lastTeamId].length ? lastChannelForTeam[lastTeamId][0] : ''; + let channels = initialState.entities.channels; + if (payload.entities.channels && currentChannelId) { + channels = { + currentChannelId, + channels: { + [currentChannelId]: payload.entities.channels.channels[currentChannelId], + }, + myMembers: { + [currentChannelId]: payload.entities.channels.myMembers[currentChannelId], + }, + }; + } + let threadDrafts = initialState.views.thread.drafts; if (payload.views.thread && payload.views.thread.drafts) { threadDrafts = payload.views.thread.drafts; @@ -132,6 +146,7 @@ function resetStateForNewVersion(action) { version: DeviceInfo.getVersion(), }, entities: { + channels, general, teams, users, diff --git a/app/utils/network.js b/app/utils/network.js index f54323664..34a6f9b53 100644 --- a/app/utils/network.js +++ b/app/utils/network.js @@ -9,7 +9,7 @@ import {Client4} from 'mattermost-redux/client'; const PING_TIMEOUT = 10000; export async function checkConnection(isConnected) { - if (Client4.getBaseRoute() === '/api/v4') { + if (!Client4.getBaseRoute().startsWith('http')) { // If we don't have a server yet, return the default implementation return isConnected; } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index b5809fd6d..f4914fce1 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -2309,6 +2309,8 @@ "mobile.announcement_banner.dismiss": "Dismiss", "mobile.announcement_banner.ok": "OK", "mobile.announcement_banner.title": "Announcement", + "mobile.authentication_error.title": "Authentication Error", + "mobile.authentication_error.message": "Mattermost has encountered an error. Please re-authenticate to start a new session.", "mobile.channel.markAsRead": "Mark As Read", "mobile.channel_drawer.search": "Jump to...", "mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to archive the {term} {name}?", diff --git a/package-lock.json b/package-lock.json index 3adb9e8f4..4cff221e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10100,8 +10100,8 @@ } }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#9d922ba3f3ed1264f192cbe3c7f0030d249756e3", - "from": "github:mattermost/mattermost-redux#9d922ba3f3ed1264f192cbe3c7f0030d249756e3", + "version": "github:mattermost/mattermost-redux#ade5924a6e509ea76757bd78a2cc193043a584ce", + "from": "github:mattermost/mattermost-redux#ade5924a6e509ea76757bd78a2cc193043a584ce", "requires": { "deep-equal": "1.0.1", "eslint-plugin-header": "1.2.0", diff --git a/package.json b/package.json index 6b383815b..c359db339 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "intl": "1.2.5", "jail-monkey": "1.0.0", "jsc-android": "216113.0.3", - "mattermost-redux": "github:mattermost/mattermost-redux#9d922ba3f3ed1264f192cbe3c7f0030d249756e3", + "mattermost-redux": "github:mattermost/mattermost-redux#ade5924a6e509ea76757bd78a2cc193043a584ce", "mime-db": "1.33.0", "prop-types": "15.6.1", "react": "16.3.2",