// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import CookieManager, {Cookie} from '@react-native-cookies/cookies'; import {AppState, AppStateStatus, DeviceEventEmitter, Platform} from 'react-native'; import FastImage from 'react-native-fast-image'; import {storeOnboardingViewedValue} from '@actions/app/global'; import {cancelSessionNotification, logout, scheduleSessionNotification} from '@actions/remote/session'; import {Events, Launch} from '@constants'; import DatabaseManager from '@database/manager'; import {resetMomentLocale} from '@i18n'; import {getAllServerCredentials, removeServerCredentials} from '@init/credentials'; import {relaunchApp} from '@init/launch'; import PushNotifications from '@init/push_notifications'; import * as analytics from '@managers/analytics'; import NetworkManager from '@managers/network_manager'; import WebsocketManager from '@managers/websocket_manager'; import {getAllServers, getServerDisplayName} from '@queries/app/servers'; import {getCurrentUser} from '@queries/servers/user'; import {getThemeFromState} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {deleteFileCache, deleteFileCacheByDir} from '@utils/file'; import {isMainActivity} from '@utils/helpers'; import {addNewServer} from '@utils/server'; import type {LaunchType} from '@typings/launch'; type LogoutCallbackArg = { serverUrl: string; removeServer: boolean; } class SessionManager { private previousAppState: AppStateStatus; private scheduling = false; private terminatingSessionUrl: undefined|string; constructor() { if (Platform.OS === 'android') { AppState.addEventListener('blur', () => { this.onAppStateChange('inactive'); }); AppState.addEventListener('focus', () => { this.onAppStateChange('active'); }); } else { AppState.addEventListener('change', this.onAppStateChange); } DeviceEventEmitter.addListener(Events.SERVER_LOGOUT, this.onLogout); DeviceEventEmitter.addListener(Events.SESSION_EXPIRED, this.onSessionExpired); this.previousAppState = AppState.currentState; } init() { this.cancelAllSessionNotifications(); } private cancelAllSessionNotifications = async () => { const serverCredentials = await getAllServerCredentials(); for (const {serverUrl} of serverCredentials) { cancelSessionNotification(serverUrl); } }; private clearCookies = async (serverUrl: string, webKit: boolean) => { try { const cookies = await CookieManager.get(serverUrl, webKit); const values = Object.values(cookies); values.forEach((cookie: Cookie) => { CookieManager.clearByName(serverUrl, cookie.name, webKit); }); } catch (error) { // Nothing to clear } }; private clearCookiesForServer = async (serverUrl: string) => { if (Platform.OS === 'ios') { this.clearCookies(serverUrl, false); // Also delete any cookies that were set by react-native-webview this.clearCookies(serverUrl, true); } else if (Platform.OS === 'android') { CookieManager.flush(); } }; private scheduleAllSessionNotifications = async () => { if (!this.scheduling) { this.scheduling = true; const serverCredentials = await getAllServerCredentials(); const promises: Array> = []; for (const {serverUrl} of serverCredentials) { promises.push(scheduleSessionNotification(serverUrl)); } await Promise.all(promises); this.scheduling = false; } }; private resetLocale = async () => { if (Object.keys(DatabaseManager.serverDatabases).length) { const serverDatabase = await DatabaseManager.getActiveServerDatabase(); const user = await getCurrentUser(serverDatabase!); resetMomentLocale(user?.locale); } else { resetMomentLocale(); } }; private terminateSession = async (serverUrl: string, removeServer: boolean) => { cancelSessionNotification(serverUrl); await removeServerCredentials(serverUrl); PushNotifications.removeServerNotifications(serverUrl); NetworkManager.invalidateClient(serverUrl); WebsocketManager.invalidateClient(serverUrl); if (removeServer) { await DatabaseManager.destroyServerDatabase(serverUrl); } else { await DatabaseManager.deleteServerDatabase(serverUrl); } const analyticsClient = analytics.get(serverUrl); if (analyticsClient) { analyticsClient.reset(); analytics.invalidate(serverUrl); } this.resetLocale(); this.clearCookiesForServer(serverUrl); FastImage.clearDiskCache(); deleteFileCache(serverUrl); deleteFileCacheByDir('mmPasteInput'); deleteFileCacheByDir('thumbnails'); if (Platform.OS === 'android') { deleteFileCacheByDir('image_cache'); } }; private onAppStateChange = async (appState: AppStateStatus) => { if (appState === this.previousAppState || !isMainActivity()) { return; } this.previousAppState = appState; switch (appState) { case 'active': setTimeout(this.cancelAllSessionNotifications, 750); break; case 'inactive': this.scheduleAllSessionNotifications(); break; } }; private onLogout = async ({serverUrl, removeServer}: LogoutCallbackArg) => { if (this.terminatingSessionUrl === serverUrl) { return; } const activeServerUrl = await DatabaseManager.getActiveServerUrl(); const activeServerDisplayName = await DatabaseManager.getActiveServerDisplayName(); await this.terminateSession(serverUrl, removeServer); if (activeServerUrl === serverUrl) { let displayName = ''; let launchType: LaunchType = Launch.AddServer; if (!Object.keys(DatabaseManager.serverDatabases).length) { EphemeralStore.theme = undefined; launchType = Launch.Normal; if (activeServerDisplayName) { displayName = activeServerDisplayName; } } // set the onboardingViewed value to false so the launch will show the onboarding screen after all servers were removed const servers = await getAllServers(); if (!servers.length) { await storeOnboardingViewedValue(false); } relaunchApp({launchType, serverUrl, displayName}); } }; private onSessionExpired = async (serverUrl: string) => { this.terminatingSessionUrl = serverUrl; await logout(serverUrl, false, false, true); await this.terminateSession(serverUrl, false); const activeServerUrl = await DatabaseManager.getActiveServerUrl(); const serverDisplayName = await getServerDisplayName(serverUrl); await relaunchApp({launchType: Launch.Normal, serverUrl, displayName: serverDisplayName}); if (activeServerUrl) { addNewServer(getThemeFromState(), serverUrl, serverDisplayName); } else { EphemeralStore.theme = undefined; } this.terminatingSessionUrl = undefined; }; } export default new SessionManager();