From c0e921c1607da8557a1c9772bb017b286d8f282d Mon Sep 17 00:00:00 2001 From: yogevbd Date: Thu, 1 Aug 2019 02:03:36 +0300 Subject: [PATCH] typescript WIP --- RNNotifications/RCTConvert+RNNotifications.m | 2 +- RNNotifications/RNNotificationParser.m | 8 +- example/index.ios.js | 145 +++++++----------- .../project.pbxproj | 45 ++++++ lib/src/Notifications.ts | 7 +- lib/src/adapters/CompletionCallbackWrapper.ts | 29 ++++ lib/src/adapters/NativeCommandsSender.mock.ts | 1 + lib/src/adapters/NativeCommandsSender.ts | 14 +- lib/src/adapters/NativeEventsReceiver.ts | 23 ++- lib/src/commands/Commands.test.ts | 10 +- lib/src/commands/Commands.ts | 2 +- lib/src/events/EventsRegistry.test.ts | 140 +++++++++++++++++ lib/src/events/EventsRegistry.test.tsx | 25 --- lib/src/events/EventsRegistry.ts | 36 ++++- lib/src/interfaces/Notification.ts | 12 ++ lib/src/interfaces/NotificationEvents.ts | 18 ++- 16 files changed, 377 insertions(+), 140 deletions(-) create mode 100644 lib/src/adapters/CompletionCallbackWrapper.ts create mode 100644 lib/src/adapters/NativeCommandsSender.mock.ts create mode 100644 lib/src/events/EventsRegistry.test.ts delete mode 100644 lib/src/events/EventsRegistry.test.tsx diff --git a/RNNotifications/RCTConvert+RNNotifications.m b/RNNotifications/RCTConvert+RNNotifications.m index 92609ec..ce065cf 100644 --- a/RNNotifications/RCTConvert+RNNotifications.m +++ b/RNNotifications/RCTConvert+RNNotifications.m @@ -109,7 +109,7 @@ formattedNotification[@"body"] = RCTNullIfNil(content.body); formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); formattedNotification[@"thread"] = RCTNullIfNil(content.threadIdentifier); - [formattedNotification addEntriesFromDictionary:RCTNullIfNil(RCTJSONClean(content.userInfo))]; + formattedNotification[@"data"] = [NSDictionary dictionaryWithDictionary:RCTNullIfNil(RCTJSONClean(content.userInfo))]; return formattedNotification; } diff --git a/RNNotifications/RNNotificationParser.m b/RNNotifications/RNNotificationParser.m index 9a2815a..6800f05 100644 --- a/RNNotifications/RNNotificationParser.m +++ b/RNNotifications/RNNotificationParser.m @@ -4,15 +4,11 @@ @implementation RNNotificationParser + (NSDictionary *)parseNotification:(UNNotification *)notification { - NSDictionary* notificationDict = @{@"identifier": notification.request.identifier, - @"payload": [RCTConvert UNNotificationPayload:notification] - }; - - return notificationDict; + return [RCTConvert UNNotificationPayload:notification]; } + (NSDictionary *)parseNotificationResponse:(UNNotificationResponse *)response { - NSDictionary* responseDict = @{@"payload": [RCTConvert UNNotificationPayload:response.notification], @"identifier": response.notification.request.identifier, @"action": [self parseNotificationResponseAction:response]}; + NSDictionary* responseDict = @{@"notification": [RCTConvert UNNotificationPayload:response.notification], @"identifier": response.notification.request.identifier, @"action": [self parseNotificationResponseAction:response]}; return responseDict; } diff --git a/example/index.ios.js b/example/index.ios.js index a80704c..0e2dc64 100644 --- a/example/index.ios.js +++ b/example/index.ios.js @@ -6,90 +6,92 @@ import { Button } from 'react-native'; import React, {Component} from 'react'; - import { Notifications } from '../lib/dist/index'; -// let upvoteAction = new NotificationAction({ -// activationMode: 'background', -// title: String.fromCodePoint(0x1F44D), -// identifier: 'UPVOTE_ACTION' -// }); - -// let replyAction = new NotificationAction({ -// activationMode: 'background', -// title: 'Reply', -// authenticationRequired: true, -// textInput: { -// buttonTitle: 'Reply now', -// placeholder: 'Insert message' -// }, -// identifier: 'REPLY_ACTION' -// }); - class NotificationsExampleApp extends Component { - constructor() { super(); this.state = { notifications: [] }; - Notifications.events().registerNotificationsReceived((notification) => { - alert(JSON.stringify(notification)); - }) - // NotificationsIOS.addEventListener('remoteNotificationsRegistered', this.onPushRegistered.bind(this)); - // NotificationsIOS.addEventListener('remoteNotificationsRegistrationFailed', this.onPushRegisteredFailed.bind(this)); + this.registerNotificationEvents(); + } - // NotificationsIOS.addEventListener('pushKitRegistered', this.onPushKitRegistered.bind(this)); - // NotificationsIOS.registerPushKit(); + registerNotificationEvents() { + Notifications.events().registerNotificationReceived((notification, completion) => { + this.setState({ + notifications: [...this.state.notifications, notification.data.link] + }); - // NotificationsIOS.addEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground.bind(this)); - // NotificationsIOS.addEventListener('notificationOpened', this.onNotificationOpened.bind(this)); - // NotificationsIOS.addEventListener('pushKitNotificationReceived', this.onPushKitNotificationReceived.bind(this)); - } + completion({alert: true, sound: false, badge: false}); + }); - async componentDidMount() { - const initialNotification = await Notifications.getInitialNotification(); - if (initialNotification) { - this.setState({notifications: [initialNotification.getData().link, ...this.state.notifications]}); - } + Notifications.events().registerRemoteNotificationOpened((response, completion) => { + this.setState({ + notifications: [...this.state.notifications, `Notification Clicked: ${response.notification.data.link}`] + }); + + completion(); + }); } - onPushRegistered(deviceToken) { - console.log('Device Token Received: ' + deviceToken); + renderNotification(notification) { + return {`${notification}`}; } - onPushRegisteredFailed(error) { - console.log('Remote notifiction registration failed: ' + error); + requestPermissions() { + Notifications.requestPermissions(); } - onPushKitRegistered(deviceToken) { - console.log('PushKit Token Received: ' + deviceToken); - } + setCategories() { + const upvoteAction = { + activationMode: 'background', + title: String.fromCodePoint(0x1F44D), + identifier: 'UPVOTE_ACTION' + }; + + const replyAction = { + activationMode: 'background', + title: 'Reply', + authenticationRequired: true, + textInput: { + buttonTitle: 'Reply now', + placeholder: 'Insert message' + }, + identifier: 'REPLY_ACTION' + }; + + const category = { + identifier: 'SOME_CATEGORY', + actions: [upvoteAction, replyAction] + }; - onPushKitNotificationReceived(notification) { - console.log('PushKit notification Received: ' + JSON.stringify(notification)); + Notifications.setCategories([category]); } - onNotificationReceivedForeground(notification, completion) { - console.log('Notification Received Foreground with title: ' + JSON.stringify(notification)); - this.setState({ - notifications: [...this.state.notifications, notification.getData().link] + sendLocalNotification() { + Notifications.localNotification({ + body: 'Local notificiation!', + title: 'Local Notification Title', + sound: 'chime.aiff', + category: 'SOME_CATEGORY', + userInfo: { link: 'localNotificationLink' }, }); + } - completion({alert: notification.getData().showAlert, sound: false, badge: false}); + removeAllDeliveredNotifications() { + Notifications.removeAllDeliveredNotifications(); } - onNotificationOpened(notification, completion, action) { - console.log('Notification Opened: ' + JSON.stringify(notification) + JSON.stringify(action)); - this.setState({ - notifications: [...this.state.notifications, `Notification Clicked: ${notification.getData().link}`] - }); - completion(); + async componentDidMount() { + const initialNotification = await Notifications.getInitialNotification(); + if (initialNotification) { + this.setState({notifications: [initialNotification.data.link, ...this.state.notifications]}); + } } - renderNotification(notification) { - return {`${notification}`}; + componentWillUnmount() { } render() { @@ -109,35 +111,6 @@ class NotificationsExampleApp extends Component { ); } - - requestPermissions() { - // let cat = new NotificationCategory({ - // identifier: 'SOME_CATEGORY', - // actions: [upvoteAction, replyAction] - // }); - Notifications.requestPermissions(/*[cat]*/); - } - - sendLocalNotification() { - Notifications.localNotification({ - body: 'Local notificiation!', - title: 'Local Notification Title', - sound: 'chime.aiff', - category: 'SOME_CATEGORY', - userInfo: { link: 'localNotificationLink' }, - }); - } - - removeAllDeliveredNotifications() { - // NotificationsIOS.removeAllDeliveredNotifications(); - } - - componentWillUnmount() { - // NotificationsIOS.removeEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground.bind(this)); - // NotificationsIOS.removeEventListener('notificationOpened', this.onNotificationOpened.bind(this)); - // NotificationsIOS.removeEventListener('remoteNotificationsRegistered', this.onPushRegistered.bind(this)); - // NotificationsIOS.removeEventListener('pushKitRegistered', this.onPushKitRegistered.bind(this)); - } } const styles = StyleSheet.create({ diff --git a/example/ios/NotificationsExampleApp.xcodeproj/project.pbxproj b/example/ios/NotificationsExampleApp.xcodeproj/project.pbxproj index cbd0289..8925461 100644 --- a/example/ios/NotificationsExampleApp.xcodeproj/project.pbxproj +++ b/example/ios/NotificationsExampleApp.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 50CBD3CD22F2558C00142352 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CBD3A922F2556900142352 /* libRCTAnimation.a */; }; 50F1F0CC22CE3B4700FD5829 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 50F1F06022CE3A6100FD5829 /* libReact.a */; }; 50F1F0CD22CE3B6300FD5829 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 50F1F08A22CE3AA000FD5829 /* libRCTActionSheet.a */; }; 50F1F0CF22CE3B6300FD5829 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 50F1F09522CE3ABE00FD5829 /* libRCTImage.a */; }; @@ -27,6 +28,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 50CBD3A822F2556900142352 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50CBD3A222F2556900142352 /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTAnimation; + }; + 50CBD3AA22F2556900142352 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50CBD3A222F2556900142352 /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 2D2A28201D9B03D100D4039D; + remoteInfo = "RCTAnimation-tvOS"; + }; 50E49F4022D1F06C007160C1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; @@ -283,6 +298,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NotificationsExampleApp/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = NotificationsExampleApp/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 50CBD3A222F2556900142352 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 50F1F08522CE3A9F00FD5829 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; 50F1F0A022CE3B0600FD5829 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; @@ -296,6 +312,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50CBD3CD22F2558C00142352 /* libRCTAnimation.a in Frameworks */, 50F1F0D722CE3C1E00FD5829 /* libcxxreact.a in Frameworks */, 50F1F0D622CE3C0F00FD5829 /* libyoga.a in Frameworks */, 50F1F0CD22CE3B6300FD5829 /* libRCTActionSheet.a in Frameworks */, @@ -346,6 +363,15 @@ name = NotificationsExampleApp; sourceTree = ""; }; + 50CBD3A322F2556900142352 /* Products */ = { + isa = PBXGroup; + children = ( + 50CBD3A922F2556900142352 /* libRCTAnimation.a */, + 50CBD3AB22F2556900142352 /* libRCTAnimation.a */, + ); + name = Products; + sourceTree = ""; + }; 50F1F04D22CE3A6100FD5829 /* Products */ = { isa = PBXGroup; children = ( @@ -444,6 +470,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 50CBD3A222F2556900142352 /* RCTAnimation.xcodeproj */, 50F1F0A022CE3B0600FD5829 /* RCTNetwork.xcodeproj */, D85498C21D97B31100DEEE06 /* RNNotifications.xcodeproj */, 146833FF1AC3E56700842450 /* React.xcodeproj */, @@ -542,6 +569,10 @@ ProductGroup = 50F1F08622CE3A9F00FD5829 /* Products */; ProjectRef = 50F1F08522CE3A9F00FD5829 /* RCTActionSheet.xcodeproj */; }, + { + ProductGroup = 50CBD3A322F2556900142352 /* Products */; + ProjectRef = 50CBD3A222F2556900142352 /* RCTAnimation.xcodeproj */; + }, { ProductGroup = 50F1F09022CE3ABE00FD5829 /* Products */; ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; @@ -587,6 +618,20 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 50CBD3A922F2556900142352 /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 50CBD3A822F2556900142352 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 50CBD3AB22F2556900142352 /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 50CBD3AA22F2556900142352 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 50E49F4122D1F06C007160C1 /* libjsi.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/lib/src/Notifications.ts b/lib/src/Notifications.ts index 3d28969..a5b1fbe 100644 --- a/lib/src/Notifications.ts +++ b/lib/src/Notifications.ts @@ -4,6 +4,7 @@ import { Commands } from './commands/Commands'; import { EventsRegistry } from './events/EventsRegistry'; import { Notification, NotificationCategory } from './interfaces/Notification'; import { UniqueIdProvider } from './adapters/UniqueIdProvider'; +import { CompletionCallbackWrapper } from './adapters/CompletionCallbackWrapper'; export class NotificationsRoot { private readonly nativeEventsReceiver: NativeEventsReceiver; @@ -11,16 +12,18 @@ export class NotificationsRoot { private readonly commands: Commands; private readonly eventsRegistry: EventsRegistry; private readonly uniqueIdProvider: UniqueIdProvider; + private readonly completionCallbackWrapper: CompletionCallbackWrapper; constructor() { this.nativeEventsReceiver = new NativeEventsReceiver(); this.nativeCommandsSender = new NativeCommandsSender(); + this.completionCallbackWrapper = new CompletionCallbackWrapper(this.nativeCommandsSender); this.uniqueIdProvider = new UniqueIdProvider(); this.commands = new Commands( this.nativeCommandsSender, this.uniqueIdProvider ); - this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver); + this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.completionCallbackWrapper); } /** @@ -45,7 +48,7 @@ export class NotificationsRoot { } /** - * + * getInitialNotification */ public getInitialNotification(): Promise { return this.commands.getInitialNotification(); diff --git a/lib/src/adapters/CompletionCallbackWrapper.ts b/lib/src/adapters/CompletionCallbackWrapper.ts new file mode 100644 index 0000000..2458e79 --- /dev/null +++ b/lib/src/adapters/CompletionCallbackWrapper.ts @@ -0,0 +1,29 @@ +import { NativeCommandsSender } from './NativeCommandsSender'; +import { NotificationCompletion, Notification } from '../interfaces/Notification'; +import { NotificationResponse } from '../interfaces/NotificationEvents'; + +export class CompletionCallbackWrapper { + constructor( + private readonly nativeCommandsSender: NativeCommandsSender + ) {} + + public wrapReceivedCallback(callback: Function): (notification: Notification) => void { + return (notification) => { + const completion = (response: NotificationCompletion) => { + this.nativeCommandsSender.finishPresentingNotification(notification.identifier, response); + }; + + callback(notification, completion); + } + } + + public wrapOpenedCallback(callback: Function): (response: NotificationResponse) => void { + return (response) => { + const completion = () => { + this.nativeCommandsSender.finishHandlingAction(response.notification.identifier); + }; + + callback(response, completion); + } + } +} diff --git a/lib/src/adapters/NativeCommandsSender.mock.ts b/lib/src/adapters/NativeCommandsSender.mock.ts new file mode 100644 index 0000000..138de4c --- /dev/null +++ b/lib/src/adapters/NativeCommandsSender.mock.ts @@ -0,0 +1 @@ +export const { NativeCommandsSender } = jest.genMockFromModule('./NativeCommandsSender'); diff --git a/lib/src/adapters/NativeCommandsSender.ts b/lib/src/adapters/NativeCommandsSender.ts index 1b9b916..1e4e7f3 100644 --- a/lib/src/adapters/NativeCommandsSender.ts +++ b/lib/src/adapters/NativeCommandsSender.ts @@ -1,5 +1,5 @@ import { NativeModules } from 'react-native'; -import { Notification, NotificationCategory, NotificationPermissions } from '../interfaces/Notification'; +import { Notification, NotificationCategory, NotificationPermissions, NotificationCompletion } from '../interfaces/Notification'; interface NativeCommandsModule { getInitialNotification(): Promise; @@ -16,6 +16,8 @@ interface NativeCommandsModule { removeDeliveredNotifications(identifiers: Array): void; removeAllDeliveredNotifications(): void; setCategories(categories: [NotificationCategory?]): void; + finishPresentingNotification(notificationId: string, callback: NotificationCompletion): void; + finishHandlingAction(notificationId: string): void; } export class NativeCommandsSender { @@ -28,7 +30,7 @@ export class NativeCommandsSender { return this.nativeCommandsModule.localNotification(notification, id); } - getInitialNotification() { + getInitialNotification(): Promise { return this.nativeCommandsModule.getInitialNotification(); } @@ -79,4 +81,12 @@ export class NativeCommandsSender { removeDeliveredNotifications(identifiers: Array) { return this.nativeCommandsModule.removeDeliveredNotifications(identifiers); } + + finishPresentingNotification(notificationId: string, notificationCompletion: NotificationCompletion): void { + this.nativeCommandsModule.finishPresentingNotification(notificationId, notificationCompletion); + } + + finishHandlingAction(notificationId: string): void { + this.nativeCommandsModule.finishHandlingAction(notificationId); + } } diff --git a/lib/src/adapters/NativeEventsReceiver.ts b/lib/src/adapters/NativeEventsReceiver.ts index a8b3f53..99bc6f9 100644 --- a/lib/src/adapters/NativeEventsReceiver.ts +++ b/lib/src/adapters/NativeEventsReceiver.ts @@ -1,7 +1,8 @@ import { NativeModules, NativeEventEmitter, EventEmitter, EmitterSubscription } from 'react-native'; import { - NotificationRegisteredEvent, NotificationReceived + Registered, RegistrationError, RegisteredPushKit, NotificationResponse } from '../interfaces/NotificationEvents'; +import { Notification } from '../interfaces/Notification'; export class NativeEventsReceiver { private emitter: EventEmitter; @@ -9,11 +10,27 @@ export class NativeEventsReceiver { this.emitter = new NativeEventEmitter(NativeModules.RNEventEmitter); } - public registerRemoteNotificationsRegistered(callback: (event: NotificationRegisteredEvent) => void): EmitterSubscription { + public registerRemoteNotificationsRegistered(callback: (event: Registered) => void): EmitterSubscription { return this.emitter.addListener('remoteNotificationsRegistered', callback); } - public registerRemoteNotificationReceived(callback: (event: NotificationReceived) => void): EmitterSubscription { + public registerPushKitRegistered(callback: (event: RegisteredPushKit) => void): EmitterSubscription { + return this.emitter.addListener('pushKitRegistered', callback); + } + + public registerRemoteNotificationReceived(callback: (notification: Notification) => void): EmitterSubscription { + return this.emitter.addListener('notificationReceivedForeground', callback); + } + + public registerPushKitNotificationReceived(callback: (event: object) => void): EmitterSubscription { return this.emitter.addListener('notificationReceivedForeground', callback); } + + public registerRemoteNotificationOpened(callback: (response: NotificationResponse, completion: () => void) => void): EmitterSubscription { + return this.emitter.addListener('notificationOpened', callback); + } + + public registerRemoteNotificationsRegistrationFailed(callback: (event: RegistrationError) => void): EmitterSubscription { + return this.emitter.addListener('remoteNotificationsRegistrationFailed', callback); + } } diff --git a/lib/src/commands/Commands.test.ts b/lib/src/commands/Commands.test.ts index aba9afc..ddac792 100644 --- a/lib/src/commands/Commands.test.ts +++ b/lib/src/commands/Commands.test.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { mock, verify, instance, deepEqual, when, anything, anyString } from 'ts-mockito'; +import { mock, verify, instance, when, anything, anyString } from 'ts-mockito'; import { Commands } from './Commands'; import { NativeCommandsSender } from '../adapters/NativeCommandsSender'; @@ -28,7 +28,7 @@ describe('Commands', () => { }); it('returns a promise with the initial notification', async () => { - const expectedNotification: Notification = {data: {}, alert: 'alert'}; + const expectedNotification: Notification = {identifier: 'id', data: {}, alert: 'alert'}; when(mockedNativeCommandsSender.getInitialNotification()).thenResolve( expectedNotification ); @@ -75,19 +75,19 @@ describe('Commands', () => { describe('sendLocalNotification', () => { it('sends to native', () => { - const notification: Notification = {data: {}, alert: 'alert'}; + const notification: Notification = {identifier: 'id', alert: 'alert', data: {}}; uut.sendLocalNotification(notification); verify(mockedNativeCommandsSender.sendLocalNotification(notification, anyString())).called(); }); it('generates unique identifier', () => { - const notification: Notification = {data: {}, alert: 'alert'}; + const notification: Notification = {identifier: 'id', data: {}, alert: 'alert'}; uut.sendLocalNotification(notification); verify(mockedNativeCommandsSender.sendLocalNotification(notification, `Notification_+UNIQUE_ID`)).called(); }); it('use passed notification id', () => { - const notification: Notification = {data: {}, alert: 'alert'}; + const notification: Notification = {identifier: 'id', data: {}, alert: 'alert'}; const passedId: string = "passedId"; uut.sendLocalNotification(notification, passedId); verify(mockedNativeCommandsSender.sendLocalNotification(notification, passedId)).called(); diff --git a/lib/src/commands/Commands.ts b/lib/src/commands/Commands.ts index 07986a6..7d80491 100644 --- a/lib/src/commands/Commands.ts +++ b/lib/src/commands/Commands.ts @@ -15,7 +15,7 @@ export class Commands { return result; } - public getInitialNotification() { + public getInitialNotification(): Promise { const result = this.nativeCommandsSender.getInitialNotification(); return result; } diff --git a/lib/src/events/EventsRegistry.test.ts b/lib/src/events/EventsRegistry.test.ts new file mode 100644 index 0000000..367ecd0 --- /dev/null +++ b/lib/src/events/EventsRegistry.test.ts @@ -0,0 +1,140 @@ +import { EventsRegistry } from './EventsRegistry'; +import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock'; +import { NotificationCompletion, Notification } from '../interfaces/Notification'; +import { CompletionCallbackWrapper } from '../adapters/CompletionCallbackWrapper'; +import { NativeCommandsSender } from '../adapters/NativeCommandsSender.mock'; +import { NotificationResponse } from '../interfaces/NotificationEvents'; + +describe('EventsRegistry', () => { + let uut: EventsRegistry; + const mockNativeEventsReceiver = new NativeEventsReceiver(); + const mockNativeCommandsSender = new NativeCommandsSender(); + const completionCallbackWrapper = new CompletionCallbackWrapper(mockNativeCommandsSender); + + beforeEach(() => { + uut = new EventsRegistry(mockNativeEventsReceiver, completionCallbackWrapper); + }); + + describe('registerRemoteNotificationsReceived', () => { + it('delegates to nativeEventsReceiver', () => { + const cb = jest.fn(); + + uut.registerNotificationReceived(cb); + + expect(mockNativeEventsReceiver.registerRemoteNotificationReceived).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerRemoteNotificationReceived).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('should wrap callback with completion block', () => { + const wrappedCallback = jest.fn(); + const notification: Notification = {identifier: 'identifier', data: {}, alert: 'alert'} + + uut.registerNotificationReceived(wrappedCallback); + const call = mockNativeEventsReceiver.registerRemoteNotificationReceived.mock.calls[0][0]; + call(notification); + + expect(wrappedCallback).toBeCalledWith(notification, expect.any(Function)); + expect(wrappedCallback).toBeCalledTimes(1); + }); + + it('should wrap callback with completion block', () => { + const expectedNotification: Notification = {identifier: 'identifier', data: {}, alert: 'alert'} + + uut.registerNotificationReceived((notification) => { + expect(notification).toEqual(expectedNotification); + }); + const call = mockNativeEventsReceiver.registerRemoteNotificationReceived.mock.calls[0][0]; + call(expectedNotification); + }); + + it('calling completion should invoke finishPresentingNotification', () => { + const notification: Notification = {identifier: 'notificationId', data: {}, alert: 'alert'} + const response: NotificationCompletion = {alert: true} + + uut.registerNotificationReceived((notification, completion) => { + completion(response); + + expect(mockNativeCommandsSender.finishPresentingNotification).toBeCalledWith(notification.identifier, response); + }); + const call = mockNativeEventsReceiver.registerRemoteNotificationReceived.mock.calls[0][0]; + call(notification); + }); + }); + + describe('', () => { + it('delegates to nativeEventsReceiver', () => { + const cb = jest.fn(); + + uut.registerRemoteNotificationOpened(cb); + + expect(mockNativeEventsReceiver.registerRemoteNotificationOpened).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerRemoteNotificationOpened).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('should wrap callback with completion block', () => { + const wrappedCallback = jest.fn(); + const notification: Notification = {identifier: 'identifier', data: {}, alert: 'alert'}; + const response: NotificationResponse = {notification, identifier: 'responseId'}; + + uut.registerRemoteNotificationOpened(wrappedCallback); + const call = mockNativeEventsReceiver.registerRemoteNotificationOpened.mock.calls[0][0]; + call(response); + + expect(wrappedCallback).toBeCalledWith(response, expect.any(Function)); + expect(wrappedCallback).toBeCalledTimes(1); + }); + + it('should wrap callback with completion block', () => { + const notification: Notification = {identifier: 'identifier', data: {}, alert: 'alert'} + const expectedResponse: NotificationResponse = {notification, identifier: 'responseId'} + + uut.registerRemoteNotificationOpened((response) => { + expect(response).toEqual(expectedResponse); + }); + const call = mockNativeEventsReceiver.registerRemoteNotificationOpened.mock.calls[0][0]; + call(expectedResponse); + }); + + it('calling completion should invoke finishHandlingAction', () => { + const notification: Notification = {identifier: 'notificationId', data: {}, alert: 'alert'} + const expectedResponse: NotificationResponse = {identifier: 'responseId', notification}; + + uut.registerRemoteNotificationOpened((response, completion) => { + completion(); + + expect(response).toEqual(expectedResponse); + expect(mockNativeCommandsSender.finishHandlingAction).toBeCalledWith(notification.identifier); + }); + const call = mockNativeEventsReceiver.registerRemoteNotificationOpened.mock.calls[0][0]; + call(expectedResponse); + }); + }); + + it('delegates registerRemoteNotificationsRegistered to nativeEventsReceiver', () => { + const cb = jest.fn(); + uut.registerRemoteNotificationsRegistered(cb); + expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistered).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistered).toHaveBeenCalledWith(cb); + }); + + it('delegates registerPushKitRegistered to nativeEventsReceiver', () => { + const cb = jest.fn(); + uut.registerPushKitRegistered(cb); + expect(mockNativeEventsReceiver.registerPushKitRegistered).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerPushKitRegistered).toHaveBeenCalledWith(cb); + }); + + it('delegates registerPushKitNotificationReceived to nativeEventsReceiver', () => { + const cb = jest.fn(); + uut.registerPushKitNotificationReceived(cb); + expect(mockNativeEventsReceiver.registerPushKitNotificationReceived).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerPushKitNotificationReceived).toHaveBeenCalledWith(cb); + }); + + it('delegates registerRemoteNotificationsRegistrationFailed to nativeEventsReceiver', () => { + const cb = jest.fn(); + uut.registerRemoteNotificationsRegistrationFailed(cb); + expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistrationFailed).toHaveBeenCalledTimes(1); + expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistrationFailed).toHaveBeenCalledWith(cb); + }); +}); diff --git a/lib/src/events/EventsRegistry.test.tsx b/lib/src/events/EventsRegistry.test.tsx deleted file mode 100644 index 2d323b8..0000000 --- a/lib/src/events/EventsRegistry.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { EventsRegistry } from './EventsRegistry'; -import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock'; - -describe('EventsRegistry', () => { - let uut: EventsRegistry; - const mockNativeEventsReceiver = new NativeEventsReceiver(); - - beforeEach(() => { - uut = new EventsRegistry(mockNativeEventsReceiver); - }); - - it('delegates registerRemoteNotificationsRegistered to nativeEventsReceiver', () => { - const cb = jest.fn(); - uut.registerRemoteNotificationsRegistered(cb); - expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistered).toHaveBeenCalledTimes(1); - expect(mockNativeEventsReceiver.registerRemoteNotificationsRegistered).toHaveBeenCalledWith(cb); - }); - - it('delegates registerRemoteNotificationsReceived to nativeEventsReceiver', () => { - const cb = jest.fn(); - uut.registerNotificationReceived(cb); - expect(mockNativeEventsReceiver.registerRemoteNotificationReceived).toHaveBeenCalledTimes(1); - expect(mockNativeEventsReceiver.registerRemoteNotificationReceived).toHaveBeenCalledWith(cb); - }); -}); diff --git a/lib/src/events/EventsRegistry.ts b/lib/src/events/EventsRegistry.ts index cd70540..d143de4 100644 --- a/lib/src/events/EventsRegistry.ts +++ b/lib/src/events/EventsRegistry.ts @@ -1,18 +1,42 @@ import { EmitterSubscription } from 'react-native'; import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver'; import { - NotificationRegisteredEvent, - NotificationReceived + Registered, + RegistrationError, + RegisteredPushKit, + NotificationResponse } from '../interfaces/NotificationEvents'; +import { CompletionCallbackWrapper } from '../adapters/CompletionCallbackWrapper'; +import { NotificationCompletion, Notification } from '../interfaces/Notification'; export class EventsRegistry { - constructor(private nativeEventsReceiver: NativeEventsReceiver) { } + constructor( + private nativeEventsReceiver: NativeEventsReceiver, + private completionCallbackWrapper: CompletionCallbackWrapper) + {} - public registerRemoteNotificationsRegistered(callback: (event: NotificationRegisteredEvent) => void): EmitterSubscription { + public registerRemoteNotificationsRegistered(callback: (event: Registered) => void): EmitterSubscription { return this.nativeEventsReceiver.registerRemoteNotificationsRegistered(callback); } - public registerNotificationReceived(callback: (event: NotificationReceived) => void): EmitterSubscription { - return this.nativeEventsReceiver.registerRemoteNotificationReceived(callback); + public registerPushKitRegistered(callback: (event: RegisteredPushKit) => void): EmitterSubscription { + return this.nativeEventsReceiver.registerPushKitRegistered(callback); } + + public registerNotificationReceived(callback: (notification: Notification, completion: (response: NotificationCompletion) => void) => void): EmitterSubscription { + return this.nativeEventsReceiver.registerRemoteNotificationReceived(this.completionCallbackWrapper.wrapReceivedCallback(callback)); + } + + public registerPushKitNotificationReceived(callback: (event: object) => void): EmitterSubscription { + return this.nativeEventsReceiver.registerPushKitNotificationReceived(callback); + } + + public registerRemoteNotificationOpened(callback: (response: NotificationResponse, completion: () => void) => void): EmitterSubscription { + return this.nativeEventsReceiver.registerRemoteNotificationOpened(this.completionCallbackWrapper.wrapOpenedCallback(callback)); + } + + public registerRemoteNotificationsRegistrationFailed(callback: (event: RegistrationError) => void): EmitterSubscription { + return this.nativeEventsReceiver.registerRemoteNotificationsRegistrationFailed(callback); + } + } diff --git a/lib/src/interfaces/Notification.ts b/lib/src/interfaces/Notification.ts index a564909..106393a 100644 --- a/lib/src/interfaces/Notification.ts +++ b/lib/src/interfaces/Notification.ts @@ -1,4 +1,5 @@ export interface Notification { + identifier: string; data: object; alert: string sound?: string; @@ -31,3 +32,14 @@ export interface NotificationAction { authenticationRequired: boolean; textInput: NotificationTextInput } + +export interface NotificationActionResponse { + identifier: string; + text: string; +} + +export interface NotificationCompletion { + badge?: boolean; + alert?: boolean; + sound?: boolean; +} diff --git a/lib/src/interfaces/NotificationEvents.ts b/lib/src/interfaces/NotificationEvents.ts index 53ce2ea..7284220 100644 --- a/lib/src/interfaces/NotificationEvents.ts +++ b/lib/src/interfaces/NotificationEvents.ts @@ -1,9 +1,21 @@ -import { Notification } from './Notification'; +import { Notification, NotificationActionResponse } from './Notification'; -export interface NotificationRegisteredEvent { +export interface Registered { deviceToken: string; } -export interface NotificationReceived { +export interface RegistrationError { + code: string; + domain: string; + localizedDescription: string; +} + +export interface RegisteredPushKit { + pushKitToken: string; +} + +export interface NotificationResponse { + identifier: string; notification: Notification; + action?: NotificationActionResponse } -- 2.26.2