react-native-notify-kit 9.3.0 → 9.5.0

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 (53) hide show
  1. package/README.md +68 -2
  2. package/android/build.gradle +5 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/androidTest/java/app/notifee/core/DoScheduledWorkOrderingTest.java +234 -0
  5. package/android/src/androidTest/java/app/notifee/core/RebootRecoveryTest.java +204 -0
  6. package/android/src/androidTest/java/app/notifee/core/database/TimingWorkDataRepository.java +56 -0
  7. package/android/src/androidTest/java/app/notifee/core/database/WorkDataRepositoryRaceTest.java +221 -0
  8. package/android/src/androidTest/res/drawable/test_icon.xml +14 -0
  9. package/android/src/main/baseline-prof.txt +382 -0
  10. package/android/src/main/java/app/notifee/core/ForegroundService.java +269 -80
  11. package/android/src/main/java/app/notifee/core/InitProvider.java +48 -5
  12. package/android/src/main/java/app/notifee/core/Logger.java +5 -0
  13. package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +160 -79
  14. package/android/src/main/java/app/notifee/core/NotificationManager.java +370 -288
  15. package/android/src/main/java/app/notifee/core/WarmupHelper.java +84 -0
  16. package/android/src/main/java/app/notifee/core/database/WorkDataRepository.java +38 -34
  17. package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +17 -1
  18. package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +30 -0
  19. package/android/src/test/java/app/notifee/core/ForegroundServiceTest.java +182 -0
  20. package/android/src/test/java/app/notifee/core/database/WorkDataRepositoryFutureContractTest.java +279 -0
  21. package/dist/NotifeeApiModule.d.ts +1 -0
  22. package/dist/NotifeeApiModule.js +6 -0
  23. package/dist/NotifeeApiModule.js.map +1 -1
  24. package/dist/specs/NativeNotifeeModule.d.ts +1 -0
  25. package/dist/specs/NativeNotifeeModule.js.map +1 -1
  26. package/dist/types/Module.d.ts +19 -0
  27. package/dist/types/NotificationAndroid.d.ts +58 -0
  28. package/dist/types/NotificationAndroid.js +43 -0
  29. package/dist/types/NotificationAndroid.js.map +1 -1
  30. package/dist/utils/validate.d.ts +1 -0
  31. package/dist/utils/validate.js +10 -4
  32. package/dist/utils/validate.js.map +1 -1
  33. package/dist/validators/validateAndroidChannel.js +3 -3
  34. package/dist/validators/validateAndroidChannel.js.map +1 -1
  35. package/dist/validators/validateAndroidNotification.js +36 -12
  36. package/dist/validators/validateAndroidNotification.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m +5 -1
  40. package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +5 -5
  41. package/ios/NotifeeCore/NotifeeCore.m +28 -9
  42. package/ios/NotifeeCore/NotifeeCoreDelegateHolder.m +5 -1
  43. package/ios/NotifeeCore/NotifeeCoreExtensionHelper.m +7 -2
  44. package/ios/RNNotifee/NotifeeApiModule.mm +31 -10
  45. package/package.json +1 -1
  46. package/src/NotifeeApiModule.ts +7 -0
  47. package/src/specs/NativeNotifeeModule.ts +1 -0
  48. package/src/types/Module.ts +20 -0
  49. package/src/types/NotificationAndroid.ts +62 -0
  50. package/src/utils/validate.ts +12 -4
  51. package/src/validators/validateAndroidChannel.ts +3 -3
  52. package/src/validators/validateAndroidNotification.ts +43 -10
  53. package/src/version.ts +1 -1
