react-native-notify-kit 10.1.0 → 10.2.1

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 (52) hide show
  1. package/README.md +99 -96
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/AndroidManifest.xml +0 -1
  4. package/android/src/main/java/app/notifee/core/ForegroundService.java +1 -0
  5. package/android/src/main/java/app/notifee/core/NotificationManager.java +21 -8
  6. package/android/src/main/java/app/notifee/core/RebootBroadcastReceiver.java +16 -0
  7. package/android/src/main/java/app/notifee/core/ReceiverService.java +18 -13
  8. package/android/src/main/java/app/notifee/core/model/NotificationAndroidActionModel.java +1 -2
  9. package/android/src/main/java/app/notifee/core/model/NotificationAndroidPressActionModel.java +3 -9
  10. package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +42 -27
  11. package/android/src/main/java/app/notifee/core/utility/IntentUtils.java +0 -28
  12. package/android/src/main/java/app/notifee/core/utility/PowerManagerUtils.java +79 -41
  13. package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +5 -1
  14. package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +6 -6
  15. package/android/src/main/kotlin/io/invertase/notifee/NotifeeReactUtils.kt +1 -2
  16. package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +150 -0
  17. package/cli/dist/lib/patchPodfile.d.ts +3 -1
  18. package/cli/dist/lib/patchPodfile.js +125 -33
  19. package/cli/dist/lib/patchPodfile.js.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/types/Module.d.ts +27 -13
  22. package/dist/types/Notification.d.ts +1 -1
  23. package/dist/types/NotificationIOS.d.ts +44 -21
  24. package/dist/types/NotificationIOS.js +15 -1
  25. package/dist/types/NotificationIOS.js.map +1 -1
  26. package/dist/types/PowerManagerInfo.d.ts +9 -4
  27. package/dist/types/Trigger.d.ts +45 -2
  28. package/dist/types/Trigger.js +22 -0
  29. package/dist/types/Trigger.js.map +1 -1
  30. package/dist/validators/validateIOSCategory.js +2 -2
  31. package/dist/validators/validateIOSCategory.js.map +1 -1
  32. package/dist/validators/validateTrigger.js +26 -1
  33. package/dist/validators/validateTrigger.js.map +1 -1
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m +20 -10
  37. package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.h +1 -2
  38. package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +44 -35
  39. package/ios/NotifeeCore/NotifeeCore.m +1042 -119
  40. package/ios/NotifeeCore/NotifeeCoreUtil.h +36 -1
  41. package/ios/NotifeeCore/NotifeeCoreUtil.m +533 -9
  42. package/ios/RNNotifee/NotifeeApiModule.mm +2 -2
  43. package/package.json +1 -1
  44. package/src/index.ts +2 -0
  45. package/src/types/Module.ts +27 -13
  46. package/src/types/Notification.ts +1 -1
  47. package/src/types/NotificationIOS.ts +43 -28
  48. package/src/types/PowerManagerInfo.ts +9 -4
  49. package/src/types/Trigger.ts +45 -1
  50. package/src/validators/validateIOSCategory.ts +4 -2
  51. package/src/validators/validateTrigger.ts +38 -0
  52. package/src/version.ts +1 -1
@@ -17,6 +17,7 @@
17
17
 
18
18
  #import <UIKit/UIKit.h>
19
19
  #import <dispatch/dispatch.h>
20
+ #include <math.h>
20
21
 
21
22
  #import "Intents/Intents.h"
22
23
  #import "NotifeeCore+UNUserNotificationCenter.h"
@@ -25,8 +26,935 @@
25
26
  #import "NotifeeCoreExtensionHelper.h"
26
27
  #import "NotifeeCoreUtil.h"
27
28
 
29
+ static NSString *const kNotifeeRollingPublicId = @"notifee_rolling_public_id";
30
+ static NSString *const kNotifeeRollingOccurrenceMs = @"notifee_rolling_occurrence_ms";
31
+ static NSString *const kNotifeeRollingInternalId = @"notifee_rolling_internal_id";
32
+ static NSString *const kNotifeeRollingInternalIdPrefix = @"__notifee_rolling__";
33
+ static NSString *const kNotifeeCoreErrorDomain = @"app.notifee.core";
34
+
35
+ typedef NS_ENUM(NSInteger, NotifeeCoreRollingErrorCode) {
36
+ NotifeeCoreRollingErrorCodeInvalidTrigger = 1,
37
+ NotifeeCoreRollingErrorCodeBudgetExceeded = 2,
38
+ NotifeeCoreRollingErrorCodeStorageFailed = 3,
39
+ };
40
+
28
41
  @implementation NotifeeCore
29
42
 
