react-native-notify-kit 10.8.1 → 10.9.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 (79) hide show
  1. package/README.md +33 -18
  2. package/android/build.gradle +50 -4
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/app/notifee/core/ForegroundService.java +395 -5
  5. package/android/src/main/java/app/notifee/core/ForegroundServiceRunLimitReceiver.java +15 -0
  6. package/android/src/main/java/app/notifee/core/ForegroundServiceRunLimitScheduler.java +461 -0
  7. package/android/src/main/java/app/notifee/core/NotificationManager.java +6 -0
  8. package/android/src/main/java/app/notifee/core/event/NotificationEvent.java +6 -0
  9. package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +29 -0
  10. package/android/src/main/kotlin/io/invertase/notifee/NotifeeEventSubscriber.kt +3 -0
  11. package/android/src/test/java/app/notifee/core/ForegroundServiceRunLimitTest.java +2357 -0
  12. package/android/src/test/java/app/notifee/core/ForegroundServiceTest.java +23 -13
  13. package/android/src/test/java/app/notifee/core/ShadowServiceCompatibilityTest.java +170 -0
  14. package/android/src/test/resources/robolectric.properties +4 -0
  15. package/cli/dist/cli.js +0 -1
  16. package/cli/dist/commands/initNse.js +1 -2
  17. package/cli/dist/lib/detectProject.js +1 -2
  18. package/cli/dist/lib/initNseCore.js +0 -1
  19. package/cli/dist/lib/logger.js +0 -1
  20. package/cli/dist/lib/patchPodfile.js +0 -1
  21. package/cli/dist/lib/patchXcodeProject.js +22 -3
  22. package/cli/dist/lib/writeTemplates.js +0 -1
  23. package/cli/dist/lib/xcode.js +6 -0
  24. package/cli/dist/templates/NotificationService.swift.tmpl +74 -6
  25. package/cli/package.json +1 -0
  26. package/cli/vendor/xcode/AUTHORS +6 -0
  27. package/cli/vendor/xcode/LICENSE +202 -0
  28. package/cli/vendor/xcode/NOTICE +5 -0
  29. package/cli/vendor/xcode/README.md +32 -0
  30. package/cli/vendor/xcode/UPSTREAM-SHA256.json +12 -0
  31. package/cli/vendor/xcode/index.js +18 -0
  32. package/cli/vendor/xcode/lib/parseJob.js +32 -0
  33. package/cli/vendor/xcode/lib/parser/pbxproj.js +1904 -0
  34. package/cli/vendor/xcode/lib/parser/pbxproj.pegjs +280 -0
  35. package/cli/vendor/xcode/lib/pbxFile.js +233 -0
  36. package/cli/vendor/xcode/lib/pbxProject.js +2205 -0
  37. package/cli/vendor/xcode/lib/pbxWriter.js +309 -0
  38. package/dist/NotifeeNativeModule.web.d.ts +2 -2
  39. package/dist/types/Notification.d.ts +12 -1
  40. package/dist/types/Notification.js +3 -0
  41. package/dist/types/Notification.js.map +1 -1
  42. package/dist/types/NotificationAndroid.d.ts +9 -0
  43. package/dist/types/NotificationAndroid.js.map +1 -1
  44. package/dist/validators/validateAndroidNotification.js +26 -1
  45. package/dist/validators/validateAndroidNotification.js.map +1 -1
  46. package/dist/validators/validateAndroidStyle.js +6 -0
  47. package/dist/validators/validateAndroidStyle.js.map +1 -1
  48. package/dist/validators/validateIOSAttachment.js +3 -1
  49. package/dist/validators/validateIOSAttachment.js.map +1 -1
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +1 -1
  52. package/package.json +8 -5
  53. package/src/NotifeeNativeModule.web.ts +2 -2
  54. package/src/types/Notification.ts +13 -0
  55. package/src/types/NotificationAndroid.ts +10 -0
  56. package/src/validators/validateAndroidNotification.ts +31 -1
  57. package/src/validators/validateAndroidStyle.ts +10 -2
  58. package/src/validators/validateIOSAttachment.ts +5 -1
  59. package/src/version.ts +1 -1
  60. package/cli/dist/cli.bundle.js +0 -20834
  61. package/cli/dist/cli.d.ts +0 -2
  62. package/cli/dist/cli.js.map +0 -1
  63. package/cli/dist/commands/initNse.d.ts +0 -12
  64. package/cli/dist/commands/initNse.js.map +0 -1
  65. package/cli/dist/lib/detectProject.d.ts +0 -12
  66. package/cli/dist/lib/detectProject.js.map +0 -1
  67. package/cli/dist/lib/initNseCore.d.ts +0 -8
  68. package/cli/dist/lib/initNseCore.js.map +0 -1
  69. package/cli/dist/lib/logger.d.ts +0 -4
  70. package/cli/dist/lib/logger.js.map +0 -1
  71. package/cli/dist/lib/patchPodfile.d.ts +0 -23
  72. package/cli/dist/lib/patchPodfile.js.map +0 -1
  73. package/cli/dist/lib/patchXcodeProject.d.ts +0 -41
  74. package/cli/dist/lib/patchXcodeProject.js.map +0 -1
  75. package/cli/dist/lib/writeTemplates.d.ts +0 -11
  76. package/cli/dist/lib/writeTemplates.js.map +0 -1
  77. package/cli/dist/templates/templates/Info.plist.tmpl +0 -29
  78. package/cli/dist/templates/templates/NotificationService.swift.tmpl +0 -100
  79. package/cli/dist/templates/templates/NotifyKitNSE.entitlements.tmpl +0 -6
