Skip to content
Merged
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# NOTE
Master branch is not the latest - check "trusted-care-master" for newest code.
Basically master had some PushWoosh stuff for Android from the guy that i forked, and there were naming conflicts for Android on the PushNotification plugin that we are using - https://github.com/phonegap/phonegap-plugin-push, therefore i reverted to the original creator last commit and made changes on top of that in the new branch.
https://github.com/AleksandarTokarev/cordova-plugin-callkit/tree/trusted-care-master

Regarding Android Push Notifications, we have modified this plugin and you can use this branch from this repo here to achieve Calling
https://github.com/AleksandarTokarev/phonegap-plugin-push/tree/android-native-caller

# cordova-plugin-callkit
Cordova plugin that enables CallKit + PushKit (iOS) & ConnectionService (Android) functionality to display native UI.

Expand Down
1 change: 1 addition & 0 deletions src/ios/CordovaCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@property (nonatomic, copy) NSString *VoIPPushCallbackId;
@property (nonatomic, copy) NSString *VoIPPushClassName;
@property (nonatomic, copy) NSString *VoIPPushMethodName;
@property (nonatomic, copy) NSString *VoIPPushToken;

- (void)init:(CDVInvokedUrlCommand*)command;

Expand Down
227 changes: 172 additions & 55 deletions src/ios/CordovaCall.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ @implementation CordovaCall

@synthesize VoIPPushCallbackId, VoIPPushClassName, VoIPPushMethodName;

BOOL hasVideo = NO;
BOOL hasVideo = YES;
NSString* appName;
NSString* ringtone;
NSString* icon;
BOOL includeInRecents = NO;
NSMutableDictionary *callbackIds;
NSMutableDictionary<NSString*, NSMutableArray*> *callbackIds;
NSDictionary* pendingCallFromRecents;
BOOL monitorAudioRouteChange = NO;
BOOL enableDTMF = NO;
PKPushRegistry *_voipRegistry;

BOOL isCancelPush = NO;
NSString* callBackUrl;
NSString* callId;

NSMutableArray* pendingCallResponses;
NSString* const PENDING_RESPONSE_ANSWER = @"pendingResponseAnswer";
NSString* const PENDING_RESPONSE_REJECT = @"pendingResponseReject";

NSString* const KEY_VOIP_PUSH_TOKEN = @"PK_deviceToken";

- (void)pluginInitialize
{
Expand Down Expand Up @@ -45,10 +56,27 @@ - (void)pluginInitialize
[callbackIds setObject:[NSMutableArray array] forKey:@"speakerOn"];
[callbackIds setObject:[NSMutableArray array] forKey:@"speakerOff"];
[callbackIds setObject:[NSMutableArray array] forKey:@"DTMF"];

// Add call response (answer or reject) to pending if event listeners are not added at the time of responding
pendingCallResponses = [NSMutableArray new];

//allows user to make call from recents
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveCallFromRecents:) name:@"RecentsCallNotification" object:nil];
//detect Audio Route Changes to make speakerOn and speakerOff event handlers
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];

// Initialize PKPushRegistry
//http://stackoverflow.com/questions/27245808/implement-pushkit-and-test-in-development-behavior/28562124#28562124
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// Create a push registry object
_voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue];
// Set the registry's delegate to self
[_voipRegistry setDelegate:(id<PKPushRegistryDelegate> _Nullable)self];
// Set the push type to VoIP
_voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];

// Read VoIPPushToken from UserDefaults
self.VoIPPushToken = [[NSUserDefaults standardUserDefaults] stringForKey:KEY_VOIP_PUSH_TOKEN];
}