43
+ + (dispatch_queue_t)rollingTimestampQueue {
44
+ static dispatch_queue_t queue;
45
+ static dispatch_once_t onceToken;
46
+ dispatch_once(&onceToken, ^{
47
+ queue = dispatch_queue_create("app.notifee.core.rollingTimestamp", DISPATCH_QUEUE_SERIAL);
48
+ });
49
+ return queue;
50
+ }
51
+
52
+ + (NSError *)rollingTimestampErrorWithCode:(NotifeeCoreRollingErrorCode)code
53
+ message:(NSString *)message {
54
+ return [NSError errorWithDomain:kNotifeeCoreErrorDomain
55
+ code:code
56
+ userInfo:@{NSLocalizedDescriptionKey : message}];
57
+ }
58
+
59
+ + (NSNumber *)currentTimestampMs {
60
+ return @((long long)llround([[NSDate date] timeIntervalSince1970] * 1000.0));
61
+ }
62
+
63
+ + (void)resolveBlock:(notifeeMethodVoidBlock)block withError:(NSError *)error {
64
+ dispatch_async(dispatch_get_main_queue(), ^{
65
+ block(error);
66
+ });
67
+ }
68
+
69
+ + (NSArray<UNNotificationRequest *> *)pendingNotificationRequests:
70
+ (UNUserNotificationCenter *)center {
71
+ __block NSArray<UNNotificationRequest *> *pendingRequests = @[];
72
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
73
+
74
+ [center getPendingNotificationRequestsWithCompletionHandler:^(
75
+ NSArray<UNNotificationRequest *> *_Nonnull requests) {
76
+ pendingRequests = requests != nil ? requests : @[];
77
+ dispatch_semaphore_signal(semaphore);
78
+ }];
79
+
80
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
81
+ return pendingRequests;
82
+ }
83
+
84
+ + (NSArray<UNNotification *> *)deliveredNotifications:(UNUserNotificationCenter *)center {
85
+ __block NSArray<UNNotification *> *deliveredNotifications = @[];
86
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
87
+
88
+ [center getDeliveredNotificationsWithCompletionHandler:^(
89
+ NSArray<UNNotification *> *_Nonnull notifications) {
90
+ deliveredNotifications = notifications != nil ? notifications : @[];
91
+ dispatch_semaphore_signal(semaphore);
92
+ }];
93
+
94
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
95
+ return deliveredNotifications;
96
+ }
97
+
98
+ + (NSString *)rollingPublicIdForNotification:(NSDictionary *)notification {
99
+ NSString *publicId = notification[@"id"];
100
+ if (![publicId isKindOfClass:NSString.class] || [publicId length] == 0) {
101
+ return nil;
102
+ }
103
+
104
+ return publicId;
105
+ }
106
+
107
+ + (BOOL)isPotentialRollingInternalNotificationId:(NSString *)notificationId {
108
+ return [notificationId isKindOfClass:NSString.class] &&
109
+ [notificationId hasPrefix:kNotifeeRollingInternalIdPrefix];
110
+ }
111
+
112
+ + (NSString *)rollingPublicIdForRequest:(UNNotificationRequest *)request {
113
+ NSString *identifier = request.identifier;
114
+ NSString *mappedPublicId = [NotifeeCoreUtil rollingPublicIdFromInternalNotificationId:identifier];
115
+ if ([mappedPublicId isKindOfClass:NSString.class] && [mappedPublicId length] > 0) {
116
+ return mappedPublicId;
117
+ }
118
+
119
+ if ([self isPotentialRollingInternalNotificationId:identifier]) {
120
+ NSString *metadataPublicId = request.content.userInfo[kNotifeeRollingPublicId];
121
+ if ([metadataPublicId isKindOfClass:NSString.class] && [metadataPublicId length] > 0) {
122
+ return metadataPublicId;
123
+ }
124
+
125
+ NSDictionary *notification = request.content.userInfo[kNotifeeUserInfoNotification];
126
+ if ([notification isKindOfClass:NSDictionary.class]) {
127
+ NSString *payloadPublicId = notification[@"id"];
128
+ if ([payloadPublicId isKindOfClass:NSString.class] && [payloadPublicId length] > 0) {
129
+ return payloadPublicId;
130
+ }
131
+ }
132
+ }
133
+
134
+ return nil;
135
+ }
136
+
137
+ + (NSString *)publicIdentifierForRequest:(UNNotificationRequest *)request {
138
+ NSString *rollingPublicId = [self rollingPublicIdForRequest:request];
139
+ if (rollingPublicId != nil) {
140
+ return rollingPublicId;
141
+ }
142
+
143
+ NSString *identifier = request.identifier;
144
+ if ([self isPotentialRollingInternalNotificationId:identifier]) {
145
+ return nil;
146
+ }
147
+
148
+ return identifier;
149
+ }
150
+
151
+ + (void)addString:(NSString *)string toOrderedSet:(NSMutableOrderedSet<NSString *> *)orderedSet {
152
+ if ([string isKindOfClass:NSString.class] && [string length] > 0) {
153
+ [orderedSet addObject:string];
154
+ }
155
+ }
156
+
157
+ + (NSMutableOrderedSet<NSString *> *)
158
+ rollingIdentifiersForPublicId:(NSString *)publicId
159
+ pendingRequests:(NSArray<UNNotificationRequest *> *)pendingRequests
160
+ record:(NSDictionary *)record {
161
+ NSMutableOrderedSet<NSString *> *identifiers = [NSMutableOrderedSet orderedSet];
162
+
163
+ NSArray *scheduledIds = record[@"scheduledIds"];
164
+ if ([scheduledIds isKindOfClass:NSArray.class]) {
165
+ for (id scheduledId in scheduledIds) {
166
+ [self addString:scheduledId toOrderedSet:identifiers];
167
+ }
168
+ }
169
+
170
+ for (UNNotificationRequest *request in pendingRequests) {
171
+ NSString *identifier = request.identifier;
172
+ if ([identifier isEqualToString:publicId]) {
173
+ [self addString:identifier toOrderedSet:identifiers];
174
+ continue;
175
+ }
176
+
177
+ NSString *mappedPublicId =
178
+ [NotifeeCoreUtil rollingPublicIdFromInternalNotificationId:identifier];
179
+ if ([mappedPublicId isEqualToString:publicId]) {
180
+ [self addString:identifier toOrderedSet:identifiers];
181
+ }
182
+ }
183
+
184
+ return identifiers;
185
+ }
186
+
187
+ + (NSMutableOrderedSet<NSString *> *)
188
+ rollingDeliveredIdentifiersForPublicId:(NSString *)publicId
189
+ deliveredNotifications:(NSArray<UNNotification *> *)deliveredNotifications
190
+ record:(NSDictionary *)record {
191
+ NSMutableOrderedSet<NSString *> *identifiers = [NSMutableOrderedSet orderedSet];
192
+
193
+ NSArray *scheduledIds = record[@"scheduledIds"];
194
+ if ([scheduledIds isKindOfClass:NSArray.class]) {
195
+ for (id scheduledId in scheduledIds) {
196
+ [self addString:scheduledId toOrderedSet:identifiers];
197
+ }
198
+ }
199
+
200
+ for (UNNotification *notification in deliveredNotifications) {
201
+ UNNotificationRequest *request = notification.request;
202
+ NSString *identifier = request.identifier;
203
+ if ([identifier isEqualToString:publicId]) {
204
+ [self addString:identifier toOrderedSet:identifiers];
205
+ continue;
206
+ }
207
+
208
+ NSString *mappedPublicId = [self rollingPublicIdForRequest:request];
209
+ if ([mappedPublicId isEqualToString:publicId]) {
210
+ [self addString:identifier toOrderedSet:identifiers];
211
+ }
212
+ }
213
+
214
+ return identifiers;
215
+ }
216
+
217
+ + (NSUInteger)pendingRequestCountForIdentifiers:(NSOrderedSet<NSString *> *)identifiers
218
+ pendingRequests:
219
+ (NSArray<UNNotificationRequest *> *)pendingRequests {
220
+ NSUInteger count = 0;
221
+ for (UNNotificationRequest *request in pendingRequests) {
222
+ if ([identifiers containsObject:request.identifier]) {
223
+ count += 1;
224
+ }
225
+ }
226
+ return count;
227
+ }
228
+
229
+ + (UNCalendarNotificationTrigger *)rollingOneShotTriggerForOccurrenceMs:(NSNumber *)occurrenceMs {
230
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:([occurrenceMs doubleValue] / 1000.0)];
231
+ NSDateComponents *components = [[NSCalendar currentCalendar]
232
+ components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour |
233
+ NSCalendarUnitMinute | NSCalendarUnitSecond
234
+ fromDate:date];
235
+
236
+ return [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:NO];
237
+ }
238
+
239
+ + (UNMutableNotificationContent *)rollingContentFromContent:(UNMutableNotificationContent *)content
240
+ publicId:(NSString *)publicId
241
+ occurrenceMs:(NSNumber *)occurrenceMs
242
+ internalId:(NSString *)internalId {
243
+ UNMutableNotificationContent *rollingContent = [content mutableCopy];
244
+ NSMutableDictionary *userInfo = [rollingContent.userInfo mutableCopy];
245
+ userInfo[kNotifeeRollingPublicId] = publicId;
246
+ userInfo[kNotifeeRollingOccurrenceMs] = occurrenceMs;
247
+ userInfo[kNotifeeRollingInternalId] = internalId;
248
+ rollingContent.userInfo = userInfo;
249
+
250
+ return rollingContent;
251
+ }
252
+
253
+ + (UNMutableNotificationContent *)
254
+ triggerNotificationContentForNotification:(NSDictionary *)notification
255
+ trigger:(NSDictionary *)trigger {
256
+ UNMutableNotificationContent *content = [self buildNotificationContent:notification
257
+ withTrigger:trigger];
258
+
259
+ if (@available(iOS 15.0, *)) {
260
+ if (notification[@"ios"][@"communicationInfo"] != nil) {
261
+ INSendMessageIntent *intent = [NotifeeCoreUtil
262
+ generateSenderIntentForCommunicationNotification:notification[@"ios"]
263
+ [@"communicationInfo"]];
264
+
265
+ INInteraction *interaction = [[INInteraction alloc] initWithIntent:intent response:nil];
266
+ interaction.direction = INInteractionDirectionIncoming;
267
+ [interaction donateInteractionWithCompletion:^(NSError *donateError) {
268
+ if (donateError)
269
+ NSLog(@"NotifeeCore: Could not donate interaction for communication notification: %@",
270
+ donateError);
271
+ }];
272
+
273
+ NSError *contentUpdateError = nil;
274
+ UNNotificationContent *updatedContent =
275
+ [content contentByUpdatingWithProvider:intent error:&contentUpdateError];
276
+ if (contentUpdateError) {
277
+ NSLog(@"NotifeeCore: Could not update notification content with communication intent: %@",
278
+ contentUpdateError);
279
+ } else if (updatedContent != nil) {
280
+ content = [updatedContent mutableCopy];
281
+ }
282
+ }
283
+ }
284
+
285
+ return content;
286
+ }
287
+
288
+ + (UNNotificationRequest *)rollingNotificationRequestForPublicId:(NSString *)publicId
289
+ occurrenceMs:(NSNumber *)occurrenceMs
290
+ content:
291
+ (UNMutableNotificationContent *)content
292
+ error:(NSError **)error {
293
+ NSString *internalId = [NotifeeCoreUtil rollingInternalNotificationIdForPublicId:publicId
294
+ occurrenceMs:occurrenceMs];
295
+ if (internalId == nil) {
296
+ if (error != nil) {
297
+ *error =
298
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
299
+ message:@"NotifeeCore: Failed to create rolling timestamp "
300
+ @"notification identifier."];
301
+ }
302
+ return nil;
303
+ }
304
+
305
+ UNMutableNotificationContent *rollingContent = [self rollingContentFromContent:content
306
+ publicId:publicId
307
+ occurrenceMs:occurrenceMs
308
+ internalId:internalId];
309
+ return [UNNotificationRequest
310
+ requestWithIdentifier:internalId
311
+ content:rollingContent
312
+ trigger:[self rollingOneShotTriggerForOccurrenceMs:occurrenceMs]];
313
+ }
314
+
315
+ + (void)removeRollingPendingRequestsForIds:(NSArray<NSString *> *)identifiers
316
+ center:(UNUserNotificationCenter *)center {
317
+ if ([identifiers count] > 0) {
318
+ [center removePendingNotificationRequestsWithIdentifiers:identifiers];
319
+ }
320
+ }
321
+
322
+ + (NSMutableSet<NSString *> *)pendingIdentifierSetFromRequests:
323
+ (NSArray<UNNotificationRequest *> *)pendingRequests {
324
+ NSMutableSet<NSString *> *identifiers = [NSMutableSet set];
325
+ for (UNNotificationRequest *request in pendingRequests) {
326
+ NSString *identifier = request.identifier;
327
+ if ([identifier isKindOfClass:NSString.class] && [identifier length] > 0) {
328
+ [identifiers addObject:identifier];
329
+ }
330
+ }
331
+ return identifiers;
332
+ }
333
+
334
+ + (NSComparisonResult)compareRollingRebalanceState:(NSDictionary *)firstState
335
+ otherState:(NSDictionary *)secondState {
336
+ return [firstState[@"publicId"] compare:secondState[@"publicId"]];
337
+ }
338
+
339
+ + (NSError *)scheduleRollingNotificationRequests:(NSArray<UNNotificationRequest *> *)requests
340
+ center:(UNUserNotificationCenter *)center
341
+ successfulIdentifiers:(NSMutableSet<NSString *> *)successfulIdentifiers {
342
+ if ([requests count] == 0) {
343
+ return nil;
344
+ }
345
+
346
+ dispatch_group_t group = dispatch_group_create();
347
+ NSObject *scheduleLock = [[NSObject alloc] init];
348
+ __block NSError *scheduleError = nil;
349
+
350
+ for (__unused UNNotificationRequest *request in requests) {
351
+ dispatch_group_enter(group);
352
+ }
353
+
354
+ dispatch_async(dispatch_get_main_queue(), ^{
355
+ for (UNNotificationRequest *request in requests) {
356
+ [center addNotificationRequest:request
357
+ withCompletionHandler:^(NSError *_Nullable error) {
358
+ @synchronized(scheduleLock) {
359
+ if (error != nil && scheduleError == nil) {
360
+ scheduleError = error;
361
+ } else if (error == nil && successfulIdentifiers != nil) {
362
+ [successfulIdentifiers addObject:request.identifier];
363
+ }
364
+ }
365
+ dispatch_group_leave(group);
366
+ }];
367
+ }
368
+ });
369
+
370
+ dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
371
+ return scheduleError;
372
+ }
373
+
374
+ + (BOOL)persistedRollingRecords:(NSDictionary *)persistedRecords
375
+ matchUpdatedRecords:(NSDictionary *)updatedRecords {
376
+ if ([persistedRecords count] != [updatedRecords count]) {
377
+ return NO;
378
+ }
379
+
380
+ for (id publicId in updatedRecords) {
381
+ if (persistedRecords[publicId] == nil) {
382
+ return NO;
383
+ }
384
+ }
385
+
386
+ return YES;
387
+ }
388
+
389
+ + (NSArray<NSMutableDictionary *> *)rollingRebalanceStatesFromRecords:(NSDictionary *)records
390
+ nowMs:(NSNumber *)nowMs
391
+ requiredPublicId:(NSString *)requiredPublicId
392
+ error:(NSError **)error {
393
+ NSMutableArray<NSMutableDictionary *> *states = [NSMutableArray array];
394
+ BOOL foundRequiredPublicId = requiredPublicId == nil;
395
+
396
+ for (id key in records) {
397
+ if (![key isKindOfClass:NSString.class] || [key length] == 0) {
398
+ continue;
399
+ }
400
+
401
+ NSString *publicId = key;
402
+ BOOL isRequiredPublicId = [publicId isEqualToString:requiredPublicId];
403
+ if (isRequiredPublicId) {
404
+ foundRequiredPublicId = YES;
405
+ }
406
+
407
+ NSDictionary *record = records[publicId];
408
+ NSDictionary *notification = record[@"notification"];
409
+ NSDictionary *trigger = record[@"trigger"];
410
+ if (![notification isKindOfClass:NSDictionary.class] ||
411
+ ![trigger isKindOfClass:NSDictionary.class] ||
412
+ ![NotifeeCoreUtil isRollingTimestampTrigger:trigger]) {
413
+ if (isRequiredPublicId && error != nil) {
414
+ *error = [self
415
+ rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
416
+ message:@"NotifeeCore: Rolling timestamp trigger requires a "
417
+ @"valid notification and trigger record."];
418
+ return nil;
419
+ }
420
+ continue;
421
+ }
422
+
423
+ NSArray<NSNumber *> *firstOccurrence =
424
+ [NotifeeCoreUtil rollingTimestampOccurrencesFromTrigger:trigger nowMs:nowMs maxCount:1];
425
+ NSNumber *firstOccurrenceMs = [firstOccurrence firstObject];
426
+ NSString *firstInternalId =
427
+ [NotifeeCoreUtil rollingInternalNotificationIdForPublicId:publicId
428
+ occurrenceMs:firstOccurrenceMs];
429
+ if (firstOccurrenceMs == nil || firstInternalId == nil) {
430
+ if (isRequiredPublicId && error != nil) {
431
+ *error =
432
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
433
+ message:@"NotifeeCore: Rolling timestamp trigger did not "
434
+ @"produce any future occurrences."];
435
+ return nil;
436
+ }
437
+ continue;
438
+ }
439
+
440
+ NSNumber *createdAt = record[@"createdAtMs"];
441
+ if (![createdAt isKindOfClass:NSNumber.class]) {
442
+ createdAt = nowMs;
443
+ }
444
+
445
+ NSMutableDictionary *state = [NSMutableDictionary dictionary];
446
+ state[@"publicId"] = publicId;
447
+ state[@"notification"] = notification;
448
+ state[@"trigger"] = trigger;
449
+ state[@"createdAtMs"] = createdAt;
450
+ state[@"quota"] = @0;
451
+ [states addObject:state];
452
+ }
453
+
454
+ if (!foundRequiredPublicId) {
455
+ if (error != nil) {
456
+ *error = [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
457
+ message:@"NotifeeCore: Rolling timestamp trigger "
458
+ @"record was not found for rebalance."];
459
+ }
460
+ return nil;
461
+ }
462
+
463
+ [states
464
+ sortUsingComparator:^NSComparisonResult(NSDictionary *firstState, NSDictionary *secondState) {
465
+ return [self compareRollingRebalanceState:firstState otherState:secondState];
466
+ }];
467
+
468
+ return states;
469
+ }
470
+
471
+ + (BOOL)prepareRollingRebalanceDesiredSchedulesForStates:(NSArray<NSMutableDictionary *> *)states
472
+ nowMs:(NSNumber *)nowMs
473
+ error:(NSError **)error {
474
+ for (NSMutableDictionary *state in states) {
475
+ NSInteger quota = [state[@"quota"] integerValue];
476
+ if (quota <= 0) {
477
+ continue;
478
+ }
479
+
480
+ NSString *publicId = state[@"publicId"];
481
+ NSDictionary *notification = state[@"notification"];
482
+ NSDictionary *trigger = state[@"trigger"];
483
+ NSArray<NSNumber *> *occurrences =
484
+ [NotifeeCoreUtil rollingTimestampOccurrencesFromTrigger:trigger nowMs:nowMs maxCount:quota];
485
+ if ([occurrences count] == 0) {
486
+ if (error != nil) {
487
+ *error =
488
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
489
+ message:@"NotifeeCore: Rolling timestamp trigger did not "
490
+ @"produce any future occurrences."];
491
+ }
492
+ return NO;
493
+ }
494
+
495
+ UNMutableNotificationContent *content =
496
+ [self triggerNotificationContentForNotification:notification trigger:trigger];
497
+ NSMutableOrderedSet<NSString *> *desiredIds = [NSMutableOrderedSet orderedSet];
498
+ NSMutableDictionary<NSString *, UNNotificationRequest *> *desiredRequests =
499
+ [NSMutableDictionary dictionary];
500
+ NSNumber *lastScheduledOccurrence = nil;
501
+
502
+ for (NSNumber *occurrenceMs in occurrences) {
503
+ if ([desiredIds count] >= (NSUInteger)quota) {
504
+ break;
505
+ }
506
+
507
+ NSError *requestError = nil;
508
+ UNNotificationRequest *request = [self rollingNotificationRequestForPublicId:publicId
509
+ occurrenceMs:occurrenceMs
510
+ content:content
511
+ error:&requestError];
512
+ if (request == nil) {
513
+ if (error != nil) {
514
+ *error = requestError;
515
+ }
516
+ return NO;
517
+ }
518
+
519
+ if ([desiredIds containsObject:request.identifier]) {
520
+ continue;
521
+ }
522
+
523
+ [desiredIds addObject:request.identifier];
524
+ desiredRequests[request.identifier] = request;
525
+ lastScheduledOccurrence = occurrenceMs;
526
+ }
527
+
528
+ if ([desiredIds count] == 0 || lastScheduledOccurrence == nil) {
529
+ if (error != nil) {
530
+ *error =
531
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
532
+ message:@"NotifeeCore: Rolling timestamp trigger did not "
533
+ @"produce any future occurrences."];
534
+ }
535
+ return NO;
536
+ }
537
+
538
+ NSMutableDictionary *updatedRecord = [NSMutableDictionary dictionary];
539
+ updatedRecord[@"publicId"] = publicId;
540
+ updatedRecord[@"notification"] = notification;
541
+ updatedRecord[@"trigger"] = trigger;
542
+ updatedRecord[@"scheduledIds"] = [desiredIds array];
543
+ updatedRecord[@"lastScheduledOccurrenceMs"] = lastScheduledOccurrence;
544
+ updatedRecord[@"createdAtMs"] = state[@"createdAtMs"];
545
+
546
+ state[@"desiredIds"] = desiredIds;
547
+ state[@"desiredRequests"] = desiredRequests;
548
+ state[@"updatedRecord"] = updatedRecord;
549
+ }
550
+
551
+ return YES;
552
+ }
553
+
554
+ + (void)rollbackRollingRebalanceRemovedRequests:(NSArray<UNNotificationRequest *> *)removedRequests
555
+ successfulNewIdentifiers:(NSSet<NSString *> *)successfulNewIdentifiers
556
+ center:(UNUserNotificationCenter *)center {
557
+ if ([successfulNewIdentifiers count] > 0) {
558
+ [self removeRollingPendingRequestsForIds:[successfulNewIdentifiers allObjects] center:center];
559
+ }
560
+
561
+ if ([removedRequests count] > 0) {
562
+ [self scheduleRollingNotificationRequests:removedRequests
563
+ center:center
564
+ successfulIdentifiers:nil];
565
+ }
566
+ }
567
+
568
+ + (BOOL)rebalanceRollingTimestampTriggerRecords:(NSDictionary *)records
569
+ center:(UNUserNotificationCenter *)center
570
+ pendingRequests:(NSArray<UNNotificationRequest *> *)pendingRequests
571
+ nowMs:(NSNumber *)nowMs
572
+ refreshPublicId:(NSString *)refreshPublicId
573
+ requiredPublicId:(NSString *)requiredPublicId
574
+ error:(NSError **)error {
575
+ NSError *stateError = nil;
576
+ NSArray<NSMutableDictionary *> *states = [self rollingRebalanceStatesFromRecords:records
577
+ nowMs:nowMs
578
+ requiredPublicId:requiredPublicId
579
+ error:&stateError];
580
+ if (states == nil) {
581
+ if (error != nil) {
582
+ *error = stateError;
583
+ }
584
+ return NO;
585
+ }
586
+
587
+ NSMutableSet<NSString *> *pendingIdentifierSet =
588
+ [self pendingIdentifierSetFromRequests:pendingRequests];
589
+ NSMutableDictionary<NSString *, UNNotificationRequest *> *pendingRequestByIdentifier =
590
+ [NSMutableDictionary dictionary];
591
+ NSMutableOrderedSet<NSString *> *rollingPendingIdentifiers = [NSMutableOrderedSet orderedSet];
592
+ NSMutableOrderedSet<NSString *> *replacementPendingIdentifiers = [NSMutableOrderedSet orderedSet];
593
+ NSInteger nonRollingPendingCount = 0;
594
+
595
+ for (UNNotificationRequest *request in pendingRequests) {
596
+ NSString *identifier = request.identifier;
597
+ if (![identifier isKindOfClass:NSString.class] || [identifier length] == 0) {
598
+ continue;
599
+ }
600
+
601
+ pendingRequestByIdentifier[identifier] = request;
602
+ if ([self isPotentialRollingInternalNotificationId:identifier]) {
603
+ [rollingPendingIdentifiers addObject:identifier];
604
+ } else if ([identifier isEqualToString:refreshPublicId]) {
605
+ [replacementPendingIdentifiers addObject:identifier];
606
+ } else {
607
+ nonRollingPendingCount += 1;
608
+ }
609
+ }
610
+
611
+ NSInteger rollingBudget = [NotifeeCoreUtil rollingPendingBudget] - nonRollingPendingCount;
612
+ if (rollingBudget < 0) {
613
+ rollingBudget = 0;
614
+ }
615
+
616
+ if ((NSInteger)[states count] > rollingBudget) {
617
+ if (error != nil) {
618
+ *error = [self
619
+ rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeBudgetExceeded
620
+ message:@"NotifeeCore: iOS rolling timestamp trigger budget "
621
+ @"cannot allocate one occurrence per active trigger."];
622
+ }
623
+ return NO;
624
+ }
625
+
626
+ NSInteger targetPerTrigger = [NotifeeCoreUtil rollingTargetPerTrigger];
627
+ NSInteger remainingBudget = rollingBudget;
628
+ for (NSMutableDictionary *state in states) {
629
+ state[@"quota"] = @1;
630
+ remainingBudget -= 1;
631
+ }
632
+
633
+ BOOL allocatedAdditionalSlot = YES;
634
+ while (remainingBudget > 0 && allocatedAdditionalSlot) {
635
+ allocatedAdditionalSlot = NO;
636
+ for (NSMutableDictionary *state in states) {
637
+ if (remainingBudget <= 0) {
638
+ break;
639
+ }
640
+
641
+ NSInteger quota = [state[@"quota"] integerValue];
642
+ if (quota >= targetPerTrigger) {
643
+ continue;
644
+ }
645
+
646
+ state[@"quota"] = @(quota + 1);
647
+ remainingBudget -= 1;
648
+ allocatedAdditionalSlot = YES;
649
+ }
650
+ }
651
+
652
+ NSError *planError = nil;
653
+ if (![self prepareRollingRebalanceDesiredSchedulesForStates:states
654
+ nowMs:nowMs
655
+ error:&planError]) {
656
+ if (error != nil) {
657
+ *error = planError;
658
+ }
659
+ return NO;
660
+ }
661
+
662
+ NSMutableOrderedSet<NSString *> *desiredIdentifiers = [NSMutableOrderedSet orderedSet];
663
+ NSMutableDictionary *updatedRecords = [NSMutableDictionary dictionary];
664
+ for (NSMutableDictionary *state in states) {
665
+ NSOrderedSet<NSString *> *stateDesiredIds = state[@"desiredIds"];
666
+ [desiredIdentifiers unionOrderedSet:stateDesiredIds];
667
+
668
+ NSString *publicId = state[@"publicId"];
669
+ NSDictionary *updatedRecord = state[@"updatedRecord"];
670
+ if (publicId != nil && updatedRecord != nil) {
671
+ updatedRecords[publicId] = updatedRecord;
672
+ }
673
+ }
674
+
675
+ if (requiredPublicId != nil && updatedRecords[requiredPublicId] == nil) {
676
+ if (error != nil) {
677
+ *error = [self
678
+ rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
679
+ message:@"NotifeeCore: Rolling timestamp trigger did not produce "
680
+ @"any future occurrences."];
681
+ }
682
+ return NO;
683
+ }
684
+
685
+ NSMutableOrderedSet<NSString *> *identifiersToRemove = [NSMutableOrderedSet orderedSet];
686
+ for (NSString *identifier in rollingPendingIdentifiers) {
687
+ if (![desiredIdentifiers containsObject:identifier]) {
688
+ [identifiersToRemove addObject:identifier];
689
+ }
690
+ }
691
+ [identifiersToRemove unionOrderedSet:replacementPendingIdentifiers];
692
+
693
+ NSMutableArray<UNNotificationRequest *> *removedRequests = [NSMutableArray array];
694
+ for (NSString *identifier in identifiersToRemove) {
695
+ UNNotificationRequest *request = pendingRequestByIdentifier[identifier];
696
+ if (request != nil) {
697
+ [removedRequests addObject:request];
698
+ }
699
+ }
700
+
701
+ NSMutableArray<UNNotificationRequest *> *requestsToSchedule = [NSMutableArray array];
702
+ NSMutableSet<NSString *> *newRequestIdentifiers = [NSMutableSet set];
703
+ for (NSMutableDictionary *state in states) {
704
+ NSString *publicId = state[@"publicId"];
705
+ NSOrderedSet<NSString *> *stateDesiredIds = state[@"desiredIds"];
706
+ NSDictionary<NSString *, UNNotificationRequest *> *desiredRequests = state[@"desiredRequests"];
707
+ BOOL shouldRefresh = refreshPublicId != nil && [publicId isEqualToString:refreshPublicId];
708
+
709
+ for (NSString *identifier in stateDesiredIds) {
710
+ UNNotificationRequest *request = desiredRequests[identifier];
711
+ if (request == nil) {
712
+ continue;
713
+ }
714
+
715
+ BOOL isAlreadyPending = [pendingIdentifierSet containsObject:identifier];
716
+ if (!isAlreadyPending || shouldRefresh) {
717
+ [requestsToSchedule addObject:request];
718
+ }
719
+ if (!isAlreadyPending) {
720
+ [newRequestIdentifiers addObject:identifier];
721
+ }
722
+ }
723
+ }
724
+
725
+ [self removeRollingPendingRequestsForIds:[identifiersToRemove array] center:center];
726
+
727
+ NSMutableSet<NSString *> *successfulScheduledIdentifiers = [NSMutableSet set];
728
+ NSError *scheduleError =
729
+ [self scheduleRollingNotificationRequests:requestsToSchedule
730
+ center:center
731
+ successfulIdentifiers:successfulScheduledIdentifiers];
732
+ if (scheduleError != nil) {
733
+ NSMutableSet<NSString *> *successfulNewIdentifiers = [NSMutableSet set];
734
+ for (NSString *identifier in successfulScheduledIdentifiers) {
735
+ if ([newRequestIdentifiers containsObject:identifier]) {
736
+ [successfulNewIdentifiers addObject:identifier];
737
+ }
738
+ }
739
+
740
+ [self rollbackRollingRebalanceRemovedRequests:removedRequests
741
+ successfulNewIdentifiers:successfulNewIdentifiers
742
+ center:center];
743
+ if (error != nil) {
744
+ *error = scheduleError;
745
+ }
746
+ return NO;
747
+ }
748
+
749
+ [NotifeeCoreUtil setRollingTimestampTriggers:updatedRecords];
750
+ NSDictionary *persistedRecords = [NotifeeCoreUtil getRollingTimestampTriggers];
751
+ if (![self persistedRollingRecords:persistedRecords matchUpdatedRecords:updatedRecords]) {
752
+ NSMutableSet<NSString *> *successfulNewIdentifiers = [NSMutableSet set];
753
+ for (NSString *identifier in successfulScheduledIdentifiers) {
754
+ if ([newRequestIdentifiers containsObject:identifier]) {
755
+ [successfulNewIdentifiers addObject:identifier];
756
+ }
757
+ }
758
+ [self removeRollingPendingRequestsForIds:[successfulNewIdentifiers allObjects] center:center];
759
+
760
+ if (error != nil) {
761
+ *error =
762
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeStorageFailed
763
+ message:@"NotifeeCore: Failed to persist rolling timestamp "
764
+ @"trigger records after rebalance."];
765
+ }
766
+ return NO;
767
+ }
768
+
769
+ return YES;
770
+ }
771
+
772
+ + (void)topUpRollingTimestampTriggersWithCompletion:(void (^)(NSError *error))completion {
773
+ dispatch_async([self rollingTimestampQueue], ^{
774
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
775
+ NSNumber *nowMs = [self currentTimestampMs];
776
+ NSArray<UNNotificationRequest *> *pendingRequests = [self pendingNotificationRequests:center];
777
+ NSMutableDictionary *records = [NotifeeCoreUtil getRollingTimestampTriggers];
778
+ NSError *topUpError = nil;
779
+
780
+ [self rebalanceRollingTimestampTriggerRecords:records
781
+ center:center
782
+ pendingRequests:pendingRequests
783
+ nowMs:nowMs
784
+ refreshPublicId:nil
785
+ requiredPublicId:nil
786
+ error:&topUpError];
787
+
788
+ if (completion != nil) {
789
+ dispatch_async(dispatch_get_main_queue(), ^{
790
+ completion(topUpError);
791
+ });
792
+ }
793
+ });
794
+ }
795
+
796
+ + (void)createRollingTriggerNotification:(NSDictionary *)notification
797
+ withTrigger:(NSDictionary *)trigger
798
+ withNotificationDetail:(NSDictionary *)notificationDetail
799
+ center:(UNUserNotificationCenter *)center
800
+ block:(notifeeMethodVoidBlock)block {
801
+ dispatch_async([self rollingTimestampQueue], ^{
802
+ NSString *publicId = [self rollingPublicIdForNotification:notification];
803
+ if (publicId == nil) {
804
+ NSError *error =
805
+ [self rollingTimestampErrorWithCode:NotifeeCoreRollingErrorCodeInvalidTrigger
806
+ message:@"NotifeeCore: Rolling timestamp trigger requires a "
807
+ @"notification id."];
808
+ [self resolveBlock:block withError:error];
809
+ return;
810
+ }
811
+
812
+ NSArray<UNNotificationRequest *> *pendingRequests = [self pendingNotificationRequests:center];
813
+ NSMutableDictionary *records = [NotifeeCoreUtil getRollingTimestampTriggers];
814
+ NSNumber *nowMs = [self currentTimestampMs];
815
+ NSDictionary *record = @{
816
+ @"publicId" : publicId,
817
+ @"notification" : notification,
818
+ @"trigger" : trigger,
819
+ @"scheduledIds" : @[],
820
+ @"createdAtMs" : nowMs
821
+ };
822
+ records[publicId] = record;
823
+
824
+ NSError *rebalanceError = nil;
825
+ if (![self rebalanceRollingTimestampTriggerRecords:records
826
+ center:center
827
+ pendingRequests:pendingRequests
828
+ nowMs:nowMs
829
+ refreshPublicId:publicId
830
+ requiredPublicId:publicId
831
+ error:&rebalanceError]) {
832
+ [self resolveBlock:block withError:rebalanceError];
833
+ return;
834
+ }
835
+
836
+ dispatch_async(dispatch_get_main_queue(), ^{
837
+ [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{
838
+ @"type" : @(NotifeeCoreEventTypeTriggerNotificationCreated),
839
+ @"detail" : @{
840
+ @"notification" : notificationDetail,
841
+ }
842
+ }];
843
+ block(nil);
844
+ });
845
+ });
846
+ }
847
+
848
+ + (void)cancelRollingNotification:(NSString *)notificationId
849
+ withNotificationType:(NSInteger)notificationType
850
+ block:(notifeeMethodVoidBlock)block {
851
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
852
+ BOOL cancelDisplayed = notificationType == NotifeeCoreNotificationTypeDisplayed ||
853
+ notificationType == NotifeeCoreNotificationTypeAll;
854
+ BOOL cancelTrigger = notificationType == NotifeeCoreNotificationTypeTrigger ||
855
+ notificationType == NotifeeCoreNotificationTypeAll;
856
+
857
+ if (!cancelDisplayed && !cancelTrigger) {
858
+ block(nil);
859
+ return;
860
+ }
861
+
862
+ dispatch_async([self rollingTimestampQueue], ^{
863
+ NSDictionary *records = [NotifeeCoreUtil getRollingTimestampTriggers];
864
+ NSDictionary *record = records[notificationId];
865
+
866
+ if (cancelDisplayed) {
867
+ NSArray<UNNotification *> *deliveredNotifications = [self deliveredNotifications:center];
868
+ NSMutableOrderedSet<NSString *> *deliveredIdentifiersToRemove =
869
+ [self rollingDeliveredIdentifiersForPublicId:notificationId
870
+ deliveredNotifications:deliveredNotifications
871
+ record:record];
872
+ [self addString:notificationId toOrderedSet:deliveredIdentifiersToRemove];
873
+ if ([deliveredIdentifiersToRemove count] > 0) {
874
+ [center removeDeliveredNotificationsWithIdentifiers:[deliveredIdentifiersToRemove array]];
875
+ }
876
+ }
877
+
878
+ if (cancelTrigger) {
879
+ NSArray<UNNotificationRequest *> *pendingRequests = [self pendingNotificationRequests:center];
880
+ NSMutableOrderedSet<NSString *> *pendingIdentifiersToRemove =
881
+ [self rollingIdentifiersForPublicId:notificationId
882
+ pendingRequests:pendingRequests
883
+ record:record];
884
+ [self addString:notificationId toOrderedSet:pendingIdentifiersToRemove];
885
+
886
+ [self removeRollingPendingRequestsForIds:[pendingIdentifiersToRemove array] center:center];
887
+ if (record != nil) {
888
+ [NotifeeCoreUtil removeRollingTimestampTriggerRecordForPublicId:notificationId];
889
+ }
890
+ }
891
+
892
+ [self resolveBlock:block withError:nil];
893
+ });
894
+ }
895
+
896
+ + (void)cancelRollingNotificationsWithIds:(NSArray<NSString *> *)ids
897
+ withNotificationType:(NSInteger)notificationType
898
+ block:(notifeeMethodVoidBlock)block {
899
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
900
+ BOOL cancelDisplayed = notificationType == NotifeeCoreNotificationTypeDisplayed ||
901
+ notificationType == NotifeeCoreNotificationTypeAll;
902
+ BOOL cancelTrigger = notificationType == NotifeeCoreNotificationTypeTrigger ||
903
+ notificationType == NotifeeCoreNotificationTypeAll;
904
+
905
+ NSMutableOrderedSet<NSString *> *publicIds = [NSMutableOrderedSet orderedSet];
906
+ for (id notificationId in ids) {
907
+ [self addString:notificationId toOrderedSet:publicIds];
908
+ }
909
+
910
+ if (!cancelDisplayed && !cancelTrigger) {
911
+ block(nil);
912
+ return;
913
+ }
914
+
915
+ dispatch_async([self rollingTimestampQueue], ^{
916
+ NSDictionary *records = [NotifeeCoreUtil getRollingTimestampTriggers];
917
+ NSArray<UNNotification *> *deliveredNotifications =
918
+ cancelDisplayed ? [self deliveredNotifications:center] : @[];
919
+ NSArray<UNNotificationRequest *> *pendingRequests =
920
+ cancelTrigger ? [self pendingNotificationRequests:center] : @[];
921
+ NSMutableOrderedSet<NSString *> *deliveredIdentifiersToRemove =
922
+ [NSMutableOrderedSet orderedSet];
923
+ NSMutableOrderedSet<NSString *> *pendingIdentifiersToRemove = [NSMutableOrderedSet orderedSet];
924
+
925
+ for (NSString *publicId in publicIds) {
926
+ NSDictionary *record = records[publicId];
927
+
928
+ if (cancelDisplayed) {
929
+ NSMutableOrderedSet<NSString *> *rollingDeliveredIdentifiers =
930
+ [self rollingDeliveredIdentifiersForPublicId:publicId
931
+ deliveredNotifications:deliveredNotifications
932
+ record:record];
933
+ [deliveredIdentifiersToRemove unionOrderedSet:rollingDeliveredIdentifiers];
934
+ [self addString:publicId toOrderedSet:deliveredIdentifiersToRemove];
935
+ }
936
+
937
+ if (cancelTrigger) {
938
+ NSMutableOrderedSet<NSString *> *rollingPendingIdentifiers =
939
+ [self rollingIdentifiersForPublicId:publicId
940
+ pendingRequests:pendingRequests
941
+ record:record];
942
+ [pendingIdentifiersToRemove unionOrderedSet:rollingPendingIdentifiers];
943
+ [self addString:publicId toOrderedSet:pendingIdentifiersToRemove];
944
+ if (record != nil) {
945
+ [NotifeeCoreUtil removeRollingTimestampTriggerRecordForPublicId:publicId];
946
+ }
947
+ }
948
+ }
949
+
950
+ if ([deliveredIdentifiersToRemove count] > 0) {
951
+ [center removeDeliveredNotificationsWithIdentifiers:[deliveredIdentifiersToRemove array]];
952
+ }
953
+ [self removeRollingPendingRequestsForIds:[pendingIdentifiersToRemove array] center:center];
954
+ [self resolveBlock:block withError:nil];
955
+ });
956
+ }
957
+
30
958
  #pragma mark - Library Methods
