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.
Files changed (227) hide show
  1. package/LICENSE +206 -0
  2. package/README.md +179 -0
  3. package/android/build.gradle +113 -0
  4. package/android/gradle.properties +29 -0
  5. package/android/src/main/AndroidManifest.xml +30 -0
  6. package/android/src/main/java/com/twiliovoicereactnative/AudioSwitchManager.java +141 -0
  7. package/android/src/main/java/com/twiliovoicereactnative/CallListenerProxy.java +186 -0
  8. package/android/src/main/java/com/twiliovoicereactnative/CallMessageListenerProxy.java +103 -0
  9. package/android/src/main/java/com/twiliovoicereactnative/CallRecordDatabase.java +183 -0
  10. package/android/src/main/java/com/twiliovoicereactnative/CommonConstants.java +195 -0
  11. package/android/src/main/java/com/twiliovoicereactnative/ConfigurationProperties.java +41 -0
  12. package/android/src/main/java/com/twiliovoicereactnative/Constants.java +23 -0
  13. package/android/src/main/java/com/twiliovoicereactnative/ExpoActivityLifecycleListener.java +27 -0
  14. package/android/src/main/java/com/twiliovoicereactnative/ExpoApplicationLifecycleListener.java +14 -0
  15. package/android/src/main/java/com/twiliovoicereactnative/ExpoModule.kt +36 -0
  16. package/android/src/main/java/com/twiliovoicereactnative/ExpoPackage.java +20 -0
  17. package/android/src/main/java/com/twiliovoicereactnative/JSEventEmitter.java +98 -0
  18. package/android/src/main/java/com/twiliovoicereactnative/MediaPlayerManager.java +58 -0
  19. package/android/src/main/java/com/twiliovoicereactnative/NotificationUtility.java +363 -0
  20. package/android/src/main/java/com/twiliovoicereactnative/ReactNativeArgumentsSerializer.java +276 -0
  21. package/android/src/main/java/com/twiliovoicereactnative/SDKLog.java +74 -0
  22. package/android/src/main/java/com/twiliovoicereactnative/StatsListenerProxy.java +178 -0
  23. package/android/src/main/java/com/twiliovoicereactnative/TwilioVoiceReactNativeModule.java +778 -0
  24. package/android/src/main/java/com/twiliovoicereactnative/TwilioVoiceReactNativePackage.java +28 -0
  25. package/android/src/main/java/com/twiliovoicereactnative/VoiceActivityProxy.java +101 -0
  26. package/android/src/main/java/com/twiliovoicereactnative/VoiceApplicationProxy.java +133 -0
  27. package/android/src/main/java/com/twiliovoicereactnative/VoiceFirebaseMessagingService.java +88 -0
  28. package/android/src/main/java/com/twiliovoicereactnative/VoiceService.java +403 -0
  29. package/android/src/main/res/drawable/answered_call_small_icon.png +0 -0
  30. package/android/src/main/res/drawable/ic_launcher_round.png +0 -0
  31. package/android/src/main/res/drawable/ic_launcher_sdk.png +0 -0
  32. package/android/src/main/res/drawable/incoming_call_small_icon.png +0 -0
  33. package/android/src/main/res/drawable/outgoing_call_small_icon.png +0 -0
  34. package/android/src/main/res/raw/disconnect.wav +0 -0
  35. package/android/src/main/res/raw/incoming.wav +0 -0
  36. package/android/src/main/res/raw/outgoing.wav +0 -0
  37. package/android/src/main/res/raw/ringtone.wav +0 -0
  38. package/android/src/main/res/raw/silent.wav +0 -0
  39. package/android/src/main/res/values/colors.xml +11 -0
  40. package/android/src/main/res/values/config.xml +5 -0
  41. package/android/src/main/res/values/dimens.xml +7 -0
  42. package/android/src/main/res/values/strings.xml +25 -0
  43. package/android/src/main/res/values/styles.xml +10 -0
  44. package/android/src/main/res/values-night/colors.xml +11 -0
  45. package/expo-config-plugin/android.js +191 -0
  46. package/expo-config-plugin/ios.js +22 -0
  47. package/expo-module.config.json +6 -0
  48. package/ios/TwilioVoicePushRegistry.h +19 -0
  49. package/ios/TwilioVoicePushRegistry.m +72 -0
  50. package/ios/TwilioVoiceReactNative+CallInvite.m +56 -0
  51. package/ios/TwilioVoiceReactNative+CallKit.m +533 -0
  52. package/ios/TwilioVoiceReactNative+CallMessage.m +81 -0
  53. package/ios/TwilioVoiceReactNative.h +74 -0
  54. package/ios/TwilioVoiceReactNative.m +1046 -0
  55. package/ios/TwilioVoiceReactNative.xcodeproj/project.pbxproj +328 -0
  56. package/ios/TwilioVoiceReactNativeConstants.h +200 -0
  57. package/ios/Utilities/TwilioVoiceStatsReport.h +175 -0
  58. package/lib/commonjs/AudioDevice.js +101 -0
  59. package/lib/commonjs/AudioDevice.js.map +1 -0
  60. package/lib/commonjs/Call.js +765 -0
  61. package/lib/commonjs/Call.js.map +1 -0
  62. package/lib/commonjs/CallInvite.js +452 -0
  63. package/lib/commonjs/CallInvite.js.map +1 -0
  64. package/lib/commonjs/CallMessage/CallMessage.js +54 -0
  65. package/lib/commonjs/CallMessage/CallMessage.js.map +1 -0
  66. package/lib/commonjs/CallMessage/IncomingCallMessage.js +120 -0
  67. package/lib/commonjs/CallMessage/IncomingCallMessage.js.map +1 -0
  68. package/lib/commonjs/CallMessage/OutgoingCallMessage.js +148 -0
  69. package/lib/commonjs/CallMessage/OutgoingCallMessage.js.map +1 -0
  70. package/lib/commonjs/ExpoModule.js +69 -0
  71. package/lib/commonjs/ExpoModule.js.map +1 -0
  72. package/lib/commonjs/Voice.js +611 -0
  73. package/lib/commonjs/Voice.js.map +1 -0
  74. package/lib/commonjs/common.js +26 -0
  75. package/lib/commonjs/common.js.map +1 -0
  76. package/lib/commonjs/constants.js +158 -0
  77. package/lib/commonjs/constants.js.map +1 -0
  78. package/lib/commonjs/error/InvalidArgumentError.js +32 -0
  79. package/lib/commonjs/error/InvalidArgumentError.js.map +1 -0
  80. package/lib/commonjs/error/InvalidStateError.js +33 -0
  81. package/lib/commonjs/error/InvalidStateError.js.map +1 -0
  82. package/lib/commonjs/error/TwilioError.js +38 -0
  83. package/lib/commonjs/error/TwilioError.js.map +1 -0
  84. package/lib/commonjs/error/UnsupportedPlatformError.js +33 -0
  85. package/lib/commonjs/error/UnsupportedPlatformError.js.map +1 -0
  86. package/lib/commonjs/error/generated.js +2343 -0
  87. package/lib/commonjs/error/generated.js.map +1 -0
  88. package/lib/commonjs/error/index.js +112 -0
  89. package/lib/commonjs/error/index.js.map +1 -0
  90. package/lib/commonjs/error/utility.js +37 -0
  91. package/lib/commonjs/error/utility.js.map +1 -0
  92. package/lib/commonjs/index.js +95 -0
  93. package/lib/commonjs/index.js.map +1 -0
  94. package/lib/commonjs/type/AudioDevice.js +6 -0
  95. package/lib/commonjs/type/AudioDevice.js.map +1 -0
  96. package/lib/commonjs/type/Call.js +6 -0
  97. package/lib/commonjs/type/Call.js.map +1 -0
  98. package/lib/commonjs/type/CallInvite.js +6 -0
  99. package/lib/commonjs/type/CallInvite.js.map +1 -0
  100. package/lib/commonjs/type/CallKit.js +30 -0
  101. package/lib/commonjs/type/CallKit.js.map +1 -0
  102. package/lib/commonjs/type/CallMessage.js +6 -0
  103. package/lib/commonjs/type/CallMessage.js.map +1 -0
  104. package/lib/commonjs/type/Error.js +6 -0
  105. package/lib/commonjs/type/Error.js.map +1 -0
  106. package/lib/commonjs/type/NativeModule.js +6 -0
  107. package/lib/commonjs/type/NativeModule.js.map +1 -0
  108. package/lib/commonjs/type/RTCStats.js +29 -0
  109. package/lib/commonjs/type/RTCStats.js.map +1 -0
  110. package/lib/commonjs/type/Voice.js +6 -0
  111. package/lib/commonjs/type/Voice.js.map +1 -0
  112. package/lib/commonjs/type/common.js +2 -0
  113. package/lib/commonjs/type/common.js.map +1 -0
  114. package/lib/module/AudioDevice.js +97 -0
  115. package/lib/module/AudioDevice.js.map +1 -0
  116. package/lib/module/Call.js +766 -0
  117. package/lib/module/Call.js.map +1 -0
  118. package/lib/module/CallInvite.js +450 -0
  119. package/lib/module/CallInvite.js.map +1 -0
  120. package/lib/module/CallMessage/CallMessage.js +51 -0
  121. package/lib/module/CallMessage/CallMessage.js.map +1 -0
  122. package/lib/module/CallMessage/IncomingCallMessage.js +116 -0
  123. package/lib/module/CallMessage/IncomingCallMessage.js.map +1 -0
  124. package/lib/module/CallMessage/OutgoingCallMessage.js +154 -0
  125. package/lib/module/CallMessage/OutgoingCallMessage.js.map +1 -0
  126. package/lib/module/ExpoModule.js +58 -0
  127. package/lib/module/ExpoModule.js.map +1 -0
  128. package/lib/module/Voice.js +598 -0
  129. package/lib/module/Voice.js.map +1 -0
  130. package/lib/module/common.js +11 -0
  131. package/lib/module/common.js.map +1 -0
  132. package/lib/module/constants.js +151 -0
  133. package/lib/module/constants.js.map +1 -0
  134. package/lib/module/error/InvalidArgumentError.js +23 -0
  135. package/lib/module/error/InvalidArgumentError.js.map +1 -0
  136. package/lib/module/error/InvalidStateError.js +24 -0
  137. package/lib/module/error/InvalidStateError.js.map +1 -0
  138. package/lib/module/error/TwilioError.js +29 -0
  139. package/lib/module/error/TwilioError.js.map +1 -0
  140. package/lib/module/error/UnsupportedPlatformError.js +24 -0
  141. package/lib/module/error/UnsupportedPlatformError.js.map +1 -0
  142. package/lib/module/error/generated.js +2315 -0
  143. package/lib/module/error/generated.js.map +1 -0
  144. package/lib/module/error/index.js +6 -0
  145. package/lib/module/error/index.js.map +1 -0
  146. package/lib/module/error/utility.js +27 -0
  147. package/lib/module/error/utility.js.map +1 -0
  148. package/lib/module/index.js +23 -0
  149. package/lib/module/index.js.map +1 -0
  150. package/lib/module/type/AudioDevice.js +2 -0
  151. package/lib/module/type/AudioDevice.js.map +1 -0
  152. package/lib/module/type/Call.js +2 -0
  153. package/lib/module/type/Call.js.map +1 -0
  154. package/lib/module/type/CallInvite.js +2 -0
  155. package/lib/module/type/CallInvite.js.map +1 -0
  156. package/lib/module/type/CallKit.js +22 -0
  157. package/lib/module/type/CallKit.js.map +1 -0
  158. package/lib/module/type/CallMessage.js +2 -0
  159. package/lib/module/type/CallMessage.js.map +1 -0
  160. package/lib/module/type/Error.js +2 -0
  161. package/lib/module/type/Error.js.map +1 -0
  162. package/lib/module/type/NativeModule.js +2 -0
  163. package/lib/module/type/NativeModule.js.map +1 -0
  164. package/lib/module/type/RTCStats.js +21 -0
  165. package/lib/module/type/RTCStats.js.map +1 -0
  166. package/lib/module/type/Voice.js +2 -0
  167. package/lib/module/type/Voice.js.map +1 -0
  168. package/lib/module/type/common.js +2 -0
  169. package/lib/module/type/common.js.map +1 -0
  170. package/lib/typescript/AudioDevice.d.ts +74 -0
  171. package/lib/typescript/Call.d.ts +874 -0
  172. package/lib/typescript/CallInvite.d.ts +484 -0
  173. package/lib/typescript/CallMessage/CallMessage.d.ts +53 -0
  174. package/lib/typescript/CallMessage/IncomingCallMessage.d.ts +75 -0
  175. package/lib/typescript/CallMessage/OutgoingCallMessage.d.ts +198 -0
  176. package/lib/typescript/ExpoModule.d.ts +13 -0
  177. package/lib/typescript/Voice.d.ts +620 -0
  178. package/lib/typescript/common.d.ts +11 -0
  179. package/lib/typescript/constants.d.ts +148 -0
  180. package/lib/typescript/error/InvalidArgumentError.d.ts +11 -0
  181. package/lib/typescript/error/InvalidStateError.d.ts +12 -0
  182. package/lib/typescript/error/TwilioError.d.ts +14 -0
  183. package/lib/typescript/error/UnsupportedPlatformError.d.ts +12 -0
  184. package/lib/typescript/error/generated.d.ts +1558 -0
  185. package/lib/typescript/error/index.d.ts +5 -0
  186. package/lib/typescript/error/utility.d.ts +12 -0
  187. package/lib/typescript/index.d.ts +18 -0
  188. package/lib/typescript/type/AudioDevice.d.ts +15 -0
  189. package/lib/typescript/type/Call.d.ts +59 -0
  190. package/lib/typescript/type/CallInvite.d.ts +40 -0
  191. package/lib/typescript/type/CallKit.d.ts +60 -0
  192. package/lib/typescript/type/CallMessage.d.ts +20 -0
  193. package/lib/typescript/type/Error.d.ts +9 -0
  194. package/lib/typescript/type/NativeModule.d.ts +59 -0
  195. package/lib/typescript/type/RTCStats.d.ts +91 -0
  196. package/lib/typescript/type/Voice.d.ts +16 -0
  197. package/lib/typescript/type/common.d.ts +15 -0
  198. package/package.json +167 -0
  199. package/src/AudioDevice.tsx +88 -0
  200. package/src/Call.tsx +1343 -0
  201. package/src/CallInvite.tsx +757 -0
  202. package/src/CallMessage/CallMessage.ts +83 -0
  203. package/src/CallMessage/IncomingCallMessage.ts +104 -0
  204. package/src/CallMessage/OutgoingCallMessage.ts +308 -0
  205. package/src/ExpoModule.ts +59 -0
  206. package/src/Voice.tsx +1010 -0
  207. package/src/common.ts +16 -0
  208. package/src/constants.ts +193 -0
  209. package/src/error/InvalidArgumentError.ts +19 -0
  210. package/src/error/InvalidStateError.ts +19 -0
  211. package/src/error/TwilioError.ts +22 -0
  212. package/src/error/UnsupportedPlatformError.ts +19 -0
  213. package/src/error/generated.ts +2277 -0
  214. package/src/error/index.ts +18 -0
  215. package/src/error/utility.ts +36 -0
  216. package/src/index.tsx +24 -0
  217. package/src/type/AudioDevice.ts +18 -0
  218. package/src/type/Call.ts +102 -0
  219. package/src/type/CallInvite.ts +59 -0
  220. package/src/type/CallKit.ts +61 -0
  221. package/src/type/CallMessage.ts +31 -0
  222. package/src/type/Error.ts +11 -0
  223. package/src/type/NativeModule.ts +93 -0
  224. package/src/type/RTCStats.ts +99 -0
  225. package/src/type/Voice.ts +31 -0
  226. package/src/type/common.ts +16 -0
  227. package/twilio-voice-react-native.podspec +22 -0
