voice-react-native-sdk 1.6.2-fork.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +206 -0
- package/README.md +179 -0
- package/android/build.gradle +113 -0
- package/android/gradle.properties +29 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/java/com/twiliovoicereactnative/AudioSwitchManager.java +141 -0
- package/android/src/main/java/com/twiliovoicereactnative/CallListenerProxy.java +186 -0
- package/android/src/main/java/com/twiliovoicereactnative/CallMessageListenerProxy.java +103 -0
- package/android/src/main/java/com/twiliovoicereactnative/CallRecordDatabase.java +183 -0
- package/android/src/main/java/com/twiliovoicereactnative/CommonConstants.java +195 -0
- package/android/src/main/java/com/twiliovoicereactnative/ConfigurationProperties.java +41 -0
- package/android/src/main/java/com/twiliovoicereactnative/Constants.java +23 -0
- package/android/src/main/java/com/twiliovoicereactnative/ExpoActivityLifecycleListener.java +27 -0
- package/android/src/main/java/com/twiliovoicereactnative/ExpoApplicationLifecycleListener.java +14 -0
- package/android/src/main/java/com/twiliovoicereactnative/ExpoModule.kt +36 -0
- package/android/src/main/java/com/twiliovoicereactnative/ExpoPackage.java +20 -0
- package/android/src/main/java/com/twiliovoicereactnative/JSEventEmitter.java +98 -0
- package/android/src/main/java/com/twiliovoicereactnative/MediaPlayerManager.java +58 -0
- package/android/src/main/java/com/twiliovoicereactnative/NotificationUtility.java +363 -0
- package/android/src/main/java/com/twiliovoicereactnative/ReactNativeArgumentsSerializer.java +276 -0
- package/android/src/main/java/com/twiliovoicereactnative/SDKLog.java +74 -0
- package/android/src/main/java/com/twiliovoicereactnative/StatsListenerProxy.java +178 -0
- package/android/src/main/java/com/twiliovoicereactnative/TwilioVoiceReactNativeModule.java +778 -0
- package/android/src/main/java/com/twiliovoicereactnative/TwilioVoiceReactNativePackage.java +28 -0
- package/android/src/main/java/com/twiliovoicereactnative/VoiceActivityProxy.java +101 -0
- package/android/src/main/java/com/twiliovoicereactnative/VoiceApplicationProxy.java +133 -0
- package/android/src/main/java/com/twiliovoicereactnative/VoiceFirebaseMessagingService.java +88 -0
- package/android/src/main/java/com/twiliovoicereactnative/VoiceService.java +403 -0
- package/android/src/main/res/drawable/answered_call_small_icon.png +0 -0
- package/android/src/main/res/drawable/ic_launcher_round.png +0 -0
- package/android/src/main/res/drawable/ic_launcher_sdk.png +0 -0
- package/android/src/main/res/drawable/incoming_call_small_icon.png +0 -0
- package/android/src/main/res/drawable/outgoing_call_small_icon.png +0 -0
- package/android/src/main/res/raw/disconnect.wav +0 -0
- package/android/src/main/res/raw/incoming.wav +0 -0
- package/android/src/main/res/raw/outgoing.wav +0 -0
- package/android/src/main/res/raw/ringtone.wav +0 -0
- package/android/src/main/res/raw/silent.wav +0 -0
- package/android/src/main/res/values/colors.xml +11 -0
- package/android/src/main/res/values/config.xml +5 -0
- package/android/src/main/res/values/dimens.xml +7 -0
- package/android/src/main/res/values/strings.xml +25 -0
- package/android/src/main/res/values/styles.xml +10 -0
- package/android/src/main/res/values-night/colors.xml +11 -0
- package/expo-config-plugin/android.js +191 -0
- package/expo-config-plugin/ios.js +22 -0
- package/expo-module.config.json +6 -0
- package/ios/TwilioVoicePushRegistry.h +19 -0
- package/ios/TwilioVoicePushRegistry.m +72 -0
- package/ios/TwilioVoiceReactNative+CallInvite.m +56 -0
- package/ios/TwilioVoiceReactNative+CallKit.m +533 -0
- package/ios/TwilioVoiceReactNative+CallMessage.m +81 -0
- package/ios/TwilioVoiceReactNative.h +74 -0
- package/ios/TwilioVoiceReactNative.m +1046 -0
- package/ios/TwilioVoiceReactNative.xcodeproj/project.pbxproj +328 -0
- package/ios/TwilioVoiceReactNativeConstants.h +200 -0
- package/ios/Utilities/TwilioVoiceStatsReport.h +175 -0
- package/lib/commonjs/AudioDevice.js +101 -0
- package/lib/commonjs/AudioDevice.js.map +1 -0
- package/lib/commonjs/Call.js +765 -0
- package/lib/commonjs/Call.js.map +1 -0
- package/lib/commonjs/CallInvite.js +452 -0
- package/lib/commonjs/CallInvite.js.map +1 -0
- package/lib/commonjs/CallMessage/CallMessage.js +54 -0
- package/lib/commonjs/CallMessage/CallMessage.js.map +1 -0
- package/lib/commonjs/CallMessage/IncomingCallMessage.js +120 -0
- package/lib/commonjs/CallMessage/IncomingCallMessage.js.map +1 -0
- package/lib/commonjs/CallMessage/OutgoingCallMessage.js +148 -0
- package/lib/commonjs/CallMessage/OutgoingCallMessage.js.map +1 -0
- package/lib/commonjs/ExpoModule.js +69 -0
- package/lib/commonjs/ExpoModule.js.map +1 -0
- package/lib/commonjs/Voice.js +611 -0
- package/lib/commonjs/Voice.js.map +1 -0
- package/lib/commonjs/common.js +26 -0
- package/lib/commonjs/common.js.map +1 -0
- package/lib/commonjs/constants.js +158 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/error/InvalidArgumentError.js +32 -0
- package/lib/commonjs/error/InvalidArgumentError.js.map +1 -0
- package/lib/commonjs/error/InvalidStateError.js +33 -0
- package/lib/commonjs/error/InvalidStateError.js.map +1 -0
- package/lib/commonjs/error/TwilioError.js +38 -0
- package/lib/commonjs/error/TwilioError.js.map +1 -0
- package/lib/commonjs/error/UnsupportedPlatformError.js +33 -0
- package/lib/commonjs/error/UnsupportedPlatformError.js.map +1 -0
- package/lib/commonjs/error/generated.js +2343 -0
- package/lib/commonjs/error/generated.js.map +1 -0
- package/lib/commonjs/error/index.js +112 -0
- package/lib/commonjs/error/index.js.map +1 -0
- package/lib/commonjs/error/utility.js +37 -0
- package/lib/commonjs/error/utility.js.map +1 -0
- package/lib/commonjs/index.js +95 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/type/AudioDevice.js +6 -0
- package/lib/commonjs/type/AudioDevice.js.map +1 -0
- package/lib/commonjs/type/Call.js +6 -0
- package/lib/commonjs/type/Call.js.map +1 -0
- package/lib/commonjs/type/CallInvite.js +6 -0
- package/lib/commonjs/type/CallInvite.js.map +1 -0
- package/lib/commonjs/type/CallKit.js +30 -0
- package/lib/commonjs/type/CallKit.js.map +1 -0
- package/lib/commonjs/type/CallMessage.js +6 -0
- package/lib/commonjs/type/CallMessage.js.map +1 -0
- package/lib/commonjs/type/Error.js +6 -0
- package/lib/commonjs/type/Error.js.map +1 -0
- package/lib/commonjs/type/NativeModule.js +6 -0
- package/lib/commonjs/type/NativeModule.js.map +1 -0
- package/lib/commonjs/type/RTCStats.js +29 -0
- package/lib/commonjs/type/RTCStats.js.map +1 -0
- package/lib/commonjs/type/Voice.js +6 -0
- package/lib/commonjs/type/Voice.js.map +1 -0
- package/lib/commonjs/type/common.js +2 -0
- package/lib/commonjs/type/common.js.map +1 -0
- package/lib/module/AudioDevice.js +97 -0
- package/lib/module/AudioDevice.js.map +1 -0
- package/lib/module/Call.js +766 -0
- package/lib/module/Call.js.map +1 -0
- package/lib/module/CallInvite.js +450 -0
- package/lib/module/CallInvite.js.map +1 -0
- package/lib/module/CallMessage/CallMessage.js +51 -0
- package/lib/module/CallMessage/CallMessage.js.map +1 -0
- package/lib/module/CallMessage/IncomingCallMessage.js +116 -0
- package/lib/module/CallMessage/IncomingCallMessage.js.map +1 -0
- package/lib/module/CallMessage/OutgoingCallMessage.js +154 -0
- package/lib/module/CallMessage/OutgoingCallMessage.js.map +1 -0
- package/lib/module/ExpoModule.js +58 -0
- package/lib/module/ExpoModule.js.map +1 -0
- package/lib/module/Voice.js +598 -0
- package/lib/module/Voice.js.map +1 -0
- package/lib/module/common.js +11 -0
- package/lib/module/common.js.map +1 -0
- package/lib/module/constants.js +151 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/error/InvalidArgumentError.js +23 -0
- package/lib/module/error/InvalidArgumentError.js.map +1 -0
- package/lib/module/error/InvalidStateError.js +24 -0
- package/lib/module/error/InvalidStateError.js.map +1 -0
- package/lib/module/error/TwilioError.js +29 -0
- package/lib/module/error/TwilioError.js.map +1 -0
- package/lib/module/error/UnsupportedPlatformError.js +24 -0
- package/lib/module/error/UnsupportedPlatformError.js.map +1 -0
- package/lib/module/error/generated.js +2315 -0
- package/lib/module/error/generated.js.map +1 -0
- package/lib/module/error/index.js +6 -0
- package/lib/module/error/index.js.map +1 -0
- package/lib/module/error/utility.js +27 -0
- package/lib/module/error/utility.js.map +1 -0
- package/lib/module/index.js +23 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/type/AudioDevice.js +2 -0
- package/lib/module/type/AudioDevice.js.map +1 -0
- package/lib/module/type/Call.js +2 -0
- package/lib/module/type/Call.js.map +1 -0
- package/lib/module/type/CallInvite.js +2 -0
- package/lib/module/type/CallInvite.js.map +1 -0
- package/lib/module/type/CallKit.js +22 -0
- package/lib/module/type/CallKit.js.map +1 -0
- package/lib/module/type/CallMessage.js +2 -0
- package/lib/module/type/CallMessage.js.map +1 -0
- package/lib/module/type/Error.js +2 -0
- package/lib/module/type/Error.js.map +1 -0
- package/lib/module/type/NativeModule.js +2 -0
- package/lib/module/type/NativeModule.js.map +1 -0
- package/lib/module/type/RTCStats.js +21 -0
- package/lib/module/type/RTCStats.js.map +1 -0
- package/lib/module/type/Voice.js +2 -0
- package/lib/module/type/Voice.js.map +1 -0
- package/lib/module/type/common.js +2 -0
- package/lib/module/type/common.js.map +1 -0
- package/lib/typescript/AudioDevice.d.ts +74 -0
- package/lib/typescript/Call.d.ts +874 -0
- package/lib/typescript/CallInvite.d.ts +484 -0
- package/lib/typescript/CallMessage/CallMessage.d.ts +53 -0
- package/lib/typescript/CallMessage/IncomingCallMessage.d.ts +75 -0
- package/lib/typescript/CallMessage/OutgoingCallMessage.d.ts +198 -0
- package/lib/typescript/ExpoModule.d.ts +13 -0
- package/lib/typescript/Voice.d.ts +620 -0
- package/lib/typescript/common.d.ts +11 -0
- package/lib/typescript/constants.d.ts +148 -0
- package/lib/typescript/error/InvalidArgumentError.d.ts +11 -0
- package/lib/typescript/error/InvalidStateError.d.ts +12 -0
- package/lib/typescript/error/TwilioError.d.ts +14 -0
- package/lib/typescript/error/UnsupportedPlatformError.d.ts +12 -0
- package/lib/typescript/error/generated.d.ts +1558 -0
- package/lib/typescript/error/index.d.ts +5 -0
- package/lib/typescript/error/utility.d.ts +12 -0
- package/lib/typescript/index.d.ts +18 -0
- package/lib/typescript/type/AudioDevice.d.ts +15 -0
- package/lib/typescript/type/Call.d.ts +59 -0
- package/lib/typescript/type/CallInvite.d.ts +40 -0
- package/lib/typescript/type/CallKit.d.ts +60 -0
- package/lib/typescript/type/CallMessage.d.ts +20 -0
- package/lib/typescript/type/Error.d.ts +9 -0
- package/lib/typescript/type/NativeModule.d.ts +59 -0
- package/lib/typescript/type/RTCStats.d.ts +91 -0
- package/lib/typescript/type/Voice.d.ts +16 -0
- package/lib/typescript/type/common.d.ts +15 -0
- package/package.json +167 -0
- package/src/AudioDevice.tsx +88 -0
- package/src/Call.tsx +1343 -0
- package/src/CallInvite.tsx +757 -0
- package/src/CallMessage/CallMessage.ts +83 -0
- package/src/CallMessage/IncomingCallMessage.ts +104 -0
- package/src/CallMessage/OutgoingCallMessage.ts +308 -0
- package/src/ExpoModule.ts +59 -0
- package/src/Voice.tsx +1010 -0
- package/src/common.ts +16 -0
- package/src/constants.ts +193 -0
- package/src/error/InvalidArgumentError.ts +19 -0
- package/src/error/InvalidStateError.ts +19 -0
- package/src/error/TwilioError.ts +22 -0
- package/src/error/UnsupportedPlatformError.ts +19 -0
- package/src/error/generated.ts +2277 -0
- package/src/error/index.ts +18 -0
- package/src/error/utility.ts +36 -0
- package/src/index.tsx +24 -0
- package/src/type/AudioDevice.ts +18 -0
- package/src/type/Call.ts +102 -0
- package/src/type/CallInvite.ts +59 -0
- package/src/type/CallKit.ts +61 -0
- package/src/type/CallMessage.ts +31 -0
- package/src/type/Error.ts +11 -0
- package/src/type/NativeModule.ts +93 -0
- package/src/type/RTCStats.ts +99 -0
- package/src/type/Voice.ts +31 -0
- package/src/type/common.ts +16 -0
- package/twilio-voice-react-native.podspec +22 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TwilioVoiceReactNative+CallKit.m
|
|
3
|
+
// TwilioVoiceReactNative
|
|
4
|
+
//
|
|
5
|
+
// Copyright © 2022 Twilio, Inc. All rights reserved.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
@import CallKit;
|
|
9
|
+
@import TwilioVoice;
|
|
10
|
+
|
|
11
|
+
#import "TwilioVoiceReactNative.h"
|
|
12
|
+
#import "TwilioVoiceReactNativeConstants.h"
|
|
13
|
+
|
|
14
|
+
NSString * const kDefaultCallKitConfigurationName = @"Twilio Voice React Native";
|
|
15
|
+
|
|
16
|
+
@interface TwilioVoiceReactNative (CallKit) <CXProviderDelegate, TVOCallDelegate, AVAudioPlayerDelegate>
|
|
17
|
+
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
@implementation TwilioVoiceReactNative (CallKit)
|
|
21
|
+
|
|
22
|
+
#pragma mark - CallKit helper methods
|
|
23
|
+
|
|
24
|
+
- (void)initializeCallKit {
|
|
25
|
+
[self initializeCallKitWithConfiguration:nil];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (void)initializeCallKitWithConfiguration:(NSDictionary *)configuration {
|
|
29
|
+
CXProviderConfiguration *callKitConfiguration = [CXProviderConfiguration new];
|
|
30
|
+
|
|
31
|
+
if (configuration[kTwilioVoiceReactNativeCallKitMaximumCallGroups]) {
|
|
32
|
+
callKitConfiguration.maximumCallGroups = [configuration[kTwilioVoiceReactNativeCallKitMaximumCallGroups] intValue];
|
|
33
|
+
} else {
|
|
34
|
+
callKitConfiguration.maximumCallGroups = 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (configuration[kTwilioVoiceReactNativeCallKitMaximumCallsPerCallGroup]) {
|
|
38
|
+
callKitConfiguration.maximumCallsPerCallGroup = [configuration[kTwilioVoiceReactNativeCallKitMaximumCallsPerCallGroup] intValue];
|
|
39
|
+
} else {
|
|
40
|
+
callKitConfiguration.maximumCallsPerCallGroup = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
float version = [[UIDevice currentDevice].systemVersion floatValue];
|
|
44
|
+
if (version > 11.0 && configuration[kTwilioVoiceReactNativeCallKitIncludesCallsInRecents]) {
|
|
45
|
+
callKitConfiguration.includesCallsInRecents = [configuration[kTwilioVoiceReactNativeCallKitIncludesCallsInRecents] boolValue];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (configuration[kTwilioVoiceReactNativeCallKitSupportedHandleTypes]) {
|
|
49
|
+
NSSet *supportedHandleTypes = [NSSet setWithArray:configuration[kTwilioVoiceReactNativeCallKitSupportedHandleTypes]];
|
|
50
|
+
callKitConfiguration.supportedHandleTypes = supportedHandleTypes;
|
|
51
|
+
} else {
|
|
52
|
+
callKitConfiguration.supportedHandleTypes = [NSSet setWithArray:@[@(CXHandleTypeGeneric), @(CXHandleTypePhoneNumber)]];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (configuration[kTwilioVoiceReactNativeCallKitIconTemplateImageData] && [configuration[kTwilioVoiceReactNativeCallKitIconTemplateImageData] isKindOfClass:[NSString class]]) {
|
|
56
|
+
UIImage *icon = [UIImage imageNamed:configuration[kTwilioVoiceReactNativeCallKitIconTemplateImageData]];
|
|
57
|
+
callKitConfiguration.iconTemplateImageData = UIImagePNGRepresentation(icon);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (configuration[kTwilioVoiceReactNativeCallKitRingtoneSound] && [configuration[kTwilioVoiceReactNativeCallKitRingtoneSound] isKindOfClass:[NSString class]]) {
|
|
61
|
+
callKitConfiguration.ringtoneSound = configuration[kTwilioVoiceReactNativeCallKitRingtoneSound];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
self.callKitProvider = [[CXProvider alloc] initWithConfiguration:callKitConfiguration];
|
|
65
|
+
[self.callKitProvider setDelegate:self queue:nil];
|
|
66
|
+
self.callKitCallController = [CXCallController new];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (NSString *)getDisplayName:(NSString *)template
|
|
70
|
+
customParameters:(NSDictionary<NSString *, NSString *> *)customParameters {
|
|
71
|
+
NSString *processedTemplate = template;
|
|
72
|
+
for (NSString *paramKey in customParameters) {
|
|
73
|
+
NSString *paramValue = customParameters[paramKey];
|
|
74
|
+
NSString *wrappedParamKey = [NSString stringWithFormat:@"${%@}", paramKey];
|
|
75
|
+
processedTemplate = [processedTemplate stringByReplacingOccurrencesOfString:wrappedParamKey withString:paramValue];
|
|
76
|
+
}
|
|
77
|
+
return processedTemplate;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
- (void)reportNewIncomingCall:(TVOCallInvite *)callInvite {
|
|
81
|
+
NSString *handleName = callInvite.from;
|
|
82
|
+
if (handleName == nil) {
|
|
83
|
+
handleName = @"Unknown Caller";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
|
87
|
+
NSString *preferenceKey = @"incomingCallContactHandleTemplate";
|
|
88
|
+
if ([preferences objectForKey:preferenceKey] != nil) {
|
|
89
|
+
const NSString *preferenceVal = [preferences stringForKey:preferenceKey];
|
|
90
|
+
if ([preferenceVal length] > 0) {
|
|
91
|
+
handleName = [self getDisplayName:preferenceVal customParameters:[callInvite customParameters]];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handleName];
|
|
96
|
+
|
|
97
|
+
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
|
98
|
+
callUpdate.remoteHandle = callHandle;
|
|
99
|
+
callUpdate.localizedCallerName = handleName;
|
|
100
|
+
callUpdate.supportsDTMF = YES;
|
|
101
|
+
callUpdate.supportsHolding = YES;
|
|
102
|
+
callUpdate.supportsGrouping = NO;
|
|
103
|
+
callUpdate.supportsUngrouping = NO;
|
|
104
|
+
callUpdate.hasVideo = NO;
|
|
105
|
+
|
|
106
|
+
[self.callKitProvider reportNewIncomingCallWithUUID:callInvite.uuid update:callUpdate completion:^(NSError *error) {
|
|
107
|
+
if (!error) {
|
|
108
|
+
NSLog(@"Incoming call successfully reported.");
|
|
109
|
+
} else {
|
|
110
|
+
NSLog(@"Failed to report incoming call: %@.", error);
|
|
111
|
+
}
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
- (void)answerCallInvite:(NSUUID *)uuid
|
|
116
|
+
completion:(void(^)(BOOL success))completionHandler {
|
|
117
|
+
self.callKitCompletionCallback = completionHandler;
|
|
118
|
+
CXAnswerCallAction *answerCallAction = [[CXAnswerCallAction alloc] initWithCallUUID:uuid];
|
|
119
|
+
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:answerCallAction];
|
|
120
|
+
|
|
121
|
+
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
|
|
122
|
+
if (error) {
|
|
123
|
+
NSLog(@"Failed to submit answer-call transaction request: %@", error);
|
|
124
|
+
} else {
|
|
125
|
+
NSLog(@"Answer-call transaction successfully done");
|
|
126
|
+
}
|
|
127
|
+
}];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
- (void)endCallWithUuid:(NSUUID *)uuid {
|
|
131
|
+
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid];
|
|
132
|
+
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];
|
|
133
|
+
|
|
134
|
+
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
|
|
135
|
+
if (error) {
|
|
136
|
+
NSLog(@"Failed to submit end-call transaction request: %@", error);
|
|
137
|
+
} else {
|
|
138
|
+
NSLog(@"End-call transaction successfully done");
|
|
139
|
+
}
|
|
140
|
+
}];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (void)makeCallWithAccessToken:(NSString *)accessToken
|
|
144
|
+
params:(NSDictionary *)params
|
|
145
|
+
contactHandle:(NSString *)contactHandle {
|
|
146
|
+
self.accessToken = accessToken;
|
|
147
|
+
self.twimlParams = params;
|
|
148
|
+
|
|
149
|
+
NSString *handle = @"Default Contact";
|
|
150
|
+
if ([contactHandle length] > 0) {
|
|
151
|
+
handle = contactHandle;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
|
155
|
+
NSUUID *uuid = [NSUUID UUID];
|
|
156
|
+
CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
|
|
157
|
+
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction];
|
|
158
|
+
|
|
159
|
+
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
|
|
160
|
+
if (error) {
|
|
161
|
+
NSLog(@"StartCallAction transaction request failed: %@", [error localizedDescription]);
|
|
162
|
+
} else {
|
|
163
|
+
NSLog(@"StartCallAction transaction request successful");
|
|
164
|
+
|
|
165
|
+
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
|
166
|
+
|
|
167
|
+
callUpdate.remoteHandle = callHandle;
|
|
168
|
+
callUpdate.supportsDTMF = YES;
|
|
169
|
+
callUpdate.supportsHolding = YES;
|
|
170
|
+
callUpdate.supportsGrouping = NO;
|
|
171
|
+
callUpdate.supportsUngrouping = NO;
|
|
172
|
+
callUpdate.hasVideo = NO;
|
|
173
|
+
|
|
174
|
+
[self.callKitProvider reportCallWithUUID:uuid updated:callUpdate];
|
|
175
|
+
}
|
|
176
|
+
}];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (void)performVoiceCallWithUUID:(NSUUID *)uuid
|
|
180
|
+
client:(NSString *)client
|
|
181
|
+
completion:(void(^)(BOOL success))completionHandler {
|
|
182
|
+
TVOConnectOptions *connectOptions = [TVOConnectOptions optionsWithAccessToken:self.accessToken block:^(TVOConnectOptionsBuilder *builder) {
|
|
183
|
+
builder.params = self.twimlParams;
|
|
184
|
+
builder.uuid = uuid;
|
|
185
|
+
builder.callMessageDelegate = self;
|
|
186
|
+
}];
|
|
187
|
+
TVOCall *call = [TwilioVoiceSDK connectWithOptions:connectOptions delegate:self];
|
|
188
|
+
if (call) {
|
|
189
|
+
self.callMap[call.uuid.UUIDString] = call;
|
|
190
|
+
self.callPromiseResolver([self callInfo:call]);
|
|
191
|
+
}
|
|
192
|
+
self.callKitCompletionCallback = completionHandler;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
- (void)performAnswerVoiceCallWithUUID:(NSUUID *)uuid
|
|
196
|
+
completion:(void(^)(BOOL success))completionHandler {
|
|
197
|
+
NSAssert(self.callInviteMap[uuid.UUIDString], @"No call invite");
|
|
198
|
+
|
|
199
|
+
TVOCallInvite *callInvite = self.callInviteMap[uuid.UUIDString];
|
|
200
|
+
TVOAcceptOptions *acceptOptions = [TVOAcceptOptions optionsWithCallInvite:callInvite block:^(TVOAcceptOptionsBuilder *builder) {
|
|
201
|
+
builder.uuid = uuid;
|
|
202
|
+
builder.callMessageDelegate = self;
|
|
203
|
+
}];
|
|
204
|
+
|
|
205
|
+
TVOCall *call = [callInvite acceptWithOptions:acceptOptions delegate:self];
|
|
206
|
+
|
|
207
|
+
if (!call) {
|
|
208
|
+
completionHandler(NO);
|
|
209
|
+
} else {
|
|
210
|
+
self.callMap[call.uuid.UUIDString] = call;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCallInvite
|
|
214
|
+
body:@{
|
|
215
|
+
kTwilioVoiceReactNativeCallInviteEventKeyType: kTwilioVoiceReactNativeCallInviteEventTypeValueAccepted,
|
|
216
|
+
kTwilioVoiceReactNativeCallInviteEventKeyCallSid: callInvite.callSid,
|
|
217
|
+
kTwilioVoiceReactNativeEventKeyCallInvite: [self callInviteInfo:callInvite]}];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
- (void)updateCall:(NSString *)uuid callerHandle:(NSString *)handle {
|
|
221
|
+
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
|
222
|
+
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
|
223
|
+
callUpdate.remoteHandle = callHandle;
|
|
224
|
+
callUpdate.localizedCallerName = handle;
|
|
225
|
+
callUpdate.supportsDTMF = YES;
|
|
226
|
+
callUpdate.supportsHolding = YES;
|
|
227
|
+
callUpdate.supportsGrouping = NO;
|
|
228
|
+
callUpdate.supportsUngrouping = NO;
|
|
229
|
+
callUpdate.hasVideo = NO;
|
|
230
|
+
|
|
231
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
232
|
+
[self.callKitProvider reportCallWithUUID:[[NSUUID alloc] initWithUUIDString:uuid] updated:callUpdate];
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#pragma mark - CXProviderDelegate
|
|
237
|
+
|
|
238
|
+
- (void)providerDidReset:(CXProvider *)provider {
|
|
239
|
+
[TwilioVoiceReactNative twilioAudioDevice].enabled = NO;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
- (void)providerDidBegin:(CXProvider *)provider {
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession {
|
|
247
|
+
[TwilioVoiceReactNative twilioAudioDevice].enabled = YES;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession {
|
|
251
|
+
[TwilioVoiceReactNative twilioAudioDevice].enabled = NO;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
|
|
255
|
+
if (self.callMap[action.callUUID.UUIDString]) {
|
|
256
|
+
TVOCall *call = self.callMap[action.callUUID.UUIDString];
|
|
257
|
+
[call disconnect];
|
|
258
|
+
} else if (self.callInviteMap[action.callUUID.UUIDString]) {
|
|
259
|
+
TVOCallInvite *callInvite = self.callInviteMap[action.callUUID.UUIDString];
|
|
260
|
+
[callInvite reject];
|
|
261
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCallInvite
|
|
262
|
+
body:@{
|
|
263
|
+
kTwilioVoiceReactNativeCallInviteEventKeyType: kTwilioVoiceReactNativeCallInviteEventTypeValueRejected,
|
|
264
|
+
kTwilioVoiceReactNativeCallInviteEventKeyCallSid: callInvite.callSid,
|
|
265
|
+
kTwilioVoiceReactNativeEventKeyCallInvite: [self callInviteInfo:callInvite]}];
|
|
266
|
+
[self.callInviteMap removeObjectForKey:action.callUUID.UUIDString];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
[action fulfill];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
|
|
273
|
+
[TwilioVoiceReactNative twilioAudioDevice].enabled = NO;
|
|
274
|
+
[TwilioVoiceReactNative twilioAudioDevice].block();
|
|
275
|
+
|
|
276
|
+
[self.callKitProvider reportOutgoingCallWithUUID:action.callUUID startedConnectingAtDate:[NSDate date]];
|
|
277
|
+
|
|
278
|
+
__weak typeof(self) weakSelf = self;
|
|
279
|
+
[self performVoiceCallWithUUID:action.callUUID client:nil completion:^(BOOL success) {
|
|
280
|
+
__strong typeof(self) strongSelf = weakSelf;
|
|
281
|
+
if (success) {
|
|
282
|
+
NSLog(@"performVoiceCallWithUUID successful");
|
|
283
|
+
[strongSelf.callKitProvider reportOutgoingCallWithUUID:action.callUUID connectedAtDate:[NSDate date]];
|
|
284
|
+
} else {
|
|
285
|
+
NSLog(@"performVoiceCallWithUUID failed");
|
|
286
|
+
}
|
|
287
|
+
}];
|
|
288
|
+
|
|
289
|
+
[action fulfill];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
|
|
293
|
+
[TwilioVoiceReactNative twilioAudioDevice].enabled = NO;
|
|
294
|
+
[TwilioVoiceReactNative twilioAudioDevice].block();
|
|
295
|
+
|
|
296
|
+
[self performAnswerVoiceCallWithUUID:action.callUUID completion:^(BOOL success) {
|
|
297
|
+
if (success) {
|
|
298
|
+
NSLog(@"performAnswerVoiceCallWithUUID successful");
|
|
299
|
+
} else {
|
|
300
|
+
NSLog(@"performAnswerVoiceCallWithUUID failed");
|
|
301
|
+
}
|
|
302
|
+
}];
|
|
303
|
+
|
|
304
|
+
[action fulfill];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
- (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action {
|
|
308
|
+
if (self.callMap[action.callUUID.UUIDString]) {
|
|
309
|
+
TVOCall *call = self.callMap[action.callUUID.UUIDString];
|
|
310
|
+
[call setOnHold:action.isOnHold];
|
|
311
|
+
[action fulfill];
|
|
312
|
+
} else {
|
|
313
|
+
[action fail];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
|
|
318
|
+
if (self.callMap[action.callUUID.UUIDString]) {
|
|
319
|
+
TVOCall *call = self.callMap[action.callUUID.UUIDString];
|
|
320
|
+
[call setMuted:action.isMuted];
|
|
321
|
+
[action fulfill];
|
|
322
|
+
} else {
|
|
323
|
+
[action fail];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
- (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action {
|
|
328
|
+
if (self.callMap[action.callUUID.UUIDString]) {
|
|
329
|
+
TVOCall *call = self.callMap[action.callUUID.UUIDString];
|
|
330
|
+
[call sendDigits:action.digits];
|
|
331
|
+
[action fulfill];
|
|
332
|
+
} else {
|
|
333
|
+
[action fail];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#pragma mark - TVOCallDelegate
|
|
338
|
+
|
|
339
|
+
- (void)callDidStartRinging:(TVOCall *)call {
|
|
340
|
+
[self playRingback];
|
|
341
|
+
|
|
342
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
343
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventRinging,
|
|
344
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call]}];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
- (void)callDidConnect:(TVOCall *)call {
|
|
348
|
+
self.callConnectMap[call.uuid.UUIDString] = [self getSimplifiedISO8601FormattedTimestamp:[NSDate date]];
|
|
349
|
+
|
|
350
|
+
[self stopRingback];
|
|
351
|
+
|
|
352
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
353
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventConnected,
|
|
354
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call]}];
|
|
355
|
+
|
|
356
|
+
if (self.callKitCompletionCallback) {
|
|
357
|
+
self.callKitCompletionCallback(YES);
|
|
358
|
+
self.callKitCompletionCallback = nil;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
- (void)call:(TVOCall *)call didDisconnectWithError:(NSError *)error {
|
|
363
|
+
NSDictionary *messageBody = [NSDictionary dictionary];
|
|
364
|
+
if (error) {
|
|
365
|
+
messageBody = @{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventDisconnected,
|
|
366
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call],
|
|
367
|
+
kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
|
|
368
|
+
kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}};
|
|
369
|
+
} else {
|
|
370
|
+
messageBody = @{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventDisconnected,
|
|
371
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call]};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall body:messageBody];
|
|
375
|
+
|
|
376
|
+
if (!self.userInitiatedDisconnect) {
|
|
377
|
+
CXCallEndedReason reason = CXCallEndedReasonRemoteEnded;
|
|
378
|
+
if (error) {
|
|
379
|
+
reason = CXCallEndedReasonFailed;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
[self.callKitProvider reportCallWithUUID:call.uuid endedAtDate:[NSDate date] reason:reason];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
[self callDisconnected:call];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
- (void)call:(TVOCall *)call didFailToConnectWithError:(NSError *)error {
|
|
389
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
390
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventConnectFailure,
|
|
391
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call],
|
|
392
|
+
kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
|
|
393
|
+
kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}}];
|
|
394
|
+
|
|
395
|
+
if (self.callKitCompletionCallback) {
|
|
396
|
+
self.callKitCompletionCallback(NO);
|
|
397
|
+
self.callKitCompletionCallback = nil;
|
|
398
|
+
}
|
|
399
|
+
[self.callKitProvider reportCallWithUUID:call.uuid endedAtDate:[NSDate date] reason:CXCallEndedReasonFailed];
|
|
400
|
+
|
|
401
|
+
[self callDisconnected:call];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
- (void)callDisconnected:(TVOCall *)call {
|
|
405
|
+
for (NSString *uuidKey in [self.callMap allKeys]) {
|
|
406
|
+
TVOCall *activeCall = self.callMap[uuidKey];
|
|
407
|
+
if (activeCall == call) {
|
|
408
|
+
[self.callMap removeObjectForKey:uuidKey];
|
|
409
|
+
[self.callConnectMap removeObjectForKey:call.uuid.UUIDString];
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Remove the corresponding call invite only when the incoming call is finished.
|
|
415
|
+
[self.callInviteMap removeObjectForKey:call.uuid.UUIDString];
|
|
416
|
+
|
|
417
|
+
[self stopRingback];
|
|
418
|
+
self.userInitiatedDisconnect = NO;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
- (void)call:(TVOCall *)call isReconnectingWithError:(NSError *)error {
|
|
422
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
423
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventReconnecting,
|
|
424
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call],
|
|
425
|
+
kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
|
|
426
|
+
kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}}];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
- (void)callDidReconnect:(TVOCall *)call {
|
|
430
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
431
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventReconnected,
|
|
432
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call]}];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
- (void)call:(TVOCall *)call
|
|
436
|
+
didReceiveQualityWarnings:(NSSet<NSNumber *> *)currentWarnings
|
|
437
|
+
previousWarnings:(NSSet<NSNumber *> *)previousWarnings {
|
|
438
|
+
NSMutableArray<NSString *> *currentWarningEvents = [NSMutableArray array];
|
|
439
|
+
for (NSNumber *warning in currentWarnings) {
|
|
440
|
+
NSString *event = [self warningNameWithNumber:warning];
|
|
441
|
+
[currentWarningEvents addObject:event];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
NSMutableArray<NSString *> *previousWarningEvents = [NSMutableArray array];
|
|
445
|
+
for (NSNumber *warning in previousWarnings) {
|
|
446
|
+
NSString *event = [self warningNameWithNumber:warning];
|
|
447
|
+
[previousWarningEvents addObject:event];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
451
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventQualityWarningsChanged,
|
|
452
|
+
kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call],
|
|
453
|
+
kTwilioVoiceReactNativeCallEventCurrentWarnings: currentWarningEvents,
|
|
454
|
+
kTwilioVoiceReactNativeCallEventPreviousWarnings: previousWarningEvents}];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
#pragma mark - Ringback
|
|
458
|
+
|
|
459
|
+
- (void)playRingback {
|
|
460
|
+
NSString *ringtonePath = [[NSBundle mainBundle] pathForResource:@"ringtone" ofType:@"wav"];
|
|
461
|
+
if ([ringtonePath length] <= 0) {
|
|
462
|
+
NSLog(@"Can't find sound file");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
NSError *error;
|
|
467
|
+
self.ringbackPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:ringtonePath] error:&error];
|
|
468
|
+
if (error != nil) {
|
|
469
|
+
NSLog(@"Failed to initialize audio player: %@", error);
|
|
470
|
+
} else {
|
|
471
|
+
self.ringbackPlayer.delegate = self;
|
|
472
|
+
self.ringbackPlayer.numberOfLoops = -1;
|
|
473
|
+
|
|
474
|
+
self.ringbackPlayer.volume = 1.0f;
|
|
475
|
+
[self.ringbackPlayer play];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
- (void)stopRingback {
|
|
480
|
+
if (!self.ringbackPlayer.isPlaying) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
[self.ringbackPlayer stop];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
|
|
488
|
+
if (flag) {
|
|
489
|
+
NSLog(@"Audio player finished playing successfully");
|
|
490
|
+
} else {
|
|
491
|
+
NSLog(@"Audio player finished playing with some error");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
|
|
496
|
+
NSLog(@"Decode error occurred: %@", error);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
#pragma mark - Warning event conversion
|
|
500
|
+
|
|
501
|
+
- (NSString *)warningNameWithNumber:(NSNumber *)warning {
|
|
502
|
+
if ([warning intValue] < 0 || [warning intValue] > 4) {
|
|
503
|
+
NSLog(@"Warning number out of TVOCallQualityWarning range");
|
|
504
|
+
return @"undefined";
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
TVOCallQualityWarning warningValue = [warning intValue];
|
|
508
|
+
switch (warningValue) {
|
|
509
|
+
case TVOCallQualityWarningHighRtt:
|
|
510
|
+
return @"high-rtt";
|
|
511
|
+
case TVOCallQualityWarningHighJitter:
|
|
512
|
+
return @"high-jitter";
|
|
513
|
+
case TVOCallQualityWarningHighPacketsLostFraction:
|
|
514
|
+
return @"high-packets-lost-fraction";
|
|
515
|
+
case TVOCallQualityWarningLowMos:
|
|
516
|
+
return @"low-mos";
|
|
517
|
+
case TVOCallQualityWarningConstantAudioInputLevel:
|
|
518
|
+
return @"constant-audio-input-level";
|
|
519
|
+
default:
|
|
520
|
+
return @"undefined";
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
- (NSString *)getSimplifiedISO8601FormattedTimestamp:(NSDate *)date {
|
|
525
|
+
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
526
|
+
NSLocale *locale = [NSLocale currentLocale];
|
|
527
|
+
[formatter setLocale:locale];
|
|
528
|
+
[formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZ"];
|
|
529
|
+
|
|
530
|
+
return [formatter stringFromDate:date];
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
@end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TwilioVoiceReactNative+CallMessage.m
|
|
3
|
+
// TwilioVoiceReactNative
|
|
4
|
+
//
|
|
5
|
+
// Copyright © 2024 Twilio, Inc. All rights reserved.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
@import TwilioVoice;
|
|
9
|
+
|
|
10
|
+
#import "TwilioVoiceReactNative.h"
|
|
11
|
+
#import "TwilioVoiceReactNativeConstants.h"
|
|
12
|
+
|
|
13
|
+
@interface TwilioVoiceReactNative (CallMessage) <TVOCallMessageDelegate>
|
|
14
|
+
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@implementation TwilioVoiceReactNative (CallMessage)
|
|
18
|
+
|
|
19
|
+
#pragma mark - TVOCallMessageDelegate (Call)
|
|
20
|
+
|
|
21
|
+
- (void)messageReceivedForCallSid:(NSString *)callSid message:(TVOCallMessage *)callMessage {
|
|
22
|
+
NSArray *keys = self.callMap.allKeys;
|
|
23
|
+
for (NSString *uuid in keys) {
|
|
24
|
+
TVOCall *call = self.callMap[uuid];
|
|
25
|
+
if ([call.sid isEqualToString:callSid]) {
|
|
26
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCall
|
|
27
|
+
body:@{kTwilioVoiceReactNativeEventKeyCall: [self callInfo:call],
|
|
28
|
+
kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventMessageReceived,
|
|
29
|
+
kTwilioVoiceReactNativeJSEventKeyCallMessageInfo: [self callMessageInfo:callMessage]}];
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
keys = self.callInviteMap.allKeys;
|
|
35
|
+
for (NSString *uuid in keys) {
|
|
36
|
+
TVOCallInvite *callInvite = self.callInviteMap[uuid];
|
|
37
|
+
if ([callInvite.callSid isEqualToString:callSid]) {
|
|
38
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCallInvite
|
|
39
|
+
body:@{kTwilioVoiceReactNativeCallInviteEventKeyCallSid: callSid,
|
|
40
|
+
kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventMessageReceived,
|
|
41
|
+
kTwilioVoiceReactNativeJSEventKeyCallMessageInfo: [self callMessageInfo:callMessage]}];
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
NSLog(@"No match call or call invite for %@", callSid);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
- (void)messageSentForCallSid:(NSString *)callSid voiceEventSid:(NSString *)voiceEventSid {
|
|
50
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCallMessage
|
|
51
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventMessageSent,
|
|
52
|
+
kTwilioVoiceReactNativeVoiceEventSid: voiceEventSid}];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
- (void)messageFailedForCallSid:(NSString *)callSid voiceEventSid:(NSString *)voiceEventSid error:(NSError *)error {
|
|
56
|
+
// NOTE(mhuynh): We need a delay here to prevent race conditions where some errors are synchronously handled
|
|
57
|
+
// by the C++ SDK. For those synchronously handled errors, the JS layer is not given enough time to construct
|
|
58
|
+
// and bind event listeners for this event.
|
|
59
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
60
|
+
[self sendEventWithName:kTwilioVoiceReactNativeScopeCallMessage
|
|
61
|
+
body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeCallEventMessageFailure,
|
|
62
|
+
kTwilioVoiceReactNativeVoiceEventSid: voiceEventSid,
|
|
63
|
+
kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
|
|
64
|
+
kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}}];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#pragma mark - Utility
|
|
69
|
+
|
|
70
|
+
- (NSDictionary *)callMessageInfo:(TVOCallMessage *)callMessage {
|
|
71
|
+
NSDictionary *callMessageInfoObject = @{
|
|
72
|
+
kTwilioVoiceReactNativeVoiceEventSid: callMessage.voiceEventSid,
|
|
73
|
+
kTwilioVoiceReactNativeCallMessageContent: callMessage.content,
|
|
74
|
+
kTwilioVoiceReactNativeCallMessageContentType: callMessage.contentType,
|
|
75
|
+
kTwilioVoiceReactNativeCallMessageMessageType: callMessage.messageType
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return callMessageInfoObject;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TwilioVoiceReactNative.h
|
|
3
|
+
// TwilioVoiceReactNative
|
|
4
|
+
//
|
|
5
|
+
// Copyright © 2022 Twilio, Inc. All rights reserved.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <AVFoundation/AVFoundation.h>
|
|
9
|
+
|
|
10
|
+
#import <React/RCTBridgeModule.h>
|
|
11
|
+
#import <React/RCTEventEmitter.h>
|
|
12
|
+
|
|
13
|
+
@class CXCallController;
|
|
14
|
+
@class CXProvider;
|
|
15
|
+
@class TVOCall;
|
|
16
|
+
@class TVOCallInvite;
|
|
17
|
+
@class TVOCancelledCallInvite;
|
|
18
|
+
@class TVODefaultAudioDevice;
|
|
19
|
+
|
|
20
|
+
FOUNDATION_EXPORT NSString * const kTwilioVoiceReactNativeEventKeyCall;
|
|
21
|
+
FOUNDATION_EXPORT NSString * const kTwilioVoiceReactNativeEventKeyCallInvite;
|
|
22
|
+
FOUNDATION_EXPORT NSString * const kTwilioVoiceReactNativeEventKeyCancelledCallInvite;
|
|
23
|
+
|
|
24
|
+
@interface TwilioVoiceReactNative : RCTEventEmitter <RCTBridgeModule>
|
|
25
|
+
|
|
26
|
+
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, TVOCall *> *callMap;
|
|
27
|
+
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, NSString *> *callConnectMap;
|
|
28
|
+
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, TVOCallInvite *> *callInviteMap;
|
|
29
|
+
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, TVOCancelledCallInvite *> *cancelledCallInviteMap;
|
|
30
|
+
|
|
31
|
+
@property (nonatomic, strong) CXProvider *callKitProvider;
|
|
32
|
+
@property (nonatomic, strong) CXCallController *callKitCallController;
|
|
33
|
+
|
|
34
|
+
@property (nonatomic, copy) NSString *accessToken;
|
|
35
|
+
@property (nonatomic, copy) NSDictionary *twimlParams;
|
|
36
|
+
@property (nonatomic, strong) void(^callKitCompletionCallback)(BOOL);
|
|
37
|
+
@property (nonatomic, strong) RCTPromiseResolveBlock callPromiseResolver;
|
|
38
|
+
|
|
39
|
+
// Indicates if the disconnect is triggered from app UI, instead of the system Call UI
|
|
40
|
+
@property (nonatomic, assign) BOOL userInitiatedDisconnect;
|
|
41
|
+
|
|
42
|
+
@property (nonatomic, strong) AVAudioPlayer *ringbackPlayer;
|
|
43
|
+
|
|
44
|
+
+ (TVODefaultAudioDevice *)twilioAudioDevice;
|
|
45
|
+
|
|
46
|
+
@end
|
|
47
|
+
|
|
48
|
+
@interface TwilioVoiceReactNative (EventEmitter)
|
|
49
|
+
|
|
50
|
+
// Override so we can check the event observer before emitting events
|
|
51
|
+
- (void)sendEventWithName:(NSString *)eventName body:(id)body;
|
|
52
|
+
|
|
53
|
+
@end
|
|
54
|
+
|
|
55
|
+
@interface TwilioVoiceReactNative (CallKit)
|
|
56
|
+
|
|
57
|
+
- (void)initializeCallKit;
|
|
58
|
+
- (void)initializeCallKitWithConfiguration:(NSDictionary *)configuration;
|
|
59
|
+
- (void)makeCallWithAccessToken:(NSString *)accessToken
|
|
60
|
+
params:(NSDictionary *)params
|
|
61
|
+
contactHandle:(NSString *)contactHandle;
|
|
62
|
+
- (void)reportNewIncomingCall:(TVOCallInvite *)callInvite;
|
|
63
|
+
- (void)endCallWithUuid:(NSUUID *)uuid;
|
|
64
|
+
/* Initiate the answering from the app UI */
|
|
65
|
+
- (void)answerCallInvite:(NSUUID *)uuid
|
|
66
|
+
completion:(void(^)(BOOL success))completionHandler;
|
|
67
|
+
- (void)updateCall:(NSString *)uuid callerHandle:(NSString *)handle;
|
|
68
|
+
|
|
69
|
+
/* Utility */
|
|
70
|
+
- (NSDictionary *)callInfo:(TVOCall *)call;
|
|
71
|
+
- (NSDictionary *)callInviteInfo:(TVOCallInvite *)callInvite;
|
|
72
|
+
- (NSDictionary *)cancelledCallInviteInfo:(TVOCancelledCallInvite *)cancelledCallInvite;
|
|
73
|
+
|
|
74
|
+
@end
|