react-native-notify-kit 10.3.0 → 10.3.2

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 (25) hide show
  1. package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +1 -3
  2. package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +2 -1
  3. package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerCurrentBehaviorTest.java +3 -3
  4. package/android/src/test/java/app/notifee/core/NotificationManagerCreateTriggerNotificationCurrentBehaviorTest.java +12 -6
  5. package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java +2 -1
  6. package/android/src/test/java/app/notifee/core/NotificationReceiverHandlerInitialNotificationTest.java +2 -1
  7. package/android/src/test/java/app/notifee/core/model/ChannelModelTest.java +5 -4
  8. package/android/src/test/java/app/notifee/core/model/IntervalTriggerModelTest.java +2 -1
  9. package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelTest.java +5 -4
  10. package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelTest.java +4 -3
  11. package/android/src/test/java/app/notifee/core/utility/BundleValueReaderTest.java +2 -1
  12. package/android/src/test/java/app/notifee/core/utility/ObjectUtilsTest.java +1 -2
  13. package/android/src/test/java/io/invertase/notifee/HeadlessTaskReactHostTest.java +6 -5
  14. package/android/src/test/java/io/invertase/notifee/NotifeeEventSubscriberRoutingTest.java +19 -10
  15. package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsDrawerTest.java +1 -1
  16. package/cli/dist/templates/templates/NotificationService.swift.tmpl +33 -6
  17. package/dist/version.d.ts +1 -1
  18. package/dist/version.js +1 -1
  19. package/ios/NotifeeCore/NotifeeCore+NSURLSession.m +20 -3
  20. package/ios/NotifeeCore/NotifeeCore.m +2 -1
  21. package/ios/NotifeeCore/NotifeeCoreExtensionHelper.h +2 -2
  22. package/ios/NotifeeCore/NotifeeCoreExtensionHelper.m +138 -32
  23. package/ios/NotifeeCore/NotifeeCoreUtil.m +13 -5
  24. package/package.json +1 -1
  25. package/src/version.ts +1 -1
