Do not schedule session expiry notification for 5.24+ (#4291)

This commit is contained in:
Elias Nahum 2020-05-19 12:15:45 -04:00 committed by GitHub
parent be2b0a03e6
commit c04c1f26f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 156 additions and 70 deletions

View file

@ -0,0 +1,68 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import moment from 'moment-timezone';
import {getSessions} from '@mm-redux/actions/users';
import {Client4} from '@mm-redux/client';
import {getConfig} from '@mm-redux/selectors/entities/general';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import PushNotifications from 'app/push_notifications';
const sortByNewest = (a, b) => {
if (a.create_at > b.create_at) {
return -1;
}
return 1;
};
export function scheduleExpiredNotification(intl) {
return async (dispatch, getState) => {
const state = getState();
const {currentUserId} = state.entities.users;
const config = getConfig(state);
if (isMinimumServerVersion(Client4.serverVersion, 5, 24) && config.ExtendSessionLengthWithActivity === 'true') {
PushNotifications.cancelAllLocalNotifications();
return;
}
let sessions;
try {
sessions = await dispatch(getSessions(currentUserId));
} catch (e) {
console.warn('Failed to get current session', e); // eslint-disable-line no-console
return;
}
if (!Array.isArray(sessions.data)) {
return;
}
const session = sessions.data.sort(sortByNewest)[0];
const expiresAt = session?.expires_at || 0; //eslint-disable-line camelcase
const expiresInDays = parseInt(Math.ceil(Math.abs(moment.duration(moment().diff(expiresAt)).asDays())), 10);
const message = intl.formatMessage({
id: 'mobile.session_expired',
defaultMessage: 'Session Expired: Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.',
}, {
siteName: config.SiteName,
daysCount: expiresInDays,
});
if (expiresAt) {
// eslint-disable-next-line no-console
console.log('Schedule Session Expiry Local Push Notification', expiresAt);
PushNotifications.localNotificationSchedule({
date: new Date(expiresAt),
message,
userInfo: {
localNotification: true,
},
});
}
};
}

View file

@ -9,6 +9,14 @@ import {getLocales} from 'react-native-localize';
import RNFetchBlob from 'rn-fetch-blob';
import semver from 'semver/preload';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from '@actions/device';
import {selectDefaultChannel} from '@actions/views/channel';
import {showOverlay} from '@actions/navigation';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from '@actions/views/root';
import {loadMe, logout} from '@actions/views/user';
import LocalConfig from '@assets/config';
import {NavigationTypes, ViewTypes} from '@constants';
import {getTranslations, resetMomentLocale} from '@i18n';
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
import {close as closeWebSocket} from '@actions/websocket';
@ -19,15 +27,7 @@ import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {getCurrentUser, getUser} from '@mm-redux/selectors/entities/users';
import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from '@actions/device';
import {selectDefaultChannel} from '@actions/views/channel';
import {showOverlay} from '@actions/navigation';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from '@actions/views/root';
import {loadMe, logout} from '@actions/views/user';
import LocalConfig from '@assets/config';
import {NavigationTypes, ViewTypes} from '@constants';
import {getTranslations, resetMomentLocale} from '@i18n';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {getCurrentLocale} from '@selectors/i18n';
import initialState from '@store/initial_state';
import Store from '@store/store';
@ -156,10 +156,6 @@ class GlobalEventHandler {
emmProvider.handleManagedConfig(true);
};
onServerConfigChanged = (config) => {
this.configureAnalytics(config);
};
onLogout = async () => {
Store.redux.dispatch(closeWebSocket(false));
Store.redux.dispatch(setServerVersion(''));
@ -198,6 +194,12 @@ class GlobalEventHandler {
if (this.launchApp) {
this.launchApp();
}
// Reset the state after sending
// the user to the Select server URL screen
// To avoid unavailable data crashes while components are
// still mounted.
this.resetState();
};
onOrientationChange = (dimensions) => {
@ -246,30 +248,35 @@ class GlobalEventHandler {
}
};
onServerVersionChanged = async (serverVersion) => {
if (Store?.redux?.dispatch) {
const {dispatch, getState} = Store.redux;
const state = getState();
const match = serverVersion && serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g);
const version = match && match[0];
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
onServerConfigChanged = (config) => {
this.configureAnalytics(config);
if (serverVersion) {
if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) {
Alert.alert(
translations[t('mobile.server_upgrade.title')],
translations[t('mobile.server_upgrade.description')],
[{
text: translations[t('mobile.server_upgrade.button')],
onPress: this.serverUpgradeNeeded,
}],
{cancelable: false},
);
} else if (state.entities.users && state.entities.users.currentUserId) {
dispatch(setServerVersion(serverVersion));
dispatch(loadConfigAndLicense());
}
if (isMinimumServerVersion(Client4.serverVersion, 5, 24) && config.ExtendSessionLengthWithActivity === 'true') {
PushNotifications.cancelAllLocalNotifications();
}
};
onServerVersionChanged = async (serverVersion) => {
const {dispatch, getState} = Store.redux;
const state = getState();
const match = serverVersion && serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g);
const version = match && match[0];
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
if (serverVersion) {
if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) {
Alert.alert(
translations[t('mobile.server_upgrade.title')],
translations[t('mobile.server_upgrade.description')],
[{
text: translations[t('mobile.server_upgrade.button')],
onPress: this.serverUpgradeNeeded,
}],
{cancelable: false},
);
} else if (state.entities.users && state.entities.users.currentUserId) {
dispatch(setServerVersion(serverVersion));
dispatch(loadConfigAndLicense());
}
}
};

View file

@ -4,11 +4,11 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {login} from 'app/actions/views/user';
import {login} from '@actions/views/user';
import {scheduleExpiredNotification} from '@actions/views/session';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {getConfig, getLicense} from '@mm-redux/selectors/entities/general';
import {isLandscape} from 'app/selectors/device';
import LoginActions from 'app/actions/views/login';
import {isLandscape} from '@selectors/device';
import Login from './login.js';
@ -27,7 +27,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
...LoginActions,
scheduleExpiredNotification,
login,
}, dispatch),
};