31
959
 
32
960
  + (void)setCoreDelegate:(id<NotifeeCoreDelegate>)coreDelegate {
@@ -42,17 +970,7 @@
42
970
  + (void)cancelNotification:(NSString *)notificationId
43
971
  withNotificationType:(NSInteger)notificationType
44
972
  withBlock:(notifeeMethodVoidBlock)block {
45
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
46
- // cancel displayed notification
47
- if (notificationType == NotifeeCoreNotificationTypeDisplayed ||
48
- notificationType == NotifeeCoreNotificationTypeAll)
49
- [center removeDeliveredNotificationsWithIdentifiers:@[ notificationId ]];
50
-
51
- // cancel trigger notification
52
- if (notificationType == NotifeeCoreNotificationTypeTrigger ||
53
- notificationType == NotifeeCoreNotificationTypeAll)
54
- [center removePendingNotificationRequestsWithIdentifiers:@[ notificationId ]];
55
- block(nil);
973
+ [self cancelRollingNotification:notificationId withNotificationType:notificationType block:block];
56
974
  }
57
975
 
58
976
  /**
@@ -69,11 +987,17 @@
69
987
  notificationType == NotifeeCoreNotificationTypeAll)
70
988
  [center removeAllDeliveredNotifications];
71
989
 
72
- // cancel trigger notifications
73
- if (notificationType == NotifeeCoreNotificationTypeTrigger ||
74
- notificationType == NotifeeCoreNotificationTypeAll)
990
+ if (notificationType != NotifeeCoreNotificationTypeTrigger &&
991
+ notificationType != NotifeeCoreNotificationTypeAll) {
992
+ block(nil);
993
+ return;
994
+ }
995
+
996
+ dispatch_async([self rollingTimestampQueue], ^{
75
997
  [center removeAllPendingNotificationRequests];
76
- block(nil);
998
+ [NotifeeCoreUtil clearRollingTimestampTriggerRecords];
999
+ [self resolveBlock:block withError:nil];
1000
+ });
77
1001
  }
78
1002
 
79
1003
  /**
@@ -86,18 +1010,7 @@
86
1010
  + (void)cancelAllNotificationsWithIds:(NSInteger)notificationType
87
1011
  withIds:(NSArray<NSString *> *)ids
88
1012
  withBlock:(notifeeMethodVoidBlock)block {
89
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
90
-
91
- // cancel displayed notifications
92
- if (notificationType == NotifeeCoreNotificationTypeDisplayed ||
93
- notificationType == NotifeeCoreNotificationTypeAll)
94
- [center removeDeliveredNotificationsWithIdentifiers:ids];
95
-
96
- // cancel trigger notifications
97
- if (notificationType == NotifeeCoreNotificationTypeTrigger ||
98
- notificationType == NotifeeCoreNotificationTypeAll)
99
- [center removePendingNotificationRequestsWithIdentifiers:ids];
100
- block(nil);
1013
+ [self cancelRollingNotificationsWithIds:ids withNotificationType:notificationType block:block];
101
1014
  }
102
1015
 
103
1016
  + (void)getDisplayedNotifications:(notifeeMethodNSArrayBlock)block {
@@ -106,20 +1019,31 @@
106
1019
  [center getDeliveredNotificationsWithCompletionHandler:^(
107
1020
  NSArray<UNNotification *> *_Nonnull deliveredNotifications) {
108
1021
  for (UNNotification *deliveredNotification in deliveredNotifications) {
1022
+ UNNotificationRequest *request = deliveredNotification.request;
1023
+ NSString *notificationId = [self publicIdentifierForRequest:request];
1024
+ if (notificationId == nil) {
1025
+ continue;
1026
+ }
1027
+
109
1028
  NSMutableDictionary *triggerNotification = [NSMutableDictionary dictionary];
110
- triggerNotification[@"id"] = deliveredNotification.request.identifier;
1029
+ triggerNotification[@"id"] = notificationId;
111
1030
 
112
1031
  triggerNotification[@"date"] =
113
1032
  [NotifeeCoreUtil convertToTimestamp:deliveredNotification.date];
114
- triggerNotification[@"notification"] =
115
- deliveredNotification.request.content.userInfo[kNotifeeUserInfoNotification];
116
- triggerNotification[@"trigger"] =
117
- deliveredNotification.request.content.userInfo[kNotifeeUserInfoTrigger];
1033
+ triggerNotification[@"notification"] = request.content.userInfo[kNotifeeUserInfoNotification];
1034
+ triggerNotification[@"trigger"] = request.content.userInfo[kNotifeeUserInfoTrigger];
118
1035
 
119
1036
  if (triggerNotification[@"notification"] == nil) {
120
1037
  // parse remote notification
121
- triggerNotification[@"notification"] =
122
- [NotifeeCoreUtil parseUNNotificationRequest:deliveredNotification.request];
1038
+ triggerNotification[@"notification"] = [NotifeeCoreUtil parseUNNotificationRequest:request];
1039
+ }
1040
+
1041
+ NSString *rollingPublicId = [self rollingPublicIdForRequest:request];
1042
+ NSDictionary *notification = triggerNotification[@"notification"];
1043
+ if (rollingPublicId != nil && [notification isKindOfClass:NSDictionary.class]) {
1044
+ NSMutableDictionary *publicNotification = [notification mutableCopy];
1045
+ publicNotification[@"id"] = rollingPublicId;
1046
+ triggerNotification[@"notification"] = publicNotification;
123
1047
  }
124
1048
 
125
1049
  [triggerNotifications addObject:triggerNotification];
@@ -133,9 +1057,41 @@
133
1057
 
134
1058
  [center getPendingNotificationRequestsWithCompletionHandler:^(
135
1059
  NSArray<UNNotificationRequest *> *_Nonnull requests) {
1060
+ NSDictionary *rollingRecords = [NotifeeCoreUtil getRollingTimestampTriggers];
1061
+ NSMutableSet<NSString *> *rollingPublicIds = [NSMutableSet set];
136
1062
  NSMutableArray *triggerNotifications = [[NSMutableArray alloc] init];
137
1063
 
1064
+ for (id publicId in rollingRecords) {
1065
+ if (![publicId isKindOfClass:NSString.class]) {
1066
+ continue;
1067
+ }
1068
+
1069
+ NSDictionary *record = rollingRecords[publicId];
1070
+ NSDictionary *notification = record[@"notification"];
1071
+ NSDictionary *trigger = record[@"trigger"];
1072
+ if (![notification isKindOfClass:NSDictionary.class] ||
1073
+ ![trigger isKindOfClass:NSDictionary.class]) {
1074
+ continue;
1075
+ }
1076
+
1077
+ NSMutableDictionary *triggerNotification = [NSMutableDictionary dictionary];
1078
+ triggerNotification[@"notification"] = notification;
1079
+ triggerNotification[@"trigger"] = trigger;
1080
+ [triggerNotifications addObject:triggerNotification];
1081
+ [rollingPublicIds addObject:publicId];
1082
+ }
1083
+
138
1084
  for (UNNotificationRequest *request in requests) {
1085
+ NSString *rollingPublicId =
1086
+ [NotifeeCoreUtil rollingPublicIdFromInternalNotificationId:request.identifier];
1087
+ if (rollingPublicId != nil) {
1088
+ continue;
1089
+ }
1090
+
1091
+ if ([rollingPublicIds containsObject:request.identifier]) {
1092
+ continue;
1093
+ }
1094
+
139
1095
  NSMutableDictionary *triggerNotification = [NSMutableDictionary dictionary];
140
1096
 
141
1097
  triggerNotification[@"notification"] = request.content.userInfo[kNotifeeUserInfoNotification];
@@ -158,14 +1114,21 @@
158
1114
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
159
1115
  [center getPendingNotificationRequestsWithCompletionHandler:^(
160
1116
  NSArray<UNNotificationRequest *> *_Nonnull requests) {
161
- NSMutableArray<NSString *> *idsArray = [[NSMutableArray alloc] init];
1117
+ NSMutableOrderedSet<NSString *> *ids = [NSMutableOrderedSet orderedSet];
162
1118
 
163
1119
  for (UNNotificationRequest *request in requests) {
164
1120
  NSString *notificationId = request.identifier;
165
- [idsArray addObject:notificationId];
1121
+ NSString *rollingPublicId =
1122
+ [NotifeeCoreUtil rollingPublicIdFromInternalNotificationId:notificationId];
1123
+ [self addString:(rollingPublicId != nil ? rollingPublicId : notificationId) toOrderedSet:ids];
1124
+ }
1125
+
1126
+ NSDictionary *rollingRecords = [NotifeeCoreUtil getRollingTimestampTriggers];
1127
+ for (id publicId in rollingRecords) {
1128
+ [self addString:publicId toOrderedSet:ids];
166
1129
  }
167
1130
 
168
- block(nil, idsArray);
1131
+ block(nil, [ids array]);
169
1132
  }];
170
1133
  }
171
1134
 
@@ -219,26 +1182,33 @@
219
1182
  trigger:nil];
220
1183
 
221
1184
  dispatch_async(dispatch_get_main_queue(), ^{
222
- [center
223
- addNotificationRequest:request
224
- withCompletionHandler:^(NSError *error) {
225
- if (error == nil) {
226
- // When the app is in foreground, willPresentNotification: emits
227
- // DELIVERED for all Notifee-owned notifications. Only emit here
228
- // when the app is NOT active to avoid duplicate events.
229
- dispatch_async(dispatch_get_main_queue(), ^{
230
- if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
231
- [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{
232
- @"type" : @(NotifeeCoreEventTypeDelivered),
233
- @"detail" : @{
234
- @"notification" : notificationDetail,
1185
+ [center addNotificationRequest:request
1186
+ withCompletionHandler:^(NSError *error) {
1187
+ if (error == nil) {
1188
+ // When the app is in foreground, willPresentNotification: emits
1189
+ // DELIVERED for all Notifee-owned notifications. Only emit here
1190
+ // when the app is NOT active to avoid duplicate events.
1191
+ dispatch_async(dispatch_get_main_queue(), ^{
1192
+ BOOL isApplicationActive = NO;
1193
+ if (![NotifeeCoreUtil isAppExtension]) {
1194
+ UIApplication *application = [NotifeeCoreUtil notifeeUIApplication];
1195
+ if (application != nil) {
1196
+ isApplicationActive =
1197
+ application.applicationState == UIApplicationStateActive;
1198
+ }
235
1199
  }
236
- }];
1200
+ if (!isApplicationActive) {
1201
+ [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{
1202
+ @"type" : @(NotifeeCoreEventTypeDelivered),
1203
+ @"detail" : @{
1204
+ @"notification" : notificationDetail,
1205
+ }
1206
+ }];
1207
+ }
1208
+ });
237
1209
  }
238
- });
239
- }
240
- block(error);
241
- }];
1210
+ block(error);
1211
+ }];
242
1212
  });
243
1213
  });
244
1214
  }
@@ -253,47 +1223,29 @@
253
1223
  withTrigger:(NSDictionary *)trigger
254
1224
  withBlock:(notifeeMethodVoidBlock)block {
255
1225
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
256
- UNMutableNotificationContent *content = [self buildNotificationContent:notification
257
- withTrigger:trigger];
258
- UNNotificationTrigger *unTrigger = [NotifeeCoreUtil triggerFromDictionary:trigger];
259
-
260
- if (unTrigger == nil) {
261
- // do nothing if trigger is null
262
- return dispatch_async(dispatch_get_main_queue(), ^{
263
- block(nil);
264
- });
265
- }
266
-
1226
+ UNMutableNotificationContent *content =
1227
+ [self triggerNotificationContentForNotification:notification trigger:trigger];
267
1228
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
268
1229
 
269
1230
  NSMutableDictionary *notificationDetail = [notification mutableCopy];
270
1231
  notificationDetail[@"remote"] = @NO;
271
1232
 
272
- if (@available(iOS 15.0, *)) {
273
- if (notification[@"ios"][@"communicationInfo"] != nil) {
274
- INSendMessageIntent *intent = [NotifeeCoreUtil
275
- generateSenderIntentForCommunicationNotification:notification[@"ios"]
276
- [@"communicationInfo"]];
1233
+ if ([NotifeeCoreUtil isRollingTimestampTrigger:trigger]) {
1234
+ [self createRollingTriggerNotification:notification
1235
+ withTrigger:trigger
1236
+ withNotificationDetail:notificationDetail
1237
+ center:center
1238
+ block:block];
1239
+ return;
1240
+ }
277
1241
 
278
- // Use the intent to initialize the interaction.
279
- INInteraction *interaction = [[INInteraction alloc] initWithIntent:intent response:nil];
280
- interaction.direction = INInteractionDirectionIncoming;
281
- [interaction donateInteractionWithCompletion:^(NSError *donateError) {
282
- if (donateError)
283
- NSLog(@"NotifeeCore: Could not donate interaction for communication notification: %@",
284
- donateError);
285
- }];
1242
+ UNNotificationTrigger *unTrigger = [NotifeeCoreUtil triggerFromDictionary:trigger];
286
1243
 
287
- NSError *contentUpdateError = nil;
288
- UNNotificationContent *updatedContent =
289
- [content contentByUpdatingWithProvider:intent error:&contentUpdateError];
290
- if (contentUpdateError) {
291
- NSLog(@"NotifeeCore: Could not update notification content with communication intent: %@",
292
- contentUpdateError);
293
- } else if (updatedContent != nil) {
294
- content = [updatedContent mutableCopy];
295
- }
296
- }
1244
+ if (unTrigger == nil) {
1245
+ // do nothing if trigger is null
1246
+ return dispatch_async(dispatch_get_main_queue(), ^{
1247
+ block(nil);
1248
+ });
297
1249
  }
298
1250
 
299
1251
  UNNotificationRequest *request =
@@ -447,18 +1399,6 @@
447
1399
  content.threadIdentifier = iosDict[@"threadId"];
448
1400
  }
449
1401
 
450
- if (@available(iOS 12.0, *)) {
451
- // summaryArgument
452
- if (iosDict[@"summaryArgument"] != nil) {
453
- content.summaryArgument = iosDict[@"summaryArgument"];
454
- }
455
-
456
- // summaryArgumentCount
457
- if (iosDict[@"summaryArgumentCount"] != nil) {
458
- content.summaryArgumentCount = [iosDict[@"summaryArgumentCount"] unsignedIntValue];
459
- }
460
- }
461
-
462
1402
  if (@available(iOS 13.0, *)) {
463
1403
  // targetContentId
464
1404
  if (iosDict[@"targetContentId"] != nil) {
@@ -512,12 +1452,7 @@
512
1452
  }
513
1453
  }
514
1454
 
515
- if (@available(iOS 13.0, *)) {
516
- categoryDictionary[@"allowAnnouncement"] = @(
517
- ((notificationCategory.options & UNNotificationCategoryOptionAllowAnnouncement) != 0));
518
- } else {
519
- categoryDictionary[@"allowAnnouncement"] = @(NO);
520
- }
1455
+ categoryDictionary[@"allowAnnouncement"] = @(NO);
521
1456
 
522
1457
  categoryDictionary[@"actions"] =
523
1458
  [NotifeeCoreUtil notificationActionsToDictionaryArray:notificationCategory.actions];
@@ -570,12 +1505,6 @@
570
1505
  }
571
1506
  }
572
1507
 
573
- if (@available(iOS 13.0, *)) {
574
- if ([categoryDictionary[@"allowAnnouncement"] isEqual:@(YES)]) {
575
- options |= UNNotificationCategoryOptionAllowAnnouncement;
576
- }
577
- }
578
-
579
1508
  if (@available(iOS 12.0, *)) {
580
1509
  category = [UNNotificationCategory categoryWithIdentifier:id
581
1510
  actions:actions
@@ -641,12 +1570,6 @@
641
1570
  }
642
1571
  }
643
1572
 
644
- if ([permissions[@"announcement"] isEqual:@(YES)]) {
645
- if (@available(iOS 13.0, *)) {
646
- options |= UNAuthorizationOptionAnnouncement;
647
- }
648
- }
649
-
650
1573
  if ([permissions[@"carPlay"] isEqual:@(YES)]) {
651
1574
  options |= UNAuthorizationOptionCarPlay;
652
1575
  }