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.
- package/CHANGELOG.md +13 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java +75 -21
- package/android/src/main/java/expo/modules/notifications/notifications/debug/DebugLogging.kt +86 -0
- package/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt +10 -3
- package/android/src/main/java/expo/modules/notifications/service/NotificationsService.kt +7 -1
- package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoNotificationLifecycleListener.java +3 -9
- package/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +8 -1
- package/build/NotificationsEmitter.d.ts.map +1 -1
- package/build/NotificationsEmitter.js +5 -2
- package/build/NotificationsEmitter.js.map +1 -1
- package/build/useLastNotificationResponse.d.ts +3 -1
- package/build/useLastNotificationResponse.d.ts.map +1 -1
- package/build/useLastNotificationResponse.js +23 -16
- package/build/useLastNotificationResponse.js.map +1 -1
- package/build/utils/mapNotificationResponse.d.ts +21 -3
- package/build/utils/mapNotificationResponse.d.ts.map +1 -1
- package/build/utils/mapNotificationResponse.js +21 -6
- package/build/utils/mapNotificationResponse.js.map +1 -1
- package/ios/EXNotifications/Notifications/EXNotificationCenterDelegate.h +4 -0
- package/ios/EXNotifications/Notifications/EXNotificationCenterDelegate.m +2 -0
- package/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m +3 -4
- package/package.json +2 -2
- package/src/NotificationsEmitter.ts +8 -2
- package/src/useLastNotificationResponse.ts +39 -20
- 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
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '0.28.
|
|
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.
|
|
17
|
+
versionName '0.28.14'
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
buildFeatures {
|
package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
242
|
+
Bundle serializedTrigger = new Bundle();
|
|
243
|
+
serializedTrigger.putString("type", "push");
|
|
244
|
+
serializedTrigger.putString("channelId", extras.getString("channelId"));
|
|
205
245
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
251
|
+
Bundle serializedNotification = new Bundle();
|
|
252
|
+
serializedNotification.putLong("date", extras.getLong("google.sent_time"));
|
|
253
|
+
serializedNotification.putBundle("request", serializedRequest);
|
|
214
254
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
255
|
+
Bundle serializedResponse = new Bundle();
|
|
256
|
+
serializedResponse.putString("actionIdentifier", "expo.modules.notifications.actions.DEFAULT");
|
|
257
|
+
serializedResponse.putBundle("notification", serializedNotification);
|
|
218
258
|
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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;
|
|
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():
|
|
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;
|
|
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 {
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,
|
|
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) =>
|
|
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;
|
|
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
|
-
|
|
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 =
|
|
29
|
+
const dataString = mappedNotification?.request?.content['dataString'];
|
|
15
30
|
if (typeof dataString === 'string') {
|
|
16
|
-
|
|
17
|
-
delete
|
|
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
|
|
36
|
+
console.log(`Error in notification: ${e}`);
|
|
22
37
|
}
|
|
23
|
-
return
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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>(
|
|
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 {
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { NotificationResponse } from './Notifications.types';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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] =
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
34
|
+
const dataString = mappedNotification?.request?.content['dataString'];
|
|
19
35
|
if (typeof dataString === 'string') {
|
|
20
|
-
|
|
21
|
-
delete
|
|
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
|
|
40
|
+
console.log(`Error in notification: ${e}`);
|
|
25
41
|
}
|
|
26
|
-
return
|
|
42
|
+
return mappedNotification;
|
|
27
43
|
};
|