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