├── .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 | [](https://badge.fury.io/js/react-native-callkit)
10 | [](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 | [](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 |
--------------------------------------------------------------------------------