expo-notifications 0.28.12 → 0.28.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java +75 -21
  4. package/android/src/main/java/expo/modules/notifications/notifications/debug/DebugLogging.kt +86 -0
  5. package/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt +10 -3
  6. package/android/src/main/java/expo/modules/notifications/service/NotificationsService.kt +7 -1
  7. package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoNotificationLifecycleListener.java +3 -9
  8. package/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +8 -1
  9. package/build/NotificationsEmitter.d.ts.map +1 -1
  10. package/build/NotificationsEmitter.js +5 -2
  11. package/build/NotificationsEmitter.js.map +1 -1
  12. package/build/useLastNotificationResponse.d.ts +3 -1
  13. package/build/useLastNotificationResponse.d.ts.map +1 -1
  14. package/build/useLastNotificationResponse.js +23 -16
  15. package/build/useLastNotificationResponse.js.map +1 -1
  16. package/build/utils/mapNotificationResponse.d.ts +21 -3
  17. package/build/utils/mapNotificationResponse.d.ts.map +1 -1
  18. package/build/utils/mapNotificationResponse.js +21 -6
  19. package/build/utils/mapNotificationResponse.js.map +1 -1
  20. package/ios/EXNotifications/Notifications/EXNotificationCenterDelegate.h +4 -0
  21. package/ios/EXNotifications/Notifications/EXNotificationCenterDelegate.m +2 -0
  22. package/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m +3 -4
  23. package/package.json +2 -2
  24. package/src/NotificationsEmitter.ts +8 -2
  25. package/src/useLastNotificationResponse.ts +39 -20
  26. package/src/utils/mapNotificationResponse.ts +25 -9
