Do not schedule session expiry notification for 5.24+ (#4291)
This commit is contained in:
parent
be2b0a03e6
commit
c04c1f26f7
12 changed files with 156 additions and 70 deletions
68
app/actions/views/session.js
Normal file
68
app/actions/views/session.js
Normal 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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')) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue