diff --git a/CHANGELOG.md b/CHANGELOG.md
index 877e7cfb3..da70323d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Mattermost Mobile Apps Changelog
+## 1.20.1 Release
+- Release Date: June 21, 2019
+- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
+
+### Combatibility
+ - Mobile App v1.13+ is required for Mattermost Server v5.4+.
+ - Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
+ - iPhone 5s devices and later with iOS 11+ is required.
+
+### Bug Fixes
+ - Fixed an issue where some Android devices were crashing.
+ - Fixed an issue where messages were missing after reconnecting the network.
+
## 1.20.0 Release
- Release Date: June 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
diff --git a/README.md b/README.md
index 244fadf6e..6afea1902 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ To help with testing app updates before they're released, you can:
### Contribute Code
-1. Look in [GitHub issues](https://github.com/mattermost/mattermost-server/issues?q=label%3A"React+Native") for issues marked as [Help Wanted]
+1. Look in [GitHub issues](https://mattermost.com/pl/help-wanted-mattermost-mobile) for issues marked as [Help Wanted]
2. Comment to let people know you’re working on it
3. Follow [these instructions](https://developers.mattermost.com/contribute/mobile/developer-setup/) to set up your developer environment
4. Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions
diff --git a/android/app/build.gradle b/android/app/build.gradle
index bb876b35d..20028c302 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -114,8 +114,8 @@ android {
}
packagingOptions {
- pickFirst 'lib/x86_64/libjsc.so'
- pickFirst 'lib/arm64-v8a/libjsc.so'
+ pickFirst '**/libjsc.so'
+ pickFirst '**/libc++_shared.so'
}
defaultConfig {
@@ -123,8 +123,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
- versionCode 198
- versionName "1.20.0"
+ versionCode 203
+ versionName "1.21.0"
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
@@ -194,9 +194,6 @@ repositories {
configurations.all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
- if (details.requested.name == 'android-jsc') {
- details.useTarget group: details.requested.group, name: 'android-jsc-intl', version: 'r241213'
- }
if (details.requested.name == 'play-services-base') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
}
@@ -214,6 +211,9 @@ configurations.all {
}
dependencies {
+ // Make sure to put android-jsc at the top
+ implementation "org.webkit:android-jsc-intl:r241213"
+
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.android.support:design:28.0.0'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e3d966fce..4fff02aa5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -44,7 +44,8 @@
android:exported="false" />
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+ android:resizeableActivity="true"/>
getConstants() {
- Map 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
- * replyFromPushNotification
- */
-
- 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 keys = new ArrayList(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 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]);
- constants.put("replyFromPushNotification", app.replyFromPushNotification);
- app.replyFromPushNotification = false;
-
- return constants;
- }
-}
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java b/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java
deleted file mode 100644
index 79e7e12e7..000000000
--- a/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java
+++ /dev/null
@@ -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 createNativeModules(ReactApplicationContext reactContext) {
- return Arrays.asList(new InitializationModule(mApplication, reactContext));
- }
-
- public List> createJSModules() {
- return Collections.emptyList();
- }
-
- @Override
- public List createViewManagers(ReactApplicationContext reactContext) {
- return Collections.emptyList();
- }
-}
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
index 8521c9efb..c089baeee 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
@@ -62,7 +62,6 @@ public class MainApplication extends NavigationApplication implements INotificat
public static MainApplication instance;
public Boolean sharedExtensionIsOpened = false;
- public Boolean replyFromPushNotification = false;
public long APP_START_TIME;
@@ -116,7 +115,6 @@ public class MainApplication extends NavigationApplication implements INotificat
new ReactNativeDocumentPicker(),
new SharePackage(this),
new KeychainPackage(),
- new InitializationPackage(this),
new AsyncStoragePackage(),
new NetInfoPackage(),
new RNCWebViewPackage(),
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java b/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java
new file mode 100644
index 000000000..1c1d331ee
--- /dev/null
+++ b/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java
@@ -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 keys = new ArrayList(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 asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
+ String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL);
+
+ keychainModule.getGenericPasswordForOptions(serverUrl, promise);
+ }
+}
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java b/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java
index 1e73f5e9c..68ecbec2a 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.WritableMap;
public class MattermostManagedModule extends ReactContextBaseJavaModule {
private static MattermostManagedModule instance;
@@ -73,6 +74,19 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
System.exit(0);
}
+ @ReactMethod
+ public void isRunningInSplitView(final Promise promise) {
+ WritableMap result = Arguments.createMap();
+ Activity current = getCurrentActivity();
+ if (current != null) {
+ result.putBoolean("isSplitView", current.isInMultiWindowMode());
+ } else {
+ result.putBoolean("isSplitView", false);
+ }
+
+ promise.resolve(result);
+ }
+
@ReactMethod
public void quitApp() {
getCurrentActivity().finish();
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java
index 2d69a3b2d..dd02b935e 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java
@@ -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;
@@ -36,34 +35,30 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ final CharSequence message = getReplyMessage(intent);
+ if (message == null) {
+ return;
+ }
+
mContext = context;
bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
- final CharSequence message = getReplyMessage(intent);
- 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);
@@ -75,7 +70,11 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) {
final String channelId = bundle.getString("channel_id");
- final String rootId = bundle.getString("post_id");
+ final String postId = bundle.getString("post_id");
+ String rootId = bundle.getString("root_id");
+ if (android.text.TextUtils.isEmpty(rootId)) {
+ rootId = postId;
+ }
if (token == null || serverUrl == null) {
onReplyFailed(notificationManager, notificationId, channelId);
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java
index 64ec0d96f..a276fec0a 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java
@@ -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);
diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index 511f5a8b6..687d1178d 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -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));
};
}
@@ -198,7 +198,7 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
});
}
} else {
- const {lastConnectAt} = state.device.websocket;
+ const {lastConnectAt} = state.websocket;
const lastGetPosts = state.views.channel.lastGetPosts[channelId];
let since;
@@ -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));
}
diff --git a/app/actions/views/login.js b/app/actions/views/login.js
index 740659e4d..f84d97b9d 100644
--- a/app/actions/views/login.js
+++ b/app/actions/views/login.js
@@ -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,
},
});
diff --git a/app/actions/views/login.test.js b/app/actions/views/login.test.js
index 66020d79c..f75dee2f2 100644
--- a/app/actions/views/login.test.js
+++ b/app/actions/views/login.test.js
@@ -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', () => ({
diff --git a/app/app.js b/app/app.js
deleted file mode 100644
index ec896bc18..000000000
--- a/app/app.js
+++ /dev/null
@@ -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 {resetToChannel, resetToSelectServer} from 'app/actions/navigation';
-import {setDeepLinkURL} from 'app/actions/views/root';
-import tracker from 'app/utils/time_tracker';
-import {getCurrentLocale} from 'app/selectors/i18n';
-
-import {getTranslations as getLocalTranslations} from 'app/i18n';
-import {store, handleManagedConfig, initializeModules} 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.canLaunchEntry = true;
- 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}`;
-
- if (this.waitForRehydration) {
- this.waitForRehydration = false;
- this.token = token;
- this.url = url;
- }
-
- // 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.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':
- dispatch(resetToSelectServer(this.allowOtherServers));
- break;
- case 'Channel':
- dispatch(resetToChannel());
- break;
- }
-
- initializeModules();
-
- this.setAppStarted(true);
- }
-}
diff --git a/app/components/message_attachments/action_button/action_button.js b/app/components/message_attachments/action_button/action_button.js
index 86374fd04..a604f6995 100644
--- a/app/components/message_attachments/action_button/action_button.js
+++ b/app/components/message_attachments/action_button/action_button.js
@@ -12,17 +12,18 @@ import ActionButtonText from './action_button_text';
export default class ActionButton extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
- doPostAction: PropTypes.func.isRequired,
+ doPostActionWithCookie: PropTypes.func.isRequired,
}).isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
postId: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
+ cookie: PropTypes.string.isRequired,
};
handleActionPress = preventDoubleTap(() => {
- const {actions, id, postId} = this.props;
- actions.doPostAction(postId, id);
+ const {actions, id, postId, cookie} = this.props;
+ actions.doPostActionWithCookie(postId, id, cookie);
}, 4000);
render() {
diff --git a/app/components/message_attachments/action_button/index.js b/app/components/message_attachments/action_button/index.js
index 7b87164f8..fc7e7cec3 100644
--- a/app/components/message_attachments/action_button/index.js
+++ b/app/components/message_attachments/action_button/index.js
@@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
-import {doPostAction} from 'mattermost-redux/actions/posts';
+import {doPostActionWithCookie} from 'mattermost-redux/actions/posts';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import ActionButton from './action_button';
@@ -18,7 +18,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
- doPostAction,
+ doPostActionWithCookie,
}, dispatch),
};
}
diff --git a/app/components/message_attachments/attachment_actions.js b/app/components/message_attachments/attachment_actions.js
index 67e77886e..9f4203fbd 100644
--- a/app/components/message_attachments/attachment_actions.js
+++ b/app/components/message_attachments/attachment_actions.js
@@ -50,6 +50,7 @@ export default class AttachmentActions extends PureComponent {
diff --git a/app/components/post_attachment_opengraph/post_attachment_opengraph.js b/app/components/post_attachment_opengraph/post_attachment_opengraph.js
index 9ae9ace99..cb7d413ef 100644
--- a/app/components/post_attachment_opengraph/post_attachment_opengraph.js
+++ b/app/components/post_attachment_opengraph/post_attachment_opengraph.js
@@ -89,6 +89,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
const bestImage = getNearestPoint(bestDimensions, data.images, 'width', 'height');
const imageUrl = bestImage.secure_url || bestImage.url;
+
let ogImage;
if (imagesMetadata && imagesMetadata[imageUrl]) {
ogImage = imagesMetadata[imageUrl];
@@ -98,6 +99,12 @@ export default class PostAttachmentOpenGraph extends PureComponent {
ogImage = data.images.find((i) => i.url === imageUrl || i.secure_url === imageUrl);
}
+ // Fallback when the ogImage does not have dimensions but there is a metaImage defined
+ const metaImages = imagesMetadata ? Object.values(imagesMetadata) : null;
+ if ((!ogImage?.width || !ogImage?.height) && metaImages?.length) {
+ ogImage = metaImages[0];
+ }
+
let dimensions = bestDimensions;
if (ogImage?.width && ogImage?.height) {
dimensions = calculateDimensions(ogImage.height, ogImage.width, this.getViewPostWidth());
@@ -139,9 +146,16 @@ export default class PostAttachmentOpenGraph extends PureComponent {
ogImage = openGraphData.images.find((i) => i.url === openGraphImageUrl || i.secure_url === openGraphImageUrl);
}
+ // Fallback when the ogImage does not have dimensions but there is a metaImage defined
+ const metaImages = imagesMetadata ? Object.values(imagesMetadata) : null;
+ if ((!ogImage?.width || !ogImage?.height) && metaImages?.length) {
+ ogImage = metaImages[0];
+ }
+
if (ogImage?.width && ogImage?.height) {
this.setImageSize(imageUrl, ogImage.width, ogImage.height);
} else {
+ // if we get to this point there can be a scroll pop
Image.getSize(imageUrl, (width, height) => {
this.setImageSize(imageUrl, width, height);
}, () => null);
diff --git a/app/components/post_body/index.js b/app/components/post_body/index.js
index 01c0de74a..5d4b9ef26 100644
--- a/app/components/post_body/index.js
+++ b/app/components/post_body/index.js
@@ -11,6 +11,7 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
+import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
import {memoizeResult} from 'mattermost-redux/utils/helpers';
import {
@@ -33,10 +34,12 @@ const POST_TIMEOUT = 20000;
function makeMapStateToProps() {
const memoizeHasEmojisOnly = memoizeResult((message, customEmojis) => hasEmojisOnly(message, customEmojis));
+ const getReactionsForPost = makeGetReactionsForPost();
return (state, ownProps) => {
const post = ownProps.post;
const channel = getChannel(state, post.channel_id) || {};
+ const reactions = getReactionsForPost(state, post.id);
let isFailed = post.failed;
let isPending = post.id === post.pending_post_id;
@@ -86,7 +89,7 @@ function makeMapStateToProps() {
fileIds: post.file_ids,
hasBeenDeleted: post.state === Posts.POST_DELETED,
hasBeenEdited: isEdited(post),
- hasReactions: post.has_reactions,
+ hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions),
isFailed,
isPending,
isPostAddChannelMember,
diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js
index 80c925615..f5b311d16 100644
--- a/app/components/post_body_additional_content/post_body_additional_content.js
+++ b/app/components/post_body_additional_content/post_body_additional_content.js
@@ -10,10 +10,13 @@ import {
Platform,
StyleSheet,
TouchableOpacity,
+ StatusBar,
} from 'react-native';
import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube';
import {intlShape} from 'react-intl';
+import EventEmitter from 'mattermost-redux/utils/event_emitter';
+
import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import PostAttachmentImage from 'app/components/post_attachment_image';
import ProgressiveImage from 'app/components/progressive_image';
@@ -179,11 +182,12 @@ export default class PostBodyAdditionalContent extends PureComponent {
};
generateStaticEmbed = (isYouTube, isImage) => {
- if (isYouTube || isImage) {
+ const {isReplyPost, link, metadata, openGraphData, showLinkPreviews, theme} = this.props;
+
+ if (isYouTube || (isImage && !openGraphData)) {
return null;
}
- const {isReplyPost, link, metadata, openGraphData, showLinkPreviews, theme} = this.props;
const attachments = this.getMessageAttachment();
if (attachments) {
return attachments;
@@ -439,6 +443,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
if (Platform.OS === 'ios') {
YouTubeStandaloneIOS.
playVideo(videoId, startTime).
+ then(this.playYouTubeVideoEnded).
catch(this.playYouTubeVideoError);
} else {
const {googleDeveloperKey} = this.props;
@@ -456,6 +461,13 @@ export default class PostBodyAdditionalContent extends PureComponent {
}
};
+ playYouTubeVideoEnded = () => {
+ if (Platform.OS === 'ios') {
+ StatusBar.setHidden(false);
+ EventEmitter.emit('update_safe_area_view');
+ }
+ };
+
playYouTubeVideoError = (errorMessage) => {
const {formatMessage} = this.context.intl;
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index 39e16dcac..262c0d373 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -280,7 +280,9 @@ export default class PostList extends PureComponent {
scrollToBottom = () => {
setTimeout(() => {
- this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
+ if (this.flatListRef && this.flatListRef.current) {
+ this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
+ }
}, 250);
};
@@ -291,11 +293,13 @@ export default class PostList extends PureComponent {
this.props.initialIndex > 0 &&
!this.hasDoneInitialScroll
) {
- this.flatListRef.current.scrollToIndex({
- animated: false,
- index: this.props.initialIndex,
- viewOffset: 50,
- viewPosition: 0.5,
+ requestAnimationFrame(() => {
+ this.flatListRef.current.scrollToIndex({
+ animated: false,
+ index: this.props.initialIndex,
+ viewOffset: 0,
+ viewPosition: 1, // 0 is at bottom
+ });
});
this.hasDoneInitialScroll = true;
}
diff --git a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap
new file mode 100644
index 000000000..ac7535189
--- /dev/null
+++ b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap
@@ -0,0 +1,149 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PostTextBox should match, full snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/post_textbox/post_textbox.test.js b/app/components/post_textbox/post_textbox.test.js
new file mode 100644
index 000000000..6362f1397
--- /dev/null
+++ b/app/components/post_textbox/post_textbox.test.js
@@ -0,0 +1,90 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {shallowWithIntl} from 'test/intl-test-helper';
+
+import Preferences from 'mattermost-redux/constants/preferences';
+
+import PostTextbox from './post_textbox.ios';
+
+jest.mock('NativeEventEmitter');
+
+describe('PostTextBox', () => {
+ const baseProps = {
+ actions: {
+ addReactionToLatestPost: jest.fn(),
+ createPost: jest.fn(),
+ executeCommand: jest.fn(),
+ handleCommentDraftChanged: jest.fn(),
+ handlePostDraftChanged: jest.fn(),
+ handleClearFiles: jest.fn(),
+ handleClearFailedFiles: jest.fn(),
+ handleRemoveLastFile: jest.fn(),
+ initUploadFiles: jest.fn(),
+ userTyping: jest.fn(),
+ handleCommentDraftSelectionChanged: jest.fn(),
+ setStatus: jest.fn(),
+ selectPenultimateChannel: jest.fn(),
+ },
+ canUploadFiles: true,
+ channelId: 'channel-id',
+ channelDisplayName: 'Test Channel',
+ channelTeamId: 'channel-team-id',
+ channelIsLoading: false,
+ channelIsReadOnly: false,
+ currentUserId: 'current-user-id',
+ deactivatedChannel: false,
+ files: [],
+ maxFileSize: 1024,
+ maxMessageLength: 4000,
+ navigator: {
+ showModal: jest.fn(),
+ },
+ rootId: '',
+ theme: Preferences.THEMES.default,
+ uploadFileRequestStatus: 'NOT_STARTED',
+ value: '',
+ userIsOutOfOffice: false,
+ channelIsArchived: false,
+ onCloseChannel: jest.fn(),
+ cursorPositionEvent: '',
+ valueEvent: '',
+ };
+
+ test('should match, full snapshot', () => {
+ const wrapper = shallowWithIntl(
+
+ );
+
+ expect(wrapper.getElement()).toMatchSnapshot();
+ });
+
+ test('should emit the event but no text is save to draft', () => {
+ const wrapper = shallowWithIntl(
+
+ );
+
+ wrapper.setState({value: 'some text'});
+
+ const instance = wrapper.instance();
+
+ instance.changeDraft = jest.fn();
+ instance.handleAppStateChange('active');
+ expect(instance.changeDraft).not.toBeCalled();
+ });
+
+ test('should emit the event and text is save to draft', () => {
+ const wrapper = shallowWithIntl(
+
+ );
+
+ const instance = wrapper.instance();
+ const value = 'some text';
+
+ wrapper.setState({value});
+ instance.handleAppStateChange('background');
+ expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledWith(baseProps.channelId, value);
+ expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js
index fdc5d12bc..f129c7d74 100644
--- a/app/components/post_textbox/post_textbox_base.js
+++ b/app/components/post_textbox/post_textbox_base.js
@@ -5,6 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Alert,
+ AppState,
BackHandler,
findNodeHandle,
Keyboard,
@@ -98,7 +99,10 @@ export default class PostTextBoxBase extends PureComponent {
componentDidMount() {
const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
+
EventEmitter.on(event, this.handleInsertTextToDraft);
+ AppState.addEventListener('change', this.handleAppStateChange);
+
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
@@ -113,7 +117,10 @@ export default class PostTextBoxBase extends PureComponent {
componentWillUnmount() {
const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
+
EventEmitter.off(event, this.handleInsertTextToDraft);
+ AppState.removeEventListener('change', this.handleAppStateChange);
+
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
@@ -246,6 +253,12 @@ export default class PostTextBoxBase extends PureComponent {
return false;
};
+ handleAppStateChange = (nextAppState) => {
+ if (nextAppState !== 'active') {
+ this.changeDraft(this.state.value);
+ }
+ };
+
handleEndEditing = (e) => {
if (e && e.nativeEvent) {
this.changeDraft(e.nativeEvent.text || '');
diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js
index 4758c992e..00d73702a 100644
--- a/app/components/safe_area_view/safe_area_view.ios.js
+++ b/app/components/safe_area_view/safe_area_view.ios.js
@@ -6,6 +6,8 @@ import PropTypes from 'prop-types';
import {Dimensions, Keyboard, NativeModules, View} from 'react-native';
import SafeArea from 'react-native-safe-area';
+import EventEmitter from 'mattermost-redux/utils/event_emitter';
+
import {DeviceTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
@@ -52,6 +54,7 @@ export default class SafeAreaIos extends PureComponent {
componentDidMount() {
Dimensions.addEventListener('change', this.getSafeAreaInsets);
+ EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets);
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
this.getStatusBarHeight();
@@ -60,6 +63,7 @@ export default class SafeAreaIos extends PureComponent {
componentWillUnmount() {
this.mounted = false;
Dimensions.removeEventListener('change', this.getSafeAreaInsets);
+ EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets);
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
this.mounted = false;
diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
index e69b5ce57..1bfdb0263 100644
--- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
+++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
@@ -80,14 +80,12 @@ exports[`ChannelItem should match snapshot 1`] = `
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -194,14 +192,12 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -308,14 +304,12 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -422,14 +416,12 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -525,14 +517,12 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -634,14 +624,12 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
@@ -739,14 +727,12 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
style={
Array [
Object {
+ "alignSelf": "center",
"color": "rgba(255,255,255,0.4)",
"flex": 1,
"fontSize": 14,
"fontWeight": "600",
- "height": "100%",
- "lineHeight": 44,
"paddingRight": 10,
- "textAlignVertical": "center",
},
Object {
"color": "#ffffff",
diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
index a5d01ffce..e5af091d4 100644
--- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js
+++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
@@ -240,10 +240,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 14,
fontWeight: '600',
paddingRight: 10,
- height: '100%',
flex: 1,
- textAlignVertical: 'center',
- lineHeight: 44,
+ alignSelf: 'center',
},
textActive: {
color: theme.sidebarTextActiveColor,
diff --git a/app/components/sidebars/main/main_sidebar.js b/app/components/sidebars/main/main_sidebar.js
index 1b048cc00..deb649a42 100644
--- a/app/components/sidebars/main/main_sidebar.js
+++ b/app/components/sidebars/main/main_sidebar.js
@@ -5,6 +5,7 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
BackHandler,
+ Dimensions,
Keyboard,
StyleSheet,
View,
@@ -17,6 +18,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import SafeAreaView from 'app/components/safe_area_view';
import DrawerLayout, {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import {DeviceTypes} from 'app/constants';
+import mattermostManaged from 'app/mattermost_managed';
import tracker from 'app/utils/time_tracker';
import {t} from 'app/utils/i18n';
@@ -69,15 +71,19 @@ export default class ChannelSidebar extends Component {
openDrawerOffset,
drawerOpened: false,
searching: false,
+ isSplitView: false,
};
}
componentDidMount() {
+ this.mounted = true;
this.props.actions.getTeams();
+ this.handleDimensions();
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.on('renderDrawer', this.handleShowDrawerContent);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
+ Dimensions.addEventListener('change', this.handleDimensions);
}
componentWillReceiveProps(nextProps) {
@@ -97,7 +103,7 @@ export default class ChannelSidebar extends Component {
shouldComponentUpdate(nextProps, nextState) {
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
- const {openDrawerOffset, show, searching} = this.state;
+ const {openDrawerOffset, isSplitView, show, searching} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
return true;
@@ -105,14 +111,17 @@ export default class ChannelSidebar extends Component {
return nextProps.currentTeamId !== currentTeamId ||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
- nextProps.teamsCount !== teamsCount;
+ nextProps.teamsCount !== teamsCount ||
+ nextState.isSplitView !== isSplitView;
}
componentWillUnmount() {
+ this.mounted = false;
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
EventEmitter.off('renderDrawer', this.handleShowDrawerContent);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
+ Dimensions.addEventListener('change', this.handleDimensions);
}
handleAndroidBack = () => {
@@ -124,6 +133,15 @@ export default class ChannelSidebar extends Component {
return false;
};
+ handleDimensions = () => {
+ if (DeviceTypes.IS_TABLET && this.mounted) {
+ mattermostManaged.isRunningInSplitView().then((result) => {
+ const isSplitView = Boolean(result.isSplitView);
+ this.setState({isSplitView});
+ });
+ }
+ };
+
handleShowDrawerContent = () => {
this.setState({show: true});
};
@@ -378,6 +396,7 @@ export default class ChannelSidebar extends Component {
render() {
const {children, deviceWidth} = this.props;
const {openDrawerOffset} = this.state;
+ const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView;
const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset);
return (
@@ -388,7 +407,7 @@ export default class ChannelSidebar extends Component {
onDrawerOpen={this.handleDrawerOpen}
drawerWidth={drawerWidth}
useNativeAnimations={true}
- isTablet={DeviceTypes.IS_TABLET}
+ isTablet={isTablet}
>
{children}
diff --git a/app/init/credentials.js b/app/init/credentials.js
new file mode 100644
index 000000000..14b401467
--- /dev/null
+++ b/app/init/credentials.js
@@ -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;
+ }
+}
diff --git a/app/init/emm_provider.js b/app/init/emm_provider.js
new file mode 100644
index 000000000..77dfad013
--- /dev/null
+++ b/app/init/emm_provider.js
@@ -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();
diff --git a/app/fetch_preconfig.js b/app/init/fetch.js
similarity index 97%
rename from app/fetch_preconfig.js
rename to app/init/fetch.js
index f5f74050f..57cd167a4 100644
--- a/app/fetch_preconfig.js
+++ b/app/init/fetch.js
@@ -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 = {
diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js
new file mode 100644
index 000000000..df17fb796
--- /dev/null
+++ b/app/init/global_event_handler.js
@@ -0,0 +1,232 @@
+// 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.launchApp = opts.launchApp;
+
+ 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.launchApp) {
+ this.launchApp();
+ }
+ };
+
+ 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.launchApp) {
+ this.launchApp();
+ }
+ };
+
+ 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();
diff --git a/app/mattermost.js b/app/mattermost.js
index 32744fff4..8f2d56903 100644
--- a/app/mattermost.js
+++ b/app/mattermost.js
@@ -1,505 +1,97 @@
// 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 {Navigation, NativeEventsReceiver} from 'react-native-navigation';
+import {Linking, NativeModules, Platform} from 'react-native';
+import {Navigation} 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 {loadMe} from 'mattermost-redux/actions/users';
import {resetToChannel, resetToSelectServer} from 'app/actions/navigation';
-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 {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 telemetry from 'app/telemetry';
-
-import App from './app';
+import configureStore from 'app/store';
import EphemeralStore from 'app/store/ephemeral_store';
-import './fetch_preconfig';
+import telemetry from 'app/telemetry';
+import pushNotificationsUtils from 'app/utils/push_notifications';
-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 sharedExtensionStarted = 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 init = async () => {
+ if (EphemeralStore.appStarted) {
+ launchAppAndAuthenticateIfNeeded();
+ return;
+ }
-const lazyLoadAnalytics = () => {
- const initAnalytics = require('app/utils/segment').init;
-
- return {
- initAnalytics,
- };
-};
-
-export 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);
+ pushNotificationsUtils.configure(store);
+ globalEventHandler.configure({
+ store,
+ launchApp,
});
- if (config) {
- configureAnalytics(config);
+ registerScreens(store, Provider);
+
+ if (sharedExtensionStarted) {
+ EphemeralStore.appStarted = true;
}
- handleOrientationChange({window});
-
- if (Platform.OS === 'ios') {
- StatusBarSizeIOS.addEventListener('willChange', handleStatusBarHeightChange);
-
- StatusBarManager.getHeight(
- (data) => {
- handleStatusBarHeightChange(data.height);
- }
- );
+ if (!EphemeralStore.appStarted) {
+ launchAppAndAuthenticateIfNeeded();
}
};
-const configureAnalytics = (config) => {
- const {
- initAnalytics,
- } = lazyLoadAnalytics();
- if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) {
- initAnalytics(config);
+const launchApp = async () => {
+ telemetry.start([
+ 'start:select_server_screen',
+ 'start:channel_screen',
+ ]);
+
+ const credentials = await getAppCredentials();
+ if (credentials) {
+ store.dispatch(loadMe());
+ store.dispatch(resetToChannel({skipMetrics: true}));
} 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();
- store.dispatch(resetToSelectServer(app.allowOtherServers));
-};
-
-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
+ store.dispatch(resetToSelectServer(emmProvider.allowOtherServers));
}
- store.dispatch(resetToChannel());
+ telemetry.startSinceLaunch(['start:splash_screen']);
+ EphemeralStore.appStarted = true;
};
-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();
+const launchAppAndAuthenticateIfNeeded = async () => {
+ await emmProvider.handleManagedConfig(store);
+ await launchApp();
- 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();
- },
- });
+ if (emmProvider.enabled) {
+ if (emmProvider.jailbreakProtection) {
+ emmProvider.checkIfDeviceIsTrusted();
}
- 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 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);
+ if (emmProvider.inAppPinCode) {
+ await emmProvider.handleAuthentication(store);
}
}
- 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 = () => {
- Navigation.events().registerAppLaunchedListener(() => {
- telemetry.start([
- 'start:select_server_screen',
- 'start:channel_screen',
- ]);
-
- // Keep track of the latest componentId to appear and disappear
- Navigation.events().registerComponentDidAppearListener(({componentId}) => {
- EphemeralStore.addComponentIdToStack(componentId);
- });
- Navigation.events().registerComponentDidDisappearListener(({componentId}) => {
- EphemeralStore.removeComponentIdFromStack(componentId);
- });
-
- Navigation.setRoot({
- root: {
- stack: {
- children: [{
- component: {
- name: 'Entry',
- },
- }],
- options: {
- layout: {
- backgroundColor: 'transparent',
- },
- statusBar: {
- visible: true,
- },
- topBar: {
- visible: false,
- height: 0,
- },
- },
- },
- },
- });
-
- telemetry.startSinceLaunch(['start:splash_screen']);
+ Linking.getInitialURL().then((url) => {
+ store.dispatch(setDeepLinkURL(url));
});
};
-configurePushNotifications();
-const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened;
-const fromPushNotification = Platform.OS === 'android' && Initialization.replyFromPushNotification;
+Navigation.events().registerAppLaunchedListener(() => {
+ init();
-if (startedSharedExtension || fromPushNotification) {
- // Hold on launching Entry screen
- app.canLaunchEntry(false);
-
- // Listen for when the user opens the app
- new NativeEventsReceiver().appLaunched(() => {
- app.canLaunchEntry(true);
- launchEntry();
+ // Keep track of the latest componentId to appear and disappear
+ Navigation.events().registerComponentDidAppearListener(({componentId}) => {
+ EphemeralStore.addComponentIdToStack(componentId);
});
-}
-
-if (app.canLaunchEntry) {
- launchEntry();
-}
+ Navigation.events().registerComponentDidDisappearListener(({componentId}) => {
+ EphemeralStore.removeComponentIdFromStack(componentId);
+ });
+});
diff --git a/app/mattermost_managed/mattermost-managed.android.js b/app/mattermost_managed/mattermost-managed.android.js
index da192cb58..778396a9f 100644
--- a/app/mattermost_managed/mattermost-managed.android.js
+++ b/app/mattermost_managed/mattermost-managed.android.js
@@ -36,6 +36,9 @@ export default {
},
authenticate: LocalAuth.auth,
blurAppScreen: MattermostManaged.blurAppScreen,
+ appGroupIdentifier: null,
+ hasSafeAreaInsets: null,
+ isRunningInSplitView: MattermostManaged.isRunningInSplitView,
getConfig: async () => {
try {
cachedConfig = await MattermostManaged.getConfig();
diff --git a/app/mattermost_managed/mattermost-managed.ios.js b/app/mattermost_managed/mattermost-managed.ios.js
index 7b6d81525..8073f9a26 100644
--- a/app/mattermost_managed/mattermost-managed.ios.js
+++ b/app/mattermost_managed/mattermost-managed.ios.js
@@ -51,7 +51,9 @@ export default {
getCachedConfig: () => {
return cachedConfig;
},
+ appGroupIdentifier: MattermostManaged.appGroupIdentifier,
hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets,
+ isRunningInSplitView: MattermostManaged.isRunningInSplitView,
isDeviceSecure: async () => {
try {
return await LocalAuth.isDeviceSecure();
diff --git a/app/push_notifications/push_notifications.android.js b/app/push_notifications/push_notifications.android.js
index 9e30212f1..2e3943a32 100644
--- a/app/push_notifications/push_notifications.android.js
+++ b/app/push_notifications/push_notifications.android.js
@@ -1,11 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {AppRegistry, AppState, NativeModules} from 'react-native';
+import {AppState, NativeModules} from 'react-native';
import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
-import Notification from 'react-native-notifications/notification.android';
-
-import {emptyFunction} from 'app/utils/general';
const {NotificationPreferences} = NativeModules;
@@ -37,23 +34,6 @@ class PushNotification {
this.handleNotification(data, true);
}
});
-
- AppRegistry.registerHeadlessTask('notificationReplied', () => async (deviceNotification) => {
- const notification = new Notification(deviceNotification);
- const data = notification.getData();
- const completed = emptyFunction;
-
- if (this.onReply) {
- this.onReply(data, data.text, parseInt(data.badge, 10) - parseInt(data.msg_count, 10), completed);
- } else {
- this.deviceNotification = {
- data,
- text: data.text,
- badge: parseInt(data.badge, 10) - parseInt(data.msg_count, 10),
- completed, // used to identify that the notification belongs to a reply
- };
- }
- });
}
handleNotification = (data, userInteraction) => {
diff --git a/app/reducers/device/index.js b/app/reducers/device/index.js
index 14a8e351d..5145f3e85 100644
--- a/app/reducers/device/index.js
+++ b/app/reducers/device/index.js
@@ -8,7 +8,6 @@ import dimension from './dimension';
import isTablet from './is_tablet';
import orientation from './orientation';
import statusBarHeight from './status_bar';
-import websocket from './websocket';
export default combineReducers({
connection,
@@ -16,5 +15,4 @@ export default combineReducers({
isTablet,
orientation,
statusBarHeight,
- websocket,
});
diff --git a/app/reducers/device/websocket.js b/app/reducers/device/websocket.js
deleted file mode 100644
index 803e8fdde..000000000
--- a/app/reducers/device/websocket.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import {GeneralTypes, UserTypes} from 'mattermost-redux/action_types';
-
-function getInitialState() {
- return {
- connected: false,
- lastConnectAt: 0,
- lastDisconnectAt: 0,
- };
-}
-
-export default function(state = getInitialState(), action) {
- if (!state.connected && action.type === GeneralTypes.WEBSOCKET_SUCCESS) {
- return {
- ...state,
- connected: true,
- lastConnectAt: new Date().getTime(),
- };
- } else if (state.connected && (action.type === GeneralTypes.WEBSOCKET_FAILURE || action.type === GeneralTypes.WEBSOCKET_CLOSED)) {
- return {
- ...state,
- connected: false,
- lastDisconnectAt: new Date().getTime(),
- };
- }
-
- if (action.type === UserTypes.LOGOUT_SUCCESS) {
- return getInitialState();
- }
-
- return state;
-}
diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js
index 87fd7f930..3ffce2343 100644
--- a/app/reducers/views/channel.js
+++ b/app/reducers/views/channel.js
@@ -266,7 +266,11 @@ function postVisibility(state = {}, action) {
}
case ViewTypes.INCREASE_POST_VISIBILITY: {
const nextState = {...state};
- nextState[action.data] += action.amount;
+ if (nextState[action.data]) {
+ nextState[action.data] += action.amount;
+ } else {
+ nextState[action.data] = action.amount;
+ }
return nextState;
}
case ViewTypes.RECEIVED_FOCUSED_POST: {
diff --git a/app/reducers/views/channel.test.js b/app/reducers/views/channel.test.js
new file mode 100644
index 000000000..e5fa05ff3
--- /dev/null
+++ b/app/reducers/views/channel.test.js
@@ -0,0 +1,112 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import channelReducer from './channel';
+import {ViewTypes} from 'app/constants';
+
+describe('Reducers.channel', () => {
+ const initialState = {
+ displayName: '',
+ drafts: {},
+ loading: false,
+ refreshing: false,
+ postCountInChannel: {},
+ postVisibility: {},
+ loadingPosts: {},
+ lastGetPosts: {},
+ retryFailed: false,
+ loadMorePostsVisible: true,
+ lastChannelViewTime: {},
+ keepChannelIdAsUnread: null,
+ };
+
+ test('Initial state', () => {
+ const nextState = channelReducer(
+ {
+ displayName: '',
+ drafts: {},
+ loading: false,
+ refreshing: false,
+ postCountInChannel: {},
+ postVisibility: {},
+ loadingPosts: {},
+ lastGetPosts: {},
+ retryFailed: false,
+ loadMorePostsVisible: true,
+ lastChannelViewTime: {},
+ keepChannelIdAsUnread: null,
+ },
+ {}
+ );
+
+ expect(nextState).toEqual(initialState);
+ });
+
+ test('should set the postVisibility amount for a channel', () => {
+ const channelId = 'channel_id';
+ const amount = 15;
+ const nextState = channelReducer(
+ {
+ displayName: '',
+ drafts: {},
+ loading: false,
+ refreshing: false,
+ postCountInChannel: {},
+ postVisibility: {},
+ loadingPosts: {},
+ lastGetPosts: {},
+ retryFailed: false,
+ loadMorePostsVisible: true,
+ lastChannelViewTime: {},
+ keepChannelIdAsUnread: null,
+ },
+ {
+ type: ViewTypes.INCREASE_POST_VISIBILITY,
+ data: channelId,
+ amount,
+ }
+ );
+
+ expect(nextState).toEqual({
+ ...initialState,
+ postVisibility: {
+ [channelId]: amount,
+ },
+ });
+ });
+
+ test('should increase the postVisibility amount for a channel', () => {
+ const channelId = 'channel_id';
+ const amount = 15;
+ const nextState = channelReducer(
+ {
+ displayName: '',
+ drafts: {},
+ loading: false,
+ refreshing: false,
+ postCountInChannel: {},
+ postVisibility: {
+ [channelId]: amount,
+ },
+ loadingPosts: {},
+ lastGetPosts: {},
+ retryFailed: false,
+ loadMorePostsVisible: true,
+ lastChannelViewTime: {},
+ keepChannelIdAsUnread: null,
+ },
+ {
+ type: ViewTypes.INCREASE_POST_VISIBILITY,
+ data: channelId,
+ amount,
+ }
+ );
+
+ expect(nextState).toEqual({
+ ...initialState,
+ postVisibility: {
+ [channelId]: 2 * amount,
+ },
+ });
+ });
+});
diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js
index 0a96f4250..8476eb688 100644
--- a/app/screens/channel/channel_base.js
+++ b/app/screens/channel/channel_base.js
@@ -14,8 +14,6 @@ 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';
@@ -51,6 +50,7 @@ export default class ChannelBase extends PureComponent {
theme: PropTypes.object.isRequired,
showTermsOfService: PropTypes.bool,
disableTermsModal: PropTypes.bool,
+ skipMetrics: PropTypes.bool,
};
static contextTypes = {
@@ -93,7 +93,7 @@ export default class ChannelBase extends PureComponent {
}
componentDidMount() {
- if (tracker.initialLoad) {
+ if (tracker.initialLoad && !this.props.skipMetrics) {
this.props.actions.recordLoadTime('Start time', 'initialLoad');
}
@@ -103,7 +103,9 @@ export default class ChannelBase extends PureComponent {
EventEmitter.emit('renderDrawer');
- telemetry.end(['start:channel_screen']);
+ if (!this.props.skipMetrics) {
+ telemetry.end(['start:channel_screen']);
+ }
}
componentWillReceiveProps(nextProps) {
@@ -215,8 +217,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);
}
diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js
index 9e203e057..1ba9afc54 100644
--- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js
+++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js
@@ -3,9 +3,10 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {Platform, View} from 'react-native';
+import {Dimensions, Platform, View} from 'react-native';
import {DeviceTypes, ViewTypes} from 'app/constants';
+import mattermostManaged from 'app/mattermost_managed';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDrawerButton from './channel_drawer_button';
@@ -30,6 +31,30 @@ export default class ChannelNavBar extends PureComponent {
theme: PropTypes.object.isRequired,
};
+ state = {
+ isSplitView: false,
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.handleDimensions();
+ Dimensions.addEventListener('change', this.handleDimensions);
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ Dimensions.removeEventListener('change', this.handleDimensions);
+ }
+
+ handleDimensions = () => {
+ if (DeviceTypes.IS_TABLET && this.mounted) {
+ mattermostManaged.isRunningInSplitView().then((result) => {
+ const isSplitView = Boolean(result.isSplitView);
+ this.setState({isSplitView});
+ });
+ }
+ };
+
render() {
const {isLandscape, onPress, theme} = this.props;
const {openChannelDrawer, openSettingsDrawer} = this.props;
@@ -59,7 +84,7 @@ export default class ChannelNavBar extends PureComponent {
}
let drawerButtonVisible = false;
- if (!DeviceTypes.IS_TABLET) {
+ if (!DeviceTypes.IS_TABLET || this.state.isSplitView) {
drawerButtonVisible = true;
}
diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js
index dc8ca68bc..7cf52e4a1 100644
--- a/app/screens/channel/channel_post_list/channel_post_list.js
+++ b/app/screens/channel/channel_post_list/channel_post_list.js
@@ -33,7 +33,6 @@ export default class ChannelPostList extends PureComponent {
selectPost: PropTypes.func.isRequired,
recordLoadTime: PropTypes.func.isRequired,
refreshChannelWithRetry: PropTypes.func.isRequired,
- showModal: PropTypes.func.isRequired,
goToScreen: PropTypes.func.isRequired,
}).isRequired,
channelId: PropTypes.string.isRequired,
diff --git a/app/screens/channel_members/channel_members.js b/app/screens/channel_members/channel_members.js
index ce4e7c916..9bb29b8da 100644
--- a/app/screens/channel_members/channel_members.js
+++ b/app/screens/channel_members/channel_members.js
@@ -246,7 +246,7 @@ export default class ChannelMembers extends PureComponent {
enabled: props.id !== this.props.currentUserId,
};
- this.renderItem(props, selectProps);
+ return this.renderItem(props, selectProps);
}
renderUnselectableItem = (props) => {
@@ -255,7 +255,7 @@ export default class ChannelMembers extends PureComponent {
enabled: false,
};
- this.renderItem(props, selectProps);
+ return this.renderItem(props, selectProps);
};
renderLoading = () => {
diff --git a/app/screens/edit_profile/index.js b/app/screens/edit_profile/index.js
index 81a29cb97..21eb519c7 100644
--- a/app/screens/edit_profile/index.js
+++ b/app/screens/edit_profile/index.js
@@ -16,7 +16,7 @@ import EditProfile from './edit_profile';
function mapStateToProps(state, ownProps) {
const config = getConfig(state);
const {serverVersion} = state.entities.general;
- const {service} = ownProps.currentUser;
+ const {auth_service: service} = ownProps.currentUser;
const firstNameDisabled = (service === 'ldap' && config.LdapFirstNameAttributeSet === 'true') ||
(service === 'saml' && config.SamlFirstNameAttributeSet === 'true');
diff --git a/app/screens/entry/entry.js b/app/screens/entry/entry.js
deleted file mode 100644
index c7d576639..000000000
--- a/app/screens/entry/entry.js
+++ /dev/null
@@ -1,253 +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 {
- app,
- store,
-} from 'app/mattermost';
-import PushNotifications from 'app/push_notifications';
-import {stripTrailingSlashes} from 'app/utils/url';
-
-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 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,
- actions: PropTypes.shape({
- autoUpdateTimezone: PropTypes.func.isRequired,
- setDeviceToken: PropTypes.func.isRequired,
- }).isRequired,
- };
-
- constructor(props) {
- super(props);
-
- this.unsubscribeFromStore = null;
- }
-
- componentDidMount() {
- Client4.setUserAgent(DeviceInfo.getUserAgent());
-
- if (store.getState().views.root.hydrationComplete) {
- this.handleHydrationComplete();
- } else {
- this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
- }
- }
-
- 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);
- };
-
- render() {
- const {
- navigator,
- isLandscape,
- } = this.props;
-
- 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 = (
-
-
-
-
- );
-
- loading = (
-
- );
- } else {
- loading = ;
- }
-
- return (
-
- {toolbar}
- {loading}
-
- );
- }
-}
diff --git a/app/screens/entry/index.js b/app/screens/entry/index.js
deleted file mode 100644
index 22bebdae8..000000000
--- a/app/screens/entry/index.js
+++ /dev/null
@@ -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());
diff --git a/app/screens/index.js b/app/screens/index.js
index e5ab8f5af..36d38b336 100644
--- a/app/screens/index.js
+++ b/app/screens/index.js
@@ -6,7 +6,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 Root from 'app/components/root';
import SelectServer from 'app/screens/select_server';
@@ -47,7 +46,6 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('EditChannel', () => wrapper(require('app/screens/edit_channel').default), () => require('app/screens/edit_channel').default);
Navigation.registerComponent('EditPost', () => wrapper(require('app/screens/edit_post').default), () => require('app/screens/edit_post').default);
Navigation.registerComponent('EditProfile', () => wrapper(require('app/screens/edit_profile').default), () => require('app/screens/edit_profile').default);
- Navigation.registerComponent('Entry', () => wrapper(Entry), () => Entry);
Navigation.registerComponent('ExpandedAnnouncementBanner', () => wrapper(require('app/screens/expanded_announcement_banner').default), () => require('app/screens/expanded_announcement_banner').default);
Navigation.registerComponent('FlaggedPosts', () => wrapper(require('app/screens/flagged_posts').default), () => require('app/screens/flagged_posts').default);
Navigation.registerComponent('ForgotPassword', () => wrapper(require('app/screens/forgot_password').default), () => require('app/screens/forgot_password').default);
@@ -89,4 +87,4 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('TimezoneSettings', () => wrapper(require('app/screens/timezone').default), () => require('app/screens/timezone').default);
Navigation.registerComponent('ErrorTeamsList', () => wrapper(require('app/screens/error_teams_list').default), () => require('app/screens/error_teams_list').default);
Navigation.registerComponent('UserProfile', () => wrapper(require('app/screens/user_profile').default), () => require('app/screens/user_profile').default);
-}
+}
\ No newline at end of file
diff --git a/app/screens/long_post/index.js b/app/screens/long_post/index.js
index 3352ccdca..90cb2a8fe 100644
--- a/app/screens/long_post/index.js
+++ b/app/screens/long_post/index.js
@@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
-import {selectPost} from 'mattermost-redux/actions/posts';
+import {selectPost, makeGetReactionsForPost} from 'mattermost-redux/actions/posts';
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -16,14 +16,16 @@ import LongPost from './long_post';
function makeMapStateToProps() {
const getChannel = makeGetChannel();
+ const getReactionsForPost = makeGetReactionsForPost();
return function mapStateToProps(state, ownProps) {
const post = getPost(state, ownProps.postId);
const channel = post ? getChannel(state, {id: post.channel_id}) : null;
+ const reactions = getReactionsForPost(state, post.id);
return {
channelName: channel ? channel.display_name : '',
- hasReactions: post ? post.has_reactions : false,
+ hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions),
inThreadView: Boolean(state.entities.posts.selectedPostId),
fileIds: post ? post.file_ids : false,
theme: getTheme(state),
diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js
index 052044aa2..a51d25539 100644
--- a/app/screens/select_server/select_server.js
+++ b/app/screens/select_server/select_server.js
@@ -28,7 +28,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';
@@ -102,19 +102,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);
}
}
}
diff --git a/app/screens/settings/settings_item/style.ios.js b/app/screens/settings/settings_item/style.ios.js
index ddf47bf21..bebbcfe96 100644
--- a/app/screens/settings/settings_item/style.ios.js
+++ b/app/screens/settings/settings_item/style.ios.js
@@ -40,7 +40,7 @@ export default makeStyleSheetFromTheme((theme) => {
color: theme.centerChannelColor,
flex: 1,
fontSize: 17,
- lineHeight: 43,
+ alignSelf: 'center',
},
arrowContainer: {
justifyContent: 'center',
diff --git a/app/store/ephemeral_store.js b/app/store/ephemeral_store.js
index 7e69decae..f95803088 100644
--- a/app/store/ephemeral_store.js
+++ b/app/store/ephemeral_store.js
@@ -3,6 +3,9 @@
class EphemeralStore {
constructor() {
+ this.appStarted = false;
+ this.appStartedFromPushNotification = false;
+ this.deviceToken = null;
this.componentIdStack = [];
}
diff --git a/app/store/index.js b/app/store/index.js
index 46743abd2..be1155865 100644
--- a/app/store/index.js
+++ b/app/store/index.js
@@ -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,
},
},
{
diff --git a/app/store/middleware.js b/app/store/middleware.js
index 6910aeef3..05a8cbbb7 100644
--- a/app/store/middleware.js
+++ b/app/store/middleware.js
@@ -172,6 +172,10 @@ function resetStateForNewVersion(action) {
selectServer,
recentEmojis,
},
+ websocket: {
+ lastConnectAt: payload.websocket?.lastConnectAt,
+ lastDisconnectAt: payload.websocket?.lastDisconnectAt,
+ },
};
return {
@@ -328,6 +332,10 @@ export function cleanUpState(action, keepCurrent = false) {
...payload.views.channel,
},
},
+ websocket: {
+ lastConnectAt: payload.websocket?.lastConnectAt,
+ lastDisconnectAt: payload.websocket?.lastDisconnectAt,
+ },
};
nextState.errors = payload.errors;
diff --git a/app/utils/error_handling.js b/app/utils/error_handling.js
index f6597f935..64fa5a59d 100644
--- a/app/utils/error_handling.js
+++ b/app/utils/error_handling.js
@@ -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();
diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js
index a26781e90..4d057fb06 100644
--- a/app/utils/push_notifications.js
+++ b/app/utils/push_notifications.js
@@ -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();
diff --git a/app/utils/url.js b/app/utils/url.js
index 296a4e62c..e0feebc8f 100644
--- a/app/utils/url.js
+++ b/app/utils/url.js
@@ -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]};
}
diff --git a/index.js b/index.js
index 54e26e7aa..84ce150c3 100644
--- a/index.js
+++ b/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) => {
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 365ff6ec5..f9480132d 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -80,6 +80,7 @@
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; };
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
+ 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */; };
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; };
@@ -826,6 +827,8 @@
7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; };
7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; };
+ 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = ""; };
+ 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = ""; };
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = ""; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = ""; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = ""; };
@@ -1120,6 +1123,8 @@
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */,
7F151D40221B069200FAD8F3 /* 0155-keys.png */,
7F292AA51E8ABB1100A450A3 /* splash.png */,
+ 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */,
+ 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */,
);
name = Mattermost;
sourceTree = "";
@@ -2635,6 +2640,7 @@
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */,
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */,
7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */,
+ 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2779,7 +2785,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 198;
+ CURRENT_PROJECT_VERSION = 203;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2839,7 +2845,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 198;
+ CURRENT_PROJECT_VERSION = 203;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index bc5a783a7..7677d8aa4 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.20.0
+ 1.21.0
CFBundleSignature
????
CFBundleURLTypes
@@ -34,7 +34,7 @@
CFBundleVersion
- 198
+ 203
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/Mattermost/Mattermost+RCTUITextView.h b/ios/Mattermost/Mattermost+RCTUITextView.h
new file mode 100644
index 000000000..67057d05e
--- /dev/null
+++ b/ios/Mattermost/Mattermost+RCTUITextView.h
@@ -0,0 +1,17 @@
+//
+// Mattermost+RCTUITextView.h
+// Mattermost
+//
+// Created by Elias Nahum on 6/18/19.
+// Copyright © 2019 Facebook. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface Mattermost_RCTUITextView : NSObject
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/Mattermost/Mattermost+RCTUITextView.m b/ios/Mattermost/Mattermost+RCTUITextView.m
new file mode 100644
index 000000000..505798bbc
--- /dev/null
+++ b/ios/Mattermost/Mattermost+RCTUITextView.m
@@ -0,0 +1,36 @@
+//
+// Mattermost+RCTUITextView.m
+// Mattermost
+//
+// Created by Elias Nahum on 6/18/19.
+// Copyright © 2019 Facebook. All rights reserved.
+//
+
+#import "Mattermost+RCTUITextView.h"
+#import "RCTUITextView.h"
+
+@implementation Mattermost_RCTUITextView
+
+@end
+
+@implementation RCTUITextView (DisableCopyPaste)
+
+- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
+{
+ NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"com.apple.configuration.managed"];
+ if(response) {
+ NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
+ BOOL prevent = action == @selector(paste:) ||
+ action == @selector(copy:) ||
+ action == @selector(cut:) ||
+ action == @selector(_share:);
+
+ if ([copyPasteProtection isEqual: @"true"] && prevent) {
+ return NO;
+ }
+ }
+
+ return [super canPerformAction:action withSender:sender];
+}
+
+@end
diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m
index 4000f5902..a96f94d2f 100644
--- a/ios/Mattermost/MattermostManaged.m
+++ b/ios/Mattermost/MattermostManaged.m
@@ -6,7 +6,6 @@
// See License.txt for license information.
//
-#import "RCTUITextView.h"
#import "MattermostManaged.h"
#import
@@ -69,6 +68,7 @@ RCT_EXPORT_MODULE();
return @{
@"hasSafeAreaInsets": @([self hasSafeAreaInsets]),
+ @"appGroupIdentifier": APP_GROUP_ID
};
}
@@ -152,31 +152,19 @@ RCT_EXPORT_METHOD(getConfig:(RCTPromiseResolveBlock)resolve
}
}
+RCT_EXPORT_METHOD(isRunningInSplitView:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject) {
+ BOOL isRunningInFullScreen = CGRectEqualToRect(
+ [UIApplication sharedApplication].delegate.window.frame,
+ [UIApplication sharedApplication].delegate.window.screen.bounds);
+ resolve(@{
+ @"isSplitView": @(!isRunningInFullScreen)
+ });
+}
+
RCT_EXPORT_METHOD(quitApp)
{
exit(0);
}
@end
-
-@implementation RCTUITextView (DisableCopyPaste)
-
-- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
-{
- NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
- if(response) {
- NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
- BOOL prevent = action == @selector(paste:) ||
- action == @selector(copy:) ||
- action == @selector(cut:) ||
- action == @selector(_share:);
-
- if ([copyPasteProtection isEqual: @"true"] && prevent) {
- return NO;
- }
- }
-
- return [super canPerformAction:action withSender:sender];
-}
-
-@end
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index 2d5ea8b45..2b1905d7d 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 1.20.0
+ 1.21.0
CFBundleVersion
- 198
+ 203
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index c948006ef..3b893649d 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.20.0
+ 1.21.0
CFBundleSignature
????
CFBundleVersion
- 198
+ 203
diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist
index 55924d842..f2bcfb68e 100644
--- a/ios/NotificationService/Info.plist
+++ b/ios/NotificationService/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 1.20.0
+ 1.21.0
CFBundleVersion
- 198
+ 203
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj
index 871083eef..35da9ccc2 100644
--- a/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj
+++ b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj
@@ -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 = ""; };
+ 7F82907022C295AC0035544F /* MMKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMKeychainManager.m; sourceTree = ""; };
+ 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 = ""; };
7FABE04C2213818900D0F595 /* MattermostBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = ""; };
@@ -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 = "";
+ };
7FABE02D22137F2900D0F595 = {
isa = PBXGroup;
children = (
7FABE03822137F2900D0F595 /* UploadAttachments */,
7FABE03722137F2900D0F595 /* Products */,
+ 7F82901A22C28F770035544F /* Frameworks */,
);
sourceTree = "";
};
@@ -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",
diff --git a/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h
new file mode 100644
index 000000000..13489b9db
--- /dev/null
+++ b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h
@@ -0,0 +1,3 @@
+@interface MMKeychainManager : NSObject
+-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options;
+@end
diff --git a/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
new file mode 100644
index 000000000..866ce90fb
--- /dev/null
+++ b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
@@ -0,0 +1,73 @@
+#import
+#import "MMKeychainManager.h"
+
+#import
+#import
+
+@implementation MMKeychainManager
+
+// Messages from the comments in
+
+#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
diff --git a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m
index a9b99de04..8be32f6a3 100644
--- a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m
+++ b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m
@@ -32,8 +32,13 @@
if(![fileManager fileExistsAtPath:filePath]) {
return nil;
}
+
NSData *data = [NSData dataWithContentsOfFile:filePath];
- return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
+ if (data != nil) {
+ return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
+ }
+
+ return nil;
}
-(void)removeFile:(NSString *)fileName {
diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.h b/ios/UploadAttachments/UploadAttachments/StoreManager.h
index ce4e4f5eb..23fb17be6 100644
--- a/ios/UploadAttachments/UploadAttachments/StoreManager.h
+++ b/ios/UploadAttachments/UploadAttachments/StoreManager.h
@@ -1,8 +1,10 @@
#import
#import "MattermostBucket.h"
+#import "MMKeychainManager.h"
@interface StoreManager : NSObject
@property MattermostBucket *bucket;
+@property MMKeychainManager *keychain;
@property (nonatomic, strong) NSDictionary *entities;
+(instancetype)shared;
diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.m b/ios/UploadAttachments/UploadAttachments/StoreManager.m
index 143f37a5d..bdaddae1b 100644
--- a/ios/UploadAttachments/UploadAttachments/StoreManager.m
+++ b/ios/UploadAttachments/UploadAttachments/StoreManager.m
@@ -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 {
diff --git a/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h
index 194d712c7..ec1e7cbcc 100644
--- a/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h
+++ b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h
@@ -5,3 +5,4 @@
#import "MMMConstants.h"
#import "MattermostBucket.h"
#import "StoreManager.h"
+#import "MMKeychainManager.h"
diff --git a/ios/UploadAttachments/UploadAttachments/UploadSession.swift b/ios/UploadAttachments/UploadAttachments/UploadSession.swift
index de1b51726..8a9c5b64f 100644
--- a/ios/UploadAttachments/UploadAttachments/UploadSession.swift
+++ b/ios/UploadAttachments/UploadAttachments/UploadSession.swift
@@ -126,28 +126,30 @@ import os.log
public func notificationReceipt(notificationId: Any?, receivedAt: Int, type: Any?) {
let store = StoreManager.shared() as StoreManager
- let _ = store.getEntities(true)
- let serverURL = store.getServerUrl()
- let sessionToken = store.getToken()
- let urlString = "\(serverURL!)/api/v4/notifications/ack"
-
- if (notificationId != nil) {
- let jsonObject: [String: Any] = [
- "id": notificationId as Any,
- "received_at": receivedAt,
- "platform": "ios",
- "type": type as Any
- ]
+ let entities = store.getEntities(true)
+ if (entities != nil) {
+ let serverURL = store.getServerUrl()
+ let sessionToken = store.getToken()
+ let urlString = "\(serverURL!)/api/v4/notifications/ack"
- if !JSONSerialization.isValidJSONObject(jsonObject) {return}
-
- guard let url = URL(string: urlString) else {return}
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization")
- request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
- request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted)
- URLSession(configuration: .ephemeral).dataTask(with: request).resume()
+ if (notificationId != nil) {
+ let jsonObject: [String: Any] = [
+ "id": notificationId as Any,
+ "received_at": receivedAt,
+ "platform": "ios",
+ "type": type as Any
+ ]
+
+ if !JSONSerialization.isValidJSONObject(jsonObject) {return}
+
+ guard let url = URL(string: urlString) else {return}
+ var request = URLRequest(url: url)
+ request.httpMethod = "POST"
+ request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization")
+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
+ request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted)
+ URLSession(configuration: .ephemeral).dataTask(with: request).resume()
+ }
}
}
}
diff --git a/package-lock.json b/package-lock.json
index f2058a1c8..d924b3d8c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
- "version": "1.20.0",
+ "version": "1.21.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5238,6 +5238,7 @@
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
+ "optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -5282,7 +5283,8 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"is-glob": {
"version": "4.0.1",
@@ -7667,24 +7669,25 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
- "resolved": false,
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "resolved": "",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "optional": true
},
"aproba": {
"version": "1.2.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"optional": true
},
"are-we-there-yet": {
"version": "1.1.5",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"optional": true,
"requires": {
@@ -7694,13 +7697,15 @@
},
"balanced-match": {
"version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ "resolved": "",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7708,34 +7713,37 @@
},
"chownr": {
"version": "1.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
"optional": true
},
"code-point-at": {
"version": "1.1.0",
- "resolved": false,
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ "resolved": "",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "resolved": false,
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ "resolved": "",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "resolved": false,
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+ "resolved": "",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"optional": true
},
"debug": {
"version": "4.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"optional": true,
"requires": {
@@ -7744,25 +7752,25 @@
},
"deep-extend": {
"version": "0.6.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true
},
"delegates": {
"version": "1.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"optional": true
},
"detect-libc": {
"version": "1.0.3",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"optional": true,
"requires": {
@@ -7771,13 +7779,13 @@
},
"fs.realpath": {
"version": "1.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"optional": true
},
"gauge": {
"version": "2.7.4",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true,
"requires": {
@@ -7793,7 +7801,7 @@
},
"glob": {
"version": "7.1.3",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"optional": true,
"requires": {
@@ -7807,13 +7815,13 @@
},
"has-unicode": {
"version": "2.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"optional": true
},
"iconv-lite": {
"version": "0.4.24",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"optional": true,
"requires": {
@@ -7822,7 +7830,7 @@
},
"ignore-walk": {
"version": "3.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"optional": true,
"requires": {
@@ -7831,7 +7839,7 @@
},
"inflight": {
"version": "1.0.6",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true,
"requires": {
@@ -7841,46 +7849,51 @@
},
"inherits": {
"version": "2.0.3",
- "resolved": false,
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ "resolved": "",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "optional": true
},
"ini": {
"version": "1.3.5",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"isarray": {
"version": "1.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"optional": true
},
"minimatch": {
"version": "3.0.4",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "resolved": false,
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ "resolved": "",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "optional": true
},
"minipass": {
"version": "2.3.5",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7888,7 +7901,7 @@
},
"minizlib": {
"version": "1.2.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
"optional": true,
"requires": {
@@ -7897,21 +7910,22 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"optional": true
},
"needle": {
"version": "2.3.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
"optional": true,
"requires": {
@@ -7922,7 +7936,7 @@
},
"node-pre-gyp": {
"version": "0.12.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
"optional": true,
"requires": {
@@ -7940,7 +7954,7 @@
},
"nopt": {
"version": "4.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"optional": true,
"requires": {
@@ -7950,13 +7964,13 @@
},
"npm-bundled": {
"version": "1.0.6",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
"optional": true
},
"npm-packlist": {
"version": "1.4.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
"optional": true,
"requires": {
@@ -7966,7 +7980,7 @@
},
"npmlog": {
"version": "4.1.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true,
"requires": {
@@ -7978,38 +7992,40 @@
},
"number-is-nan": {
"version": "1.0.1",
- "resolved": false,
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ "resolved": "",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"optional": true
},
"once": {
"version": "1.4.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "optional": true,
"requires": {
"wrappy": "1"
}
},
"os-homedir": {
"version": "1.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"optional": true
},
"osenv": {
"version": "0.1.5",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"optional": true,
"requires": {
@@ -8019,19 +8035,19 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"optional": true
},
"rc": {
"version": "1.2.8",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"requires": {
@@ -8043,7 +8059,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"optional": true
}
@@ -8051,7 +8067,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"optional": true,
"requires": {
@@ -8066,7 +8082,7 @@
},
"rimraf": {
"version": "2.6.3",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"optional": true,
"requires": {
@@ -8075,43 +8091,45 @@
},
"safe-buffer": {
"version": "5.1.2",
- "resolved": false,
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ "resolved": "",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"optional": true
},
"sax": {
"version": "1.2.4",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"optional": true
},
"semver": {
"version": "5.7.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"optional": true
},
"set-blocking": {
"version": "2.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"optional": true
},
"signal-exit": {
"version": "3.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"optional": true
},
"string-width": {
"version": "1.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -8120,7 +8138,7 @@
},
"string_decoder": {
"version": "1.1.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"optional": true,
"requires": {
@@ -8129,21 +8147,22 @@
},
"strip-ansi": {
"version": "3.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"optional": true
},
"tar": {
"version": "4.4.8",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"optional": true,
"requires": {
@@ -8158,13 +8177,13 @@
},
"util-deprecate": {
"version": "1.0.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"optional": true
},
"wide-align": {
"version": "1.1.3",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"optional": true,
"requires": {
@@ -8173,13 +8192,15 @@
},
"wrappy": {
"version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ "resolved": "",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "resolved": false,
- "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+ "resolved": "",
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+ "optional": true
}
}
},
@@ -12563,9 +12584,9 @@
"dev": true
},
"jsc-android": {
- "version": "241213.1.0",
- "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.1.0.tgz",
- "integrity": "sha512-AH8NYyMNLNhcUEF97QbMxPNLNW+oiSBlvm1rsMNzgJ1d5TQzdh/AOJGsxeeESp3m9YIWGLCgUvGTVoVLs0p68A=="
+ "version": "241213.2.0",
+ "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.2.0.tgz",
+ "integrity": "sha512-nfddejB9jxFSG+Uewf+zwATFi8F2CZEEgoHLoOj13egiBDoC7zMoxK1c5/Ycf3AGmGuwCgjpn3LWe0f4tKYbjw=="
},
"jsdom": {
"version": "11.12.0",
@@ -13139,8 +13160,8 @@
"integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A=="
},
"mattermost-redux": {
- "version": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e",
- "from": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e",
+ "version": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7",
+ "from": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "3.0.0",
@@ -14165,7 +14186,7 @@
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
@@ -14177,7 +14198,7 @@
},
"cliui": {
"version": "4.1.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
"dev": true,
"requires": {
@@ -14206,7 +14227,7 @@
},
"find-up": {
"version": "3.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
@@ -14221,13 +14242,13 @@
},
"invert-kv": {
"version": "2.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
"dev": true
},
"lcid": {
"version": "2.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
"dev": true,
"requires": {
@@ -14236,7 +14257,7 @@
},
"locate-path": {
"version": "3.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
@@ -14263,7 +14284,7 @@
},
"os-locale": {
"version": "3.1.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
"dev": true,
"requires": {
@@ -14283,7 +14304,7 @@
},
"p-locate": {
"version": "3.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
@@ -14304,7 +14325,7 @@
},
"resolve-from": {
"version": "4.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
@@ -14338,7 +14359,7 @@
},
"strip-ansi": {
"version": "4.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@@ -14347,13 +14368,13 @@
},
"uuid": {
"version": "3.3.2",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
},
"y18n": {
"version": "4.0.0",
- "resolved": false,
+ "resolved": "",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
@@ -16035,7 +16056,8 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"braces": {
"version": "2.3.2",
@@ -16298,7 +16320,8 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"micromatch": {
"version": "3.1.10",
diff --git a/package.json b/package.json
index 9cdb3d4cd..d6280e23d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
- "version": "1.20.0",
+ "version": "1.21.0",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -19,8 +19,8 @@
"fuse.js": "3.4.4",
"intl": "1.2.5",
"jail-monkey": "2.2.0",
- "jsc-android": "241213.1.0",
- "mattermost-redux": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e",
+ "jsc-android": "241213.2.0",
+ "mattermost-redux": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7",
"mime-db": "1.40.0",
"moment-timezone": "0.5.25",
"prop-types": "15.7.2",
diff --git a/packager/moduleNames.js b/packager/moduleNames.js
index a19dc859d..17572aadf 100644
--- a/packager/moduleNames.js
+++ b/packager/moduleNames.js
@@ -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',
@@ -109,7 +32,6 @@ module.exports = [
'app/reducers/device/is_tablet.js',
'app/reducers/device/orientation.js',
'app/reducers/device/status_bar.js',
- 'app/reducers/device/websocket.js',
'app/reducers/index.js',
'app/reducers/navigation/index.js',
'app/reducers/views/announcement.js',
@@ -128,61 +50,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 +86,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 +164,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 +185,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 +346,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 +354,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 +378,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/lib/dist/Navigation.js',
@@ -553,7 +411,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',
@@ -574,13 +431,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',
@@ -617,7 +470,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',
@@ -626,10 +478,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',
@@ -646,7 +496,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',
@@ -730,15 +579,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',
@@ -748,10 +612,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',
];
\ No newline at end of file
diff --git a/packager/modulePaths.js b/packager/modulePaths.js
index 6c18ad900..084158aa9 100644
--- a/packager/modulePaths.js
+++ b/packager/modulePaths.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',
@@ -107,7 +31,6 @@ module.exports = ['./node_modules/app/app.js',
'./node_modules/app/reducers/device/is_tablet.js',
'./node_modules/app/reducers/device/orientation.js',
'./node_modules/app/reducers/device/status_bar.js',
- './node_modules/app/reducers/device/websocket.js',
'./node_modules/app/reducers/index.js',
'./node_modules/app/reducers/navigation/index.js',
'./node_modules/app/reducers/views/announcement.js',
@@ -126,40 +49,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 +84,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 +162,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 +183,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 +344,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 +352,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 +376,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/lib/dist/Navigation.js',
@@ -529,7 +409,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',
@@ -550,13 +429,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',
@@ -593,7 +468,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',
@@ -602,10 +476,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',
@@ -622,7 +494,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',
@@ -706,15 +577,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',
@@ -724,9 +610,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'];
\ No newline at end of file
+ './node_modules/node_modules/url-parse/index.js'];
diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js
index a1cfa5679..00f357d03 100644
--- a/share_extension/android/extension_post/extension_post.js
+++ b/share_extension/android/extension_post/extension_post.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) {