Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ RNCallKeep.registerAndroidEvents();
| [checkReachability](#checkReachability) | ❌ | ✅ |
| [didChangeAudioRoute](#didChangeAudioRoute) | ✅ | ✅ |
| [onHasActiveCall](#onHasActiveCall) | ❌ | ✅ |
| [didFailCallAction](#didFailCallAction) | ✅ | ❌ |

### didReceiveStartCallAction

Expand Down Expand Up @@ -901,6 +902,30 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
- `callUUID` (string)
- The UUID of the call.

### didFailCallAction

CallKit refused a call action that callkeep requested: `startCall`, `answerIncomingCall`, `endCall`, `setOnHold`, `setMutedCall` or `sendDTMF`. Until now such a refusal was only written to the native log, so JavaScript kept believing the action had happened. The most common case is `startCall` while another call is active with `maximumCallGroups: '1'`: CallKit answers `MaximumCallGroupsReached`, no outgoing call is shown, and no `didReceiveStartCallAction` follows.

```js
RNCallKeep.addEventListener('didFailCallAction', ({ callUUID, action, errorCode, error }) => {
if (action === 'startCall') {
// CallKit never started this call: hang up your own leg and forget it.
hangUpAndForget(callUUID);
}
});
```

- `callUUID` (string)
- The UUID of the call the refused action was for (lowercase), or `''` when the action has none.
- `action` (string)
- `startCall`, `answerCall`, `endCall`, `setHeld`, `setMuted`, `playDTMF`, or `unknown`.
- `errorCode` (string)
- The `CXErrorCodeRequestTransactionError`: `Unentitled`, `UnknownCallProvider`, `EmptyTransaction`, `UnknownCallUUID`, `CallUUIDAlreadyExists`, `InvalidAction`, `MaximumCallGroupsReached`, `CallIsProtected`, or `Unknown`.
- `error` (string)
- The error's localized description.

iOS only. One sent before JavaScript is listening is replayed in `didLoadWithEvents`, like the other events.

### didLoadWithEvents

iOS only.
Expand Down
7 changes: 6 additions & 1 deletion actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const RNCallKeepOnSilenceIncomingCall = 'RNCallKeepOnSilenceIncomingCall';
const RNCallKeepOnIncomingConnectionFailed = 'RNCallKeepOnIncomingConnectionFailed';
const RNCallKeepDidChangeAudioRoute = 'RNCallKeepDidChangeAudioRoute';
const RNCallKeepHasActiveCall = 'RNCallKeepHasActiveCall';
const RNCallKeepDidFailCallAction = 'RNCallKeepDidFailCallAction';
const isIOS = Platform.OS === 'ios';

const didReceiveStartCallAction = handler => {
Expand Down Expand Up @@ -76,6 +77,9 @@ const didResetProvider = handler =>
const checkReachability = handler =>
eventEmitter.addListener(RNCallKeepCheckReachability, handler);

const didFailCallAction = handler =>
eventEmitter.addListener(RNCallKeepDidFailCallAction, (data) => handler(data));

const didLoadWithEvents = handler =>
eventEmitter.addListener(RNCallKeepDidLoadWithEvents, handler);

Expand Down Expand Up @@ -107,5 +111,6 @@ export const listeners = {
silenceIncomingCall,
createIncomingConnectionFailed,
didChangeAudioRoute,
onHasActiveCall
onHasActiveCall,
didFailCallAction
};
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare module 'react-native-callkeep' {
didResetProvider: 'RNCallKeepProviderReset';
didLoadWithEvents: 'RNCallKeepDidLoadWithEvents';
onHasActiveCall : 'onHasActiveCall';
didFailCallAction: 'RNCallKeepDidFailCallAction';
}

export type InitialEvents = Array<{
Expand Down Expand Up @@ -56,6 +57,12 @@ declare module 'react-native-callkeep' {
didResetProvider: undefined;
didLoadWithEvents: InitialEvents;
onHasActiveCall : undefined;
didFailCallAction: {
callUUID: string,
action: 'startCall' | 'answerCall' | 'endCall' | 'setHeld' | 'setMuted' | 'playDTMF' | 'unknown',
errorCode: 'Unentitled' | 'UnknownCallProvider' | 'EmptyTransaction' | 'UnknownCallUUID' | 'CallUUIDAlreadyExists' | 'InvalidAction' | 'MaximumCallGroupsReached' | 'CallIsProtected' | 'Unknown',
error: string,
};
}

type HandleType = 'generic' | 'number' | 'email';
Expand Down
58 changes: 57 additions & 1 deletion ios/RNCallKeep/RNCallKeep.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
static NSString *const RNCallKeepCheckReachability = @"RNCallKeepCheckReachability";
static NSString *const RNCallKeepDidChangeAudioRoute = @"RNCallKeepDidChangeAudioRoute";
static NSString *const RNCallKeepDidLoadWithEvents = @"RNCallKeepDidLoadWithEvents";
static NSString *const RNCallKeepDidFailCallAction = @"RNCallKeepDidFailCallAction";

@implementation RNCallKeep
{
Expand Down Expand Up @@ -115,7 +116,8 @@ - (void)dealloc
RNCallKeepProviderReset,
RNCallKeepCheckReachability,
RNCallKeepDidLoadWithEvents,
RNCallKeepDidChangeAudioRoute
RNCallKeepDidChangeAudioRoute,
RNCallKeepDidFailCallAction
];
}

Expand Down Expand Up @@ -675,6 +677,17 @@ - (void)requestTransaction:(CXTransaction *)transaction
[self.callKeepCallController requestTransaction:transaction completion:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"[RNCallKeep][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error);
// Without this event JavaScript never learns that CallKit refused the
// action: a refused startCall (e.g. maximumCallGroups reached) leaves
// the app holding a call that CallKit does not know about.
CXAction *action = [transaction.actions firstObject];
NSUUID *callUUID = [action isKindOfClass:[CXCallAction class]] ? ((CXCallAction *)action).callUUID : nil;
[self sendEventWithNameWrapper:RNCallKeepDidFailCallAction body:@{
@"callUUID": callUUID ? [callUUID.UUIDString lowercaseString] : @"",
@"action": [self getCallActionName:action],
@"errorCode": [self getRequestTransactionErrorCode:error],
@"error": error.localizedDescription ? error.localizedDescription : @"",
}];
} else {
NSLog(@"[RNCallKeep][requestTransaction] Requested transaction successfully");

Expand Down Expand Up @@ -815,6 +828,49 @@ + (void)reportNewIncomingCall:(NSString *)uuidString
}];
}

- (NSString *)getCallActionName:(CXAction *)action {
if ([action isKindOfClass:[CXStartCallAction class]]) {
return @"startCall";
} else if ([action isKindOfClass:[CXAnswerCallAction class]]) {
return @"answerCall";
} else if ([action isKindOfClass:[CXEndCallAction class]]) {
return @"endCall";
} else if ([action isKindOfClass:[CXSetHeldCallAction class]]) {
return @"setHeld";
} else if ([action isKindOfClass:[CXSetMutedCallAction class]]) {
return @"setMuted";
} else if ([action isKindOfClass:[CXPlayDTMFCallAction class]]) {
return @"playDTMF";
}
return @"unknown";
}

- (NSString *)getRequestTransactionErrorCode:(NSError *)error {
if (![[error domain] isEqualToString:CXErrorDomainRequestTransaction]) {
return @"Unknown";
}
switch ([error code]) {
case CXErrorCodeRequestTransactionErrorUnentitled:
return @"Unentitled";
case CXErrorCodeRequestTransactionErrorUnknownCallProvider:
return @"UnknownCallProvider";
case CXErrorCodeRequestTransactionErrorEmptyTransaction:
return @"EmptyTransaction";
case CXErrorCodeRequestTransactionErrorUnknownCallUUID:
return @"UnknownCallUUID";
case CXErrorCodeRequestTransactionErrorCallUUIDAlreadyExists:
return @"CallUUIDAlreadyExists";
case CXErrorCodeRequestTransactionErrorInvalidAction:
return @"InvalidAction";
case CXErrorCodeRequestTransactionErrorMaximumCallGroupsReached:
return @"MaximumCallGroupsReached";
case 8: // CXErrorCodeRequestTransactionErrorCallIsProtected; a literal so older SDKs still build
return @"CallIsProtected";
default:
return @"Unknown";
}
}

- (NSString *)getIncomingCallErrorCode:(NSError *)error {
if ([error code] == CXErrorCodeIncomingCallErrorUnentitled) {
return @"Unentitled";
Expand Down