Merge branch 'master' into rnn-v2
This commit is contained in:
commit
51aac1e6e7
80 changed files with 1859 additions and 2114 deletions
13
CHANGELOG.md
13
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@
|
|||
android:exported="false" />
|
||||
<activity
|
||||
android:name="com.reactnativenavigation.controllers.NavigationActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:resizeableActivity="true"/>
|
||||
<activity
|
||||
android:name="com.mattermost.share.ShareActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
|
|
|
|||
|
|
@ -1,146 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.mattermost.react_native_interface.AsyncStorageHelper;
|
||||
import com.mattermost.react_native_interface.KeysReadableArray;
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
import com.oblador.keychain.KeychainModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InitializationModule extends ReactContextBaseJavaModule {
|
||||
|
||||
static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND";
|
||||
static final String TOOLBAR_TEXT_COLOR = "TOOLBAR_TEXT_COLOR";
|
||||
static final String APP_BACKGROUND = "APP_BACKGROUND";
|
||||
|
||||
private final Application mApplication;
|
||||
|
||||
public InitializationModule(Application application, ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mApplication = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Initialization";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Package all native module variables in constants
|
||||
* in order to avoid the native bridge
|
||||
*
|
||||
* KeyStore:
|
||||
* credentialsExist
|
||||
* deviceToken
|
||||
* currentUserId
|
||||
* token
|
||||
* url
|
||||
*
|
||||
* AsyncStorage:
|
||||
* toolbarBackground
|
||||
* toolbarTextColor
|
||||
* appBackground
|
||||
*
|
||||
* Miscellaneous:
|
||||
* MattermostManaged.Config
|
||||
* 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<String> keys = new ArrayList<String>(5);
|
||||
keys.add(TOOLBAR_BACKGROUND);
|
||||
keys.add(TOOLBAR_TEXT_COLOR);
|
||||
keys.add(APP_BACKGROUND);
|
||||
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) {
|
||||
return keys.get(index);
|
||||
}
|
||||
};
|
||||
|
||||
AsyncStorageHelper asyncStorage = new AsyncStorageHelper(this.getReactApplicationContext());
|
||||
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
|
||||
|
||||
String toolbarBackground = asyncStorageResults.get(TOOLBAR_BACKGROUND);
|
||||
String toolbarTextColor = asyncStorageResults.get(TOOLBAR_TEXT_COLOR);
|
||||
String appBackground = asyncStorageResults.get(APP_BACKGROUND);
|
||||
|
||||
if (toolbarBackground != null
|
||||
&& toolbarTextColor != null
|
||||
&& appBackground != null) {
|
||||
|
||||
constants.put("themesExist", true);
|
||||
constants.put("toolbarBackground", toolbarBackground);
|
||||
constants.put("toolbarTextColor", toolbarTextColor);
|
||||
constants.put("appBackground", appBackground);
|
||||
} else {
|
||||
constants.put("themesExist", false);
|
||||
}
|
||||
|
||||
|
||||
if (credentialsExist[0]) {
|
||||
constants.put("credentialsExist", true);
|
||||
constants.put("credentials", credentials[0]);
|
||||
} else {
|
||||
constants.put("credentialsExist", false);
|
||||
}
|
||||
|
||||
constants.put("managedConfig", config[0]);
|
||||
constants.put("replyFromPushNotification", app.replyFromPushNotification);
|
||||
app.replyFromPushNotification = false;
|
||||
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class InitializationPackage implements ReactPackage {
|
||||
|
||||
private final Application mApplication;
|
||||
|
||||
public InitializationPackage(Application application) {
|
||||
mApplication = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(new InitializationModule(mApplication, reactContext));
|
||||
}
|
||||
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import com.oblador.keychain.KeychainModule;
|
||||
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
import com.mattermost.react_native_interface.AsyncStorageHelper;
|
||||
import com.mattermost.react_native_interface.KeysReadableArray;
|
||||
|
||||
public class MattermostCredentialsHelper {
|
||||
static final String CURRENT_SERVER_URL = "@currentServerUrl";
|
||||
|
||||
public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) {
|
||||
final KeychainModule keychainModule = new KeychainModule(context);
|
||||
final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context);
|
||||
final ArrayList<String> keys = new ArrayList<String>(1);
|
||||
keys.add(CURRENT_SERVER_URL);
|
||||
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) {
|
||||
return keys.get(index);
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
|
||||
String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL);
|
||||
|
||||
keychainModule.getGenericPasswordForOptions(serverUrl, promise);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -15,17 +15,18 @@ import okhttp3.Response;
|
|||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.oblador.keychain.KeychainModule;
|
||||
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
|
||||
public class ReceiptDelivery {
|
||||
static final String CURRENT_SERVER_URL = "@currentServerUrl";
|
||||
|
||||
public static void send (Context context, final String ackId, final String type) {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final KeychainModule keychainModule = new KeychainModule(reactApplicationContext);
|
||||
|
||||
keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() {
|
||||
MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() {
|
||||
@Override
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (value instanceof Boolean && !(Boolean)value) {
|
||||
|
|
@ -34,14 +35,8 @@ public class ReceiptDelivery {
|
|||
|
||||
WritableMap map = (WritableMap) value;
|
||||
if (map != null) {
|
||||
String[] credentials = map.getString("password").split(",[ ]*");
|
||||
String token = null;
|
||||
String serverUrl = null;
|
||||
if (credentials.length == 2) {
|
||||
token = credentials[0];
|
||||
serverUrl = credentials[1];
|
||||
|
||||
}
|
||||
String token = map.getString("password");
|
||||
String serverUrl = map.getString("service");
|
||||
|
||||
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token));
|
||||
execute(serverUrl, token, ackId, type);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from 'mattermost-redux/actions/posts';
|
||||
import {getFilesForPost} from 'mattermost-redux/actions/files';
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
|
||||
import {getTeamMembersByIds, selectTeam} from 'mattermost-redux/actions/teams';
|
||||
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
|
||||
import {General, Preferences} from 'mattermost-redux/constants';
|
||||
import {getPostIdsInChannel} from 'mattermost-redux/selectors/entities/posts';
|
||||
|
|
@ -54,8 +54,8 @@ import {isDirectChannelVisible, isGroupChannelVisible} from 'app/utils/channels'
|
|||
const MAX_POST_TRIES = 3;
|
||||
|
||||
export function loadChannelsIfNecessary(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
await fetchMyChannelsAndMembers(teamId)(dispatch, getState);
|
||||
return async (dispatch) => {
|
||||
await dispatch(fetchMyChannelsAndMembers(teamId));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
|
|||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {app} from 'app/mattermost';
|
||||
import {setAppCredentials} from 'app/init/credentials';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getDeviceTimezone} from 'app/utils/timezone';
|
||||
import {setCSRFFromCookie} from 'app/utils/security';
|
||||
|
|
@ -45,7 +45,7 @@ export function handleSuccessfulLogin() {
|
|||
const currentUserId = getCurrentUserId(state);
|
||||
|
||||
await setCSRFFromCookie(url);
|
||||
app.setAppCredentials(deviceToken, currentUserId, token, url);
|
||||
setAppCredentials(deviceToken, currentUserId, token, url);
|
||||
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
if (enableTimezone) {
|
||||
|
|
@ -56,7 +56,6 @@ export function handleSuccessfulLogin() {
|
|||
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
|
||||
data: {
|
||||
url,
|
||||
token,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import {
|
|||
handlePasswordChanged,
|
||||
} from 'app/actions/views/login';
|
||||
|
||||
jest.mock('app/mattermost', () => ({
|
||||
app: {
|
||||
setAppCredentials: () => jest.fn(),
|
||||
},
|
||||
jest.mock('app/init/credentials', () => ({
|
||||
setAppCredentials: () => jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-native-cookies', () => ({
|
||||
|
|
|
|||
316
app/app.js
316
app/app.js
|
|
@ -1,316 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable global-require*/
|
||||
import {Linking, NativeModules, Platform, Text} from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import {setGenericPassword, getGenericPassword, resetGenericPassword} from 'react-native-keychain';
|
||||
|
||||
import {loadMe} from 'mattermost-redux/actions/users';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export default class AttachmentActions extends PureComponent {
|
|||
<ActionButton
|
||||
key={action.id}
|
||||
id={action.id}
|
||||
cookie={action.cookie}
|
||||
name={action.name}
|
||||
postId={postId}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PostTextBox should match, full snapshot 1`] = `
|
||||
<React.Fragment>
|
||||
<Connect(Typing) />
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderTopColor": "rgba(61,60,64,0.2)",
|
||||
"borderTopWidth": 1,
|
||||
"flexDirection": "row",
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<AttachmentButton
|
||||
blurTextBox={[Function]}
|
||||
browseFileTypes="public.item"
|
||||
canBrowseFiles={true}
|
||||
canBrowsePhotoLibrary={true}
|
||||
canBrowseVideoLibrary={true}
|
||||
canTakePhoto={true}
|
||||
canTakeVideo={true}
|
||||
extraOptions={null}
|
||||
fileCount={0}
|
||||
maxFileCount={5}
|
||||
maxFileSize={1024}
|
||||
navigator={
|
||||
Object {
|
||||
"showModal": [MockFunction],
|
||||
}
|
||||
}
|
||||
onShowFileMaxWarning={[Function]}
|
||||
onShowFileSizeWarning={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
uploadFiles={[Function]}
|
||||
validMimeTypes={Array []}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "#fff",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginRight": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
blurOnSubmit={false}
|
||||
disableFullscreenUI={true}
|
||||
editable={true}
|
||||
keyboardType="default"
|
||||
multiline={true}
|
||||
onChangeText={[Function]}
|
||||
onEndEditing={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Write to Test Channel"
|
||||
placeholderTextColor="rgba(0,0,0,0.5)"
|
||||
rejectResponderTermination={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#000",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"maxHeight": 100,
|
||||
"paddingBottom": 8,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 12,
|
||||
"paddingTop": 8,
|
||||
}
|
||||
}
|
||||
underlineColorAndroid="transparent"
|
||||
value=""
|
||||
/>
|
||||
<Fade
|
||||
visible={false}
|
||||
>
|
||||
<SendButton
|
||||
disabled={false}
|
||||
handleSendMessage={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Fade>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
90
app/components/post_textbox/post_textbox.test.js
Normal file
90
app/components/post_textbox/post_textbox.test.js
Normal file
|
|
@ -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(
|
||||
<PostTextbox {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should emit the event but no text is save to draft', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostTextbox {...baseProps}/>
|
||||
);
|
||||
|
||||
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(
|
||||
<PostTextbox {...baseProps}/>
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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 || '');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
</DrawerLayout>
|
||||
|
|
|
|||
128
app/init/credentials.js
Normal file
128
app/init/credentials.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import * as KeyChain from 'react-native-keychain';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {setCSRFFromCookie} from 'app/utils/security';
|
||||
|
||||
const CURRENT_SERVER = '@currentServerUrl';
|
||||
|
||||
export const getCurrentServerUrl = async () => {
|
||||
return AsyncStorage.getItem(CURRENT_SERVER);
|
||||
};
|
||||
|
||||
export const setAppCredentials = (deviceToken, currentUserId, token, url) => {
|
||||
if (!currentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only save to keychain if the url and token are set
|
||||
if (url && token) {
|
||||
try {
|
||||
const username = `${deviceToken}, ${currentUserId}`;
|
||||
|
||||
EphemeralStore.deviceToken = deviceToken;
|
||||
AsyncStorage.setItem(CURRENT_SERVER, url);
|
||||
KeyChain.setInternetCredentials(url, username, token, {accessGroup: mattermostManaged.appGroupIdentifier});
|
||||
} catch (e) {
|
||||
console.warn('could not set credentials', e); //eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getAppCredentials = async () => {
|
||||
const serverUrl = await AsyncStorage.getItem(CURRENT_SERVER);
|
||||
|
||||
if (serverUrl) {
|
||||
return getInternetCredentials(serverUrl);
|
||||
}
|
||||
|
||||
return getCredentialsFromGenericKeyChain();
|
||||
};
|
||||
|
||||
export const removeAppCredentials = async () => {
|
||||
const url = await getCurrentServerUrl();
|
||||
|
||||
Client4.setCSRF(null);
|
||||
Client4.serverVersion = '';
|
||||
Client4.setUserId('');
|
||||
Client4.setToken('');
|
||||
Client4.setUrl('');
|
||||
|
||||
if (url) {
|
||||
KeyChain.resetInternetCredentials(url);
|
||||
}
|
||||
|
||||
KeyChain.resetGenericPassword();
|
||||
AsyncStorage.removeItem(CURRENT_SERVER);
|
||||
};
|
||||
|
||||
async function getCredentialsFromGenericKeyChain() {
|
||||
try {
|
||||
const credentials = await KeyChain.getGenericPassword();
|
||||
|
||||
if (credentials) {
|
||||
const usernameParsed = credentials.username.split(',');
|
||||
const passwordParsed = credentials.password.split(',');
|
||||
|
||||
// username == deviceToken, currentUserId
|
||||
// password == token, url
|
||||
if (usernameParsed.length === 2 && passwordParsed.length === 2) {
|
||||
const [deviceToken, currentUserId] = usernameParsed;
|
||||
const [token, url] = passwordParsed;
|
||||
|
||||
// if for any case the url and the token aren't valid proceed with re-hydration
|
||||
if (url && url !== 'undefined' && token && token !== 'undefined') {
|
||||
Client4.setUserId(currentUserId);
|
||||
Client4.setUrl(url);
|
||||
Client4.setToken(token);
|
||||
await setCSRFFromCookie(url);
|
||||
|
||||
// Migration: remove the generic credentials and add a server specific one
|
||||
setAppCredentials(deviceToken, currentUserId, token, url);
|
||||
KeyChain.resetGenericPassword();
|
||||
|
||||
return {
|
||||
username: usernameParsed,
|
||||
password: token,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getInternetCredentials(url) {
|
||||
try {
|
||||
const credentials = await KeyChain.getInternetCredentials(url);
|
||||
|
||||
if (credentials) {
|
||||
const usernameParsed = credentials.username.split(',');
|
||||
const token = credentials.password;
|
||||
const [deviceToken, currentUserId] = usernameParsed;
|
||||
|
||||
if (token && token !== 'undefined') {
|
||||
EphemeralStore.deviceToken = deviceToken;
|
||||
Client4.setUserId(currentUserId);
|
||||
Client4.setUrl(url);
|
||||
Client4.setToken(token);
|
||||
await setCSRFFromCookie(url);
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
165
app/init/emm_provider.js
Normal file
165
app/init/emm_provider.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert, Platform} from 'react-native';
|
||||
|
||||
import {handleLoginIdChanged} from 'app/actions/views/login';
|
||||
import {handleServerUrlChanged} from 'app/actions/views/select_server';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
import {getAppCredentials} from './credentials';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
class EMMProvider {
|
||||
constructor() {
|
||||
this.enabled = false;
|
||||
this.performingAuthentication = false;
|
||||
this.previousAppState = null;
|
||||
|
||||
this.inAppPinCode = false;
|
||||
this.blurApplicationScreen = false;
|
||||
this.jailbreakProtection = false;
|
||||
this.vendor = null;
|
||||
|
||||
this.allowOtherServers = true;
|
||||
this.emmServerUrl = null;
|
||||
this.emmUsername = null;
|
||||
}
|
||||
|
||||
checkIfDeviceIsTrusted = (store) => {
|
||||
const isTrusted = mattermostManaged.isTrustedDevice();
|
||||
|
||||
if (!isTrusted) {
|
||||
const state = store.getState();
|
||||
const locale = getCurrentLocale(state);
|
||||
const translations = getTranslations(locale);
|
||||
Alert.alert(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor),
|
||||
translations[t('mobile.managed.jailbreak')].replace('{vendor}', this.vendor),
|
||||
[{
|
||||
text: translations[t('mobile.managed.exit')],
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
mattermostManaged.quitApp();
|
||||
},
|
||||
}],
|
||||
{cancelable: false}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleAuthentication = async (store, prompt = true) => {
|
||||
this.performingAuthentication = true;
|
||||
const isSecured = await mattermostManaged.isDeviceSecure();
|
||||
const state = store.getState();
|
||||
const locale = getCurrentLocale(state);
|
||||
const translations = getTranslations(locale);
|
||||
|
||||
if (isSecured) {
|
||||
try {
|
||||
mattermostBucket.setPreference('emm', this.vendor);
|
||||
if (prompt) {
|
||||
await mattermostManaged.authenticate({
|
||||
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', this.vendor),
|
||||
fallbackToPasscode: true,
|
||||
suppressEnterPassword: true,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
mattermostManaged.quitApp();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
await this.showNotSecuredAlert(translations);
|
||||
|
||||
mattermostManaged.quitApp();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setPerformingAuthentication(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
handleManagedConfig = async (store) => {
|
||||
if (this.performingAuthentication) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const {dispatch} = store;
|
||||
|
||||
if (LocalConfig.AutoSelectServerUrl) {
|
||||
dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl));
|
||||
this.allowOtherServers = false;
|
||||
}
|
||||
|
||||
const managedConfig = await mattermostManaged.getConfig();
|
||||
|
||||
if (managedConfig && Object.keys(managedConfig).length) {
|
||||
this.enabled = true;
|
||||
this.inAppPinCode = managedConfig.inAppPinCode === 'true';
|
||||
|
||||
this.blurApplicationScreen = managedConfig.blurApplicationScreen === 'true';
|
||||
this.jailbreakProtection = managedConfig.jailbreakProtection === 'true';
|
||||
this.vendor = managedConfig.vendor || 'Mattermost';
|
||||
|
||||
const credentials = await getAppCredentials();
|
||||
if (!credentials) {
|
||||
this.emmServerUrl = managedConfig.serverUrl;
|
||||
this.emmUsername = managedConfig.username;
|
||||
|
||||
if (managedConfig.allowOtherServers && managedConfig.allowOtherServers === 'false') {
|
||||
this.allowOtherServers = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.blurApplicationScreen) {
|
||||
mattermostManaged.blurAppScreen(true);
|
||||
}
|
||||
|
||||
if (this.emmServerUrl) {
|
||||
dispatch(handleServerUrlChanged(this.emmServerUrl));
|
||||
}
|
||||
|
||||
if (this.emmUsername) {
|
||||
dispatch(handleLoginIdChanged(this.emmUsername));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
showNotSecuredAlert = (translations) => {
|
||||
return new Promise((resolve) => {
|
||||
const options = [];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
options.push({
|
||||
text: translations[t('mobile.managed.settings')],
|
||||
onPress: () => {
|
||||
mattermostManaged.goToSecuritySettings();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
options.push({
|
||||
text: translations[t('mobile.managed.exit')],
|
||||
onPress: resolve,
|
||||
style: 'cancel',
|
||||
});
|
||||
|
||||
Alert.alert(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor),
|
||||
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
|
||||
options,
|
||||
{cancelable: false, onDismiss: resolve},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new EMMProvider();
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
|
|
@ -146,6 +147,8 @@ const initFetchConfig = async () => {
|
|||
// no managed config
|
||||
}
|
||||
|
||||
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
const certificate = await mattermostBucket.getPreference('cert');
|
||||
fetchConfig = {
|
||||
232
app/init/global_event_handler.js
Normal file
232
app/init/global_event_handler.js
Normal file
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
112
app/reducers/views/channel.test.js
Normal file
112
app/reducers/views/channel.test.js
Normal file
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<View>
|
||||
<StatusBar headerColor={app.toolbarBackground}/>
|
||||
<EmptyToolbar
|
||||
theme={toolbarTheme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
loading = (
|
||||
<ChannelLoader
|
||||
backgroundColor={backgroundColor}
|
||||
channelIsLoading={true}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
loading = <Loading/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
navBarBackgroundColor={app.toolbarBackground}
|
||||
backgroundColor={backgroundColor}
|
||||
navigator={navigator}
|
||||
>
|
||||
{toolbar}
|
||||
{loading}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {getDeviceTimezone} from 'app/utils/timezone';
|
||||
|
||||
const lazyLoadEntry = () => {
|
||||
return require('./entry').default;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const deviceTimezone = getDeviceTimezone();
|
||||
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
enableTimezone,
|
||||
deviceTimezone,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
autoUpdateTimezone,
|
||||
setDeviceToken,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(lazyLoadEntry());
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default makeStyleSheetFromTheme((theme) => {
|
|||
color: theme.centerChannelColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
lineHeight: 43,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
arrowContainer: {
|
||||
justifyContent: 'center',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
class EphemeralStore {
|
||||
constructor() {
|
||||
this.appStarted = false;
|
||||
this.appStartedFromPushNotification = false;
|
||||
this.deviceToken = null;
|
||||
this.componentIdStack = [];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {logError} from 'mattermost-redux/actions/errors';
|
|||
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import {purgeOfflineStore} from 'app/actions/views/root';
|
||||
import {DEFAULT_LOCALE, getTranslations} from 'app/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {
|
||||
captureException,
|
||||
captureJSException,
|
||||
|
|
@ -21,54 +23,55 @@ import {
|
|||
LOGGER_NATIVE,
|
||||
} from 'app/utils/sentry';
|
||||
|
||||
import {app, store} from 'app/mattermost';
|
||||
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const errorHandler = (e, isFatal) => {
|
||||
if (__DEV__ && !e && !isFatal) {
|
||||
// react-native-exception-handler redirects console.error to call this, and React calls
|
||||
// console.error without an exception when prop type validation fails, so this ends up
|
||||
// being called with no arguments when the error handler is enabled in dev mode.
|
||||
return;
|
||||
class JavascriptAndNativeErrorHandler {
|
||||
initializeErrorHandling = (store) => {
|
||||
this.store = store;
|
||||
initializeSentry();
|
||||
setJSExceptionHandler(this.errorHandler, false);
|
||||
setNativeExceptionHandler(this.nativeErrorHandler, false);
|
||||
}
|
||||
|
||||
console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
|
||||
captureJSException(e, isFatal, store);
|
||||
nativeErrorHandler = (e) => {
|
||||
console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console
|
||||
captureException(e, LOGGER_NATIVE, this.store);
|
||||
};
|
||||
|
||||
const {dispatch} = store;
|
||||
errorHandler = (e, isFatal) => {
|
||||
if (__DEV__ && !e && !isFatal) {
|
||||
// react-native-exception-handler redirects console.error to call this, and React calls
|
||||
// console.error without an exception when prop type validation fails, so this ends up
|
||||
// being called with no arguments when the error handler is enabled in dev mode.
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(closeWebSocket());
|
||||
console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
|
||||
captureJSException(e, isFatal, this.store);
|
||||
|
||||
if (Client4.getUrl()) {
|
||||
dispatch(logError(e));
|
||||
}
|
||||
const {dispatch} = this.store;
|
||||
|
||||
if (isFatal && e instanceof Error) {
|
||||
const translations = app.getTranslations();
|
||||
dispatch(closeWebSocket());
|
||||
|
||||
Alert.alert(
|
||||
translations[t('mobile.error_handler.title')],
|
||||
translations[t('mobile.error_handler.description')],
|
||||
[{
|
||||
text: translations[t('mobile.error_handler.button')],
|
||||
onPress: () => {
|
||||
// purge the store
|
||||
dispatch(purgeOfflineStore());
|
||||
},
|
||||
}],
|
||||
{cancelable: false}
|
||||
);
|
||||
}
|
||||
};
|
||||
if (Client4.getUrl()) {
|
||||
dispatch(logError(e));
|
||||
}
|
||||
|
||||
const nativeErrorHandler = (e) => {
|
||||
console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console
|
||||
captureException(e, LOGGER_NATIVE, store);
|
||||
};
|
||||
if (isFatal && e instanceof Error) {
|
||||
const translations = getTranslations(DEFAULT_LOCALE);
|
||||
|
||||
export function initializeErrorHandling() {
|
||||
initializeSentry();
|
||||
setJSExceptionHandler(errorHandler, false);
|
||||
setNativeExceptionHandler(nativeErrorHandler, false);
|
||||
Alert.alert(
|
||||
translations[t('mobile.error_handler.title')],
|
||||
translations[t('mobile.error_handler.description')],
|
||||
[{
|
||||
text: translations[t('mobile.error_handler.button')],
|
||||
onPress: () => {
|
||||
// purge the store
|
||||
dispatch(purgeOfflineStore());
|
||||
},
|
||||
}],
|
||||
{cancelable: false}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new JavascriptAndNativeErrorHandler();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {setDeviceToken} from 'mattermost-redux/actions/general';
|
|||
import {getPosts} from 'mattermost-redux/actions/posts';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {markChannelViewedAndRead, retryGetPostsAction} from 'app/actions/views/channel';
|
||||
|
|
@ -17,169 +16,179 @@ import {
|
|||
loadFromPushNotification,
|
||||
} from 'app/actions/views/root';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {DEFAULT_LOCALE, getLocalizedMessage} from 'app/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {
|
||||
app,
|
||||
store,
|
||||
} from 'app/mattermost';
|
||||
import {getLocalizedMessage} from 'app/i18n';
|
||||
import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const onRegisterDevice = (data) => {
|
||||
app.setIsNotificationsConfigured(true);
|
||||
const state = store.getState();
|
||||
|
||||
let prefix;
|
||||
if (Platform.OS === 'ios') {
|
||||
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
|
||||
if (DeviceInfo.getBundleId().includes('rnbeta')) {
|
||||
prefix = `${prefix}beta`;
|
||||
}
|
||||
} else {
|
||||
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
class PushNotificationUtils {
|
||||
constructor() {
|
||||
this.configured = false;
|
||||
this.replyNotificationData = null;
|
||||
}
|
||||
|
||||
const token = `${prefix}:${data.token}`;
|
||||
if (state.views.root.hydrationComplete) {
|
||||
app.setDeviceToken(token);
|
||||
store.dispatch(setDeviceToken(token));
|
||||
} else {
|
||||
app.setDeviceToken(token);
|
||||
}
|
||||
};
|
||||
configure = (store) => {
|
||||
this.store = store;
|
||||
|
||||
const loadFromNotification = async (notification) => {
|
||||
await store.dispatch(loadFromPushNotification(notification, true));
|
||||
if (!app.startAppFromPushNotification) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
|
||||
PushNotifications.resetNotification();
|
||||
}
|
||||
};
|
||||
|
||||
const onPushNotification = async (deviceNotification) => {
|
||||
const {dispatch, getState} = store;
|
||||
let unsubscribeFromStore = null;
|
||||
let stopLoadingNotification = false;
|
||||
|
||||
// mark the app as started as soon as possible
|
||||
if (!app.appStarted) {
|
||||
app.setStartAppFromPushNotification(true);
|
||||
}
|
||||
|
||||
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
|
||||
const notification = {
|
||||
data,
|
||||
message,
|
||||
PushNotifications.configure({
|
||||
onRegister: this.onRegisterDevice,
|
||||
onNotification: this.onPushNotification,
|
||||
onReply: this.onPushNotificationReply,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true,
|
||||
});
|
||||
};
|
||||
|
||||
if (userInfo) {
|
||||
notification.localNotification = userInfo.localNotification;
|
||||
}
|
||||
loadFromNotification = async (notification) => {
|
||||
await this.store.dispatch(loadFromPushNotification(notification, true));
|
||||
|
||||
if (data.type === 'clear') {
|
||||
dispatch(markChannelViewedAndRead(data.channel_id, null, false));
|
||||
} else {
|
||||
// get the posts for the channel as soon as possible
|
||||
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
|
||||
|
||||
if (foreground) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
|
||||
} else if (userInteraction && !notification.localNotification) {
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
if (getState().views.root.hydrationComplete) {
|
||||
setTimeout(() => {
|
||||
loadFromNotification(notification);
|
||||
}, 0);
|
||||
} else {
|
||||
const waitForHydration = () => {
|
||||
if (getState().views.root.hydrationComplete && !stopLoadingNotification) {
|
||||
stopLoadingNotification = true;
|
||||
unsubscribeFromStore();
|
||||
loadFromNotification(notification);
|
||||
}
|
||||
};
|
||||
|
||||
unsubscribeFromStore = store.subscribe(waitForHydration);
|
||||
}
|
||||
if (!EphemeralStore.appStartedFromPushNotification) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
|
||||
PushNotifications.resetNotification();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const onPushNotificationReply = async (data, text, badge, completed) => {
|
||||
const {dispatch, getState} = store;
|
||||
const state = getState();
|
||||
const reduxCurrentUser = getCurrentUser(state);
|
||||
const reduxCredentialsUrl = state.entities.general.credentials.url;
|
||||
const reduxCredentialsToken = state.entities.general.credentials.token;
|
||||
onPushNotification = async (deviceNotification) => {
|
||||
const {dispatch, getState} = this.store;
|
||||
let unsubscribeFromStore = null;
|
||||
let stopLoadingNotification = false;
|
||||
|
||||
const currentUserId = reduxCurrentUser ? reduxCurrentUser.id : app.currentUserId;
|
||||
const url = reduxCredentialsUrl || app.url;
|
||||
const token = reduxCredentialsToken || app.token;
|
||||
// mark the app as started as soon as possible
|
||||
if (!EphemeralStore.appStarted) {
|
||||
EphemeralStore.appStartedFromPushNotification = true;
|
||||
}
|
||||
|
||||
if (currentUserId) {
|
||||
// one thing to note is that for android it will reply to the last post in the stack
|
||||
const rootId = data.root_id || data.post_id;
|
||||
const post = {
|
||||
user_id: currentUserId,
|
||||
channel_id: data.channel_id,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
message: text,
|
||||
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
|
||||
const notification = {
|
||||
data,
|
||||
message,
|
||||
};
|
||||
|
||||
if (!Client4.getUrl()) {
|
||||
// Make sure the Client has the server url set
|
||||
Client4.setUrl(url);
|
||||
if (userInfo) {
|
||||
notification.localNotification = userInfo.localNotification;
|
||||
}
|
||||
|
||||
if (!Client4.getToken()) {
|
||||
// Make sure the Client has the server token set
|
||||
Client4.setToken(token);
|
||||
}
|
||||
if (data.type === 'clear') {
|
||||
dispatch(markChannelViewedAndRead(data.channel_id, null, false));
|
||||
} else {
|
||||
// get the posts for the channel as soon as possible
|
||||
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
|
||||
|
||||
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
|
||||
const result = await dispatch(createPostForNotificationReply(post));
|
||||
if (result.error) {
|
||||
const locale = reduxCurrentUser ? reduxCurrentUser.locale : DEFAULT_LOCALE;
|
||||
PushNotifications.localNotification({
|
||||
message: getLocalizedMessage(locale, t('mobile.reply_post.failed')),
|
||||
userInfo: {
|
||||
localNotification: true,
|
||||
localTest: true,
|
||||
},
|
||||
});
|
||||
console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console
|
||||
if (foreground) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
|
||||
} else if (userInteraction && !notification.localNotification) {
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
if (getState().views.root.hydrationComplete) { //TODO: Replace when realm is ready
|
||||
setTimeout(() => {
|
||||
this.loadFromNotification(notification);
|
||||
}, 0);
|
||||
} else {
|
||||
const waitForHydration = () => {
|
||||
if (getState().views.root.hydrationComplete && !stopLoadingNotification) {
|
||||
stopLoadingNotification = true;
|
||||
unsubscribeFromStore();
|
||||
this.loadFromNotification(notification);
|
||||
}
|
||||
};
|
||||
|
||||
unsubscribeFromStore = this.store.subscribe(waitForHydration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onPushNotificationReply = async (data, text, badge, completed) => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const state = getState();
|
||||
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
|
||||
const url = await getCurrentServerUrl(); // TODO Change to handle multiple servers
|
||||
const token = credentials.password;
|
||||
const usernameParsed = credentials.username.split(',');
|
||||
const [, currentUserId] = usernameParsed;
|
||||
|
||||
if (currentUserId) {
|
||||
// one thing to note is that for android it will reply to the last post in the stack
|
||||
const rootId = data.root_id || data.post_id;
|
||||
const post = {
|
||||
user_id: currentUserId,
|
||||
channel_id: data.channel_id,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
message: text,
|
||||
};
|
||||
|
||||
if (!Client4.getUrl()) {
|
||||
// Make sure the Client has the server url set
|
||||
Client4.setUrl(url);
|
||||
}
|
||||
|
||||
if (!Client4.getToken()) {
|
||||
// Make sure the Client has the server token set
|
||||
Client4.setToken(token);
|
||||
}
|
||||
|
||||
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
|
||||
const result = await dispatch(createPostForNotificationReply(post));
|
||||
if (result.error) {
|
||||
const locale = getCurrentLocale(state);
|
||||
PushNotifications.localNotification({
|
||||
message: getLocalizedMessage(locale, t('mobile.reply_post.failed')),
|
||||
userInfo: {
|
||||
localNotification: true,
|
||||
localTest: true,
|
||||
},
|
||||
});
|
||||
console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console
|
||||
completed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (badge >= 0) {
|
||||
PushNotifications.setApplicationIconBadgeNumber(badge);
|
||||
}
|
||||
|
||||
dispatch(markChannelViewedAndRead(data.channel_id));
|
||||
this.replyNotificationData = null;
|
||||
completed();
|
||||
return;
|
||||
} else {
|
||||
this.replyNotificationData = {
|
||||
data,
|
||||
text,
|
||||
badge,
|
||||
completed,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
onRegisterDevice = (data) => {
|
||||
const {dispatch, getState} = this.store;
|
||||
let unsubscribeFromStore = null;
|
||||
|
||||
let prefix;
|
||||
if (Platform.OS === 'ios') {
|
||||
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
|
||||
if (DeviceInfo.getBundleId().includes('rnbeta')) {
|
||||
prefix = `${prefix}beta`;
|
||||
}
|
||||
} else {
|
||||
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
}
|
||||
|
||||
if (badge >= 0) {
|
||||
PushNotifications.setApplicationIconBadgeNumber(badge);
|
||||
}
|
||||
EphemeralStore.deviceToken = `${prefix}:${data.token}`;
|
||||
|
||||
dispatch(markChannelViewedAndRead(data.channel_id));
|
||||
app.setReplyNotificationData(null);
|
||||
completed();
|
||||
} else {
|
||||
app.setReplyNotificationData({
|
||||
data,
|
||||
text,
|
||||
badge,
|
||||
completed,
|
||||
});
|
||||
// TODO: Remove when realm is ready
|
||||
const waitForHydration = () => {
|
||||
if (getState().views.root.hydrationComplete && !this.configured) {
|
||||
this.configured = true;
|
||||
dispatch(setDeviceToken(EphemeralStore.deviceToken));
|
||||
unsubscribeFromStore();
|
||||
}
|
||||
};
|
||||
|
||||
unsubscribeFromStore = this.store.subscribe(waitForHydration);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const configurePushNotifications = () => {
|
||||
PushNotifications.configure({
|
||||
onRegister: onRegisterDevice,
|
||||
onNotification: onPushNotification,
|
||||
onReply: onPushNotificationReply,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true,
|
||||
});
|
||||
|
||||
if (app) {
|
||||
app.setIsNotificationsConfigured(true);
|
||||
}
|
||||
};
|
||||
export default new PushNotificationUtils();
|
||||
|
|
|
|||
|
|
@ -103,9 +103,10 @@ export function matchDeepLink(url, serverURL, siteURL) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const linkRoot = `(?:${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`;
|
||||
const linkRoot = `(?:${escapeRegex('mattermost:/')}|${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`;
|
||||
|
||||
let match = new RegExp('^' + linkRoot + '\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url);
|
||||
|
||||
if (match) {
|
||||
return {type: DeepLinkTypes.CHANNEL, teamName: match[1], channelName: match[2]};
|
||||
}
|
||||
|
|
|
|||
44
index.js
44
index.js
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import 'react-native/Libraries/Core/InitializeCore';
|
||||
import {AppRegistry, DeviceEventEmitter, Platform, YellowBox} from 'react-native';
|
||||
import {AppRegistry, DeviceEventEmitter, Platform, Text, YellowBox} from 'react-native';
|
||||
import 'react-native-gesture-handler';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
|
@ -10,16 +10,46 @@ import LocalConfig from 'assets/config';
|
|||
import telemetry from 'app/telemetry';
|
||||
|
||||
import 'app/mattermost';
|
||||
import ShareExtension from 'share_extension/android';
|
||||
|
||||
YellowBox.ignoreWarnings([
|
||||
'Warning: componentWillMount is deprecated',
|
||||
'Warning: componentWillUpdate is deprecated',
|
||||
'Warning: componentWillReceiveProps is deprecated',
|
||||
]);
|
||||
if (__DEV__) {
|
||||
YellowBox.ignoreWarnings([
|
||||
'Warning: componentWillMount is deprecated',
|
||||
'Warning: componentWillUpdate is deprecated',
|
||||
'Warning: componentWillReceiveProps is deprecated',
|
||||
|
||||
// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
|
||||
'Require cycle: node_modules/react-native/Libraries/Network/fetch.js',
|
||||
]);
|
||||
}
|
||||
|
||||
const setFontFamily = () => {
|
||||
// Set a global font for Android
|
||||
const defaultFontFamily = {
|
||||
style: {
|
||||
fontFamily: 'Roboto',
|
||||
},
|
||||
};
|
||||
const TextRender = Text.render;
|
||||
const initialDefaultProps = Text.defaultProps;
|
||||
Text.defaultProps = {
|
||||
...initialDefaultProps,
|
||||
...defaultFontFamily,
|
||||
};
|
||||
Text.render = function render(props, ...args) {
|
||||
const oldProps = props;
|
||||
let newProps = {...props, style: [defaultFontFamily.style, props.style]};
|
||||
try {
|
||||
return Reflect.apply(TextRender, this, [newProps, ...args]);
|
||||
} finally {
|
||||
newProps = oldProps;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
const ShareExtension = require('share_extension/android').default;
|
||||
AppRegistry.registerComponent('MattermostShare', () => ShareExtension);
|
||||
setFontFamily();
|
||||
|
||||
if (LocalConfig.TelemetryEnabled) {
|
||||
const metricsSubscription = DeviceEventEmitter.addListener('nativeMetrics', (metrics) => {
|
||||
|
|
|
|||
|
|
@ -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 = "<group>"; };
|
||||
7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
|
||||
7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = "<group>"; };
|
||||
7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = "<group>"; };
|
||||
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; };
|
||||
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
|
||||
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
|
||||
|
|
@ -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 = "<group>";
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.20.0</string>
|
||||
<string>1.21.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>198</string>
|
||||
<string>203</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
17
ios/Mattermost/Mattermost+RCTUITextView.h
Normal file
17
ios/Mattermost/Mattermost+RCTUITextView.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Mattermost+RCTUITextView.h
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Elias Nahum on 6/18/19.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Mattermost_RCTUITextView : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
36
ios/Mattermost/Mattermost+RCTUITextView.m
Normal file
36
ios/Mattermost/Mattermost+RCTUITextView.m
Normal file
|
|
@ -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
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
// See License.txt for license information.
|
||||
//
|
||||
|
||||
#import "RCTUITextView.h"
|
||||
#import "MattermostManaged.h"
|
||||
#import <UploadAttachments/MMMConstants.h>
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.20.0</string>
|
||||
<string>1.21.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>198</string>
|
||||
<string>203</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.20.0</string>
|
||||
<string>1.21.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>198</string>
|
||||
<string>203</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.20.0</string>
|
||||
<string>1.21.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>198</string>
|
||||
<string>203</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* Constants.h */; };
|
||||
7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* MMMConstants.h */; };
|
||||
7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F82907022C295AC0035544F /* MMKeychainManager.m */; };
|
||||
7F82907322C296970035544F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907222C296970035544F /* Security.framework */; };
|
||||
7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907422C296A10035544F /* LocalAuthentication.framework */; };
|
||||
7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE04C2213818900D0F595 /* MattermostBucket.m */; };
|
||||
7FABE055221387B500D0F595 /* MMMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE053221387B400D0F595 /* MMMConstants.m */; };
|
||||
7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE057221388D600D0F595 /* UploadSessionManager.swift */; };
|
||||
|
|
@ -25,13 +28,17 @@
|
|||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */,
|
||||
7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
7F82906F22C295AB0035544F /* MMKeychainManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMKeychainManager.h; sourceTree = "<group>"; };
|
||||
7F82907022C295AC0035544F /* MMKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMKeychainManager.m; sourceTree = "<group>"; };
|
||||
7F82907222C296970035544F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
7F82907422C296A10035544F /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
|
||||
7FABE03622137F2900D0F595 /* libUploadAttachments.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUploadAttachments.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7FABE04B2213818900D0F595 /* UploadAttachments-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UploadAttachments-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
7FABE04C2213818900D0F595 /* MattermostBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = "<group>"; };
|
||||
|
|
@ -52,17 +59,29 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */,
|
||||
7F82907322C296970035544F /* Security.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
7F82901A22C28F770035544F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F82907422C296A10035544F /* LocalAuthentication.framework */,
|
||||
7F82907222C296970035544F /* Security.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7FABE02D22137F2900D0F595 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7FABE03822137F2900D0F595 /* UploadAttachments */,
|
||||
7FABE03722137F2900D0F595 /* Products */,
|
||||
7F82901A22C28F770035544F /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
|
@ -77,6 +96,8 @@
|
|||
7FABE03822137F2900D0F595 /* UploadAttachments */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F82906F22C295AB0035544F /* MMKeychainManager.h */,
|
||||
7F82907022C295AC0035544F /* MMKeychainManager.m */,
|
||||
7FABE05A2213892100D0F595 /* AttachmentArray.swift */,
|
||||
7FABE0592213892100D0F595 /* AttachmentItem.swift */,
|
||||
7FABE054221387B500D0F595 /* MMMConstants.h */,
|
||||
|
|
@ -175,6 +196,7 @@
|
|||
files = (
|
||||
7FABE05C2213892200D0F595 /* AttachmentArray.swift in Sources */,
|
||||
7FABE0FA2214674200D0F595 /* StoreManager.m in Sources */,
|
||||
7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */,
|
||||
7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */,
|
||||
7FABE0F7221466F900D0F595 /* UploadManager.swift in Sources */,
|
||||
7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */,
|
||||
|
|
@ -309,6 +331,7 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -331,6 +354,7 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@interface MMKeychainManager : NSObject
|
||||
-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options;
|
||||
@end
|
||||
73
ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
Normal file
73
ios/UploadAttachments/UploadAttachments/MMKeychainManager.m
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#import <Security/Security.h>
|
||||
#import "MMKeychainManager.h"
|
||||
|
||||
#import <LocalAuthentication/LAContext.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation MMKeychainManager
|
||||
|
||||
// Messages from the comments in <Security/SecBase.h>
|
||||
|
||||
#pragma mark - Proposed functionality - Helpers
|
||||
|
||||
#define kAuthenticationType @"authenticationType"
|
||||
#define kAuthenticationTypeBiometrics @"AuthenticationWithBiometrics"
|
||||
|
||||
#define kAccessControlType @"accessControl"
|
||||
#define kAccessControlUserPresence @"UserPresence"
|
||||
#define kAccessControlBiometryAny @"BiometryAny"
|
||||
#define kAccessControlBiometryCurrentSet @"BiometryCurrentSet"
|
||||
#define kAccessControlDevicePasscode @"DevicePasscode"
|
||||
#define kAccessControlApplicationPassword @"ApplicationPassword"
|
||||
#define kAccessControlBiometryAnyOrDevicePasscode @"BiometryAnyOrDevicePasscode"
|
||||
#define kAccessControlBiometryCurrentSetOrDevicePasscode @"BiometryCurrentSetOrDevicePasscode"
|
||||
|
||||
#define kBiometryTypeTouchID @"TouchID"
|
||||
#define kBiometryTypeFaceID @"FaceID"
|
||||
|
||||
#define kAuthenticationPromptMessage @"authenticationPrompt"
|
||||
|
||||
#pragma mark - Native access
|
||||
|
||||
-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options
|
||||
{
|
||||
if (server == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *query = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
|
||||
(__bridge NSString *)kSecAttrServer: server,
|
||||
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne
|
||||
};
|
||||
|
||||
// Look up server in the keychain
|
||||
NSDictionary *found = nil;
|
||||
CFTypeRef foundTypeRef = NULL;
|
||||
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef);
|
||||
|
||||
if (osStatus != noErr && osStatus != errSecItemNotFound) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
found = (__bridge NSDictionary*)(foundTypeRef);
|
||||
if (!found) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Found
|
||||
NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
|
||||
NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
|
||||
|
||||
CFRelease(foundTypeRef);
|
||||
NSDictionary *result = @{
|
||||
@"server": server,
|
||||
@"username": username,
|
||||
@"password": password
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "MattermostBucket.h"
|
||||
#import "MMKeychainManager.h"
|
||||
|
||||
@interface StoreManager : NSObject
|
||||
@property MattermostBucket *bucket;
|
||||
@property MMKeychainManager *keychain;
|
||||
@property (nonatomic, strong) NSDictionary *entities;
|
||||
|
||||
+(instancetype)shared;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@
|
|||
-(instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.bucket = [[MattermostBucket alloc] init];
|
||||
[self getEntities:true];
|
||||
self.bucket = [[MattermostBucket alloc] init];
|
||||
self.keychain = [[MMKeychainManager alloc] init];
|
||||
[self getEntities:true];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
|
@ -150,10 +151,12 @@
|
|||
}
|
||||
|
||||
-(NSString *)getToken {
|
||||
NSDictionary *general = [self.entities objectForKey:@"general"];
|
||||
NSDictionary *credentials = [general objectForKey:@"credentials"];
|
||||
NSDictionary *options = @{
|
||||
@"accessGroup": APP_GROUP_ID
|
||||
};
|
||||
NSDictionary *credentials = [self.keychain getInternetCredentialsForServer:[self getServerUrl] withOptions:options];
|
||||
|
||||
return [credentials objectForKey:@"token"];
|
||||
return [credentials objectForKey:@"password"];
|
||||
}
|
||||
|
||||
-(UInt64)scanValueFromConfig:(NSDictionary *)config key:(NSString *)key {
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
#import "MMMConstants.h"
|
||||
#import "MattermostBucket.h"
|
||||
#import "StoreManager.h"
|
||||
#import "MMKeychainManager.h"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
219
package-lock.json
generated
219
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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'];
|
||||
'./node_modules/node_modules/url-parse/index.js'];
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
|||
import Video from 'react-native-video';
|
||||
import LocalAuth from 'react-native-local-auth';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import {getGenericPassword} from 'react-native-keychain';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
|
|
@ -30,8 +29,8 @@ import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_
|
|||
import Loading from 'app/components/loading';
|
||||
import PaperPlane from 'app/components/paper_plane';
|
||||
import {MAX_FILE_COUNT} from 'app/constants/post_textbox';
|
||||
import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
|
||||
import {getExtensionFromMime} from 'app/utils/file';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {setCSRFFromCookie} from 'app/utils/security';
|
||||
|
|
@ -49,7 +48,6 @@ import {
|
|||
import ChannelButton from './channel_button';
|
||||
import TeamButton from './team_button';
|
||||
|
||||
const {Initialization} = NativeModules;
|
||||
const defaultTheme = Preferences.THEMES.default;
|
||||
const extensionSvg = {
|
||||
csv: ExcelSvg,
|
||||
|
|
@ -232,32 +230,18 @@ export default class ExtensionPost extends PureComponent {
|
|||
|
||||
getAppCredentials = async () => {
|
||||
try {
|
||||
const credentials = await avoidNativeBridge(
|
||||
() => {
|
||||
return Initialization.credentialsExist;
|
||||
},
|
||||
() => {
|
||||
return Initialization.credentials;
|
||||
},
|
||||
() => {
|
||||
return getGenericPassword();
|
||||
}
|
||||
);
|
||||
const url = await getCurrentServerUrl();
|
||||
const credentials = await getAppCredentials();
|
||||
|
||||
if (credentials) {
|
||||
const passwordParsed = credentials.password.split(',');
|
||||
const token = credentials.password;
|
||||
|
||||
// password == token, url
|
||||
if (passwordParsed.length === 2) {
|
||||
const [token, url] = passwordParsed;
|
||||
|
||||
if (url && url !== 'undefined' && token && token !== 'undefined') {
|
||||
this.token = token;
|
||||
this.url = url;
|
||||
Client4.setUrl(url);
|
||||
Client4.setToken(token);
|
||||
await setCSRFFromCookie(url);
|
||||
}
|
||||
if (url && url !== 'undefined' && token && token !== 'undefined') {
|
||||
this.token = token;
|
||||
this.url = url;
|
||||
Client4.setUrl(url);
|
||||
Client4.setToken(token);
|
||||
await setCSRFFromCookie(url);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue