diff --git a/LICENSE b/LICENSE index a8014c4..b47f191 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, Ian Yu-Hsun Lin +Copyright (c) 2016-2020, The react-native-voip-push-notification Contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/README.md b/README.md index 12c24b5..c350cb0 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,6 @@ React Native VoIP Push Notification - Currently iOS >= 8.0 only -## Motivation - -Since iOS 8.0 there is an execellent feature called **VoIP Push Notification** ([PushKit][1]), while in React Native only the traditional push notification is supported which limits the possibilities of building a VoIP app with React Native (like me!). - -To understand the benefits of **Voip Push Notification**, please see [VoIP Best Practices][2]. - -**Note 1**: Not sure if Android support this sort of stuff since I'm neither an iOS nor Android expert, from my limited understanding that GCM's [sending high priority push notification][5] might be the case. Correct me if I'm wrong! - -**Note 2** This module is inspired by [PushNotificationIOS][6] and [React Native Push Notification][7] - ## RN Version * 1.1.0+ ( RN 40+ ) @@ -103,7 +93,19 @@ Make sure you enabled the folowing in `Xcode` -> `Signing & Capabilities`: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -... + RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; + + + + // ===== (THIS IS OPTIONAL) ===== + // --- register VoipPushNotification here ASAP rather than in JS. Doing this from the JS side may be too slow for some use cases + // --- see: https://github.com/react-native-webrtc/react-native-voip-push-notification/issues/59#issuecomment-691685841 + [RNVoipPushNotificationManager voipRegistration]; + // ===== (THIS IS OPTIONAL) ===== + + + + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"AppName" initialProperties:nil]; } ... @@ -121,13 +123,7 @@ Make sure you enabled the folowing in `Xcode` -> `Signing & Capabilities`: // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token. } - -// --- Handle incoming pushes (for ios <= 10) -- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type { - [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; -} - -// --- Handle incoming pushes (for ios >= 11) +// --- Handle incoming pushes - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { @@ -160,34 +156,61 @@ Make sure you enabled the folowing in `Xcode` -> `Signing & Capabilities`: ## Linking: On RN60+, auto linking with pod file should work. -Or you can try below: - -## Linking Manually: +
+ Linking Manually -### Add PushKit Framework: + ### Add PushKit Framework: + + - In your Xcode project, select `Build Phases` --> `Link Binary With Libraries` + - Add `PushKit.framework` + + ### Add RNVoipPushNotification: + + #### Option 1: Use [rnpm][3] + + ```bash + rnpm link react-native-voip-push-notification + ``` + + **Note**: If you're using rnpm link make sure the `Header Search Paths` is `recursive`. (In step 3 of manually linking) + + #### Option 2: Manually + + 1. Drag `node_modules/react-native-voip-push-notification/ios/RNVoipPushNotification.xcodeproj` under `/Libraries` + 2. Select `` --> `Build Phases` --> `Link Binary With Libraries` + - Drag `Libraries/RNVoipPushNotification.xcodeproj/Products/libRNVoipPushNotification.a` to `Link Binary With Libraries` + 3. Select `` --> `Build Settings` + - In `Header Search Paths`, add `$(SRCROOT)/../node_modules/react-native-voip-push-notification/ios/RNVoipPushNotification` with `recursive` +
-- In your Xcode project, select `Build Phases` --> `Link Binary With Libraries` -- Add `PushKit.framework` +## API and Usage: -### Add RNVoipPushNotification: +#### Native API: -#### Option 1: Use [rnpm][3] +Voip Push is time sensitive, these native API mainly used in AppDelegate.m, especially before JS bridge is up. +This usually -```bash -rnpm link react-native-voip-push-notification -``` +* `(void)voipRegistration` --- + register delegate for PushKit if you like to register in AppDelegate.m ASAP instead JS side ( too late for some use cases ) +* `(void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type` --- + call this api to fire 'register' event to JS +* `(void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type` --- + call this api to fire 'notification' event to JS +* `(void)addCompletionHandler:(NSString *)uuid completionHandler:(RNVoipPushNotificationCompletion)completionHandler` --- + add completionHandler to RNVoipPush module +* `(void)removeCompletionHandler:(NSString *)uuid` --- + remove completionHandler to RNVoipPush module -**Note**: If you're using rnpm link make sure the `Header Search Paths` is `recursive`. (In step 3 of manually linking) +#### JS API: -#### Option 2: Manually +* `registerVoipToken()` --- JS method to register PushKit delegate +* `onVoipNotificationCompleted(notification.uuid)` --- JS mehtod to tell PushKit we have handled received voip push -1. Drag `node_modules/react-native-voip-push-notification/ios/RNVoipPushNotification.xcodeproj` under `/Libraries` -2. Select `` --> `Build Phases` --> `Link Binary With Libraries` - - Drag `Libraries/RNVoipPushNotification.xcodeproj/Products/libRNVoipPushNotification.a` to `Link Binary With Libraries` -3. Select `` --> `Build Settings` - - In `Header Search Paths`, add `$(SRCROOT)/../node_modules/react-native-voip-push-notification/ios/RNVoipPushNotification` with `recursive` +#### Events: -## Usage: +* `'register'` --- fired when PushKit give us the latest token +* `'notification'` --- fired when received voip push notification +* `'didLoadWithEvents'` --- fired when there are not-fired events been cached before js bridge is up ```javascript @@ -201,51 +224,48 @@ class MyComponent extends React.Component { ... - componentDidMount() { // or anywhere which is most comfortable and appropriate for you - VoipPushNotification.requestPermissions(); // --- optional, you can use another library to request permissions - VoipPushNotification.registerVoipToken(); // --- required - - VoipPushNotification.addEventListener('register', (token) => { - // --- send token to your apn provider server - }); - - VoipPushNotification.addEventListener('localNotification', (notification) => { - // --- when user click local push - }); - - VoipPushNotification.addEventListener('notification', (notification) => { - // --- when receive remote voip push, register your VoIP client, show local notification ... etc - //this.doRegisterOrSomething(); + // --- or anywhere which is most comfortable and appropriate for you, usually ASAP + componentDidMount() { + VoipPushNotification.registerVoipToken(); // --- register token + + VoipPushNotification.addEventListener('didLoadWithEvents', (events) => { + // --- this will fire when there are events occured before js bridge initialized + // --- use this event to execute your event handler manually by event type + + if (!events || !Array.isArray(events) || events.length < 1) { + return; + } + for (let voipPushEvent of events) { + let { name, data } = voipPushEvent; + if (name === VoipPushNotification.RNVoipPushRemoteNotificationsRegisteredEvent) { + this.onVoipPushNotificationRegistered(data); + } else if (name === VoipPushNotification.RNVoipPushRemoteNotificationReceivedEvent) { + this.onVoipPushNotificationiReceived(data); + } + } + }); - // --- This is a boolean constant exported by this module - // --- you can use this constant to distinguish the app is launched by VoIP push notification or not - if (VoipPushNotification.wakeupByPush) { - // this.doSomething() - - // --- remember to set this static variable back to false - // --- since the constant are exported only at initialization time, and it will keep the same in the whole app - VoipPushNotification.wakeupByPush = false; - } - - - // --- optionally, if you `addCompletionHandler` from the native side, once you have done the js jobs to initiate a call, call `completion()` - VoipPushNotification.onVoipNotificationCompleted(notification.getData().uuid); - - - /** - * Local Notification Payload - * - * - `alertBody` : The message displayed in the notification alert. - * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view"; - * - `soundName` : The sound played when the notification is fired (optional). - * - `category` : The category of this notification, required for actionable notifications (optional). - * - `userInfo` : An optional object containing additional notification data. - */ - VoipPushNotification.presentLocalNotification({ - alertBody: "hello! " + notification.getMessage() - }); - }); - } + // --- onVoipPushNotificationRegistered + VoipPushNotification.addEventListener('register', (token) => { + // --- send token to your apn provider server + }); + + // --- onVoipPushNotificationiReceived + VoipPushNotification.addEventListener('notification', (notification) => { + // --- when receive remote voip push, register your VoIP client, show local notification ... etc + this.doSomething(); + + // --- optionally, if you `addCompletionHandler` from the native side, once you have done the js jobs to initiate a call, call `completion()` + VoipPushNotification.onVoipNotificationCompleted(notification.uuid); + }); + } + + // --- unsubscribe event listeners + componentWillUnmount() { + VoipPushNotification.removeEventListener('didLoadWithEvents'); + VoipPushNotification.removeEventListener('register'); + VoipPushNotification.removeEventListener('notification'); + } ... } @@ -263,6 +283,3 @@ class MyComponent extends React.Component { [2]: https://developer.apple.com/library/ios/documentation/Performance/Conceptual/EnergyGuide-iOS/OptimizeVoIP.html [3]: https://github.com/rnpm/rnpm [4]: https://opensource.org/licenses/ISC -[5]: https://developers.google.com/cloud-messaging/concept-options#setting-the-priority-of-a-message -[6]: https://facebook.github.io/react-native/docs/pushnotificationios.html -[7]: https://github.com/zo0r/react-native-push-notification diff --git a/index.js b/index.js index a6f2cd3..88949f1 100644 --- a/index.js +++ b/index.js @@ -2,36 +2,32 @@ import { NativeModules, - DeviceEventEmitter, + NativeEventEmitter, Platform, } from 'react-native'; -var RNVoipPushNotificationManager = NativeModules.RNVoipPushNotificationManager; -var invariant = require('fbjs/lib/invariant'); +const RNVoipPushNotificationManager = NativeModules.RNVoipPushNotificationManager; -var _notifHandlers = new Map(); +const eventEmitter = new NativeEventEmitter(RNVoipPushNotificationManager); +const _eventHandlers = new Map(); -var DEVICE_NOTIF_EVENT = 'voipRemoteNotificationReceived'; -var NOTIF_REGISTER_EVENT = 'voipRemoteNotificationsRegistered'; -var DEVICE_LOCAL_NOTIF_EVENT = 'voipLocalNotificationReceived'; +// --- native unique event names +const RNVoipPushRemoteNotificationsRegisteredEvent = "RNVoipPushRemoteNotificationsRegisteredEvent"; // --- 'register' +const RNVoipPushRemoteNotificationReceivedEvent = "RNVoipPushRemoteNotificationReceivedEvent"; // --- 'notification' +const RNVoipPushDidLoadWithEvents = "RNVoipPushDidLoadWithEvents"; // --- 'didLoadWithEvents' export default class RNVoipPushNotification { - static wakeupByPush = (Platform.OS == 'ios' && RNVoipPushNotificationManager.wakeupByPush === 'true'); + static get RNVoipPushRemoteNotificationsRegisteredEvent() { + return RNVoipPushRemoteNotificationsRegisteredEvent; + } - /** - * Schedules the localNotification for immediate presentation. - * - * details is an object containing: - * - * - `alertBody` : The message displayed in the notification alert. - * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view"; - * - `soundName` : The sound played when the notification is fired (optional). - * - `category` : The category of this notification, required for actionable notifications (optional). - * - `userInfo` : An optional object containing additional notification data. - */ - static presentLocalNotification(details) { - RNVoipPushNotificationManager.presentLocalNotification(details); + static get RNVoipPushRemoteNotificationReceivedEvent() { + return RNVoipPushRemoteNotificationReceivedEvent; + } + + static get RNVoipPushDidLoadWithEvents() { + return RNVoipPushDidLoadWithEvents; } /** @@ -40,89 +36,53 @@ export default class RNVoipPushNotification { * * Valid events are: * - * - `notification` : Fired when a remote notification is received. The - * handler will be invoked with an instance of `PushNotificationIOS`. - * - `register`: Fired when the user registers for remote notifications. The - * handler will be invoked with a hex string representing the deviceToken. + * - `notification` : Fired when a remote notification is received. + * - `register`: Fired when the user registers for remote notifications. + * - `didLoadWithEvents`: Fired when the user have initially subscribed any listener and has cached events already. */ static addEventListener(type, handler) { - invariant( - type === 'notification' || type === 'register' || type === 'localNotification', - 'RNVoipPushNotificationManager only supports `notification`, `register` and `localNotification` events' - ); - var listener; + let listener; if (type === 'notification') { - listener = DeviceEventEmitter.addListener( - DEVICE_NOTIF_EVENT, - (notifData) => { - handler(new RNVoipPushNotification(notifData)); + listener = eventEmitter.addListener( + RNVoipPushRemoteNotificationReceivedEvent, + (notificationPayload) => { + handler(notificationPayload); } ); - } else if (type === 'localNotification') { - listener = DeviceEventEmitter.addListener( - DEVICE_LOCAL_NOTIF_EVENT, - (notifData) => { - handler(new RNVoipPushNotification(notifData)); + } else if (type === 'register') { + listener = eventEmitter.addListener( + RNVoipPushRemoteNotificationsRegisteredEvent, + (deviceToken) => { + handler(deviceToken); } ); - } else if (type === 'register') { - listener = DeviceEventEmitter.addListener( - NOTIF_REGISTER_EVENT, - (registrationInfo) => { - handler(registrationInfo.deviceToken); + } else if (type === 'didLoadWithEvents') { + listener = eventEmitter.addListener( + RNVoipPushDidLoadWithEvents, + (events) => { + handler(events); } ); + } else { + return; } - _notifHandlers.set(handler, listener); + + // --- we only support one listener at a time, remove to prevent leak + RNVoipPushNotification.removeEventListener(type); + _eventHandlers.set(type, listener); } /** * Removes the event listener. Do this in `componentWillUnmount` to prevent * memory leaks */ - static removeEventListener(type, handler) { - invariant( - type === 'notification' || type === 'register' || type === 'localNotification', - 'RNVoipPushNotification only supports `notification`, `register` and `localNotification` events' - ); - var listener = _notifHandlers.get(handler); + static removeEventListener(type) { + let listener = _eventHandlers.get(type); if (!listener) { return; } listener.remove(); - _notifHandlers.delete(handler); - } - - /** - * Requests notification permissions from iOS, prompting the user's - * dialog box. By default, it will request all notification permissions, but - * a subset of these can be requested by passing a map of requested - * permissions. - * The following permissions are supported: - * - * - `alert` - * - `badge` - * - `sound` - * - * If a map is provided to the method, only the permissions with truthy values - * will be requested. - */ - static requestPermissions(permissions) { - var requestedPermissions = {}; - if (permissions) { - requestedPermissions = { - alert: !!permissions.alert, - badge: !!permissions.badge, - sound: !!permissions.sound - }; - } else { - requestedPermissions = { - alert: true, - badge: true, - sound: true - }; - } - RNVoipPushNotificationManager.requestPermissions(requestedPermissions); + _eventHandlers.delete(type); } /** @@ -150,63 +110,4 @@ export default class RNVoipPushNotification { RNVoipPushNotificationManager.onVoipNotificationCompleted(uuid); } - /** - * You will never need to instantiate `RNVoipPushNotification` yourself. - * Listening to the `notification` event and invoking - * `popInitialNotification` is sufficient - */ - constructor(nativeNotif) { - this._data = {}; - - // Extract data from Apple's `aps` dict as defined: - - // https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html - - Object.keys(nativeNotif).forEach((notifKey) => { - var notifVal = nativeNotif[notifKey]; - if (notifKey === 'aps') { - this._alert = notifVal.alert; - this._sound = notifVal.sound; - this._badgeCount = notifVal.badge; - } else { - this._data[notifKey] = notifVal; - } - }); - } - - /** - * An alias for `getAlert` to get the notification's main message string - */ - getMessage() { - // alias because "alert" is an ambiguous name - return this._alert; - } - - /** - * Gets the sound string from the `aps` object - */ - getSound() { - return this._sound; - } - - /** - * Gets the notification's main message from the `aps` object - */ - getAlert() { - return this._alert; - } - - /** - * Gets the badge count number from the `aps` object - */ - getBadgeCount() { - return this._badgeCount; - } - - /** - * Gets the data object on the notif - */ - getData() { - return this._data; - } } diff --git a/ios/RNVoipPushNotification/RNVoipPushNotificationManager.h b/ios/RNVoipPushNotification/RNVoipPushNotificationManager.h index f53d5b5..ea116a8 100644 --- a/ios/RNVoipPushNotification/RNVoipPushNotificationManager.h +++ b/ios/RNVoipPushNotification/RNVoipPushNotificationManager.h @@ -2,26 +2,24 @@ // RNVoipPushNotificationManager.h // RNVoipPushNotification // -// Created by Ian Yu-Hsun Lin on 4/18/16. -// Copyright © 2016 ianyuhsunlin. All rights reserved. +// Copyright 2016-2020 The react-native-voip-push-notification Contributors +// see: https://github.com/react-native-webrtc/react-native-voip-push-notification/graphs/contributors +// SPDX-License-Identifier: ISC, MIT // #import - #import +#import -@interface RNVoipPushNotificationManager : NSObject +@interface RNVoipPushNotificationManager : RCTEventEmitter typedef void (^RNVoipPushNotificationCompletion)(void); @property (nonatomic, strong) NSMutableDictionary *completionHandlers; -- (void)voipRegistration; -- (void)registerUserNotification:(NSDictionary *)permissions; -- (NSDictionary *)checkPermissions; ++ (void)voipRegistration; + (void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type; + (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type; -+ (NSString *)getCurrentAppBackgroundState; + (void)addCompletionHandler:(NSString *)uuid completionHandler:(RNVoipPushNotificationCompletion)completionHandler; + (void)removeCompletionHandler:(NSString *)uuid; diff --git a/ios/RNVoipPushNotification/RNVoipPushNotificationManager.m b/ios/RNVoipPushNotification/RNVoipPushNotificationManager.m index 9781361..2699a77 100644 --- a/ios/RNVoipPushNotification/RNVoipPushNotificationManager.m +++ b/ios/RNVoipPushNotification/RNVoipPushNotificationManager.m @@ -2,8 +2,9 @@ // RNVoipPushNotificationManager.m // RNVoipPushNotification // -// Created by Ian Yu-Hsun Lin on 4/18/16. -// Copyright © 2016 ianyuhsunlin. All rights reserved. +// Copyright 2016-2020 The react-native-voip-push-notification Contributors +// see: https://github.com/react-native-webrtc/react-native-voip-push-notification/graphs/contributors +// SPDX-License-Identifier: ISC, MIT // #import @@ -12,63 +13,48 @@ #import #import #import +#import #import -NSString *const RNVoipRemoteNotificationsRegistered = @"voipRemoteNotificationsRegistered"; -NSString *const RNVoipLocalNotificationReceived = @"voipLocalNotificationReceived"; -NSString *const RNVoipRemoteNotificationReceived = @"voipRemoteNotificationReceived"; +NSString *const RNVoipPushRemoteNotificationsRegisteredEvent = @"RNVoipPushRemoteNotificationsRegisteredEvent"; +NSString *const RNVoipPushRemoteNotificationReceivedEvent = @"RNVoipPushRemoteNotificationReceivedEvent"; +NSString *const RNVoipPushDidLoadWithEvents = @"RNVoipPushDidLoadWithEvents"; -static NSString *RCTCurrentAppBackgroundState() +@implementation RNVoipPushNotificationManager { - static NSDictionary *states; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - states = @{ - @(UIApplicationStateActive): @"active", - @(UIApplicationStateBackground): @"background", - @(UIApplicationStateInactive): @"inactive" - }; - }); - - if (RCTRunningInAppExtension()) { - return @"extension"; - } - - return states[@(RCTSharedApplication().applicationState)] ? : @"unknown"; + bool _hasListeners; + NSMutableArray *_delayedEvents; } -@implementation RCTConvert (UILocalNotification) - -+ (UILocalNotification *)UILocalNotification:(id)json -{ - NSDictionary *details = [self NSDictionary:json]; - UILocalNotification *notification = [UILocalNotification new]; - notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; - notification.alertTitle = [RCTConvert NSString:details[@"alertTitle"]]; - notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; - notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; - notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; - notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; - notification.category = [RCTConvert NSString:details[@"category"]]; - return notification; -} +RCT_EXPORT_MODULE(); -@end +static bool _isVoipRegistered = NO; +static NSMutableDictionary *completionHandlers = nil; -@implementation RNVoipPushNotificationManager -RCT_EXPORT_MODULE(); +// ===== +// ===== RN Module Configure and Override ===== +// ===== -@synthesize bridge = _bridge; -static NSMutableDictionary *completionHandlers = nil; -+ (NSMutableDictionary *)completionHandlers { - if (completionHandlers == nil) { - completionHandlers = [NSMutableDictionary new]; +- (instancetype)init +{ + if (self = [super init]) { + _delayedEvents = [NSMutableArray array]; } - return completionHandlers; + return self; } ++ (id)allocWithZone:(NSZone *)zone { + static RNVoipPushNotificationManager *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [super allocWithZone:zone]; + }); + return sharedInstance; +} + +// --- clean observer and completionHandlers when app close - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -86,128 +72,116 @@ - (void)dealloc [[RNVoipPushNotificationManager completionHandlers] removeAllObjects]; } -- (void)setBridge:(RCTBridge *)bridge ++ (BOOL)requiresMainQueueSetup { - _bridge = bridge; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationsRegistered:) - name:RNVoipRemoteNotificationsRegistered - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleLocalNotificationReceived:) - name:RNVoipLocalNotificationReceived - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationReceived:) - name:RNVoipRemoteNotificationReceived - object:nil]; + return YES; } -- (NSDictionary *)constantsToExport +// --- Override method of RCTEventEmitter +- (NSArray *)supportedEvents { - NSString *currentState = RCTCurrentAppBackgroundState(); - NSLog(@"[RNVoipPushNotificationManager] constantsToExport currentState = %@", currentState); - return @{@"wakeupByPush": (currentState == @"background") ? @"true" : @"false"}; + return @[ + RNVoipPushRemoteNotificationsRegisteredEvent, + RNVoipPushRemoteNotificationReceivedEvent, + RNVoipPushDidLoadWithEvents + ]; } -- (void)registerUserNotification:(NSDictionary *)permissions +- (void)startObserving { - UIUserNotificationType types = UIUserNotificationTypeNone; - if (permissions) { - if ([RCTConvert BOOL:permissions[@"alert"]]) { - types |= UIUserNotificationTypeAlert; - } - if ([RCTConvert BOOL:permissions[@"badge"]]) { - types |= UIUserNotificationTypeBadge; - } - if ([RCTConvert BOOL:permissions[@"sound"]]) { - types |= UIUserNotificationTypeSound; - } - } else { - types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; + _hasListeners = YES; + if ([_delayedEvents count] > 0) { + [self sendEventWithName:RNVoipPushDidLoadWithEvents body:_delayedEvents]; } - - UIApplication *app = RCTSharedApplication(); - UIUserNotificationSettings *notificationSettings = - [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil]; - [app registerUserNotificationSettings:notificationSettings]; } -- (void)voipRegistration +- (void)stopObserving { - NSLog(@"[RNVoipPushNotificationManager] voipRegistration"); - - dispatch_queue_t mainQueue = dispatch_get_main_queue(); - dispatch_async(mainQueue, ^{ - // Create a push registry object - PKPushRegistry * voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue]; - // Set the registry's delegate to AppDelegate - voipRegistry.delegate = (RNVoipPushNotificationManager *)RCTSharedApplication().delegate; - // Set the push type to VoIP - voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; - }); + _hasListeners = NO; } -- (NSDictionary *)checkPermissions -{ - NSUInteger types = [RCTSharedApplication() currentUserNotificationSettings].types; - - return @{ - @"alert": @((types & UIUserNotificationTypeAlert) > 0), - @"badge": @((types & UIUserNotificationTypeBadge) > 0), - @"sound": @((types & UIUserNotificationTypeSound) > 0), - }; - + + +// ===== +// ===== Class Method ===== +// ===== + +// --- send directly if has listeners, cache it otherwise +- (void)sendEventWithNameWrapper:(NSString *)name body:(id)body { + if (_hasListeners) { + [self sendEventWithName:name body:body]; + } else { + NSDictionary *dictionary = @{ + @"name": name, + @"data": body + }; + [_delayedEvents addObject:dictionary]; + } } -+ (NSString *)getCurrentAppBackgroundState +// --- register delegate for PushKit to delivery credential and remote voip push to your delegate +// --- this usually register once and ASAP after your app launch ++ (void)voipRegistration { - return RCTCurrentAppBackgroundState(); + if (_isVoipRegistered) { +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] voipRegistration is already registered"); +#endif + } else { + _isVoipRegistered = YES; +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] voipRegistration enter"); +#endif + dispatch_queue_t mainQueue = dispatch_get_main_queue(); + dispatch_async(mainQueue, ^{ + // --- Create a push registry object + PKPushRegistry * voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue]; + // --- Set the registry's delegate to AppDelegate + voipRegistry.delegate = (RNVoipPushNotificationManager *)RCTSharedApplication().delegate; + // --- Set the push type to VoIP + voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; + }); + } } +// --- should be called from `AppDelegate.didUpdatePushCredentials` + (void)didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { - NSLog(@"[RNVoipPushNotificationManager] didUpdatePushCredentials credentials.token = %@, type = %@", credentials.token, type); +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] didUpdatePushCredentials credentials.token = %@, type = %@", credentials.token, type); +#endif + NSUInteger voipTokenLength = credentials.token.length; + if (voipTokenLength == 0) { + return; + } NSMutableString *hexString = [NSMutableString string]; - NSUInteger voipTokenLength = credentials.token.length; const unsigned char *bytes = credentials.token.bytes; for (NSUInteger i = 0; i < voipTokenLength; i++) { [hexString appendFormat:@"%02x", bytes[i]]; } - [[NSNotificationCenter defaultCenter] postNotificationName:RNVoipRemoteNotificationsRegistered - object:self - userInfo:@{@"deviceToken" : [hexString copy]}]; -} -+ (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type -{ - NSLog(@"[RNVoipPushNotificationManager] didReceiveIncomingPushWithPayload payload.dictionaryPayload = %@, type = %@", payload.dictionaryPayload, type); - [[NSNotificationCenter defaultCenter] postNotificationName:RNVoipRemoteNotificationReceived - object:self - userInfo:payload.dictionaryPayload]; + RNVoipPushNotificationManager *voipPushManager = [RNVoipPushNotificationManager allocWithZone: nil]; + [voipPushManager sendEventWithNameWrapper:RNVoipPushRemoteNotificationsRegisteredEvent body:[hexString copy]]; } -- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification +// --- should be called from `AppDelegate.didReceiveIncomingPushWithPayload` ++ (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { - NSLog(@"[RNVoipPushNotificationManager] handleRemoteNotificationsRegistered notification.userInfo = %@", notification.userInfo); - [_bridge.eventDispatcher sendDeviceEventWithName:@"voipRemoteNotificationsRegistered" - body:notification.userInfo]; -} +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] didReceiveIncomingPushWithPayload payload.dictionaryPayload = %@, type = %@", payload.dictionaryPayload, type); +#endif -- (void)handleLocalNotificationReceived:(NSNotification *)notification -{ - NSLog(@"[RNVoipPushNotificationManager] handleLocalNotificationReceived notification.userInfo = %@", notification.userInfo); - [_bridge.eventDispatcher sendDeviceEventWithName:@"voipLocalNotificationReceived" - body:notification.userInfo]; + RNVoipPushNotificationManager *voipPushManager = [RNVoipPushNotificationManager allocWithZone: nil]; + [voipPushManager sendEventWithNameWrapper:RNVoipPushRemoteNotificationReceivedEvent body:payload.dictionaryPayload]; } -- (void)handleRemoteNotificationReceived:(NSNotification *)notification -{ - NSLog(@"[RNVoipPushNotificationManager] handleRemoteNotificationReceived notification.userInfo = %@", notification.userInfo); - [_bridge.eventDispatcher sendDeviceEventWithName:@"voipRemoteNotificationReceived" - body:notification.userInfo]; +// --- getter for completionHandlers ++ (NSMutableDictionary *)completionHandlers { + if (completionHandlers == nil) { + completionHandlers = [NSMutableDictionary new]; + } + return completionHandlers; } + (void)addCompletionHandler:(NSString *)uuid completionHandler:(RNVoipPushNotificationCompletion)completionHandler @@ -221,60 +195,41 @@ + (void)removeCompletionHandler:(NSString *)uuid [self.completionHandlers removeObjectForKey:uuid]; } -RCT_EXPORT_METHOD(onVoipNotificationCompleted:(NSString *)uuid) -{ - RNVoipPushNotificationCompletion completion = [[RNVoipPushNotificationManager completionHandlers] objectForKey:uuid]; - if (completion) { - [RNVoipPushNotificationManager removeCompletionHandler: uuid]; - dispatch_async(dispatch_get_main_queue(), ^{ - NSLog(@"[RNVoipPushNotificationManager] onVoipNotificationCompleted() complete(). uuid = %@", uuid); - completion(); - }); - } else { - NSLog(@"[RNVoipPushNotificationManager] onVoipNotificationCompleted() not found. uuid = %@", uuid); - } -} -RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) -{ - if (RCTRunningInAppExtension()) { - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [self registerUserNotification:permissions]; - }); -} +// ===== +// ===== React Method ===== +// ===== + +// --- register voip push token RCT_EXPORT_METHOD(registerVoipToken) { if (RCTRunningInAppExtension()) { return; } dispatch_async(dispatch_get_main_queue(), ^{ - [self voipRegistration]; + [RNVoipPushNotificationManager voipRegistration]; }); } -RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) +// --- called from js when finished to process incoming voip push +RCT_EXPORT_METHOD(onVoipNotificationCompleted:(NSString *)uuid) { - if (RCTRunningInAppExtension()) { - callback(@[@{@"alert": @NO, @"badge": @NO, @"sound": @NO}]); + RNVoipPushNotificationCompletion completion = [[RNVoipPushNotificationManager completionHandlers] objectForKey:uuid]; + if (completion) { +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] onVoipNotificationCompleted() complete(). uuid = %@", uuid); +#endif + [RNVoipPushNotificationManager removeCompletionHandler: uuid]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); return; } - callback(@[[self checkPermissions]]); -} - -RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [RCTSharedApplication() presentLocalNotificationNow:notification]; - }); -} - -+ (BOOL)requiresMainQueueSetup -{ - return YES; +#ifdef DEBUG + RCTLog(@"[RNVoipPushNotificationManager] onVoipNotificationCompleted() not found. uuid = %@", uuid); +#endif } @end