diff --git a/app/push_notifications/push_notifications.android.js b/app/push_notifications/push_notifications.android.js index a937c8f84..4502ab50f 100644 --- a/app/push_notifications/push_notifications.android.js +++ b/app/push_notifications/push_notifications.android.js @@ -12,7 +12,6 @@ class PushNotification { constructor() { this.onRegister = null; this.onNotification = null; - this.onReply = null; this.deviceNotification = null; this.deviceToken = null; @@ -55,7 +54,6 @@ class PushNotification { configure(options) { this.onRegister = options.onRegister; this.onNotification = options.onNotification; - this.onReply = options.onReply; if (this.onRegister && this.deviceToken) { this.onRegister({token: this.deviceToken}); diff --git a/app/push_notifications/push_notifications.ios.js b/app/push_notifications/push_notifications.ios.js index 8868fd94d..bd4ebffe8 100644 --- a/app/push_notifications/push_notifications.ios.js +++ b/app/push_notifications/push_notifications.ios.js @@ -19,14 +19,11 @@ import {t} from 'app/utils/i18n'; const CATEGORY = 'CAN_REPLY'; const REPLY_ACTION = 'REPLY_ACTION'; -const replies = new Set(); - class PushNotification { constructor() { this.deviceNotification = null; this.onRegister = null; this.onNotification = null; - this.onReply = null; this.reduxStore = null; NotificationsIOS.addEventListener(DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT, this.onRemoteNotificationsRegistered); @@ -48,22 +45,10 @@ class PushNotification { } }; - handleReply = (notification, text, completion) => { - const data = notification.getData(); - - if (this.onReply && !replies.has(data.identifier)) { - replies.add(data.identifier); - this.onReply(data, text, completion); - } else { - completion(); - } - }; - configure(options) { this.reduxStore = options.reduxStore; this.onRegister = options.onRegister; this.onNotification = options.onNotification; - this.onReply = options.onReply; this.requestNotificationReplyPermissions(); @@ -175,18 +160,14 @@ class PushNotification { this.handleNotification(info, true, false); }; - onNotificationOpened = (notification, completion, action) => { - if (action.identifier === REPLY_ACTION) { - this.handleReply(notification, action.text, completion); - } else { - const data = notification.getData(); - const info = { - ...data, - message: data.body || notification.getMessage(), - }; - this.handleNotification(info, false, true); - completion(); - } + onNotificationOpened = (notification, completion) => { + const data = notification.getData(); + const info = { + ...data, + message: data.body || notification.getMessage(), + }; + this.handleNotification(info, false, true); + completion(); }; onRemoteNotificationsRegistered = (deviceToken) => { diff --git a/app/utils/push_notifications.test.js b/app/utils/push_notifications.test.js deleted file mode 100644 index fa748c6a4..000000000 --- a/app/utils/push_notifications.test.js +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -import {PostTypes} from 'mattermost-redux/action_types'; - -import PushNotificationUtils from './push_notifications'; - -jest.mock('app/init/credentials', () => ({ - getAppCredentials: jest.fn().mockReturnValue({ - username: 'device-token,current-user-id', - password: 'token,url', - }), - getCurrentServerUrl: jest.fn().mockReturnValue('url'), -})); - -jest.mock('app/actions/views/root', () => ({ - createPostForNotificationReply: jest.fn().mockImplementation(() => { - return () => ({error: 'error'}); - }), -})); - -jest.mock('app/selectors/i18n', () => ({ - getCurrentLocale: jest.fn().mockReturnValue('en'), -})); - -jest.mock('react-native-notifications', () => { - return { - requestPermissions: jest.fn(), - addEventListener: jest.fn(), - NotificationAction: jest.fn(), - NotificationCategory: jest.fn(), - localNotification: jest.fn(), - getInitialNotification: jest.fn(() => Promise.resolve()), - }; -}); - -const mockStore = configureMockStore([thunk]); -const store = mockStore({}); -PushNotificationUtils.configure(store); - -describe('PushNotifications', () => { - it('should add channel_id to failed push notification reply', async () => { - let notification = PushNotificationUtils.getNotification(); - expect(notification).toBe(null); - - const channelID = 'channel-id'; - const data = {channel_id: channelID}; - const text = 'text'; - const completion = () => {}; - await PushNotificationUtils.onPushNotificationReply(data, text, completion); - - const storeActions = store.getActions(); - const receivedPost = storeActions.some((action) => action.type === PostTypes.RECEIVED_POST); - expect(receivedPost).toBe(false); - - notification = PushNotificationUtils.getNotification(); - expect(notification.userInfo.channel_id).toBe(channelID); - }); -}); \ No newline at end of file diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index ce65e8b01..ef54a4776 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -5,6 +5,7 @@ }; objectVersion = 46; objects = { + /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* MattermostTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MattermostTests.m */; }; 0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */; }; @@ -22,6 +23,7 @@ 499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 499F7A642354F51900E7AF6E /* OnPasteEventManager.m */; }; 499F7AF1235511FD00E7AF6E /* Mattermost-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 499F7AF0235511FC00E7AF6E /* Mattermost-Regular.otf */; }; 49B4C050230C981C006E919E /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; }; + 536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */; }; 552835DCC0C24FC691EE6CAB /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */; }; 55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */; }; 62A8448264674B4D95A5A7C2 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */; }; @@ -184,6 +186,8 @@ 499F7B412355141200E7AF6E /* libYoutubePlayer-in-WKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libYoutubePlayer-in-WKWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 49B4BF51230C83D2006E919E /* libz.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.dylib; path = ../../../../../../../../../usr/lib/libz.1.dylib; sourceTree = ""; }; 49D0879035464473A11A0E1C /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = ""; }; + 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RNNotificationEventHandler+HandleReplyAction.m"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.m"; sourceTree = ""; }; + 536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNNotificationEventHandler+HandleReplyAction.h"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.h"; sourceTree = ""; }; 563B800AC53A447FA18F47D3 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; }; 57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.debug.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig"; sourceTree = ""; }; 5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -265,7 +269,6 @@ D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Bold.ttf"; path = "../assets/fonts/OpenSans-Bold.ttf"; sourceTree = ""; }; EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.debug.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig"; sourceTree = ""; }; FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = ""; }; - 81061F4CBB31484A94D5A8EE /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -370,6 +373,8 @@ 13B07FAE1A68108700A75B9A /* Mattermost */ = { isa = PBXGroup; children = ( + 536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */, + 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */, 499F7A632354F51900E7AF6E /* OnPasteEventManager.h */, 499F7A642354F51900E7AF6E /* OnPasteEventManager.m */, 499F7A612354F4CC00E7AF6E /* NSData+MimeType.h */, @@ -816,56 +821,56 @@ showEnvVarsInLog = 0; }; 1D22DC6FC0A37F7F657B8274 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - ); - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n"; - showEnvVarsInLog = 0; + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", + "${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n"; + showEnvVarsInLog = 0; }; AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; @@ -980,6 +985,7 @@ 499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */, 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */, 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */, + 536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.h b/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.h new file mode 100644 index 000000000..d66bd5e5f --- /dev/null +++ b/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.h @@ -0,0 +1,17 @@ + +// +// RNNotificationEventHandler+HandleReplyAction.h +// Mattermost +// +// Created by Miguel Alatzar on 1/29/20. +// Copyright © 2020 Mattermost. All rights reserved. +// + +#import +#import + +@interface RNNotificationEventHandler (HandleReplyAction) +@property (nonatomic, strong) RNNotificationCenter *notificationCenter; +@end + +extern NSString *const ReplyActionID; diff --git a/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.m b/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.m new file mode 100644 index 000000000..56f9a4284 --- /dev/null +++ b/ios/Mattermost/RNNotificationEventHandler+HandleReplyAction.m @@ -0,0 +1,136 @@ +// +// RNNotificationEventHandler+HandleReplyAction.m +// Mattermost +// +// Created by Miguel Alatzar on 1/29/20. +// Copyright © 2020 Mattermost. All rights reserved. +// + +#import "AppDelegate.h" +#import "RNNotificationEventHandler+HandleReplyAction.h" +#import +#import +#import + +#define notificationCenterKey @"notificationCenter" + +NSString *const ReplyActionID = @"REPLY_ACTION"; + +@implementation RNNotificationEventHandler (HandleReplyAction) + +- (RNNotificationCenter *)notificationCenter{ + return objc_getAssociatedObject(self, notificationCenterKey); +} + +- (void)setNotificationCenter:(RNNotificationCenter *)notificationCenter{ + objc_setAssociatedObject(self, notificationCenterKey, notificationCenter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + ++ (void)load { + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + Class class = [self class]; + + SEL originalSelector = @selector(didReceiveNotificationResponse:completionHandler:); + SEL swizzledSelector = @selector(handleReplyAction_didReceiveNotificationResponse:completionHandler:); + + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); + }); +} + +- (void)sendReply:(UNNotificationResponse *)response { + StoreManager *store = [StoreManager shared]; + [store getEntities:true]; + NSString *serverUrl = [store getServerUrl]; + NSString *sessionToken = [store getToken]; + if (serverUrl == nil || sessionToken == nil) { + [self handleReplyFailure:@""]; + return; + } + + NSString *completionKey = response.notification.request.identifier; + NSDictionary *parsedResponse = [RNNotificationParser parseNotificationResponse:response]; + NSString *message = [parsedResponse valueForKeyPath:@"action.text"]; + NSString *channelId = [parsedResponse valueForKeyPath:@"payload.channel_id"]; + NSString *rootId = [parsedResponse valueForKeyPath:@"payload.root_id"]; + if (rootId == nil) { + rootId = [parsedResponse valueForKeyPath:@"payload.post_id"]; + } + + NSDictionary *post = @{ + @"message": message, + @"channel_id": channelId, + @"root_id": rootId + }; + NSError *error; + NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:0 error:&error]; + if (!postData) { + [self handleReplyFailure:channelId]; + return; + } + + NSString *urlString = [NSString stringWithFormat:@"%@/api/v4/posts", serverUrl]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + [request setHTTPMethod:@"POST"]; + [request setValue:[NSString stringWithFormat:@"Bearer %@", sessionToken] forHTTPHeaderField:@"Authorization"]; + [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:postData]; + + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; + NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; + if (statusCode == 201) { + [self handleReplySuccess:completionKey]; + } else { + [self handleReplyFailure:channelId]; + } + }]; + + [task resume]; +} + +- (void) handleReplySuccess:(NSString *)completionKey { + [[RNNotificationsStore sharedInstance] completeAction:completionKey]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getDeliveredNotificationsWithCompletionHandler:^(NSArray * _Nonnull notifications) { + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:[notifications count]]; + }]; +} + +- (void) handleReplyFailure:(NSString *)channelId { + RNNotificationCenter *notificationCenter = [self notificationCenter]; + if (!notificationCenter) { + notificationCenter = [RNNotificationCenter new]; + [self setNotificationCenter:notificationCenter]; + } + + NSString *id = [[NSUUID UUID] UUIDString];; + NSDictionary *notification = @{ + @"body": @"Message failed to send.", + @"alertAction": @"", + @"userInfo": @{ + @"localNotification": @YES, + @"localTest": @YES, + @"channel_id": channelId + } + }; + [notificationCenter sendLocalNotification:notification withId:id]; +} + +#pragma mark - Method Swizzling + +- (void)handleReplyAction_didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(void))completionHandler { + if ([response.actionIdentifier isEqualToString:ReplyActionID]) { + [self sendReply:response]; + completionHandler(); + } else { + [self handleReplyAction_didReceiveNotificationResponse:response completionHandler:completionHandler]; + } +} + +@end