package/CHANGELOG.md CHANGED
@@ -10,6 +10,19 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.28.14 — 2024-07-30
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - `useLastNotificationResponse` should have only one effect. ([#30653](https://github.com/expo/expo/pull/30653) by [@douglowder](https://github.com/douglowder))
18
+
19
+ ## 0.28.13 — 2024-07-29
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - [Android] map Expo and Firebase notifications correctly. ([#30615](https://github.com/expo/expo/pull/30615) by [@douglowder](https://github.com/douglowder))
24
+ - [Android] Apply requested changes from #30615. ([#30658](https://github.com/expo/expo/pull/30615) by [@lukmccall](https://github.com/lukmccall))
25
+
13
26
  ## 0.28.12 — 2024-07-25
14
27
 
15
28
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '0.28.12'
4
+ version = '0.28.14'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,7 +14,7 @@ android {
14
14
  namespace "expo.modules.notifications"
15
15
  defaultConfig {
16
16
  versionCode 21
17
- versionName '0.28.12'
17
+ versionName '0.28.14'
18
18
  }
19
19
 
20
20
  buildFeatures {
@@ -5,8 +5,11 @@ import android.os.Bundle;
5
5
 
6
6
  import androidx.annotation.Nullable;
7
7
 
8
+ import com.google.firebase.messaging.RemoteMessage;
9
+
8
10
  import org.jetbrains.annotations.NotNull;
9
11
  import org.json.JSONArray;
12
+ import org.json.JSONException;
10
13
  import org.json.JSONObject;
11
14
  import expo.modules.core.arguments.MapArguments;
12
15
 
@@ -52,11 +55,37 @@ public class NotificationSerializer {
52
55
  public static Bundle toBundle(NotificationRequest request) {
53
56
  Bundle serializedRequest = new Bundle();
54
57
  serializedRequest.putString("identifier", request.getIdentifier());
55
- serializedRequest.putBundle("content", toBundle(request.getContent()));
56
58
  serializedRequest.putBundle("trigger", toBundle(request.getTrigger()));
59
+ Bundle content = toBundle(request.getContent());
60
+ Bundle existingContentData = content.getBundle("data");
61
+ if (existingContentData == null) {
62
+ FirebaseNotificationTrigger trigger = (FirebaseNotificationTrigger) request.getTrigger();
63
+ RemoteMessage message = trigger.getRemoteMessage();
64
+ RemoteMessage.Notification notification = message.getNotification();
65
+ Map<String, String> data = message.getData();
66
+ String dataBody = data.get("body");
67
+ String notificationBody = notification != null ? notification.getBody() : null;
68
+ if (isValidJSONString(dataBody) && notificationBody != null && notificationBody.equals(data.get("message"))) {
69
+ // Expo sends notification.body as data.message, and JSON stringifies data.body
70
+ content.putString("dataString", dataBody);
71
+ } else {
72
+ // The message was sent directly from Firebase or some other service,
73
+ // and we copy the data as is
74
+ content.putBundle("data", toBundle(data));
75
+ }
76
+ }
77
+ serializedRequest.putBundle("content", content);
57
78
  return serializedRequest;
58
79
  }
59
80
 
81
+ public static Bundle toBundle(Map<String, String> map) {
82
+ Bundle result = new Bundle();
83
+ for (String key: map.keySet()) {
84
+ result.putString(key, map.get(key));
85
+ }
86
+ return result;
87
+ }
88
+
60
89
  public static Bundle toBundle(NotificationContent content) {
61
90
  Bundle serializedContent = new Bundle();
62
91
  serializedContent.putString("title", content.getTitle());
@@ -65,7 +94,7 @@ public class NotificationSerializer {
65
94
  if (content.getColor() != null) {
66
95
  serializedContent.putString("color", String.format("#%08X", content.getColor().intValue()));
67
96
  }
68
- serializedContent.putBundle("data", toBundle(content.getBody()));
97
+
69
98
  if (content.getBadgeCount() != null) {
70
99
  serializedContent.putInt("badge", content.getBadgeCount().intValue());
71
100
  } else {
@@ -192,30 +221,55 @@ public class NotificationSerializer {
192
221
  return null;
193
222
  }
194
223
 
195
- @NotNull
196
- public static Bundle toResponseBundleFromExtras(Bundle extras) {
197
- Bundle serializedContent = new Bundle();
198
- serializedContent.putString("title", extras.getString("title"));
224
+ @NotNull
225
+ public static Bundle toResponseBundleFromExtras(Bundle extras) {
226
+ Bundle serializedContent = new Bundle();
227
+ serializedContent.putString("title", extras.getString("title"));
228
+ String body = extras.getString("body");
229
+ String projectId = extras.getString("projectId");
230
+ if (projectId != null && isValidJSONString(body) ) {
231
+ // If projectId is set in the bundle, and the body is a JSON string,
232
+ // the notification was sent by the Expo notification service,
233
+ // so we do the expected remapping of fields
234
+ serializedContent.putString("dataString", body);
199
235
  serializedContent.putString("body", extras.getString("message"));
200
- serializedContent.putString("dataString", extras.getString("body"));
236
+ } else {
237
+ // The notification came directly from Firebase or some other service,
238
+ // so we copy the data as is from the extras bundle
239
+ serializedContent.putBundle("data", extras);
240
+ }
201
241
 
202
- Bundle serializedTrigger = new Bundle();
203
- serializedTrigger.putString("type", "push");
204
- serializedTrigger.putString("channelId", extras.getString("channelId"));
242
+ Bundle serializedTrigger = new Bundle();
243
+ serializedTrigger.putString("type", "push");
244
+ serializedTrigger.putString("channelId", extras.getString("channelId"));
205
245
 
206
- Bundle serializedRequest = new Bundle();
207
- serializedRequest.putString("identifier", extras.getString("google.message_id"));
208
- serializedRequest.putBundle("trigger", serializedTrigger);
209
- serializedRequest.putBundle("content", serializedContent);
246
+ Bundle serializedRequest = new Bundle();
247
+ serializedRequest.putString("identifier", extras.getString("google.message_id"));
248
+ serializedRequest.putBundle("trigger", serializedTrigger);
249
+ serializedRequest.putBundle("content", serializedContent);
210
250
 
211
- Bundle serializedNotification = new Bundle();
212
- serializedNotification.putLong("date", extras.getLong("google.sent_time"));
213
- serializedNotification.putBundle("request", serializedRequest);
251
+ Bundle serializedNotification = new Bundle();
252
+ serializedNotification.putLong("date", extras.getLong("google.sent_time"));
253
+ serializedNotification.putBundle("request", serializedRequest);
214
254
 
215
- Bundle serializedResponse = new Bundle();
216
- serializedResponse.putString("actionIdentifier", "expo.modules.notifications.actions.DEFAULT");
217
- serializedResponse.putBundle("notification", serializedNotification);
255
+ Bundle serializedResponse = new Bundle();
256
+ serializedResponse.putString("actionIdentifier", "expo.modules.notifications.actions.DEFAULT");
257
+ serializedResponse.putBundle("notification", serializedNotification);
218
258
 
219
- return serializedResponse;
259
+ return serializedResponse;
260
+ }
261
+
262
+ public static boolean isValidJSONString(String test) {
263
+ try {
264
+ new JSONObject(test);
265
+ } catch (JSONException objectEx) {
266
+ try {
267
+ new JSONArray(test);
268
+ } catch (JSONException arrayEx) {
269
+ return false;
270
+ }
220
271
  }
272
+ return true;
273
+ }
274
+
221
275
  }
@@ -0,0 +1,86 @@
1
+ package expo.modules.notifications.notifications.debug
2
+
3
+ import android.os.Build
4
+ import android.os.Bundle
5
+ import android.util.Log
6
+ import androidx.annotation.RequiresApi
7
+ import com.google.firebase.messaging.RemoteMessage
8
+ import expo.modules.notifications.BuildConfig
9
+ import expo.modules.notifications.notifications.model.Notification
10
+ import java.util.function.Consumer
11
+
12
+ object DebugLogging {
13
+ @JvmStatic
14
+ fun logBundle(caller: String, bundleToLog: Bundle) {
15
+ if (!BuildConfig.DEBUG) {
16
+ // Do not log in release/production builds
17
+ return
18
+ }
19
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
20
+ return
21
+ }
22
+ Log.i("expo-notifications", "$caller:\n${bundleString(caller, bundleToLog, 0)}")
23
+ }
24
+
25
+ @RequiresApi(Build.VERSION_CODES.N)
26
+ private fun bundleString(ignoredCaller: String, bundleToLog: Bundle, indent: Int): String {
27
+ return buildString {
28
+ bundleToLog.keySet().forEach(
29
+ Consumer { key: String ->
30
+ val value = bundleToLog[key]
31
+ if (value is Bundle) {
32
+ append("${" ".repeat(indent)}${key}\n")
33
+ append(bundleString(ignoredCaller, value, indent + 2))
34
+ } else {
35
+ val stringValue = value?.toString() ?: "(null)"
36
+ append("${" ".repeat(indent)}$key: $stringValue\n")
37
+ }
38
+ }
39
+ )
40
+ }
41
+ }
42
+
43
+ fun logRemoteMessage(caller: String, message: RemoteMessage) {
44
+ if (!BuildConfig.DEBUG) {
45
+ // Do not log for release/production builds
46
+ return
47
+ }
48
+ val logMessage =
49
+ """
50
+ $caller:
51
+ notification.channelId: ${message.notification?.channelId}
52
+ notification.vibrateTimings: ${message.notification?.vibrateTimings?.contentToString()}
53
+ notification.body: ${message.notification?.body}
54
+ notification.color: ${message.notification?.color}
55
+ notification.sound: ${message.notification?.sound}
56
+ notification.title: ${message.notification?.title}
57
+ notification.collapseKey: ${message.collapseKey}
58
+ data: ${message.data}
59
+ """.trimIndent()
60
+
61
+ Log.i("expo-notifications", logMessage)
62
+ }
63
+
64
+ @RequiresApi(Build.VERSION_CODES.O)
65
+ fun logNotification(caller: String, notification: Notification) {
66
+ if (!BuildConfig.DEBUG) {
67
+ // Do not log for release/production builds
68
+ return
69
+ }
70
+ val logMessage =
71
+ """
72
+ $caller:
73
+ notification.notificationRequest.content.title: ${notification.notificationRequest.content.title}
74
+ notification.notificationRequest.content.subtitle: ${notification.notificationRequest.content.subtitle}
75
+ notification.notificationRequest.content.text: ${notification.notificationRequest.content.text}
76
+ notification.notificationRequest.content.sound: ${notification.notificationRequest.content.sound}
77
+ notification.notificationRequest.content.body: ${notification.notificationRequest.content.body}
78
+ notification.notificationRequest.content.color: ${notification.notificationRequest.content.color}
79
+ notification.notificationRequest.content.vibrationPattern: ${notification.notificationRequest.content.vibrationPattern.contentToString()}
80
+ notification.notificationRequest.trigger.notificationChannel: ${notification.notificationRequest.trigger.notificationChannel}
81
+ notification.notificationRequest.identifier: ${notification.notificationRequest.identifier}
82
+ """.trimIndent()
83
+
84
+ Log.i("expo-notifications", logMessage)
85
+ }
86
+ }
@@ -4,6 +4,7 @@ import android.os.Bundle
4
4
  import expo.modules.kotlin.modules.Module
5
5
  import expo.modules.kotlin.modules.ModuleDefinition
6
6
  import expo.modules.notifications.notifications.NotificationSerializer
7
+ import expo.modules.notifications.notifications.debug.DebugLogging
7
8
  import expo.modules.notifications.notifications.interfaces.NotificationListener
8
9
  import expo.modules.notifications.notifications.interfaces.NotificationManager
9
10
  import expo.modules.notifications.notifications.model.Notification
@@ -49,7 +50,9 @@ open class NotificationsEmitter : Module(), NotificationListener {
49
50
  * @param notification Notification received
50
51
  */
51
52
  override fun onNotificationReceived(notification: Notification) {
52
- sendEvent(NEW_MESSAGE_EVENT_NAME, NotificationSerializer.toBundle(notification))
53
+ val bundle = NotificationSerializer.toBundle(notification)
54
+ DebugLogging.logBundle("NotificationsEmitter.onNotificationReceived", bundle)
55
+ sendEvent(NEW_MESSAGE_EVENT_NAME, bundle)
53
56
  }
54
57
 
55
58
  /**
@@ -60,13 +63,17 @@ open class NotificationsEmitter : Module(), NotificationListener {
60
63
  * @return Whether notification has been handled
61
64
  */
62
65
  override fun onNotificationResponseReceived(response: NotificationResponse): Boolean {
63
- lastNotificationResponseBundle = NotificationSerializer.toBundle(response)
66
+ val bundle = NotificationSerializer.toBundle(response)
67
+ DebugLogging.logBundle("NotificationsEmitter.onNotificationResponseReceived", bundle)
68
+ lastNotificationResponseBundle = bundle
64
69
  sendEvent(NEW_RESPONSE_EVENT_NAME, lastNotificationResponseBundle)
65
70
  return true
66
71
  }
67
72
 
68
73
  override fun onNotificationResponseIntentReceived(extras: Bundle?) {
69
- lastNotificationResponseBundle = NotificationSerializer.toResponseBundleFromExtras(extras)
74
+ val bundle = NotificationSerializer.toResponseBundleFromExtras(extras)
75
+ DebugLogging.logBundle("NotificationsEmitter.onNotificationResponseIntentReceived", bundle)
76
+ lastNotificationResponseBundle = bundle
70
77
  sendEvent(NEW_RESPONSE_EVENT_NAME, lastNotificationResponseBundle)
71
78
  }
72
79
 
@@ -10,6 +10,7 @@ import android.net.Uri
10
10
  import android.os.*
11
11
  import android.util.Log
12
12
  import androidx.core.app.RemoteInput
13
+ import expo.modules.notifications.BuildConfig
13
14
  import expo.modules.notifications.notifications.model.*
14
15
  import expo.modules.notifications.service.delegates.ExpoCategoriesDelegate
15
16
  import expo.modules.notifications.service.delegates.ExpoHandlingDelegate
@@ -644,7 +645,12 @@ open class NotificationsService : BroadcastReceiver() {
644
645
  // If we ended up here, the callbacks must have completed successfully
645
646
  receiver?.send(SUCCESS_CODE, resultData)
646
647
  } catch (e: Exception) {
647
- Log.e("expo-notifications", "Action ${intent.action} failed: ${e.message}")
648
+ if (BuildConfig.DEBUG) {
649
+ // Log stack trace for debugging
650
+ Log.e("expo-notifications", "Action ${intent.action} failed: ${e.message}\n${e.stackTraceToString()}")
651
+ } else {
652
+ Log.e("expo-notifications", "Action ${intent.action} failed: ${e.message}")
653
+ }
648
654
  e.printStackTrace()
649
655
 
650
656
  receiver?.send(ERROR_CODE, Bundle().also { it.putSerializable(EXCEPTION_KEY, e) })
@@ -1,22 +1,14 @@
1
1
  package expo.modules.notifications.service.delegates;
2
2
 
3
3
  import android.app.Activity;
4
- import android.app.NotificationChannel;
5
- import android.app.PendingIntent;
6
4
  import android.content.Context;
7
5
  import android.content.Intent;
8
6
  import android.os.Bundle;
9
- import android.os.Parcel;
10
7
  import android.util.Log;
11
8
 
12
- import androidx.core.app.NotificationCompat;
13
-
14
- import java.util.Objects;
15
-
16
9
  import expo.modules.core.interfaces.ReactActivityLifecycleListener;
17
10
  import expo.modules.notifications.notifications.NotificationManager;
18
- import expo.modules.notifications.notifications.model.Notification;
19
- import expo.modules.notifications.notifications.model.NotificationResponse;
11
+ import expo.modules.notifications.notifications.debug.DebugLogging;
20
12
 
21
13
  public class ExpoNotificationLifecycleListener implements ReactActivityLifecycleListener {
22
14
 
@@ -45,6 +37,7 @@ public class ExpoNotificationLifecycleListener implements ReactActivityLifecycle
45
37
  Log.d("ReactNativeJS", "[native] ExpoNotificationLifecycleListener contains an unmarshaled notification response. Skipping.");
46
38
  return;
47
39
  }
40
+ DebugLogging.logBundle("ExpoNotificationLifeCycleListener.onCreate:", extras);
48
41
  mNotificationManager.onNotificationResponseFromExtras(extras);
49
42
  }
50
43
  }
@@ -68,6 +61,7 @@ public class ExpoNotificationLifecycleListener implements ReactActivityLifecycle
68
61
  intent.removeExtra("notificationResponse");
69
62
  return ReactActivityLifecycleListener.super.onNewIntent(intent);
70
63
  }
64
+ DebugLogging.logBundle("ExpoNotificationLifeCycleListener.onNewIntent:", extras);
71
65
  mNotificationManager.onNotificationResponseFromExtras(extras);
72
66
  }
73
67
  return ReactActivityLifecycleListener.super.onNewIntent(intent);
@@ -1,10 +1,12 @@
1
1
  package expo.modules.notifications.service.delegates
2
2
 
3
3
  import android.content.Context
4
+ import android.os.Build
4
5
  import com.google.firebase.messaging.RemoteMessage
5
6
  import expo.modules.notifications.notifications.JSONNotificationContentBuilder
6
7
  import expo.modules.notifications.notifications.RemoteMessageSerializer
7
8
  import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
9
+ import expo.modules.notifications.notifications.debug.DebugLogging
8
10
  import expo.modules.notifications.notifications.model.Notification
9
11
  import expo.modules.notifications.notifications.model.NotificationContent
10
12
  import expo.modules.notifications.notifications.model.NotificationRequest
@@ -90,7 +92,12 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
90
92
  fun getBackgroundTasks() = sBackgroundTaskConsumerReferences.values.mapNotNull { it.get() }
91
93
 
92
94
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
93
- NotificationsService.receive(context, createNotification(remoteMessage))
95
+ DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage)
96
+ val notification = createNotification(remoteMessage)
97
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
98
+ DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
99
+ }
100
+ NotificationsService.receive(context, notification)
94
101
  getBackgroundTasks().forEach {
95
102
  it.scheduleJob(RemoteMessageSerializer.toBundle(remoteMessage))
96
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationsEmitter.d.ts","sourceRoot":"","sources":["../src/NotificationsEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEpF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAY3E,eAAO,MAAM,yBAAyB,+CAA+C,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GACtC,YAAY,CAEd;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,YAAY,CAElF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAC9C,YAAY,CAQd;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,YAAY,QAExE;AAGD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAO7F"}
1
+ {"version":3,"file":"NotificationsEmitter.d.ts","sourceRoot":"","sources":["../src/NotificationsEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEpF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAY3E,eAAO,MAAM,yBAAyB,+CAA+C,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GACtC,YAAY,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,YAAY,CAElF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAC9C,YAAY,CAQd;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,YAAY,QAExE;AAGD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAO7F"}
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter, UnavailabilityError } from 'expo-modules-core';
2
2
  import NotificationsEmitterModule from './NotificationsEmitterModule';
3
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
3
+ import { mapNotification, mapNotificationResponse } from './utils/mapNotificationResponse';
4
4
  // Web uses SyntheticEventEmitter
5
5
  const emitter = new EventEmitter(NotificationsEmitterModule);
6
6
  const didReceiveNotificationEventName = 'onDidReceiveNotification';
@@ -33,7 +33,10 @@ export const DEFAULT_ACTION_IDENTIFIER = 'expo.modules.notifications.actions.DEF
33
33
  * @header listen
34
34
  */
35
35
  export function addNotificationReceivedListener(listener) {
36
- return emitter.addListener(didReceiveNotificationEventName, listener);
36
+ return emitter.addListener(didReceiveNotificationEventName, (notification) => {
37
+ const mappedNotification = mapNotification(notification);
38
+ listener(mappedNotification);
39
+ });
37
40
  }
38
41
  /**
39
42
  * Listeners registered by this method will be called whenever some notifications have been dropped by the server.
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationsEmitter.js","sourceRoot":"","sources":["../src/NotificationsEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGpF,OAAO,0BAA0B,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,iCAAiC;AACjC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,0BAA0B,CAAC,CAAC;AAE7D,MAAM,+BAA+B,GAAG,0BAA0B,CAAC;AACnE,MAAM,6BAA6B,GAAG,wBAAwB,CAAC;AAC/D,MAAM,uCAAuC,GAAG,kCAAkC,CAAC;AAEnF,eAAe;AACf,MAAM,CAAC,MAAM,yBAAyB,GAAG,4CAA4C,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAAuC;IAEvC,OAAO,OAAO,CAAC,WAAW,CAAe,+BAA+B,EAAE,QAAQ,CAAC,CAAC;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,QAAoB;IAClE,OAAO,OAAO,CAAC,WAAW,CAAO,6BAA6B,EAAE,QAAQ,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,uCAAuC,CACrD,QAA+C;IAE/C,OAAO,OAAO,CAAC,WAAW,CACxB,uCAAuC,EACvC,CAAC,QAA8B,EAAE,EAAE;QACjC,MAAM,cAAc,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACzD,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC3B,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,YAA0B;IACvE,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;AAC3C,CAAC;AAED,eAAe;AACf;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC;IACpD,IAAI,CAAC,0BAA0B,CAAC,gCAAgC,EAAE;QAChE,MAAM,IAAI,mBAAmB,CAAC,mBAAmB,EAAE,kCAAkC,CAAC,CAAC;KACxF;IACD,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC,gCAAgC,EAAE,CAAC;IACrF,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC/E,OAAO,cAAc,CAAC;AACxB,CAAC","sourcesContent":["import { EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-core';\n\nimport { Notification, NotificationResponse } from './Notifications.types';\nimport NotificationsEmitterModule from './NotificationsEmitterModule';\nimport { mapNotificationResponse } from './utils/mapNotificationResponse';\n\n// Web uses SyntheticEventEmitter\nconst emitter = new EventEmitter(NotificationsEmitterModule);\n\nconst didReceiveNotificationEventName = 'onDidReceiveNotification';\nconst didDropNotificationsEventName = 'onNotificationsDeleted';\nconst didReceiveNotificationResponseEventName = 'onDidReceiveNotificationResponse';\n\n// @docsMissing\nexport const DEFAULT_ACTION_IDENTIFIER = 'expo.modules.notifications.actions.DEFAULT';\n\n/**\n * Listeners registered by this method will be called whenever a notification is received while the app is running.\n * @param listener A function accepting a notification ([`Notification`](#notification)) as an argument.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @example Registering a notification listener using a React hook:\n * ```jsx\n * import React from 'react';\n * import * as Notifications from 'expo-notifications';\n *\n * export default function App() {\n * React.useEffect(() => {\n * const subscription = Notifications.addNotificationReceivedListener(notification => {\n * console.log(notification);\n * });\n * return () => subscription.remove();\n * }, []);\n *\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport function addNotificationReceivedListener(\n listener: (event: Notification) => void\n): Subscription {\n return emitter.addListener<Notification>(didReceiveNotificationEventName, listener);\n}\n\n/**\n * Listeners registered by this method will be called whenever some notifications have been dropped by the server.\n * Applicable only to Firebase Cloud Messaging which we use as a notifications service on Android. It corresponds to `onDeletedMessages()` callback.\n * More information can be found in [Firebase docs](https://firebase.google.com/docs/cloud-messaging/android/receive#override-ondeletedmessages).\n * @param listener A callback function.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @header listen\n */\nexport function addNotificationsDroppedListener(listener: () => void): Subscription {\n return emitter.addListener<void>(didDropNotificationsEventName, listener);\n}\n\n/**\n * Listeners registered by this method will be called whenever a user interacts with a notification (for example, taps on it).\n * @param listener A function accepting notification response ([`NotificationResponse`](#notificationresponse)) as an argument.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @example Register a notification responder listener:\n * ```jsx\n * import React from 'react';\n * import { Linking } from 'react-native';\n * import * as Notifications from 'expo-notifications';\n *\n * export default function Container() {\n * React.useEffect(() => {\n * const subscription = Notifications.addNotificationResponseReceivedListener(response => {\n * const url = response.notification.request.content.data.url;\n * Linking.openURL(url);\n * });\n * return () => subscription.remove();\n * }, []);\n *\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport function addNotificationResponseReceivedListener(\n listener: (event: NotificationResponse) => void\n): Subscription {\n return emitter.addListener<NotificationResponse>(\n didReceiveNotificationResponseEventName,\n (response: NotificationResponse) => {\n const mappedResponse = mapNotificationResponse(response);\n listener(mappedResponse);\n }\n );\n}\n\n/**\n * Removes a notification subscription returned by an `addNotificationListener` call.\n * @param subscription A subscription returned by `addNotificationListener` method.\n * @header listen\n */\nexport function removeNotificationSubscription(subscription: Subscription) {\n emitter.removeSubscription(subscription);\n}\n\n// @docsMissing\n/**\n * @header listen\n */\nexport async function getLastNotificationResponseAsync(): Promise<NotificationResponse | null> {\n if (!NotificationsEmitterModule.getLastNotificationResponseAsync) {\n throw new UnavailabilityError('ExpoNotifications', 'getLastNotificationResponseAsync');\n }\n const response = await NotificationsEmitterModule.getLastNotificationResponseAsync();\n const mappedResponse = response ? mapNotificationResponse(response) : response;\n return mappedResponse;\n}\n"]}
1
+ {"version":3,"file":"NotificationsEmitter.js","sourceRoot":"","sources":["../src/NotificationsEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGpF,OAAO,0BAA0B,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE3F,iCAAiC;AACjC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,0BAA0B,CAAC,CAAC;AAE7D,MAAM,+BAA+B,GAAG,0BAA0B,CAAC;AACnE,MAAM,6BAA6B,GAAG,wBAAwB,CAAC;AAC/D,MAAM,uCAAuC,GAAG,kCAAkC,CAAC;AAEnF,eAAe;AACf,MAAM,CAAC,MAAM,yBAAyB,GAAG,4CAA4C,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAAuC;IAEvC,OAAO,OAAO,CAAC,WAAW,CACxB,+BAA+B,EAC/B,CAAC,YAA0B,EAAE,EAAE;QAC7B,MAAM,kBAAkB,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;QACzD,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,QAAoB;IAClE,OAAO,OAAO,CAAC,WAAW,CAAO,6BAA6B,EAAE,QAAQ,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,uCAAuC,CACrD,QAA+C;IAE/C,OAAO,OAAO,CAAC,WAAW,CACxB,uCAAuC,EACvC,CAAC,QAA8B,EAAE,EAAE;QACjC,MAAM,cAAc,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACzD,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC3B,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,YAA0B;IACvE,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;AAC3C,CAAC;AAED,eAAe;AACf;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC;IACpD,IAAI,CAAC,0BAA0B,CAAC,gCAAgC,EAAE;QAChE,MAAM,IAAI,mBAAmB,CAAC,mBAAmB,EAAE,kCAAkC,CAAC,CAAC;KACxF;IACD,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC,gCAAgC,EAAE,CAAC;IACrF,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC/E,OAAO,cAAc,CAAC;AACxB,CAAC","sourcesContent":["import { EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-core';\n\nimport { Notification, NotificationResponse } from './Notifications.types';\nimport NotificationsEmitterModule from './NotificationsEmitterModule';\nimport { mapNotification, mapNotificationResponse } from './utils/mapNotificationResponse';\n\n// Web uses SyntheticEventEmitter\nconst emitter = new EventEmitter(NotificationsEmitterModule);\n\nconst didReceiveNotificationEventName = 'onDidReceiveNotification';\nconst didDropNotificationsEventName = 'onNotificationsDeleted';\nconst didReceiveNotificationResponseEventName = 'onDidReceiveNotificationResponse';\n\n// @docsMissing\nexport const DEFAULT_ACTION_IDENTIFIER = 'expo.modules.notifications.actions.DEFAULT';\n\n/**\n * Listeners registered by this method will be called whenever a notification is received while the app is running.\n * @param listener A function accepting a notification ([`Notification`](#notification)) as an argument.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @example Registering a notification listener using a React hook:\n * ```jsx\n * import React from 'react';\n * import * as Notifications from 'expo-notifications';\n *\n * export default function App() {\n * React.useEffect(() => {\n * const subscription = Notifications.addNotificationReceivedListener(notification => {\n * console.log(notification);\n * });\n * return () => subscription.remove();\n * }, []);\n *\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport function addNotificationReceivedListener(\n listener: (event: Notification) => void\n): Subscription {\n return emitter.addListener<Notification>(\n didReceiveNotificationEventName,\n (notification: Notification) => {\n const mappedNotification = mapNotification(notification);\n listener(mappedNotification);\n }\n );\n}\n\n/**\n * Listeners registered by this method will be called whenever some notifications have been dropped by the server.\n * Applicable only to Firebase Cloud Messaging which we use as a notifications service on Android. It corresponds to `onDeletedMessages()` callback.\n * More information can be found in [Firebase docs](https://firebase.google.com/docs/cloud-messaging/android/receive#override-ondeletedmessages).\n * @param listener A callback function.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @header listen\n */\nexport function addNotificationsDroppedListener(listener: () => void): Subscription {\n return emitter.addListener<void>(didDropNotificationsEventName, listener);\n}\n\n/**\n * Listeners registered by this method will be called whenever a user interacts with a notification (for example, taps on it).\n * @param listener A function accepting notification response ([`NotificationResponse`](#notificationresponse)) as an argument.\n * @return A [`Subscription`](#subscription) object represents the subscription of the provided listener.\n * @example Register a notification responder listener:\n * ```jsx\n * import React from 'react';\n * import { Linking } from 'react-native';\n * import * as Notifications from 'expo-notifications';\n *\n * export default function Container() {\n * React.useEffect(() => {\n * const subscription = Notifications.addNotificationResponseReceivedListener(response => {\n * const url = response.notification.request.content.data.url;\n * Linking.openURL(url);\n * });\n * return () => subscription.remove();\n * }, []);\n *\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport function addNotificationResponseReceivedListener(\n listener: (event: NotificationResponse) => void\n): Subscription {\n return emitter.addListener<NotificationResponse>(\n didReceiveNotificationResponseEventName,\n (response: NotificationResponse) => {\n const mappedResponse = mapNotificationResponse(response);\n listener(mappedResponse);\n }\n );\n}\n\n/**\n * Removes a notification subscription returned by an `addNotificationListener` call.\n * @param subscription A subscription returned by `addNotificationListener` method.\n * @header listen\n */\nexport function removeNotificationSubscription(subscription: Subscription) {\n emitter.removeSubscription(subscription);\n}\n\n// @docsMissing\n/**\n * @header listen\n */\nexport async function getLastNotificationResponseAsync(): Promise<NotificationResponse | null> {\n if (!NotificationsEmitterModule.getLastNotificationResponseAsync) {\n throw new UnavailabilityError('ExpoNotifications', 'getLastNotificationResponseAsync');\n }\n const response = await NotificationsEmitterModule.getLastNotificationResponseAsync();\n const mappedResponse = response ? mapNotificationResponse(response) : response;\n return mappedResponse;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { NotificationResponse } from './Notifications.types';
2
+ type MaybeNotificationResponse = NotificationResponse | null | undefined;
2
3
  /**
3
4
  * A React hook always returns the notification response that was received most recently
4
5
  * (a notification response designates an interaction with a notification, such as tapping on it).
@@ -35,5 +36,6 @@ import { NotificationResponse } from './Notifications.types';
35
36
  * ```
36
37
  * @header listen
37
38
  */
38
- export default function useLastNotificationResponse(): NotificationResponse | null | undefined;
39
+ export default function useLastNotificationResponse(): MaybeNotificationResponse;
40
+ export {};
39
41
  //# sourceMappingURL=useLastNotificationResponse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLastNotificationResponse.d.ts","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAK7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B,4CA0BlD"}
1
+ {"version":3,"file":"useLastNotificationResponse.d.ts","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAM7D,KAAK,yBAAyB,GAAG,oBAAoB,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B,8BA0ClD"}
@@ -1,7 +1,5 @@
1
- import { useEffect, useLayoutEffect, useState } from 'react';
2
- import { addNotificationResponseReceivedListener } from './NotificationsEmitter';
3
- import NotificationsEmitterModule from './NotificationsEmitterModule';
4
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
1
+ import { useLayoutEffect, useState } from 'react';
2
+ import { addNotificationResponseReceivedListener, getLastNotificationResponseAsync, } from './NotificationsEmitter';
5
3
  /**
6
4
  * A React hook always returns the notification response that was received most recently
7
5
  * (a notification response designates an interaction with a notification, such as tapping on it).
@@ -40,24 +38,33 @@ import { mapNotificationResponse } from './utils/mapNotificationResponse';
40
38
  */
41
39
  export default function useLastNotificationResponse() {
42
40
  const [lastNotificationResponse, setLastNotificationResponse] = useState(undefined);
41
+ // Pure function that returns the new response if it is different from the previous,
42
+ // otherwise return the previous response
43
+ const newResponseIfNeeded = (prevResponse, newResponse) => {
44
+ // If the new response is undefined or null, no need for update
45
+ if (!newResponse) {
46
+ return prevResponse;
47
+ }
48
+ // If the previous response is undefined or null and the new response is not, we should update
49
+ if (!prevResponse) {
50
+ return newResponse;
51
+ }
52
+ return prevResponse.notification.request.identifier !==
53
+ newResponse.notification.request.identifier
54
+ ? newResponse
55
+ : prevResponse;
56
+ };
43
57
  // useLayoutEffect ensures the listener is registered as soon as possible
44
58
  useLayoutEffect(() => {
45
- const subscription = addNotificationResponseReceivedListener((response) => {
46
- const mappedResponse = mapNotificationResponse(response);
47
- setLastNotificationResponse(mappedResponse);
48
- });
59
+ // Get the last response first, in case it was set earlier (even in native code on startup)
60
+ // before this renders
61
+ getLastNotificationResponseAsync?.().then((response) => setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response)));
62
+ // Set up listener for responses that come in, and set the last response if needed
63
+ const subscription = addNotificationResponseReceivedListener((response) => setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response)));
49
64
  return () => {
50
65
  subscription.remove();
51
66
  };
52
67
  }, []);
53
- // On each mount of this hook we fetch last notification response
54
- // from the native module which is an "always active listener"
55
- // and always returns the most recent response.
56
- useEffect(() => {
57
- NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {
58
- setLastNotificationResponse(response);
59
- });
60
- }, []);
61
68
  return lastNotificationResponse;
62
69
  }
63
70
  //# sourceMappingURL=useLastNotificationResponse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLastNotificationResponse.js","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG7D,OAAO,EAAE,uCAAuC,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,0BAA0B,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B;IACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,GAAG,QAAQ,CAEtE,SAAS,CAAC,CAAC;IAEb,yEAAyE;IACzE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,YAAY,GAAG,uCAAuC,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxE,MAAM,cAAc,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACzD,2BAA2B,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,iEAAiE;IACjE,8DAA8D;IAC9D,+CAA+C;IAC/C,SAAS,CAAC,GAAG,EAAE;QACb,0BAA0B,CAAC,gCAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChF,2BAA2B,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,wBAAwB,CAAC;AAClC,CAAC","sourcesContent":["import { useEffect, useLayoutEffect, useState } from 'react';\n\nimport { NotificationResponse } from './Notifications.types';\nimport { addNotificationResponseReceivedListener } from './NotificationsEmitter';\nimport NotificationsEmitterModule from './NotificationsEmitterModule';\nimport { mapNotificationResponse } from './utils/mapNotificationResponse';\n\n/**\n * A React hook always returns the notification response that was received most recently\n * (a notification response designates an interaction with a notification, such as tapping on it).\n *\n * > If you don't want to use a hook, you can use `Notifications.getLastNotificationResponseAsync()` instead.\n *\n * @return The hook may return one of these three types/values:\n * - `undefined` - until we're sure of what to return,\n * - `null` - if no notification response has been received yet,\n * - a [`NotificationResponse`](#notificationresponse) object - if a notification response was received.\n *\n * @example\n * Responding to a notification tap by opening a URL that could be put into the notification's `data`\n * (opening the URL is your responsibility and is not a part of the `expo-notifications` API):\n * ```jsx\n * import * as Notifications from 'expo-notifications';\n * import { Linking } from 'react-native';\n *\n * export default function App() {\n * const lastNotificationResponse = Notifications.useLastNotificationResponse();\n * React.useEffect(() => {\n * if (\n * lastNotificationResponse &&\n * lastNotificationResponse.notification.request.content.data.url &&\n * lastNotificationResponse.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER\n * ) {\n * Linking.openURL(lastNotificationResponse.notification.request.content.data.url);\n * }\n * }, [lastNotificationResponse]);\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport default function useLastNotificationResponse() {\n const [lastNotificationResponse, setLastNotificationResponse] = useState<\n NotificationResponse | null | undefined\n >(undefined);\n\n // useLayoutEffect ensures the listener is registered as soon as possible\n useLayoutEffect(() => {\n const subscription = addNotificationResponseReceivedListener((response) => {\n const mappedResponse = mapNotificationResponse(response);\n setLastNotificationResponse(mappedResponse);\n });\n return () => {\n subscription.remove();\n };\n }, []);\n\n // On each mount of this hook we fetch last notification response\n // from the native module which is an \"always active listener\"\n // and always returns the most recent response.\n useEffect(() => {\n NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {\n setLastNotificationResponse(response);\n });\n }, []);\n\n return lastNotificationResponse;\n}\n"]}
1
+ {"version":3,"file":"useLastNotificationResponse.js","sourceRoot":"","sources":["../src/useLastNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGlD,OAAO,EACL,uCAAuC,EACvC,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAIhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B;IACjD,MAAM,CAAC,wBAAwB,EAAE,2BAA2B,CAAC,GAC3D,QAAQ,CAA4B,SAAS,CAAC,CAAC;IAEjD,oFAAoF;IACpF,yCAAyC;IACzC,MAAM,mBAAmB,GAAG,CAC1B,YAAuC,EACvC,WAAsC,EACtC,EAAE;QACF,+DAA+D;QAC/D,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO,YAAY,CAAC;SACrB;QACD,8FAA8F;QAC9F,IAAI,CAAC,YAAY,EAAE;YACjB,OAAO,WAAW,CAAC;SACpB;QACD,OAAO,YAAY,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU;YACjD,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU;YAC3C,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,YAAY,CAAC;IACnB,CAAC,CAAC;IAEF,yEAAyE;IACzE,eAAe,CAAC,GAAG,EAAE;QACnB,2FAA2F;QAC3F,sBAAsB;QACtB,gCAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CACrD,2BAA2B,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,mBAAmB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAC3F,CAAC;QAEF,kFAAkF;QAClF,MAAM,YAAY,GAAG,uCAAuC,CAAC,CAAC,QAAQ,EAAE,EAAE,CACxE,2BAA2B,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,mBAAmB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAC3F,CAAC;QACF,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,wBAAwB,CAAC;AAClC,CAAC","sourcesContent":["import { useLayoutEffect, useState } from 'react';\n\nimport { NotificationResponse } from './Notifications.types';\nimport {\n addNotificationResponseReceivedListener,\n getLastNotificationResponseAsync,\n} from './NotificationsEmitter';\n\ntype MaybeNotificationResponse = NotificationResponse | null | undefined;\n\n/**\n * A React hook always returns the notification response that was received most recently\n * (a notification response designates an interaction with a notification, such as tapping on it).\n *\n * > If you don't want to use a hook, you can use `Notifications.getLastNotificationResponseAsync()` instead.\n *\n * @return The hook may return one of these three types/values:\n * - `undefined` - until we're sure of what to return,\n * - `null` - if no notification response has been received yet,\n * - a [`NotificationResponse`](#notificationresponse) object - if a notification response was received.\n *\n * @example\n * Responding to a notification tap by opening a URL that could be put into the notification's `data`\n * (opening the URL is your responsibility and is not a part of the `expo-notifications` API):\n * ```jsx\n * import * as Notifications from 'expo-notifications';\n * import { Linking } from 'react-native';\n *\n * export default function App() {\n * const lastNotificationResponse = Notifications.useLastNotificationResponse();\n * React.useEffect(() => {\n * if (\n * lastNotificationResponse &&\n * lastNotificationResponse.notification.request.content.data.url &&\n * lastNotificationResponse.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER\n * ) {\n * Linking.openURL(lastNotificationResponse.notification.request.content.data.url);\n * }\n * }, [lastNotificationResponse]);\n * return (\n * // Your app content\n * );\n * }\n * ```\n * @header listen\n */\nexport default function useLastNotificationResponse() {\n const [lastNotificationResponse, setLastNotificationResponse] =\n useState<MaybeNotificationResponse>(undefined);\n\n // Pure function that returns the new response if it is different from the previous,\n // otherwise return the previous response\n const newResponseIfNeeded = (\n prevResponse: MaybeNotificationResponse,\n newResponse: MaybeNotificationResponse\n ) => {\n // If the new response is undefined or null, no need for update\n if (!newResponse) {\n return prevResponse;\n }\n // If the previous response is undefined or null and the new response is not, we should update\n if (!prevResponse) {\n return newResponse;\n }\n return prevResponse.notification.request.identifier !==\n newResponse.notification.request.identifier\n ? newResponse\n : prevResponse;\n };\n\n // useLayoutEffect ensures the listener is registered as soon as possible\n useLayoutEffect(() => {\n // Get the last response first, in case it was set earlier (even in native code on startup)\n // before this renders\n getLastNotificationResponseAsync?.().then((response) =>\n setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))\n );\n\n // Set up listener for responses that come in, and set the last response if needed\n const subscription = addNotificationResponseReceivedListener((response) =>\n setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))\n );\n return () => {\n subscription.remove();\n };\n }, []);\n\n return lastNotificationResponse;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { NotificationResponse } from '../Notifications.types';
1
+ import { Notification, NotificationResponse } from '../Notifications.types';
2
2
  /**
3
3
  * @hidden
4
4
  *
@@ -9,13 +9,31 @@ import { NotificationResponse } from '../Notifications.types';
9
9
  * @param response The raw response passed in from native code
10
10
  * @returns the mapped response.
11
11
  */
12
- export declare const mapNotificationResponse: (response: NotificationResponse) => NotificationResponse & {
13
- notification: {
12
+ export declare const mapNotificationResponse: (response: NotificationResponse) => {
13
+ notification: Notification & {
14
14
  request: {
15
15
  content: {
16
16
  dataString?: string;
17
17
  };
18
18
  };
19
19
  };
20
+ actionIdentifier: string;
21
+ userText?: string | undefined;
22
+ };
23
+ /**
24
+ * @hidden
25
+ *
26
+ * Does any required processing of a notification from native code
27
+ * before it is passed to a notification listener.
28
+ *
29
+ * @param notification The raw notification passed in from native code
30
+ * @returns the mapped notification.
31
+ */
32
+ export declare const mapNotification: (notification: Notification) => Notification & {
33
+ request: {
34
+ content: {
35
+ dataString?: string;
36
+ };
37
+ };
20
38
  };
21
39
  //# sourceMappingURL=mapNotificationResponse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapNotificationResponse.d.ts","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,aAAc,oBAAoB;kBAEpD;QAAE,OAAO,EAAE;YAAE,OAAO,EAAE;gBAAE,UAAU,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE;CAYlE,CAAC"}
1
+ {"version":3,"file":"mapNotificationResponse.d.ts","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE5E;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,aAAc,oBAAoB;;iBAkBzD;YAAE,OAAO,EAAE;gBAAE,UAAU,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE;;;;CAbhD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,iBAAkB,YAAY;aAE7C;QAAE,OAAO,EAAE;YAAE,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;CAYhD,CAAC"}
@@ -9,17 +9,32 @@
9
9
  * @returns the mapped response.
10
10
  */
11
11
  export const mapNotificationResponse = (response) => {
12
- const mappedResponse = { ...response };
12
+ return {
13
+ ...response,
14
+ notification: mapNotification(response.notification),
15
+ };
16
+ };
17
+ /**
18
+ * @hidden
19
+ *
20
+ * Does any required processing of a notification from native code
21
+ * before it is passed to a notification listener.
22
+ *
23
+ * @param notification The raw notification passed in from native code
24
+ * @returns the mapped notification.
25
+ */
26
+ export const mapNotification = (notification) => {
27
+ const mappedNotification = { ...notification };
13
28
  try {
14
- const dataString = mappedResponse?.notification?.request?.content['dataString'];
29
+ const dataString = mappedNotification?.request?.content['dataString'];
15
30
  if (typeof dataString === 'string') {
16
- mappedResponse.notification.request.content.data = JSON.parse(dataString);
17
- delete mappedResponse.notification.request.content.dataString;
31
+ mappedNotification.request.content.data = JSON.parse(dataString);
32
+ delete mappedNotification.request.content.dataString;
18
33
  }
19
34
  }
20
35
  catch (e) {
21
- console.log(`Error in response: ${e}`);
36
+ console.log(`Error in notification: ${e}`);
22
37
  }
23
- return mappedResponse;
38
+ return mappedNotification;
24
39
  };
25
40
  //# sourceMappingURL=mapNotificationResponse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapNotificationResponse.js","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAA8B,EAAE,EAAE;IACxE,MAAM,cAAc,GAEhB,EAAE,GAAG,QAAQ,EAAE,CAAC;IACpB,IAAI;QACF,MAAM,UAAU,GAAG,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QAChF,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;YAClC,cAAc,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC1E,OAAO,cAAc,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;SAC/D;KACF;IAAC,OAAO,CAAM,EAAE;QACf,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;KACxC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC","sourcesContent":["import { NotificationResponse } from '../Notifications.types';\n\n/**\n * @hidden\n *\n * Does any required processing of a notification response from native code\n * before it is passed to a notification response listener, or to the\n * last notification response hook.\n *\n * @param response The raw response passed in from native code\n * @returns the mapped response.\n */\nexport const mapNotificationResponse = (response: NotificationResponse) => {\n const mappedResponse: NotificationResponse & {\n notification: { request: { content: { dataString?: string } } };\n } = { ...response };\n try {\n const dataString = mappedResponse?.notification?.request?.content['dataString'];\n if (typeof dataString === 'string') {\n mappedResponse.notification.request.content.data = JSON.parse(dataString);\n delete mappedResponse.notification.request.content.dataString;\n }\n } catch (e: any) {\n console.log(`Error in response: ${e}`);\n }\n return mappedResponse;\n};\n"]}
1
+ {"version":3,"file":"mapNotificationResponse.js","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAA8B,EAAE,EAAE;IACxE,OAAO;QACL,GAAG,QAAQ;QACX,YAAY,EAAE,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC;KACrD,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,YAA0B,EAAE,EAAE;IAC5D,MAAM,kBAAkB,GAEpB,EAAE,GAAG,YAAY,EAAE,CAAC;IACxB,IAAI;QACF,MAAM,UAAU,GAAG,kBAAkB,EAAE,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACtE,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;YAClC,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACjE,OAAO,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;SACtD;KACF;IAAC,OAAO,CAAM,EAAE;QACf,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC;KAC5C;IACD,OAAO,kBAAkB,CAAC;AAC5B,CAAC,CAAC","sourcesContent":["import { Notification, NotificationResponse } from '../Notifications.types';\n\n/**\n * @hidden\n *\n * Does any required processing of a notification response from native code\n * before it is passed to a notification response listener, or to the\n * last notification response hook.\n *\n * @param response The raw response passed in from native code\n * @returns the mapped response.\n */\nexport const mapNotificationResponse = (response: NotificationResponse) => {\n return {\n ...response,\n notification: mapNotification(response.notification),\n };\n};\n\n/**\n * @hidden\n *\n * Does any required processing of a notification from native code\n * before it is passed to a notification listener.\n *\n * @param notification The raw notification passed in from native code\n * @returns the mapped notification.\n */\nexport const mapNotification = (notification: Notification) => {\n const mappedNotification: Notification & {\n request: { content: { dataString?: string } };\n } = { ...notification };\n try {\n const dataString = mappedNotification?.request?.content['dataString'];\n if (typeof dataString === 'string') {\n mappedNotification.request.content.data = JSON.parse(dataString);\n delete mappedNotification.request.content.dataString;\n }\n } catch (e: any) {\n console.log(`Error in notification: ${e}`);\n }\n return mappedNotification;\n};\n"]}
@@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
12
12
 
13
13
  - (void)addDelegate:(id<EXNotificationsDelegate>)delegate;
14
14
  - (void)removeDelegate:(id<EXNotificationsDelegate>)delegate;
15
+ - (nullable UNNotificationResponse *)lastNotificationResponse;
16
+ - (void)setLastNotificationResponse:(nullable UNNotificationResponse *)response;
15
17
 
16
18
  @end
17
19
 
@@ -24,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN
24
26
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
25
27
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification;
26
28
 
29
+ @property (nonatomic, strong, nullable) UNNotificationResponse *lastNotificationResponse;
30
+
27
31
  @end
28
32
 
29
33
  NS_ASSUME_NONNULL_END
@@ -123,6 +123,8 @@ EX_REGISTER_SINGLETON_MODULE(NotificationCenterDelegate);
123
123
 
124
124
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
125
125
  {
126
+ // Save last response here for use by EXNotificationsEmitter
127
+ self.lastNotificationResponse = response;
126
128
  // Save response to pending responses array if none of the handlers will handle it.
127
129
  BOOL responseWillBeHandledByAppropriateDelegate = NO;
128
130
  for (int i = 0; i < _delegates.count; i++) {
@@ -15,8 +15,6 @@
15
15
 
16
16
  @property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
17
17
 
18
- @property (nonatomic, strong) UNNotificationResponse *lastNotificationResponse;
19
-
20
18
  @end
21
19
 
22
20
  @implementation EXNotificationsEmitter
@@ -26,7 +24,8 @@ EX_EXPORT_MODULE(ExpoNotificationsEmitter);
26
24
  EX_EXPORT_METHOD_AS(getLastNotificationResponseAsync,
27
25
  getLastNotificationResponseAsyncWithResolver:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject)
28
26
  {
29
- resolve(_lastNotificationResponse ? [self serializedNotificationResponse:_lastNotificationResponse] : [NSNull null]);
27
+ UNNotificationResponse* lastResponse = _notificationCenterDelegate.lastNotificationResponse;
28
+ resolve(lastResponse ? [self serializedNotificationResponse:lastResponse] : [NSNull null]);
30
29
  }
31
30
 
32
31
  # pragma mark - EXModuleRegistryConsumer
@@ -77,7 +76,7 @@ EX_EXPORT_METHOD_AS(getLastNotificationResponseAsync,
77
76
 
78
77
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
79
78
  {
80
- _lastNotificationResponse = response;
79
+ _notificationCenterDelegate.lastNotificationResponse = response;
81
80
  [self sendEventWithName:onDidReceiveNotificationResponse body:[self serializedNotificationResponse:response]];
82
81
  completionHandler();
83
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-notifications",
3
- "version": "0.28.12",
3
+ "version": "0.28.14",
4
4
  "description": "Notifications module",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "peerDependencies": {
56
56
  "expo": "*"
57
57
  },
58
- "gitHead": "e4d9b651e6693856e4ffe674490f6463e34f3ecc"
58
+ "gitHead": "10eaf729822bf46ab821a000eb961cf8df40b35b"
59
59
  }
@@ -2,7 +2,7 @@ import { EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-co
2
2
 
3
3
  import { Notification, NotificationResponse } from './Notifications.types';
4
4
  import NotificationsEmitterModule from './NotificationsEmitterModule';
5
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
5
+ import { mapNotification, mapNotificationResponse } from './utils/mapNotificationResponse';
6
6
 
7
7
  // Web uses SyntheticEventEmitter
8
8
  const emitter = new EventEmitter(NotificationsEmitterModule);
@@ -41,7 +41,13 @@ export const DEFAULT_ACTION_IDENTIFIER = 'expo.modules.notifications.actions.DEF
41
41
  export function addNotificationReceivedListener(
42
42
  listener: (event: Notification) => void
43
43
  ): Subscription {
44
- return emitter.addListener<Notification>(didReceiveNotificationEventName, listener);
44
+ return emitter.addListener<Notification>(
45
+ didReceiveNotificationEventName,
46
+ (notification: Notification) => {
47
+ const mappedNotification = mapNotification(notification);
48
+ listener(mappedNotification);
49
+ }
50
+ );
45
51
  }
46
52
 
47
53
  /**
@@ -1,9 +1,12 @@
1
- import { useEffect, useLayoutEffect, useState } from 'react';
1
+ import { useLayoutEffect, useState } from 'react';
2
2
 
3
3
  import { NotificationResponse } from './Notifications.types';
4
- import { addNotificationResponseReceivedListener } from './NotificationsEmitter';
5
- import NotificationsEmitterModule from './NotificationsEmitterModule';
6
- import { mapNotificationResponse } from './utils/mapNotificationResponse';
4
+ import {
5
+ addNotificationResponseReceivedListener,
6
+ getLastNotificationResponseAsync,
7
+ } from './NotificationsEmitter';
8
+
9
+ type MaybeNotificationResponse = NotificationResponse | null | undefined;
7
10
 
8
11
  /**
9
12
  * A React hook always returns the notification response that was received most recently
@@ -42,29 +45,45 @@ import { mapNotificationResponse } from './utils/mapNotificationResponse';
42
45
  * @header listen
43
46
  */
44
47
  export default function useLastNotificationResponse() {
45
- const [lastNotificationResponse, setLastNotificationResponse] = useState<
46
- NotificationResponse | null | undefined
47
- >(undefined);
48
+ const [lastNotificationResponse, setLastNotificationResponse] =
49
+ useState<MaybeNotificationResponse>(undefined);
50
+
51
+ // Pure function that returns the new response if it is different from the previous,
52
+ // otherwise return the previous response
53
+ const newResponseIfNeeded = (
54
+ prevResponse: MaybeNotificationResponse,
55
+ newResponse: MaybeNotificationResponse
56
+ ) => {
57
+ // If the new response is undefined or null, no need for update
58
+ if (!newResponse) {
59
+ return prevResponse;
60
+ }
61
+ // If the previous response is undefined or null and the new response is not, we should update
62
+ if (!prevResponse) {
63
+ return newResponse;
64
+ }
65
+ return prevResponse.notification.request.identifier !==
66
+ newResponse.notification.request.identifier
67
+ ? newResponse
68
+ : prevResponse;
69
+ };
48
70
 
49
71
  // useLayoutEffect ensures the listener is registered as soon as possible
50
72
  useLayoutEffect(() => {
51
- const subscription = addNotificationResponseReceivedListener((response) => {
52
- const mappedResponse = mapNotificationResponse(response);
53
- setLastNotificationResponse(mappedResponse);
54
- });
73
+ // Get the last response first, in case it was set earlier (even in native code on startup)
74
+ // before this renders
75
+ getLastNotificationResponseAsync?.().then((response) =>
76
+ setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))
77
+ );
78
+
79
+ // Set up listener for responses that come in, and set the last response if needed
80
+ const subscription = addNotificationResponseReceivedListener((response) =>
81
+ setLastNotificationResponse((prevResponse) => newResponseIfNeeded(prevResponse, response))
82
+ );
55
83
  return () => {
56
84
  subscription.remove();
57
85
  };
58
86
  }, []);
59
87
 
60
- // On each mount of this hook we fetch last notification response
61
- // from the native module which is an "always active listener"
62
- // and always returns the most recent response.
63
- useEffect(() => {
64
- NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {
65
- setLastNotificationResponse(response);
66
- });
67
- }, []);
68
-
69
88
  return lastNotificationResponse;
70
89
  }
@@ -1,4 +1,4 @@
1
- import { NotificationResponse } from '../Notifications.types';
1
+ import { Notification, NotificationResponse } from '../Notifications.types';
2
2
 
3
3
  /**
4
4
  * @hidden
@@ -11,17 +11,33 @@ import { NotificationResponse } from '../Notifications.types';
11
11
  * @returns the mapped response.
12
12
  */
13
13
  export const mapNotificationResponse = (response: NotificationResponse) => {
14
- const mappedResponse: NotificationResponse & {
15
- notification: { request: { content: { dataString?: string } } };
16
- } = { ...response };
14
+ return {
15
+ ...response,
16
+ notification: mapNotification(response.notification),
17
+ };
18
+ };
19
+
20
+ /**
21
+ * @hidden
22
+ *
23
+ * Does any required processing of a notification from native code
24
+ * before it is passed to a notification listener.
25
+ *
26
+ * @param notification The raw notification passed in from native code
27
+ * @returns the mapped notification.
28
+ */
29
+ export const mapNotification = (notification: Notification) => {
30
+ const mappedNotification: Notification & {
31
+ request: { content: { dataString?: string } };
32
+ } = { ...notification };
17
33
  try {
18
- const dataString = mappedResponse?.notification?.request?.content['dataString'];
34
+ const dataString = mappedNotification?.request?.content['dataString'];
19
35
  if (typeof dataString === 'string') {
20
- mappedResponse.notification.request.content.data = JSON.parse(dataString);
21
- delete mappedResponse.notification.request.content.dataString;
36
+ mappedNotification.request.content.data = JSON.parse(dataString);
37
+ delete mappedNotification.request.content.dataString;
22
38
  }
23
39
  } catch (e: any) {
24
- console.log(`Error in response: ${e}`);
40
+ console.log(`Error in notification: ${e}`);
25
41
  }
26
- return mappedResponse;
42
+ return mappedNotification;
27
43
  };