From 34a37cef05cf30c9735de740af50a67c8e6abe63 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 7 Aug 2018 13:11:48 -0400 Subject: [PATCH] MM-11477 Ensure client calls are wrapped with error handling (#1976) --- app/actions/views/login.js | 26 ++++++++++++++++++-------- app/actions/views/root.js | 4 +++- app/app.js | 8 +++++++- app/fetch_preconfig.js | 1 + app/mattermost.js | 9 +++++++-- app/screens/sso/sso.js | 8 +++++++- app/utils/push_notifications.js | 21 +++++++++++++-------- 7 files changed, 56 insertions(+), 21 deletions(-) diff --git a/app/actions/views/login.js b/app/actions/views/login.js index 4d6c06132..1386617b8 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -6,7 +6,7 @@ import {GeneralTypes} from 'mattermost-redux/action_types'; import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone'; import {Client4} from 'mattermost-redux/client'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; +import {getCurrentUserId, getSessions} from 'mattermost-redux/selectors/entities/users'; import {ViewTypes} from 'app/constants'; import {app} from 'app/mattermost'; @@ -73,15 +73,25 @@ export function getSession() { const {credentials} = state.entities.general; const token = credentials && credentials.token; - if (currentUserId && token) { - const session = await Client4.getSessions(currentUserId, token); - if (Array.isArray(session) && session[0]) { - const s = session[0]; - return s.expires_at; - } + if (!currentUserId || !token) { + return 0; } - return false; + let sessions; + try { + sessions = await getSessions(currentUserId); + } catch (e) { + console.warn('Failed to get current session', e); // eslint-disable-line no-console + return 0; + } + + if (!Array.isArray(sessions)) { + return 0; + } + + const session = sessions.find((s) => s.token === token); + + return session && session.expires_at ? session.expires_at : 0; }; } diff --git a/app/actions/views/root.js b/app/actions/views/root.js index 5788e3613..8fe81acd4 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -102,7 +102,9 @@ export function purgeOfflineStore() { return {type: General.OFFLINE_STORE_PURGE}; } -export function createPost(post) { +// A non-optimistic version of the createPost action in mattermost-redux with the file handling +// removed since it's not needed. +export function createPostForNotificationReply(post) { return (dispatch, getState) => { const state = getState(); const currentUserId = state.entities.users.currentUserId; diff --git a/app/app.js b/app/app.js index 47ccfa556..91818f1d9 100644 --- a/app/app.js +++ b/app/app.js @@ -259,7 +259,13 @@ export default class App { if (this.token && this.url) { screen = 'Channel'; tracker.initialLoad = Date.now(); - dispatch(loadMe()); + + try { + dispatch(loadMe()); + } catch (e) { + // Fall through since we should have a previous version of the current user because we have a token + console.warn('Failed to load current user when starting on Channel screen', e); // eslint-disable-line no-console + } } switch (screen) { diff --git a/app/fetch_preconfig.js b/app/fetch_preconfig.js index 9a8e02451..281726e1c 100644 --- a/app/fetch_preconfig.js +++ b/app/fetch_preconfig.js @@ -42,6 +42,7 @@ Client4.doFetchWithResponse = async (url, options) => { } throw { + message: 'Received invalid response from the server.', intl: { id: 'mobile.request.invalid_response', defaultMessage: 'Received invalid response from the server.', diff --git a/app/mattermost.js b/app/mattermost.js index 704a3b939..e7bbc5f11 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -142,8 +142,13 @@ const handleLogout = () => { const restartApp = async () => { Navigation.dismissModal({animationType: 'none'}); - await store.dispatch(loadConfigAndLicense()); - await store.dispatch(loadMe()); + try { + await store.dispatch(loadConfigAndLicense()); + await store.dispatch(loadMe()); + } catch (e) { + console.warn('Failed to load initial data while restarting', e); // eslint-disable-line no-console + } + launchChannel(); }; diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index 736958af6..2eb19f45d 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -187,12 +187,18 @@ class SSO extends PureComponent { setStoreFromLocalData({url: this.props.serverUrl, token}). then(handleSuccessfulLogin). then(getSession). - then(this.goToLoadTeam); + then(this.goToLoadTeam). + catch(this.onLoadEndError); } }); } }; + onLoadEndError = (e) => { + console.warn('Failed to set store from local data', e); // eslint-disable-line no-console + this.setState({error: e.message}); + } + renderLoading = () => { return ; }; diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index f4662a214..6249c7509 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -16,7 +16,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {ViewTypes} from 'app/constants'; import {retryGetPostsAction} from 'app/actions/views/channel'; import { - createPost, + createPostForNotificationReply, loadFromPushNotification, } from 'app/actions/views/root'; @@ -136,15 +136,20 @@ export const onPushNotificationReply = (data, text, badge, completed) => { } retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); - dispatch(createPost(post)).then(() => { - dispatch(markChannelAsRead(data.channel_id)); + dispatch(createPostForNotificationReply(post)). + then(() => { + dispatch(markChannelAsRead(data.channel_id)); - if (badge >= 0) { - PushNotifications.setApplicationIconBadgeNumber(badge); - } + if (badge >= 0) { + PushNotifications.setApplicationIconBadgeNumber(badge); + } - app.setReplyNotificationData(null); - }).then(completed); + app.setReplyNotificationData(null); + }). + then(completed). + catch((e) => { + console.warn('Failed to send reply to push notification', e); // eslint-disable-line no-console + }); } else { app.setReplyNotificationData({ data,