From ba76e5eac7043e67b9f03d1af5652ff730aa852b Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Mon, 18 Nov 2019 14:29:49 -0800 Subject: [PATCH] [MM-16232] ID loaded push notifications (#3562) * [MM-16232] Android: Fetch notification in notificationReceiptDelivery (#3552) * Fetch notification in notificationReceiptDelivery * Fix patch * Fix patch take 2 * No need to send user_id to ack endpoint * Just putString in mNotificationProps * Fix patch take 3 * Revert react-native-notifications patch * Update patch and fix rejections * Remove trailing newline in patch * Move PushNotification changes to end of patch * npm cache test * Revert "npm cache test" This reverts commit d31030aaeeb010c1c3d22a5f6196191eeb849add. * Created patch after upgrading node * Created patch after upgrading node take 2 * Remove androidx changes from patch * Patch packages then jetify * Cache node_modules without patches * Remove adding of default message (#3557) * [MM-16232] iOS: Fetch id-loaded push notification from server (#3556) * Fetch notification from server * Parse fetched notification response * Fix id-loaded notifications for DM/GM's * audit fix * Only add keys if they exist * Throw exception if response code is not 200 --- .circleci/config.yml | 9 +++-- Makefile | 3 +- .../rnbeta/CustomPushNotification.java | 39 +++++++++++++++---- .../mattermost/rnbeta/ReceiptDelivery.java | 34 +++++++++++++--- .../push_notifications.ios.js | 17 ++++---- .../NotificationService.swift | 34 +++++++++++++--- .../UploadAttachments/UploadSession.swift | 17 ++++++-- .../react-native-notifications+2.0.6.patch | 18 +++++++-- 8 files changed, 134 insertions(+), 37 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d2b61f1fb..dd272f7f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -91,15 +91,18 @@ commands: steps: - restore_cache: name: Restore npm cache - key: v1-npm-{{ checksum "package.json" }}-{{ arch }} + key: v2-npm-{{ checksum "package.json" }}-{{ arch }} - run: name: Getting JavaScript dependencies - command: NODE_ENV=development npm install + command: NODE_ENV=development npm install --ignore-scripts - save_cache: name: Save npm cache - key: v1-npm-{{ checksum "package.json" }}-{{ arch }} + key: v2-npm-{{ checksum "package.json" }}-{{ arch }} paths: - node_modules + - run: + name: "Run post install scripts" + command: make post-install pods-dependencies: description: "Get cocoapods dependencies" diff --git a/Makefile b/Makefile index 33549d57c..8823431df 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,7 @@ clean: ## Cleans dependencies, previous builds and temp files @echo Cleanup finished post-install: + @./node_modules/.bin/patch-package @./node_modules/.bin/jetify @rm -f node_modules/intl/.babelrc @@ -84,8 +85,6 @@ post-install: @sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json @sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json - @./node_modules/.bin/patch-package - start: | pre-run ## Starts the React Native packager server $(call start_packager) diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java index 3824054ad..0c272ec8e 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java @@ -20,6 +20,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.Build; import android.provider.Settings.System; +import androidx.annotation.Nullable; +import android.util.Log; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; @@ -35,6 +37,9 @@ import com.wix.reactnativenotifications.core.JsIOHelper; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME; +import com.mattermost.react_native_interface.ResolvePromise; +import com.facebook.react.bridge.WritableMap; + public class CustomPushNotification extends PushNotification { public static final int MESSAGE_NOTIFICATION_ID = 435345; public static final String GROUP_KEY_MESSAGES = "mm_group_key_messages"; @@ -42,6 +47,8 @@ public class CustomPushNotification extends PushNotification { public static final String KEY_TEXT_REPLY = "CAN_REPLY"; public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied"; + private static final String PUSH_TYPE_ID_LOADED = "id_loaded"; + private NotificationChannel mHighImportanceChannel; private NotificationChannel mMinImportanceChannel; @@ -93,16 +100,34 @@ public class CustomPushNotification extends PushNotification { @Override public void onReceived() throws InvalidNotificationException { - Bundle data = mNotificationProps.asBundle(); - final String channelId = data.getString("channel_id"); - final String type = data.getString("type"); - final String ackId = data.getString("ack_id"); + final Bundle initialData = mNotificationProps.asBundle(); + final String type = initialData.getString("type"); + final String ackId = initialData.getString("ack_id"); + final String postId = initialData.getString("post_id"); + final String channelId = initialData.getString("channel_id"); int notificationId = MESSAGE_NOTIFICATION_ID; if (ackId != null) { - notificationReceiptDelivery(ackId, type); + notificationReceiptDelivery(ackId, postId, type, new ResolvePromise() { + @Override + public void resolve(@Nullable Object value) { + if (PUSH_TYPE_ID_LOADED.equals(type)) { + Bundle response = (Bundle) value; + mNotificationProps = createProps(response); + } + } + + @Override + public void reject(String code, String message) { + Log.e("ReactNative", code + ": " + message); + } + }); } + // notificationReceiptDelivery can override mNotificationProps + // so we fetch the bundle again + final Bundle data = mNotificationProps.asBundle(); + if (channelId != null) { notificationId = channelId.hashCode(); Object objCount = channelIdToNotificationCount.get(channelId); @@ -493,8 +518,8 @@ public class CustomPushNotification extends PushNotification { return message.replaceFirst(senderName, "").replaceFirst(": ", "").trim(); } - private void notificationReceiptDelivery(String ackId, String type) { - ReceiptDelivery.send(context, ackId, type); + private void notificationReceiptDelivery(String ackId, String postId, String type, ResolvePromise promise) { + ReceiptDelivery.send(context, ackId, postId, type, promise); } private void createNotificationChannels() { diff --git a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java index d63f9de60..e768179f4 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java @@ -2,6 +2,7 @@ package com.mattermost.rnbeta; import android.content.Context; import androidx.annotation.Nullable; +import android.os.Bundle; import android.util.Log; import java.lang.System; @@ -18,13 +19,14 @@ import org.json.JSONException; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Arguments; 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) { + public static void send(Context context, final String ackId, final String postId, final String type, ResolvePromise promise) { final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context); MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() { @@ -47,17 +49,22 @@ public class ReceiptDelivery { } 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); + execute(serverUrl, postId, token, ackId, type, promise); } } }); } - protected static void execute(String serverUrl, String token, String ackId, String type) { - if (token == null || serverUrl == null) { + protected static void execute(String serverUrl, String postId, String token, String ackId, String type, ResolvePromise promise) { + if (token == null) { + promise.reject("Receipt delivery failure", "Invalid token"); return; } + if (serverUrl == null) { + promise.reject("Receipt delivery failure", "Invalid server URL"); + } + JSONObject json; long receivedAt = System.currentTimeMillis(); @@ -67,8 +74,10 @@ public class ReceiptDelivery { json.put("received_at", receivedAt); json.put("platform", "android"); json.put("type", type); + json.put("post_id", postId); } catch (JSONException e) { Log.e("ReactNative", "Receipt delivery failed to build json payload"); + promise.reject("Receipt delivery failure", e.toString()); return; } @@ -86,9 +95,24 @@ public class ReceiptDelivery { .build(); try { - client.newCall(request).execute(); + Response response = client.newCall(request).execute(); + String responseBody = response.body().toString(); + if (response.code() != 200) { + throw new Exception(responseBody); + } + JSONObject jsonResponse = new JSONObject(responseBody); + Bundle bundle = new Bundle(); + String keys[] = new String[] {"post_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"}; + for (int i = 0; i < keys.length; i++) { + String key = keys[i]; + if (jsonResponse.has(key)) { + bundle.putString(key, jsonResponse.getString(key)); + } + } + promise.resolve(bundle); } catch (Exception e) { Log.e("ReactNative", "Receipt delivery failed to send"); + promise.reject("Receipt delivery failure", e.toString()); } } } diff --git a/app/push_notifications/push_notifications.ios.js b/app/push_notifications/push_notifications.ios.js index b04850f0c..8868fd94d 100644 --- a/app/push_notifications/push_notifications.ios.js +++ b/app/push_notifications/push_notifications.ios.js @@ -38,7 +38,7 @@ class PushNotification { this.deviceNotification = { data, foreground, - message: data.message, + message: data.body || data.message, userInfo: data.userInfo, userInteraction, }; @@ -155,9 +155,10 @@ class PushNotification { ephemeralStore.appStartedFromPushNotification = true; } + const data = notification.getData(); const info = { - ...notification.getData(), - message: notification.getMessage(), + ...data, + message: data.body || notification.getMessage(), }; if (!userInteraction) { @@ -166,9 +167,10 @@ class PushNotification { }; onNotificationReceivedForeground = (notification) => { + const data = notification.getData(); const info = { - ...notification.getData(), - message: notification.getMessage(), + ...data, + message: data.body || notification.getMessage(), }; this.handleNotification(info, true, false); }; @@ -177,9 +179,10 @@ class PushNotification { if (action.identifier === REPLY_ACTION) { this.handleReply(notification, action.text, completion); } else { + const data = notification.getData(); const info = { - ...notification.getData(), - message: notification.getMessage(), + ...data, + message: data.body || notification.getMessage(), }; this.handleNotification(info, false, true); completion(); diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 371a7f36f..5e686d4c5 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -9,15 +9,37 @@ class NotificationService: UNNotificationServiceExtension { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - if let bestAttemptContent = bestAttemptContent { + let ackId = bestAttemptContent.userInfo["ack_id"] + let type = bestAttemptContent.userInfo["type"] + let postId = bestAttemptContent.userInfo["post_id"] UploadSession.shared.notificationReceipt( - notificationId: bestAttemptContent.userInfo["ack_id"], + notificationId: ackId, receivedAt: Date().millisencondsSince1970, - type: bestAttemptContent.userInfo["type"] - ) - - contentHandler(bestAttemptContent) + type: type, + postId: postId + ) { data, error in + if (type as? String == "id_loaded") { + guard let data = data, error == nil else { + return + } + + let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary + bestAttemptContent.title = json!["channel_name"] as! String + bestAttemptContent.body = json!["message"] as! String + + bestAttemptContent.userInfo["channel_name"] = json!["channel_name"] as! String + bestAttemptContent.userInfo["team_id"] = json!["team_id"] as? String + bestAttemptContent.userInfo["sender_id"] = json!["sender_id"] as! String + bestAttemptContent.userInfo["sender_name"] = json!["sender_name"] as! String + bestAttemptContent.userInfo["root_id"] = json!["root_id"] as? String + bestAttemptContent.userInfo["override_username"] = json!["override_username"] as? String + bestAttemptContent.userInfo["override_icon_url"] = json!["override_icon_url"] as? String + bestAttemptContent.userInfo["from_webhook"] = json!["from_webhook"] as? String + } + + contentHandler(bestAttemptContent) + } } } diff --git a/ios/UploadAttachments/UploadAttachments/UploadSession.swift b/ios/UploadAttachments/UploadAttachments/UploadSession.swift index d0dcd858e..7cad6f6ee 100644 --- a/ios/UploadAttachments/UploadAttachments/UploadSession.swift +++ b/ios/UploadAttachments/UploadAttachments/UploadSession.swift @@ -126,8 +126,12 @@ import os.log } }) } - + public func notificationReceipt(notificationId: Any?, receivedAt: Int, type: Any?) { + notificationReceipt(notificationId:notificationId, receivedAt:receivedAt, type:type, postId:nil, completion:{_, _ in}) + } + + public func notificationReceipt(notificationId: Any?, receivedAt: Int, type: Any?, postId: Any? = nil, completion: @escaping (Data?, Error?) -> Void) { if (notificationId != nil) { let store = StoreManager.shared() as StoreManager let entities = store.getEntities(true) @@ -142,18 +146,23 @@ import os.log "id": notificationId as Any, "received_at": receivedAt, "platform": "ios", - "type": type as Any + "type": type as Any, + "post_id": postId 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() + + let task = URLSession(configuration: .ephemeral).dataTask(with: request) { data, _, error in + completion(data, error) + } + task.resume() } } } diff --git a/patches/react-native-notifications+2.0.6.patch b/patches/react-native-notifications+2.0.6.patch index 56857ce1d..041401825 100644 --- a/patches/react-native-notifications+2.0.6.patch +++ b/patches/react-native-notifications+2.0.6.patch @@ -165,7 +165,7 @@ index 0d70024..47b962e 100644 PushNotificationProps asProps(); } diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java -index 5e4e3d2..871e157 100644 +index 5e4e3d2..ec37f87 100644 --- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java +++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java @@ -1,5 +1,6 @@ @@ -175,16 +175,28 @@ index 5e4e3d2..871e157 100644 import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -@@ -20,7 +21,9 @@ import com.wix.reactnativenotifications.core.InitialNotificationHolder; +@@ -20,18 +21,20 @@ import com.wix.reactnativenotifications.core.InitialNotificationHolder; import com.wix.reactnativenotifications.core.JsIOHelper; import com.wix.reactnativenotifications.core.NotificationIntentAdapter; import com.wix.reactnativenotifications.core.ProxyService; +import com.wix.reactnativenotifications.core.helpers.ScheduleNotificationHelper; - + +import static com.wix.reactnativenotifications.Defs.LOGTAG; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_FOREGROUND_EVENT_NAME; + + public class PushNotification implements IPushNotification { + ++ protected PushNotificationProps mNotificationProps; + final protected Context mContext; + final protected AppLifecycleFacade mAppLifecycleFacade; + final protected AppLaunchHelper mAppLaunchHelper; + final protected JsIOHelper mJsIOHelper; +- final protected PushNotificationProps mNotificationProps; + final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() { + @Override + public void onAppVisible() { @@ -80,6 +83,41 @@ public class PushNotification implements IPushNotification { return postNotification(notificationId); }