Files
mattermost-mobile/app/mattermost.js
Elias Nahum 3feaa8e6bb iOS Native Share Extension (Swift) (#2575)
* iOS Native Share Extension (Swift)

* Re-arrange files

* Fix .gitignore
2019-02-26 14:31:57 -03:00

479 lines
15 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable global-require*/
import {
Alert,
AppState,
Dimensions,
InteractionManager,
Keyboard,
NativeModules,
Platform,
YellowBox,
} from 'react-native';
const {StatusBarManager, MattermostShare, Initialization} = NativeModules;
import DeviceInfo from 'react-native-device-info';
import {Navigation, NativeEventsReceiver} from 'react-native-navigation';
import {Provider} from 'react-redux';
import semver from 'semver';
import {Client4} from 'mattermost-redux/client';
import {setAppState, setServerVersion} from 'mattermost-redux/actions/general';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {selectDefaultChannel} from 'app/actions/views/channel';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import {loadConfigAndLicense, startDataCleanup} from 'app/actions/views/root';
import initialState from 'app/initial_state';
import configureStore from 'app/store';
import {NavigationTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {configurePushNotifications} from 'app/utils/push_notifications';
import PushNotifications from 'app/push_notifications';
import {registerScreens} from 'app/screens';
import {deleteFileCache} from 'app/utils/file';
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import {t} from 'app/utils/i18n';
import LocalConfig from 'assets/config';
import App from './app';
import './fetch_preconfig';
const AUTHENTICATION_TIMEOUT = 5 * 60 * 1000;
// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
YellowBox.ignoreWarnings(['Require cycle: node_modules/react-native/Libraries/Network/fetch.js']);
export const app = new App();
export const store = configureStore(initialState);
registerScreens(store, Provider);
const lazyLoadExternalModules = () => {
const StatusBarSizeIOS = require('react-native-status-bar-size');
const initializeErrorHandling = require('app/utils/error_handling').initializeErrorHandling;
return {
StatusBarSizeIOS,
initializeErrorHandling,
};
};
const lazyLoadAnalytics = () => {
const initAnalytics = require('app/utils/segment').init;
return {
initAnalytics,
};
};
const initializeModules = () => {
const {
StatusBarSizeIOS,
initializeErrorHandling,
} = lazyLoadExternalModules();
const {
config,
} = store.getState().entities.general;
const window = Dimensions.get('window');
initializeErrorHandling();
EventEmitter.on(NavigationTypes.NAVIGATION_RESET, handleLogout);
EventEmitter.on(NavigationTypes.RESTART_APP, restartApp);
EventEmitter.on(General.SERVER_VERSION_CHANGED, handleServerVersionChanged);
EventEmitter.on(General.CONFIG_CHANGED, handleConfigChanged);
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, handleSwithToDefaultChannel);
Dimensions.addEventListener('change', handleOrientationChange);
mattermostManaged.addEventListener('managedConfigDidChange', () => {
handleManagedConfig(true);
});
if (config) {
configureAnalytics(config);
}
handleOrientationChange({window});
if (Platform.OS === 'ios') {
StatusBarSizeIOS.addEventListener('willChange', handleStatusBarHeightChange);
StatusBarManager.getHeight(
(data) => {
handleStatusBarHeightChange(data.height);
}
);
}
};
const configureAnalytics = (config) => {
const {
initAnalytics,
} = lazyLoadAnalytics();
if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) {
initAnalytics(config);
} else {
global.analytics = null;
}
};
const resetBadgeAndVersion = () => {
Client4.serverVersion = '';
Client4.setUserId('');
PushNotifications.setApplicationIconBadgeNumber(0);
PushNotifications.cancelAllLocalNotifications();
store.dispatch(setServerVersion(''));
};
const handleLogout = () => {
// Because we can logout while being offline we reset
// the Client online flag to true cause the network handler
// is not available at this point
Client4.setOnline(true);
Client4.setCSRF(null);
store.dispatch(closeWebSocket(false));
app.setAppStarted(true);
app.clearNativeCache();
deleteFileCache();
resetBadgeAndVersion();
launchSelectServer();
};
const restartApp = async () => {
Navigation.dismissModal({animationType: 'none'});
try {
await store.dispatch(loadConfigAndLicense());
await store.dispatch(loadMe());
} catch (e) {
console.warn('Failed to load initial data while restarting', e); // eslint-disable-line no-console
}
launchChannel();
};
const handleServerVersionChanged = async (serverVersion) => {
const {dispatch, getState} = store;
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
const translations = app.getTranslations();
const state = getState();
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: handleServerVersionUpgradeNeeded,
}],
{cancelable: false}
);
} else if (state.entities.users && state.entities.users.currentUserId) {
dispatch(setServerVersion(serverVersion));
const data = await dispatch(loadConfigAndLicense());
configureAnalytics(data.config);
}
}
};
const handleConfigChanged = (config) => {
configureAnalytics(config);
};
const handleServerVersionUpgradeNeeded = async () => {
const {dispatch, getState} = store;
resetBadgeAndVersion();
if (getState().entities.general.credentials.token) {
InteractionManager.runAfterInteractions(() => {
dispatch(logout());
});
}
};
const handleStatusBarHeightChange = (nextStatusBarHeight) => {
store.dispatch(setStatusBarHeight(nextStatusBarHeight));
};
const handleOrientationChange = (dimensions) => {
const {dispatch} = store;
if (DeviceInfo.isTablet()) {
dispatch(setDeviceAsTablet());
}
const {height, width} = dimensions.window;
const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE';
dispatch(setDeviceOrientation(orientation));
dispatch(setDeviceDimensions(height, width));
};
export const handleManagedConfig = async (eventFromEmmServer = false) => {
if (app.performingEMMAuthentication) {
return true;
}
const {dispatch, getState} = store;
const state = getState();
let authNeeded = false;
let blurApplicationScreen = false;
let jailbreakProtection = false;
let vendor = null;
let serverUrl = null;
let username = null;
if (LocalConfig.AutoSelectServerUrl) {
dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl));
app.setAllowOtherServers(false);
}
try {
const config = await avoidNativeBridge(
() => {
return true;
},
() => {
return Initialization.managedConfig;
},
() => {
return mattermostManaged.getConfig();
}
);
if (config && Object.keys(config).length) {
app.setEMMEnabled(true);
authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
blurApplicationScreen = config.blurApplicationScreen && config.blurApplicationScreen === 'true';
jailbreakProtection = config.jailbreakProtection && config.jailbreakProtection === 'true';
vendor = config.vendor || 'Mattermost';
if (!state.entities.general.credentials.token) {
serverUrl = config.serverUrl;
username = config.username;
if (config.allowOtherServers && config.allowOtherServers === 'false') {
app.setAllowOtherServers(false);
}
}
if (jailbreakProtection) {
const isTrusted = mattermostManaged.isTrustedDevice();
if (!isTrusted) {
const translations = app.getTranslations();
Alert.alert(
translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
translations[t('mobile.managed.jailbreak')].replace('{vendor}', vendor),
[{
text: translations[t('mobile.managed.exit')],
style: 'destructive',
onPress: () => {
mattermostManaged.quitApp();
},
}],
{cancelable: false}
);
return false;
}
}
if (authNeeded && !eventFromEmmServer) {
const authenticated = await handleAuthentication(vendor);
if (!authenticated) {
return false;
}
}
if (blurApplicationScreen) {
mattermostManaged.blurAppScreen(true);
}
if (serverUrl) {
dispatch(handleServerUrlChanged(serverUrl));
}
if (username) {
dispatch(handleLoginIdChanged(username));
}
}
} catch (error) {
return true;
}
return true;
};
const handleAuthentication = async (vendor) => {
app.setPerformingEMMAuthentication(true);
const isSecured = await mattermostManaged.isDeviceSecure();
const translations = app.getTranslations();
if (isSecured) {
try {
mattermostBucket.setPreference('emm', vendor);
await mattermostManaged.authenticate({
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor),
fallbackToPasscode: true,
suppressEnterPassword: true,
});
} catch (err) {
mattermostManaged.quitApp();
return false;
}
}
app.setPerformingEMMAuthentication(false);
return true;
};
const handleSwithToDefaultChannel = (teamId) => {
store.dispatch(selectDefaultChannel(teamId));
};
const launchSelectServer = () => {
Navigation.startSingleScreenApp({
screen: {
screen: 'SelectServer',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
},
passProps: {
allowOtherServers: app.allowOtherServers,
},
appStyle: {
orientation: 'auto',
},
animationType: 'fade',
});
};
const launchChannel = () => {
Navigation.startSingleScreenApp({
screen: {
screen: 'Channel',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
},
appStyle: {
orientation: 'auto',
},
animationType: 'fade',
});
};
const handleAppStateChange = (appState) => {
const isActive = appState === 'active';
store.dispatch(setAppState(isActive));
if (isActive) {
handleAppActive();
return;
}
handleAppInActive();
};
const handleAppActive = async () => {
const authExpired = (Date.now() - app.inBackgroundSince) >= AUTHENTICATION_TIMEOUT;
// This handles when the app was started in the background
// cause of an iOS push notification reply
if (Platform.OS === 'ios' && app.shouldRelaunchWhenActive) {
app.launchApp();
app.setShouldRelaunchWhenActive(false);
}
// Once the app becomes active after more than 5 minutes in the background and is controlled by an EMM Provider
if (app.emmEnabled && app.inBackgroundSince && authExpired) {
try {
const config = await mattermostManaged.getConfig();
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
if (authNeeded) {
await handleAuthentication(config.vendor);
}
} catch (error) {
// do nothing
}
}
app.setInBackgroundSince(null);
Keyboard.dismiss();
};
const handleAppInActive = () => {
const {dispatch, getState} = store;
const theme = getTheme(getState());
// 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
app.setInBackgroundSince(Date.now());
app.setStartupThemes(
theme.sidebarHeaderBg,
theme.sidebarHeaderTextColor,
theme.centerChannelBg,
);
dispatch(startDataCleanup());
};
AppState.addEventListener('change', handleAppStateChange);
const launchEntry = () => {
Navigation.startSingleScreenApp({
screen: {
screen: 'Entry',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
},
},
passProps: {
initializeModules,
},
appStyle: {
orientation: 'auto',
},
animationType: 'fade',
});
};
configurePushNotifications();
const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened;
const fromPushNotification = Platform.OS === 'android' && Initialization.replyFromPushNotification;
if (startedSharedExtension || fromPushNotification) {
// Hold on launching Entry screen
app.setAppStarted(true);
// Listen for when the user opens the app
new NativeEventsReceiver().appLaunched(() => {
app.setAppStarted(false);
launchEntry();
});
}
if (!app.appStarted) {
launchEntry();
}