MM-16430 refactor app entry point (#2924)

* MM-16430 refactor app entry point

* iOS Extensions to use session token from KeyChain

* Remove token from general credentials entity

* Fix mattermost-managed.ios to return the cachedConfig

* Migrate server based keychain for android push notifications

* remove unneeded async

* Remove unneeded android InitializationModule
This commit is contained in:
Elias Nahum 2019-06-26 15:21:20 -04:00 committed by Harrison Healey
parent ce4b48b695
commit 89b96d51db
38 changed files with 1094 additions and 1886 deletions

View file

@ -1,143 +0,0 @@
package com.mattermost.rnbeta;
import android.app.Application;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.mattermost.react_native_interface.AsyncStorageHelper;
import com.mattermost.react_native_interface.KeysReadableArray;
import com.mattermost.react_native_interface.ResolvePromise;
import com.oblador.keychain.KeychainModule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class InitializationModule extends ReactContextBaseJavaModule {
static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND";
static final String TOOLBAR_TEXT_COLOR = "TOOLBAR_TEXT_COLOR";
static final String APP_BACKGROUND = "APP_BACKGROUND";
private final Application mApplication;
public InitializationModule(Application application, ReactApplicationContext reactContext) {
super(reactContext);
mApplication = application;
}
@Override
public String getName() {
return "Initialization";
}
@Nullable
@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
/**
* Package all native module variables in constants
* in order to avoid the native bridge
*
* KeyStore:
* credentialsExist
* deviceToken
* currentUserId
* token
* url
*
* AsyncStorage:
* toolbarBackground
* toolbarTextColor
* appBackground
*
* Miscellaneous:
* MattermostManaged.Config
*/
MainApplication app = (MainApplication) mApplication;
final Boolean[] credentialsExist = {false};
final WritableMap[] credentials = {null};
final Object[] config = {null};
// Get KeyStore credentials
KeychainModule module = new KeychainModule(this.getReactApplicationContext());
module.getGenericPasswordForOptions(null, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
credentialsExist[0] = false;
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
credentialsExist[0] = true;
credentials[0] = map;
}
}
});
// Get managedConfig from MattermostManagedModule
MattermostManagedModule.getInstance().getConfig(new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
WritableNativeMap nativeMap = (WritableNativeMap) value;
config[0] = value;
}
});
// Get AsyncStorage key/values
final ArrayList<String> keys = new ArrayList<String>(5);
keys.add(TOOLBAR_BACKGROUND);
keys.add(TOOLBAR_TEXT_COLOR);
keys.add(APP_BACKGROUND);
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
@Override
public int size() {
return keys.size();
}
@Override
public String getString(int index) {
return keys.get(index);
}
};
AsyncStorageHelper asyncStorage = new AsyncStorageHelper(this.getReactApplicationContext());
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
String toolbarBackground = asyncStorageResults.get(TOOLBAR_BACKGROUND);
String toolbarTextColor = asyncStorageResults.get(TOOLBAR_TEXT_COLOR);
String appBackground = asyncStorageResults.get(APP_BACKGROUND);
if (toolbarBackground != null
&& toolbarTextColor != null
&& appBackground != null) {
constants.put("themesExist", true);
constants.put("toolbarBackground", toolbarBackground);
constants.put("toolbarTextColor", toolbarTextColor);
constants.put("appBackground", appBackground);
} else {
constants.put("themesExist", false);
}
if (credentialsExist[0]) {
constants.put("credentialsExist", true);
constants.put("credentials", credentials[0]);
} else {
constants.put("credentialsExist", false);
}
constants.put("managedConfig", config[0]);
return constants;
}
}

View file

@ -1,36 +0,0 @@
package com.mattermost.rnbeta;
import android.app.Application;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class InitializationPackage implements ReactPackage {
private final Application mApplication;
public InitializationPackage(Application application) {
mApplication = application;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new InitializationModule(mApplication, reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View file

@ -100,7 +100,6 @@ public class MainApplication extends NavigationApplication implements INotificat
new ReactNativeDocumentPicker(),
new SharePackage(this),
new KeychainPackage(),
new InitializationPackage(this),
new AsyncStoragePackage(),
new NetInfoPackage(),
new RNCWebViewPackage(),

View file

@ -0,0 +1,41 @@
package com.mattermost.rnbeta;
import android.content.Context;
import java.util.ArrayList;
import java.util.HashMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.oblador.keychain.KeychainModule;
import com.mattermost.react_native_interface.ResolvePromise;
import com.mattermost.react_native_interface.AsyncStorageHelper;
import com.mattermost.react_native_interface.KeysReadableArray;
public class MattermostCredentialsHelper {
static final String CURRENT_SERVER_URL = "@currentServerUrl";
public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) {
final KeychainModule keychainModule = new KeychainModule(context);
final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context);
final ArrayList<String> keys = new ArrayList<String>(1);
keys.add(CURRENT_SERVER_URL);
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
@Override
public int size() {
return keys.size();
}
@Override
public String getString(int index) {
return keys.get(index);
}
};
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL);
keychainModule.getGenericPasswordForOptions(serverUrl, promise);
}
}

View file