View file

@ -39,7 +39,6 @@ export const mfaExpectedErrors = ['mfa.validate_token.authenticate.app_error', '
export default class Login extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
handleSuccessfulLogin: PropTypes.func.isRequired,
scheduleExpiredNotification: PropTypes.func.isRequired,
login: PropTypes.func.isRequired,
}).isRequired,
@ -81,7 +80,6 @@ export default class Login extends PureComponent {
telemetry.remove(['start:overall']);
tracker.initialLoad = Date.now();
this.scheduleSessionExpiredNotification();
resetToChannel();
@ -94,7 +92,7 @@ export default class Login extends PureComponent {
const loginId = this.loginId;
const password = this.password;
goToScreen(screen, title, {onMfaComplete: this.checkLoginResponse, loginId, password});
goToScreen(screen, title, {onMfaComplete: this.checkLoginResponse, goToChannel: this.goToChannel, loginId, password});
};
blur = () => {
@ -125,7 +123,6 @@ export default class Login extends PureComponent {
}
this.setState({isLoading: false});
resetToChannel();
return true;
};
@ -309,12 +306,14 @@ export default class Login extends PureComponent {
}
}
signIn = () => {
signIn = async () => {
const {actions} = this.props;
const {isLoading} = this.state;
if (isLoading) {
actions.login(this.loginId.toLowerCase(), this.password).
then(this.checkLoginResponse);
const result = await actions.login(this.loginId.toLowerCase(), this.password);
if (this.checkLoginResponse(result)) {
this.goToChannel();
}
}
};

View file

@ -23,7 +23,6 @@ describe('Login', () => {
},
loginRequest: {},
actions: {
handleSuccessfulLogin: jest.fn(),
scheduleExpiredNotification: jest.fn(),
login: jest.fn(),
},
@ -94,6 +93,7 @@ describe('Login', () => {
'MFA',
'Multi-factor Authentication',
{
goToChannel: wrapper.instance().goToChannel,
onMfaComplete: wrapper.instance().checkLoginResponse,
loginId,
password,

View file

@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {login} from 'app/actions/views/user';
import {login} from '@actions/views/user';
import Mfa from './mfa';

View file

@ -31,6 +31,7 @@ export default class Mfa extends PureComponent {
actions: PropTypes.shape({
login: PropTypes.func.isRequired,
}).isRequired,
goToChannel: PropTypes.func.isRequired,
loginId: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
onMfaComplete: PropTypes.func.isRequired,
@ -78,7 +79,7 @@ export default class Mfa extends PureComponent {
};
submit = preventDoubleTap(() => {
const {actions, loginId, password, onMfaComplete} = this.props;
const {actions, goToChannel, loginId, password, onMfaComplete} = this.props;
const {token} = this.state;
Keyboard.dismiss();
@ -95,11 +96,14 @@ export default class Mfa extends PureComponent {
}
setMfaPreflightDone(true);
this.setState({isLoading: true});
actions.login(loginId, password, token).then(() => {
if (!onMfaComplete()) {
popTopScreen();
}
actions.login(loginId, password, token).then((result) => {
this.setState({isLoading: false});
if (onMfaComplete(result)) {
goToChannel();
return;
}
popTopScreen();
});
});

View file

@ -4,15 +4,14 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {setLastUpgradeCheck} from '@actions/views/client_upgrade';
import {loadConfigAndLicense} from '@actions/views/root';
import {handleServerUrlChanged} from '@actions/views/select_server';
import {scheduleExpiredNotification} from '@actions/views/session';
import {getPing, resetPing, setServerVersion} from '@mm-redux/actions/general';
import {login} from '@mm-redux/actions/users';
import {getConfig, getLicense} from '@mm-redux/selectors/entities/general';
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
import {handleSuccessfulLogin, scheduleExpiredNotification} from 'app/actions/views/login';
import {loadConfigAndLicense} from 'app/actions/views/root';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import getClientUpgrade from 'app/selectors/client_upgrade';
import getClientUpgrade from '@selectors/client_upgrade';
import SelectServer from './select_server';
@ -35,7 +34,6 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
handleSuccessfulLogin,
getPing,
scheduleExpiredNotification,
handleServerUrlChanged,

View file

@ -48,7 +48,6 @@ export default class SelectServer extends PureComponent {
actions: PropTypes.shape({
getPing: PropTypes.func.isRequired,
handleServerUrlChanged: PropTypes.func.isRequired,
handleSuccessfulLogin: PropTypes.func.isRequired,
scheduleExpiredNotification: PropTypes.func.isRequired,
loadConfigAndLicense: PropTypes.func.isRequired,
login: PropTypes.func.isRequired,
@ -297,7 +296,6 @@ export default class SelectServer extends PureComponent {
tracker.initialLoad = Date.now();
await this.props.actions.login('credential', 'password');
await this.props.actions.handleSuccessfulLogin();
this.scheduleSessionExpiredNotification();
resetToChannel();

View file

@ -4,10 +4,10 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {scheduleExpiredNotification} from 'app/actions/views/login';
import {ssoLogin} from 'app/actions/views/user';
import {ssoLogin} from '@actions/views/user';
import {scheduleExpiredNotification} from '@actions/views/session';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {isLandscape} from 'app/selectors/device';
import {isLandscape} from '@selectors/device';
import SSO from './sso';

View file

@ -16,8 +16,8 @@ export function getMfaPreflightDone() {
export function setCSRFFromCookie(url) {
return new Promise((resolve) => {
CookieManager.get(url, false).then((res) => {
const token = res.MMCSRF;
CookieManager.get(url, false).then((cookies) => {
const token = cookies.MMCSRF;
if (token) {
let value = null;
if (typeof token === 'object' && Object.prototype.hasOwnProperty.call(token, 'value')) {

View file

@ -1,15 +1,27 @@
diff --git a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
index 7053040..2a97b04 100644
index 7053040..ad63eff 100644
--- a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
+++ b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
@@ -29,6 +29,7 @@
@@ -11,6 +11,7 @@
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
@@ -29,7 +30,11 @@
<service
android:name=".fcm.FcmInstanceIdRefreshHandlerService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
+ <receiver android:name=".core.notification.PushNotificationPublisher"
+ android:enabled="true"
+ android:exported="false" />
</application>
</manifest>
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
index 7b47aed..200cca8 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java