diff --git a/README.md b/README.md index 60bb5171aa51466472c24d367d1ed62956843afe..bc21c16c64798638235c7567e1e04d5faebe5d1e 100644 --- a/README.md +++ b/README.md @@ -431,6 +431,28 @@ Now the server should push the notification a bit differently- background instea --- +## Remove notifications (iOS only) + +### getDeliveredNotifications + +`PushNotification.getDeliveredNotifications(callback: (notifications: [Object]) => void)` + +Provides you with a list of the app’s notifications that are still displayed in Notification Center. + +### removeDeliveredNotifications + +`PushNotification.removeDeliveredNotifications(identifiers: [string])` + +Removes the specified notifications from Notification Center. + +### removeAllDeliveredNotifications + +`PushNotification.removeAllDeliveredNotifications()` + +Removes all delivered notifications from Notification Center. + +--- + ## PushKit API (iOS only) The PushKit framework provides the classes for your iOS apps to receive background pushes from remote servers. it has better support for background notifications compared to regular push notifications with `content-available: 1`. More info in [iOS PushKit documentation](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Reference/PushKit_Framework/). diff --git a/RNNotifications/RNNotifications.m b/RNNotifications/RNNotifications.m index 8c7959d6b453b3c4b1d9c5bd422c013226ae179f..ff231ef5067c885298a817fe5079bbe30d3080b6 100644 --- a/RNNotifications/RNNotifications.m +++ b/RNNotifications/RNNotifications.m @@ -15,6 +15,7 @@ #import "RCTUtils.h" #endif #import "RNNotificationsBridgeQueue.h" +#import NSString* const RNNotificationCreateAction = @"CREATE"; NSString* const RNNotificationClearAction = @"CLEAR"; @@ -107,6 +108,29 @@ RCT_ENUM_CONVERTER(UIUserNotificationActionBehavior, (@{ } @end +static NSDictionary *RCTFormatUNNotification(UNNotification *notification) +{ + NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; + UNNotificationContent *content = notification.request.content; + + formattedNotification[@"identifier"] = notification.request.identifier; + + if (notification.date) { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; + NSString *dateString = [formatter stringFromDate:notification.date]; + formattedNotification[@"fireDate"] = dateString; + } + + formattedNotification[@"alertTitle"] = RCTNullIfNil(content.title); + formattedNotification[@"alertBody"] = RCTNullIfNil(content.body); + formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); + formattedNotification[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); + formattedNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); + + return formattedNotification; +} + @implementation RNNotifications RCT_EXPORT_MODULE() @@ -542,4 +566,39 @@ RCT_EXPORT_METHOD(cancelAllLocalNotifications) [RCTSharedApplication() cancelAllLocalNotifications]; } +#if !TARGET_OS_TV + +RCT_EXPORT_METHOD(removeAllDeliveredNotifications) +{ + if ([UNUserNotificationCenter class]) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllDeliveredNotifications]; + } +} + +RCT_EXPORT_METHOD(removeDeliveredNotifications:(NSArray *)identifiers) +{ + if ([UNUserNotificationCenter class]) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeDeliveredNotificationsWithIdentifiers:identifiers]; + } +} + +RCT_EXPORT_METHOD(getDeliveredNotifications:(RCTResponseSenderBlock)callback) +{ + if ([UNUserNotificationCenter class]) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getDeliveredNotificationsWithCompletionHandler:^(NSArray * _Nonnull notifications) { + NSMutableArray *formattedNotifications = [NSMutableArray new]; + + for (UNNotification *notification in notifications) { + [formattedNotifications addObject:RCTFormatUNNotification(notification)]; + } + callback(@[formattedNotifications]); + }]; + } +} + +#endif !TARGET_OS_TV + @end diff --git a/index.ios.js b/index.ios.js index 6dd788fd31723d7c892dbe9b0fef711e4064795f..566f48e9bf580f0623ed2f7455502a343d49caf4 100644 --- a/index.ios.js +++ b/index.ios.js @@ -199,4 +199,39 @@ export default class NotificationsIOS { static cancelAllLocalNotifications() { NativeRNNotifications.cancelAllLocalNotifications(); } + + /** + * Remove all delivered notifications from Notification Center + */ + static removeAllDeliveredNotifications() { + return NativeRNNotifications.removeAllDeliveredNotifications(); + } + + /** + * Removes the specified notifications from Notification Center + * + * @param identifiers Array of notification identifiers + */ + static removeDeliveredNotifications(identifiers: [string]) { + return NativeRNNotifications.removeDeliveredNotifications(identifiers); + } + + /** + * Provides you with a list of the app’s notifications that are still displayed in Notification Center + * + * @param callback Function which receive an array of delivered notifications + * + * A delivered notification is an object containing: + * + * - `identifier` : The identifier of this notification. + * - `alertBody` : The message displayed in the notification alert. + * - `alertTitle` : The message title displayed in the notification. + * - `category` : The category of this notification, if has one. + * - `userInfo` : An optional object containing additional notification data. + * - `thread-id` : The thread identifier of this notification, if has one. + * - `fireDate` : The date and time when the system should deliver the notification. if not specified, the notification will be dispatched immediately. + */ + static getDeliveredNotifications(callback: (notifications: [Object]) => void) { + return NativeRNNotifications.getDeliveredNotifications(callback); + } } diff --git a/notification.ios.js b/notification.ios.js index 016d4e2971ac4bf09889897d4fee4011d64d05db..4042a964eae2735ae6c294b0146af019fc5fcd61 100644 --- a/notification.ios.js +++ b/notification.ios.js @@ -5,6 +5,7 @@ export default class IOSNotification { _badge: number; _category: string; _type: string; // regular / managed + _thread: string; constructor(notification: Object) { this._data = {}; @@ -21,6 +22,7 @@ export default class IOSNotification { this._badge = notification.aps.badge; this._category = notification.managedAps.category; this._type = "managed"; + this._thread = notification.aps["thread-id"]; } else if ( notification.aps && notification.aps.alert) { @@ -30,6 +32,7 @@ export default class IOSNotification { this._badge = notification.aps.badge; this._category = notification.aps.category; this._type = "regular"; + this._thread = notification.aps["thread-id"]; } Object.keys(notification).filter(key => key !== "aps").forEach(key => { @@ -60,4 +63,8 @@ export default class IOSNotification { getType(): ?string { return this._type; } + + getThread(): ?string { + return this._thread; + } } diff --git a/test/index.ios.spec.js b/test/index.ios.spec.js index 2ba23fb41131fd4c6d9ace387b2134fd4440633a..4bfc32f672410361f9b76d060efc47fd2228db85 100644 --- a/test/index.ios.spec.js +++ b/test/index.ios.spec.js @@ -27,10 +27,14 @@ describe("NotificationsIOS", () => { nativeConsumeBackgroundQueue, nativeLocalNotification, nativeCancelLocalNotification, - nativeCancelAllLocalNotifications; + nativeCancelAllLocalNotifications, + nativeRemoveAllDeliveredNotifications, + nativeRemoveDeliveredNotifications, + nativeGetDeliveredNotifications; let NotificationsIOS, NotificationAction, NotificationCategory; let someHandler = () => {}; let constantGuid = "some-random-uuid"; + let identifiers = ["some-random-uuid", "other-random-uuid"]; /*eslint-enable indent*/ before(() => { @@ -46,6 +50,9 @@ describe("NotificationsIOS", () => { nativeLocalNotification = sinon.spy(); nativeCancelLocalNotification = sinon.spy(); nativeCancelAllLocalNotifications = sinon.spy(); + nativeRemoveAllDeliveredNotifications = sinon.spy(); + nativeRemoveDeliveredNotifications = sinon.spy(); + nativeGetDeliveredNotifications = sinon.spy(); let libUnderTest = proxyquire("../index.ios", { "uuid": { @@ -61,7 +68,10 @@ describe("NotificationsIOS", () => { consumeBackgroundQueue: nativeConsumeBackgroundQueue, localNotification: nativeLocalNotification, cancelLocalNotification: nativeCancelLocalNotification, - cancelAllLocalNotifications: nativeCancelAllLocalNotifications + cancelAllLocalNotifications: nativeCancelAllLocalNotifications, + removeAllDeliveredNotifications: nativeRemoveAllDeliveredNotifications, + removeDeliveredNotifications: nativeRemoveDeliveredNotifications, + getDeliveredNotifications: nativeGetDeliveredNotifications } }, NativeAppEventEmitter: { @@ -100,6 +110,9 @@ describe("NotificationsIOS", () => { nativeLocalNotification.reset(); nativeCancelLocalNotification.reset(); nativeCancelAllLocalNotifications.reset(); + nativeRemoveAllDeliveredNotifications.reset(); + nativeRemoveDeliveredNotifications.reset(); + nativeGetDeliveredNotifications.reset(); }); after(() => { @@ -115,6 +128,9 @@ describe("NotificationsIOS", () => { nativeLocalNotification = null; nativeCancelLocalNotification = null; nativeCancelAllLocalNotifications = null; + nativeRemoveAllDeliveredNotifications = null; + nativeRemoveDeliveredNotifications = null; + nativeGetDeliveredNotifications = null; NotificationsIOS = null; NotificationAction = null; @@ -282,4 +298,29 @@ describe("NotificationsIOS", () => { expect(nativeCancelAllLocalNotifications).to.have.been.calledWith(); }); }); + + describe("Remove all delivered notifications", () => { + it("should call native remove all delivered notifications method", () => { + NotificationsIOS.removeAllDeliveredNotifications(); + + expect(nativeRemoveAllDeliveredNotifications).to.have.been.calledWith(); + }); + }); + + describe("Remove delivered notifications", () => { + it("should call native remove delivered notifications method", () => { + NotificationsIOS.removeDeliveredNotifications(identifiers); + + expect(nativeRemoveDeliveredNotifications).to.have.been.calledWith(identifiers); + }); + }); + + describe("Get delivered notifications", () => { + it("should call native get delivered notifications method", () => { + const callback = (notifications) => console.log(notifications); + NotificationsIOS.getDeliveredNotifications(callback); + + expect(nativeGetDeliveredNotifications).to.have.been.calledWith(callback); + }); + }); }); diff --git a/test/notification.ios.spec.js b/test/notification.ios.spec.js index 0620108aac126f52c057af4d75d65a7bf7673cac..b19a901f9093eef5024914e83310eb551f1c1818 100644 --- a/test/notification.ios.spec.js +++ b/test/notification.ios.spec.js @@ -4,7 +4,7 @@ import IOSNotification from "../notification.ios"; describe("iOS Notification Object", () => { let notification; - let someBadgeCount = 123, someSound = "someSound", someCategory = "some_notification_category"; + let someBadgeCount = 123, someSound = "someSound", someCategory = "some_notification_category", someThread = "thread-1"; describe("for a regular iOS push notification", () => { let regularNativeNotifications = [ @@ -17,7 +17,8 @@ describe("iOS Notification Object", () => { }, badge: someBadgeCount, sound: someSound, - category: someCategory + category: someCategory, + "thread-id": someThread }, key1: "value1", key2: "value2" @@ -33,7 +34,8 @@ describe("iOS Notification Object", () => { }, badge: someBadgeCount, sound: someSound, - category: someCategory + category: someCategory, + "thread-id": someThread }, key1: "value1", key2: "value2" @@ -65,6 +67,10 @@ describe("iOS Notification Object", () => { expect(notification.getCategory()).to.equal(someCategory); }); + it("should return the thread", () => { + expect(notification.getThread()).to.equal("thread-1"); + }); + it("should return the custom data", () => { expect(notification.getData()).to.deep.equal({ key1: "value1", key2: "value2" }); });