// CallKit - Interface
Expand Down Expand Up @@ -173,7 +201,6 @@ - (void)setVideo:(CDVInvokedUrlCommand*)command
- (void)receiveCall:(CDVInvokedUrlCommand*)command
{
BOOL hasId = ![[command.arguments objectAtIndex:1] isEqual:[NSNull null]];
CDVPluginResult* pluginResult = nil;
NSString* callName = [command.arguments objectAtIndex:0];
NSString* callId = hasId?[command.arguments objectAtIndex:1]:callName;
NSUUID *callUUID = [[NSUUID alloc] init];
Expand All @@ -193,19 +220,26 @@ - (void)receiveCall:(CDVInvokedUrlCommand*)command
callUpdate.supportsUngrouping = NO;
callUpdate.supportsHolding = NO;
callUpdate.supportsDTMF = enableDTMF;

[self.provider reportNewIncomingCallWithUUID:callUUID update:callUpdate completion:^(NSError * _Nullable error) {
if(error == nil) {
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Incoming call successful"] callbackId:command.callbackId];
} else {
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]] callbackId:command.callbackId];
if (!isCancelPush) {
[self.provider reportNewIncomingCallWithUUID:callUUID update:callUpdate completion:^(NSError * _Nullable error) {
if(error == nil) {
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Incoming call successful"] callbackId:command.callbackId];
} else {
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]] callbackId:command.callbackId];
}
}];
for (id callbackId in callbackIds[@"receiveCall"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"receiveCall event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
}];
for (id callbackId in callbackIds[@"receiveCall"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"receiveCall event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
} else {
NSArray<CXCall *> *calls = self.callController.callObserver.calls;
if([calls count] == 1) {
[self.provider reportCallWithUUID:calls[0].UUID endedAtDate:nil reason:CXCallEndedReasonRemoteEnded];
}

}
} else {
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Caller id can't be empty"] callbackId:command.callbackId];
Expand Down Expand Up @@ -263,7 +297,6 @@ - (void)endCall:(CDVInvokedUrlCommand*)command
NSArray<CXCall *> *calls = self.callController.callObserver.calls;

if([calls count] == 1) {
//[self.provider reportCallWithUUID:calls[0].UUID endedAtDate:nil reason:CXCallEndedReasonRemoteEnded];
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:calls[0].UUID];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];
[self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
Expand Down Expand Up @@ -293,6 +326,16 @@ - (void)registerEvent:(CDVInvokedUrlCommand*)command
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

// In case of registerEvent answer or reject called after responding to call, trigger cordova event for the appropriate answer
if ([eventName isEqualToString:@"answer"] && [pendingCallResponses containsObject:PENDING_RESPONSE_ANSWER]) {
[self triggerCordovaEventForCallResponse:@"answer"];
[pendingCallResponses removeObject:PENDING_RESPONSE_ANSWER];
}
if ([eventName isEqualToString:@"reject"] && [pendingCallResponses containsObject:PENDING_RESPONSE_REJECT]) {
[self triggerCordovaEventForCallResponse:@"reject"];
[pendingCallResponses removeObject:PENDING_RESPONSE_REJECT];
}
}

- (void)mute:(CDVInvokedUrlCommand*)command
Expand Down Expand Up @@ -477,11 +520,22 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct
{
[self setupAudioSession];
[action fulfill];
for (id callbackId in callbackIds[@"answer"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"answer event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];

// Notify Webhook that Native Call has been Answered
NSURL *statusUpdateUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@?id=%@&input=%@", callBackUrl, callId, @"pickup"]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:statusUpdateUrl
completionHandler:^(NSData *statusUpdateData,
NSURLResponse *statusUpdateResponse,
NSError *statusUpdateError) {
// handle response
}] resume];

if ([callbackIds[@"answer"] count] == 0) {
// callbackId for event not registered, add to pending to trigger on registration
[pendingCallResponses addObject:PENDING_RESPONSE_ANSWER];
} else {
[self triggerCordovaEventForCallResponse:@"answer"];
}
//[action fail];
}
Expand All @@ -498,11 +552,23 @@ - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
} else {
for (id callbackId in callbackIds[@"reject"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"reject event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
// Notify Webhook that Native Call has been Declined
if (!isCancelPush) {
NSURL *statusUpdateUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@?id=%@&input=%@", callBackUrl, callId, @"declined_callee"]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:statusUpdateUrl
completionHandler:^(NSData *statusUpdateData,
NSURLResponse *statusUpdateResponse,
NSError *statusUpdateError) {
// handle response
}] resume];
}

if ([callbackIds[@"reject"] count] == 0) {
// callbackId for event not registered, add to pending to trigger on registration
[pendingCallResponses addObject:PENDING_RESPONSE_REJECT];
} else {
[self triggerCordovaEventForCallResponse:@"reject"];
}
}
}
Expand All @@ -511,6 +577,24 @@ - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)
//[action fail];
}