@@ -0,0 +1,1046 @@
1
+ //
2
+ // TwilioVoiceReactNative.m
3
+ // TwilioVoiceReactNative
4
+ //
5
+ // Copyright © 2022 Twilio, Inc. All rights reserved.
6
+ //
7
+
8
+ @import AVKit;
9
+
10
+ #import "TwilioVoicePushRegistry.h"
11
+ #import "TwilioVoiceReactNative.h"
12
+ #import "TwilioVoiceReactNativeConstants.h"
13
+ #import "TwilioVoiceStatsReport.h"
14
+
15
+ NSString * const kTwilioVoiceReactNativeVoiceError = @"Voice error";
16
+ dispatch_time_t const kPushRegistryDeviceTokenRetryTimeout = 3;
17
+ dispatch_time_t const kExponentialBackoff = 2;
18
+
19
+ // Call & call invite
20
+ NSString * const kTwilioVoiceReactNativeEventKeyCall = @"call";
21
+ NSString * const kTwilioVoiceReactNativeEventKeyCallInvite = @"callInvite";
22
+ NSString * const kTwilioVoiceReactNativeEventKeyCancelledCallInvite = @"cancelledCallInvite";
23
+
24
+ // Audio device
25
+ NSString * const kTwilioVoiceAudioDeviceUid = @"uid";
26
+
27
+ static TVODefaultAudioDevice *sTwilioAudioDevice;
28
+
29
+ @import TwilioVoice;
30
+
31
+ #pragma mark - Custom AVRoutePickerView Implementation
32
+
33
+ @interface TVRNAVRoutePickerView : AVRoutePickerView
34
+
35
+ - (void)present;
36
+
37
+ @end
38
+
39
+ @implementation TVRNAVRoutePickerView
40
+
41
+ - (instancetype)initWithFrame:(CGRect)frame {
42
+ if (self = [super initWithFrame:frame]) {
43
+ self.hidden = YES;
44
+ }
45
+
46
+ return self;
47
+ }
48
+
49
+ - (void)present {
50
+ UIButton *routePickerButton;
51
+ for (id view in self.subviews) {
52
+ if ([view isKindOfClass:[UIButton class]]) {
53
+ routePickerButton = (UIButton *)view;
54
+ [routePickerButton sendActionsForControlEvents:UIControlEventTouchUpInside];
55
+ break;
56
+ }
57
+ }
58
+ }
59
+
60
+ @end
61
+
62
+ #pragma mark - TwilioVoiceReactNative
63
+
64
+ @interface TwilioVoiceReactNative ()
65
+
66
+ @property (nonatomic, strong) TwilioVoicePushRegistry *twilioVoicePushRegistry;
67
+
68
+ @property(nonatomic, strong) NSData *deviceTokenData;
69
+ @property(nonatomic, strong) NSMutableDictionary *audioDevices;
70
+ @property(nonatomic, strong) NSDictionary *selectedAudioDevice;
71
+ @property(nonatomic, assign) BOOL registrationInProgress;
72
+
73
+ @end
74
+
75
+ @implementation TwilioVoiceReactNative {
76
+ BOOL _hasObserver;
77
+ }
78
+
79
+ - (instancetype)init {
80
+ if (self = [super init]) {
81
+ _callMap = [NSMutableDictionary dictionary];
82
+ _callConnectMap = [NSMutableDictionary dictionary];
83
+ _callInviteMap = [NSMutableDictionary dictionary];
84
+ _cancelledCallInviteMap = [NSMutableDictionary dictionary];
85
+ _audioDevices = [NSMutableDictionary dictionary];
86
+
87
+ NSString *reactNativeSDK = kTwilioVoiceReactNativeReactNativeVoiceSDK;
88
+ setenv("global-env-sdk", [reactNativeSDK UTF8String], 1);
89
+
90
+ NSString *reactNativeSdkVersion = kTwilioVoiceReactNativeReactNativeVoiceSDKVer;
91
+ setenv("com.twilio.voice.env.sdk.version", [reactNativeSdkVersion UTF8String], 1);
92
+
93
+ sTwilioAudioDevice = [TVODefaultAudioDevice audioDevice];
94
+ TwilioVoiceSDK.audioDevice = sTwilioAudioDevice;
95
+
96
+ TwilioVoiceSDK.logLevel = TVOLogLevelTrace;
97
+
98
+ [self subscribeToNotifications];
99
+ [self initializeCallKit];
100
+ [self initializeAudioDeviceList];
101
+ }
102
+
103
+ return self;
104
+ }
105
+
106
+ - (void)initializePushRegistry {
107
+ self.twilioVoicePushRegistry = [TwilioVoicePushRegistry new];
108
+ [self.twilioVoicePushRegistry updatePushRegistry];
109
+ }
110
+
111
+ - (void)subscribeToNotifications {
112
+ [[NSNotificationCenter defaultCenter] addObserver:self
113
+ selector:@selector(handlePushRegistryNotification:)
114
+ name:kTwilioVoicePushRegistryNotification
115
+ object:nil];
116
+
117
+ [[NSNotificationCenter defaultCenter] addObserver:self
118
+ selector:@selector(handleRouteChange:)
119
+ name:AVAudioSessionRouteChangeNotification
120
+ object:nil];
121
+ }
122
+
123
+ - (void)dealloc {
124
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
125
+ }
126
+
127
+ - (void)handlePushRegistryNotification:(NSNotification *)notification {
128
+ NSMutableDictionary *eventBody = [notification.userInfo mutableCopy];
129
+ NSString *type = eventBody[kTwilioVoiceReactNativeVoiceEventType];
130
+ if ([type isEqualToString:kTwilioVoicePushRegistryNotificationDeviceTokenUpdated]) {
131
+ self.deviceTokenData = eventBody[kTwilioVoicePushRegistryNotificationDeviceToken];
132
+
133
+ // Skip the event emitting since 1, the listener has not registered and 2, the app does not need to know about this
134
+ return;
135
+ } else if ([type isEqualToString:kTwilioVoicePushRegistryNotificationIncomingPushReceived]) {
136
+ NSDictionary *payload = eventBody[kTwilioVoicePushRegistryNotificationIncomingPushPayload];
137
+ [TwilioVoiceSDK handleNotification:payload delegate:self delegateQueue:nil callMessageDelegate:self];
138
+ }
139
+ }
140
+
141
+ + (TVODefaultAudioDevice *)twilioAudioDevice {
142
+ return sTwilioAudioDevice;
143
+ }
144
+
145
+ - (void)handleRouteChange:(NSNotification *)notification {
146
+ [self availableAudioDevices];
147
+
148
+ NSMutableArray *nativeAudioDeviceInfos = [NSMutableArray array];
149
+ for (NSString *key in [self.audioDevices allKeys]) {
150
+ [nativeAudioDeviceInfos addObject:self.audioDevices[key]];
151
+ }
152
+
153
+ NSMutableDictionary *eventBody = [@{
154
+ kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeVoiceEventAudioDevicesUpdated,
155
+ kTwilioVoiceReactNativeAudioDeviceKeyAudioDevices: nativeAudioDeviceInfos
156
+ } mutableCopy];
157
+
158
+ if (self.selectedAudioDevice != nil) {
159
+ eventBody[kTwilioVoiceReactNativeAudioDeviceKeySelectedDevice] = self.selectedAudioDevice;
160
+ }
161
+
162
+ [self sendEventWithName:kTwilioVoiceReactNativeScopeVoice body:eventBody];
163
+ }
164
+
165
+ - (void)initializeAudioDeviceList {
166
+ NSUUID *receiverUuid = [NSUUID UUID];
167
+ NSDictionary *builtInReceiver = @{ kTwilioVoiceReactNativeAudioDeviceKeyUuid: receiverUuid.UUIDString,
168
+ kTwilioVoiceReactNativeAudioDeviceKeyType: kTwilioVoiceReactNativeAudioDeviceKeyEarpiece,
169
+ kTwilioVoiceReactNativeAudioDeviceKeyName: @"iPhone",
170
+ kTwilioVoiceAudioDeviceUid: AVAudioSessionPortBuiltInReceiver};
171
+ self.audioDevices[receiverUuid.UUIDString] = builtInReceiver;
172
+
173
+ NSUUID *speakerUuid = [NSUUID UUID];
174
+ NSDictionary *builtInSpeaker = @{ kTwilioVoiceReactNativeAudioDeviceKeyUuid: speakerUuid.UUIDString,
175
+ kTwilioVoiceReactNativeAudioDeviceKeyType: kTwilioVoiceReactNativeAudioDeviceKeySpeaker,
176
+ kTwilioVoiceReactNativeAudioDeviceKeyName: @"Speaker",
177
+ kTwilioVoiceAudioDeviceUid: AVAudioSessionPortBuiltInSpeaker};
178
+ self.audioDevices[speakerUuid.UUIDString] = builtInSpeaker;
179
+
180
+ [self availableAudioDevices];
181
+ }
182
+
183
+ - (NSDictionary *)availableAudioDevices {
184
+ // Pop all except the built-in earpiece and speaker
185
+ for (NSString *key in [self.audioDevices allKeys]) {
186
+ NSDictionary *audioDevice = self.audioDevices[key];
187
+ if (![audioDevice[kTwilioVoiceAudioDeviceUid] isEqualToString:AVAudioSessionPortBuiltInReceiver] &&
188
+ ![audioDevice[kTwilioVoiceAudioDeviceUid] isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
189
+ [self.audioDevices removeObjectForKey:key];
190
+ }
191
+ }
192
+
193
+ NSLog(@"Available audio input devices");
194
+ NSArray *availableInputs = [[AVAudioSession sharedInstance] availableInputs];
195
+ for (AVAudioSessionPortDescription *port in availableInputs) {
196
+ NSLog(@"\t%@, %@, %@", port.portType, port. portName, port.UID);
197
+
198
+ if ([port.portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {
199
+ NSUUID *uuid = [NSUUID UUID];
200
+ NSDictionary *bluetoothHfpDevice = @{ kTwilioVoiceReactNativeAudioDeviceKeyUuid: uuid.UUIDString,
201
+ kTwilioVoiceReactNativeAudioDeviceKeyType: [self audioPortTypeMapping:port.portType],
202
+ kTwilioVoiceReactNativeAudioDeviceKeyName: port.portName,
203
+ kTwilioVoiceAudioDeviceUid: port.UID };
204
+ self.audioDevices[uuid.UUIDString] = bluetoothHfpDevice;
205
+ }
206
+ }
207
+
208
+ NSLog(@"Current route, inputs");
209
+ AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute];
210
+ NSArray *inputs = currentRoute.inputs;
211
+ for (AVAudioSessionPortDescription *port in inputs) {
212
+ NSLog(@"\t%@, %@, %@", port.portType, port.portName, port.UID);
213
+ }
214
+
215
+ NSLog(@"Current route, outputs");
216
+ NSArray *outputs = currentRoute.outputs;
217
+ for (AVAudioSessionPortDescription *port in outputs) {
218
+ NSLog(@"\t%@, %@, %@", port.portType, port.portName, port.UID);
219
+
220
+ if ([port.portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
221
+ for (NSString *key in [self.audioDevices allKeys]) {
222
+ NSDictionary *device = self.audioDevices[key];
223
+ if ([device[kTwilioVoiceReactNativeAudioDeviceKeyType] isEqualToString:kTwilioVoiceReactNativeAudioDeviceKeyEarpiece]) {
224
+ self.selectedAudioDevice = device;
225
+ break;
226
+ }
227
+ }
228
+ } else if ([port.portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
229
+ for (NSString *key in [self.audioDevices allKeys]) {
230
+ NSDictionary *device = self.audioDevices[key];
231
+ if ([device[kTwilioVoiceReactNativeAudioDeviceKeyType] isEqualToString:kTwilioVoiceReactNativeAudioDeviceKeySpeaker]) {
232
+ self.selectedAudioDevice = device;
233
+ }
234
+ }
235
+ } else {
236
+ BOOL found = NO;
237
+ for (NSString *key in [self.audioDevices allKeys]) {
238
+ NSDictionary *device = self.audioDevices[key];
239
+ if ([device[kTwilioVoiceAudioDeviceUid] isEqualToString:port.UID]) {
240
+ found = YES;
241
+ self.selectedAudioDevice = device;
242
+ break;
243
+ }
244
+ }
245
+
246
+ if (!found) {
247
+ NSLog(@"Unidentified output device selected: %@, %@, %@", port.portType, port.portName, port.UID);
248
+ NSUUID *uuid = [NSUUID UUID];
249
+ NSDictionary *unidentifiedDevice = @{ kTwilioVoiceReactNativeAudioDeviceKeyUuid: uuid.UUIDString,
250
+ kTwilioVoiceReactNativeAudioDeviceKeyType: [self audioPortTypeMapping:port.portType],
251
+ kTwilioVoiceReactNativeAudioDeviceKeyName: port.portName,
252
+ kTwilioVoiceAudioDeviceUid: port.UID };
253
+ self.audioDevices[uuid.UUIDString] = unidentifiedDevice;
254
+ self.selectedAudioDevice = unidentifiedDevice;
255
+ }
256
+ }
257
+ }
258
+
259
+ return self.audioDevices;
260
+ }
261
+
262
+ - (NSString *)audioPortTypeMapping:(NSString *)portType {
263
+ if ([portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
264
+ return kTwilioVoiceReactNativeAudioDeviceKeyEarpiece;
265
+ } else if ([portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
266
+ return kTwilioVoiceReactNativeAudioDeviceKeySpeaker;
267
+ } else if ([portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {
268
+ return kTwilioVoiceReactNativeAudioDeviceKeyBluetooth;
269
+ }
270
+
271
+ return portType;
272
+ }
273
+
274
+ - (BOOL)selectAudioDevice:(NSString *)uuid {
275
+ if (!self.audioDevices[uuid]) {
276
+ NSLog(@"No matching audio device found for %@", uuid);
277
+ return NO;
278
+ }
279
+
280
+ NSDictionary *device = self.audioDevices[uuid];
281
+ NSString *portUid = device[kTwilioVoiceAudioDeviceUid];
282
+ NSString *portType = device[kTwilioVoiceReactNativeAudioDeviceKeyType];
283
+
284
+ NSLog(@"Selecting %@(%@), %@", device[kTwilioVoiceReactNativeAudioDeviceKeyName], device[kTwilioVoiceReactNativeAudioDeviceKeyType], device[kTwilioVoiceAudioDeviceUid]);
285
+
286
+ AVAudioSessionPortDescription *portDescription = nil;
287
+ if ([portType isEqualToString:kTwilioVoiceReactNativeAudioDeviceKeyEarpiece]) {
288
+ NSArray *availableInputs = [[AVAudioSession sharedInstance] availableInputs];
289
+ for (AVAudioSessionPortDescription *port in availableInputs) {
290
+ if ([port.portType isEqualToString:AVAudioSessionPortBuiltInMic]) {
291
+ portDescription = port;
292
+ break;
293
+ }
294
+ }
295
+
296
+ if (!portDescription) {
297
+ NSLog(@"Built-in mic not found");
298
+ return NO;
299
+ }
300
+ } else if ([portType isEqualToString:kTwilioVoiceReactNativeAudioDeviceKeyBluetooth]) {
301
+ NSArray *availableInputs = [[AVAudioSession sharedInstance] availableInputs];
302
+ for (AVAudioSessionPortDescription *port in availableInputs) {
303
+ if ([port.UID isEqualToString:portUid]) {
304
+ portDescription = port;
305
+ break;
306
+ }
307
+ }
308
+
309
+ if (!portDescription) {
310
+ NSLog(@"Bluetooth device %@ not found", device[kTwilioVoiceReactNativeAudioDeviceKeyName]);
311
+ return NO;
312
+ }
313
+ }
314
+
315
+ // Update preferred input
316
+ NSError *inputError;
317
+ [[AVAudioSession sharedInstance] setPreferredInput:portDescription error:&inputError];
318
+ if (inputError) {
319
+ NSLog(@"Failed to set preferred input: %@", inputError);
320
+ return NO;
321
+ }
322
+
323
+ // Override output to speaker if speaker is selected
324
+ if ([portType isEqualToString:kTwilioVoiceReactNativeAudioDeviceKeySpeaker]) {
325
+ AVAudioSessionPortOverride outputOverride = AVAudioSessionPortOverrideSpeaker;
326
+ NSError *outputError;
327
+ [[AVAudioSession sharedInstance] overrideOutputAudioPort:outputOverride error:&outputError];
328
+ if (outputError) {
329
+ NSLog(@"Failed to override output port: %@", outputError);
330
+ return NO;
331
+ }
332
+ }
333
+
334
+ return YES;
335
+ }
336
+
337
+ // TODO: Move to separate utility file someday
338
+ - (NSDictionary *)callInfo:(TVOCall *)call {
339
+ NSMutableDictionary *callInfo = [@{kTwilioVoiceReactNativeCallInfoIsMuted: @(call.isMuted),
340
+ kTwilioVoiceReactNativeCallInfoIsOnHold: @(call.isOnHold),
341
+ kTwilioVoiceReactNativeCallInfoSid: call.sid,
342
+ kTwilioVoiceReactNativeCallInfoState: [self stringOfState:call.state]} mutableCopy];
343
+
344
+ if (call.uuid != nil) {
345
+ callInfo[kTwilioVoiceReactNativeCallInfoUuid] = call.uuid.UUIDString;
346
+ }
347
+
348
+ if (call.from != nil) {
349
+ callInfo[kTwilioVoiceReactNativeCallInfoFrom] = call.from;
350
+ }
351
+
352
+ if (call.to != nil) {
353
+ callInfo[kTwilioVoiceReactNativeCallInfoTo] = call.to;
354
+ }
355
+
356
+ NSString *initialConnectedTimestamp = self.callConnectMap[call.uuid.UUIDString];
357
+ if (initialConnectedTimestamp != nil) {
358
+ callInfo[kTwilioVoiceReactNativeCallInfoInitialConnectedTimestamp] = initialConnectedTimestamp;
359
+ }
360
+
361
+ TVOCallInvite *callInvite = self.callInviteMap[call.uuid.UUIDString];
362
+ if (callInvite && callInvite.customParameters) {
363
+ callInfo[kTwilioVoiceReactNativeCallInviteInfoCustomParameters] = [callInvite.customParameters copy];
364
+ }
365
+
366
+ return callInfo;
367
+ }
368
+
369
+ - (NSDictionary *)callInviteInfo:(TVOCallInvite *)callInvite {
370
+ NSMutableDictionary *callInviteInfo = [@{kTwilioVoiceReactNativeCallInviteInfoUuid: callInvite.uuid.UUIDString,
371
+ kTwilioVoiceReactNativeCallInviteInfoCallSid: callInvite.callSid,
372
+ kTwilioVoiceReactNativeCallInviteInfoTo: callInvite.to} mutableCopy];
373
+
374
+ if (callInvite.from != nil) {
375
+ callInviteInfo[kTwilioVoiceReactNativeCallInviteInfoFrom] = callInvite.from;
376
+ }
377
+
378
+ if (callInvite.customParameters != nil) {
379
+ callInviteInfo[kTwilioVoiceReactNativeCallInviteInfoCustomParameters] = [callInvite.customParameters copy];
380
+ }
381
+
382
+ return callInviteInfo;
383
+ }
384
+
385
+ - (NSDictionary *)cancelledCallInviteInfo:(TVOCancelledCallInvite *)cancelledCallInvite {
386
+ NSMutableDictionary *cancelledCallInviteInfo = [@{
387
+ kTwilioVoiceReactNativeCancelledCallInviteInfoCallSid: cancelledCallInvite.callSid,
388
+ kTwilioVoiceReactNativeCancelledCallInviteInfoTo: cancelledCallInvite.to
389
+ } mutableCopy];
390
+
391
+ if (cancelledCallInvite.from != nil) {
392
+ cancelledCallInviteInfo[kTwilioVoiceReactNativeCancelledCallInviteInfoFrom] = cancelledCallInvite.from;
393
+ }
394
+
395
+ if (cancelledCallInvite.customParameters != nil) {
396
+ cancelledCallInviteInfo[kTwilioVoiceReactNativeCancelledCallInviteInfoCustomParameters] = [cancelledCallInvite.customParameters copy];
397
+ }
398
+
399
+ return cancelledCallInviteInfo;
400
+ }
401
+
402
+ RCT_EXPORT_MODULE();
403
+
404
+ #pragma mark - React Native
405
+
406
+ - (NSArray<NSString *> *)supportedEvents
407
+ {
408
+ return @[kTwilioVoiceReactNativeScopeVoice, kTwilioVoiceReactNativeScopeCall, kTwilioVoiceReactNativeScopeCallInvite, kTwilioVoiceReactNativeScopeCallMessage];
409
+ }
410
+
411
+ + (BOOL)requiresMainQueueSetup
412
+ {
413
+ return YES;
414
+ }
415
+
416
+ - (void)startObserving {
417
+ NSLog(@"Started observing");
418
+ _hasObserver = YES;
419
+ }
420
+
421
+ - (void)stopObserving {
422
+ NSLog(@"Stopped observing");
423
+ _hasObserver = NO;
424
+ }
425
+
426
+ - (void)sendEventWithName:(NSString *)eventName body:(id)body {
427
+ if (_hasObserver) {
428
+ [super sendEventWithName:eventName body:body];
429
+ } else {
430
+ NSLog(@"No event observer registered yet. Omitting event: %@, event body: %@", eventName, body);
431
+ }
432
+ }
433
+
434
+ #pragma mark - Bingings (Voice methods)
435
+
436
+ RCT_EXPORT_METHOD(voice_getVersion:(RCTPromiseResolveBlock)resolve
437
+ rejecter:(RCTPromiseRejectBlock)reject)
438
+ {
439
+ resolve(TwilioVoiceSDK.sdkVersion);
440
+ }
441
+
442
+ RCT_EXPORT_METHOD(voice_getDeviceToken:(RCTPromiseResolveBlock)resolve
443
+ rejecter:(RCTPromiseRejectBlock)reject)
444
+ {
445
+ if (self.deviceTokenData) {
446
+ const char *tokenBytes = (const char *)[self.deviceTokenData bytes];
447
+ NSMutableString *deviceTokenString = [NSMutableString string];
448
+ for (NSUInteger i = 0; i < [self.deviceTokenData length]; ++i) {
449
+ [deviceTokenString appendFormat:@"%02.2hhx", tokenBytes[i]];
450
+ }
451
+ resolve(deviceTokenString);
452
+ } else {
453
+ resolve(@"");
454
+ }
455
+ }
456
+
457
+ RCT_EXPORT_METHOD(voice_initializePushRegistry:(RCTPromiseResolveBlock)resolve
458
+ rejecter:(RCTPromiseRejectBlock)reject)
459
+ {
460
+ [self initializePushRegistry];
461
+ resolve(nil);
462
+ }
463
+
464
+ RCT_EXPORT_METHOD(voice_setCallKitConfiguration:(NSDictionary *)configuration
465
+ resolver:(RCTPromiseResolveBlock)resolve
466
+ rejecter:(RCTPromiseRejectBlock)reject)
467
+ {
468
+ [self initializeCallKitWithConfiguration:configuration];
469
+ resolve(nil);
470
+ }
471
+
472
+ RCT_EXPORT_METHOD(voice_register:(NSString *)accessToken
473
+ resolver:(RCTPromiseResolveBlock)resolve
474
+ rejecter:(RCTPromiseRejectBlock)reject)
475
+ {
476
+ #if TARGET_IPHONE_SIMULATOR
477
+ if (!self.deviceTokenData) {
478
+ NSLog(@"Please note that PushKit and incoming call are not supported on simulators");
479
+ NSString *testDeviceToken = @"deadbeefdeadbeefdeadbeefdeadbeef";
480
+ self.deviceTokenData = [testDeviceToken dataUsingEncoding:NSUTF8StringEncoding];
481
+ }
482
+ #endif
483
+
484
+ if (self.registrationInProgress) {
485
+ reject(kTwilioVoiceReactNativeVoiceError, @"Registration in progress. Please try again later", nil);
486
+ return;
487
+ }
488
+
489
+ self.registrationInProgress = YES;
490
+
491
+ [self asyncPushRegistryInitialization:kPushRegistryDeviceTokenRetryTimeout
492
+ completion:^(NSData *deviceTokenData) {
493
+ if (deviceTokenData) {
494
+ [TwilioVoiceSDK registerWithAccessToken:accessToken
495
+ deviceToken:deviceTokenData
496
+ completion:^(NSError *error) {
497
+ self.registrationInProgress = NO;
498
+ if (error) {
499
+ NSString *errorMessage = [NSString stringWithFormat:@"Failed to register: %@", error];
500
+ NSLog(@"%@", errorMessage);
501
+ [self sendEventWithName:kTwilioVoiceReactNativeScopeVoice
502
+ body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeVoiceEventError,
503
+ kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
504
+ kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}}];
505
+ reject(kTwilioVoiceReactNativeVoiceError, errorMessage, nil);
506
+ } else {
507
+ [self sendEventWithName:kTwilioVoiceReactNativeScopeVoice
508
+ body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeVoiceEventRegistered}];
509
+ resolve(nil);
510
+ }
511
+ }];
512
+ } else {
513
+ self.registrationInProgress = NO;
514
+ reject(kTwilioVoiceReactNativeVoiceError, @"Failed to initialize PushKit device token", nil);
515
+ }
516
+ }];
517
+ }
518
+
519
+ - (void)asyncPushRegistryInitialization:(dispatch_time_t)timeout
520
+ completion:(void(^)(NSData *deviceTokenData))completion {
521
+ if (self.deviceTokenData) {
522
+ completion(self.deviceTokenData);
523
+ return;
524
+ }
525
+
526
+ if (timeout > 0) {
527
+ __block dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
528
+ __weak typeof(self) weakSelf = self;
529
+ dispatch_after(delay, dispatch_get_main_queue(), ^{
530
+ dispatch_time_t backoffTimeout = (timeout > kExponentialBackoff)? timeout - kExponentialBackoff : 0;
531
+ [weakSelf asyncPushRegistryInitialization:backoffTimeout completion:completion];
532
+ });
533
+ } else {
534
+ // Device token could be nil. The voice_register() method will handle the promise.
535
+ completion(self.deviceTokenData);
536
+ }
537
+ }
538
+
539
+ RCT_EXPORT_METHOD(voice_unregister:(NSString *)accessToken
540
+ resolver:(RCTPromiseResolveBlock)resolve
541
+ rejecter:(RCTPromiseRejectBlock)reject)
542
+ {
543
+ #if TARGET_IPHONE_SIMULATOR
544
+ if (!self.deviceTokenData) {
545
+ NSLog(@"Please note that PushKit and incoming call are not supported on simulators");
546
+ NSString *testDeviceToken = @"deadbeefdeadbeefdeadbeefdeadbeef";
547
+ self.deviceTokenData = [testDeviceToken dataUsingEncoding:NSUTF8StringEncoding];
548
+ }
549
+ #endif
550
+
551
+ if (self.registrationInProgress) {
552
+ reject(kTwilioVoiceReactNativeVoiceError, @"Registration in progress. Please try again later", nil);
553
+ return;
554
+ }
555
+
556
+ self.registrationInProgress = YES;
557
+
558
+ [self asyncPushRegistryInitialization:kPushRegistryDeviceTokenRetryTimeout
559
+ completion:^(NSData *deviceTokenData) {
560
+ if (deviceTokenData) {
561
+ [TwilioVoiceSDK unregisterWithAccessToken:accessToken
562
+ deviceToken:deviceTokenData
563
+ completion:^(NSError *error) {
564
+ self.registrationInProgress = NO;
565
+ if (error) {
566
+ NSString *errorMessage = [NSString stringWithFormat:@"Failed to unregister: %@", error];
567
+ NSLog(@"%@", errorMessage);
568
+ [self sendEventWithName:kTwilioVoiceReactNativeScopeVoice
569
+ body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeVoiceEventError,
570
+ kTwilioVoiceReactNativeVoiceErrorKeyError: @{kTwilioVoiceReactNativeVoiceErrorKeyCode: @(error.code),
571
+ kTwilioVoiceReactNativeVoiceErrorKeyMessage: [error localizedDescription]}}];
572
+ reject(kTwilioVoiceReactNativeVoiceError, errorMessage, nil);
573
+ } else {
574
+ [self sendEventWithName:kTwilioVoiceReactNativeScopeVoice
575
+ body:@{kTwilioVoiceReactNativeVoiceEventType: kTwilioVoiceReactNativeVoiceEventUnregistered}];
576
+ resolve(nil);
577
+ }
578
+ }];
579
+ } else {
580
+ self.registrationInProgress = NO;
581
+ reject(kTwilioVoiceReactNativeVoiceError, @"Failed to initialize PushKit device token", nil);
582
+ }
583
+ }];
584
+ }
585
+
586
+ RCT_EXPORT_METHOD(voice_connect_ios:(NSString *)accessToken
587
+ params:(NSDictionary *)params
588
+ contactHandle:(NSString *)contactHandle
589
+ resolver:(RCTPromiseResolveBlock)resolve
590
+ rejecter:(RCTPromiseRejectBlock)reject)
591
+ {
592
+ [self makeCallWithAccessToken:accessToken params:params contactHandle:contactHandle];
593
+ self.callPromiseResolver = resolve;
594
+ }
595
+
596
+ RCT_EXPORT_METHOD(voice_getCalls:(RCTPromiseResolveBlock)resolve
597
+ rejecter:(RCTPromiseRejectBlock)reject)
598
+ {
599
+ NSMutableArray *callInfoArray = [NSMutableArray array];
600
+ for (NSString *uuid in [self.callMap allKeys]) {
601
+ TVOCall *call = self.callMap[uuid];
602
+ [callInfoArray addObject:[self callInfo:call]];
603
+ }
604
+ resolve(callInfoArray);
605
+ }
606
+
607
+ RCT_EXPORT_METHOD(voice_getCallInvites:(RCTPromiseResolveBlock)resolve
608
+ rejecter:(RCTPromiseRejectBlock)reject)
609
+ {
610
+ NSMutableArray *callInviteInfoArray = [NSMutableArray array];
611
+ for (NSString *uuid in [self.callInviteMap allKeys]) {
612
+ TVOCallInvite *callInvite = self.callInviteMap[uuid];
613
+ [callInviteInfoArray addObject:[self callInviteInfo:callInvite]];
614
+ }
615
+ resolve(callInviteInfoArray);
616
+ }
617
+
618
+ RCT_EXPORT_METHOD(voice_getAudioDevices:(RCTPromiseResolveBlock)resolve
619
+ rejecter:(RCTPromiseRejectBlock)reject)
620
+ {
621
+ NSMutableArray *nativeAudioDeviceInfos = [NSMutableArray array];
622
+ for (NSString *key in [self.audioDevices allKeys]) {
623
+ [nativeAudioDeviceInfos addObject:self.audioDevices[key]];
624
+ }
625
+ resolve(@{kTwilioVoiceReactNativeAudioDeviceKeyAudioDevices: nativeAudioDeviceInfos,
626
+ kTwilioVoiceReactNativeAudioDeviceKeySelectedDevice: self.selectedAudioDevice});
627
+ }
628
+
629
+ RCT_EXPORT_METHOD(voice_selectAudioDevice:(NSString *)uuid
630
+ resolver:(RCTPromiseResolveBlock)resolve
631
+ rejecter:(RCTPromiseRejectBlock)reject)
632
+ {
633
+ if ([self selectAudioDevice:uuid]) {
634
+ resolve(nil);
635
+ } else {
636
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Failed to select audio device %@", uuid], nil);
637
+ }
638
+ }
639
+
640
+ RCT_EXPORT_METHOD(voice_showNativeAvRoutePicker:(RCTPromiseResolveBlock)resolve
641
+ rejecter:(RCTPromiseRejectBlock)reject)
642
+ {
643
+ TVRNAVRoutePickerView *routePicker = [[TVRNAVRoutePickerView alloc] initWithFrame:CGRectZero];
644
+
645
+ UIWindow *window = [UIApplication sharedApplication].windows[0];
646
+ UIViewController *rootViewController = window.rootViewController;
647
+ if (rootViewController) {
648
+ UIViewController *topViewController = rootViewController;
649
+ while (topViewController.presentedViewController) {
650
+ topViewController = topViewController.presentedViewController;
651
+ }
652
+
653
+ dispatch_async(dispatch_get_main_queue(), ^{
654
+ [topViewController.view addSubview:routePicker];
655
+ [routePicker present];
656
+ });
657
+ }
658
+
659
+ resolve(nil);
660
+ }
661
+
662
+ #pragma mark - Bingings (Call)
663
+
664
+ RCT_EXPORT_METHOD(call_disconnect:(NSString *)uuid
665
+ resolver:(RCTPromiseResolveBlock)resolve
666
+ rejecter:(RCTPromiseRejectBlock)reject)
667
+ {
668
+ if (self.callMap[uuid]) {
669
+ [self endCallWithUuid:[[NSUUID alloc] initWithUUIDString:uuid]];
670
+ resolve(nil);
671
+ } else {
672
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
673
+ }
674
+ }
675
+
676
+ RCT_EXPORT_METHOD(call_getState:(NSString *)uuid
677
+ resolver:(RCTPromiseResolveBlock)resolve
678
+ rejecter:(RCTPromiseRejectBlock)reject)
679
+ {
680
+ TVOCall *call = self.callMap[uuid];
681
+ NSString *state = @"";
682
+ if (call) {
683
+ state = [self stringOfState:call.state];
684
+ }
685
+
686
+ resolve(state);
687
+ }
688
+
689
+ RCT_EXPORT_METHOD(call_getSid:(NSString *)uuid
690
+ resolver:(RCTPromiseResolveBlock)resolve
691
+ rejecter:(RCTPromiseRejectBlock)reject)
692
+ {
693
+ TVOCall *call = self.callMap[uuid];
694
+ NSString *callSid = (call && call.state != TVOCallStateConnecting)? call.sid : @"";
695
+
696
+ resolve(callSid);
697
+ }
698
+
699
+ RCT_EXPORT_METHOD(call_getFrom:(NSString *)uuid
700
+ resolver:(RCTPromiseResolveBlock)resolve
701
+ rejecter:(RCTPromiseRejectBlock)reject)
702
+ {
703
+ TVOCall *call = self.callMap[uuid];
704
+ NSString *from = (call && [call.from length] > 0)? call.from : @"";
705
+
706
+ resolve(from);
707
+ }
708
+
709
+ RCT_EXPORT_METHOD(call_getTo:(NSString *)uuid
710
+ resolver:(RCTPromiseResolveBlock)resolve
711
+ rejecter:(RCTPromiseRejectBlock)reject)
712
+ {
713
+ TVOCall *call = self.callMap[uuid];
714
+ NSString *to = (call && [call.to length] > 0)? call.to : @"";
715
+
716
+ resolve(to);
717
+ }
718
+
719
+ RCT_EXPORT_METHOD(call_hold:(NSString *)uuid
720
+ onHold:(BOOL)onHold
721
+ resolver:(RCTPromiseResolveBlock)resolve
722
+ rejecter:(RCTPromiseRejectBlock)reject)
723
+ {
724
+ TVOCall *call = self.callMap[uuid];
725
+ if (call) {
726
+ [call setOnHold:onHold];
727
+ resolve(@(call.isOnHold));
728
+ } else {
729
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
730
+ }
731
+ }
732
+
733
+ RCT_EXPORT_METHOD(call_isOnHold:(NSString *)uuid
734
+ resolver:(RCTPromiseResolveBlock)resolve
735
+ rejecter:(RCTPromiseRejectBlock)reject)
736
+ {
737
+ TVOCall *call = self.callMap[uuid];
738
+ if (call) {
739
+ resolve(@(call.isOnHold));
740
+ } else {
741
+ resolve(@(NO));
742
+ }
743
+ }
744
+
745
+ RCT_EXPORT_METHOD(call_mute:(NSString *)uuid
746
+ muted:(BOOL)muted
747
+ resolver:(RCTPromiseResolveBlock)resolve
748
+ rejecter:(RCTPromiseRejectBlock)reject)
749
+ {
750
+ TVOCall *call = self.callMap[uuid];
751
+ if (call) {
752
+ [call setMuted:muted];
753
+ resolve(@(call.isMuted));
754
+ } else {
755
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
756
+ }
757
+ }
758
+
759
+ RCT_EXPORT_METHOD(call_isMuted:(NSString *)uuid
760
+ resolver:(RCTPromiseResolveBlock)resolve
761
+ rejecter:(RCTPromiseRejectBlock)reject)
762
+ {
763
+ TVOCall *call = self.callMap[uuid];
764
+ if (call) {
765
+ resolve(@(call.isMuted));
766
+ } else {
767
+ resolve(@(NO));
768
+ }
769
+ }
770
+
771
+ RCT_EXPORT_METHOD(call_sendDigits:(NSString *)uuid
772
+ digits:(NSString *)digits
773
+ resolver:(RCTPromiseResolveBlock)resolve
774
+ rejecter:(RCTPromiseRejectBlock)reject)
775
+ {
776
+ TVOCall *call = self.callMap[uuid];
777
+ if (call) {
778
+ [call sendDigits:digits];
779
+ resolve(nil);
780
+ } else {
781
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
782
+ }
783
+ }
784
+
785
+ RCT_EXPORT_METHOD(call_postFeedback:(NSString *)uuid
786
+ score:(NSString *)score
787
+ issue:(NSString *)issue
788
+ resolver:(RCTPromiseResolveBlock)resolve
789
+ rejecter:(RCTPromiseRejectBlock)reject)
790
+ {
791
+ TVOCall *call = self.callMap[uuid];
792
+ if (call) {
793
+ [call postFeedback:[self scoreFromString:score] issue:[self issueFromString:issue]];
794
+ resolve(nil);
795
+ } else {
796
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
797
+ }
798
+ }
799
+
800
+ RCT_EXPORT_METHOD(call_getStats:(NSString *)uuid
801
+ resolver:(RCTPromiseResolveBlock)resolve
802
+ rejecter:(RCTPromiseRejectBlock)reject)
803
+ {
804
+ TVOCall *call = self.callMap[uuid];
805
+ if (call) {
806
+ [call getStatsWithBlock:^(NSArray<TVOStatsReport *> *statsReports) {
807
+ NSAssert([statsReports count] >= 1, @"Invalid stats reports array size");
808
+ NSArray *statsReportJson = [TwilioVoiceStatsReport jsonWithStatsReportsArray:statsReports];
809
+ resolve(statsReportJson);
810
+ }];
811
+ } else {
812
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
813
+ }
814
+ }
815
+
816
+ RCT_EXPORT_METHOD(call_sendMessage:(NSString *)uuid
817
+ content:(NSString *)content
818
+ contentType:(NSString *)contentType
819
+ messageType:(NSString *)messageType
820
+ resolver:(RCTPromiseResolveBlock)resolve
821
+ rejecter:(RCTPromiseRejectBlock)reject)
822
+ {
823
+ TVOCallInvite *callInvite = self.callInviteMap[uuid];
824
+ TVOCall *call = self.callMap[uuid];
825
+ if (call) {
826
+ TVOCallMessage *callMessage = [TVOCallMessage messageWithContent:content
827
+ messageType:messageType
828
+ block:^(TVOCallMessageBuilder *builder) {
829
+ builder.contentType = contentType;
830
+ }];
831
+ NSString *voiceEventSid = [call sendMessage:callMessage];
832
+ resolve(voiceEventSid);
833
+ } else if (callInvite) {
834
+ TVOCallMessage *callMessage = [TVOCallMessage messageWithContent:content
835
+ messageType:messageType
836
+ block:^(TVOCallMessageBuilder *builder) {
837
+ builder.contentType = contentType;
838
+ }];
839
+ NSString *voiceEventSid = [callInvite sendMessage:callMessage];
840
+ resolve(voiceEventSid);
841
+ } else {
842
+ reject(kTwilioVoiceReactNativeVoiceError, [NSString stringWithFormat:@"Call with %@ not found", uuid], nil);
843
+ }
844
+ }
845
+
846
+ #pragma mark - Bingings (Call Invite)
847
+
848
+ RCT_EXPORT_METHOD(callInvite_accept:(NSString *)callInviteUuid
849
+ acceptOptions:(NSDictionary *)acceptOptions
850
+ resolver:(RCTPromiseResolveBlock)resolve
851
+ rejecter:(RCTPromiseRejectBlock)reject)
852
+ {
853
+ [self answerCallInvite:[[NSUUID alloc] initWithUUIDString:callInviteUuid]
854
+ completion:^(BOOL success) {
855
+ if (success) {
856
+ BOOL found = NO;
857
+ for (NSString *uuidKey in [self.callMap allKeys]) {
858
+ if ([uuidKey isEqualToString:callInviteUuid]) {
859
+ found = YES;
860
+ TVOCall *call = self.callMap[uuidKey];
861
+ resolve([self callInfo:call]);
862
+ }
863
+ }
864
+
865
+ if (!found) {
866
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching call", nil);
867
+ }
868
+ } else {
869
+ reject(kTwilioVoiceReactNativeVoiceError, @"Failed to answer the call invite", nil);
870
+ }
871
+ }];
872
+ }
873
+
874
+ RCT_EXPORT_METHOD(callInvite_reject:(NSString *)callInviteUuid
875
+ resolver:(RCTPromiseResolveBlock)resolve
876
+ rejecter:(RCTPromiseRejectBlock)reject)
877
+ {
878
+ [self endCallWithUuid:[[NSUUID alloc] initWithUUIDString:callInviteUuid]];
879
+ resolve(nil);
880
+ }
881
+
882
+ RCT_EXPORT_METHOD(callInvite_isValid:(NSString *)callInviteUuid
883
+ resolver:(RCTPromiseResolveBlock)resolve
884
+ rejecter:(RCTPromiseRejectBlock)reject)
885
+ {
886
+ resolve(@(YES));
887
+ }
888
+
889
+ RCT_EXPORT_METHOD(callInvite_getCallSid:(NSString *)callInviteUuid
890
+ resolver:(RCTPromiseResolveBlock)resolve
891
+ rejecter:(RCTPromiseRejectBlock)reject)
892
+ {
893
+ if (self.callInviteMap[callInviteUuid]) {
894
+ TVOCallInvite *callInvite = self.callInviteMap[callInviteUuid];
895
+ resolve(callInvite.callSid);
896
+ } else {
897
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching call invite", nil);
898
+ }
899
+ }
900
+
901
+ RCT_EXPORT_METHOD(callInvite_getFrom:(NSString *)callInviteUuid
902
+ resolver:(RCTPromiseResolveBlock)resolve
903
+ rejecter:(RCTPromiseRejectBlock)reject)
904
+ {
905
+ if (self.callInviteMap[callInviteUuid]) {
906
+ TVOCallInvite *callInvite = self.callInviteMap[callInviteUuid];
907
+ resolve(callInvite.from);
908
+ } else {
909
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching call invite", nil);
910
+ }
911
+ }
912
+
913
+ RCT_EXPORT_METHOD(callInvite_getTo:(NSString *)callInviteUuid
914
+ resolver:(RCTPromiseResolveBlock)resolve
915
+ rejecter:(RCTPromiseRejectBlock)reject)
916
+ {
917
+ if (self.callInviteMap[callInviteUuid]) {
918
+ TVOCallInvite *callInvite = self.callInviteMap[callInviteUuid];
919
+ resolve(callInvite.to);
920
+ } else {
921
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching call invite", nil);
922
+ }
923
+ }
924
+
925
+ RCT_EXPORT_METHOD(callInvite_updateCallerHandle:(NSString *)callInviteUuid
926
+ handle:(NSString *)handle
927
+ resolver:(RCTPromiseResolveBlock)resolve
928
+ rejecter:(RCTPromiseRejectBlock)reject)
929
+ {
930
+ if (self.callInviteMap[callInviteUuid]) {
931
+ [self updateCall:callInviteUuid callerHandle:handle];
932
+ resolve(nil);
933
+ } else {
934
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching call invite", nil);
935
+ }
936
+ }
937
+
938
+ RCT_EXPORT_METHOD(cancelledCallInvite_getCallSid:(NSString *)cancelledCallInviteUuid
939
+ resolver:(RCTPromiseResolveBlock)resolve
940
+ rejecter:(RCTPromiseRejectBlock)reject)
941
+ {
942
+ if (self.cancelledCallInviteMap[cancelledCallInviteUuid]) {
943
+ TVOCancelledCallInvite *cancelledCallInvite = self.cancelledCallInviteMap[cancelledCallInviteUuid];
944
+ resolve(cancelledCallInvite.callSid);
945
+ } else {
946
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching cancelled call invite", nil);
947
+ }
948
+ }
949
+
950
+ RCT_EXPORT_METHOD(cancelledCallInvite_getFrom:(NSString *)cancelledCallInviteUuid
951
+ resolver:(RCTPromiseResolveBlock)resolve
952
+ rejecter:(RCTPromiseRejectBlock)reject)
953
+ {
954
+ if (self.cancelledCallInviteMap[cancelledCallInviteUuid]) {
955
+ TVOCancelledCallInvite *cancelledCallInvite = self.cancelledCallInviteMap[cancelledCallInviteUuid];
956
+ resolve(cancelledCallInvite.from);
957
+ } else {
958
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching cancelled call invite", nil);
959
+ }
960
+ }
961
+
962
+ RCT_EXPORT_METHOD(cancelledCallInvite_getTo:(NSString *)cancelledCallInviteUuid
963
+ resolver:(RCTPromiseResolveBlock)resolve
964
+ rejecter:(RCTPromiseRejectBlock)reject)
965
+ {
966
+ if (self.cancelledCallInviteMap[cancelledCallInviteUuid]) {
967
+ TVOCancelledCallInvite *cancelledCallInvite = self.cancelledCallInviteMap[cancelledCallInviteUuid];
968
+ resolve(cancelledCallInvite.to);
969
+ } else {
970
+ reject(kTwilioVoiceReactNativeVoiceError, @"No matching cancelled call invite", nil);
971
+ }
972
+ }
973
+
974
+ #pragma mark - utility
975
+
976
+ RCT_EXPORT_METHOD(util_generateId:(RCTPromiseResolveBlock)resolve
977
+ rejecter:(RCTPromiseRejectBlock)reject)
978
+ {
979
+ resolve([NSUUID UUID].UUIDString);
980
+ }
981
+
982
+ RCT_EXPORT_METHOD(voice_setIncomingCallContactHandleTemplate:(NSString *)template
983
+ resolver:(RCTPromiseResolveBlock)resolve
984
+ rejecter:(RCTPromiseRejectBlock)reject)
985
+ {
986
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
987
+ NSString *preferenceKey = @"incomingCallContactHandleTemplate";
988
+ [preferences setObject:template forKey:preferenceKey];
989
+ [preferences synchronize];
990
+ resolve(NULL);
991
+ }
992
+
993
+ - (NSString *)stringOfState:(TVOCallState)state {
994
+ switch (state) {
995
+ case TVOCallStateConnecting:
996
+ return kTwilioVoiceReactNativeCallStateConnecting;
997
+ case TVOCallStateRinging:
998
+ return kTwilioVoiceReactNativeCallStateRinging;
999
+ case TVOCallStateConnected:
1000
+ return kTwilioVoiceReactNativeCallStateConnected;
1001
+ case TVOCallStateReconnecting:
1002
+ return kTwilioVoiceReactNativeCallStateReconnecting;
1003
+ case TVOCallStateDisconnected:
1004
+ return kTwilioVoiceReactNativeCallStateDisconnected;
1005
+ default:
1006
+ return kTwilioVoiceReactNativeCallStateConnecting;
1007
+ }
1008
+ }
1009
+
1010
+ - (TVOCallFeedbackScore)scoreFromString:(NSString *)score {
1011
+ if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreNotReported]) {
1012
+ return TVOCallFeedbackScoreNotReported;
1013
+ } else if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreOne]) {
1014
+ return TVOCallFeedbackScoreOnePoint;
1015
+ } else if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreTwo]) {
1016
+ return TVOCallFeedbackScoreTwoPoints;
1017
+ } else if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreThree]) {
1018
+ return TVOCallFeedbackScoreThreePoints;
1019
+ } else if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreFour]) {
1020
+ return TVOCallFeedbackScoreFourPoints;
1021
+ } else if ([score isEqualToString:kTwilioVoiceReactNativeCallFeedbackScoreFive]) {
1022
+ return TVOCallFeedbackScoreFivePoints;
1023
+ }
1024
+ return TVOCallFeedbackScoreNotReported;
1025
+ }
1026
+
1027
+ - (TVOCallFeedbackIssue)issueFromString:(NSString *)issue {
1028
+ if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueNotReported]) {
1029
+ return TVOCallFeedbackIssueNotReported;
1030
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueDroppedCall]) {
1031
+ return TVOCallFeedbackIssueDroppedCall;
1032
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueAudioLatency]) {
1033
+ return TVOCallFeedbackIssueAudioLatency;
1034
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueOneWayAudio]) {
1035
+ return TVOCallFeedbackIssueOneWayAudio;
1036
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueChoppyAudio]) {
1037
+ return TVOCallFeedbackIssueChoppyAudio;
1038
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueNoisyCall]) {
1039
+ return TVOCallFeedbackIssueNoisyCall;
1040
+ } else if ([issue isEqualToString:kTwilioVoiceReactNativeCallFeedbackIssueEcho]) {
1041
+ return TVOCallFeedbackIssueEcho;
1042
+ }
1043
+ return TVOCallFeedbackIssueNotReported;
1044
+ }
1045
+
1046
+ @end