@ -24,7 +24,6 @@ import okhttp3.Response;
import com.mattermost.react_native_interface.ResolvePromise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.oblador.keychain.KeychainModule;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
@ -47,27 +46,19 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
final KeychainModule keychainModule = new KeychainModule(reactApplicationContext);
keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() {
MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
String channelId = bundle.getString("channel_id");
onReplyFailed(notificationManager, notificationId, channelId);
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
String[] credentials = map.getString("password").split(",[ ]*");
String token = null;
String serverUrl = null;
if (credentials.length == 2) {
token = credentials[0];
serverUrl = credentials[1];
}
String token = map.getString("password");
String serverUrl = map.getString("service");
Log.i("ReactNative", String.format("URL=%s TOKEN=%s", serverUrl, token));
replyToMessage(serverUrl, token, notificationId, message);

View file

@ -15,17 +15,18 @@ import okhttp3.Response;
import org.json.JSONObject;
import org.json.JSONException;
import com.mattermost.react_native_interface.ResolvePromise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.oblador.keychain.KeychainModule;
import com.mattermost.react_native_interface.ResolvePromise;
public class ReceiptDelivery {
static final String CURRENT_SERVER_URL = "@currentServerUrl";
public static void send (Context context, final String ackId, final String type) {
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final KeychainModule keychainModule = new KeychainModule(reactApplicationContext);
keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() {
MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
@ -34,14 +35,8 @@ public class ReceiptDelivery {
WritableMap map = (WritableMap) value;
if (map != null) {
String[] credentials = map.getString("password").split(",[ ]*");
String token = null;
String serverUrl = null;
if (credentials.length == 2) {
token = credentials[0];
serverUrl = credentials[1];
}
String token = map.getString("password");
String serverUrl = map.getString("service");
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token));
execute(serverUrl, token, ackId, type);

View file

@ -21,7 +21,7 @@ import {
} from 'mattermost-redux/actions/posts';
import {getFilesForPost} from 'mattermost-redux/actions/files';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
import {getTeamMembersByIds, selectTeam} from 'mattermost-redux/actions/teams';
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
import {General, Preferences} from 'mattermost-redux/constants';
import {getPostIdsInChannel} from 'mattermost-redux/selectors/entities/posts';
@ -54,8 +54,8 @@ import {isDirectChannelVisible, isGroupChannelVisible} from 'app/utils/channels'
const MAX_POST_TRIES = 3;
export function loadChannelsIfNecessary(teamId) {
return async (dispatch, getState) => {
await fetchMyChannelsAndMembers(teamId)(dispatch, getState);
return async (dispatch) => {
await dispatch(fetchMyChannelsAndMembers(teamId));
};
}
@ -416,6 +416,12 @@ export function handleSelectChannelByName(channelName, teamName) {
const currentTeamName = currentTeam?.name;
const {data: channel} = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName));
const currentChannelId = getCurrentChannelId(state);
if (teamName && teamName !== currentTeamName) {
const team = getTeamByName(state, teamName);
dispatch(selectTeam(team));
}
if (channel && currentChannelId !== channel.id) {
dispatch(handleSelectChannel(channel.id));
}

View file

@ -11,7 +11,7 @@ import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {ViewTypes} from 'app/constants';
import {app} from 'app/mattermost';
import {setAppCredentials} from 'app/init/credentials';
import PushNotifications from 'app/push_notifications';
import {getDeviceTimezone} from 'app/utils/timezone';
import {setCSRFFromCookie} from 'app/utils/security';
@ -45,7 +45,7 @@ export function handleSuccessfulLogin() {
const currentUserId = getCurrentUserId(state);
await setCSRFFromCookie(url);
app.setAppCredentials(deviceToken, currentUserId, token, url);
setAppCredentials(deviceToken, currentUserId, token, url);
const enableTimezone = isTimezoneEnabled(state);
if (enableTimezone) {
@ -56,7 +56,6 @@ export function handleSuccessfulLogin() {
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
data: {
url,
token,
},
});

View file

@ -11,10 +11,8 @@ import {
handlePasswordChanged,
} from 'app/actions/views/login';
jest.mock('app/mattermost', () => ({
app: {
setAppCredentials: () => jest.fn(),
},
jest.mock('app/init/credentials', () => ({
setAppCredentials: () => jest.fn(),
}));
jest.mock('react-native-cookies', () => ({

View file

@ -1,316 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable global-require*/
import {Linking, NativeModules, Platform, Text} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import {setGenericPassword, getGenericPassword, resetGenericPassword} from 'react-native-keychain';
import {loadMe} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {setDeepLinkURL} from 'app/actions/views/root';
import {ViewTypes} from 'app/constants';
import tracker from 'app/utils/time_tracker';
import {getCurrentLocale} from 'app/selectors/i18n';
import {getTranslations as getLocalTranslations} from 'app/i18n';
import {store, handleManagedConfig} from 'app/mattermost';
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import {setCSRFFromCookie} from 'app/utils/security';
const {Initialization} = NativeModules;
const TOOLBAR_BACKGROUND = 'TOOLBAR_BACKGROUND';
const TOOLBAR_TEXT_COLOR = 'TOOLBAR_TEXT_COLOR';
const APP_BACKGROUND = 'APP_BACKGROUND';
export default class App {
constructor() {
// Usage: app.js
this.shouldRelaunchWhenActive = false;
this.inBackgroundSince = null;
this.previousAppState = null;
// Usage: screen/entry.js
this.startAppFromPushNotification = false;
this.isNotificationsConfigured = false;
this.allowOtherServers = true;
this.appStarted = false;
this.emmEnabled = false;
this.performingEMMAuthentication = false;
this.translations = null;
this.toolbarBackground = null;
this.toolbarTextColor = null;
this.appBackground = null;
// Usage utils/push_notifications.js
this.replyNotificationData = null;
this.deviceToken = null;
// Usage credentials
this.currentUserId = null;
this.token = null;
this.url = null;
// Usage deeplinking
Linking.addEventListener('url', this.handleDeepLink);
this.setFontFamily();
this.getStartupThemes();
this.getAppCredentials();
}
setFontFamily = () => {
// Set a global font for Android
if (Platform.OS === 'android') {
const defaultFontFamily = {
style: {
fontFamily: 'Roboto',
},
};
const TextRender = Text.render;
const initialDefaultProps = Text.defaultProps;
Text.defaultProps = {
...initialDefaultProps,
...defaultFontFamily,
};
Text.render = function render(props, ...args) {
const oldProps = props;
let newProps = {...props, style: [defaultFontFamily.style, props.style]};
try {
return Reflect.apply(TextRender, this, [newProps, ...args]);
} finally {
newProps = oldProps;
}
};
}
};
getTranslations = () => {
if (this.translations) {
return this.translations;
}
const state = store.getState();
const locale = getCurrentLocale(state);
this.translations = getLocalTranslations(locale);
return this.translations;
};
getAppCredentials = async () => {
try {
const credentials = await avoidNativeBridge(
() => {
return Initialization.credentialsExist;
},
() => {
return Initialization.credentials;
},
() => {
this.waitForRehydration = true;
return getGenericPassword();
}
);
if (credentials) {
const usernameParsed = credentials.username.split(',');
const passwordParsed = credentials.password.split(',');
// username == deviceToken, currentUserId
// password == token, url
if (usernameParsed.length === 2 && passwordParsed.length === 2) {
const [deviceToken, currentUserId] = usernameParsed;
const [token, url] = passwordParsed;
// if for any case the url and the token aren't valid proceed with re-hydration
if (url && url !== 'undefined' && token && token !== 'undefined') {
this.deviceToken = deviceToken;
this.currentUserId = currentUserId;
this.token = token;
this.url = url;
Client4.setUrl(url);
Client4.setToken(token);
await setCSRFFromCookie(url);
} else {
this.waitForRehydration = true;
}
}
} else {
this.waitForRehydration = false;
}
} catch (error) {
return null;
}
return null;
};
getStartupThemes = async () => {
try {
const [
toolbarBackground,
toolbarTextColor,
appBackground,
] = await avoidNativeBridge(
() => {
return Initialization.themesExist;
},
() => {
return [
Initialization.toolbarBackground,
Initialization.toolbarTextColor,
Initialization.appBackground,
];
},
() => {
return Promise.all([
AsyncStorage.getItem(TOOLBAR_BACKGROUND),
AsyncStorage.getItem(TOOLBAR_TEXT_COLOR),
AsyncStorage.getItem(APP_BACKGROUND),
]);
}
);
if (toolbarBackground) {
this.toolbarBackground = toolbarBackground;
this.toolbarTextColor = toolbarTextColor;
this.appBackground = appBackground;
}
} catch (error) {
return null;
}
return null;
};
setPerformingEMMAuthentication = (authenticating) => {
this.performingEMMAuthentication = authenticating;
};
setAppCredentials = (deviceToken, currentUserId, token, url) => {
if (!currentUserId) {
return;
}
const username = `${deviceToken}, ${currentUserId}`;
const password = `${token},${url}`;
this.token = token;
this.url = url;
if (this.waitForRehydration) {
this.waitForRehydration = false;
}
// Only save to keychain if the url and token are set
if (url && token) {
try {
setGenericPassword(username, password);
} catch (e) {
console.warn('could not set credentials', e); //eslint-disable-line no-console
}
}
};
setStartupThemes = (toolbarBackground, toolbarTextColor, appBackground) => {
AsyncStorage.setItem(TOOLBAR_BACKGROUND, toolbarBackground);
AsyncStorage.setItem(TOOLBAR_TEXT_COLOR, toolbarTextColor);
AsyncStorage.setItem(APP_BACKGROUND, appBackground);
};
setStartAppFromPushNotification = (startAppFromPushNotification) => {
this.startAppFromPushNotification = startAppFromPushNotification;
};
setIsNotificationsConfigured = (isNotificationsConfigured) => {
this.isNotificationsConfigured = isNotificationsConfigured;
};
setAllowOtherServers = (allowOtherServers) => {
this.allowOtherServers = allowOtherServers;
};
setAppStarted = (appStarted) => {
this.appStarted = appStarted;
};
setEMMEnabled = (emmEnabled) => {
this.emmEnabled = emmEnabled;
};
setDeviceToken = (deviceToken) => {
this.deviceToken = deviceToken;
};
setReplyNotificationData = (replyNotificationData) => {
this.replyNotificationData = replyNotificationData;
};
setInBackgroundSince = (inBackgroundSince) => {
this.inBackgroundSince = inBackgroundSince;
};
setShouldRelaunchWhenActive = (shouldRelaunchWhenActive) => {
this.shouldRelaunchWhenActive = shouldRelaunchWhenActive;
};
clearNativeCache = () => {
resetGenericPassword();
AsyncStorage.multiRemove([
TOOLBAR_BACKGROUND,
TOOLBAR_TEXT_COLOR,
APP_BACKGROUND,
]);
};
handleDeepLink = (event) => {
const {url} = event;
store.dispatch(setDeepLinkURL(url));
}
launchApp = async () => {
const shouldStart = await handleManagedConfig();
if (shouldStart) {
this.startApp();
}
};
startApp = () => {
if (this.appStarted || this.waitForRehydration) {
return;
}
const {dispatch} = store;
Linking.getInitialURL().then((url) => {
dispatch(setDeepLinkURL(url));
});
let screen = 'SelectServer';
if (this.token && this.url) {
screen = 'Channel';
tracker.initialLoad = Date.now();
try {
dispatch(loadMe());
} catch (e) {
// Fall through since we should have a previous version of the current user because we have a token
console.warn('Failed to load current user when starting on Channel screen', e); // eslint-disable-line no-console
}
}
switch (screen) {
case 'SelectServer':
EventEmitter.emit(ViewTypes.LAUNCH_LOGIN, true);
break;
case 'Channel':
EventEmitter.emit(ViewTypes.LAUNCH_CHANNEL, true);
break;
}
this.setAppStarted(true);
}
}

128
app/init/credentials.js Normal file
View file

@ -0,0 +1,128 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AsyncStorage from '@react-native-community/async-storage';
import * as KeyChain from 'react-native-keychain';
import {Client4} from 'mattermost-redux/client';
import mattermostManaged from 'app/mattermost_managed';
import ephemeralStore from 'app/store/ephemeral_store';
import {setCSRFFromCookie} from 'app/utils/security';
const CURRENT_SERVER = '@currentServerUrl';
export const getCurrentServerUrl = async () => {
return AsyncStorage.getItem(CURRENT_SERVER);
};
export const setAppCredentials = (deviceToken, currentUserId, token, url) => {
if (!currentUserId) {
return;
}
// Only save to keychain if the url and token are set
if (url && token) {
try {
const username = `${deviceToken}, ${currentUserId}`;
ephemeralStore.deviceToken = deviceToken;
AsyncStorage.setItem(CURRENT_SERVER, url);
KeyChain.setInternetCredentials(url, username, token, {accessGroup: mattermostManaged.appGroupIdentifier});
} catch (e) {
console.warn('could not set credentials', e); //eslint-disable-line no-console
}
}
};
export const getAppCredentials = async () => {
const serverUrl = await AsyncStorage.getItem(CURRENT_SERVER);
if (serverUrl) {
return getInternetCredentials(serverUrl);
}
return getCredentialsFromGenericKeyChain();
};
export const removeAppCredentials = async () => {
const url = await getCurrentServerUrl();
Client4.setCSRF(null);
Client4.serverVersion = '';
Client4.setUserId('');
Client4.setToken('');
Client4.setUrl('');
if (url) {
KeyChain.resetInternetCredentials(url);
}
KeyChain.resetGenericPassword();
AsyncStorage.removeItem(CURRENT_SERVER);
};
async function getCredentialsFromGenericKeyChain() {
try {
const credentials = await KeyChain.getGenericPassword();
if (credentials) {
const usernameParsed = credentials.username.split(',');
const passwordParsed = credentials.password.split(',');
// username == deviceToken, currentUserId
// password == token, url
if (usernameParsed.length === 2 && passwordParsed.length === 2) {
const [deviceToken, currentUserId] = usernameParsed;
const [token, url] = passwordParsed;
// if for any case the url and the token aren't valid proceed with re-hydration
if (url && url !== 'undefined' && token && token !== 'undefined') {
Client4.setUserId(currentUserId);
Client4.setUrl(url);
Client4.setToken(token);
await setCSRFFromCookie(url);
// Migration: remove the generic credentials and add a server specific one
setAppCredentials(deviceToken, currentUserId, token, url);
KeyChain.resetGenericPassword();
return {
username: usernameParsed,
password: token,
};
}
}
}
return null;
} catch (e) {
return null;
}
}
async function getInternetCredentials(url) {
try {
const credentials = await KeyChain.getInternetCredentials(url);
if (credentials) {
const usernameParsed = credentials.username.split(',');
const token = credentials.password;
const [deviceToken, currentUserId] = usernameParsed;
if (token && token !== 'undefined') {
ephemeralStore.deviceToken = deviceToken;
Client4.setUserId(currentUserId);
Client4.setUrl(url);
Client4.setToken(token);
await setCSRFFromCookie(url);
return credentials;
}
}
return null;
} catch (e) {
return null;
}
}

165
app/init/emm_provider.js Normal file
View file

@ -0,0 +1,165 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Alert, Platform} from 'react-native';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import {getTranslations} from 'app/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {getCurrentLocale} from 'app/selectors/i18n';
import {t} from 'app/utils/i18n';
import {getAppCredentials} from './credentials';
import LocalConfig from 'assets/config';
class EMMProvider {
constructor() {
this.enabled = false;
this.performingAuthentication = false;
this.previousAppState = null;
this.inAppPinCode = false;
this.blurApplicationScreen = false;
this.jailbreakProtection = false;
this.vendor = null;
this.allowOtherServers = true;
this.emmServerUrl = null;
this.emmUsername = null;
}
checkIfDeviceIsTrusted = (store) => {
const isTrusted = mattermostManaged.isTrustedDevice();
if (!isTrusted) {
const state = store.getState();
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
Alert.alert(
translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor),
translations[t('mobile.managed.jailbreak')].replace('{vendor}', this.vendor),
[{
text: translations[t('mobile.managed.exit')],
style: 'destructive',
onPress: () => {
mattermostManaged.quitApp();
},
}],
{cancelable: false}
);
}
};
handleAuthentication = async (store, prompt = true) => {
this.performingAuthentication = true;
const isSecured = await mattermostManaged.isDeviceSecure();
const state = store.getState();
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
if (isSecured) {
try {
mattermostBucket.setPreference('emm', this.vendor);
if (prompt) {
await mattermostManaged.authenticate({
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', this.vendor),
fallbackToPasscode: true,
suppressEnterPassword: true,
});
}
} catch (err) {
mattermostManaged.quitApp();
return false;
}
} else {
await this.showNotSecuredAlert(translations);
mattermostManaged.quitApp();
return false;
}
this.setPerformingAuthentication(false);
return true;
};
handleManagedConfig = async (store) => {
if (this.performingAuthentication) {
return true;
}
const {dispatch} = store;
if (LocalConfig.AutoSelectServerUrl) {
dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl));
this.allowOtherServers = false;
}
const managedConfig = await mattermostManaged.getConfig();
if (managedConfig && Object.keys(managedConfig).length) {
this.enabled = true;
this.inAppPinCode = managedConfig.inAppPinCode === 'true';
this.blurApplicationScreen = managedConfig.blurApplicationScreen === 'true';
this.jailbreakProtection = managedConfig.jailbreakProtection === 'true';
this.vendor = managedConfig.vendor || 'Mattermost';
const credentials = await getAppCredentials();
if (!credentials) {
this.emmServerUrl = managedConfig.serverUrl;
this.emmUsername = managedConfig.username;
if (managedConfig.allowOtherServers && managedConfig.allowOtherServers === 'false') {
this.allowOtherServers = false;
}
}
if (this.blurApplicationScreen) {
mattermostManaged.blurAppScreen(true);
}
if (this.emmServerUrl) {
dispatch(handleServerUrlChanged(this.emmServerUrl));
}
if (this.emmUsername) {
dispatch(handleLoginIdChanged(this.emmUsername));
}
}
return true;
};
showNotSecuredAlert = (translations) => {
return new Promise((resolve) => {
const options = [];
if (Platform.OS === 'android') {
options.push({
text: translations[t('mobile.managed.settings')],
onPress: () => {
mattermostManaged.goToSecuritySettings();
},
});
}
options.push({
text: translations[t('mobile.managed.exit')],
onPress: resolve,
style: 'cancel',
});
Alert.alert(
translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor),
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
options,
{cancelable: false, onDismiss: resolve},
);
});
}
}
export default new EMMProvider();

View file

@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import RNFetchBlob from 'rn-fetch-blob';
import urlParse from 'url-parse';
@ -146,6 +147,8 @@ const initFetchConfig = async () => {
// no managed config
}
Client4.setUserAgent(DeviceInfo.getUserAgent());
if (Platform.OS === 'ios') {
const certificate = await mattermostBucket.getPreference('cert');
fetchConfig = {

View file

@ -0,0 +1,233 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Alert, AppState, Dimensions, Linking, NativeModules, Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import semver from 'semver';
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 {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
import {selectDefaultChannel} from 'app/actions/views/channel';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
import {NavigationTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
import mattermostManaged from 'app/mattermost_managed';
import PushNotifications from 'app/push_notifications';
import {getCurrentLocale} from 'app/selectors/i18n';
import {t} from 'app/utils/i18n';
import {deleteFileCache} from 'app/utils/file';
import LocalConfig from 'assets/config';
import {getAppCredentials, removeAppCredentials} from './credentials';
import emmProvider from './emm_provider';
const {StatusBarManager} = NativeModules;
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000;
class GlobalEventHandler {
constructor() {
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 () => {
// 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(this.store, prompt);
}
emmProvider.inBackgroundSince = null;
};
appInactive = () => {
const {dispatch} = this.store;
// 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.store = opts.store;
this.launchEntry = opts.launchEntry;
const window = Dimensions.get('window');
this.onOrientationChange({window});
this.StatusBarSizeIOS = require('react-native-status-bar-size');
if (Platform.OS === 'ios') {
this.StatusBarSizeIOS.addEventListener('willChange', this.onStatusBarHeightChange);
StatusBarManager.getHeight(
(data) => {
this.onStatusBarHeightChange(data.height);
}
);
}
this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default;
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(this.store);
mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
};
configureAnalytics = (config) => {
const initAnalytics = require('app/utils/segment').init;
if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) {
initAnalytics(config);
} else {
global.analytics = null;
}
};
onAppStateChange = (appState) => {
const isActive = appState === 'active';
const isBackground = appState === 'background';
if (this.store) {
this.store.dispatch(setAppState(isActive));
}
if (isActive && emmProvider.previousAppState === 'background') {
this.appActive();
} else if (isBackground) {
this.appInactive();
}
emmProvider.previousAppState = appState;
};
onDeepLink = (event) => {
const {url} = event;
this.store.dispatch(setDeepLinkURL(url));
};
onManagedConfigurationChange = () => {
emmProvider.handleManagedConfig(this.store, true);
};
onServerConfigChanged = (config) => {
this.configureAnalytics(config);
};
onLogout = async () => {
this.store.dispatch(closeWebSocket(false));
this.store.dispatch(setServerVersion(''));
deleteFileCache();
removeAppCredentials();
PushNotifications.setApplicationIconBadgeNumber(0);
PushNotifications.cancelAllLocalNotifications(); // TODO: Only cancel the notification that belongs to this server
if (this.launchEntry) {
this.launchEntry();
}
};
onOrientationChange = (dimensions) => {
if (this.store) {
const {dispatch} = this.store;
if (DeviceInfo.isTablet()) {
dispatch(setDeviceAsTablet());
}
const {height, width} = dimensions.window;
const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE';
dispatch(setDeviceOrientation(orientation));
dispatch(setDeviceDimensions(height, width));
}
};
onRestartApp = async () => {
await this.store.dispatch(loadConfigAndLicense());
await this.store.dispatch(loadMe());
const window = Dimensions.get('window');
this.onOrientationChange({window});
if (Platform.OS === 'ios') {
StatusBarManager.getHeight(
(data) => {
this.onStatusBarHeightChange(data.height);
}
);
}
if (this.launchEntry) {
const credentials = await getAppCredentials();
this.launchEntry(credentials);
}
};
onServerVersionChanged = async (serverVersion) => {
const {dispatch, getState} = this.store;
const state = getState();
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[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));
const data = await dispatch(loadConfigAndLicense());
this.configureAnalytics(data.config);
}
}
};
onStatusBarHeightChange = (nextStatusBarHeight) => {
this.store.dispatch(setStatusBarHeight(nextStatusBarHeight));
};
onSwitchToDefaultChannel = (teamId) => {
this.store.dispatch(selectDefaultChannel(teamId));
};
serverUpgradeNeeded = async () => {
const {dispatch} = this.store;
dispatch(setServerVersion(''));
Client4.serverVersion = '';
PushNotifications.setApplicationIconBadgeNumber(0);
PushNotifications.cancelAllLocalNotifications(); // TODO: Only cancel the notification that belongs to this server
const credentials = await getAppCredentials();
if (credentials) {
dispatch(logout());
}
};
}
export default new GlobalEventHandler();

View file

@ -1,389 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable global-require*/
import {
Alert,
AppState,
Dimensions,
InteractionManager,
NativeModules,
Platform,
YellowBox,
} from 'react-native';
const {StatusBarManager, MattermostShare, Initialization} = NativeModules;
import DeviceInfo from 'react-native-device-info';
import {Linking, NativeModules, Platform} from 'react-native';
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 {loadMe} from 'mattermost-redux/actions/users';
import {setDeepLinkURL} 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 {getAppCredentials} from 'app/init/credentials';
import emmProvider from 'app/init/emm_provider';
import 'app/init/fetch';
import globalEventHandler from 'app/init/global_event_handler';
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 configureStore from 'app/store';
import ephemeralStore from 'app/store/ephemeral_store';
import telemetry from 'app/telemetry';
import pushNotificationsUtils from 'app/utils/push_notifications';
import App from './app';
import './fetch_preconfig';
const PROMPT_IN_APP_PIN_CODE_AFTER = 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();
const {MattermostShare} = NativeModules;
const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened;
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, handleSwitchToDefaultChannel);
Dimensions.addEventListener('change', handleOrientationChange);
mattermostManaged.addEventListener('managedConfigDidChange', () => {
handleManagedConfig(true);
const init = async () => {
const credentials = await getAppCredentials();
pushNotificationsUtils.configure(store);
globalEventHandler.configure({
store,
launchEntry,
});
if (config) {
configureAnalytics(config);
registerScreens(store, Provider);
if (startedSharedExtension) {
ephemeralStore.appStarted = true;
}
handleOrientationChange({window});
if (Platform.OS === 'ios') {
StatusBarSizeIOS.addEventListener('willChange', handleStatusBarHeightChange);
StatusBarManager.getHeight(
(data) => {
handleStatusBarHeightChange(data.height);
}
);
if (!ephemeralStore.appStarted) {
launchEntryAndAuthenticateIfNeeded(credentials);
}
};
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 = () => {
Client4.setCSRF(null);
store.dispatch(closeWebSocket(false));
app.setAppStarted(true);
app.clearNativeCache();
deleteFileCache();
resetBadgeAndVersion();
launchSelectServer();
};
const restartApp = async () => {
Navigation.dismissModal({animationType: 'none'});
try {
const window = Dimensions.get('window');
handleOrientationChange({window});
await store.dispatch(loadConfigAndLicense());
await store.dispatch(loadMe());
if (Platform.OS === 'ios') {
StatusBarManager.getHeight(
(data) => {
handleStatusBarHeightChange(data.height);
}
);
}
} 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 === 'true';
blurApplicationScreen = config.blurApplicationScreen === 'true';
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, prompt = true) => {
app.setPerformingEMMAuthentication(true);
const isSecured = await mattermostManaged.isDeviceSecure();
const translations = app.getTranslations();
if (isSecured) {
try {
mattermostBucket.setPreference('emm', vendor);
if (prompt) {
await mattermostManaged.authenticate({
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor),
fallbackToPasscode: true,
suppressEnterPassword: true,
});
}
} catch (err) {
mattermostManaged.quitApp();
return false;
}
} else {
await showNotSecuredAlert(vendor, translations);
mattermostManaged.quitApp();
return false;
}
app.setPerformingEMMAuthentication(false);
return true;
};
function showNotSecuredAlert(vendor, translations) {
return new Promise((resolve) => {
const options = [];
if (Platform.OS === 'android') {
options.push({
text: translations[t('mobile.managed.settings')],
onPress: () => {
mattermostManaged.goToSecuritySettings();
},
});
}
options.push({
text: translations[t('mobile.managed.exit')],
onPress: resolve,
style: 'cancel',
});
Alert.alert(
translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
options,
{cancelable: false, onDismiss: resolve},
);
});
}
const handleSwitchToDefaultChannel = (teamId) => {
store.dispatch(selectDefaultChannel(teamId));
};
const launchSelectServer = () => {
Navigation.startSingleScreenApp({
screen: {
@ -396,7 +54,7 @@ const launchSelectServer = () => {
},
},
passProps: {
allowOtherServers: app.allowOtherServers,
allowOtherServers: emmProvider.allowOtherServers,
},
appStyle: {
orientation: 'auto',
@ -415,9 +73,9 @@ const launchChannel = (skipMetrics = false) => {
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
passProps: {
skipMetrics,
},
},
passProps: {
skipMetrics,
},
appStyle: {
orientation: 'auto',
@ -426,108 +84,52 @@ const launchChannel = (skipMetrics = false) => {
});
};
const handleAppStateChange = (appState) => {
const isActive = appState === 'active';
const isBackground = appState === 'background';
store.dispatch(setAppState(isActive));
if (isActive && app.previousAppState === 'background') {
handleAppActive();
} else if (isBackground) {
handleAppInActive();
}
app.previousAppState = appState;
};
const handleAppActive = async () => {
// 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);
}
// if the app is being controlled by an EMM provider
if (app.emmEnabled) {
const config = await mattermostManaged.getConfig();
const authNeeded = config.inAppPinCode === 'true';
const authExpired = (Date.now() - app.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;
// Once the app becomes active we check if the device needs to have a passcode set
if (authNeeded) {
const prompt = app.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
await handleAuthentication(config.vendor, prompt);
}
}
app.setInBackgroundSince(null);
};
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 = () => {
const launchEntry = (credentials) => {
telemetry.start([
'start:select_server_screen',
'start:channel_screen',
]);
Navigation.startSingleScreenApp({
screen: {
screen: 'Entry',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
},
},
passProps: {
initializeModules,
},
appStyle: {
orientation: 'auto',
},
animationType: 'fade',
});
if (credentials) {
store.dispatch(loadMe());
launchChannel();
} else {
launchSelectServer();
}
telemetry.startSinceLaunch(['start:splash_screen']);
ephemeralStore.appStarted = true;
};
configurePushNotifications();
const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened;
const launchEntryAndAuthenticateIfNeeded = async (credentials) => {
await emmProvider.handleManagedConfig(store);
launchEntry(credentials);
if (startedSharedExtension) {
// Hold on launching Entry screen
app.setAppStarted(true);
}
if (emmProvider.enabled) {
if (emmProvider.jailbreakProtection) {
emmProvider.checkIfDeviceIsTrusted();
}
if (!app.appStarted) {
launchEntry();
}
if (emmProvider.inAppPinCode) {
await emmProvider.handleAuthentication(store);
}
}
new NativeEventsReceiver().appLaunched(() => {
Linking.getInitialURL().then((url) => {
store.dispatch(setDeepLinkURL(url));
});
};
new NativeEventsReceiver().appLaunched(async () => {
const credentials = await getAppCredentials();
if (startedSharedExtension) {
app.setAppStarted(false);
launchEntry();
} else if (app.token && app.url) {
ephemeralStore.appStarted = true;
await launchEntryAndAuthenticateIfNeeded(credentials);
} else if (credentials) {
launchChannel(true);
} else {
launchSelectServer();
}
});
init();

View file

@ -36,6 +36,8 @@ export default {
},
authenticate: LocalAuth.auth,
blurAppScreen: MattermostManaged.blurAppScreen,
appGroupIdentifier: null,
hasSafeAreaInsets: null,
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
getConfig: async () => {
try {

View file

@ -51,6 +51,7 @@ export default {
getCachedConfig: () => {
return cachedConfig;
},
appGroupIdentifier: MattermostManaged.appGroupIdentifier,
hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets,
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
isDeviceSecure: async () => {

View file

@ -14,8 +14,6 @@ import {
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {app} from 'app/mattermost';
import EmptyToolbar from 'app/components/start/empty_toolbar';
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
import MainSidebar from 'app/components/sidebars/main';
@ -24,6 +22,7 @@ import SettingsSidebar from 'app/components/sidebars/settings';
import {preventDoubleTap} from 'app/utils/tap';
import PushNotifications from 'app/push_notifications';
import ephemeralStore from 'app/store/ephemeral_store';
import tracker from 'app/utils/time_tracker';
import telemetry from 'app/telemetry';
@ -232,8 +231,8 @@ export default class ChannelBase extends PureComponent {
loadChannelsIfNecessary(teamId).then(() => {
loadProfilesAndTeamMembersForDMSidebar(teamId);
if (app.startAppFromPushNotification) {
app.setStartAppFromPushNotification(false);
if (ephemeralStore.appStartedFromPushNotification) {
ephemeralStore.appStartedFromPushNotification = false;
} else {
selectInitialChannel(teamId);
}

View file

@ -1,321 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
View,
AppState,
Platform,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {
app,
store,
} from 'app/mattermost';
import {ViewTypes} from 'app/constants';
import PushNotifications from 'app/push_notifications';
import {stripTrailingSlashes} from 'app/utils/url';
import {wrapWithContextProvider} from 'app/utils/wrap_context_provider';
import ChannelLoader from 'app/components/channel_loader';
import EmptyToolbar from 'app/components/start/empty_toolbar';
import Loading from 'app/components/loading';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
const lazyLoadSelectServer = () => {
return require('app/screens/select_server').default;
};
const lazyLoadChannel = () => {
return require('app/screens/channel').default;
};
const lazyLoadPushNotifications = () => {
return require('app/utils/push_notifications').configurePushNotifications;
};
const lazyLoadReplyPushNotifications = () => {
return require('app/utils/push_notifications').onPushNotificationReply;
};
/**
* Entry Component:
* With very little overhead navigate to
* - Login or
* - Channel Component
*
* The idea is to render something to the user as soon as possible
*/
export default class Entry extends PureComponent {
static propTypes = {
theme: PropTypes.object,
navigator: PropTypes.object,
isLandscape: PropTypes.bool,
enableTimezone: PropTypes.bool,
deviceTimezone: PropTypes.string,
initializeModules: PropTypes.func,
actions: PropTypes.shape({
autoUpdateTimezone: PropTypes.func.isRequired,
setDeviceToken: PropTypes.func.isRequired,
}).isRequired,
};
constructor(props) {
super(props);
this.state = {
launchLogin: false,
launchChannel: false,
};
this.unsubscribeFromStore = null;
}
componentDidMount() {
Client4.setUserAgent(DeviceInfo.getUserAgent());
if (store.getState().views.root.hydrationComplete) {
this.handleHydrationComplete();
} else {
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
}
EventEmitter.on(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
EventEmitter.on(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
}
componentWillUnmount() {
EventEmitter.off(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
EventEmitter.off(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
}
handleLaunchLogin = (initializeModules) => {
this.setState({launchLogin: true});
if (initializeModules) {
this.props.initializeModules();
}
};
handleLaunchChannel = (initializeModules) => {
this.setState({launchChannel: true});
if (initializeModules) {
this.props.initializeModules();
}
};
listenForHydration = () => {
const {getState} = store;
const state = getState();
if (!app.isNotificationsConfigured) {
this.configurePushNotifications();
}
if (state.views.root.hydrationComplete) {
this.handleHydrationComplete();
}
};
handleHydrationComplete = () => {
if (this.unsubscribeFromStore) {
this.unsubscribeFromStore();
this.unsubscribeFromStore = null;
}
this.autoUpdateTimezone();
this.setAppCredentials();
this.setStartupThemes();
this.handleNotification();
this.loadSystemEmojis();
if (Platform.OS === 'android') {
this.launchForAndroid();
} else {
this.launchForiOS();
}
};
autoUpdateTimezone = () => {
const {
actions: {
autoUpdateTimezone,
},
enableTimezone,
deviceTimezone,
} = this.props;
if (enableTimezone) {
autoUpdateTimezone(deviceTimezone);
}
};
configurePushNotifications = () => {
const configureNotifications = lazyLoadPushNotifications();
configureNotifications();
};
setAppCredentials = () => {
const {
actions: {
setDeviceToken,
},
} = this.props;
const {getState} = store;
const state = getState();
const {credentials} = state.entities.general;
const {currentUserId} = state.entities.users;
if (app.deviceToken) {
setDeviceToken(app.deviceToken);
}
if (credentials.token && credentials.url) {
Client4.setToken(credentials.token);
Client4.setUrl(stripTrailingSlashes(credentials.url));
} else if (app.waitForRehydration) {
app.waitForRehydration = false;
}
if (currentUserId) {
Client4.setUserId(currentUserId);
}
app.setAppCredentials(app.deviceToken, currentUserId, credentials.token, credentials.url);
};
setStartupThemes = () => {
const {theme} = this.props;
if (app.toolbarBackground === theme.sidebarHeaderBg) {
return;
}
app.setStartupThemes(
theme.sidebarHeaderBg,
theme.sidebarHeaderTextColor,
theme.centerChannelBg
);
};
handleNotification = async () => {
const notification = PushNotifications.getNotification();
// If notification exists, it means that the app was started through a reply
// and the app was not sitting in the background nor opened
if (notification || app.replyNotificationData) {
const onPushNotificationReply = lazyLoadReplyPushNotifications();
const notificationData = notification || app.replyNotificationData;
const {data, text, badge, completed} = notificationData;
// if the notification has a completed property it means that we are replying to a notification
if (completed) {
onPushNotificationReply(data, text, badge, completed);
}
PushNotifications.resetNotification();
}
};
launchForAndroid = () => {
app.launchApp();
};
launchForiOS = () => {
const appNotActive = AppState.currentState !== 'active';
if (appNotActive) {
// for iOS replying from push notification starts the app in the background
app.setShouldRelaunchWhenActive(true);
} else {
app.launchApp();
}
};
loadSystemEmojis = () => {
const EmojiIndicesByAlias = require('app/utils/emojis').EmojiIndicesByAlias;
setSystemEmojis(EmojiIndicesByAlias);
};
renderLogin = () => {
const SelectServer = lazyLoadSelectServer();
const props = {
allowOtherServers: app.allowOtherServers,
navigator: this.props.navigator,
};
return wrapWithContextProvider(SelectServer)(props);
};
renderChannel = () => {
const ChannelScreen = lazyLoadChannel();
const props = {
navigator: this.props.navigator,
};
return wrapWithContextProvider(ChannelScreen, false)(props);
};
render() {
const {
navigator,
isLandscape,
} = this.props;
if (this.state.launchLogin) {
return this.renderLogin();
}
if (this.state.launchChannel) {
return this.renderChannel();
}
let toolbar = null;
let loading = null;
const backgroundColor = app.appBackground ? app.appBackground : '#ffff';
if (app.token && app.toolbarBackground) {
const toolbarTheme = {
sidebarHeaderBg: app.toolbarBackground,
sidebarHeaderTextColor: app.toolbarTextColor,
};
toolbar = (
<View>
<StatusBar headerColor={app.toolbarBackground}/>
<EmptyToolbar
theme={toolbarTheme}
isLandscape={isLandscape}
/>
</View>
);
loading = (
<ChannelLoader
backgroundColor={backgroundColor}
channelIsLoading={true}
/>
);
} else {
loading = <Loading/>;
}
return (
<SafeAreaView
navBarBackgroundColor={app.toolbarBackground}
backgroundColor={backgroundColor}
navigator={navigator}
>
{toolbar}
{loading}
</SafeAreaView>
);
}
}

View file

@ -1,39 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {setDeviceToken} from 'mattermost-redux/actions/general';
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
import {isLandscape} from 'app/selectors/device';
import {getDeviceTimezone} from 'app/utils/timezone';
const lazyLoadEntry = () => {
return require('./entry').default;
};
function mapStateToProps(state) {
const enableTimezone = isTimezoneEnabled(state);
const deviceTimezone = getDeviceTimezone();
return {
theme: getTheme(state),
isLandscape: isLandscape(state),
enableTimezone,
deviceTimezone,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
autoUpdateTimezone,
setDeviceToken,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(lazyLoadEntry());

View file

@ -5,7 +5,6 @@ import {Navigation} from 'react-native-navigation';
import {gestureHandlerRootHOC} from 'react-native-gesture-handler';
import Channel from 'app/screens/channel';
import Entry from 'app/screens/entry';
import SelectServer from 'app/screens/select_server';
import {wrapWithContextProvider} from 'app/utils/wrap_context_provider';
@ -26,7 +25,6 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider);
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider);
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider);
Navigation.registerComponent('Entry', () => Entry, store, Provider);
Navigation.registerComponent('ExpandedAnnouncementBanner', () => wrapWithContextProvider(require('app/screens/expanded_announcement_banner').default), store, Provider);
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider);
Navigation.registerComponent('ForgotPassword', () => wrapWithContextProvider(require('app/screens/forgot_password').default), store, Provider);

View file

@ -25,7 +25,7 @@ import {Client4} from 'mattermost-redux/client';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import fetchConfig from 'app/fetch_preconfig';
import fetchConfig from 'app/init/fetch';
import mattermostBucket from 'app/mattermost_bucket';
import {GlobalStyles} from 'app/styles';
import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade';
@ -67,6 +67,16 @@ export default class SelectServer extends PureComponent {
intl: intlShape.isRequired,
};
static getDerivedStateFromProps(props, state) {
if (props.serverUrl && !state.url) {
return {
url: props.serverUrl,
};
}
return null;
}
constructor(props) {
super(props);
@ -99,19 +109,19 @@ export default class SelectServer extends PureComponent {
telemetry.save();
}
componentWillUpdate(nextProps, nextState) {
if (nextState.connected && nextProps.hasConfigAndLicense && !(this.state.connected && this.props.hasConfigAndLicense)) {
componentDidUpdate(prevProps, prevState) {
if (this.state.connected && this.props.hasConfigAndLicense && !(prevState.connected && prevProps.hasConfigAndLicense)) {
if (LocalConfig.EnableMobileClientUpgrade) {
this.props.actions.setLastUpgradeCheck();
const {currentVersion, minVersion, latestVersion} = nextProps;
const {currentVersion, minVersion, latestVersion} = prevProps;
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion);
if (isUpgradeAvailable(upgradeType)) {
this.handleShowClientUpgrade(upgradeType);
} else {
this.handleLoginOptions(nextProps);
this.handleLoginOptions(prevProps);
}
} else {
this.handleLoginOptions(nextProps);
this.handleLoginOptions(prevProps);
}
}
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class EphemeralStore {
constructor() {
this.appStarted = false;
this.appStartedFromPushNotification = false;
this.deviceToken = null;
}
}
export default new EphemeralStore();

View file

@ -248,7 +248,6 @@ export default function configureAppStore(initialState) {
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
data: {
url: state.entities.general.credentials.url,
token: state.entities.general.credentials.token,
},
},
{

View file

@ -14,6 +14,8 @@ import {logError} from 'mattermost-redux/actions/errors';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {purgeOfflineStore} from 'app/actions/views/root';
import {DEFAULT_LOCALE, getTranslations} from 'app/i18n';
import {t} from 'app/utils/i18n';
import {
captureException,
captureJSException,
@ -21,54 +23,55 @@ import {
LOGGER_NATIVE,
} from 'app/utils/sentry';
import {app, store} from 'app/mattermost';
import {t} from 'app/utils/i18n';
const errorHandler = (e, isFatal) => {
if (__DEV__ && !e && !isFatal) {
// react-native-exception-handler redirects console.error to call this, and React calls
// console.error without an exception when prop type validation fails, so this ends up
// being called with no arguments when the error handler is enabled in dev mode.
return;
class JavascriptAndNativeErrorHandler {
initializeErrorHandling = (store) => {
this.store = store;
initializeSentry();
setJSExceptionHandler(this.errorHandler, false);
setNativeExceptionHandler(this.nativeErrorHandler, false);
}
console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
captureJSException(e, isFatal, store);
nativeErrorHandler = (e) => {
console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console
captureException(e, LOGGER_NATIVE, this.store);
};
const {dispatch} = store;
errorHandler = (e, isFatal) => {
if (__DEV__ && !e && !isFatal) {
// react-native-exception-handler redirects console.error to call this, and React calls
// console.error without an exception when prop type validation fails, so this ends up
// being called with no arguments when the error handler is enabled in dev mode.
return;
}
dispatch(closeWebSocket());
console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
captureJSException(e, isFatal, this.store);
if (Client4.getUrl()) {
dispatch(logError(e));
}
const {dispatch} = this.store;
if (isFatal && e instanceof Error) {
const translations = app.getTranslations();
dispatch(closeWebSocket());
Alert.alert(
translations[t('mobile.error_handler.title')],
translations[t('mobile.error_handler.description')],
[{
text: translations[t('mobile.error_handler.button')],
onPress: () => {
// purge the store
dispatch(purgeOfflineStore());
},
}],
{cancelable: false}
);
}
};
if (Client4.getUrl()) {
dispatch(logError(e));
}
const nativeErrorHandler = (e) => {
console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console
captureException(e, LOGGER_NATIVE, store);
};
if (isFatal && e instanceof Error) {
const translations = getTranslations(DEFAULT_LOCALE);
export function initializeErrorHandling() {
initializeSentry();
setJSExceptionHandler(errorHandler, false);
setNativeExceptionHandler(nativeErrorHandler, false);
Alert.alert(
translations[t('mobile.error_handler.title')],
translations[t('mobile.error_handler.description')],
[{
text: translations[t('mobile.error_handler.button')],
onPress: () => {
// purge the store
dispatch(purgeOfflineStore());
},
}],
{cancelable: false}
);
}
};
}
export default new JavascriptAndNativeErrorHandler();

View file

@ -8,7 +8,6 @@ import {setDeviceToken} from 'mattermost-redux/actions/general';
import {getPosts} from 'mattermost-redux/actions/posts';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {markChannelViewedAndRead, retryGetPostsAction} from 'app/actions/views/channel';
@ -17,169 +16,179 @@ import {
loadFromPushNotification,
} from 'app/actions/views/root';
import {ViewTypes} from 'app/constants';
import {DEFAULT_LOCALE, getLocalizedMessage} from 'app/i18n';
import {t} from 'app/utils/i18n';
import {
app,
store,
} from 'app/mattermost';
import {getLocalizedMessage} from 'app/i18n';
import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials';
import PushNotifications from 'app/push_notifications';
import {getCurrentLocale} from 'app/selectors/i18n';
import ephemeralStore from 'app/store/ephemeral_store';
import {t} from 'app/utils/i18n';
const onRegisterDevice = (data) => {
app.setIsNotificationsConfigured(true);
const state = store.getState();
let prefix;
if (Platform.OS === 'ios') {
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
if (DeviceInfo.getBundleId().includes('rnbeta')) {
prefix = `${prefix}beta`;
}
} else {
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
class PushNotificationUtils {
constructor() {
this.configured = false;
this.replyNotificationData = null;
}
const token = `${prefix}:${data.token}`;
if (state.views.root.hydrationComplete) {
app.setDeviceToken(token);
store.dispatch(setDeviceToken(token));
} else {
app.setDeviceToken(token);
}
};
configure = (store) => {
this.store = store;
const loadFromNotification = async (notification) => {
await store.dispatch(loadFromPushNotification(notification, true));
if (!app.startAppFromPushNotification) {
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
PushNotifications.resetNotification();
}
};
const onPushNotification = async (deviceNotification) => {
const {dispatch, getState} = store;
let unsubscribeFromStore = null;
let stopLoadingNotification = false;
// mark the app as started as soon as possible
if (!app.appStarted) {
app.setStartAppFromPushNotification(true);
}
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
const notification = {
data,
message,
PushNotifications.configure({
onRegister: this.onRegisterDevice,
onNotification: this.onPushNotification,
onReply: this.onPushNotificationReply,
popInitialNotification: true,
requestPermissions: true,
});
};
if (userInfo) {
notification.localNotification = userInfo.localNotification;
}
loadFromNotification = async (notification) => {
await this.store.dispatch(loadFromPushNotification(notification, true));
if (data.type === 'clear') {
dispatch(markChannelViewedAndRead(data.channel_id, null, false));
} else {
// get the posts for the channel as soon as possible
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
if (foreground) {
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
} else if (userInteraction && !notification.localNotification) {
EventEmitter.emit('close_channel_drawer');
if (getState().views.root.hydrationComplete) {
setTimeout(() => {
loadFromNotification(notification);
}, 0);
} else {
const waitForHydration = () => {
if (getState().views.root.hydrationComplete && !stopLoadingNotification) {
stopLoadingNotification = true;
unsubscribeFromStore();
loadFromNotification(notification);
}
};
unsubscribeFromStore = store.subscribe(waitForHydration);
}
if (!ephemeralStore.appStartedFromPushNotification) {
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
PushNotifications.resetNotification();
}
}
};
};
export const onPushNotificationReply = async (data, text, badge, completed) => {
const {dispatch, getState} = store;
const state = getState();
const reduxCurrentUser = getCurrentUser(state);
const reduxCredentialsUrl = state.entities.general.credentials.url;
const reduxCredentialsToken = state.entities.general.credentials.token;
onPushNotification = async (deviceNotification) => {
const {dispatch, getState} = this.store;
let unsubscribeFromStore = null;
let stopLoadingNotification = false;
const currentUserId = reduxCurrentUser ? reduxCurrentUser.id : app.currentUserId;
const url = reduxCredentialsUrl || app.url;
const token = reduxCredentialsToken || app.token;
// mark the app as started as soon as possible
if (!ephemeralStore.appStarted) {
ephemeralStore.appStartedFromPushNotification = true;
}
if (currentUserId) {
// one thing to note is that for android it will reply to the last post in the stack
const rootId = data.root_id || data.post_id;
const post = {
user_id: currentUserId,
channel_id: data.channel_id,
root_id: rootId,
parent_id: rootId,
message: text,
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
const notification = {
data,
message,
};
if (!Client4.getUrl()) {
// Make sure the Client has the server url set
Client4.setUrl(url);
if (userInfo) {
notification.localNotification = userInfo.localNotification;
}
if (!Client4.getToken()) {
// Make sure the Client has the server token set
Client4.setToken(token);
}
if (data.type === 'clear') {
dispatch(markChannelViewedAndRead(data.channel_id, null, false));
} else {
// get the posts for the channel as soon as possible
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
const result = await dispatch(createPostForNotificationReply(post));
if (result.error) {
const locale = reduxCurrentUser ? reduxCurrentUser.locale : DEFAULT_LOCALE;
PushNotifications.localNotification({
message: getLocalizedMessage(locale, t('mobile.reply_post.failed')),
userInfo: {
localNotification: true,
localTest: true,
},
});
console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console
if (foreground) {
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
} else if (userInteraction && !notification.localNotification) {
EventEmitter.emit('close_channel_drawer');
if (getState().views.root.hydrationComplete) { //TODO: Replace when realm is ready
setTimeout(() => {
this.loadFromNotification(notification);
}, 0);
} else {
const waitForHydration = () => {
if (getState().views.root.hydrationComplete && !stopLoadingNotification) {
stopLoadingNotification = true;
unsubscribeFromStore();
this.loadFromNotification(notification);
}
};
unsubscribeFromStore = this.store.subscribe(waitForHydration);
}
}
}
};
onPushNotificationReply = async (data, text, badge, completed) => {
const {dispatch, getState} = this.store;
const state = getState();
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
const url = await getCurrentServerUrl(); // TODO Change to handle multiple servers
const token = credentials.password;
const usernameParsed = credentials.username.split(',');
const [, currentUserId] = usernameParsed;
if (currentUserId) {
// one thing to note is that for android it will reply to the last post in the stack
const rootId = data.root_id || data.post_id;
const post = {
user_id: currentUserId,
channel_id: data.channel_id,
root_id: rootId,
parent_id: rootId,
message: text,
};
if (!Client4.getUrl()) {
// Make sure the Client has the server url set
Client4.setUrl(url);
}
if (!Client4.getToken()) {
// Make sure the Client has the server token set
Client4.setToken(token);
}
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
const result = await dispatch(createPostForNotificationReply(post));
if (result.error) {
const locale = getCurrentLocale(state);
PushNotifications.localNotification({
message: getLocalizedMessage(locale, t('mobile.reply_post.failed')),
userInfo: {
localNotification: true,
localTest: true,
},
});
console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console
completed();
return;
}
if (badge >= 0) {
PushNotifications.setApplicationIconBadgeNumber(badge);
}
dispatch(markChannelViewedAndRead(data.channel_id));
this.replyNotificationData = null;
completed();
return;
} else {
this.replyNotificationData = {
data,
text,
badge,
completed,
};
}
};
onRegisterDevice = (data) => {
const {dispatch, getState} = this.store;
let unsubscribeFromStore = null;
let prefix;
if (Platform.OS === 'ios') {
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
if (DeviceInfo.getBundleId().includes('rnbeta')) {
prefix = `${prefix}beta`;
}
} else {
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
}
if (badge >= 0) {
PushNotifications.setApplicationIconBadgeNumber(badge);
}
ephemeralStore.deviceToken = `${prefix}:${data.token}`;
dispatch(markChannelViewedAndRead(data.channel_id));
app.setReplyNotificationData(null);
completed();
} else {
app.setReplyNotificationData({
data,
text,
badge,
completed,
});
// TODO: Remove when realm is ready
const waitForHydration = () => {
if (getState().views.root.hydrationComplete && !this.configured) {
this.configured = true;
dispatch(setDeviceToken(ephemeralStore.deviceToken));
unsubscribeFromStore();
}
};
unsubscribeFromStore = this.store.subscribe(waitForHydration);
}
};
}
export const configurePushNotifications = () => {
PushNotifications.configure({
onRegister: onRegisterDevice,
onNotification: onPushNotification,
onReply: onPushNotificationReply,
popInitialNotification: true,
requestPermissions: true,
});
if (app) {
app.setIsNotificationsConfigured(true);
}
};
export default new PushNotificationUtils();

View file

@ -103,9 +103,10 @@ export function matchDeepLink(url, serverURL, siteURL) {
return null;
}
const linkRoot = `(?:${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`;
const linkRoot = `(?:${escapeRegex('mattermost:/')}|${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`;
let match = new RegExp('^' + linkRoot + '\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLinkTypes.CHANNEL, teamName: match[1], channelName: match[2]};
}

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import 'react-native/Libraries/Core/InitializeCore';
import {AppRegistry, DeviceEventEmitter, Platform, YellowBox} from 'react-native';
import {AppRegistry, DeviceEventEmitter, Platform, Text, YellowBox} from 'react-native';
import 'react-native-gesture-handler';
import LocalConfig from 'assets/config';
@ -10,16 +10,46 @@ import LocalConfig from 'assets/config';
import telemetry from 'app/telemetry';
import 'app/mattermost';
import ShareExtension from 'share_extension/android';
YellowBox.ignoreWarnings([
'Warning: componentWillMount is deprecated',
'Warning: componentWillUpdate is deprecated',
'Warning: componentWillReceiveProps is deprecated',
]);
if (__DEV__) {
YellowBox.ignoreWarnings([
'Warning: componentWillMount is deprecated',
'Warning: componentWillUpdate is deprecated',
'Warning: componentWillReceiveProps is deprecated',
// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
'Require cycle: node_modules/react-native/Libraries/Network/fetch.js',
]);
}
const setFontFamily = () => {
// Set a global font for Android
const defaultFontFamily = {
style: {
fontFamily: 'Roboto',
},
};
const TextRender = Text.render;
const initialDefaultProps = Text.defaultProps;
Text.defaultProps = {
...initialDefaultProps,
...defaultFontFamily,
};
Text.render = function render(props, ...args) {
const oldProps = props;
let newProps = {...props, style: [defaultFontFamily.style, props.style]};
try {
return Reflect.apply(TextRender, this, [newProps, ...args]);
} finally {
newProps = oldProps;
}
};
};
if (Platform.OS === 'android') {
const ShareExtension = require('share_extension/android').default;
AppRegistry.registerComponent('MattermostShare', () => ShareExtension);
setFontFamily();
if (LocalConfig.TelemetryEnabled) {
const metricsSubscription = DeviceEventEmitter.addListener('nativeMetrics', (metrics) => {

View file

@ -68,6 +68,7 @@ RCT_EXPORT_MODULE();
return @{
@"hasSafeAreaInsets": @([self hasSafeAreaInsets]),
@"appGroupIdentifier": APP_GROUP_ID
};
}

View file

@ -7,7 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* Constants.h */; };
7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* MMMConstants.h */; };
7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F82907022C295AC0035544F /* MMKeychainManager.m */; };
7F82907322C296970035544F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907222C296970035544F /* Security.framework */; };
7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907422C296A10035544F /* LocalAuthentication.framework */; };
7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE04C2213818900D0F595 /* MattermostBucket.m */; };
7FABE055221387B500D0F595 /* MMMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE053221387B400D0F595 /* MMMConstants.m */; };
7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE057221388D600D0F595 /* UploadSessionManager.swift */; };
@ -25,13 +28,17 @@
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */,
7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
7F82906F22C295AB0035544F /* MMKeychainManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMKeychainManager.h; sourceTree = "<group>"; };
7F82907022C295AC0035544F /* MMKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMKeychainManager.m; sourceTree = "<group>"; };
7F82907222C296970035544F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
7F82907422C296A10035544F /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
7FABE03622137F2900D0F595 /* libUploadAttachments.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUploadAttachments.a; sourceTree = BUILT_PRODUCTS_DIR; };
7FABE04B2213818900D0F595 /* UploadAttachments-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UploadAttachments-Bridging-Header.h"; sourceTree = "<group>"; };
7FABE04C2213818900D0F595 /* MattermostBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = "<group>"; };
@ -52,17 +59,29 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */,
7F82907322C296970035544F /* Security.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7F82901A22C28F770035544F /* Frameworks */ = {
isa = PBXGroup;
children = (
7F82907422C296A10035544F /* LocalAuthentication.framework */,
7F82907222C296970035544F /* Security.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
7FABE02D22137F2900D0F595 = {
isa = PBXGroup;
children = (
7FABE03822137F2900D0F595 /* UploadAttachments */,
7FABE03722137F2900D0F595 /* Products */,
7F82901A22C28F770035544F /* Frameworks */,
);
sourceTree = "<group>";
};
@ -77,6 +96,8 @@
7FABE03822137F2900D0F595 /* UploadAttachments */ = {
isa = PBXGroup;
children = (
7F82906F22C295AB0035544F /* MMKeychainManager.h */,
7F82907022C295AC0035544F /* MMKeychainManager.m */,
7FABE05A2213892100D0F595 /* AttachmentArray.swift */,
7FABE0592213892100D0F595 /* AttachmentItem.swift */,
7FABE054221387B500D0F595 /* MMMConstants.h */,
@ -175,6 +196,7 @@
files = (
7FABE05C2213892200D0F595 /* AttachmentArray.swift in Sources */,
7FABE0FA2214674200D0F595 /* StoreManager.m in Sources */,
7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */,
7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */,
7FABE0F7221466F900D0F595 /* UploadManager.swift in Sources */,
7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */,
@ -309,6 +331,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
HEADER_SEARCH_PATHS = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -331,6 +354,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
HEADER_SEARCH_PATHS = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View file

@ -0,0 +1,3 @@
@interface MMKeychainManager : NSObject
-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options;
@end

View file

@ -0,0 +1,73 @@
#import <Security/Security.h>
#import "MMKeychainManager.h"
#import <LocalAuthentication/LAContext.h>
#import <UIKit/UIKit.h>
@implementation MMKeychainManager
// Messages from the comments in <Security/SecBase.h>
#pragma mark - Proposed functionality - Helpers
#define kAuthenticationType @"authenticationType"
#define kAuthenticationTypeBiometrics @"AuthenticationWithBiometrics"
#define kAccessControlType @"accessControl"
#define kAccessControlUserPresence @"UserPresence"
#define kAccessControlBiometryAny @"BiometryAny"
#define kAccessControlBiometryCurrentSet @"BiometryCurrentSet"
#define kAccessControlDevicePasscode @"DevicePasscode"
#define kAccessControlApplicationPassword @"ApplicationPassword"
#define kAccessControlBiometryAnyOrDevicePasscode @"BiometryAnyOrDevicePasscode"
#define kAccessControlBiometryCurrentSetOrDevicePasscode @"BiometryCurrentSetOrDevicePasscode"
#define kBiometryTypeTouchID @"TouchID"
#define kBiometryTypeFaceID @"FaceID"
#define kAuthenticationPromptMessage @"authenticationPrompt"
#pragma mark - Native access
-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options
{
if (server == nil) {
return nil;
}
NSDictionary *query = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
(__bridge NSString *)kSecAttrServer: server,
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne
};
// Look up server in the keychain
NSDictionary *found = nil;
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
return nil;
}
found = (__bridge NSDictionary*)(foundTypeRef);
if (!found) {
return nil;
}
// Found
NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
CFRelease(foundTypeRef);
NSDictionary *result = @{
@"server": server,
@"username": username,
@"password": password
};
return result;
}
@end

View file

@ -1,8 +1,10 @@
#import <Foundation/Foundation.h>
#import "MattermostBucket.h"
#import "MMKeychainManager.h"
@interface StoreManager : NSObject
@property MattermostBucket *bucket;
@property MMKeychainManager *keychain;
@property (nonatomic, strong) NSDictionary *entities;
+(instancetype)shared;

View file

@ -15,8 +15,9 @@
-(instancetype)init {
self = [super init];
if (self) {
self.bucket = [[MattermostBucket alloc] init];
[self getEntities:true];
self.bucket = [[MattermostBucket alloc] init];
self.keychain = [[MMKeychainManager alloc] init];
[self getEntities:true];
}
return self;
}
@ -150,10 +151,12 @@
}
-(NSString *)getToken {
NSDictionary *general = [self.entities objectForKey:@"general"];
NSDictionary *credentials = [general objectForKey:@"credentials"];
NSDictionary *options = @{
@"accessGroup": APP_GROUP_ID
};
NSDictionary *credentials = [self.keychain getInternetCredentialsForServer:[self getServerUrl] withOptions:options];
return [credentials objectForKey:@"token"];
return [credentials objectForKey:@"password"];
}
-(UInt64)scanValueFromConfig:(NSDictionary *)config key:(NSString *)key {

View file

@ -5,3 +5,4 @@
#import "MMMConstants.h"
#import "MattermostBucket.h"
#import "StoreManager.h"
#import "MMKeychainManager.h"

View file

@ -2,88 +2,8 @@
// See LICENSE.txt for license information.
module.exports = [
'app/app.js',
'app/components/app_icon.js',
'app/components/at_mention/at_mention.js',
'app/components/at_mention/index.js',
'app/components/attachment_button.js',
'app/components/badge.js',
'app/components/emoji/emoji.js',
'app/components/emoji/index.js',
'app/components/fade.js',
'app/components/file_attachment_list/file_attachment_icon.js',
'app/components/file_attachment_list/file_attachment_image.js',
'app/components/formatted_date.js',
'app/components/formatted_markdown_text.js',
'app/components/formatted_text.js',
'app/components/formatted_time.js',
'app/components/interactive_dialog_controller/index.js',
'app/components/interactive_dialog_controller/interactive_dialog_controller.js',
'app/components/layout/keyboard_layout/index.js',
'app/components/layout/keyboard_layout/keyboard_layout.js',
'app/components/loading.js',
'app/components/markdown/hashtag/hashtag.js',
'app/components/markdown/hashtag/index.js',
'app/components/markdown/index.js',
'app/components/markdown/markdown.js',
'app/components/markdown/markdown_block_quote.js',
'app/components/markdown/markdown_code_block/index.js',
'app/components/markdown/markdown_code_block/markdown_code_block.js',
'app/components/markdown/markdown_emoji/index.js',
'app/components/markdown/markdown_emoji/markdown_emoji.js',
'app/components/markdown/markdown_image/index.js',
'app/components/markdown/markdown_image/markdown_image.js',
'app/components/markdown/markdown_link/index.js',
'app/components/markdown/markdown_link/markdown_link.js',
'app/components/markdown/markdown_list.js',
'app/components/markdown/markdown_list_item.js',
'app/components/markdown/markdown_table/index.js',
'app/components/markdown/markdown_table/markdown_table.js',
'app/components/markdown/markdown_table_cell/index.js',
'app/components/markdown/markdown_table_cell/markdown_table_cell.js',
'app/components/markdown/markdown_table_image/index.js',
'app/components/markdown/markdown_table_image/markdown_table_image.js',
'app/components/markdown/markdown_table_row/index.js',
'app/components/markdown/markdown_table_row/markdown_table_row.js',
'app/components/network_indicator/index.js',
'app/components/network_indicator/network_indicator.js',
'app/components/paper_plane.js',
'app/components/post/index.js',
'app/components/post/post.js',
'app/components/post_body/index.js',
'app/components/post_body/post_body.js',
'app/components/post_header/index.js',
'app/components/post_header/post_header.js',
'app/components/post_header/post_pre_header.js',
'app/components/post_list/date_header/date_header.js',
'app/components/post_list/date_header/index.js',
'app/components/post_list/index.js',
'app/components/post_list/new_messages_divider.js',
'app/components/post_list/post_list.js',
'app/components/post_list_retry.js',
'app/components/post_profile_picture/index.js',
'app/components/post_profile_picture/post_profile_picture.js',
'app/components/post_textbox/components/typing/index.js',
'app/components/post_textbox/components/typing/typing.js',
'app/components/post_textbox/index.js',
'app/components/post_textbox/post_textbox.android.js',
'app/components/post_textbox/post_textbox_base.js',
'app/components/profile_picture/index.js',
'app/components/profile_picture/profile_picture.js',
'app/components/progressive_image/index.js',
'app/components/progressive_image/progressive_image.js',
'app/components/reply_icon.js',
'app/components/safe_area_view/index.js',
'app/components/safe_area_view/safe_area_view.android.js',
'app/components/send_button.js',
'app/components/show_more_button/index.js',
'app/components/show_more_button/show_more_button.js',
'app/components/start/empty_toolbar.js',
'app/components/status_bar/index.js',
'app/components/status_bar/status_bar.js',
'app/components/user_status/index.js',
'app/components/user_status/user_status.js',
'app/components/vector_icon.js',
'app/constants/custom_prop_types.js',
'app/constants/deep_linking.js',
'app/constants/device.js',
@ -92,7 +12,10 @@ module.exports = [
'app/constants/navigation.js',
'app/constants/permissions.js',
'app/constants/view.js',
'app/fetch_preconfig.js',
'app/init/credentials.js',
'app/init/emm_provider.js',
'app/init/fetch.js',
'app/init/global_event_handler.js',
'app/initial_state.js',
'app/mattermost.js',
'app/mattermost_bucket/index.js',
@ -128,61 +51,17 @@ module.exports = [
'app/reducers/views/team.js',
'app/reducers/views/thread.js',
'app/reducers/views/user.js',
'app/screens/channel/channel.android.js',
'app/screens/channel/channel_base.js',
'app/screens/channel/channel_nav_bar/channel_drawer_button.js',
'app/screens/channel/channel_nav_bar/channel_nav_bar.js',
'app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js',
'app/screens/channel/channel_nav_bar/channel_search_button/index.js',
'app/screens/channel/channel_nav_bar/channel_title/channel_title.js',
'app/screens/channel/channel_nav_bar/channel_title/index.js',
'app/screens/channel/channel_nav_bar/index.js',
'app/screens/channel/channel_nav_bar/settings_drawer_button.js',
'app/screens/channel/channel_post_list/channel_post_list.js',
'app/screens/channel/channel_post_list/index.js',
'app/screens/channel/index.js',
'app/screens/entry/entry.js',
'app/screens/entry/index.js',
'app/screens/index.js',
'app/screens/select_server/index.js',
'app/screens/select_server/select_server.js',
'app/selectors/client_upgrade.js',
'app/store/ephemeral_store.js',
'app/store/index.js',
'app/store/middleware.js',
'app/store/thunk.js',
'app/telemetry/index.js',
'app/telemetry/telemetry.android.js',
'app/telemetry/telemetry_utils.js',
'app/utils/avoid_native_bridge.js',
'app/utils/general.js',
'app/utils/i18n.js',
'app/utils/image_cache_manager.js',
'app/utils/network.js',
'app/utils/push_notifications.js',
'app/utils/sentry/middleware.js',
'app/utils/theme.js',
'app/utils/time_tracker.js',
'dist/assets/config.json',
'dist/assets/images/icons/audio.png',
'dist/assets/images/icons/brokenimage.png',
'dist/assets/images/icons/code.png',
'dist/assets/images/icons/excel.png',
'dist/assets/images/icons/generic.png',
'dist/assets/images/icons/image.png',
'dist/assets/images/icons/patch.png',
'dist/assets/images/icons/pdf.png',
'dist/assets/images/icons/ppt.png',
'dist/assets/images/icons/video.png',
'dist/assets/images/icons/webhook.jpg',
'dist/assets/images/icons/word.png',
'dist/assets/images/post_header/flag.png',
'dist/assets/images/post_header/pin.png',
'dist/assets/images/profile.jpg',
'dist/assets/images/status/away.png',
'dist/assets/images/status/dnd.png',
'dist/assets/images/status/offline.png',
'dist/assets/images/status/online.png',
'dist/assets/images/thumb.png',
'index.js',
'node_modules/@babel/runtime/helpers/arrayWithHoles.js',
'node_modules/@babel/runtime/helpers/assertThisInitialized.js',
@ -208,9 +87,7 @@ module.exports = [
'node_modules/@babel/runtime/regenerator/index.js',
'node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
'node_modules/@react-native-community/async-storage/lib/index.js',
'node_modules/@react-native-community/netinfo/js/index.js',
'node_modules/base-64/base64.js',
'node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js',
'node_modules/component-emitter/index.js',
'node_modules/core-js/modules/_a-function.js',
'node_modules/core-js/modules/_add-to-unscopables.js',
@ -288,12 +165,10 @@ module.exports = [
'node_modules/core-js/modules/es6.set.js',
'node_modules/core-js/modules/es6.string.includes.js',
'node_modules/core-js/modules/es6.string.iterator.js',
'node_modules/core-js/modules/es6.string.starts-with.js',
'node_modules/core-js/modules/es6.symbol.js',
'node_modules/core-js/modules/es7.array.includes.js',
'node_modules/core-js/modules/es7.object.define-getter.js',
'node_modules/core-js/modules/es7.object.define-setter.js',
'node_modules/core-js/modules/es7.object.entries.js',
'node_modules/core-js/modules/es7.object.values.js',
'node_modules/core-js/modules/es7.symbol.async-iterator.js',
'node_modules/core-js/modules/web.dom.iterable.js',
@ -311,7 +186,6 @@ module.exports = [
'node_modules/fbjs/lib/keyOf.js',
'node_modules/fbjs/lib/performance.js',
'node_modules/fbjs/lib/performanceNow.js',
'node_modules/fuse.js/dist/fuse.js',
'node_modules/global/window.js',
'node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
'node_modules/intl-format-cache/index.js',
@ -473,10 +347,6 @@ module.exports = [
'node_modules/mattermost-redux/reducers/requests/search.js',
'node_modules/mattermost-redux/reducers/requests/teams.js',
'node_modules/mattermost-redux/reducers/requests/users.js',
'node_modules/mattermost-redux/selectors/entities/emojis.js',
'node_modules/mattermost-redux/selectors/entities/general.js',
'node_modules/mattermost-redux/selectors/entities/integrations.js',
'node_modules/mattermost-redux/selectors/entities/teams.js',
'node_modules/mattermost-redux/store/configureStore.prod.js',
'node_modules/mattermost-redux/store/helpers.js',
'node_modules/mattermost-redux/store/index.js',
@ -485,17 +355,9 @@ module.exports = [
'node_modules/mattermost-redux/store/reducer_registry.js',
'node_modules/mattermost-redux/utils/deep_freeze.js',
'node_modules/mattermost-redux/utils/event_emitter.js',
'node_modules/mattermost-redux/utils/file_utils.js',
'node_modules/mattermost-redux/utils/helpers.js',
'node_modules/mattermost-redux/utils/key_mirror.js',
'node_modules/mattermost-redux/utils/post_list.js',
'node_modules/mattermost-redux/utils/theme_utils.js',
'node_modules/moment-timezone/data/packed/latest.json',
'node_modules/moment-timezone/index.js',
'node_modules/moment-timezone/moment-timezone.js',
'node_modules/moment/moment.js',
'node_modules/object-assign/index.js',
'node_modules/pascalcase/index.js',
'node_modules/path-to-regexp/index.js',
'node_modules/path-to-regexp/node_modules/isarray/index.js',
'node_modules/promise/setimmediate/core.js',
@ -517,11 +379,8 @@ module.exports = [
'node_modules/react-intl/locale-data/index.js',
'node_modules/react-is/cjs/react-is.production.min.js',
'node_modules/react-is/index.js',
'node_modules/react-native-button/Button.js',
'node_modules/react-native-button/coalesceNonElementChildren.js',
'node_modules/react-native-device-info/deviceinfo.js',
'node_modules/react-native-linear-gradient/common.js',
'node_modules/react-native-linear-gradient/index.android.js',
'node_modules/react-native-keychain/index.js',
'node_modules/react-native-local-auth/LocalAuth.android.js',
'node_modules/react-native-local-auth/index.js',
'node_modules/react-native-navigation/src/NativeEventsReceiver.js',
@ -541,7 +400,6 @@ module.exports = [
'node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js',
'node_modules/react-native/Libraries/Animated/src/Easing.js',
'node_modules/react-native/Libraries/Animated/src/components/AnimatedText.js',
'node_modules/react-native/Libraries/Animated/src/components/AnimatedView.js',
'node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js',
'node_modules/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js',
'node_modules/react-native/Libraries/Animated/src/nodes/AnimatedNode.js',
@ -562,13 +420,9 @@ module.exports = [
'node_modules/react-native/Libraries/Components/TextInput/TextInputState.js',
'node_modules/react-native/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js',
'node_modules/react-native/Libraries/Components/Touchable/Touchable.js',
'node_modules/react-native/Libraries/Components/Touchable/TouchableOpacity.js',
'node_modules/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js',
'node_modules/react-native/Libraries/Components/View/PlatformViewPropTypes.android.js',
'node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js',
'node_modules/react-native/Libraries/Components/View/ReactNativeViewAttributes.js',
'node_modules/react-native/Libraries/Components/View/View.js',
'node_modules/react-native/Libraries/Components/View/ViewNativeComponent.js',
'node_modules/react-native/Libraries/Core/ExceptionsManager.js',
'node_modules/react-native/Libraries/Core/InitializeCore.js',
'node_modules/react-native/Libraries/Core/ReactNativeVersion.js',
@ -605,7 +459,6 @@ module.exports = [
'node_modules/react-native/Libraries/Image/AssetRegistry.js',
'node_modules/react-native/Libraries/Image/AssetSourceResolver.js',
'node_modules/react-native/Libraries/Image/Image.android.js',
'node_modules/react-native/Libraries/Image/ImageBackground.js',
'node_modules/react-native/Libraries/Image/resolveAssetSource.js',
'node_modules/react-native/Libraries/Interaction/PanResponder.js',
'node_modules/react-native/Libraries/JSInspector/InspectorAgent.js',
@ -614,10 +467,8 @@ module.exports = [
'node_modules/react-native/Libraries/Linking/Linking.js',
'node_modules/react-native/Libraries/Network/NetInfo.js',
'node_modules/react-native/Libraries/Performance/Systrace.js',
'node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js',
'node_modules/react-native/Libraries/Promise.js',
'node_modules/react-native/Libraries/ReactNative/AppRegistry.js',
'node_modules/react-native/Libraries/ReactNative/FabricUIManager.js',
'node_modules/react-native/Libraries/ReactNative/I18nManager.js',
'node_modules/react-native/Libraries/ReactNative/UIManager.js',
'node_modules/react-native/Libraries/ReactNative/UIManagerProperties.js',
@ -634,7 +485,6 @@ module.exports = [
'node_modules/react-native/Libraries/StyleSheet/processTransform.js',
'node_modules/react-native/Libraries/Text/Text.js',
'node_modules/react-native/Libraries/Text/TextStylePropTypes.js',
'node_modules/react-native/Libraries/Utilities/BackHandler.android.js',
'node_modules/react-native/Libraries/Utilities/DeviceInfo.js',
'node_modules/react-native/Libraries/Utilities/Dimensions.js',
'node_modules/react-native/Libraries/Utilities/HMRClient.js',
@ -718,15 +568,30 @@ module.exports = [
'node_modules/redux-thunk/lib/index.js',
'node_modules/redux/lib/redux.js',
'node_modules/regenerator-runtime/runtime.js',
'node_modules/reselect/lib/index.js',
'node_modules/rn-fetch-blob/android.js',
'node_modules/rn-fetch-blob/cba/index.js',
'node_modules/rn-fetch-blob/class/RNFetchBlobFile.js',
'node_modules/rn-fetch-blob/class/RNFetchBlobReadStream.js',
'node_modules/rn-fetch-blob/class/RNFetchBlobSession.js',
'node_modules/rn-fetch-blob/class/RNFetchBlobWriteStream.js',
'node_modules/rn-fetch-blob/fs.js',
'node_modules/rn-fetch-blob/index.js',
'node_modules/rn-fetch-blob/json-stream.js',
'node_modules/rn-fetch-blob/lib/oboe-browser.min.js',
'node_modules/rn-fetch-blob/polyfill/Blob.js',
'node_modules/rn-fetch-blob/polyfill/Event.js',
'node_modules/rn-fetch-blob/polyfill/EventTarget.js',
'node_modules/rn-fetch-blob/polyfill/Fetch.js',
'node_modules/rn-fetch-blob/polyfill/File.js',
'node_modules/rn-fetch-blob/polyfill/FileReader.js',
'node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
'node_modules/rn-fetch-blob/polyfill/index.js',
'node_modules/rn-fetch-blob/utils/log.js',
'node_modules/rn-fetch-blob/utils/unicode.js',
'node_modules/rn-fetch-blob/utils/uri.js',
'node_modules/rn-fetch-blob/utils/uuid.js',
'node_modules/rn-host-detect/index.js',
'node_modules/sc-errors/decycle.js',
'node_modules/sc-errors/index.js',
@ -736,10 +601,8 @@ module.exports = [
'node_modules/scheduler/index.js',
'node_modules/scheduler/tracing.js',
'node_modules/semver/semver.js',
'node_modules/shallow-equals/index.js',
'node_modules/stacktrace-parser/dist/stack-trace-parser.umd.js',
'node_modules/symbol-observable/lib/index.js',
'node_modules/symbol-observable/lib/ponyfill.js',
'node_modules/tinycolor2/tinycolor.js',
'node_modules/url-parse/index.js',
];

View file

@ -1,87 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
module.exports = ['./node_modules/app/app.js',
'./node_modules/app/components/app_icon.js',
'./node_modules/app/components/at_mention/at_mention.js',
'./node_modules/app/components/at_mention/index.js',
'./node_modules/app/components/attachment_button.js',
'./node_modules/app/components/badge.js',
'./node_modules/app/components/emoji/emoji.js',
'./node_modules/app/components/emoji/index.js',
'./node_modules/app/components/fade.js',
'./node_modules/app/components/file_attachment_list/file_attachment_icon.js',
'./node_modules/app/components/file_attachment_list/file_attachment_image.js',
'./node_modules/app/components/formatted_date.js',
'./node_modules/app/components/formatted_markdown_text.js',
'./node_modules/app/components/formatted_text.js',
'./node_modules/app/components/formatted_time.js',
'./node_modules/app/components/interactive_dialog_controller/index.js',
'./node_modules/app/components/interactive_dialog_controller/interactive_dialog_controller.js',
'./node_modules/app/components/layout/keyboard_layout/index.js',
'./node_modules/app/components/layout/keyboard_layout/keyboard_layout.js',
'./node_modules/app/components/loading.js',
'./node_modules/app/components/markdown/hashtag/hashtag.js',
'./node_modules/app/components/markdown/hashtag/index.js',
'./node_modules/app/components/markdown/index.js',
'./node_modules/app/components/markdown/markdown.js',
'./node_modules/app/components/markdown/markdown_block_quote.js',
'./node_modules/app/components/markdown/markdown_code_block/index.js',
'./node_modules/app/components/markdown/markdown_code_block/markdown_code_block.js',
'./node_modules/app/components/markdown/markdown_emoji/index.js',
'./node_modules/app/components/markdown/markdown_emoji/markdown_emoji.js',
'./node_modules/app/components/markdown/markdown_image/index.js',
'./node_modules/app/components/markdown/markdown_image/markdown_image.js',
'./node_modules/app/components/markdown/markdown_link/index.js',
'./node_modules/app/components/markdown/markdown_link/markdown_link.js',
'./node_modules/app/components/markdown/markdown_list.js',
'./node_modules/app/components/markdown/markdown_list_item.js',
'./node_modules/app/components/markdown/markdown_table/index.js',
'./node_modules/app/components/markdown/markdown_table/markdown_table.js',
'./node_modules/app/components/markdown/markdown_table_cell/index.js',
'./node_modules/app/components/markdown/markdown_table_cell/markdown_table_cell.js',
'./node_modules/app/components/markdown/markdown_table_image/index.js',
'./node_modules/app/components/markdown/markdown_table_image/markdown_table_image.js',
'./node_modules/app/components/markdown/markdown_table_row/index.js',
'./node_modules/app/components/markdown/markdown_table_row/markdown_table_row.js',
'./node_modules/app/components/network_indicator/index.js',
'./node_modules/app/components/network_indicator/network_indicator.js',
module.exports = ['./node_modules/app/components/loading.js',
'./node_modules/app/components/paper_plane.js',
'./node_modules/app/components/post/index.js',
'./node_modules/app/components/post/post.js',
'./node_modules/app/components/post_body/index.js',
'./node_modules/app/components/post_body/post_body.js',
'./node_modules/app/components/post_header/index.js',
'./node_modules/app/components/post_header/post_header.js',
'./node_modules/app/components/post_header/post_pre_header.js',
'./node_modules/app/components/post_list/date_header/date_header.js',
'./node_modules/app/components/post_list/date_header/index.js',
'./node_modules/app/components/post_list/index.js',
'./node_modules/app/components/post_list/new_messages_divider.js',
'./node_modules/app/components/post_list/post_list.js',
'./node_modules/app/components/post_list_retry.js',
'./node_modules/app/components/post_profile_picture/index.js',
'./node_modules/app/components/post_profile_picture/post_profile_picture.js',
'./node_modules/app/components/post_textbox/components/typing/index.js',
'./node_modules/app/components/post_textbox/components/typing/typing.js',
'./node_modules/app/components/post_textbox/index.js',
'./node_modules/app/components/post_textbox/post_textbox.android.js',
'./node_modules/app/components/post_textbox/post_textbox_base.js',
'./node_modules/app/components/profile_picture/index.js',
'./node_modules/app/components/profile_picture/profile_picture.js',
'./node_modules/app/components/progressive_image/index.js',
'./node_modules/app/components/progressive_image/progressive_image.js',
'./node_modules/app/components/reply_icon.js',
'./node_modules/app/components/safe_area_view/index.js',
'./node_modules/app/components/safe_area_view/safe_area_view.android.js',
'./node_modules/app/components/send_button.js',
'./node_modules/app/components/show_more_button/index.js',
'./node_modules/app/components/show_more_button/show_more_button.js',
'./node_modules/app/components/start/empty_toolbar.js',
'./node_modules/app/components/status_bar/index.js',
'./node_modules/app/components/status_bar/status_bar.js',
'./node_modules/app/components/user_status/index.js',
'./node_modules/app/components/user_status/user_status.js',
'./node_modules/app/components/vector_icon.js',
'./node_modules/app/constants/custom_prop_types.js',
'./node_modules/app/constants/deep_linking.js',
'./node_modules/app/constants/device.js',
@ -90,7 +11,10 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/app/constants/navigation.js',
'./node_modules/app/constants/permissions.js',
'./node_modules/app/constants/view.js',
'./node_modules/app/fetch_preconfig.js',
'./node_modules/app/init/credentials.js',
'./node_modules/app/init/emm_provider.js',
'./node_modules/app/init/fetch.js',
'./node_modules/app/init/global_event_handler.js',
'./node_modules/app/initial_state.js',
'./node_modules/app/mattermost.js',
'./node_modules/app/mattermost_bucket/index.js',
@ -126,40 +50,16 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/app/reducers/views/team.js',
'./node_modules/app/reducers/views/thread.js',
'./node_modules/app/reducers/views/user.js',
'./node_modules/app/screens/channel/channel.android.js',
'./node_modules/app/screens/channel/channel_base.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_drawer_button.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_nav_bar.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_search_button/index.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_title/channel_title.js',
'./node_modules/app/screens/channel/channel_nav_bar/channel_title/index.js',
'./node_modules/app/screens/channel/channel_nav_bar/index.js',
'./node_modules/app/screens/channel/channel_nav_bar/settings_drawer_button.js',
'./node_modules/app/screens/channel/channel_post_list/channel_post_list.js',
'./node_modules/app/screens/channel/channel_post_list/index.js',
'./node_modules/app/screens/channel/index.js',
'./node_modules/app/screens/entry/entry.js',
'./node_modules/app/screens/entry/index.js',
'./node_modules/app/screens/index.js',
'./node_modules/app/screens/select_server/index.js',
'./node_modules/app/screens/select_server/select_server.js',
'./node_modules/app/selectors/client_upgrade.js',
'./node_modules/app/store/ephemeral_store.js',
'./node_modules/app/store/index.js',
'./node_modules/app/store/middleware.js',
'./node_modules/app/store/thunk.js',
'./node_modules/app/telemetry/index.js',
'./node_modules/app/telemetry/telemetry.android.js',
'./node_modules/app/telemetry/telemetry_utils.js',
'./node_modules/app/utils/avoid_native_bridge.js',
'./node_modules/app/utils/general.js',
'./node_modules/app/utils/i18n.js',
'./node_modules/app/utils/image_cache_manager.js',
'./node_modules/app/utils/network.js',
'./node_modules/app/utils/push_notifications.js',
'./node_modules/app/utils/sentry/middleware.js',
'./node_modules/app/utils/theme.js',
'./node_modules/app/utils/time_tracker.js',
'./node_modules/index.js',
'./node_modules/node_modules/@babel/runtime/helpers/arrayWithHoles.js',
'./node_modules/node_modules/@babel/runtime/helpers/assertThisInitialized.js',
@ -185,9 +85,7 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/@babel/runtime/regenerator/index.js',
'./node_modules/node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
'./node_modules/node_modules/@react-native-community/async-storage/lib/index.js',
'./node_modules/node_modules/@react-native-community/netinfo/js/index.js',
'./node_modules/node_modules/base-64/base64.js',
'./node_modules/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js',
'./node_modules/node_modules/component-emitter/index.js',
'./node_modules/node_modules/core-js/modules/_a-function.js',
'./node_modules/node_modules/core-js/modules/_add-to-unscopables.js',
@ -265,12 +163,10 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/core-js/modules/es6.set.js',
'./node_modules/node_modules/core-js/modules/es6.string.includes.js',
'./node_modules/node_modules/core-js/modules/es6.string.iterator.js',
'./node_modules/node_modules/core-js/modules/es6.string.starts-with.js',
'./node_modules/node_modules/core-js/modules/es6.symbol.js',
'./node_modules/node_modules/core-js/modules/es7.array.includes.js',
'./node_modules/node_modules/core-js/modules/es7.object.define-getter.js',
'./node_modules/node_modules/core-js/modules/es7.object.define-setter.js',
'./node_modules/node_modules/core-js/modules/es7.object.entries.js',
'./node_modules/node_modules/core-js/modules/es7.object.values.js',
'./node_modules/node_modules/core-js/modules/es7.symbol.async-iterator.js',
'./node_modules/node_modules/core-js/modules/web.dom.iterable.js',
@ -288,7 +184,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/fbjs/lib/keyOf.js',
'./node_modules/node_modules/fbjs/lib/performance.js',
'./node_modules/node_modules/fbjs/lib/performanceNow.js',
'./node_modules/node_modules/fuse.js/dist/fuse.js',
'./node_modules/node_modules/global/window.js',
'./node_modules/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
'./node_modules/node_modules/intl-format-cache/index.js',
@ -450,10 +345,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/mattermost-redux/reducers/requests/search.js',
'./node_modules/node_modules/mattermost-redux/reducers/requests/teams.js',
'./node_modules/node_modules/mattermost-redux/reducers/requests/users.js',
'./node_modules/node_modules/mattermost-redux/selectors/entities/emojis.js',
'./node_modules/node_modules/mattermost-redux/selectors/entities/general.js',
'./node_modules/node_modules/mattermost-redux/selectors/entities/integrations.js',
'./node_modules/node_modules/mattermost-redux/selectors/entities/teams.js',
'./node_modules/node_modules/mattermost-redux/store/configureStore.prod.js',
'./node_modules/node_modules/mattermost-redux/store/helpers.js',
'./node_modules/node_modules/mattermost-redux/store/index.js',
@ -462,16 +353,9 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/mattermost-redux/store/reducer_registry.js',
'./node_modules/node_modules/mattermost-redux/utils/deep_freeze.js',
'./node_modules/node_modules/mattermost-redux/utils/event_emitter.js',
'./node_modules/node_modules/mattermost-redux/utils/file_utils.js',
'./node_modules/node_modules/mattermost-redux/utils/helpers.js',
'./node_modules/node_modules/mattermost-redux/utils/key_mirror.js',
'./node_modules/node_modules/mattermost-redux/utils/post_list.js',
'./node_modules/node_modules/mattermost-redux/utils/theme_utils.js',
'./node_modules/node_modules/moment-timezone/index.js',
'./node_modules/node_modules/moment-timezone/moment-timezone.js',
'./node_modules/node_modules/moment/moment.js',
'./node_modules/node_modules/object-assign/index.js',
'./node_modules/node_modules/pascalcase/index.js',
'./node_modules/node_modules/path-to-regexp/index.js',
'./node_modules/node_modules/path-to-regexp/node_modules/isarray/index.js',
'./node_modules/node_modules/promise/setimmediate/core.js',
@ -493,11 +377,8 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-intl/locale-data/index.js',
'./node_modules/node_modules/react-is/cjs/react-is.production.min.js',
'./node_modules/node_modules/react-is/index.js',
'./node_modules/node_modules/react-native-button/Button.js',
'./node_modules/node_modules/react-native-button/coalesceNonElementChildren.js',
'./node_modules/node_modules/react-native-device-info/deviceinfo.js',
'./node_modules/node_modules/react-native-linear-gradient/common.js',
'./node_modules/node_modules/react-native-linear-gradient/index.android.js',
'./node_modules/node_modules/react-native-keychain/index.js',
'./node_modules/node_modules/react-native-local-auth/LocalAuth.android.js',
'./node_modules/node_modules/react-native-local-auth/index.js',
'./node_modules/node_modules/react-native-navigation/src/NativeEventsReceiver.js',
@ -517,7 +398,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/Easing.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/components/AnimatedText.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/components/AnimatedView.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js',
'./node_modules/node_modules/react-native/Libraries/Animated/src/nodes/AnimatedNode.js',
@ -538,13 +418,9 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-native/Libraries/Components/TextInput/TextInputState.js',
'./node_modules/node_modules/react-native/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js',
'./node_modules/node_modules/react-native/Libraries/Components/Touchable/Touchable.js',
'./node_modules/node_modules/react-native/Libraries/Components/Touchable/TouchableOpacity.js',
'./node_modules/node_modules/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js',
'./node_modules/node_modules/react-native/Libraries/Components/View/PlatformViewPropTypes.android.js',
'./node_modules/node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js',
'./node_modules/node_modules/react-native/Libraries/Components/View/ReactNativeViewAttributes.js',
'./node_modules/node_modules/react-native/Libraries/Components/View/View.js',
'./node_modules/node_modules/react-native/Libraries/Components/View/ViewNativeComponent.js',
'./node_modules/node_modules/react-native/Libraries/Core/ExceptionsManager.js',
'./node_modules/node_modules/react-native/Libraries/Core/InitializeCore.js',
'./node_modules/node_modules/react-native/Libraries/Core/ReactNativeVersion.js',
@ -581,7 +457,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-native/Libraries/Image/AssetRegistry.js',
'./node_modules/node_modules/react-native/Libraries/Image/AssetSourceResolver.js',
'./node_modules/node_modules/react-native/Libraries/Image/Image.android.js',
'./node_modules/node_modules/react-native/Libraries/Image/ImageBackground.js',
'./node_modules/node_modules/react-native/Libraries/Image/resolveAssetSource.js',
'./node_modules/node_modules/react-native/Libraries/Interaction/PanResponder.js',
'./node_modules/node_modules/react-native/Libraries/JSInspector/InspectorAgent.js',
@ -590,10 +465,8 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-native/Libraries/Linking/Linking.js',
'./node_modules/node_modules/react-native/Libraries/Network/NetInfo.js',
'./node_modules/node_modules/react-native/Libraries/Performance/Systrace.js',
'./node_modules/node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js',
'./node_modules/node_modules/react-native/Libraries/Promise.js',
'./node_modules/node_modules/react-native/Libraries/ReactNative/AppRegistry.js',
'./node_modules/node_modules/react-native/Libraries/ReactNative/FabricUIManager.js',
'./node_modules/node_modules/react-native/Libraries/ReactNative/I18nManager.js',
'./node_modules/node_modules/react-native/Libraries/ReactNative/UIManager.js',
'./node_modules/node_modules/react-native/Libraries/ReactNative/UIManagerProperties.js',
@ -610,7 +483,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/react-native/Libraries/StyleSheet/processTransform.js',
'./node_modules/node_modules/react-native/Libraries/Text/Text.js',
'./node_modules/node_modules/react-native/Libraries/Text/TextStylePropTypes.js',
'./node_modules/node_modules/react-native/Libraries/Utilities/BackHandler.android.js',
'./node_modules/node_modules/react-native/Libraries/Utilities/DeviceInfo.js',
'./node_modules/node_modules/react-native/Libraries/Utilities/Dimensions.js',
'./node_modules/node_modules/react-native/Libraries/Utilities/HMRClient.js',
@ -694,15 +566,30 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/redux-thunk/lib/index.js',
'./node_modules/node_modules/redux/lib/redux.js',
'./node_modules/node_modules/regenerator-runtime/runtime.js',
'./node_modules/node_modules/reselect/lib/index.js',
'./node_modules/node_modules/rn-fetch-blob/android.js',
'./node_modules/node_modules/rn-fetch-blob/cba/index.js',
'./node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobFile.js',
'./node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobReadStream.js',
'./node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobSession.js',
'./node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobWriteStream.js',
'./node_modules/node_modules/rn-fetch-blob/fs.js',
'./node_modules/node_modules/rn-fetch-blob/index.js',
'./node_modules/node_modules/rn-fetch-blob/json-stream.js',
'./node_modules/node_modules/rn-fetch-blob/lib/oboe-browser.min.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/Blob.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/Event.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/EventTarget.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/Fetch.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/File.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/FileReader.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/index.js',
'./node_modules/node_modules/rn-fetch-blob/utils/log.js',
'./node_modules/node_modules/rn-fetch-blob/utils/unicode.js',
'./node_modules/node_modules/rn-fetch-blob/utils/uri.js',
'./node_modules/node_modules/rn-fetch-blob/utils/uuid.js',
'./node_modules/node_modules/rn-host-detect/index.js',
'./node_modules/node_modules/sc-errors/decycle.js',
'./node_modules/node_modules/sc-errors/index.js',
@ -712,9 +599,7 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/node_modules/scheduler/index.js',
'./node_modules/node_modules/scheduler/tracing.js',
'./node_modules/node_modules/semver/semver.js',
'./node_modules/node_modules/shallow-equals/index.js',
'./node_modules/node_modules/stacktrace-parser/dist/stack-trace-parser.umd.js',
'./node_modules/node_modules/symbol-observable/lib/index.js',
'./node_modules/node_modules/symbol-observable/lib/ponyfill.js',
'./node_modules/node_modules/tinycolor2/tinycolor.js',
'./node_modules/node_modules/url-parse/index.js'];
'./node_modules/node_modules/url-parse/index.js'];

View file

@ -21,7 +21,6 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import Video from 'react-native-video';
import LocalAuth from 'react-native-local-auth';
import RNFetchBlob from 'rn-fetch-blob';
import {getGenericPassword} from 'react-native-keychain';
import {Client4} from 'mattermost-redux/client';
import {Preferences} from 'mattermost-redux/constants';
@ -30,8 +29,8 @@ import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_
import Loading from 'app/components/loading';
import PaperPlane from 'app/components/paper_plane';
import {MAX_FILE_COUNT} from 'app/constants/post_textbox';
import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials';
import mattermostManaged from 'app/mattermost_managed';
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import {getExtensionFromMime} from 'app/utils/file';
import {emptyFunction} from 'app/utils/general';
import {setCSRFFromCookie} from 'app/utils/security';
@ -49,7 +48,6 @@ import {
import ChannelButton from './channel_button';
import TeamButton from './team_button';
const {Initialization} = NativeModules;
const defaultTheme = Preferences.THEMES.default;
const extensionSvg = {
csv: ExcelSvg,
@ -232,32 +230,18 @@ export default class ExtensionPost extends PureComponent {
getAppCredentials = async () => {
try {
const credentials = await avoidNativeBridge(
() => {
return Initialization.credentialsExist;
},
() => {
return Initialization.credentials;
},
() => {
return getGenericPassword();
}
);
const url = await getCurrentServerUrl();
const credentials = await getAppCredentials();
if (credentials) {
const passwordParsed = credentials.password.split(',');
const token = credentials.password;
// password == token, url
if (passwordParsed.length === 2) {
const [token, url] = passwordParsed;
if (url && url !== 'undefined' && token && token !== 'undefined') {
this.token = token;
this.url = url;
Client4.setUrl(url);
Client4.setToken(token);
await setCSRFFromCookie(url);
}
if (url && url !== 'undefined' && token && token !== 'undefined') {
this.token = token;
this.url = url;
Client4.setUrl(url);
Client4.setToken(token);
await setCSRFFromCookie(url);
}
}
} catch (error) {