@@ -471,9 +471,7 @@ public class NotificationAndroidModel {
471
471
 
472
472
  int importance =
473
473
  BundleValueReader.getIntPreserving(
474
- mNotificationAndroidBundle,
475
- "importance",
476
- NotificationManagerCompat.IMPORTANCE_DEFAULT);
474
+ mNotificationAndroidBundle, "importance", NotificationManagerCompat.IMPORTANCE_DEFAULT);
477
475
  switch (importance) {
478
476
  case NotificationManagerCompat.IMPORTANCE_HIGH:
479
477
  return NotificationCompat.PRIORITY_HIGH;
@@ -9,6 +9,7 @@ import android.os.Build
9
9
  import android.os.Bundle
10
10
  import app.notifee.core.Logger
11
11
  import app.notifee.core.Notifee
12
+ import app.notifee.core.WarmupHelper
12
13
  import com.facebook.react.bridge.Arguments
13
14
  import com.facebook.react.bridge.Promise
14
15
  import com.facebook.react.bridge.ReactApplicationContext
@@ -331,7 +332,7 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
331
332
  executor.submit {
332
333
  android.os.Trace.beginSection("notifee:prewarm")
333
334
  try {
334
- app.notifee.core.WarmupHelper.runWarmup(context)
335
+ WarmupHelper.runWarmup(context)
335
336
  promise.resolve(null)
336
337
  } catch (t: Throwable) {
337
338
  // Best-effort semantics: warmup failures don't reject the promise.
@@ -199,7 +199,8 @@ public class NotifeeAlarmManagerCurrentBehaviorTest {
199
199
 
200
200
  @Test
201
201
  public void rescheduleNotification_otherInteger_completesWithoutRoomUpdate() throws Exception {
202
- runRescheduleNotificationExpectingNoUpdate(rescheduleTriggerWithType(Integer.valueOf(99), true));
202
+ runRescheduleNotificationExpectingNoUpdate(
203
+ rescheduleTriggerWithType(Integer.valueOf(99), true));
203
204
  }
204
205
 
205
206
  @Test
@@ -256,8 +257,7 @@ public class NotifeeAlarmManagerCurrentBehaviorTest {
256
257
  String id = "am-type-reboot-" + suffix;
257
258
  Bundle triggerBundle = rescheduleTriggerWithType(typeValue, true);
258
259
  seedDisplayRow(id, triggerBundle);
259
- long originalTimestamp =
260
- triggerTimestamp(repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
260
+ long originalTimestamp = triggerTimestamp(repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
261
261
 
262
262
  runRescheduleNotificationsFromRoom();
263
263
 
@@ -63,7 +63,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
63
63
  public void createTriggerNotification_missingType_dispatchesTimestampBranch() throws Exception {
64
64
  Bundle triggerBundle = new Bundle();
65
65
 
66
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
66
+ assertCompletes(
67
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
67
68
 
68
69
  verifyTimestampBranch(triggerBundle);
69
70
  verifyIntervalBranchNeverCalled();
@@ -75,7 +76,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
75
76
  Bundle triggerBundle = new Bundle();
76
77
  triggerBundle.putString("type", null);
77
78
 
78
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
79
+ assertCompletes(
80
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
79
81
 
80
82
  verifyTimestampBranch(triggerBundle);
81
83
  verifyIntervalBranchNeverCalled();
@@ -85,7 +87,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
85
87
  public void createTriggerNotification_integerZero_dispatchesTimestampBranch() throws Exception {
86
88
  Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(0));
87
89
 
88
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
90
+ assertCompletes(
91
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
89
92
 
90
93
  verifyTimestampBranch(triggerBundle);
91
94
  verifyIntervalBranchNeverCalled();
@@ -95,7 +98,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
95
98
  public void createTriggerNotification_integerOne_dispatchesIntervalBranch() throws Exception {
96
99
  Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(1));
97
100
 
98
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
101
+ assertCompletes(
102
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
99
103
 
100
104
  verifyIntervalBranch(triggerBundle);
101
105
  verifyTimestampBranchNeverCalled();
@@ -105,7 +109,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
105
109
  public void createTriggerNotification_otherInteger_completesWithoutScheduling() throws Exception {
106
110
  Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(99));
107
111
 
108
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
112
+ assertCompletes(
113
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
109
114
 
110
115
  verifyTimestampBranchNeverCalled();
111
116
  verifyIntervalBranchNeverCalled();
@@ -115,7 +120,8 @@ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
115
120
  public void createTriggerNotification_doubleType_truncatesBeforeDispatch() throws Exception {
116
121
  Bundle triggerBundle = triggerBundleWithType(Double.valueOf(1.9d));
117
122
 
118
- assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
123
+ assertCompletes(
124
+ NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
119
125
 
120
126
  verifyIntervalBranch(triggerBundle);
121
127
  verifyTimestampBranchNeverCalled();
@@ -106,7 +106,8 @@ public class NotificationManagerPressActionOptOutTest {
106
106
 
107
107
  private static Notification displayNotification(NotificationModel notificationModel)
108
108
  throws Exception {
109
- ListenableFuture<Void> future = NotificationManager.displayNotification(notificationModel, null);
109
+ ListenableFuture<Void> future =
110
+ NotificationManager.displayNotification(notificationModel, null);
110
111
  future.get(5, TimeUnit.SECONDS);
111
112
 
112
113
  Context context = RuntimeEnvironment.getApplication();
@@ -92,7 +92,8 @@ public class NotificationReceiverHandlerInitialNotificationTest {
92
92
  assertNotNull(
93
93
  "Android 12+ handler currently posts InitialNotificationEvent for no-launch ACTION_PRESS",
94
94
  initialNotificationEvent);
95
- assertEquals("handler-action-no-launch", initialNotificationEvent.getNotificationModel().getId());
95
+ assertEquals(
96
+ "handler-action-no-launch", initialNotificationEvent.getNotificationModel().getId());
96
97
  assertPressAction(initialNotificationEvent.getExtras(), "reply", null, null);
97
98
  }
98
99
 
@@ -33,7 +33,8 @@ public class ChannelModelTest {
33
33
  integerBundle.putInt("visibility", NotificationCompat.VISIBILITY_SECRET);
34
34
  ChannelModel integerModel = ChannelModel.fromBundle(integerBundle);
35
35
 
36
- assertEquals(NotificationManagerCompat.IMPORTANCE_HIGH, integerModel.getImportance().intValue());
36
+ assertEquals(
37
+ NotificationManagerCompat.IMPORTANCE_HIGH, integerModel.getImportance().intValue());
37
38
  assertEquals(NotificationCompat.VISIBILITY_SECRET, integerModel.getVisibility());
38
39
 
39
40
  Bundle doubleBundle = new Bundle();
@@ -83,7 +84,8 @@ public class ChannelModelTest {
83
84
  Bundle validBundle = new Bundle();
84
85
  putRawParcelableArrayList(validBundle, "vibrationPattern", vibrationPattern);
85
86
 
86
- assertArrayEquals(new long[] {100L, 200L}, ChannelModel.fromBundle(validBundle).getVibrationPattern());
87
+ assertArrayEquals(
88
+ new long[] {100L, 200L}, ChannelModel.fromBundle(validBundle).getVibrationPattern());
87
89
 
88
90
  ArrayList<Object> invalidPattern = new ArrayList<>();
89
91
  invalidPattern.add(100L);
@@ -109,8 +111,7 @@ public class ChannelModelTest {
109
111
  }
110
112
 
111
113
  @SuppressWarnings({"rawtypes", "unchecked"})
112
- private static void putRawParcelableArrayList(
113
- Bundle bundle, String key, ArrayList<?> arrayList) {
114
+ private static void putRawParcelableArrayList(Bundle bundle, String key, ArrayList<?> arrayList) {
114
115
  bundle.putParcelableArrayList(key, (ArrayList) arrayList);
115
116
  }
116
117
  }
@@ -66,6 +66,7 @@ public class IntervalTriggerModelTest {
66
66
  Bundle bundle = new Bundle();
67
67
  bundle.putString("interval", "15");
68
68
 
69
- assertThrows(ClassCastException.class, () -> IntervalTriggerModel.fromBundle(bundle).getInterval());
69
+ assertThrows(
70
+ ClassCastException.class, () -> IntervalTriggerModel.fromBundle(bundle).getInterval());
70
71
  }
71
72
  }
@@ -260,7 +260,8 @@ public class NotificationAndroidModelTest {
260
260
  Bundle validBundle = new Bundle();
261
261
  putRawParcelableArrayList(validBundle, "flags", flags);
262
262
 
263
- assertArrayEquals(new int[] {1, 2}, NotificationAndroidModel.fromBundle(validBundle).getFlags());
263
+ assertArrayEquals(
264
+ new int[] {1, 2}, NotificationAndroidModel.fromBundle(validBundle).getFlags());
264
265
 
265
266
  ArrayList<Object> invalidFlags = new ArrayList<>();
266
267
  invalidFlags.add(1L);
@@ -274,7 +275,8 @@ public class NotificationAndroidModelTest {
274
275
 
275
276
  @Test
276
277
  public void vibrationPattern_missingEmptyIntegerAndUnsupportedValues_preserveCurrentBehavior() {
277
- assertArrayEquals(new long[0], NotificationAndroidModel.fromBundle(new Bundle()).getVibrationPattern());
278
+ assertArrayEquals(
279
+ new long[0], NotificationAndroidModel.fromBundle(new Bundle()).getVibrationPattern());
278
280
 
279
281
  Bundle emptyBundle = new Bundle();
280
282
  putRawParcelableArrayList(emptyBundle, "vibrationPattern", new ArrayList<Integer>());
@@ -302,8 +304,7 @@ public class NotificationAndroidModelTest {
302
304
  }
303
305
 
304
306
  @SuppressWarnings({"rawtypes", "unchecked"})
305
- private static void putRawParcelableArrayList(
306
- Bundle bundle, String key, ArrayList<?> arrayList) {
307
+ private static void putRawParcelableArrayList(Bundle bundle, String key, ArrayList<?> arrayList) {
307
308
  bundle.putParcelableArrayList(key, (ArrayList) arrayList);
308
309
  }
309
310
  }
@@ -52,7 +52,9 @@ public class NotificationAndroidStyleModelTest {
52
52
  Bundle truncatedDoubleBundle = new Bundle();
53
53
  truncatedDoubleBundle.putDouble("type", 1.9d);
54
54
  NotificationCompat.Style truncatedStyle =
55
- NotificationAndroidStyleModel.fromBundle(truncatedDoubleBundle).getStyleTask(executor).get();
55
+ NotificationAndroidStyleModel.fromBundle(truncatedDoubleBundle)
56
+ .getStyleTask(executor)
57
+ .get();
56
58
  assertTrue(truncatedStyle instanceof NotificationCompat.BigTextStyle);
57
59
  }
58
60
 
@@ -147,8 +149,7 @@ public class NotificationAndroidStyleModelTest {
147
149
  }
148
150
 
149
151
  @SuppressWarnings({"rawtypes", "unchecked"})
150
- private static void putRawParcelableArrayList(
151
- Bundle bundle, String key, ArrayList<?> arrayList) {
152
+ private static void putRawParcelableArrayList(Bundle bundle, String key, ArrayList<?> arrayList) {
152
153
  bundle.putParcelableArrayList(key, (ArrayList) arrayList);
153
154
  }
154
155
  }
@@ -102,7 +102,8 @@ public class BundleValueReaderTest {
102
102
  NullPointerException.class,
103
103
  () -> BundleValueReader.getLongPreserving(bundle, "explicitNull", 42L));
104
104
  assertThrows(
105
- ClassCastException.class, () -> BundleValueReader.getLongPreserving(bundle, "integer", 42L));
105
+ ClassCastException.class,
106
+ () -> BundleValueReader.getLongPreserving(bundle, "integer", 42L));
106
107
  }
107
108
 
108
109
  @Test
@@ -135,8 +135,7 @@ public class ObjectUtilsTest {
135
135
  }
136
136
 
137
137
  @SuppressWarnings({"rawtypes", "unchecked"})
138
- private static void putRawParcelableArrayList(
139
- Bundle bundle, String key, ArrayList<?> arrayList) {
138
+ private static void putRawParcelableArrayList(Bundle bundle, String key, ArrayList<?> arrayList) {
140
139
  bundle.putParcelableArrayList(key, (ArrayList) arrayList);
141
140
  }
142
141
  }
@@ -25,9 +25,9 @@ import org.junit.Before;
25
25
  import org.junit.Test;
26
26
  import org.junit.runner.RunWith;
27
27
  import org.mockito.ArgumentCaptor;
28
+ import org.robolectric.RobolectricTestRunner;
28
29
  import org.robolectric.RuntimeEnvironment;
29
30
  import org.robolectric.Shadows;
30
- import org.robolectric.RobolectricTestRunner;
31
31
  import org.robolectric.annotation.Config;
32
32
  import org.robolectric.annotation.LooperMode;
33
33
 
@@ -80,15 +80,16 @@ public class HeadlessTaskReactHostTest {
80
80
  "ReactContext listener should be removed after initialization",
81
81
  0,
82
82
  application.reactHost.listeners.size());
83
- Shadows.shadowOf(Looper.getMainLooper()).idleFor(499, java.util.concurrent.TimeUnit.MILLISECONDS);
84
- verify(appRegistry, never()).startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
83
+ Shadows.shadowOf(Looper.getMainLooper())
84
+ .idleFor(499, java.util.concurrent.TimeUnit.MILLISECONDS);
85
+ verify(appRegistry, never())
86
+ .startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
85
87
 
86
88
  Shadows.shadowOf(Looper.getMainLooper()).idleFor(1, java.util.concurrent.TimeUnit.MILLISECONDS);
87
89
 
88
90
  ArgumentCaptor<Integer> taskIdCaptor = ArgumentCaptor.forClass(Integer.class);
89
91
  verify(appRegistry)
90
- .startHeadlessTask(
91
- taskIdCaptor.capture(), eq("test-headless-task"), same(copiedParams));
92
+ .startHeadlessTask(taskIdCaptor.capture(), eq("test-headless-task"), same(copiedParams));
92
93
  assertEquals(taskIdCaptor.getValue().intValue(), taskConfig.getReactTaskId());
93
94
  assertEquals(
94
95
  "source payload should be copied into the queued task",
@@ -30,9 +30,9 @@ import org.junit.Before;
30
30
  import org.junit.Test;
31
31
  import org.junit.runner.RunWith;
32
32
  import org.mockito.MockedStatic;
33
+ import org.robolectric.RobolectricTestRunner;
33
34
  import org.robolectric.RuntimeEnvironment;
34
35
  import org.robolectric.Shadows;
35
- import org.robolectric.RobolectricTestRunner;
36
36
  import org.robolectric.annotation.Config;
37
37
 
38
38
  @RunWith(RobolectricTestRunner.class)
@@ -76,8 +76,8 @@ public class NotifeeEventSubscriberRoutingTest {
76
76
  WritableMap notificationMap = mock(WritableMap.class);
77
77
  WritableMap pressActionMap = mock(WritableMap.class);
78
78
 
79
- try (MockedStatic<Arguments> arguments = mockArguments(
80
- eventMap, detailMap, notificationMap, pressActionMap)) {
79
+ try (MockedStatic<Arguments> arguments =
80
+ mockArguments(eventMap, detailMap, notificationMap, pressActionMap)) {
81
81
  new NotifeeEventSubscriber().onNotificationEvent(buildPressEvent("routing-foreground"));
82
82
  }
83
83
 
@@ -90,7 +90,8 @@ public class NotifeeEventSubscriberRoutingTest {
90
90
  verify(emitter).emit(NotifeeEventSubscriber.NOTIFICATION_EVENT_KEY, eventMap);
91
91
  verify(eventMap, never()).copy();
92
92
  assertEquals("foreground routing must not queue a headless task", 0, taskQueueSize());
93
- assertEquals("foreground routing must not start ReactHost", 0, application.reactHost.startCalls);
93
+ assertEquals(
94
+ "foreground routing must not start ReactHost", 0, application.reactHost.startCalls);
94
95
  }
95
96
 
96
97
  @Test
@@ -111,8 +112,8 @@ public class NotifeeEventSubscriberRoutingTest {
111
112
  WritableMap pressActionMap = mock(WritableMap.class);
112
113
  when(eventMap.copy()).thenReturn(queuedEventMap);
113
114
 
114
- try (MockedStatic<Arguments> arguments = mockArguments(
115
- eventMap, detailMap, notificationMap, pressActionMap)) {
115
+ try (MockedStatic<Arguments> arguments =
116
+ mockArguments(eventMap, detailMap, notificationMap, pressActionMap)) {
116
117
  new NotifeeEventSubscriber().onNotificationEvent(buildPressEvent("routing-background"));
117
118
  }
118
119
 
@@ -128,11 +129,18 @@ public class NotifeeEventSubscriberRoutingTest {
128
129
 
129
130
  assertEquals("background routing should queue exactly one headless task", 1, taskQueueSize());
130
131
  HeadlessTask.TaskConfig queuedTask = headlessTaskQueue().get(0);
131
- assertEquals(NotifeeEventSubscriber.NOTIFICATION_EVENT_KEY, queuedTask.getTaskConfig().getTaskKey());
132
+ assertEquals(
133
+ NotifeeEventSubscriber.NOTIFICATION_EVENT_KEY, queuedTask.getTaskConfig().getTaskKey());
132
134
  assertEquals(60000L, queuedTask.getTaskConfig().getTimeout());
133
135
  assertSame(queuedEventMap, queuedTask.getTaskConfig().getData());
134
- assertEquals("background routing should initialize ReactHost via reflection", 1, application.reactHost.startCalls);
135
- assertEquals("background routing should register one ReactContext listener", 1, application.reactHost.listeners.size());
136
+ assertEquals(
137
+ "background routing should initialize ReactHost via reflection",
138
+ 1,
139
+ application.reactHost.startCalls);
140
+ assertEquals(
141
+ "background routing should register one ReactContext listener",
142
+ 1,
143
+ application.reactHost.listeners.size());
136
144
  }
137
145
 
138
146
  private static MockedStatic<Arguments> mockArguments(
@@ -142,7 +150,8 @@ public class NotifeeEventSubscriberRoutingTest {
142
150
  WritableMap pressActionMap) {
143
151
  MockedStatic<Arguments> arguments = mockStatic(Arguments.class);
144
152
  arguments.when(Arguments::createMap).thenReturn(eventMap, detailMap);
145
- arguments.when(() -> Arguments.fromBundle(any(Bundle.class)))
153
+ arguments
154
+ .when(() -> Arguments.fromBundle(any(Bundle.class)))
146
155
  .thenReturn(notificationMap, pressActionMap);
147
156
  return arguments;
148
157
  }
@@ -4,8 +4,8 @@ import app.notifee.core.ContextHolder;
4
4
  import org.junit.Before;
5
5
  import org.junit.Test;
6
6
  import org.junit.runner.RunWith;
7
- import org.robolectric.RuntimeEnvironment;
8
7
  import org.robolectric.RobolectricTestRunner;
8
+ import org.robolectric.RuntimeEnvironment;
9
9
  import org.robolectric.annotation.Config;
10
10
 
11
11
  @RunWith(RobolectricTestRunner.class)
@@ -23,13 +23,21 @@ private func requestedAttachmentURLs(from userInfo: [AnyHashable: Any]) -> [Stri
23
23
  class NotificationService: UNNotificationServiceExtension {
24
24
  var contentHandler: ((UNNotificationContent) -> Void)?
25
25
  var bestAttemptContent: UNMutableNotificationContent?
26
+ private let deliveryLock = NSLock()
27
+ private var didDeliver = false
26
28
 
27
29
  override func didReceive(
28
30
  _ request: UNNotificationRequest,
29
31
  withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
30
32
  ) {
33
+ let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent
34
+
35
+ deliveryLock.lock()
36
+ didDeliver = false
31
37
  self.contentHandler = contentHandler
32
- self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
38
+ self.bestAttemptContent = mutableContent
39
+ deliveryLock.unlock()
40
+
33
41
  let requestedAttachmentUrls = requestedAttachmentURLs(from: request.content.userInfo)
34
42
 
35
43
  nseLog(
@@ -39,9 +47,9 @@ class NotificationService: UNNotificationServiceExtension {
39
47
  "urls=\(requestedAttachmentUrls.joined(separator: ","))"
40
48
  )
41
49
 
42
- guard let bestAttemptContent = bestAttemptContent else {
50
+ guard let bestAttemptContent = mutableContent else {
43
51
  nseLog("mutableCopy failed for id=\(request.identifier); delivering original content")
44
- contentHandler(request.content)
52
+ deliverOnce(request.content)
45
53
  return
46
54
  }
47
55
 
@@ -55,19 +63,38 @@ class NotificationService: UNNotificationServiceExtension {
55
63
  "deliveredAttachments=\(content.attachments.count) " +
56
64
  "identifiers=\(deliveredAttachmentIds)"
57
65
  )
58
- self?.contentHandler?(content)
66
+ self?.deliverOnce(content)
59
67
  }
60
68
  )
61
69
  }
62
70
 
71
+ private func deliverOnce(_ content: UNNotificationContent) {
72
+ var handler: ((UNNotificationContent) -> Void)?
73
+
74
+ deliveryLock.lock()
75
+ if !didDeliver {
76
+ didDeliver = true
77
+ handler = contentHandler
78
+ contentHandler = nil
79
+ bestAttemptContent = nil
80
+ }
81
+ deliveryLock.unlock()
82
+
83
+ handler?(content)
84
+ }
85
+
63
86
  override func serviceExtensionTimeWillExpire() {
64
- if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
87
+ deliveryLock.lock()
88
+ let content = bestAttemptContent
89
+ deliveryLock.unlock()
90
+
91
+ if let bestAttemptContent = content {
65
92
  nseLog(
66
93
  "serviceExtensionTimeWillExpire id=\(bestAttemptContent.userInfo["gcm.message_id"] ?? "n/a") " +
67
94
  "title=\(bestAttemptContent.title) " +
68
95
  "deliveredAttachments=\(bestAttemptContent.attachments.count)"
69
96
  )
70
- contentHandler(bestAttemptContent)
97
+ deliverOnce(bestAttemptContent)
71
98
  }
72
99
  }
73
100
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "10.3.0";
1
+ export declare const version = "10.3.2";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by genversion.
2
- export const version = '10.3.0';
2
+ export const version = '10.3.2';
3
3
  //# sourceMappingURL=version.js.map
@@ -18,18 +18,35 @@
18
18
  #import "NotifeeCore+NSURLSession.h"
19
19
  #import "NotifeeCoreDownloadDelegate.h"
20
20
 
21
+ static NSTimeInterval const kNotifeeAttachmentDownloadTimeoutInterval = 25.0;
22
+
21
23
  @implementation NotifeeCoreNSURLSession
22
24
 
25
+ + (NSURLSessionConfiguration *)attachmentDownloadSessionConfiguration {
26
+ NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
27
+ config.timeoutIntervalForRequest = kNotifeeAttachmentDownloadTimeoutInterval;
28
+ config.timeoutIntervalForResource = kNotifeeAttachmentDownloadTimeoutInterval;
29
+ return config;
30
+ }
31
+
32
+ + (nullable NSString *)fileExtensionFromSuggestedFilename:(nullable NSString *)suggestedFilename {
33
+ NSString *suggestedPathExtension = [suggestedFilename pathExtension];
34
+ if (suggestedPathExtension.length == 0) {
35
+ return nil;
36
+ }
37
+
38
+ return [NSString stringWithFormat:@".%@", suggestedPathExtension];
39
+ }
40
+
23
41
  + (NSString *)downloadItemAtURL:(NSURL *)url toFile:(NSString *)localPath error:(NSError **)error {
24
42
  NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
43
+ request.timeoutInterval = kNotifeeAttachmentDownloadTimeoutInterval;
25
44
 
26
45
  NotifeeCoreDownloadDelegate *delegate =
27
46
  [[NotifeeCoreDownloadDelegate alloc] initWithFilePath:localPath];
28
47
 
29
- // The session is created with the defaultSessionConfiguration
30
- // default timeoutIntervalForRequest is 60 seconds.
31
48
  NSURLSession *session =
32
- [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
49
+ [NSURLSession sessionWithConfiguration:[self attachmentDownloadSessionConfiguration]
33
50
  delegate:delegate
34
51
  delegateQueue:nil];
35
52
 
@@ -1191,7 +1191,8 @@ typedef NS_ENUM(NSInteger, NotifeeCoreRollingErrorCode) {
1191
1191
  dispatch_async(dispatch_get_main_queue(), ^{
1192
1192
  BOOL isApplicationActive = NO;
1193
1193
  if (![NotifeeCoreUtil isAppExtension]) {
1194
- UIApplication *application = [NotifeeCoreUtil notifeeUIApplication];
1194
+ UIApplication *application =
1195
+ (UIApplication *)[NotifeeCoreUtil notifeeUIApplication];
1195
1196
  if (application != nil) {
1196
1197
  isApplicationActive =
1197
1198
  application.applicationState == UIApplicationStateActive;
@@ -24,8 +24,8 @@ static NSString *const kPayloadOptionsName = @"notifee_options";
24
24
  static NSString *const kPayloadOptionsImageURLName = @"image";
25
25
 
26
26
  @interface NotifeeCoreExtensionHelper : NSObject
27
- @property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *c);
28
- @property(nonatomic, strong) UNMutableNotificationContent *modifiedContent;
27
+ @property(nonatomic, copy, nullable) void (^contentHandler)(UNNotificationContent *c);
28
+ @property(nonatomic, strong, nullable) UNMutableNotificationContent *modifiedContent;
29
29
 
30
30
  + (NotifeeCoreExtensionHelper *)instance NS_SWIFT_NAME(serviceExtension());
31
31
 
@@ -23,30 +23,56 @@
23
23
  static NSString *const kNoExtension = @"";
24
24
  static NSString *const kImagePathPrefix = @"image/";
25
25
 
26
- @implementation NotifeeCoreExtensionHelper
27
- + (NotifeeCoreExtensionHelper *)instance {
28
- static dispatch_once_t once;
29
- static NotifeeCoreExtensionHelper *instance;
30
- dispatch_once(&once, ^{
31
- instance = [[self alloc] init];
32
- });
26
+ @interface NotifeeCoreExtensionHelper ()
27
+ - (NSMutableDictionary *)parseNotifeeOptions:(id)payload;
28
+ - (void)loadAttachment:(NSDictionary *)attachmentDict
29
+ completionHandler:(void (^)(UNNotificationAttachment *))completionHandler;
30
+ @end
33
31
 
34
- return instance;
35
- }
32
+ @interface NotifeeCoreExtensionRequestContext : NSObject
33
+ @property(nonatomic, strong) NotifeeCoreExtensionHelper *helper;
34
+ @property(nonatomic, copy) void (^contentHandler)(UNNotificationContent *content);
35
+ @property(nonatomic, strong) UNMutableNotificationContent *modifiedContent;
36
+ @property(nonatomic, assign) BOOL notificationDelivered;
36
37
 
37
- - (void)populateNotificationContent:(UNNotificationRequest *_Nullable)request
38
- withContent:(UNMutableNotificationContent *)content
39
- withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
40
- self.contentHandler = [contentHandler copy];
41
- self.modifiedContent = content;
38
+ - (instancetype)initWithHelper:(NotifeeCoreExtensionHelper *)helper
39
+ content:(UNMutableNotificationContent *)content
40
+ contentHandler:(void (^)(UNNotificationContent *content))contentHandler;
41
+ - (void)populateNotificationContentWithRequest:(UNNotificationRequest *_Nullable)request;
42
+ - (void)processCommunicationData:(NSMutableDictionary *)options;
43
+ - (void)handleAttachmentsAndDeliverNotificaiton:(NSMutableDictionary *)options;
44
+ - (void)deliverNotification;
45
+ @end
46
+
47
+ @implementation NotifeeCoreExtensionRequestContext
48
+
49
+ - (instancetype)initWithHelper:(NotifeeCoreExtensionHelper *)helper
50
+ content:(UNMutableNotificationContent *)content
51
+ contentHandler:(void (^)(UNNotificationContent *content))contentHandler {
52
+ self = [super init];
53
+ if (self != nil) {
54
+ self.helper = helper;
55
+ self.contentHandler = [contentHandler copy];
56
+ self.modifiedContent = content;
57
+ self.notificationDelivered = NO;
58
+ }
42
59
 
43
- if (!content.userInfo[@"notifee_options"]) {
60
+ return self;
61
+ }
62
+
63
+ - (void)populateNotificationContentWithRequest:(UNNotificationRequest *_Nullable)request {
64
+ id notifeeOptionsPayload = self.modifiedContent.userInfo[kPayloadOptionsName];
65
+ if (!notifeeOptionsPayload) {
44
66
  [self deliverNotification];
45
67
  return;
46
68
  }
47
69
 
48
- // fcm: apns: { payload: {notifee_options: {} } }
49
- NSMutableDictionary *options = [self.modifiedContent.userInfo[@"notifee_options"] mutableCopy];
70
+ // fcm: apns: { payload: {notifee_options: "{}" } }
71
+ NSMutableDictionary *options = [self.helper parseNotifeeOptions:notifeeOptionsPayload];
72
+ if (options == nil) {
73
+ [self deliverNotification];
74
+ return;
75
+ }
50
76
 
51
77
  options[@"remote"] = @YES;
52
78
 
@@ -60,7 +86,7 @@ static NSString *const kImagePathPrefix = @"image/";
60
86
  options[@"id"] = request.identifier;
61
87
  }
62
88
 
63
- if (options[@"title"] == nil && content.title != nil) {
89
+ if (options[@"title"] == nil && self.modifiedContent.title != nil) {
64
90
  options[@"title"] = self.modifiedContent.title;
65
91
  }
66
92
 
@@ -135,14 +161,100 @@ static NSString *const kImagePathPrefix = @"image/";
135
161
  }
136
162
 
137
163
  // Attempt to download attachment
138
- [self loadAttachment:attachmentDict
139
- completionHandler:^(UNNotificationAttachment *attachment) {
140
- if (attachment != nil) {
141
- self.modifiedContent.attachments = @[ attachment ];
142
- }
143
-
144
- [self deliverNotification];
145
- }];
164
+ [self.helper loadAttachment:attachmentDict
165
+ completionHandler:^(UNNotificationAttachment *attachment) {
166
+ if (attachment != nil) {
167
+ @synchronized(self) {
168
+ if (!self.notificationDelivered && self.modifiedContent != nil) {
169
+ self.modifiedContent.attachments = @[ attachment ];
170
+ }
171
+ }
172
+ }
173
+
174
+ [self deliverNotification];
175
+ }];
176
+ }
177
+
178
+ - (void)deliverNotification {
179
+ void (^contentHandler)(UNNotificationContent *) = nil;
180
+ UNNotificationContent *modifiedContent = nil;
181
+
182
+ @synchronized(self) {
183
+ if (self.notificationDelivered || self.contentHandler == nil) {
184
+ return;
185
+ }
186
+
187
+ contentHandler = [self.contentHandler copy];
188
+ modifiedContent = self.modifiedContent;
189
+ self.notificationDelivered = YES;
190
+ self.contentHandler = nil;
191
+ self.modifiedContent = nil;
192
+ }
193
+
194
+ if (contentHandler != nil && modifiedContent != nil) {
195
+ contentHandler(modifiedContent);
196
+ }
197
+ }
198
+
199
+ @end
200
+
201
+ @implementation NotifeeCoreExtensionHelper
202
+ + (NotifeeCoreExtensionHelper *)instance {
203
+ static dispatch_once_t once;
204
+ static NotifeeCoreExtensionHelper *instance;
205
+ dispatch_once(&once, ^{
206
+ instance = [[self alloc] init];
207
+ });
208
+
209
+ return instance;
210
+ }
211
+
212
+ - (NSMutableDictionary *)parseNotifeeOptions:(id)payload {
213
+ if ([payload isKindOfClass:[NSDictionary class]]) {
214
+ return [payload mutableCopy];
215
+ }
216
+
217
+ if ([payload isKindOfClass:[NSString class]]) {
218
+ NSData *optionsData = [payload dataUsingEncoding:NSUTF8StringEncoding];
219
+ if (optionsData == nil) {
220
+ NSLog(@"NotifeeCoreExtensionHelper: Could not decode notifee_options string as UTF-8");
221
+ return nil;
222
+ }
223
+
224
+ NSError *error = nil;
225
+ id jsonObject = [NSJSONSerialization JSONObjectWithData:optionsData
226
+ options:NSJSONReadingFragmentsAllowed
227
+ error:&error];
228
+
229
+ if (error != nil) {
230
+ NSLog(@"NotifeeCoreExtensionHelper: Could not parse notifee_options JSON: %@", error);
231
+ return nil;
232
+ }
233
+
234
+ if (![jsonObject isKindOfClass:[NSDictionary class]]) {
235
+ NSLog(@"NotifeeCoreExtensionHelper: Ignoring notifee_options JSON because it is not a "
236
+ @"dictionary: %@",
237
+ NSStringFromClass([jsonObject class]));
238
+ return nil;
239
+ }
240
+
241
+ return [jsonObject mutableCopy];
242
+ }
243
+
244
+ NSLog(@"NotifeeCoreExtensionHelper: Ignoring notifee_options because it is not a dictionary "
245
+ @"or JSON string: %@",
246
+ NSStringFromClass([payload class]));
247
+ return nil;
248
+ }
249
+
250
+ - (void)populateNotificationContent:(UNNotificationRequest *_Nullable)request
251
+ withContent:(UNMutableNotificationContent *)content
252
+ withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
253
+ NotifeeCoreExtensionRequestContext *context =
254
+ [[NotifeeCoreExtensionRequestContext alloc] initWithHelper:self
255
+ content:content
256
+ contentHandler:contentHandler];
257
+ [context populateNotificationContentWithRequest:request];
146
258
  }
147
259
 
148
260
  - (NSString *)fileExtensionForResponse:(NSURLResponse *)response {
@@ -220,10 +332,4 @@ static NSString *const kImagePathPrefix = @"image/";
220
332
  }
221
333
  }
222
334
 
223
- - (void)deliverNotification {
224
- if (self.contentHandler) {
225
- self.contentHandler(self.modifiedContent);
226
- }
227
- }
228
-
229
335
  @end
@@ -23,6 +23,12 @@
23
23
  #include <math.h>
24
24
  #import "NotifeeCore+NSURLSession.h"
25
25
 
26
+ @interface NotifeeCoreNSURLSession (NotifeeCoreUtil)
27
+
28
+ + (nullable NSString *)fileExtensionFromSuggestedFilename:(nullable NSString *)suggestedFilename;
29
+
30
+ @end
31
+
26
32
  static NSString *const kNotifeeRollingTimestampTriggersStorageKey =
27
33
  @"app.notifee.core.rollingTimestampTriggers.v1";
28
34
  static NSString *const kNotifeeRollingInternalIdPrefix = @"__notifee_rolling__";
@@ -83,7 +89,7 @@ static NSInteger const kNotifeeRollingTargetPerTrigger = 32;
83
89
  notificationActionDict[@"authenticationRequired"] =
84
90
  @(((notificationAction.options & UNNotificationActionOptionAuthenticationRequired) != 0));
85
91
 
86
- if ([[notificationAction class] isKindOfClass:[UNTextInputNotificationAction class]]) {
92
+ if ([notificationAction isKindOfClass:[UNTextInputNotificationAction class]]) {
87
93
  UNTextInputNotificationAction *notificationInputAction =
88
94
  (UNTextInputNotificationAction *)notificationAction;
89
95
  if ([notificationInputAction textInputButtonTitle] == nil &&
@@ -266,12 +272,14 @@ static NSInteger const kNotifeeRollingTargetPerTrigger = 32;
266
272
  // Rename the recently downloaded file to include its file extension
267
273
  NSFileManager *fileManager = [NSFileManager defaultManager];
268
274
 
269
- NSString *fileExtension = [NSString stringWithFormat:@".%@", [suggestedFilename pathExtension]];
275
+ NSString *fileExtension =
276
+ [NotifeeCoreNSURLSession fileExtensionFromSuggestedFilename:suggestedFilename];
270
277
 
271
- if (!fileExtension || [fileExtension isEqualToString:@""]) {
278
+ if (fileExtension.length == 0) {
272
279
  NSLog(@"NotifeeCore: Failed to determine file extension for attachment "
273
- @"with URL %@: %@",
274
- urlString, error);
280
+ @"with URL %@ and suggested filename %@",
281
+ urlString, suggestedFilename);
282
+ [fileManager removeItemAtPath:tempDestination error:nil];
275
283
  return nil;
276
284
  }
277
285
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-notify-kit",
3
- "version": "10.3.0",
3
+ "version": "10.3.2",
4
4
  "author": "Marco Crupi",
5
5
  "description": "Maintained Notifee-compatible fork for React Native — advanced local notifications for Android & iOS.",
6
6
  "main": "dist/index.js",
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '10.3.0';
2
+ export const version = '10.3.2';