forked from Ivasoft/mattermost-mobile
Gekidou session expired notification (#6639)
* Fix crash on Android when session expired notification is presented * react-native-notification patch for schedule fix on android and open local notification on iOS * Fix android scheduled session notification crash * patch react-native-navigation to support blur/focus AppState on Android * remove schedule session expired notification from login entry point * schedule session expired notification actions * add session manager * Handle open session expired notification
This commit is contained in:
@@ -2,14 +2,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {getSessions} from '@actions/remote/session';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logWarning} from '@utils/log';
|
||||
import {scheduleExpiredNotification} from '@utils/notification';
|
||||
|
||||
import {deferredAppEntryActions, entry} from './gql_common';
|
||||
|
||||
@@ -50,27 +47,6 @@ export async function loginEntry({serverUrl, user, deviceToken}: AfterLoginArgs)
|
||||
return {error: clData.error};
|
||||
}
|
||||
|
||||
// schedule local push notification if needed
|
||||
if (clData.config) {
|
||||
if (clData.config.ExtendSessionLengthWithActivity !== 'true') {
|
||||
const timeOut = setTimeout(async () => {
|
||||
clearTimeout(timeOut);
|
||||
let sessions: Session[]|undefined;
|
||||
|
||||
try {
|
||||
sessions = await getSessions(serverUrl, 'me');
|
||||
} catch (e) {
|
||||
logWarning('Failed to get user sessions', e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessions && Array.isArray(sessions)) {
|
||||
scheduleExpiredNotification(sessions, clData.config?.SiteName || serverUrl, user.locale);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
const entryData = await entry(serverUrl, '', '');
|
||||
|
||||
if ('error' in entryData) {
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import {DeviceEventEmitter, Platform} from 'react-native';
|
||||
|
||||
import {Database, Events} from '@constants';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getServerCredentials} from '@init/credentials';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import WebsocketManager from '@managers/websocket_manager';
|
||||
import {getDeviceToken} from '@queries/app/global';
|
||||
import {getCurrentUserId, getCommonSystemValues} from '@queries/servers/system';
|
||||
import {queryServerName} from '@queries/app/servers';
|
||||
import {getCurrentUserId, getCommonSystemValues, getExpiredSession} from '@queries/servers/system';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {logWarning} from '@utils/log';
|
||||
import {logWarning, logError} from '@utils/log';
|
||||
import {scheduleExpiredNotification} from '@utils/notification';
|
||||
import {getCSRFFromCookie} from '@utils/security';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone';
|
||||
|
||||
@@ -91,7 +96,7 @@ export const forceLogoutIfNecessary = async (serverUrl: string, err: ClientError
|
||||
return {error: null};
|
||||
};
|
||||
|
||||
export const getSessions = async (serverUrl: string, currentUserId: string) => {
|
||||
export const fetchSessions = async (serverUrl: string, currentUserId: string) => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
@@ -102,6 +107,7 @@ export const getSessions = async (serverUrl: string, currentUserId: string) => {
|
||||
try {
|
||||
return await client.getSessions(currentUserId);
|
||||
} catch (e) {
|
||||
logError('fetchSessions', e);
|
||||
await forceLogoutIfNecessary(serverUrl, e as ClientError);
|
||||
}
|
||||
|
||||
@@ -166,7 +172,7 @@ export const login = async (serverUrl: string, {ldapOnly = false, loginId, mfaTo
|
||||
}
|
||||
};
|
||||
|
||||
export const logout = async (serverUrl: string, skipServerLogout = false, removeServer = false) => {
|
||||
export const logout = async (serverUrl: string, skipServerLogout = false, removeServer = false, skipEvents = false) => {
|
||||
if (!skipServerLogout) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
@@ -177,7 +183,65 @@ export const logout = async (serverUrl: string, skipServerLogout = false, remove
|
||||
}
|
||||
}
|
||||
|
||||
DeviceEventEmitter.emit(Events.SERVER_LOGOUT, {serverUrl, removeServer});
|
||||
if (!skipEvents) {
|
||||
DeviceEventEmitter.emit(Events.SERVER_LOGOUT, {serverUrl, removeServer});
|
||||
}
|
||||
};
|
||||
|
||||
export const cancelSessionNotification = async (serverUrl: string) => {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const expiredSession = await getExpiredSession(database);
|
||||
const rechable = (await NetInfo.fetch()).isInternetReachable;
|
||||
|
||||
if (expiredSession?.notificationId && rechable) {
|
||||
PushNotifications.cancelScheduleNotification(parseInt(expiredSession.notificationId, 10));
|
||||
operator.handleSystem({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.SESSION_EXPIRATION,
|
||||
value: '',
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logError('cancelSessionNotification', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const scheduleSessionNotification = async (serverUrl: string) => {
|
||||
try {
|
||||
const {database: appDatabase} = DatabaseManager.getAppDatabaseAndOperator();
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const sessions = await fetchSessions(serverUrl, 'me');
|
||||
const user = await getCurrentUser(database);
|
||||
const serverName = await queryServerName(appDatabase, serverUrl);
|
||||
|
||||
await cancelSessionNotification(serverUrl);
|
||||
|
||||
if (sessions) {
|
||||
const session = await findSession(serverUrl, sessions);
|
||||
|
||||
if (session) {
|
||||
const sessionId = session.id;
|
||||
const notificationId = scheduleExpiredNotification(serverUrl, session, serverName, user?.locale);
|
||||
operator.handleSystem({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.SESSION_EXPIRATION,
|
||||
value: {
|
||||
id: sessionId,
|
||||
notificationId,
|
||||
expiresAt: session.expires_at,
|
||||
},
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('scheduleExpiredNotification', e);
|
||||
await forceLogoutIfNecessary(serverUrl, e as ClientError);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendPasswordResetEmail = async (serverUrl: string, email: string) => {
|
||||
@@ -251,3 +315,47 @@ export const ssoLogin = async (serverUrl: string, serverDisplayName: string, ser
|
||||
return {error: error as ClientError, failed: false, time: 0};
|
||||
}
|
||||
};
|
||||
|
||||
async function findSession(serverUrl: string, sessions: Session[]) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const {database: appDatabase} = DatabaseManager.getAppDatabaseAndOperator();
|
||||
const expiredSession = await getExpiredSession(database);
|
||||
const deviceToken = await getDeviceToken(appDatabase);
|
||||
|
||||
// First try and find the session by the given identifier hyqddef7jjdktqiyy36gxa8sqy
|
||||
let session = sessions.find((s) => s.id === expiredSession?.id);
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
|
||||
// Next try and find the session by deviceId
|
||||
if (deviceToken) {
|
||||
session = sessions.find((s) => s.device_id === deviceToken);
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
// Next try and find the session by the CSRF token
|
||||
const csrfToken = await getCSRFFromCookie(serverUrl);
|
||||
if (csrfToken) {
|
||||
session = sessions.find((s) => s.props?.csrf === csrfToken);
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
// Next try and find the session based on the OS
|
||||
// if multiple sessions exists with the same os type this can be inaccurate
|
||||
session = sessions.find((s) => s.props?.os.toLowerCase() === Platform.OS);
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
} catch (e) {
|
||||
logError('findSession', e);
|
||||
}
|
||||
|
||||
// At this point we did not find the session
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user