├── .gitignore ├── LICENSE ├── README.md ├── RNCallKit.podspec ├── actions.js ├── index.js ├── ios ├── RNCallKit.xcodeproj │ └── project.pbxproj └── RNCallKit │ ├── RNCallKit.h │ └── RNCallKit.m └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # CUSTOM 2 | # 3 | .*.swp 4 | 5 | # OSX 6 | # 7 | .DS_Store 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | .idea 32 | .gradle 33 | local.properties 34 | 35 | # node.js 36 | # 37 | node_modules/ 38 | npm-debug.log 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Ian Yu-Hsun Lin 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | # This is no longer supported, please use [react-native-callkeep](https://github.com/react-native-webrtc/react-native-callkeep) instead. 4 | 5 | -- 6 | 7 | ## React Native CallKit - iOS >= 10.0 only 8 | 9 | [![npm version](https://badge.fury.io/js/react-native-callkit.svg)](https://badge.fury.io/js/react-native-callkit) 10 | [![npm downloads](https://img.shields.io/npm/dm/react-native-callkit.svg?maxAge=2592000)](https://img.shields.io/npm/dm/react-native-callkit.svg?maxAge=2592000) 11 | 12 | **React Native CallKit** utilises a brand new iOS 10 framework **CallKit** to make the life easier for VoIP developers using React Native. 13 | 14 | For more information about **CallKit**, please see [Official CallKit Framework Document][1] or [Introduction to CallKit by Xamarin][2] 15 | 16 | **Note**: Since CallKit is quite new, this module will be updated frequently so be careful with the version you are using. 17 | 18 | ## Version 19 | 20 | Use version >= **1.1.0** if you're using react native >= 0.40 21 | 22 | ## Installation (without CocoaPods) 23 | 24 | ### NPM module 25 | 26 | ```bash 27 | npm install --save react-native-callkit 28 | ``` 29 | 30 | ### Link Library 31 | 32 | ```bash 33 | rnpm link react-native-callkit 34 | ``` 35 | 36 | ## Installation (with CocoaPods) 37 | 38 | ### NPM module 39 | 40 | ```bash 41 | npm install --save react-native-callkit 42 | ``` 43 | 44 | ### CocaPods 45 | ```bash 46 | cd ios 47 | pod install 48 | ``` 49 | 50 | ## Installation common steps 51 | 52 | ### Info.plist 53 | 54 | Add `voip` under `UIBackgroundModes` 55 | 56 | Note that it must be done via editing `Info.plist` as in Xcode 9 there is no `voip` option in `Capabilities`. 57 | 58 | ``` 59 | UIBackgroundModes 60 | 61 | voip 62 | 63 | ``` 64 | 65 | ### Add Frameworks 66 | 67 | In `Xcode` -> `Build Phases` -> `Link Binary With Libraries`, add `CallKit.framework` and `Intents.framework` with `Optional` status 68 | 69 | ### AppDelegate.m 70 | 71 | #### - Import Library 72 | 73 | ```obj-c 74 | #import "RNCallKit.h" 75 | ``` 76 | 77 | #### - Change the way you initialise React Root View (required if <= 1.2.1) 78 | 79 | Initialise **RNCallKit** first, since we need our custom `observer` of `NSNotificationCenter` to be started as soon as the app is initialising 80 | 81 | ```diff 82 | 83 | // This is how you normally initialise React Root View, delete it 84 | -RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 85 | - moduleName:@"MyApp" 86 | - initialProperties:nil 87 | - launchOptions:launchOptions]; 88 | 89 | // Initialise RNCallKit 90 | +RNCallKit *rncallkit = [[RNCallKit alloc] init]; 91 | 92 | // Initialise React Bridge with RNCallKit 93 | +RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation 94 | + moduleProvider:^{ return @[rncallkit]; } 95 | + launchOptions:launchOptions]; 96 | 97 | // Initialise React Root View with React Bridge you've just created 98 | +RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 99 | + moduleName:@"MyApp" 100 | + initialProperties:nil]; 101 | ``` 102 | 103 | #### - Handling User Activity 104 | 105 | This delegate will be called when the user tries to start a call from native Phone App 106 | 107 | ```obj-c 108 | 109 | - (BOOL)application:(UIApplication *)application 110 | continueUserActivity:(NSUserActivity *)userActivity 111 | restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler 112 | { 113 | return [RNCallKit application:application 114 | continueUserActivity:userActivity 115 | restorationHandler:restorationHandler]; 116 | } 117 | 118 | 119 | ``` 120 | 121 | ## API 122 | 123 | ### setup 124 | 125 | - **options**: object 126 | - **appName**: string (required) 127 | - It will be displayed on system UI when incoming calls received 128 | - **imageName**: string (optional) 129 | - If provided, it will be displayed on system UI during the call 130 | - **ringtoneSound**: string (optional) 131 | - If provided, it will be played when incoming calls received; the system will use the default ringtone if this is not provided 132 | 133 | Initialise RNCallKit with options 134 | 135 | ### displayIncomingCall 136 | 137 | - **uuid**: string 138 | - **handle**: string 139 | - **handleType**: string (optional) 140 | - generic 141 | - number (default) 142 | - email 143 | - **hasVideo**: boolean (optional) 144 | - false (default) 145 | - **localizedCallerName**: string (optional) 146 | 147 | Call when you receive incoming calls to display system UI 148 | 149 | ### startCall 150 | 151 | - **uuid**: string 152 | - **handle**: string 153 | - **handleType**: string (optional) 154 | - generic 155 | - number (default) 156 | - email 157 | - **contactIdentifier**: string (optional) 158 | - The identifier is displayed in the native call UI, and is typically the name of the call recipient. 159 | 160 | Call when you make an outgoing call 161 | 162 | ### endCall 163 | 164 | - **uuid**: string 165 | 166 | Call when you finish an incoming/outgoing call 167 | 168 | ### setMutedCall 169 | 170 | - **uuid**: string 171 | - **muted**: boolean 172 | 173 | Switch the mic on/off 174 | 175 | ### checkIfBusy 176 | 177 | Checks if there are any active calls on the device and returns a promise with a boolean value (`true` if there're active calls, `false` otherwise). 178 | 179 | ### checkSpeaker 180 | 181 | Checks if the device speaker is on and returns a promise with a boolean value (`true` if speaker is on, `false` otherwise). 182 | 183 | ## Events 184 | 185 | ### - didReceiveStartCallAction 186 | 187 | **data**: 188 | 189 | ```javascript 190 | { 191 | handle: '886900000000' // The number/name got from Recents in built-in Phone app 192 | } 193 | ``` 194 | 195 | User start call action from **Recents** in built-in **Phone** app 196 | 197 | Try to start your call action from here (e.g. get credentials of the user by `data.handle` and/or send INVITE to your SIP server) 198 | 199 | After all works are done, remember to call `RNCallKit.startCall(uuid, calleeNumber)` 200 | 201 | ### - answerCall 202 | 203 | User answer the incoming call 204 | 205 | Do your normal `Answering` actions here 206 | 207 | **data**: 208 | 209 | ```javascript 210 | { 211 | callUUID: 'f0ee907b-6dbd-45a8-858a-903decb198f8' // The UUID of the call that is to be answered 212 | } 213 | ``` 214 | 215 | ### - endCall 216 | 217 | User finish the call 218 | 219 | Do your normal `Hang Up` actions here 220 | 221 | **data**: 222 | 223 | ```javascript 224 | { 225 | callUUID: 'f0ee907b-6dbd-45a8-858a-903decb198f8' // The UUID of the call that is to be hung 226 | } 227 | ``` 228 | 229 | ### - didActivateAudioSession 230 | 231 | The `AudioSession` has been activated by **RNCallKit**, you might want to do following things when receiving this event: 232 | 233 | - Start playing ringback if it is an outgoing call 234 | 235 | ### - didDisplayIncomingCall 236 | 237 | Callback for `RNCallKit.displayIncomingCall` 238 | 239 | **error**: string (optional) 240 | 241 | ### - didPerformSetMutedCallAction 242 | 243 | A call was muted by the system or the user: 244 | 245 | **muted**: boolean 246 | 247 | ## Usage 248 | 249 | ```javascript 250 | import React from 'react'; 251 | import RNCallKit from 'react-native-callkit'; 252 | 253 | import uuid from 'uuid'; 254 | 255 | class RNCallKitExample extends React.Component { 256 | constructor(props) { 257 | 258 | // Initialise RNCallKit 259 | let options = { 260 | appName: 'RNCallKitExample', 261 | imageName: 'my_image_name_in_bundle', 262 | ringtoneSound: 'my_ringtone_sound_filename_in_bundle', 263 | }; 264 | try { 265 | RNCallKit.setup(options); 266 | } catch (err) { 267 | console.log('error:', err.message); 268 | } 269 | 270 | // Add RNCallKit Events 271 | RNCallKit.addEventListener('didReceiveStartCallAction', this.onRNCallKitDidReceiveStartCallAction); 272 | RNCallKit.addEventListener('answerCall', this.onRNCallKitPerformAnswerCallAction); 273 | RNCallKit.addEventListener('endCall', this.onRNCallKitPerformEndCallAction); 274 | RNCallKit.addEventListener('didActivateAudioSession', this.onRNCallKitDidActivateAudioSession); 275 | RNCallKit.addEventListener('didDisplayIncomingCall', this.onRNCallKitDidDisplayIncomingCall); 276 | RNCallKit.addEventListener('didPerformSetMutedCallAction', this.onRNCallKitDidPerformSetMutedCallAction); 277 | } 278 | 279 | onRNCallKitDidReceiveStartCallAction(data) { 280 | /* 281 | * Your normal start call action 282 | * 283 | * ... 284 | * 285 | */ 286 | 287 | let _uuid = uuid.v4(); 288 | RNCallKit.startCall(_uuid, data.handle); 289 | } 290 | 291 | onRNCallKitPerformAnswerCallAction(data) { 292 | /* You will get this event when the user answer the incoming call 293 | * 294 | * Try to do your normal Answering actions here 295 | * 296 | * e.g. this.handleAnswerCall(data.callUUID); 297 | */ 298 | } 299 | 300 | onRNCallKitPerformEndCallAction(data) { 301 | /* You will get this event when the user finish the incoming/outgoing call 302 | * 303 | * Try to do your normal Hang Up actions here 304 | * 305 | * e.g. this.handleHangUpCall(data.callUUID); 306 | */ 307 | } 308 | 309 | onRNCallKitDidActivateAudioSession(data) { 310 | /* You will get this event when the the AudioSession has been activated by **RNCallKit**, 311 | * you might want to do following things when receiving this event: 312 | * 313 | * - Start playing ringback if it is an outgoing call 314 | */ 315 | } 316 | 317 | onRNCallKitDidDisplayIncomingCall(error) { 318 | /* You will get this event after RNCallKit finishes showing incoming call UI 319 | * You can check if there was an error while displaying 320 | */ 321 | } 322 | 323 | onRNCallKitDidPerformSetMutedCallAction(muted) { 324 | /* You will get this event after the system or the user mutes a call 325 | * You can use it to toggle the mic on your custom call UI 326 | */ 327 | } 328 | 329 | // This is a fake function where you can receive incoming call notifications 330 | onIncomingCall() { 331 | // Store the generated uuid somewhere 332 | // You will need this when calling RNCallKit.endCall() 333 | let _uuid = uuid.v4(); 334 | RNCallKit.displayIncomingCall(_uuid, "886900000000") 335 | } 336 | 337 | // This is a fake function where you make outgoing calls 338 | onOutgoingCall() { 339 | // Store the generated uuid somewhere 340 | // You will need this when calling RNCallKit.endCall() 341 | let _uuid = uuid.v4(); 342 | RNCallKit.startCall(_uuid, "886900000000") 343 | } 344 | 345 | // This is a fake function where you hang up calls 346 | onHangUpCall() { 347 | // get the _uuid you stored earlier 348 | RNCallKit.endCall(_uuid) 349 | } 350 | 351 | render() { 352 | } 353 | } 354 | 355 | ``` 356 | 357 | ## Original Author: 358 | 359 | [![ianlin](https://avatars1.githubusercontent.com/u/914406?s=48)](https://github.com/ianlin) 360 | 361 | ## License 362 | 363 | [ISC License][3] (functionality equivalent to **MIT License**) 364 | 365 | [1]: https://developer.apple.com/reference/callkit?language=objc 366 | [2]: https://developer.xamarin.com/guides/ios/platform_features/introduction-to-ios10/callkit/ 367 | [3]: https://opensource.org/licenses/ISC 368 | [4]: https://github.com/zxcpoiu/react-native-incall-manager 369 | -------------------------------------------------------------------------------- /RNCallKit.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "RNCallKit" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.homepage = package['homepage'] 10 | s.license = package['license'] 11 | s.author = package['author'] 12 | s.source = { :git => package['repository']['url'], :tag => "v#{s.version}" } 13 | s.requires_arc = true 14 | s.platform = :ios, "8.0" 15 | s.source_files = "ios/RNCallKit/*.{h,m}" 16 | s.dependency 'React/Core' 17 | end 18 | 19 | -------------------------------------------------------------------------------- /actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | NativeModules, 3 | NativeEventEmitter, 4 | } from 'react-native'; 5 | 6 | const _RNCallKit = NativeModules.RNCallKit; 7 | const _RNCallKitEmitter = new NativeEventEmitter(_RNCallKit); 8 | 9 | const RNCallKitDidReceiveStartCallAction = 'RNCallKitDidReceiveStartCallAction'; 10 | const RNCallKitPerformAnswerCallAction = 'RNCallKitPerformAnswerCallAction'; 11 | const RNCallKitPerformEndCallAction = 'RNCallKitPerformEndCallAction'; 12 | const RNCallKitDidActivateAudioSession = 'RNCallKitDidActivateAudioSession'; 13 | const RNCallKitDidDisplayIncomingCall = 'RNCallKitDidDisplayIncomingCall'; 14 | const RNCallKitDidPerformSetMutedCallAction = 'RNCallKitDidPerformSetMutedCallAction'; 15 | 16 | didReceiveStartCallAction = handler => { 17 | const listener = _RNCallKitEmitter.addListener( 18 | RNCallKitDidReceiveStartCallAction, 19 | (data) => { handler(data);} 20 | ); 21 | _RNCallKit._startCallActionEventListenerAdded(); 22 | return listener; 23 | } 24 | 25 | answerCall = handler => ( 26 | _RNCallKitEmitter.addListener( 27 | RNCallKitPerformAnswerCallAction, 28 | (data) => { handler(data);} 29 | ) 30 | ) 31 | 32 | endCall = handler => ( 33 | _RNCallKitEmitter.addListener( 34 | RNCallKitPerformEndCallAction, 35 | (data) => { handler(data); } 36 | ) 37 | ) 38 | 39 | didActivateAudioSession = handler => ( 40 | _RNCallKitEmitter.addListener( 41 | RNCallKitDidActivateAudioSession, 42 | () => { handler(); } 43 | ) 44 | ) 45 | 46 | didDisplayIncomingCall = handler => ( 47 | _RNCallKitEmitter.addListener( 48 | RNCallKitDidDisplayIncomingCall, 49 | (data) => { handler(data.error); } 50 | ) 51 | ) 52 | 53 | didPerformSetMutedCallAction = handler => ( 54 | _RNCallKitEmitter.addListener( 55 | RNCallKitDidPerformSetMutedCallAction, 56 | (data) => { handler(data.muted); } 57 | ) 58 | ) 59 | 60 | export const listeners = { 61 | didReceiveStartCallAction, 62 | answerCall, 63 | endCall, 64 | didActivateAudioSession, 65 | didDisplayIncomingCall, 66 | didPerformSetMutedCallAction, 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | NativeModules, 5 | Platform, 6 | } from 'react-native'; 7 | 8 | import { listeners } from './actions' 9 | 10 | const _RNCallKit = NativeModules.RNCallKit; 11 | 12 | const _callkitEventHandlers = new Map(); 13 | 14 | export default class RNCallKit { 15 | 16 | static addEventListener(type, handler) { 17 | if (Platform.OS !== 'ios') return; 18 | const listener = listeners[type](handler) 19 | _callkitEventHandlers.set(handler, listener); 20 | } 21 | 22 | static removeEventListener(type, handler) { 23 | if (Platform.OS !== 'ios') return; 24 | var listener = _callkitEventHandlers.get(handler); 25 | if (!listener) { 26 | return; 27 | } 28 | listener.remove(); 29 | _callkitEventHandlers.delete(handler); 30 | } 31 | 32 | static setup(options) { 33 | if (Platform.OS !== 'ios') return; 34 | if (!options.appName) { 35 | throw new Error('RNCallKit.setup: option "appName" is required'); 36 | } 37 | if (typeof options.appName !== 'string') { 38 | throw new Error('RNCallKit.setup: option "appName" should be of type "string"'); 39 | } 40 | _RNCallKit.setup(options); 41 | } 42 | 43 | static displayIncomingCall(uuid, handle, handleType = 'number', hasVideo = false, localizedCallerName?: String) { 44 | if (Platform.OS !== 'ios') return; 45 | _RNCallKit.displayIncomingCall(uuid, handle, handleType, hasVideo, localizedCallerName); 46 | } 47 | 48 | static startCall(uuid, handle, handleType = 'number', hasVideo = false, contactIdentifier?: String) { 49 | if (Platform.OS !== 'ios') return; 50 | _RNCallKit.startCall(uuid, handle, handleType, hasVideo, contactIdentifier); 51 | } 52 | 53 | static reportConnectedOutgoingCallWithUUID(uuid) { 54 | if (Platform.OS !== 'ios') return; 55 | _RNCallKit.reportConnectedOutgoingCallWithUUID(uuid); 56 | } 57 | 58 | static endCall(uuid) { 59 | if (Platform.OS !== 'ios') return; 60 | _RNCallKit.endCall(uuid); 61 | } 62 | 63 | static endAllCalls() { 64 | if (Platform.OS !== 'ios') return; 65 | _RNCallKit.endAllCalls(); 66 | } 67 | 68 | static setMutedCAll(uuid, muted) { 69 | if (Platform.OS !== 'ios') return; 70 | _RNCallKit.setMutedCall(uuid, muted); 71 | } 72 | 73 | static checkIfBusy() { 74 | return Platform.OS === 'ios' 75 | ? _RNCallKit.checkIfBusy() 76 | : Promise.reject('RNCallKit.checkIfBusy was called from unsupported OS'); 77 | }; 78 | 79 | static checkSpeaker() { 80 | return Platform.OS === 'ios' 81 | ? _RNCallKit.checkSpeaker() 82 | : Promise.reject('RNCallKit.checkSpeaker was called from unsupported OS'); 83 | } 84 | 85 | /* 86 | static setHeldCall(uuid, onHold) { 87 | if (Platform.OS !== 'ios') return; 88 | _RNCallKit.setHeldCall(uuid, onHold); 89 | } 90 | */ 91 | } 92 | -------------------------------------------------------------------------------- /ios/RNCallKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 23DBCD131E13B465003D485F /* RNCallKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 23DBCD121E13B465003D485F /* RNCallKit.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 234528901E0B88C700D1A033 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 234528921E0B88C700D1A033 /* libRNCallKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCallKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 23DBCD111E13B465003D485F /* RNCallKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCallKit.h; path = RNCallKit/RNCallKit.h; sourceTree = SOURCE_ROOT; }; 28 | 23DBCD121E13B465003D485F /* RNCallKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCallKit.m; path = RNCallKit/RNCallKit.m; sourceTree = SOURCE_ROOT; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 2345288F1E0B88C700D1A033 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 234528891E0B88C700D1A033 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 234528941E0B88C700D1A033 /* RNCallKit */, 46 | 234528931E0B88C700D1A033 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 234528931E0B88C700D1A033 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 234528921E0B88C700D1A033 /* libRNCallKit.a */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 234528941E0B88C700D1A033 /* RNCallKit */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 23DBCD111E13B465003D485F /* RNCallKit.h */, 62 | 23DBCD121E13B465003D485F /* RNCallKit.m */, 63 | ); 64 | name = RNCallKit; 65 | path = RNCallKit; 66 | sourceTree = ""; 67 | }; 68 | /* End PBXGroup section */ 69 | 70 | /* Begin PBXNativeTarget section */ 71 | 234528911E0B88C700D1A033 /* RNCallKit */ = { 72 | isa = PBXNativeTarget; 73 | buildConfigurationList = 2345289B1E0B88C700D1A033 /* Build configuration list for PBXNativeTarget "RNCallKit" */; 74 | buildPhases = ( 75 | 2345288E1E0B88C700D1A033 /* Sources */, 76 | 2345288F1E0B88C700D1A033 /* Frameworks */, 77 | 234528901E0B88C700D1A033 /* CopyFiles */, 78 | ); 79 | buildRules = ( 80 | ); 81 | dependencies = ( 82 | ); 83 | name = RNCallKit; 84 | productName = RNCallKit; 85 | productReference = 234528921E0B88C700D1A033 /* libRNCallKit.a */; 86 | productType = "com.apple.product-type.library.static"; 87 | }; 88 | /* End PBXNativeTarget section */ 89 | 90 | /* Begin PBXProject section */ 91 | 2345288A1E0B88C700D1A033 /* Project object */ = { 92 | isa = PBXProject; 93 | attributes = { 94 | LastUpgradeCheck = 0810; 95 | ORGANIZATIONNAME = "Ian Yu-Hsun Lin"; 96 | TargetAttributes = { 97 | 234528911E0B88C700D1A033 = { 98 | CreatedOnToolsVersion = 8.1; 99 | ProvisioningStyle = Automatic; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = 2345288D1E0B88C700D1A033 /* Build configuration list for PBXProject "RNCallKit" */; 104 | compatibilityVersion = "Xcode 3.2"; 105 | developmentRegion = English; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | ); 110 | mainGroup = 234528891E0B88C700D1A033; 111 | productRefGroup = 234528931E0B88C700D1A033 /* Products */; 112 | projectDirPath = ""; 113 | projectRoot = ""; 114 | targets = ( 115 | 234528911E0B88C700D1A033 /* RNCallKit */, 116 | ); 117 | }; 118 | /* End PBXProject section */ 119 | 120 | /* Begin PBXSourcesBuildPhase section */ 121 | 2345288E1E0B88C700D1A033 /* Sources */ = { 122 | isa = PBXSourcesBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | 23DBCD131E13B465003D485F /* RNCallKit.m in Sources */, 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | /* End PBXSourcesBuildPhase section */ 130 | 131 | /* Begin XCBuildConfiguration section */ 132 | 234528991E0B88C700D1A033 /* Debug */ = { 133 | isa = XCBuildConfiguration; 134 | buildSettings = { 135 | ALWAYS_SEARCH_USER_PATHS = NO; 136 | CLANG_ANALYZER_NONNULL = YES; 137 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_WARN_BOOL_CONVERSION = YES; 142 | CLANG_WARN_CONSTANT_CONVERSION = YES; 143 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 144 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 145 | CLANG_WARN_EMPTY_BODY = YES; 146 | CLANG_WARN_ENUM_CONVERSION = YES; 147 | CLANG_WARN_INFINITE_RECURSION = YES; 148 | CLANG_WARN_INT_CONVERSION = YES; 149 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 150 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 151 | CLANG_WARN_UNREACHABLE_CODE = YES; 152 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 153 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 154 | COPY_PHASE_STRIP = NO; 155 | DEBUG_INFORMATION_FORMAT = dwarf; 156 | ENABLE_STRICT_OBJC_MSGSEND = YES; 157 | ENABLE_TESTABILITY = YES; 158 | GCC_C_LANGUAGE_STANDARD = gnu99; 159 | GCC_DYNAMIC_NO_PIC = NO; 160 | GCC_NO_COMMON_BLOCKS = YES; 161 | GCC_OPTIMIZATION_LEVEL = 0; 162 | GCC_PREPROCESSOR_DEFINITIONS = ( 163 | "DEBUG=1", 164 | "$(inherited)", 165 | ); 166 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 167 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 168 | GCC_WARN_UNDECLARED_SELECTOR = YES; 169 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 170 | GCC_WARN_UNUSED_FUNCTION = YES; 171 | GCC_WARN_UNUSED_VARIABLE = YES; 172 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 173 | MTL_ENABLE_DEBUG_INFO = YES; 174 | ONLY_ACTIVE_ARCH = YES; 175 | SDKROOT = iphoneos; 176 | }; 177 | name = Debug; 178 | }; 179 | 2345289A1E0B88C700D1A033 /* Release */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_ANALYZER_NONNULL = YES; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_WARN_BOOL_CONVERSION = YES; 189 | CLANG_WARN_CONSTANT_CONVERSION = YES; 190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 191 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 192 | CLANG_WARN_EMPTY_BODY = YES; 193 | CLANG_WARN_ENUM_CONVERSION = YES; 194 | CLANG_WARN_INFINITE_RECURSION = YES; 195 | CLANG_WARN_INT_CONVERSION = YES; 196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 197 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 198 | CLANG_WARN_UNREACHABLE_CODE = YES; 199 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 200 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 203 | ENABLE_NS_ASSERTIONS = NO; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu99; 206 | GCC_NO_COMMON_BLOCKS = YES; 207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 211 | GCC_WARN_UNUSED_FUNCTION = YES; 212 | GCC_WARN_UNUSED_VARIABLE = YES; 213 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 214 | MTL_ENABLE_DEBUG_INFO = NO; 215 | SDKROOT = iphoneos; 216 | VALIDATE_PRODUCT = YES; 217 | }; 218 | name = Release; 219 | }; 220 | 2345289C1E0B88C700D1A033 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 224 | OTHER_LDFLAGS = "-ObjC"; 225 | PRODUCT_NAME = "$(TARGET_NAME)"; 226 | SKIP_INSTALL = YES; 227 | }; 228 | name = Debug; 229 | }; 230 | 2345289D1E0B88C700D1A033 /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 234 | OTHER_LDFLAGS = "-ObjC"; 235 | PRODUCT_NAME = "$(TARGET_NAME)"; 236 | SKIP_INSTALL = YES; 237 | }; 238 | name = Release; 239 | }; 240 | /* End XCBuildConfiguration section */ 241 | 242 | /* Begin XCConfigurationList section */ 243 | 2345288D1E0B88C700D1A033 /* Build configuration list for PBXProject "RNCallKit" */ = { 244 | isa = XCConfigurationList; 245 | buildConfigurations = ( 246 | 234528991E0B88C700D1A033 /* Debug */, 247 | 2345289A1E0B88C700D1A033 /* Release */, 248 | ); 249 | defaultConfigurationIsVisible = 0; 250 | defaultConfigurationName = Release; 251 | }; 252 | 2345289B1E0B88C700D1A033 /* Build configuration list for PBXNativeTarget "RNCallKit" */ = { 253 | isa = XCConfigurationList; 254 | buildConfigurations = ( 255 | 2345289C1E0B88C700D1A033 /* Debug */, 256 | 2345289D1E0B88C700D1A033 /* Release */, 257 | ); 258 | defaultConfigurationIsVisible = 0; 259 | defaultConfigurationName = Release; 260 | }; 261 | /* End XCConfigurationList section */ 262 | }; 263 | rootObject = 2345288A1E0B88C700D1A033 /* Project object */; 264 | } 265 | -------------------------------------------------------------------------------- /ios/RNCallKit/RNCallKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNCallKit.h 3 | // RNCallKit 4 | // 5 | // Created by Ian Yu-Hsun Lin on 12/22/16. 6 | // Copyright © 2016 Ian Yu-Hsun Lin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | //#import 14 | 15 | #import 16 | 17 | @interface RNCallKit : RCTEventEmitter 18 | 19 | @property (nonatomic, strong) CXCallController *callKitCallController; 20 | @property (nonatomic, strong) CXProvider *callKitProvider; 21 | 22 | + (BOOL)application:(UIApplication *)application 23 | openURL:(NSURL *)url 24 | options:(NSDictionary *)options NS_AVAILABLE_IOS(9_0); 25 | 26 | + (BOOL)application:(UIApplication *)application 27 | continueUserActivity:(NSUserActivity *)userActivity 28 | restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/RNCallKit/RNCallKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNCallKit.m 3 | // RNCallKit 4 | // 5 | // Created by Ian Yu-Hsun Lin on 12/22/16. 6 | // Copyright © 2016 Ian Yu-Hsun Lin. All rights reserved. 7 | // 8 | 9 | #import "RNCallKit.h" 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | #import 17 | 18 | static int const DelayInSeconds = 3; 19 | 20 | static NSString *const RNCallKitHandleStartCallNotification = @"RNCallKitHandleStartCallNotification"; 21 | static NSString *const RNCallKitDidReceiveStartCallAction = @"RNCallKitDidReceiveStartCallAction"; 22 | static NSString *const RNCallKitPerformAnswerCallAction = @"RNCallKitPerformAnswerCallAction"; 23 | static NSString *const RNCallKitPerformEndCallAction = @"RNCallKitPerformEndCallAction"; 24 | static NSString *const RNCallKitDidActivateAudioSession = @"RNCallKitDidActivateAudioSession"; 25 | static NSString *const RNCallKitDidDisplayIncomingCall = @"RNCallKitDidDisplayIncomingCall"; 26 | static NSString *const RNCallKitDidPerformSetMutedCallAction = @"RNCallKitDidPerformSetMutedCallAction"; 27 | 28 | @implementation RNCallKit 29 | { 30 | NSMutableDictionary *_settings; 31 | NSOperatingSystemVersion _version; 32 | BOOL _isStartCallActionEventListenerAdded; 33 | } 34 | 35 | // should initialise in AppDelegate.m 36 | RCT_EXPORT_MODULE() 37 | 38 | - (instancetype)init 39 | { 40 | #ifdef DEBUG 41 | NSLog(@"[RNCallKit][init]"); 42 | #endif 43 | if (self = [super init]) { 44 | [[NSNotificationCenter defaultCenter] addObserver:self 45 | selector:@selector(handleStartCallNotification:) 46 | name:RNCallKitHandleStartCallNotification 47 | object:nil]; 48 | _isStartCallActionEventListenerAdded = NO; 49 | } 50 | return self; 51 | } 52 | 53 | - (void)dealloc 54 | { 55 | #ifdef DEBUG 56 | NSLog(@"[RNCallKit][dealloc]"); 57 | #endif 58 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 59 | } 60 | 61 | // Override method of RCTEventEmitter 62 | - (NSArray *)supportedEvents 63 | { 64 | return @[ 65 | RNCallKitDidReceiveStartCallAction, 66 | RNCallKitPerformAnswerCallAction, 67 | RNCallKitPerformEndCallAction, 68 | RNCallKitDidActivateAudioSession, 69 | RNCallKitDidDisplayIncomingCall, 70 | RNCallKitDidPerformSetMutedCallAction 71 | ]; 72 | } 73 | 74 | RCT_EXPORT_METHOD(setup:(NSDictionary *)options) 75 | { 76 | #ifdef DEBUG 77 | NSLog(@"[RNCallKit][setup] options = %@", options); 78 | #endif 79 | _version = [[[NSProcessInfo alloc] init] operatingSystemVersion]; 80 | self.callKitCallController = [[CXCallController alloc] init]; 81 | _settings = [[NSMutableDictionary alloc] initWithDictionary:options]; 82 | self.callKitProvider = [[CXProvider alloc] initWithConfiguration:[self getProviderConfiguration]]; 83 | [self.callKitProvider setDelegate:self queue:nil]; 84 | } 85 | 86 | RCT_REMAP_METHOD(checkIfBusy, 87 | checkIfBusyWithResolver:(RCTPromiseResolveBlock)resolve 88 | rejecter:(RCTPromiseRejectBlock)reject) 89 | { 90 | #ifdef DEBUG 91 | NSLog(@"[RNCallKit][checkIfBusy]"); 92 | #endif 93 | resolve(@(self.callKitCallController.callObserver.calls.count > 0)); 94 | } 95 | 96 | RCT_REMAP_METHOD(checkSpeaker, 97 | checkSpeakerResolver:(RCTPromiseResolveBlock)resolve 98 | rejecter:(RCTPromiseRejectBlock)reject) 99 | { 100 | #ifdef DEBUG 101 | NSLog(@"[RNCallKit][checkSpeaker]"); 102 | #endif 103 | NSString *output = [AVAudioSession sharedInstance].currentRoute.outputs.count > 0 ? [AVAudioSession sharedInstance].currentRoute.outputs[0].portType : nil; 104 | resolve(@([output isEqualToString:@"Speaker"])); 105 | } 106 | 107 | #pragma mark - CXCallController call actions 108 | 109 | // Display the incoming call to the user 110 | RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString 111 | handle:(NSString *)handle 112 | handleType:(NSString *)handleType 113 | hasVideo:(BOOL)hasVideo 114 | localizedCallerName:(NSString * _Nullable)localizedCallerName) 115 | { 116 | #ifdef DEBUG 117 | NSLog(@"[RNCallKit][displayIncomingCall] uuidString = %@", uuidString); 118 | #endif 119 | int _handleType = [self getHandleType:handleType]; 120 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 121 | CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; 122 | callUpdate.remoteHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; 123 | callUpdate.supportsDTMF = YES; 124 | // TODO: Holding 125 | callUpdate.supportsHolding = NO; 126 | callUpdate.supportsGrouping = NO; 127 | callUpdate.supportsUngrouping = NO; 128 | callUpdate.hasVideo = hasVideo; 129 | callUpdate.localizedCallerName = localizedCallerName; 130 | 131 | [self.callKitProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) { 132 | [self sendEventWithName:RNCallKitDidDisplayIncomingCall body:@{ @"error": error ? error.localizedDescription : @"" }]; 133 | if (error == nil) { 134 | // Workaround per https://forums.developer.apple.com/message/169511 135 | if ([self lessThanIos10_2]) { 136 | [self configureAudioSession]; 137 | } 138 | } 139 | }]; 140 | } 141 | 142 | RCT_EXPORT_METHOD(startCall:(NSString *)uuidString 143 | handle:(NSString *)handle 144 | handleType:(NSString *)handleType 145 | video:(BOOL)video 146 | contactIdentifier:(NSString * _Nullable)contactIdentifier) 147 | { 148 | #ifdef DEBUG 149 | NSLog(@"[RNCallKit][startCall] uuidString = %@", uuidString); 150 | #endif 151 | int _handleType = [self getHandleType:handleType]; 152 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 153 | CXHandle *callHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; 154 | CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle]; 155 | [startCallAction setVideo:video]; 156 | [startCallAction setContactIdentifier:contactIdentifier]; 157 | 158 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction]; 159 | 160 | [self requestTransaction:transaction]; 161 | } 162 | 163 | RCT_EXPORT_METHOD(endCall:(NSString *)uuidString) 164 | { 165 | #ifdef DEBUG 166 | NSLog(@"[RNCallKit][endCall] uuidString = %@", uuidString); 167 | #endif 168 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 169 | CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid]; 170 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; 171 | 172 | [self requestTransaction:transaction]; 173 | } 174 | 175 | RCT_EXPORT_METHOD(endAllCalls) 176 | { 177 | #ifdef DEBUG 178 | NSLog(@"[RNCallKit][endAllCalls] calls = %@", self.callKitCallController.callObserver.calls); 179 | #endif 180 | for (CXCall *call in self.callKitCallController.callObserver.calls) { 181 | CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:call.UUID]; 182 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; 183 | [self requestTransaction:transaction]; 184 | } 185 | } 186 | 187 | RCT_EXPORT_METHOD(setHeldCall:(NSString *)uuidString onHold:(BOOL)onHold) 188 | { 189 | #ifdef DEBUG 190 | NSLog(@"[RNCallKit][setHeldCall] uuidString = %@", uuidString); 191 | #endif 192 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 193 | CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:uuid onHold:onHold]; 194 | CXTransaction *transaction = [[CXTransaction alloc] init]; 195 | [transaction addAction:setHeldCallAction]; 196 | 197 | [self requestTransaction:transaction]; 198 | } 199 | 200 | RCT_EXPORT_METHOD(_startCallActionEventListenerAdded) 201 | { 202 | _isStartCallActionEventListenerAdded = YES; 203 | } 204 | 205 | RCT_EXPORT_METHOD(reportConnectedOutgoingCallWithUUID:(NSString *)uuidString) 206 | { 207 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 208 | [self.callKitProvider reportOutgoingCallWithUUID:uuid connectedAtDate:[NSDate date]]; 209 | } 210 | 211 | RCT_EXPORT_METHOD(setMutedCall:(NSString *)uuidString muted:(BOOL)muted) 212 | { 213 | #ifdef DEBUG 214 | NSLog(@"[RNCallKit][setMutedCall] muted = %i", muted); 215 | #endif 216 | NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 217 | CXSetMutedCallAction *setMutedAction = [[CXSetMutedCallAction alloc] initWithCallUUID:uuid muted:muted]; 218 | CXTransaction *transaction = [[CXTransaction alloc] init]; 219 | [transaction addAction:setMutedAction]; 220 | 221 | [self requestTransaction:transaction]; 222 | } 223 | 224 | - (void)requestTransaction:(CXTransaction *)transaction 225 | { 226 | #ifdef DEBUG 227 | NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction); 228 | #endif 229 | if (self.callKitCallController == nil) { 230 | self.callKitCallController = [[CXCallController alloc] init]; 231 | } 232 | [self.callKitCallController requestTransaction:transaction completion:^(NSError * _Nullable error) { 233 | if (error != nil) { 234 | NSLog(@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error); 235 | } else { 236 | NSLog(@"[RNCallKit][requestTransaction] Requested transaction successfully"); 237 | 238 | // CXStartCallAction 239 | if ([[transaction.actions firstObject] isKindOfClass:[CXStartCallAction class]]) { 240 | CXStartCallAction *startCallAction = [transaction.actions firstObject]; 241 | CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; 242 | callUpdate.remoteHandle = startCallAction.handle; 243 | callUpdate.supportsDTMF = YES; 244 | callUpdate.supportsHolding = NO; 245 | callUpdate.supportsGrouping = NO; 246 | callUpdate.supportsUngrouping = NO; 247 | callUpdate.hasVideo = NO; 248 | [self.callKitProvider reportCallWithUUID:startCallAction.callUUID updated:callUpdate]; 249 | } 250 | } 251 | }]; 252 | } 253 | 254 | - (BOOL)lessThanIos10_2 255 | { 256 | if (_version.majorVersion < 10) { 257 | return YES; 258 | } else if (_version.majorVersion > 10) { 259 | return NO; 260 | } else { 261 | return _version.minorVersion < 2; 262 | } 263 | } 264 | 265 | - (BOOL)containsLowerCaseLetter:(NSString *)callUUID 266 | { 267 | NSRegularExpression* regex = [[NSRegularExpression alloc] initWithPattern:@"[a-z]" options:0 error:nil]; 268 | return [regex numberOfMatchesInString:callUUID options:0 range:NSMakeRange(0, [callUUID length])] > 0; 269 | } 270 | 271 | - (int)getHandleType:(NSString *)handleType 272 | { 273 | int _handleType; 274 | if ([handleType isEqualToString:@"generic"]) { 275 | _handleType = CXHandleTypeGeneric; 276 | } else if ([handleType isEqualToString:@"number"]) { 277 | _handleType = CXHandleTypePhoneNumber; 278 | } else if ([handleType isEqualToString:@"email"]) { 279 | _handleType = CXHandleTypeEmailAddress; 280 | } else { 281 | _handleType = CXHandleTypeGeneric; 282 | } 283 | return _handleType; 284 | } 285 | 286 | - (CXProviderConfiguration *)getProviderConfiguration 287 | { 288 | #ifdef DEBUG 289 | NSLog(@"[RNCallKit][getProviderConfiguration]"); 290 | #endif 291 | CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:_settings[@"appName"]]; 292 | providerConfiguration.supportsVideo = YES; 293 | providerConfiguration.maximumCallGroups = 1; 294 | providerConfiguration.maximumCallsPerCallGroup = 1; 295 | providerConfiguration.supportedHandleTypes = [NSSet setWithObjects:[NSNumber numberWithInteger:CXHandleTypePhoneNumber], [NSNumber numberWithInteger:CXHandleTypeEmailAddress], [NSNumber numberWithInteger:CXHandleTypeGeneric], nil]; 296 | if (_settings[@"imageName"]) { 297 | providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:_settings[@"imageName"]]); 298 | } 299 | if (_settings[@"ringtoneSound"]) { 300 | providerConfiguration.ringtoneSound = _settings[@"ringtoneSound"]; 301 | } 302 | return providerConfiguration; 303 | } 304 | 305 | - (void)configureAudioSession 306 | { 307 | #ifdef DEBUG 308 | NSLog(@"[RNCallKit][configureAudioSession] Activating audio session"); 309 | #endif 310 | 311 | AVAudioSession* audioSession = [AVAudioSession sharedInstance]; 312 | [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; 313 | 314 | [audioSession setMode:AVAudioSessionModeVoiceChat error:nil]; 315 | 316 | double sampleRate = 44100.0; 317 | [audioSession setPreferredSampleRate:sampleRate error:nil]; 318 | 319 | NSTimeInterval bufferDuration = .005; 320 | [audioSession setPreferredIOBufferDuration:bufferDuration error:nil]; 321 | [audioSession setActive:TRUE error:nil]; 322 | } 323 | 324 | + (BOOL)application:(UIApplication *)application 325 | openURL:(NSURL *)url 326 | options:(NSDictionary *)options NS_AVAILABLE_IOS(9_0) 327 | { 328 | #ifdef DEBUG 329 | NSLog(@"[RNCallKit][application:openURL]"); 330 | #endif 331 | /* 332 | NSString *handle = [url startCallHandle]; 333 | if (handle != nil && handle.length > 0 ){ 334 | NSDictionary *userInfo = @{ 335 | @"handle": handle, 336 | @"video": @NO 337 | }; 338 | [[NSNotificationCenter defaultCenter] postNotificationName:RNCallKitHandleStartCallNotification 339 | object:self 340 | userInfo:userInfo]; 341 | return YES; 342 | } 343 | return NO; 344 | */ 345 | return YES; 346 | } 347 | 348 | + (BOOL)application:(UIApplication *)application 349 | continueUserActivity:(NSUserActivity *)userActivity 350 | restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler 351 | { 352 | #ifdef DEBUG 353 | NSLog(@"[RNCallKit][application:continueUserActivity]"); 354 | #endif 355 | INInteraction *interaction = userActivity.interaction; 356 | INPerson *contact; 357 | NSString *handle; 358 | BOOL isAudioCall = [userActivity.activityType isEqualToString:INStartAudioCallIntentIdentifier]; 359 | BOOL isVideoCall = [userActivity.activityType isEqualToString:INStartVideoCallIntentIdentifier]; 360 | 361 | if (isAudioCall) { 362 | INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent; 363 | contact = [startAudioCallIntent.contacts firstObject]; 364 | } else if (isVideoCall) { 365 | INStartVideoCallIntent *startVideoCallIntent = (INStartVideoCallIntent *)interaction.intent; 366 | contact = [startVideoCallIntent.contacts firstObject]; 367 | } 368 | 369 | if (contact != nil) { 370 | handle = contact.personHandle.value; 371 | } 372 | 373 | if (handle != nil && handle.length > 0 ){ 374 | NSDictionary *userInfo = @{ 375 | @"handle": handle, 376 | @"video": @(isVideoCall) 377 | }; 378 | 379 | [[NSNotificationCenter defaultCenter] postNotificationName:RNCallKitHandleStartCallNotification 380 | object:self 381 | userInfo:userInfo]; 382 | return YES; 383 | } 384 | return NO; 385 | } 386 | 387 | - (void)handleStartCallNotification:(NSNotification *)notification 388 | { 389 | #ifdef DEBUG 390 | NSLog(@"[RNCallKit][handleStartCallNotification] userInfo = %@", notification.userInfo); 391 | #endif 392 | int delayInSeconds; 393 | if (!_isStartCallActionEventListenerAdded) { 394 | // Workaround for when app is just launched and JS side hasn't registered to the event properly 395 | delayInSeconds = DelayInSeconds; 396 | } else { 397 | delayInSeconds = 0; 398 | } 399 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 400 | dispatch_after(popTime, dispatch_get_main_queue(), ^{ 401 | [self sendEventWithName:RNCallKitDidReceiveStartCallAction body:notification.userInfo]; 402 | }); 403 | } 404 | 405 | #pragma mark - CXProviderDelegate 406 | 407 | - (void)providerDidReset:(CXProvider *)provider{ 408 | #ifdef DEBUG 409 | NSLog(@"[RNCallKit][providerDidReset]"); 410 | #endif 411 | } 412 | 413 | // Starting outgoing call 414 | - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action 415 | { 416 | #ifdef DEBUG 417 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction]"); 418 | #endif 419 | [self.callKitProvider reportOutgoingCallWithUUID:action.callUUID startedConnectingAtDate:[NSDate date]]; 420 | [self configureAudioSession]; 421 | [action fulfill]; 422 | } 423 | 424 | // Answering incoming call 425 | - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action 426 | { 427 | #ifdef DEBUG 428 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction]"); 429 | #endif 430 | if (![self lessThanIos10_2]) { 431 | [self configureAudioSession]; 432 | } 433 | NSString *callUUID = [self containsLowerCaseLetter:action.callUUID.UUIDString] ? action.callUUID.UUIDString : [action.callUUID.UUIDString lowercaseString]; 434 | [self sendEventWithName:RNCallKitPerformAnswerCallAction body:@{ @"callUUID": callUUID }]; 435 | [action fulfill]; 436 | } 437 | 438 | // Ending incoming call 439 | - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action 440 | { 441 | #ifdef DEBUG 442 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction]"); 443 | #endif 444 | NSString *callUUID = [self containsLowerCaseLetter:action.callUUID.UUIDString] ? action.callUUID.UUIDString : [action.callUUID.UUIDString lowercaseString]; 445 | [self sendEventWithName:RNCallKitPerformEndCallAction body:@{ @"callUUID": callUUID }]; 446 | [action fulfill]; 447 | } 448 | 449 | - (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action 450 | { 451 | #ifdef DEBUG 452 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetHeldCallAction]"); 453 | #endif 454 | } 455 | 456 | - (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action 457 | { 458 | #ifdef DEBUG 459 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction]"); 460 | #endif 461 | } 462 | 463 | - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession 464 | { 465 | #ifdef DEBUG 466 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession]"); 467 | #endif 468 | [self sendEventWithName:RNCallKitDidActivateAudioSession body:nil]; 469 | } 470 | 471 | - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession 472 | { 473 | #ifdef DEBUG 474 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession]"); 475 | #endif 476 | } 477 | 478 | -(void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action 479 | { 480 | #ifdef DEBUG 481 | NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction]"); 482 | #endif 483 | [self sendEventWithName:RNCallKitDidPerformSetMutedCallAction body:@{ @"muted": @(action.muted) }]; 484 | [action fulfill]; 485 | } 486 | 487 | @end 488 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-callkit", 3 | "version": "1.3.4", 4 | "description": "iOS 10 CallKit Framework For React Native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ianlin/react-native-callkit.git" 12 | }, 13 | "author": "Ian Yu-Hsun Lin ", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/ianlin/react-native-callkit/issues" 17 | }, 18 | "homepage": "https://github.com/ianlin/react-native-callkit#readme", 19 | "peerDependencies": { 20 | "react-native": ">=0.19.0" 21 | }, 22 | "devDependencies": { 23 | "react-native": ">=0.19.0" 24 | } 25 | } 26 | --------------------------------------------------------------------------------