From 04d63a3e93c97f0da3c518b0a8619b4df5c01e56 Mon Sep 17 00:00:00 2001 From: enahum Date: Thu, 18 May 2017 08:55:49 -0400 Subject: [PATCH] Local push notification when session expires (#551) --- app/actions/views/login.js | 17 +++++++++--- app/mattermost.js | 27 ++++++++++++------- app/screens/login/login.js | 20 ++++++++++++-- app/screens/notification/notification.js | 24 +++++++++-------- app/screens/saml/saml.js | 34 +++++++++++++++++++----- assets/base/i18n/en.json | 1 + yarn.lock | 6 +---- 7 files changed, 92 insertions(+), 37 deletions(-) diff --git a/app/actions/views/login.js b/app/actions/views/login.js index 3c583e826..b4aa4baf1 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -1,9 +1,9 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import {GeneralTypes} from 'mattermost-redux/action_types'; +import {Client4} from 'mattermost-redux/client'; import {ViewTypes} from 'app/constants'; -import {Client4} from 'mattermost-redux/client'; export function handleLoginIdChanged(loginId) { return async (dispatch, getState) => { @@ -24,14 +24,25 @@ export function handlePasswordChanged(password) { } export function handleSuccessfulLogin() { - return async (dispatch) => { + return async (dispatch, getState) => { + const {currentUserId} = getState().entities.users; + const token = Client4.getToken(); + const session = await Client4.getSessions(currentUserId, token); + dispatch({ type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: { url: Client4.getUrl(), - token: Client4.getToken() + token } }); + + if (Array.isArray(session) && session[0]) { + const s = session[0]; + return s.expires_at; + } + + return false; }; } diff --git a/app/mattermost.js b/app/mattermost.js index 0b656b731..44d7785aa 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -5,7 +5,7 @@ import 'babel-polyfill'; import Orientation from 'react-native-orientation'; import {Provider} from 'react-redux'; import {Navigation} from 'react-native-navigation'; -import DeviceNotification from 'react-native-push-notification'; +import PushNotification from 'react-native-push-notification'; import { Alert, AppState, @@ -71,6 +71,7 @@ export default class Mattermost { handleReset = () => { Client4.serverVersion = ''; + PushNotification.cancelAllLocalNotifications(); this.startApp('fade'); }; @@ -100,7 +101,7 @@ export default class Mattermost { }; configurePushNotifications = () => { - DeviceNotification.configure({ + PushNotification.configure({ onRegister: this.onRegisterDevice, onNotification: this.onPushNotification, senderID: Config.GooglePlaySenderId, @@ -117,7 +118,7 @@ export default class Mattermost { }; onPushNotification = (deviceNotification) => { - const {foreground, userInteraction, data, message} = deviceNotification; + const {data, foreground, message, userInfo, userInteraction} = deviceNotification; let notification; if (Platform.OS === 'android') { @@ -135,19 +136,25 @@ export default class Mattermost { }; } + if (userInfo) { + notification.localNotification = userInfo.localNotification; + } + if (foreground) { EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification); } else if (userInteraction) { const {dispatch, getState} = store; const state = getState(); - if (!state.views.root.appInitializing) { - // go to notification if the app is initialized - goToNotification(notification)(dispatch, getState); - EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED); - } else if (state.entities.general.credentials.token) { - // queue notification if app is not initialized but we are logged in - queueNotification(notification)(dispatch, getState); + if (!notification.localNotification) { + if (!state.views.root.appInitializing) { + // go to notification if the app is initialized + goToNotification(notification)(dispatch, getState); + EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED); + } else if (state.entities.general.credentials.token) { + // queue notification if app is not initialized but we are logged in + queueNotification(notification)(dispatch, getState); + } } } }; diff --git a/app/screens/login/login.js b/app/screens/login/login.js index ea64f0bf0..cd016da4c 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -18,6 +18,7 @@ import { View } from 'react-native'; import Button from 'react-native-button'; +import PushNotification from 'react-native-push-notification'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; @@ -73,8 +74,23 @@ class Login extends PureComponent { } } - goToLoadTeam = () => { - const {navigator, theme} = this.props; + goToLoadTeam = (expiresAt) => { + const {intl, navigator, theme} = this.props; + + if (expiresAt) { + PushNotification.localNotificationSchedule({ + alertAction: null, + date: new Date(expiresAt), + message: intl.formatMessage({ + id: 'mobile.session_expired', + defaultMessage: 'Session Expired: Please log in to continue receiving notifications.' + }), + userInfo: { + localNotification: true + } + }); + } + navigator.resetTo({ screen: 'LoadTeam', title: '', diff --git a/app/screens/notification/notification.js b/app/screens/notification/notification.js index a1db4ef45..a107bde5e 100644 --- a/app/screens/notification/notification.js +++ b/app/screens/notification/notification.js @@ -31,17 +31,19 @@ export default class Notification extends PureComponent { const {actions, navigator, notification, theme} = this.props; navigator.dismissInAppNotification(); - actions.goToNotification(notification); - navigator.resetTo({ - screen: 'Channel', - animated: false, - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: theme.centerChannelBg - } - }); + if (!notification.localNotification) { + actions.goToNotification(notification); + navigator.resetTo({ + screen: 'Channel', + animated: false, + navigatorStyle: { + navBarHidden: true, + statusBarHidden: false, + statusBarHideWithNavBar: false, + screenBackgroundColor: theme.centerChannelBg + } + }); + } }; render() { diff --git a/app/screens/saml/saml.js b/app/screens/saml/saml.js index 2a0f171c2..9e0009ed0 100644 --- a/app/screens/saml/saml.js +++ b/app/screens/saml/saml.js @@ -3,18 +3,21 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; +import {injectIntl, intlShape} from 'react-intl'; import { InteractionManager, StatusBar, View, WebView } from 'react-native'; - import CookieManager from 'react-native-cookies'; +import PushNotification from 'react-native-push-notification'; + import {Client4} from 'mattermost-redux/client'; -export default class Saml extends PureComponent { +class Saml extends PureComponent { static propTypes = { + intl: intlShape.isRequired, navigator: PropTypes.object, theme: PropTypes.object, serverUrl: PropTypes.string.isRequired, @@ -30,8 +33,23 @@ export default class Saml extends PureComponent { }); } - goToLoadTeam = () => { - const {navigator, theme} = this.props; + goToLoadTeam = (expiresAt) => { + const {intl, navigator, theme} = this.props; + + if (expiresAt) { + PushNotification.localNotificationSchedule({ + alertAction: null, + date: new Date(expiresAt), + message: intl.formatMessage({ + id: 'mobile.session_expired', + defaultMessage: 'Session Expired: Please log in to continue receiving notifications.' + }), + userInfo: { + localNotification: true + } + }); + } + navigator.resetTo({ screen: 'LoadTeam', title: '', @@ -63,8 +81,10 @@ export default class Saml extends PureComponent { Client4.setToken(token); handleSuccessfulLogin(). - then(setStoreFromLocalData.bind(null, {url: this.props.serverUrl, token})). - then(this.goToLoadTeam); + then(async (expiresAt) => { + await setStoreFromLocalData({url: this.props.serverUrl, token}); + this.goToLoadTeam(expiresAt); + }); } }); } @@ -89,3 +109,5 @@ export default class Saml extends PureComponent { ); } } + +export default injectIntl(Saml); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 77506be1c..3178df627 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1745,6 +1745,7 @@ "mobile.routes.user_profile.send_message": "Send Message", "mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.", "mobile.server_url.invalid_format": "URL must start with http:// or https://", + "mobile.session_expired": "Session Expired: Please log in to continue receiving notifications.", "mobile.settings.team_selection": "Team Selection", "more_channels.close": "Close", "more_channels.create": "Create New Channel", diff --git a/yarn.lock b/yarn.lock index 5f764ba01..89bf09066 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3526,7 +3526,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/894523bf84013922d344aa4aae374889604097c0" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/3219aed65ab3ddfd738199db6ae42fe746226782" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1" @@ -4303,10 +4303,6 @@ react-native-drawer@2.3.0: dependencies: tween-functions "^1.0.1" -react-native-image-picker@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-0.26.3.tgz#0ad2eede49501a7046d8046a73813696539c3dcd" - react-native-image-picker@jp928/react-native-image-picker#6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4: version "0.26.2" resolved "https://codeload.github.com/jp928/react-native-image-picker/tar.gz/6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4"