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:
parent
ce4b48b695
commit
89b96d51db
38 changed files with 1094 additions and 1886 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => ({
|
||||
|
|
|
|||
316
app/app.js
316
app/app.js
|
|
@ -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
128
app/init/credentials.js
Normal 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
165
app/init/emm_provider.js
Normal 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();
|
||||
|
|
@ -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 = {
|
||||
233
app/init/global_event_handler.js
Normal file
233
app/init/global_event_handler.js
Normal 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();
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ export default {
|
|||
},
|
||||
authenticate: LocalAuth.auth,
|
||||
blurAppScreen: MattermostManaged.blurAppScreen,
|
||||
appGroupIdentifier: null,
|
||||
hasSafeAreaInsets: null,
|
||||
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
|
||||
getConfig: async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export default {
|
|||
getCachedConfig: () => {
|
||||
return cachedConfig;
|
||||
},
|
||||
appGroupIdentifier: MattermostManaged.appGroupIdentifier,
|
||||
hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets,
|
||||
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
|
||||
isDeviceSecure: async () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
app/store/ephemeral_store.js
Normal file
12
app/store/ephemeral_store.js
Normal 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();
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]};
|
||||
}
|
||||
|
|
|
|||
44
index.js
44
index.js
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ RCT_EXPORT_MODULE();
|
|||
|
||||
return @{
|
||||
@"hasSafeAreaInsets": @([self hasSafeAreaInsets]),
|
||||
@"appGroupIdentifier": APP_GROUP_ID
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@interface MMKeychainManager : NSObject
|
||||
-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options;
|
||||
@end
|
||||
73
ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
Normal file
73
ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
#import "MMMConstants.h"
|
||||
#import "MattermostBucket.h"
|
||||
#import "StoreManager.h"
|
||||
#import "MMKeychainManager.h"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue