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.
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +1 -3
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +2 -1
- package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerCurrentBehaviorTest.java +3 -3
- package/android/src/test/java/app/notifee/core/NotificationManagerCreateTriggerNotificationCurrentBehaviorTest.java +12 -6
- package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java +2 -1
- package/android/src/test/java/app/notifee/core/NotificationReceiverHandlerInitialNotificationTest.java +2 -1
- package/android/src/test/java/app/notifee/core/model/ChannelModelTest.java +5 -4
- package/android/src/test/java/app/notifee/core/model/IntervalTriggerModelTest.java +2 -1
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelTest.java +5 -4
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelTest.java +4 -3
- package/android/src/test/java/app/notifee/core/utility/BundleValueReaderTest.java +2 -1
- package/android/src/test/java/app/notifee/core/utility/ObjectUtilsTest.java +1 -2
- package/android/src/test/java/io/invertase/notifee/HeadlessTaskReactHostTest.java +6 -5
- package/android/src/test/java/io/invertase/notifee/NotifeeEventSubscriberRoutingTest.java +19 -10
- package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsDrawerTest.java +1 -1
- package/cli/dist/templates/templates/NotificationService.swift.tmpl +33 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCore+NSURLSession.m +20 -3
- package/ios/NotifeeCore/NotifeeCore.m +2 -1
- package/ios/NotifeeCore/NotifeeCoreExtensionHelper.h +2 -2
- package/ios/NotifeeCore/NotifeeCoreExtensionHelper.m +138 -32
- package/ios/NotifeeCore/NotifeeCoreUtil.m +13 -5
- package/package.json +1 -1
- 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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
123
|
+
assertCompletes(
|
|
124
|
+
NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
119
125
|
|
|
120
126
|
verifyIntervalBranch(triggerBundle);
|
|
121
127
|
verifyTimestampBranchNeverCalled();
|
package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java
CHANGED
|
@@ -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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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,
|
|
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())
|
|
84
|
-
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
135
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
50
|
+
guard let bestAttemptContent = mutableContent else {
|
|
43
51
|
nseLog("mutableCopy failed for id=\(request.identifier); delivering original content")
|
|
44
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
deliverOnce(bestAttemptContent)
|
|
71
98
|
}
|
|
72
99
|
}
|
|
73
100
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "10.3.
|
|
1
|
+
export declare const version = "10.3.2";
|
package/dist/version.js
CHANGED
|
@@ -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:[
|
|
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 =
|
|
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,
|
|
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
|
-
@
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
- (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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.
|
|
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 &&
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 ([
|
|
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 =
|
|
275
|
+
NSString *fileExtension =
|
|
276
|
+
[NotifeeCoreNSURLSession fileExtensionFromSuggestedFilename:suggestedFilename];
|
|
270
277
|
|
|
271
|
-
if (
|
|
278
|
+
if (fileExtension.length == 0) {
|
|
272
279
|
NSLog(@"NotifeeCore: Failed to determine file extension for attachment "
|
|
273
|
-
@"with URL
|
|
274
|
-
urlString,
|
|
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
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '10.3.
|
|
2
|
+
export const version = '10.3.2';
|