359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {AppState, Dimensions, Linking, Platform} from 'react-native';
|
|
import AsyncStorage from '@react-native-community/async-storage';
|
|
import CookieManager from '@react-native-cookies/cookies';
|
|
import DeviceInfo from 'react-native-device-info';
|
|
import {getLocales} from 'react-native-localize';
|
|
|
|
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet} 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 {resetMomentLocale} from '@i18n';
|
|
import {setupPermanentSidebar} from '@init/device';
|
|
import PushNotifications from '@init/push_notifications';
|
|
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
|
|
import {getTeams} from '@mm-redux/actions/teams';
|
|
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
|
|
import {close as closeWebSocket} from '@actions/websocket';
|
|
import {Client4} from '@client/rest';
|
|
import {General} from '@mm-redux/constants';
|
|
import {getConfig} from '@mm-redux/selectors/entities/general';
|
|
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 {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
|
import initialState from '@store/initial_state';
|
|
import Store from '@store/store';
|
|
import {deleteFileCache} from '@utils/file';
|
|
import {getDeviceTimezone} from '@utils/timezone';
|
|
|
|
import mattermostBucket from 'app/mattermost_bucket';
|
|
import mattermostManaged from 'app/mattermost_managed';
|
|
|
|
import {getAppCredentials, removeAppCredentials} from './credentials';
|
|
import emmProvider from './emm_provider';
|
|
import {analytics} from '@init/analytics.ts';
|
|
|
|
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000;
|
|
|
|
class GlobalEventHandler {
|
|
constructor() {
|
|
this.pushNotificationListener = false;
|
|
|
|
EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.onLogout);
|
|
EventEmitter.on(NavigationTypes.RESTART_APP, this.onRestartApp);
|
|
EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
|
|
EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged);
|
|
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel);
|
|
Dimensions.addEventListener('change', this.onOrientationChange);
|
|
AppState.addEventListener('change', this.onAppStateChange);
|
|
Linking.addEventListener('url', this.onDeepLink);
|
|
}
|
|
|
|
appActive = async () => {
|
|
this.turnOnInAppNotificationHandling();
|
|
this.setUserTimezone();
|
|
|
|
// if the app is being controlled by an EMM provider
|
|
if (emmProvider.enabled && emmProvider.inAppPinCode) {
|
|
const authExpired = (Date.now() - emmProvider.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;
|
|
|
|
// Once the app becomes active we check if the device needs to have a passcode set
|
|
const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
|
|
await emmProvider.handleAuthentication(prompt);
|
|
}
|
|
|
|
emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */
|
|
};
|
|
|
|
appInactive = () => {
|
|
this.turnOffInAppNotificationHandling();
|
|
|
|
const {dispatch} = Store.redux;
|
|
|
|
// When the app is sent to the background we set the time when that happens
|
|
// and perform a data clean up to improve on performance
|
|
emmProvider.inBackgroundSince = Date.now();
|
|
|
|
dispatch(startDataCleanup());
|
|
};
|
|
|
|
configure = (opts) => {
|
|
this.launchApp = opts.launchApp;
|
|
|
|
// onAppStateChange may be called by the AppState listener before we
|
|
// configure the global event handler so we manually call it here
|
|
this.onAppStateChange('active');
|
|
|
|
const window = Dimensions.get('window');
|
|
this.onOrientationChange({window});
|
|
|
|
this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default;
|
|
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(Store.redux);
|
|
|
|
mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
|
|
};
|
|
|
|
configureAnalytics = async () => {
|
|
const state = Store.redux.getState();
|
|
const config = getConfig(state);
|
|
|
|
if (config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.RudderApiKey) {
|
|
await analytics.init(config);
|
|
}
|
|
};
|
|
|
|
onAppStateChange = (appState) => {
|
|
const isActive = appState === 'active';
|
|
const isBackground = appState === 'background';
|
|
|
|
if (Store.redux) {
|
|
Store.redux.dispatch(setAppState(isActive));
|
|
|
|
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
|
|
this.appActive();
|
|
} else if (isBackground) {
|
|
this.appInactive();
|
|
}
|
|
}
|
|
|
|
emmProvider.previousAppState = appState;
|
|
};
|
|
|
|
onDeepLink = (event) => {
|
|
const {url} = event;
|
|
if (url) {
|
|
Store.redux.dispatch(setDeepLinkURL(url));
|
|
}
|
|
};
|
|
|
|
onManagedConfigurationChange = () => {
|
|
emmProvider.handleManagedConfig(true);
|
|
};
|
|
|
|
clearCookiesAndWebData = async () => {
|
|
try {
|
|
await CookieManager.clearAll(true);
|
|
|
|
if (Platform.OS === 'ios') {
|
|
// on iOS we will also detele the Cookies stored by NSCookieStore
|
|
await CookieManager.clearAll(false);
|
|
}
|
|
} catch (error) {
|
|
// Nothing to do
|
|
}
|
|
};
|
|
|
|
onLogout = async () => {
|
|
Store.redux.dispatch(closeWebSocket(false));
|
|
Store.redux.dispatch(setServerVersion(''));
|
|
|
|
await analytics.reset();
|
|
|
|
mattermostBucket.removePreference('cert');
|
|
mattermostBucket.removePreference('emm');
|
|
if (Platform.OS === 'ios') {
|
|
mattermostBucket.removeFile('entities');
|
|
}
|
|
|
|
removeAppCredentials();
|
|
deleteFileCache();
|
|
resetMomentLocale();
|
|
|
|
await this.clearCookiesAndWebData();
|
|
PushNotifications.clearNotifications();
|
|
|
|
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) => {
|
|
if (Store.redux) {
|
|
const {dispatch, getState} = Store.redux;
|
|
const deviceState = getState().device;
|
|
|
|
if (DeviceInfo.isTablet()) {
|
|
dispatch(setDeviceAsTablet());
|
|
}
|
|
|
|
const {height, width} = dimensions.window;
|
|
const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE';
|
|
const savedOrientation = deviceState?.orientation;
|
|
const savedDimension = deviceState?.dimension;
|
|
|
|
if (orientation !== savedOrientation) {
|
|
dispatch(setDeviceOrientation(orientation));
|
|
}
|
|
|
|
if (height !== savedDimension?.deviceHeight ||
|
|
width !== savedDimension?.deviceWidth) {
|
|
dispatch(setDeviceDimensions(height, width));
|
|
}
|
|
}
|
|
};
|
|
|
|
onRestartApp = async () => {
|
|
const {dispatch, getState} = Store.redux;
|
|
const state = getState();
|
|
const {currentUserId} = state.entities.users;
|
|
const user = getUser(state, currentUserId);
|
|
|
|
await dispatch(loadConfigAndLicense());
|
|
await dispatch(loadMe(user));
|
|
dispatch(getTeams());
|
|
|
|
const window = Dimensions.get('window');
|
|
this.onOrientationChange({window});
|
|
};
|
|
|
|
onServerConfigChanged = (config) => {
|
|
this.configureAnalytics(config);
|
|
|
|
if (isMinimumServerVersion(Client4.serverVersion, 5, 24) && config.ExtendSessionLengthWithActivity === 'true') {
|
|
PushNotifications.cancelAllLocalNotifications();
|
|
}
|
|
};
|
|
|
|
onServerVersionChanged = async (serverVersion) => {
|
|
const {dispatch, getState} = Store.redux;
|
|
const state = getState();
|
|
if (serverVersion && state.entities.users && state.entities.users.currentUserId) {
|
|
dispatch(setServerVersion(serverVersion));
|
|
dispatch(loadConfigAndLicense());
|
|
}
|
|
};
|
|
|
|
onSwitchToDefaultChannel = (teamId) => {
|
|
Store.redux.dispatch(selectDefaultChannel(teamId));
|
|
};
|
|
|
|
resetState = async () => {
|
|
try {
|
|
await AsyncStorage.clear();
|
|
await setupPermanentSidebar();
|
|
const state = Store.redux.getState();
|
|
const newState = {
|
|
...initialState,
|
|
app: {
|
|
build: DeviceInfo.getBuildNumber(),
|
|
version: DeviceInfo.getVersion(),
|
|
previousVersion: DeviceInfo.getVersion(),
|
|
},
|
|
entities: {
|
|
...initialState.entities,
|
|
general: {
|
|
...initialState.entities.general,
|
|
deviceToken: state.entities.general.deviceToken,
|
|
},
|
|
},
|
|
views: {
|
|
i18n: {
|
|
locale: getLocales()[0].languageTag,
|
|
},
|
|
root: {
|
|
hydrationComplete: true,
|
|
},
|
|
selectServer: {
|
|
serverUrl: state.views.selectServer.serverUrl,
|
|
},
|
|
},
|
|
_persist: {
|
|
rehydrated: true,
|
|
},
|
|
};
|
|
|
|
return Store.redux.dispatch({
|
|
type: General.OFFLINE_STORE_PURGE,
|
|
data: newState,
|
|
});
|
|
} catch (e) {
|
|
// clear error
|
|
return e;
|
|
}
|
|
}
|
|
|
|
serverUpgradeNeeded = async () => {
|
|
const {dispatch} = Store.redux;
|
|
|
|
dispatch(setServerVersion(''));
|
|
Client4.serverVersion = '';
|
|
|
|
const credentials = await getAppCredentials();
|
|
|
|
if (credentials) {
|
|
dispatch(logout());
|
|
}
|
|
};
|
|
|
|
turnOnInAppNotificationHandling = () => {
|
|
if (!this.pushNotificationListener) {
|
|
this.pushNotificationListener = true;
|
|
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
|
}
|
|
}
|
|
|
|
turnOffInAppNotificationHandling = () => {
|
|
this.pushNotificationListener = false;
|
|
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
|
}
|
|
|
|
handleInAppNotification = (notification) => {
|
|
const {payload} = notification;
|
|
const {getState} = Store.redux;
|
|
const state = getState();
|
|
const currentChannelId = getCurrentChannelId(state);
|
|
|
|
if (payload?.channel_id !== currentChannelId) {
|
|
const screen = 'Notification';
|
|
const passProps = {
|
|
notification,
|
|
};
|
|
|
|
EventEmitter.emit(NavigationTypes.NAVIGATION_SHOW_OVERLAY);
|
|
showOverlay(screen, passProps);
|
|
}
|
|
};
|
|
|
|
setUserTimezone = async () => {
|
|
const {dispatch, getState} = Store.redux;
|
|
const state = getState();
|
|
const currentUser = getCurrentUser(state);
|
|
|
|
const enableTimezone = isTimezoneEnabled(state);
|
|
if (enableTimezone && currentUser.id) {
|
|
const timezone = getDeviceTimezone();
|
|
const {
|
|
automaticTimezone,
|
|
manualTimezone,
|
|
useAutomaticTimezone,
|
|
} = currentUser.timezone;
|
|
let updateTimeZone = false;
|
|
|
|
if (useAutomaticTimezone) {
|
|
updateTimeZone = timezone !== automaticTimezone;
|
|
} else {
|
|
updateTimeZone = timezone !== manualTimezone;
|
|
}
|
|
|
|
if (updateTimeZone) {
|
|
dispatch(autoUpdateTimezone(timezone));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export default new GlobalEventHandler();
|