diff --git a/.eslintrc.json b/.eslintrc.json
index 818e88f2c..bd2345b99 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -32,7 +32,8 @@
"expect": true,
"it": true,
"jest": true,
- "test": true
+ "test": true,
+ "__DEV__": true
},
"rules": {
"array-bracket-spacing": [2, "never"],
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 802d77cb4..7578c5b85 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -113,7 +113,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 23
- versionCode 128
+ versionCode 129
versionName "1.11.0"
ndk {
abiFilters "armeabi-v7a", "x86"
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
index b1da95ad6..7ef74b51b 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
@@ -66,7 +66,7 @@ public class MainApplication extends NavigationApplication implements INotificat
new JailMonkeyPackage(),
new RNFetchBlobPackage(),
new MattermostPackage(this),
- new RNSentryPackage(this),
+ new RNSentryPackage(),
new ReactNativeExceptionHandlerPackage(),
new ReactNativeYouTube(),
new ReactVideoPackage(),
diff --git a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java
index b66ffd831..9515277df 100644
--- a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java
+++ b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java
@@ -6,6 +6,7 @@ import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
+import android.provider.OpenableColumns;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.os.Environment;
@@ -95,15 +96,33 @@ public class RealPathUtil {
public static String getPathFromSavingTempFile(Context context, final Uri uri) {
File tmpFile;
+ String fileName = null;
+
+ // Try and get the filename from the Uri
try {
- String fileName = uri.getLastPathSegment();
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = returnCursor.getString(nameIndex);
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (fileName == null) {
+ fileName = uri.getLastPathSegment().toString().trim();
+ }
+
+
File cacheDir = new File(context.getCacheDir(), "mmShare");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
String mimeType = getMimeType(uri.getPath());
- tmpFile = File.createTempFile("tmp", fileName, cacheDir);
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index be0f4b3cb..8365a9122 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -259,8 +259,11 @@ export function selectInitialChannel(teamId) {
const isGMVisible = lastChannel && lastChannel.type === General.GM_CHANNEL &&
isGroupChannelVisible(myPreferences, lastChannel);
- if (lastChannelId && myMembers[lastChannelId] &&
- (lastChannel.team_id === teamId || isDMVisible || isGMVisible)) {
+ if (
+ myMembers[lastChannelId] &&
+ lastChannel &&
+ (lastChannel.team_id === teamId || isDMVisible || isGMVisible)
+ ) {
handleSelectChannel(lastChannelId)(dispatch, getState);
markChannelAsRead(lastChannelId)(dispatch, getState);
return;
diff --git a/app/actions/views/login.js b/app/actions/views/login.js
index 4d6c06132..1386617b8 100644
--- a/app/actions/views/login.js
+++ b/app/actions/views/login.js
@@ -6,7 +6,7 @@ import {GeneralTypes} from 'mattermost-redux/action_types';
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
import {Client4} from 'mattermost-redux/client';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
-import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
+import {getCurrentUserId, getSessions} from 'mattermost-redux/selectors/entities/users';
import {ViewTypes} from 'app/constants';
import {app} from 'app/mattermost';
@@ -73,15 +73,25 @@ export function getSession() {
const {credentials} = state.entities.general;
const token = credentials && credentials.token;
- if (currentUserId && token) {
- const session = await Client4.getSessions(currentUserId, token);
- if (Array.isArray(session) && session[0]) {
- const s = session[0];
- return s.expires_at;
- }
+ if (!currentUserId || !token) {
+ return 0;
}
- return false;
+ let sessions;
+ try {
+ sessions = await getSessions(currentUserId);
+ } catch (e) {
+ console.warn('Failed to get current session', e); // eslint-disable-line no-console
+ return 0;
+ }
+
+ if (!Array.isArray(sessions)) {
+ return 0;
+ }
+
+ const session = sessions.find((s) => s.token === token);
+
+ return session && session.expires_at ? session.expires_at : 0;
};
}
diff --git a/app/actions/views/root.js b/app/actions/views/root.js
index aea994297..8fe81acd4 100644
--- a/app/actions/views/root.js
+++ b/app/actions/views/root.js
@@ -55,10 +55,15 @@ export function loadFromPushNotification(notification) {
const {data} = notification;
const {currentTeamId, teams, myMembers: myTeamMembers} = state.entities.teams;
const {currentChannelId, channels} = state.entities.channels;
- const channelId = data ? data.channel_id : '';
- // when the notification does not have a team id is because its from a DM or GM
- const teamId = data.team_id || currentTeamId;
+ let channelId = '';
+ let teamId = currentTeamId;
+ if (data) {
+ channelId = data.channel_id;
+
+ // when the notification does not have a team id is because its from a DM or GM
+ teamId = data.team_id || currentTeamId;
+ }
// load any missing data
const loading = [];
@@ -97,7 +102,9 @@ export function purgeOfflineStore() {
return {type: General.OFFLINE_STORE_PURGE};
}
-export function createPost(post) {
+// A non-optimistic version of the createPost action in mattermost-redux with the file handling
+// removed since it's not needed.
+export function createPostForNotificationReply(post) {
return (dispatch, getState) => {
const state = getState();
const currentUserId = state.entities.users.currentUserId;
diff --git a/app/app.js b/app/app.js
index 1096ec779..91818f1d9 100644
--- a/app/app.js
+++ b/app/app.js
@@ -161,6 +161,7 @@ export default class App {
if (!currentUserId) {
return;
}
+
const username = `${deviceToken}, ${currentUserId}`;
const password = `${token},${url}`;
@@ -172,7 +173,11 @@ export default class App {
// Only save to keychain if the url and token are set
if (url && token) {
- setGenericPassword(username, password);
+ try {
+ setGenericPassword(username, password);
+ } catch (e) {
+ console.warn('could not set credentials', e); //eslint-disable-line no-console
+ }
}
};
@@ -254,7 +259,13 @@ export default class App {
if (this.token && this.url) {
screen = 'Channel';
tracker.initialLoad = Date.now();
- dispatch(loadMe());
+
+ 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) {
diff --git a/app/components/combined_system_message/combined_system_message.js b/app/components/combined_system_message/combined_system_message.js
index 52ee86612..f3841615e 100644
--- a/app/components/combined_system_message/combined_system_message.js
+++ b/app/components/combined_system_message/combined_system_message.js
@@ -304,14 +304,24 @@ export default class CombinedSystemMessage extends React.PureComponent {
);
}
+ renderMessage(postType, userIds, actorId, style) {
+ return (
+
+ {this.renderFormattedMessage(postType, userIds, actorId, {baseText: style.baseText, linkText: style.linkText})}
+
+ );
+ }
+
render() {
const {
+ currentUserId,
messageData,
theme,
} = this.props;
const style = getStyleSheet(theme);
const content = [];
+ const removedUserIds = [];
for (const message of messageData) {
const {
postType,
@@ -319,23 +329,29 @@ export default class CombinedSystemMessage extends React.PureComponent {
} = message;
let userIds = message.userIds;
- if (!this.props.showJoinLeave && actorId !== this.props.currentUserId) {
- const affectsCurrentUser = userIds.indexOf(this.props.currentUserId) !== -1;
+ if (!this.props.showJoinLeave && actorId !== currentUserId) {
+ const affectsCurrentUser = userIds.indexOf(currentUserId) !== -1;
if (affectsCurrentUser) {
// Only show the message that the current user was added, etc
- userIds = [this.props.currentUserId];
+ userIds = [currentUserId];
} else {
// Not something the current user did or was affected by
continue;
}
}
- content.push(
-
- {this.renderFormattedMessage(postType, userIds, actorId, {baseText: style.baseText, linkText: style.linkText})}
-
- );
+ if (postType === REMOVE_FROM_CHANNEL) {
+ removedUserIds.push(...userIds);
+ continue;
+ }
+
+ content.push(this.renderMessage(postType, userIds, actorId, style));
+ }
+
+ if (removedUserIds.length > 0) {
+ const uniqueRemovedUserIds = removedUserIds.filter((id, index, arr) => arr.indexOf(id) === index);
+ content.push(this.renderMessage(REMOVE_FROM_CHANNEL, uniqueRemovedUserIds, currentUserId, style));
}
return (
diff --git a/app/components/combined_system_message/index.js b/app/components/combined_system_message/index.js
index 7ccee01ea..70b14c446 100644
--- a/app/components/combined_system_message/index.js
+++ b/app/components/combined_system_message/index.js
@@ -15,7 +15,7 @@ function makeMapStateToProps() {
const getProfilesByIdsAndUsernames = makeGetProfilesByIdsAndUsernames();
return (state, ownProps) => {
- const currentUser = getCurrentUser(state);
+ const currentUser = getCurrentUser(state) || {};
const {allUserIds, allUsernames} = ownProps;
return {
currentUserId: currentUser.id,
diff --git a/app/components/loading_placeholder/__snapshots__/loading_placeholder.test.js.snap b/app/components/loading_placeholder/__snapshots__/loading_placeholder.test.js.snap
new file mode 100644
index 000000000..f387312c0
--- /dev/null
+++ b/app/components/loading_placeholder/__snapshots__/loading_placeholder.test.js.snap
@@ -0,0 +1,153 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LoadingPlaceholder should match snapshot 1`] = `
+ShallowWrapper {
+ "length": 1,
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": Array [
+ ,
+ ,
+ ],
+ "ellipsizeMode": "tail",
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Loading",
+ "id": "loading_screen.loading",
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "animationDelay": 300,
+ "minOpacity": 0.4,
+ "numberOfDots": 3,
+ "style": Object {
+ "fontSize": 28,
+ "letterSpacing": -3,
+ "lineHeight": 30,
+ "marginLeft": 5,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": Array [
+ ,
+ ,
+ ],
+ "ellipsizeMode": "tail",
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Loading",
+ "id": "loading_screen.loading",
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "animationDelay": 300,
+ "minOpacity": 0.4,
+ "numberOfDots": 3,
+ "style": Object {
+ "fontSize": 28,
+ "letterSpacing": -3,
+ "lineHeight": 30,
+ "marginLeft": 5,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ },
+ },
+ },
+}
+`;
diff --git a/app/components/loading_placeholder/index.js b/app/components/loading_placeholder/index.js
new file mode 100644
index 000000000..e2dc72fb9
--- /dev/null
+++ b/app/components/loading_placeholder/index.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import AnimatedEllipsis from 'react-native-animated-ellipsis';
+import FormattedText from 'app/components/formatted_text';
+
+import {Text} from 'react-native';
+
+const LoadingPlaceholder = () => (
+
+
+
+
+);
+
+export default LoadingPlaceholder;
diff --git a/app/components/loading_placeholder/loading_placeholder.test.js b/app/components/loading_placeholder/loading_placeholder.test.js
new file mode 100644
index 000000000..96293f111
--- /dev/null
+++ b/app/components/loading_placeholder/loading_placeholder.test.js
@@ -0,0 +1,19 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {configure, shallow} from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+configure({adapter: new Adapter()});
+
+import LoadingPlaceholder from './index.js';
+
+describe('LoadingPlaceholder', () => {
+ test('should match snapshot', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/app/components/reactions/index.js b/app/components/reactions/index.js
index 48f0896cd..d29458654 100644
--- a/app/components/reactions/index.js
+++ b/app/components/reactions/index.js
@@ -22,7 +22,7 @@ function makeMapStateToProps() {
return function mapStateToProps(state, ownProps) {
const post = getPost(state, ownProps.postId);
const channelId = post ? post.channel_id : '';
- const channel = getChannel(state, channelId);
+ const channel = getChannel(state, channelId) || {};
const teamId = channel.team_id;
const channelIsArchived = channel.delete_at !== 0;
diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
index 0fcd3bb59..cba55a39a 100644
--- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
+++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
@@ -121,7 +121,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
@@ -200,7 +202,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
"delayPressOut": 100,
@@ -271,7 +275,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
],
"style": Array [
@@ -334,7 +340,9 @@ ShallowWrapper {
},
]
}
- />,
+ >
+
+ ,
undefined,
],
"style": Array [
@@ -382,7 +390,7 @@ ShallowWrapper {
"props": Object {
"accessible": true,
"allowFontScaling": true,
- "children": undefined,
+ "children": ,
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
@@ -402,7 +410,15 @@ ShallowWrapper {
],
},
"ref": null,
- "rendered": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
"type": [Function],
},
undefined,
@@ -496,7 +512,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
@@ -575,7 +593,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
"delayPressOut": 100,
@@ -646,7 +666,9 @@ ShallowWrapper {
},
]
}
- />
+ >
+
+
,
],
"style": Array [
@@ -709,7 +731,9 @@ ShallowWrapper {
},
]
}
- />,
+ >
+
+ ,
undefined,
],
"style": Array [
@@ -757,7 +781,7 @@ ShallowWrapper {
"props": Object {
"accessible": true,
"allowFontScaling": true,
- "children": undefined,
+ "children": ,
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
@@ -777,7 +801,15 @@ ShallowWrapper {
],
},
"ref": null,
- "rendered": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
"type": [Function],
},
undefined,
@@ -818,3 +850,854 @@ ShallowWrapper {
},
}
`;
+
+exports[`ChannelItem should match snapshot for no displayName 1`] = `
+ShallowWrapper {
+ "length": 1,
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children":
+
+
+
+
+
+
+
+
+ ,
+ },
+ "ref": [Function],
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "activeOpacity": 0.85,
+ "children":
+
+
+
+
+
+
+ ,
+ "delayPressOut": 100,
+ "onLongPress": [Function],
+ "onPress": [Function],
+ "underlayColor": "rgba(170,170,170,0.5)",
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ undefined,
+
+
+
+
+
+ ,
+ ],
+ "style": Array [
+ Object {
+ "flex": 1,
+ "flexDirection": "row",
+ "height": 44,
+ },
+ undefined,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ undefined,
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+ ,
+ undefined,
+ ],
+ "style": Array [
+ Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ "paddingLeft": 16,
+ },
+ undefined,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "channelId": "channel_id",
+ "isActive": false,
+ "isArchived": false,
+ "isInfo": false,
+ "isUnread": true,
+ "membersCount": 1,
+ "size": 16,
+ "status": "online",
+ "teammateDeletedAt": 0,
+ "theme": Object {
+ "sidebarText": "#aaa",
+ "sidebarTextActiveBorder": "#aaa",
+ "sidebarTextActiveColor": "#aaa",
+ "sidebarTextHoverBg": "#aaa",
+ },
+ "type": "O",
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": ,
+ "ellipsizeMode": "tail",
+ "numberOfLines": 1,
+ "style": Array [
+ Object {
+ "color": "rgba(170,170,170,0.4)",
+ "flex": 1,
+ "fontSize": 14,
+ "fontWeight": "600",
+ "height": "100%",
+ "lineHeight": 44,
+ "paddingRight": 40,
+ "textAlignVertical": "center",
+ },
+ Object {
+ "color": undefined,
+ },
+ ],
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ undefined,
+ ],
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children":
+
+
+
+
+
+
+
+
+ ,
+ },
+ "ref": [Function],
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "activeOpacity": 0.85,
+ "children":
+
+
+
+
+
+
+ ,
+ "delayPressOut": 100,
+ "onLongPress": [Function],
+ "onPress": [Function],
+ "underlayColor": "rgba(170,170,170,0.5)",
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ undefined,
+
+
+
+
+
+ ,
+ ],
+ "style": Array [
+ Object {
+ "flex": 1,
+ "flexDirection": "row",
+ "height": 44,
+ },
+ undefined,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ undefined,
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+ ,
+ undefined,
+ ],
+ "style": Array [
+ Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ "paddingLeft": 16,
+ },
+ undefined,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "channelId": "channel_id",
+ "isActive": false,
+ "isArchived": false,
+ "isInfo": false,
+ "isUnread": true,
+ "membersCount": 1,
+ "size": 16,
+ "status": "online",
+ "teammateDeletedAt": 0,
+ "theme": Object {
+ "sidebarText": "#aaa",
+ "sidebarTextActiveBorder": "#aaa",
+ "sidebarTextActiveColor": "#aaa",
+ "sidebarTextHoverBg": "#aaa",
+ },
+ "type": "O",
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": ,
+ "ellipsizeMode": "tail",
+ "numberOfLines": 1,
+ "style": Array [
+ Object {
+ "color": "rgba(170,170,170,0.4)",
+ "flex": 1,
+ "fontSize": 14,
+ "fontWeight": "600",
+ "height": "100%",
+ "lineHeight": 44,
+ "paddingRight": 40,
+ "textAlignVertical": "center",
+ },
+ Object {
+ "color": undefined,
+ },
+ ],
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ undefined,
+ ],
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ },
+ },
+ "context": Object {
+ "intl": Object {
+ "formatMessage": [MockFunction] {
+ "calls": Array [
+ Array [
+ Object {
+ "defaultMessage": "{displayName} (you)",
+ "id": "channel_header.directchannel.you",
+ },
+ Object {
+ "displayname": "",
+ },
+ ],
+ ],
+ },
+ },
+ },
+ },
+}
+`;
diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
index c87678098..cce3eca36 100644
--- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js
+++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
@@ -14,6 +14,7 @@ import {intlShape} from 'react-intl';
import Badge from 'app/components/badge';
import ChannelIcon from 'app/components/channel_icon';
+import LoadingPlaceholder from 'app/components/loading_placeholder';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -113,14 +114,6 @@ export default class ChannelItem extends PureComponent {
const {intl} = this.context;
- let channelDisplayName = displayName;
- if (isMyUser) {
- channelDisplayName = intl.formatMessage({
- id: 'channel_header.directchannel.you',
- defaultMessage: '{displayName} (you)',
- }, {displayname: displayName});
- }
-
const style = getStyleSheet(theme);
const isActive = channelId === currentChannelId;
@@ -140,6 +133,20 @@ export default class ChannelItem extends PureComponent {
extraTextStyle = style.textUnread;
}
+ let channelDisplayName = displayName;
+ if (isMyUser) {
+ channelDisplayName = intl.formatMessage({
+ id: 'channel_header.directchannel.you',
+ defaultMessage: '{displayName} (you)',
+ }, {displayname: displayName});
+ }
+
+ if (!channelDisplayName) {
+ channelDisplayName = (
+
+ );
+ }
+
let badge;
if (mentions) {
badge = (
diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js
index 3ce307c00..7f4bd614f 100644
--- a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js
+++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js
@@ -45,4 +45,18 @@ describe('ChannelItem', () => {
expect(wrapper).toMatchSnapshot();
});
+
+ test('should match snapshot for no displayName', () => {
+ const props = {
+ ...baseProps,
+ displayName: '',
+ };
+
+ const wrapper = shallow(
+ ,
+ {context: {intl: {formatMessage: jest.fn()}}},
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
});
diff --git a/app/components/sidebars/main/channels_list/channel_item/index.js b/app/components/sidebars/main/channels_list/channel_item/index.js
index 2e0e1435d..6fbd29625 100644
--- a/app/components/sidebars/main/channels_list/channel_item/index.js
+++ b/app/components/sidebars/main/channels_list/channel_item/index.js
@@ -10,9 +10,10 @@ import {
getMyChannelMember,
shouldHideDefaultChannel,
} from 'mattermost-redux/selectors/entities/channels';
-import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getTheme, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
+import {displayUsername} from 'mattermost-redux/utils/user_utils';
import ChannelItem from './channel_item';
@@ -26,12 +27,15 @@ function makeMapStateToProps() {
let isMyUser = false;
let teammateDeletedAt = 0;
+ let displayName = channel.display_name;
if (channel.type === General.DM_CHANNEL && channel.teammate_id) {
isMyUser = channel.teammate_id === currentUserId;
const teammate = getUser(state, channel.teammate_id);
if (teammate && teammate.delete_at) {
teammateDeletedAt = teammate.delete_at;
}
+ const teammateNameDisplay = getTeammateNameDisplaySetting(state);
+ displayName = displayUsername(teammate, teammateNameDisplay, false);
}
const currentChannelId = getCurrentChannelId(state);
@@ -57,10 +61,9 @@ function makeMapStateToProps() {
if (member && member.notify_props) {
showUnreadForMsgs = member.notify_props.mark_unread !== General.MENTION;
}
-
return {
currentChannelId,
- displayName: channel.display_name,
+ displayName,
fake: channel.fake,
isChannelMuted: isChannelMuted(member),
isMyUser,
diff --git a/app/components/sidebars/main/channels_list/switch_teams_button/index.js b/app/components/sidebars/main/channels_list/switch_teams_button/index.js
index aff581356..825e93234 100644
--- a/app/components/sidebars/main/channels_list/switch_teams_button/index.js
+++ b/app/components/sidebars/main/channels_list/switch_teams_button/index.js
@@ -9,7 +9,7 @@ import {getCurrentTeam, getMyTeamsCount, getChannelDrawerBadgeCount} from 'matte
import SwitchTeamsButton from './switch_teams_button';
function mapStateToProps(state) {
- const team = getCurrentTeam(state);
+ const team = getCurrentTeam(state) || {};
return {
currentTeamId: team.id,
diff --git a/app/components/sidebars/settings/index.js b/app/components/sidebars/settings/index.js
index 4e4b8f263..0ce0f01f7 100644
--- a/app/components/sidebars/settings/index.js
+++ b/app/components/sidebars/settings/index.js
@@ -13,7 +13,7 @@ import {getDimensions} from 'app/selectors/device';
import SettingsSidebar from './settings_sidebar';
function mapStateToProps(state) {
- const currentUser = getCurrentUser(state);
+ const currentUser = getCurrentUser(state) || {};
const status = getStatusForUserId(state, currentUser.id);
return {
diff --git a/app/fetch_preconfig.js b/app/fetch_preconfig.js
index 9a8e02451..281726e1c 100644
--- a/app/fetch_preconfig.js
+++ b/app/fetch_preconfig.js
@@ -42,6 +42,7 @@ Client4.doFetchWithResponse = async (url, options) => {
}
throw {
+ message: 'Received invalid response from the server.',
intl: {
id: 'mobile.request.invalid_response',
defaultMessage: 'Received invalid response from the server.',
diff --git a/app/mattermost.js b/app/mattermost.js
index 704a3b939..e7bbc5f11 100644
--- a/app/mattermost.js
+++ b/app/mattermost.js
@@ -142,8 +142,13 @@ const handleLogout = () => {
const restartApp = async () => {
Navigation.dismissModal({animationType: 'none'});
- await store.dispatch(loadConfigAndLicense());
- await store.dispatch(loadMe());
+ try {
+ await store.dispatch(loadConfigAndLicense());
+ await store.dispatch(loadMe());
+ } catch (e) {
+ console.warn('Failed to load initial data while restarting', e); // eslint-disable-line no-console
+ }
+
launchChannel();
};
diff --git a/app/mattermost_managed/mattermost-managed.android.js b/app/mattermost_managed/mattermost-managed.android.js
index 2e3f78adf..7aa71819c 100644
--- a/app/mattermost_managed/mattermost-managed.android.js
+++ b/app/mattermost_managed/mattermost-managed.android.js
@@ -56,7 +56,7 @@ export default {
}
},
isTrustedDevice: () => {
- if (__DEV__) { //eslint-disable-line no-undef
+ if (__DEV__) {
return true;
}
diff --git a/app/mattermost_managed/mattermost-managed.ios.js b/app/mattermost_managed/mattermost-managed.ios.js
index 01c134993..1e0bb973b 100644
--- a/app/mattermost_managed/mattermost-managed.ios.js
+++ b/app/mattermost_managed/mattermost-managed.ios.js
@@ -55,7 +55,7 @@ export default {
}
},
isTrustedDevice: () => {
- if (__DEV__) { //eslint-disable-line no-undef
+ if (__DEV__) {
return true;
}
diff --git a/app/screens/settings/notification_settings/index.js b/app/screens/settings/notification_settings/index.js
index d41937991..71673b63d 100644
--- a/app/screens/settings/notification_settings/index.js
+++ b/app/screens/settings/notification_settings/index.js
@@ -15,7 +15,7 @@ import NotificationSettings from './notification_settings';
function mapStateToProps(state) {
const config = getConfig(state);
- const currentUser = getCurrentUser(state);
+ const currentUser = getCurrentUser(state) || {};
const currentUserStatus = getStatusForUserId(state, currentUser.id);
const serverVersion = state.entities.general.serverVersion;
const enableAutoResponder = isMinimumServerVersion(serverVersion, 4, 9) && config.ExperimentalEnableAutomaticReplies === 'true';
diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js
index 736958af6..2eb19f45d 100644
--- a/app/screens/sso/sso.js
+++ b/app/screens/sso/sso.js
@@ -187,12 +187,18 @@ class SSO extends PureComponent {
setStoreFromLocalData({url: this.props.serverUrl, token}).
then(handleSuccessfulLogin).
then(getSession).
- then(this.goToLoadTeam);
+ then(this.goToLoadTeam).
+ catch(this.onLoadEndError);
}
});
}
};
+ onLoadEndError = (e) => {
+ console.warn('Failed to set store from local data', e); // eslint-disable-line no-console
+ this.setState({error: e.message});
+ }
+
renderLoading = () => {
return ;
};
diff --git a/app/screens/timezone/index.js b/app/screens/timezone/index.js
index 19e7280b4..fb2901c89 100644
--- a/app/screens/timezone/index.js
+++ b/app/screens/timezone/index.js
@@ -16,7 +16,7 @@ import Timezone from './timezone';
function mapStateToProps(state) {
const timezones = getTimezones(state);
- const currentUser = getCurrentUser(state);
+ const currentUser = getCurrentUser(state) || {};
const userTimezone = getUserTimezone(state, currentUser.id);
return {
diff --git a/app/utils/error_handling.js b/app/utils/error_handling.js
index b0429c097..aa9d8d191 100644
--- a/app/utils/error_handling.js
+++ b/app/utils/error_handling.js
@@ -16,27 +16,35 @@ import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {purgeOfflineStore} from 'app/actions/views/root';
import {
captureException,
+ captureJSException,
initializeSentry,
- LOGGER_JAVASCRIPT,
LOGGER_NATIVE,
} from 'app/utils/sentry';
import {app, store} from 'app/mattermost';
const errorHandler = (e, isFatal) => {
- console.warn('Handling Javascript error ', e); // eslint-disable-line no-console
+ 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;
+ }
+
+ console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
+ captureJSException(e, isFatal, store);
+
const {dispatch} = store;
- captureException(e, LOGGER_JAVASCRIPT, store);
-
- const translations = app.getTranslations();
dispatch(closeWebSocket());
if (Client4.getUrl()) {
dispatch(logError(e));
}
- if (isFatal) {
+ if (isFatal && e instanceof Error) {
+ const translations = app.getTranslations();
+
Alert.alert(
translations['mobile.error_handler.title'],
translations['mobile.error_handler.description'],
diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js
index f4662a214..6249c7509 100644
--- a/app/utils/push_notifications.js
+++ b/app/utils/push_notifications.js
@@ -16,7 +16,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {ViewTypes} from 'app/constants';
import {retryGetPostsAction} from 'app/actions/views/channel';
import {
- createPost,
+ createPostForNotificationReply,
loadFromPushNotification,
} from 'app/actions/views/root';
@@ -136,15 +136,20 @@ export const onPushNotificationReply = (data, text, badge, completed) => {
}
retryGetPostsAction(getPosts(data.channel_id), dispatch, getState);
- dispatch(createPost(post)).then(() => {
- dispatch(markChannelAsRead(data.channel_id));
+ dispatch(createPostForNotificationReply(post)).
+ then(() => {
+ dispatch(markChannelAsRead(data.channel_id));
- if (badge >= 0) {
- PushNotifications.setApplicationIconBadgeNumber(badge);
- }
+ if (badge >= 0) {
+ PushNotifications.setApplicationIconBadgeNumber(badge);
+ }
- app.setReplyNotificationData(null);
- }).then(completed);
+ app.setReplyNotificationData(null);
+ }).
+ then(completed).
+ catch((e) => {
+ console.warn('Failed to send reply to push notification', e); // eslint-disable-line no-console
+ });
} else {
app.setReplyNotificationData({
data,
diff --git a/app/utils/sentry/index.js b/app/utils/sentry/index.js
index af184abfc..be665e8d8 100644
--- a/app/utils/sentry/index.js
+++ b/app/utils/sentry/index.js
@@ -17,6 +17,9 @@ export const LOGGER_JAVASCRIPT_WARNING = 'javascript_warning';
export const LOGGER_NATIVE = 'native';
export const LOGGER_REDUX = 'redux';
+export const BREADCRUMB_UNCAUGHT_APP_ERROR = 'uncaught-app-error';
+export const BREADCRUMB_UNCAUGHT_NON_ERROR = 'uncaught-non-error';
+
export function initializeSentry() {
if (!Config.SentryEnabled) {
// Still allow Sentry to configure itself in case other code tries to call it
@@ -44,12 +47,28 @@ function getDsn() {
return '';
}
-export function captureException(error, logger, store) {
- if (error && logger && store) {
- capture(() => {
- Sentry.captureException(error, {logger});
- }, store);
+export function captureJSException(error, isFatal, store) {
+ if (!error || !store) {
+ console.warn('captureJSException called with missing arguments', error, store); // eslint-disable-line no-console
+ return;
}
+
+ if (error instanceof Error) {
+ captureException(error, LOGGER_JAVASCRIPT, store);
+ } else {
+ captureNonErrorAsBreadcrumb(error, isFatal);
+ }
+}
+
+export function captureException(error, logger, store) {
+ if (!error || !logger || !store) {
+ console.warn('captureException called with missing arguments', error, logger, store); // eslint-disable-line no-console
+ return;
+ }
+
+ capture(() => {
+ Sentry.captureException(error, {logger});
+ }, store);
}
export function captureExceptionWithoutState(err, logger) {
@@ -74,6 +93,54 @@ export function captureMessage(message, logger, store) {
}
}
+export function captureNonErrorAsBreadcrumb(obj, isFatal) {
+ if (!obj || typeof obj !== 'object') {
+ console.warning('Invalid object passed to captureNonErrorAsBreadcrumb', obj); // eslint-disable-line no-console
+ return;
+ }
+
+ const isAppError = Boolean(obj.server_error_id);
+
+ const breadcrumb = {
+ category: isAppError ? BREADCRUMB_UNCAUGHT_APP_ERROR : BREADCRUMB_UNCAUGHT_NON_ERROR,
+ data: {
+ isFatal: String(isFatal),
+ },
+ level: 'warn',
+ };
+
+ if (obj.message) {
+ breadcrumb.message = obj.message;
+ } else if (obj.intl && obj.intl.defaultMessage) {
+ breadcrumb.message = obj.intl.defaultMessage;
+ } else {
+ breadcrumb.message = 'no message provided';
+ }
+
+ if (obj.server_error_id) {
+ breadcrumb.data.server_error_id = obj.server_error_id;
+ }
+
+ if (obj.status_code) {
+ breadcrumb.data.status_code = obj.status_code;
+ }
+
+ if (obj.url) {
+ const match = (/^(?:https?:\/\/)[^/]+(\/.*)$/);
+
+ if (match && match.length >= 2) {
+ breadcrumb.data.url = match[1];
+ }
+ }
+
+ try {
+ Sentry.captureBreadcrumb(breadcrumb);
+ } catch (e) {
+ // Do nothing since this is only here to make sure we don't crash when handling an exception
+ console.warn('Failed to capture breadcrumb of non-error', e); // eslint-disable-line no-console
+ }
+}
+
// Wrapper function to any calls to Sentry so that we can gather any necessary extra data
// before sending.
function capture(captureFunc, store) {
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index ddefa5897..99def8192 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -2432,6 +2432,7 @@
"mobile.error_handler.description": "\nClick relaunch to open the app again. After restart, you can report the problem from the settings menu.\n",
"mobile.error_handler.title": "Unexpected error occurred",
"mobile.extension.authentication_required": "Authentication required: Please first login using the app.",
+ "mobile.extension.file_error": "There was an error reading the file to be shared.\nPlease try again.",
"mobile.extension.file_limit": "Sharing is limited to a maximum of 5 files.",
"mobile.extension.max_file_size": "File attachments shared in Mattermost must be less than {size}.",
"mobile.extension.permission": "Mattermost needs access to the device storage to share files.",
diff --git a/index.js b/index.js
index acb85c8b6..5746e3135 100644
--- a/index.js
+++ b/index.js
@@ -16,7 +16,6 @@ if (Platform.OS === 'android') {
/*
/!* eslint-disable no-console *!/
-/!* eslint-disable no-undef *!/
if (__DEV__) {
const modules = require.getModules();
const moduleIds = Object.keys(modules);
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 6122e5a75..8a10b0ea6 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -2531,7 +2531,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 128;
+ CURRENT_PROJECT_VERSION = 129;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2581,7 +2581,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 128;
+ CURRENT_PROJECT_VERSION = 129;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index 2a3ee0f13..90131f7ea 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -34,7 +34,7 @@
CFBundleVersion
- 128
+ 129
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index 5295ece27..cd7d4e3d1 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -23,7 +23,7 @@
CFBundleShortVersionString
1.11.0
CFBundleVersion
- 128
+ 129
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index 86e32f0a0..8ba6c5668 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 128
+ 129
diff --git a/package-lock.json b/package-lock.json
index ef5f3fe17..523f08d27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2589,27 +2589,34 @@
}
},
"@sentry/cli": {
- "version": "1.30.4",
- "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.30.4.tgz",
- "integrity": "sha1-TS9PHbMZ8pUCYrjnW21okv4Xeco=",
+ "version": "1.34.0",
+ "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.34.0.tgz",
+ "integrity": "sha512-j2nYoci4yaA3dIL3Gheai2KKAtMdDGNipq7Ws9vO5SaG6uY0FQ0EXTxY0CAMPiNXdAXWstR5rdxPptyrhxPq7Q==",
"requires": {
- "https-proxy-agent": "^2.1.1",
- "node-fetch": "^1.7.3",
+ "https-proxy-agent": "^2.2.1",
+ "node-fetch": "^2.1.2",
"progress": "2.0.0",
"proxy-from-env": "^1.0.0"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz",
+ "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA=="
+ }
}
},
"@sentry/wizard": {
- "version": "0.9.6",
- "resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-0.9.6.tgz",
- "integrity": "sha1-84+xsIgZ5rKAMFMOPTZOe2TJRZg=",
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-0.10.3.tgz",
+ "integrity": "sha512-U+O8l6Kp9CTLJAgr/ChoRH16s/owGTF/dk/veTXgK9wMn+QKit6CEK/LAlah/nqgVx+CBvEVW+h4E8KIcSXMDg==",
"requires": {
"@sentry/cli": "^1.30.1",
"chalk": "^2.3.1",
"glob": "^7.1.2",
"inquirer": "^5.1.0",
"lodash": "^4.17.5",
- "open": "^0.0.5",
+ "opn": "^5.3.0",
"r2": "^2.0.0",
"read-env": "^1.1.1",
"xcode": "^1.0.0",
@@ -2669,6 +2676,14 @@
"through": "^2.3.6"
}
},
+ "opn": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
+ "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
+ "requires": {
+ "is-wsl": "^1.1.0"
+ }
+ },
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@@ -2701,9 +2716,9 @@
}
},
"yargs": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz",
- "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
+ "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
"requires": {
"cliui": "^4.0.0",
"decamelize": "^1.1.1",
@@ -2788,9 +2803,9 @@
}
},
"agent-base": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz",
- "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+ "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"requires": {
"es6-promisify": "^5.0.0"
}
@@ -8378,6 +8393,11 @@
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
},
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
+ },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -10108,8 +10128,8 @@
}
},
"mattermost-redux": {
- "version": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527",
- "from": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527",
+ "version": "github:mattermost/mattermost-redux#f13022064f1ed24a0d1c085f433490f9d40cfc2f",
+ "from": "github:mattermost/mattermost-redux#f13022064f1ed24a0d1c085f433490f9d40cfc2f",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "1.2.0",
@@ -13875,11 +13895,6 @@
"mimic-fn": "^1.0.0"
}
},
- "open": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz",
- "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw="
- },
"opn": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/opn/-/opn-3.0.3.tgz",
@@ -14325,9 +14340,9 @@
},
"dependencies": {
"node-fetch": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
- "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz",
+ "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA=="
}
}
},
@@ -14399,9 +14414,9 @@
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
},
"raven-js": {
- "version": "3.24.2",
- "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.24.2.tgz",
- "integrity": "sha512-Dy/FHDxuo5pXywVf8Nrs5utB2juMATpkxWGqHjVbpFD3m8CaWYLvkB5SEXjWFUZ5GvUsrBVVQ+Dfcp0x6Z7SOg=="
+ "version": "3.26.4",
+ "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.26.4.tgz",
+ "integrity": "sha512-5VmC3IWhTQJkaiQaCY0S5V8za4bpUgbbuVT1MkDH7JVqgu8CPQ750XaFF8BVRbLV9F5nvoz7n0UT0CKteDuZAg=="
},
"raw-body": {
"version": "2.3.2",
@@ -14626,6 +14641,13 @@
"prop-types": "^15.5.10"
}
},
+ "react-native-animated-ellipsis": {
+ "version": "github:sudheerDev/react-native-animated-ellipsis#326167b8e0fa3b6f0422c06be412bf177b725666",
+ "from": "github:sudheerDev/react-native-animated-ellipsis#326167b8e0fa3b6f0422c06be412bf177b725666",
+ "requires": {
+ "prop-types": "^15.5.10"
+ }
+ },
"react-native-bottom-sheet": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/react-native-bottom-sheet/-/react-native-bottom-sheet-1.0.3.tgz",
@@ -14836,11 +14858,11 @@
"integrity": "sha512-kR6s97ubi1QevRWRC8RQ26NoD6EgLt36caY25GKBt9eZ+l9Qmm2JHti2cEK153cxLIMbD04Xv4+LjDhcNjxh/w=="
},
"react-native-sentry": {
- "version": "0.36.0",
- "resolved": "https://registry.npmjs.org/react-native-sentry/-/react-native-sentry-0.36.0.tgz",
- "integrity": "sha1-HGbXKvVqqAHaqlVpwJ/H0EvqZUQ=",
+ "version": "0.38.3",
+ "resolved": "https://registry.npmjs.org/react-native-sentry/-/react-native-sentry-0.38.3.tgz",
+ "integrity": "sha512-+RsSsLdjSf7MmIOkjoMqIfGTOISG3ym2hcldMaxCfa9HRTVXL7dne4naWEqFbs32C9M6KgXe/RZlp5cDaELLsA==",
"requires": {
- "@sentry/wizard": "^0.9.5",
+ "@sentry/wizard": "^0.10.2",
"raven-js": "^3.24.2"
}
},
@@ -15013,11 +15035,18 @@
}
},
"read-env": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/read-env/-/read-env-1.1.1.tgz",
- "integrity": "sha512-yzGQb8P7N8zf513nRu+kMiQPJlmADWqS7Qn2pd6aAvRcrMCxuFvRX1KL8KU0ckdWVqoTX7Qpj10U+Dx7cVL/LA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/read-env/-/read-env-1.2.0.tgz",
+ "integrity": "sha512-29PX3GtJ9nUWsM8UckqJVdrqJzv8yWPDH3lxk2+CBFN3mJ1gJ42dluzxqmADCG7H0jL26G53G5+CwAqQph2Ctw==",
"requires": {
- "camelcase": "^4.1.0"
+ "camelcase": "5.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
+ "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="
+ }
}
},
"read-pkg": {
@@ -15766,9 +15795,9 @@
}
},
"rxjs": {
- "version": "5.5.10",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
- "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==",
+ "version": "5.5.11",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz",
+ "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==",
"requires": {
"symbol-observable": "1.0.1"
},
diff --git a/package.json b/package.json
index 414e85b9f..9c4195618 100644
--- a/package.json
+++ b/package.json
@@ -15,13 +15,14 @@
"intl": "1.2.5",
"jail-monkey": "1.0.0",
"jsc-android": "216113.0.3",
- "mattermost-redux": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527",
+ "mattermost-redux": "github:mattermost/mattermost-redux#f13022064f1ed24a0d1c085f433490f9d40cfc2f",
"mime-db": "1.33.0",
"prop-types": "15.6.1",
"react": "16.3.2",
"react-intl": "2.4.0",
"react-native": "github:enahum/react-native#mm",
"react-native-animatable": "1.2.4",
+ "react-native-animated-ellipsis": "sudheerDev/react-native-animated-ellipsis.git#326167b8e0fa3b6f0422c06be412bf177b725666",
"react-native-bottom-sheet": "1.0.3",
"react-native-button": "2.3.0",
"react-native-circular-progress": "0.2.0",
@@ -45,7 +46,7 @@
"react-native-permissions": "1.1.1",
"react-native-safe-area": "0.2.3",
"react-native-section-list-get-item-layout": "2.2.2",
- "react-native-sentry": "0.36.0",
+ "react-native-sentry": "0.38.3",
"react-native-slider": "0.11.0",
"react-native-status-bar-size": "0.3.3",
"react-native-svg": "6.3.1",
diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js
index 18ce120ec..4829e8bec 100644
--- a/share_extension/android/extension_post/extension_post.js
+++ b/share_extension/android/extension_post/extension_post.js
@@ -268,10 +268,7 @@ export default class ExtensionPost extends PureComponent {
const text = [];
const files = [];
let totalSize = 0;
-
- this.props.navigation.setParams({
- post: this.onPost,
- });
+ let error;
for (let i = 0; i < items.length; i++) {
const item = items[i];
@@ -280,8 +277,18 @@ export default class ExtensionPost extends PureComponent {
text.push(item.value);
break;
default: {
+ let fileSize = {size: 0};
const fullPath = item.value;
- const fileSize = await RNFetchBlob.fs.stat(fullPath);
+ try {
+ fileSize = await RNFetchBlob.fs.stat(fullPath);
+ } catch (e) {
+ const {formatMessage} = this.context.intl;
+ error = formatMessage({
+ id: 'mobile.extension.file_error',
+ defaultMessage: 'There was an error reading the file to be shared.\nPlease try again.',
+ });
+ break;
+ }
let filename = fullPath.replace(/^.*[\\/]/, '');
let extension = filename.split('.').pop();
if (extension === filename) {
@@ -305,7 +312,13 @@ export default class ExtensionPost extends PureComponent {
const value = text.join('\n');
- this.setState({files, value, hasPermission: true, totalSize});
+ if (!error) {
+ this.props.navigation.setParams({
+ post: this.onPost,
+ });
+ }
+
+ this.setState({error, files, value, hasPermission: true, totalSize});
}
};
@@ -476,7 +489,11 @@ export default class ExtensionPost extends PureComponent {
render() {
const {formatMessage} = this.context.intl;
const {maxFileSize, token, url} = this.props;
- const {hasPermission, files, totalSize} = this.state;
+ const {error, hasPermission, files, totalSize} = this.state;
+
+ if (error) {
+ return this.renderErrorMessage(error);
+ }
if (token && url) {
if (hasPermission === false) {