From 5680988590c08b17159aeebb324347eb436bc9e7 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 27 Jun 2018 09:23:47 -0400 Subject: [PATCH] Fix iOS Share extension crash (#1832) --- assets/base/i18n/en.json | 1 + ios/MattermostShare/SessionManager.h | 2 + ios/MattermostShare/SessionManager.m | 8 +++ ios/MattermostShare/ShareViewController.m | 21 +++++-- share_extension/ios/extension_post.js | 74 +++++++++++++++++------ 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index f8642183f..69bd897da 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -2513,6 +2513,7 @@ "mobile.share_extension.error_close": "Close", "mobile.share_extension.error_message": "An error has occurred while using the share extension.", "mobile.share_extension.error_title": "Extension Error", + "mobile.share_extension.post_error": "An error has occurred while posting the message. Please try again.", "mobile.share_extension.send": "Send", "mobile.share_extension.team": "Team", "mobile.suggestion.members": "Members", diff --git a/ios/MattermostShare/SessionManager.h b/ios/MattermostShare/SessionManager.h index 53823aed1..d8959c6d7 100644 --- a/ios/MattermostShare/SessionManager.h +++ b/ios/MattermostShare/SessionManager.h @@ -3,6 +3,8 @@ @interface SessionManager : NSObject @property (nonatomic, copy) void (^savedCompletionHandler)(void); +@property (nonatomic, copy) void (^sendShareEvent)(NSString *); +@property (nonatomic, copy) void (^closeExtension)(void); @property MattermostBucket *bucket; diff --git a/ios/MattermostShare/SessionManager.m b/ios/MattermostShare/SessionManager.m index ae0e82387..1109b63e1 100644 --- a/ios/MattermostShare/SessionManager.m +++ b/ios/MattermostShare/SessionManager.m @@ -37,6 +37,7 @@ NSLog(@"ERROR %@", [error userInfo]); NSLog(@"invalidating session %@", requestWithGroup); [session invalidateAndCancel]; + self.sendShareEvent(@"extensionPostFailed"); } else if (requestWithGroup != nil) { NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; NSURL *requestUrl = [[task originalRequest] URL]; @@ -80,6 +81,8 @@ } [data setObject:fileIds forKey:@"file_ids"]; [self setDataForRequest:data forRequestWithGroup:requestWithGroup]; + } else { + self.sendShareEvent(@"extensionPostFailed"); } } } @@ -124,6 +127,7 @@ NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; if (credentials == nil) { + self.sendShareEvent(@"extensionPostFailed"); return; } @@ -164,6 +168,7 @@ NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup]; if (credentials == nil) { + self.sendShareEvent(@"extensionPostFailed"); return; } @@ -196,6 +201,9 @@ NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request]; NSLog(@"Executing post request"); [createTask resume]; + self.closeExtension(); + } else { + self.sendShareEvent(@"extensionPostFailed"); } } diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m index ff9797715..a1d36d056 100644 --- a/ios/MattermostShare/ShareViewController.m +++ b/ios/MattermostShare/ShareViewController.m @@ -12,6 +12,8 @@ NSExtensionContext* extensionContext; return YES; } +@synthesize bridge = _bridge; + - (UIView*) shareView { NSURL *jsCodeLocation; @@ -56,12 +58,23 @@ RCT_EXPORT_METHOD(close:(NSDictionary *)data appGroupId:(NSString *)appGroupId) if (isBackgroundUpload) { NSString *requestWithGroup = [NSString stringWithFormat:@"%@|%@", requestId, appGroupId]; + + [SessionManager sharedSession].closeExtension = ^{ + [extensionContext completeRequestReturningItems:nil + completionHandler:nil]; + NSLog(@"Extension closed"); + }; + + [SessionManager sharedSession].sendShareEvent = ^(NSString* eventName) { + NSLog(@"Send Share Extension Event to JS"); + [_bridge enqueueJSCall:@"RCTDeviceEventEmitter" + method:@"emit" + args:@[eventName] + completion:nil]; + }; + [[SessionManager sharedSession] setDataForRequest:data forRequestWithGroup:requestWithGroup]; [[SessionManager sharedSession] createPostForRequest:requestWithGroup]; - - [extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"Extension closed"); } else { NSDictionary *post = [data objectForKey:@"post"]; NSArray *files = [data objectForKey:@"files"]; diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js index 7458167c8..7437c7771 100644 --- a/share_extension/ios/extension_post.js +++ b/share_extension/ios/extension_post.js @@ -1,11 +1,12 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. +// 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 {intlShape} from 'react-intl'; import { ActivityIndicator, + DeviceEventEmitter, Dimensions, Image, NativeModules, @@ -93,6 +94,7 @@ export default class ExtensionPost extends PureComponent { } componentWillMount() { + this.event = DeviceEventEmitter.addListener('extensionPostFailed', this.handlePostFailed); this.loadData(); } @@ -100,6 +102,10 @@ export default class ExtensionPost extends PureComponent { this.focusInput(); } + componentWillUnmount() { + this.event.remove(); + } + componentDidUpdate() { this.focusInput(); } @@ -141,6 +147,10 @@ export default class ExtensionPost extends PureComponent { const {navigator, theme} = this.props; const {channel, entities, team} = this.state; + if (!entities || !team || !channel) { + return; + } + navigator.push({ component: ExtensionChannels, wrapperStyle: { @@ -161,7 +171,11 @@ export default class ExtensionPost extends PureComponent { goToTeams = preventDoubleTap(() => { const {navigator, theme} = this.props; const {formatMessage} = this.context.intl; - const {team} = this.state; + const {entities, team} = this.state; + + if (!entities || !team) { + return; + } navigator.push({ component: ExtensionTeams, @@ -171,7 +185,7 @@ export default class ExtensionPost extends PureComponent { backgroundColor: theme.centerChannelBg, }, passProps: { - entities: this.state.entities, + entities, currentTeamId: team.id, onSelectTeam: this.selectTeam, theme, @@ -187,6 +201,19 @@ export default class ExtensionPost extends PureComponent { this.setState({value}); }; + handlePostFailed = () => { + const {formatMessage} = this.context.intl; + this.setState({ + error: { + message: formatMessage({ + id: 'mobile.share_extension.post_error', + defaultMessage: 'An error has occurred while posting the message. Please try again.', + }), + }, + sending: false, + }); + }; + loadData = async () => { const {entities} = this.state; @@ -287,6 +314,16 @@ export default class ExtensionPost extends PureComponent { const serverMaxFileSize = getAllowedServerMaxFileSize(config); const maxSize = Math.min(MAX_FILE_SIZE, serverMaxFileSize); + if (error) { + return ( + + + {error.message} + + + ); + } + if (sending) { return ( @@ -336,16 +373,6 @@ export default class ExtensionPost extends PureComponent { ); } - if (error) { - return ( - - - {error.message} - - - ); - } - return ( @@ -552,7 +579,7 @@ export default class ExtensionPost extends PureComponent { this.setState({channel: townSquare}); } } else { - this.setState({error}); + this.setState({error, channel: null}); } }); }; @@ -627,15 +654,22 @@ export default class ExtensionPost extends PureComponent { render() { const {authenticated, theme} = this.props; - const {channel, totalSize, sending} = this.state; + const {channel, error, totalSize, sending} = this.state; const {formatMessage} = this.context.intl; const styles = getStyleSheet(theme); let postButtonText = formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'}); - if (totalSize >= MAX_FILE_SIZE || sending || !channel) { + if (totalSize >= MAX_FILE_SIZE || sending || error || !channel) { postButtonText = null; } + let cancelButton = formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'}); + if (sending) { + cancelButton = null; + } else if (error) { + cancelButton = formatMessage({id: 'mobile.share_extension.error_close', defaultMessage: 'Close'}); + } + return ( {this.renderBody(styles)} - {this.renderTeamButton(styles)} - {this.renderChannelButton(styles)} + {!error && this.renderTeamButton(styles)} + {!error && this.renderChannelButton(styles)} ); }