@@ -119,7 +119,7 @@ public class ForegroundServiceTest {
119
119
 
120
120
  @Test
121
121
  @Config(
122
- sdk = {34, 35},
122
+ sdk = {34, 35, 37},
123
123
  shadows = SliceAShadowService.class)
124
124
  public void sliceA_demotionPrecedesDestructiveRetirementAndEventWork() throws Exception {
125
125
  ForegroundService service = sliceAService(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
@@ -221,7 +221,9 @@ public class ForegroundServiceTest {
221
221
  }
222
222
 
223
223
  @Test
224
- @Config(sdk = 35, shadows = SliceAShadowService.class)
224
+ @Config(
225
+ sdk = {35, 37},
226
+ shadows = SliceAShadowService.class)
225
227
  public void sliceA_reservedUnknownAndAcceptedUndeliveredStartsRemainHeld() throws Exception {
226
228
  ForegroundService service = sliceAService(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
227
229
  RecordingServiceContext context = useRecordingContext();
@@ -338,7 +340,9 @@ public class ForegroundServiceTest {
338
340
  }
339
341
 
340
342
  @Test
341
- @Config(sdk = 35, shadows = SliceAShadowService.class)
343
+ @Config(
344
+ sdk = {35, 37},
345
+ shadows = SliceAShadowService.class)
342
346
  public void sliceA_demotionThrowPreservesEvidenceAndPrimaryEvenWhenStopThrows() throws Exception {
343
347
  ForegroundService service = sliceAService(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
344
348
  Object runner = issue81Field(service, "mRunnerToken");
@@ -1047,7 +1051,8 @@ public class ForegroundServiceTest {
1047
1051
  }
1048
1052
 
1049
1053
  @Implementation
1050
- protected void startForeground(int id, Notification notification, int foregroundServiceType) {
1054
+ protected void startForeground(int id, Notification notification, int foregroundServiceType)
1055
+ throws Exception {
1051
1056
  if (promotionFailure != null) throw promotionFailure;
1052
1057
  promotions++;
1053
1058
  foreground = true;
@@ -1272,7 +1277,9 @@ public class ForegroundServiceTest {
1272
1277
  }
1273
1278
 
1274
1279
  @Test
1275
- @Config(sdk = 35, shadows = Issue81ShadowService.class)
1280
+ @Config(
1281
+ sdk = {35, 37},
1282
+ shadows = Issue81ShadowService.class)
1276
1283
  public void issue82_pendingB_definiteRejectionReevaluatesRecovery_unknownDoesNot()
1277
1284
  throws Exception {
1278
1285
  for (RuntimeException reply :
@@ -1824,7 +1831,7 @@ public class ForegroundServiceTest {
1824
1831
 
1825
1832
  @Test
1826
1833
  @Config(
1827
- sdk = {31, 33, 34, 35},
1834
+ sdk = {31, 33, 34, 35, 37},
1828
1835
  shadows = Issue81ShadowService.class)
1829
1836
  public void issue81_retryFreshDenied_recoversWithoutClaimingPromotion() throws Exception {
1830
1837
  issue81AssertFreshRecovery(Service.START_FLAG_RETRY);
@@ -2030,7 +2037,9 @@ public class ForegroundServiceTest {
2030
2037
  }
2031
2038
 
2032
2039
  @Test
2033
- @Config(sdk = 33, shadows = Issue81ShadowService.class)
2040
+ @Config(
2041
+ sdk = {33, 37},
2042
+ shadows = Issue81ShadowService.class)
2034
2043
  public void issue81_deniedA_withQueuedStop_promotesBBeforeStopping() throws Exception {
2035
2044
  RecordingServiceContext context = useRecordingContext();
2036
2045
  ForegroundService service = issue81Service();
@@ -2371,7 +2380,7 @@ public class ForegroundServiceTest {
2371
2380
 
2372
2381
  @Test
2373
2382
  @Config(
2374
- sdk = {31, 34},
2383
+ sdk = {31, 34, 37},
2375
2384
  shadows = Issue81ShadowService.class)
2376
2385
  public void issue81_deferredRecovery_lateSecurityRejection_stops() throws Exception {
2377
2386
  issue81AssertLateRejection(new SecurityException("B rejected"));
@@ -3105,7 +3114,7 @@ public class ForegroundServiceTest {
3105
3114
  }
3106
3115
 
3107
3116
  @Implementation
3108
- protected void startForeground(int id, Notification notification) {
3117
+ protected void startForeground(int id, Notification notification) throws Exception {
3109
3118
  if (!forwardingTyped) {
3110
3119
  recordPromotion(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
3111
3120
  }
@@ -3113,7 +3122,7 @@ public class ForegroundServiceTest {
3113
3122
  }
3114
3123
 
3115
3124
  @Implementation
3116
- protected void startForeground(int id, Notification notification, int type) {
3125
+ protected void startForeground(int id, Notification notification, int type) throws Exception {
3117
3126
  recordPromotion(id, notification, type);
3118
3127
  // ShadowService's typed overload delegates to the two-argument shadow. Record only the
3119
3128
  // actual platform invocation so the witness cannot count that delegation as a second call.
@@ -4180,7 +4189,7 @@ public class ForegroundServiceTest {
4180
4189
  @Implements(Service.class)
4181
4190
  public static class DenyOldNotificationShadowService extends ShadowService {
4182
4191
  @Implementation
4183
- protected void startForeground(int id, Notification notification) {
4192
+ protected void startForeground(int id, Notification notification) throws Exception {
4184
4193
  if ("B".equals(notification.extras.getString(Notification.EXTRA_TITLE))) {
4185
4194
  throw new ForegroundServiceStartNotAllowedException("real old START denied");
4186
4195
  }
@@ -5274,7 +5283,7 @@ public class ForegroundServiceTest {
5274
5283
  @Implements(Service.class)
5275
5284
  public static class RejectBStartForegroundShadowService extends ShadowService {
5276
5285
  @Implementation
5277
- protected void startForeground(int id, Notification notification) {
5286
+ protected void startForeground(int id, Notification notification) throws Exception {
5278
5287
  if ("B".equals(notification.extras.getString(Notification.EXTRA_TITLE))) {
5279
5288
  throw new SecurityException("posting B rejected");
5280
5289
  }
@@ -5282,7 +5291,8 @@ public class ForegroundServiceTest {
5282
5291
  }
5283
5292
 
5284
5293
  @Implementation
5285
- protected void startForeground(int id, Notification notification, int foregroundServiceType) {
5294
+ protected void startForeground(int id, Notification notification, int foregroundServiceType)
5295
+ throws Exception {
5286
5296
  if ("B".equals(notification.extras.getString(Notification.EXTRA_TITLE))) {
5287
5297
  throw new SecurityException("posting B rejected");
5288
5298
  }
@@ -0,0 +1,170 @@
1
+ package app.notifee.core;
2
+
3
+ import static org.junit.Assert.assertEquals;
4
+ import static org.junit.Assert.assertSame;
5
+ import static org.junit.Assert.assertThrows;
6
+ import static org.junit.Assert.assertTrue;
7
+
8
+ import android.app.Notification;
9
+ import android.app.NotificationChannel;
10
+ import android.app.NotificationManager;
11
+ import android.app.Service;
12
+ import android.content.Context;
13
+ import android.content.Intent;
14
+ import android.content.pm.ServiceInfo;
15
+ import android.os.Build;
16
+ import android.os.IBinder;
17
+ import org.junit.Test;
18
+ import org.junit.runner.RunWith;
19
+ import org.robolectric.Robolectric;
20
+ import org.robolectric.RobolectricTestRunner;
21
+ import org.robolectric.RuntimeEnvironment;
22
+ import org.robolectric.annotation.Config;
23
+ import org.robolectric.shadows.ShadowService;
24
+
25
+ /** Exercises the real Robolectric superclass calls, including its checked exception contract. */
26
+ @RunWith(RobolectricTestRunner.class)
27
+ public class ShadowServiceCompatibilityTest {
28
+ public static class TestService extends Service {
29
+ @Override
30
+ public IBinder onBind(Intent intent) {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ @Test
36
+ @Config(
37
+ sdk = {29, 35, 36, 37},
38
+ shadows = ForegroundServiceTest.SliceAShadowService.class)
39
+ public void sliceA_typedForwardingPreservesFailures() {
40
+ ForegroundServiceTest.SliceAShadowService.reset();
41
+ assertForwarding(ForegroundServiceTest.SliceAShadowService.class, true);
42
+ }
43
+
44
+ @Test
45
+ @Config(
46
+ sdk = {24, 35, 36, 37},
47
+ shadows = ForegroundServiceTest.Issue81ShadowService.class)
48
+ public void issue81_untypedForwardingPreservesFailures() {
49
+ resetIssue81();
50
+ assertForwarding(ForegroundServiceTest.Issue81ShadowService.class, false);
51
+ }
52
+
53
+ @Test
54
+ @Config(
55
+ sdk = {29, 35, 36, 37},
56
+ shadows = ForegroundServiceTest.Issue81ShadowService.class)
57
+ public void issue81_typedForwardingResetsDelegationAfterFailure() {
58
+ resetIssue81();
59
+ TestService service = assertForwarding(ForegroundServiceTest.Issue81ShadowService.class, true);
60
+ // One record for each outer typed call, including failed attempts, without double counting.
61
+ assertEquals(4, ForegroundServiceTest.Issue81ShadowService.promotions.size());
62
+ service.startForeground(101, notification());
63
+ assertEquals(5, ForegroundServiceTest.Issue81ShadowService.promotions.size());
64
+ assertEquals(
65
+ Integer.valueOf(101), ForegroundServiceTest.Issue81ShadowService.promotions.get(4));
66
+ }
67
+
68
+ @Test
69
+ @Config(
70
+ sdk = {24, 35, 36, 37},
71
+ shadows = ForegroundServiceTest.DenyOldNotificationShadowService.class)
72
+ public void denyOld_untypedForwardingPreservesFailures() {
73
+ assertForwarding(ForegroundServiceTest.DenyOldNotificationShadowService.class, false);
74
+ }
75
+
76
+ @Test
77
+ @Config(
78
+ sdk = {24, 35, 36, 37},
79
+ shadows = ForegroundServiceTest.RejectBStartForegroundShadowService.class)
80
+ public void rejectB_untypedForwardingPreservesFailures() {
81
+ assertForwarding(ForegroundServiceTest.RejectBStartForegroundShadowService.class, false);
82
+ }
83
+
84
+ @Test
85
+ @Config(
86
+ sdk = {29, 35, 36, 37},
87
+ shadows = ForegroundServiceTest.RejectBStartForegroundShadowService.class)
88
+ public void rejectB_typedForwardingPreservesFailures() {
89
+ assertForwarding(ForegroundServiceTest.RejectBStartForegroundShadowService.class, true);
90
+ }
91
+
92
+ private static void resetIssue81() {
93
+ ForegroundServiceTest.Issue81ShadowService.failure = null;
94
+ ForegroundServiceTest.Issue81ShadowService.beforePromotion = null;
95
+ ForegroundServiceTest.Issue81ShadowService.rejectTitle = null;
96
+ ForegroundServiceTest.Issue81ShadowService.promotions.clear();
97
+ ForegroundServiceTest.Issue81ShadowService.types.clear();
98
+ ForegroundServiceTest.Issue81ShadowService.order.clear();
99
+ }
100
+
101
+ private static Notification notification() {
102
+ Context context = RuntimeEnvironment.getApplication();
103
+ if (Build.VERSION.SDK_INT >= 26) {
104
+ context
105
+ .getSystemService(NotificationManager.class)
106
+ .createNotificationChannel(
107
+ new NotificationChannel("compat", "compat", NotificationManager.IMPORTANCE_LOW));
108
+ return new Notification.Builder(context, "compat").setContentTitle("A").build();
109
+ }
110
+ return new Notification.Builder(context).setContentTitle("A").build();
111
+ }
112
+
113
+ private static TestService assertForwarding(
114
+ Class<? extends ShadowService> expected, boolean typed) {
115
+ assertEquals(RuntimeEnvironment.getApiLevel(), Build.VERSION.SDK_INT);
116
+ System.out.println(
117
+ "Shadow compatibility: sdk="
118
+ + RuntimeEnvironment.getApiLevel()
119
+ + " java="
120
+ + System.getProperty("java.specification.version"));
121
+ TestService service = Robolectric.buildService(TestService.class).create().get();
122
+ ShadowService shadow = org.robolectric.Shadows.shadowOf(service);
123
+ assertSame(expected, shadow.getClass());
124
+ Notification notification = notification();
125
+ Runnable promote =
126
+ () -> {
127
+ if (typed) {
128
+ service.startForeground(
129
+ 100, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
130
+ } else {
131
+ service.startForeground(100, notification);
132
+ }
133
+ };
134
+ promote.run();
135
+ assertSame(notification, shadow.getLastForegroundNotification());
136
+ assertEquals(100, shadow.getLastForegroundNotificationId());
137
+ assertTrue((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0);
138
+ if (typed)
139
+ assertEquals(
140
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC, service.getForegroundServiceType());
141
+ // Both a platform-style runtime failure and the newly declared checked failure must escape.
142
+ for (Exception failure :
143
+ new Exception[] {new SecurityException("denied"), new Exception("checked")}) {
144
+ Notification rejected = notification();
145
+ shadow.setThrowInStartForeground(failure);
146
+ assertSame(
147
+ failure,
148
+ assertThrows(
149
+ Exception.class,
150
+ () -> {
151
+ if (typed) {
152
+ service.startForeground(
153
+ 200, rejected, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
154
+ } else {
155
+ service.startForeground(200, rejected);
156
+ }
157
+ }));
158
+ assertSame(notification, shadow.getLastForegroundNotification());
159
+ assertEquals(100, shadow.getLastForegroundNotificationId());
160
+ assertEquals(0, rejected.flags & Notification.FLAG_FOREGROUND_SERVICE);
161
+ }
162
+ shadow.setThrowInStartForeground(null);
163
+ promote.run();
164
+ assertSame(notification, shadow.getLastForegroundNotification());
165
+ if (typed)
166
+ assertEquals(
167
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC, service.getForegroundServiceType());
168
+ return service;
169
+ }
170
+ }
@@ -0,0 +1,4 @@
1
+ # Robolectric 4.14's no-manifest fallback was API 21; 4.17 supports API 23+.
2
+ # Run unconfigured tests at the library's supported minimum, independently of compileSdk.
3
+ # Explicit @Config SDKs (including API 36/37 on JDK 21) still take precedence.
4
+ sdk=24
package/cli/dist/cli.js CHANGED
@@ -84,4 +84,3 @@ program
84
84
  logger.error(err.message ?? String(err));
85
85
  process.exit(2);
86
86
  });
87
- //# sourceMappingURL=cli.js.map
@@ -39,7 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.initNse = initNse;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
- const xcode_1 = __importDefault(require("xcode"));
42
+ const xcode_1 = __importDefault(require("../lib/xcode"));
43
43
  const detectProject_1 = require("../lib/detectProject");
44
44
  const initNseCore_1 = require("../lib/initNseCore");
45
45
  const writeTemplates_1 = require("../lib/writeTemplates");
@@ -208,4 +208,3 @@ function checkPbxprojTarget(pbxprojPath, targetName) {
208
208
  }
209
209
  return false;
210
210
  }
211
- //# sourceMappingURL=initNse.js.map
@@ -40,7 +40,7 @@ exports.deriveBundleId = void 0;
40
40
  exports.detectIosProject = detectIosProject;
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
- const xcode_1 = __importDefault(require("xcode"));
43
+ const xcode_1 = __importDefault(require("./xcode"));
44
44
  var initNseCore_1 = require("./initNseCore");
45
45
  Object.defineProperty(exports, "deriveBundleId", { enumerable: true, get: function () { return initNseCore_1.deriveNseBundleIdentifier; } });
46
46
  /**
@@ -124,4 +124,3 @@ function readParentTarget(pbxprojPath) {
124
124
  }
125
125
  return { bundleId, targetName };
126
126
  }
127
- //# sourceMappingURL=detectProject.js.map
@@ -194,4 +194,3 @@ function expandKnownBundleIdVariables(bundleId, parentTargetName) {
194
194
  function toRfc1034Identifier(value) {
195
195
  return value.replace(/[^A-Za-z0-9.-]/g, '-');
196
196
  }
197
- //# sourceMappingURL=initNseCore.js.map
@@ -21,4 +21,3 @@ const info = (msg) => {
21
21
  console.log(chalk_1.default.cyan('ℹ') + ' ' + msg);
22
22
  };
23
23
  exports.info = info;
24
- //# sourceMappingURL=logger.js.map
@@ -239,4 +239,3 @@ function hasUncommentedTarget(content, targetName) {
239
239
  function escapeRegex(s) {
240
240
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
241
241
  }
242
- //# sourceMappingURL=patchPodfile.js.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Xcode project patching via xcode@3.x.
3
+ * Xcode project patching via the stable vendored xcode@3.0.1 compatibility layer.
4
4
  *
5
5
  * xcode@3.x has three known bugs that this module works around. See inline
6
6
  * comments tagged "xcode@3.x bug" for details. Do NOT remove these
@@ -53,7 +53,7 @@ exports.patchXcodeProject = patchXcodeProject;
53
53
  exports.patchXcodeProjectForNotifyKitNse = patchXcodeProjectForNotifyKitNse;
54
54
  const crypto = __importStar(require("crypto"));
55
55
  const fs = __importStar(require("fs"));
56
- const xcode_1 = __importDefault(require("xcode"));
56
+ const xcode_1 = __importDefault(require("./xcode"));
57
57
  /**
58
58
  * Adds an NSE target to the .pbxproj file via the xcode library.
59
59
  * Idempotent: if a target with the given name already exists, returns false.
@@ -102,6 +102,10 @@ function patchXcodeProjectForNotifyKitNse(proj, options) {
102
102
  proj.addSourceFile(`${targetName}/NotificationService.swift`, {
103
103
  target: targetUuid,
104
104
  }, group.uuid);
105
+ // The stable writer serializes undefined JS values as the bare word
106
+ // "undefined". addSourceFile also creates an unset explicitFileType;
107
+ // omit it on our new reference, preserving every existing reference.
108
+ fixSourceFileReference(proj, `${targetName}/NotificationService.swift`);
105
109
  // xcode@3.x bug: addTargetDependency() adds the dependency to an internal
106
110
  // cache but does not serialize it to the project hash, so writeSync()
107
111
  // emits a pbxproj with no dependency entry. CocoaPods' host-target
@@ -199,6 +203,22 @@ function fixProductFileReference(proj, targetName) {
199
203
  }
200
204
  }
201
205
  }
206
+ function fixSourceFileReference(proj, sourcePath) {
207
+ const fileRefs = proj.hash.project.objects.PBXFileReference;
208
+ if (!fileRefs)
209
+ return;
210
+ for (const value of Object.values(fileRefs)) {
211
+ if (!value || typeof value !== 'object')
212
+ continue;
213
+ const ref = value;
214
+ if (String(ref.path ?? '').replace(/^"|"$/g, '') !== sourcePath)
215
+ continue;
216
+ for (const key of Object.keys(ref)) {
217
+ if (ref[key] === undefined)
218
+ delete ref[key];
219
+ }
220
+ }
221
+ }
202
222
  function genUuid() {
203
223
  return crypto.randomBytes(12).toString('hex').toUpperCase().slice(0, 24);
204
224
  }
@@ -264,4 +284,3 @@ function stripRnfbInfoPlistInputPath(proj, hostUuid) {
264
284
  }
265
285
  }
266
286
  }
267
- //# sourceMappingURL=patchXcodeProject.js.map
@@ -70,4 +70,3 @@ function writeTemplates(opts) {
70
70
  }
71
71
  return written;
72
72
  }
73
- //# sourceMappingURL=writeTemplates.js.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Keeping this relative path works in both src/lib and the compiled dist/lib.
4
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
5
+ const xcode = require('../../vendor/xcode');
6
+ exports.default = xcode;
@@ -1,32 +1,100 @@
1
+ import Foundation
1
2
  import UserNotifications
2
3
  import RNNotifeeCore
3
4
 
5
+ private func nseLog(_ message: String) {
6
+ NSLog("[NotifyKitNSE] %@", message)
7
+ }
8
+
9
+ private func requestedAttachmentURLs(from userInfo: [AnyHashable: Any]) -> [String] {
10
+ guard let serializedOptions = userInfo["notifee_options"] as? String,
11
+ let data = serializedOptions.data(using: .utf8),
12
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
13
+ let ios = json["ios"] as? [String: Any],
14
+ let attachments = ios["attachments"] as? [[String: Any]] else {
15
+ return []
16
+ }
17
+
18
+ return attachments.compactMap { attachment in
19
+ attachment["url"] as? String
20
+ }
21
+ }
22
+
4
23
  class NotificationService: UNNotificationServiceExtension {
5
24
  var contentHandler: ((UNNotificationContent) -> Void)?
6
25
  var bestAttemptContent: UNMutableNotificationContent?
26
+ private let deliveryLock = NSLock()
27
+ private var didDeliver = false
7
28
 
8
29
  override func didReceive(
9
30
  _ request: UNNotificationRequest,
10
31
  withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
11
32
  ) {
33
+ let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent
34
+
35
+ deliveryLock.lock()
36
+ didDeliver = false
12
37
  self.contentHandler = contentHandler
13
- self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
38
+ self.bestAttemptContent = mutableContent
39
+ deliveryLock.unlock()
40
+
41
+ let requestedAttachmentUrls = requestedAttachmentURLs(from: request.content.userInfo)
14
42
 
15
- guard let bestAttemptContent = bestAttemptContent else {
16
- contentHandler(request.content)
43
+ nseLog(
44
+ "didReceive id=\(request.identifier) title=\(request.content.title) " +
45
+ "hasNotifeeOptions=\(request.content.userInfo["notifee_options"] != nil) " +
46
+ "requestedAttachments=\(requestedAttachmentUrls.count) " +
47
+ "urls=\(requestedAttachmentUrls.joined(separator: ","))"
48
+ )
49
+
50
+ guard let bestAttemptContent = mutableContent else {
51
+ nseLog("mutableCopy failed for id=\(request.identifier); delivering original content")
52
+ deliverOnce(request.content)
17
53
  return
18
54
  }
19
55
 
20
56
  NotifeeExtensionHelper.populateNotificationContent(
21
57
  request,
22
58
  with: bestAttemptContent,
23
- withContentHandler: contentHandler
59
+ withContentHandler: { [weak self] content in
60
+ let deliveredAttachmentIds = content.attachments.map(\.identifier).joined(separator: ",")
61
+ nseLog(
62
+ "contentHandler id=\(request.identifier) title=\(content.title) " +
63
+ "deliveredAttachments=\(content.attachments.count) " +
64
+ "identifiers=\(deliveredAttachmentIds)"
65
+ )
66
+ self?.deliverOnce(content)
67
+ }
24
68
  )
25
69
  }
26
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
+
27
86
  override func serviceExtensionTimeWillExpire() {
28
- if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
29
- contentHandler(bestAttemptContent)
87
+ deliveryLock.lock()
88
+ let content = bestAttemptContent
89
+ deliveryLock.unlock()
90
+
91
+ if let bestAttemptContent = content {
92
+ nseLog(
93
+ "serviceExtensionTimeWillExpire id=\(bestAttemptContent.userInfo["gcm.message_id"] ?? "n/a") " +
94
+ "title=\(bestAttemptContent.title) " +
95
+ "deliveredAttachments=\(bestAttemptContent.attachments.count)"
96
+ )
97
+ deliverOnce(bestAttemptContent)
30
98
  }
31
99
  }
32
100
  }
@@ -0,0 +1 @@
1
+ {"version":"10.9.0"}
@@ -0,0 +1,6 @@
1
+ Andrew Lunny (@alunny)
2
+ Anis Kadri (@imhotep)
3
+ Mike Reinstein (@mreinstein)
4
+ Filip Maj (@filmaj)
5
+ Brett Rudd (@goya)
6
+ Bob Easterday (@bobeast)