- (void)triggerCordovaEventForCallResponse:(NSString*) response {
if ([response isEqualToString:@"answer"]) {
for (id callbackId in callbackIds[@"answer"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"answer event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
} else if ([response isEqualToString:@"reject"]) {
for (id callbackId in callbackIds[@"reject"]) {
CDVPluginResult* pluginResult = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"reject event called successfully"];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
}
}

- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action
{
[action fulfill];
Expand All @@ -536,20 +620,31 @@ - (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCal
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
}

// PushKit
- (void)init:(CDVInvokedUrlCommand*)command
{
self.VoIPPushCallbackId = command.callbackId;
NSLog(@"[objC] callbackId: %@", self.VoIPPushCallbackId);
self.VoIPPushCallbackId = command.callbackId;
NSLog(@"[objC] callbackId: %@", self.VoIPPushCallbackId);

[self sendTokenPluginResult];
}

- (void)sendTokenPluginResult {
if (!self.VoIPPushCallbackId || !self.VoIPPushToken) {
return;
}

//http://stackoverflow.com/questions/27245808/implement-pushkit-and-test-in-development-behavior/28562124#28562124
PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
NSMutableDictionary* results = [NSMutableDictionary dictionaryWithCapacity:2];
[results setObject:self.VoIPPushToken forKey:@"deviceToken"];
[results setObject:@"true" forKey:@"registration"];

CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
}

- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
#define PushKit Delegate Methods
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type{
if([credentials.token length] == 0) {
NSLog(@"[objC] No device token!");
return;
Expand All @@ -558,54 +653,76 @@ - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPush
//http://stackoverflow.com/a/9372848/534755
NSLog(@"[objC] Device token: %@", credentials.token);
const unsigned *tokenBytes = [credentials.token bytes];
NSString *sToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
self.VoIPPushToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

NSMutableDictionary* results = [NSMutableDictionary dictionaryWithCapacity:2];
[results setObject:sToken forKey:@"deviceToken"];
[results setObject:@"true" forKey:@"registration"];

CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; //[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
// Store VoIPPushToken in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:self.VoIPPushToken forKey:KEY_VOIP_PUSH_TOKEN];

[self sendTokenPluginResult];
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion
{
NSDictionary *payloadDict = payload.dictionaryPayload[@"aps"];
NSLog(@"[objC] didReceiveIncomingPushWithPayload: %@", payloadDict);

NSString *message = payloadDict[@"alert"];
NSLog(@"[objC] received VoIP message: %@", message);

NSString *data = payload.dictionaryPayload[@"data"];
NSDictionary *data = payload.dictionaryPayload[@"data"];
NSLog(@"[objC] received data: %@", data);

NSMutableDictionary* results = [NSMutableDictionary dictionaryWithCapacity:2];
[results setObject:message forKey:@"function"];
[results setObject:data forKey:@"extra"];
[results setObject:@"" forKey:@"extra"];

NSObject* caller = [data objectForKey:@"Caller"];
NSArray* args = [NSArray arrayWithObjects:[caller valueForKey:@"Username"], [caller valueForKey:@"ConnectionId"], nil];
CDVInvokedUrlCommand* newCommand = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"" className:self.VoIPPushClassName methodName:self.VoIPPushMethodName];

// Store URL and Call Id so they can be used for call Answer/Reject
callBackUrl = [caller valueForKey:@"CallbackUrl"];
callId = [caller valueForKey:@"ConnectionId"];
if ([[caller valueForKey:@"CancelPush"] isEqualToString:@"true"]) {
isCancelPush = YES;
} else {
isCancelPush = NO;
}
if (!isCancelPush) {
// Notify Webhook that VOIP Push Has been received and app is started
NSURL *statusUpdateUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@?id=%@&input=%@", callBackUrl, callId, @"connected"]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:statusUpdateUrl
completionHandler:^(NSData *statusUpdateData,
NSURLResponse *statusUpdateResponse,
NSError *statusUpdateError) {
// handle response
}] resume];
}

[self receiveCall:newCommand];
@try {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];

NSObject* caller = [json objectForKey:@"Caller"];
NSArray* args = [NSArray arrayWithObjects:[caller valueForKey:@"Username"], [caller valueForKey:@"ConnectionId"], nil];
NSError * err;
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&err];
NSString * dataString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[results setObject:dataString forKey:@"extra"];

CDVInvokedUrlCommand* newCommand = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"" className:self.VoIPPushClassName methodName:self.VoIPPushMethodName];

[self receiveCall:newCommand];
}
@catch (NSException *exception) {
NSLog(@"[objC] error: %@", exception.reason);
NSLog(@"[objC] error: %@", exception.reason);
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
return;
}
@finally {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
completion();
}
}

@end