@@ -0,0 +1,221 @@
1
+ package app.notifee.core.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * ---
13
+ *
14
+ * NOT RUN IN CI.
15
+ *
16
+ * This test hits a real Room in-memory database and must be executed on a connected device or
17
+ * emulator via:
18
+ *
19
+ * cd apps/smoke/android
20
+ * ./gradlew :react-native-notify-kit:connectedDebugAndroidTest
21
+ *
22
+ * before merging any change that touches the WorkDataRepository layer or the Room schema. A
23
+ * follow-up task tracks wiring this into CI with reactivecircus/android-emulator-runner — see the
24
+ * "Wire androidTest into CI" issue linked from the #549 fix PR description.
25
+ *
26
+ * The scenarios below are the instrumented analogues of the repro-549-findings.md harness:
27
+ * post-cancel consistency (Scenario B), post-create persistence (Scenario C), and concurrent
28
+ * stress (Scenario D). They run 100 iterations each and must observe zero inconsistencies after
29
+ * the #549 fix; any non-zero count is a regression.
30
+ */
31
+
32
+ import static org.junit.Assert.assertEquals;
33
+ import static org.junit.Assert.assertFalse;
34
+ import static org.junit.Assert.assertTrue;
35
+ import static org.junit.Assert.fail;
36
+
37
+ import androidx.room.Room;
38
+ import androidx.test.ext.junit.runners.AndroidJUnit4;
39
+ import androidx.test.platform.app.InstrumentationRegistry;
40
+ import com.google.common.util.concurrent.ListenableFuture;
41
+ import com.google.common.util.concurrent.ListeningExecutorService;
42
+ import com.google.common.util.concurrent.MoreExecutors;
43
+ import java.util.ArrayList;
44
+ import java.util.List;
45
+ import java.util.concurrent.ExecutionException;
46
+ import java.util.concurrent.ExecutorService;
47
+ import java.util.concurrent.Executors;
48
+ import java.util.concurrent.TimeUnit;
49
+ import org.junit.After;
50
+ import org.junit.Before;
51
+ import org.junit.Test;
52
+ import org.junit.runner.RunWith;
53
+
54
+ @RunWith(AndroidJUnit4.class)
55
+ public class WorkDataRepositoryRaceTest {
56
+
57
+ private NotifeeCoreDatabase db;
58
+ private WorkDataRepository repo;
59
+ private ListeningExecutorService executor;
60
+
61
+ @Before
62
+ public void setUp() {
63
+ db =
64
+ Room.inMemoryDatabaseBuilder(
65
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
66
+ NotifeeCoreDatabase.class)
67
+ .allowMainThreadQueries()
68
+ .build();
69
+ // Shared cached thread pool matches the production executor's concurrency profile.
70
+ executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
71
+ repo = new WorkDataRepository(db.workDao(), executor);
72
+ }
73
+
74
+ @After
75
+ public void tearDown() {
76
+ db.close();
77
+ executor.shutdownNow();
78
+ }
79
+
80
+ // ------- Scenario B analogue: post-cancel consistency -------
81
+
82
+ @Test
83
+ public void deleteAll_thenGetAll_isEmpty_100iterations()
84
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
85
+ for (int i = 0; i < 100; i++) {
86
+ // Seed 20 rows
87
+ List<ListenableFuture<Void>> seedFutures = new ArrayList<>(20);
88
+ for (int j = 0; j < 20; j++) {
89
+ seedFutures.add(repo.insert(entity("b-" + i + "-" + j)));
90
+ }
91
+ for (ListenableFuture<Void> f : seedFutures) {
92
+ f.get(2, TimeUnit.SECONDS);
93
+ }
94
+
95
+ // Assert seeded
96
+ assertEquals(20, repo.getAll().get(2, TimeUnit.SECONDS).size());
97
+
98
+ // Cancel all, then immediately read — must be empty.
99
+ repo.deleteAll().get(2, TimeUnit.SECONDS);
100
+ int immediateCount = repo.getAll().get(2, TimeUnit.SECONDS).size();
101
+ assertEquals(
102
+ "iteration " + i + ": deleteAll must complete before its future resolves",
103
+ 0,
104
+ immediateCount);
105
+ }
106
+ }
107
+
108
+ // ------- Scenario C analogue: post-create persistence -------
109
+
110
+ @Test
111
+ public void insert_thenGet_isVisible_100iterations()
112
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
113
+ for (int i = 0; i < 100; i++) {
114
+ repo.deleteAll().get(2, TimeUnit.SECONDS);
115
+ String id = "c-" + i;
116
+ repo.insert(entity(id)).get(2, TimeUnit.SECONDS);
117
+
118
+ WorkDataEntity row = repo.getWorkDataById(id).get(2, TimeUnit.SECONDS);
119
+ assertTrue(
120
+ "iteration " + i + ": insert future resolved but row is not in Room yet", row != null);
121
+ assertEquals(id, row.getId());
122
+ }
123
+ }
124
+
125
+ // ------- Delete-by-id consistency -------
126
+
127
+ @Test
128
+ public void deleteById_thenGet_isNull_100iterations()
129
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
130
+ for (int i = 0; i < 100; i++) {
131
+ String id = "d-" + i;
132
+ repo.insert(entity(id)).get(2, TimeUnit.SECONDS);
133
+ repo.deleteById(id).get(2, TimeUnit.SECONDS);
134
+ WorkDataEntity row = repo.getWorkDataById(id).get(2, TimeUnit.SECONDS);
135
+ assertTrue("iteration " + i + ": deleteById resolved but row still present", row == null);
136
+ }
137
+ }
138
+
139
+ // ------- Update visibility -------
140
+
141
+ @Test
142
+ public void update_thenGet_reflectsNewTrigger_100iterations()
143
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
144
+ for (int i = 0; i < 100; i++) {
145
+ String id = "u-" + i;
146
+ byte[] notification = new byte[] {1, 2, 3};
147
+ byte[] initialTrigger = new byte[] {(byte) i};
148
+ repo.insert(new WorkDataEntity(id, notification, initialTrigger, false))
149
+ .get(2, TimeUnit.SECONDS);
150
+
151
+ byte[] updatedTrigger = new byte[] {(byte) (i + 100)};
152
+ repo.update(new WorkDataEntity(id, notification, updatedTrigger, true))
153
+ .get(2, TimeUnit.SECONDS);
154
+
155
+ WorkDataEntity row = repo.getWorkDataById(id).get(2, TimeUnit.SECONDS);
156
+ assertTrue(row != null);
157
+ assertEquals(
158
+ "iteration " + i + ": updated trigger byte must be visible immediately",
159
+ (byte) (i + 100),
160
+ row.getTrigger()[0]);
161
+ assertTrue(row.getWithAlarmManager());
162
+ }
163
+ }
164
+
165
+ // ------- Scenario D analogue: concurrent stress -------
166
+
167
+ /**
168
+ * Fire 20 concurrent insert and 20 concurrent deleteAll operations across a fixed thread pool and
169
+ * verify the system reaches a deterministic final state after all futures complete. This does NOT
170
+ * assert a specific final count — ordering between concurrent creates and cancels is
171
+ * implementation-defined — but it does assert that every future completes successfully and the DB
172
+ * is readable at the end.
173
+ */
174
+ @Test
175
+ public void concurrentInsertAndDelete_allFuturesComplete()
176
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
177
+ ExecutorService testPool = Executors.newFixedThreadPool(8);
178
+ try {
179
+ List<ListenableFuture<Void>> futures = new ArrayList<>();
180
+ for (int i = 0; i < 20; i++) {
181
+ futures.add(repo.insert(entity("s-" + i)));
182
+ if (i % 3 == 0) {
183
+ futures.add(repo.deleteAll());
184
+ }
185
+ }
186
+ for (ListenableFuture<Void> f : futures) {
187
+ f.get(5, TimeUnit.SECONDS);
188
+ }
189
+ // Final read must succeed — we only assert the DB is readable, not the count.
190
+ int finalCount = repo.getAll().get(2, TimeUnit.SECONDS).size();
191
+ assertTrue("final count must be non-negative: " + finalCount, finalCount >= 0);
192
+ } finally {
193
+ testPool.shutdownNow();
194
+ }
195
+ }
196
+
197
+ // ------- DAO exceptions surface via ExecutionException -------
198
+
199
+ @Test
200
+ public void insertDuplicatePrimaryKey_failsFutureWithExecutionException()
201
+ throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException {
202
+ repo.insert(entity("dup")).get(2, TimeUnit.SECONDS);
203
+ try {
204
+ // Room throws SQLiteConstraintException on duplicate primary key when using OnConflict.ABORT
205
+ // (the default). If the DAO uses REPLACE this test will pass trivially; adjust when the
206
+ // DAO's @Insert strategy changes.
207
+ repo.insert(entity("dup")).get(2, TimeUnit.SECONDS);
208
+ // If we reach here the DAO uses REPLACE or similar — mark the test as passing but log.
209
+ } catch (ExecutionException e) {
210
+ assertFalse("ExecutionException must wrap a real cause, not null", e.getCause() == null);
211
+ } catch (Throwable unexpected) {
212
+ fail("Expected ExecutionException or success, got: " + unexpected);
213
+ }
214
+ }
215
+
216
+ // ------- helpers -------
217
+
218
+ private static WorkDataEntity entity(String id) {
219
+ return new WorkDataEntity(id, new byte[] {0}, new byte[] {0}, false);
220
+ }
221
+ }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ Tiny transparent vector drawable used as a smallIcon placeholder in
4
+ DoScheduledWorkOrderingTest so NotificationManagerCompat.notify() does not
5
+ reject the test notification for missing icon. Only referenced by the
6
+ androidTest APK — never shipped to consumers.
7
+ -->
8
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
9
+ android:width="24dp"
10
+ android:height="24dp"
11
+ android:viewportWidth="24"
12
+ android:viewportHeight="24">
13
+ <path android:fillColor="#00000000" android:pathData="M0,0h24v24H0z" />
14
+ </vector>