diff --git a/package.json b/package.json index 29c53c0..ae7a2f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-callkit", - "version": "2.2.2", + "version": "2.2.3", "description": "Cordova plugin that lets you use iOS CallKit UI (with PushKit) and Android ConnectionService UI", "cordova": { "id": "cordova-plugin-callkit", diff --git a/plugin.xml b/plugin.xml index ab0e2f1..5417b40 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + Cordova CallKit diff --git a/src/android/CordovaCall.java b/src/android/CordovaCall.java index 083694c..250a8fc 100644 --- a/src/android/CordovaCall.java +++ b/src/android/CordovaCall.java @@ -310,6 +310,9 @@ public void run() { this.unmute(); this.callbackContext.success("Unmuted Successfully"); return true; + } else if (action.equals("setAllowUnmute")) { + this.callbackContext.error("setAllowUnmute is not supported on Android"); + return true; } else if (action.equals("speakerOn")) { this.speakerOn(); return true; diff --git a/src/ios/CordovaCall.h b/src/ios/CordovaCall.h index a0b76ee..c9461f3 100644 --- a/src/ios/CordovaCall.h +++ b/src/ios/CordovaCall.h @@ -26,6 +26,7 @@ - (void)setRingtone:(CDVInvokedUrlCommand*)command; - (void)setIncludeInRecents:(CDVInvokedUrlCommand*)command; - (void)setDTMFState:(CDVInvokedUrlCommand*)command; +- (void)setAllowUnmute:(CDVInvokedUrlCommand*)command; - (void)setVideo:(CDVInvokedUrlCommand*)command; - (void)receiveCall:(CDVInvokedUrlCommand*)command; diff --git a/src/ios/CordovaCall.m b/src/ios/CordovaCall.m index 053b5e1..ae49d4b 100644 --- a/src/ios/CordovaCall.m +++ b/src/ios/CordovaCall.m @@ -289,6 +289,20 @@ - (void)setDTMFState:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)setAllowUnmute:(CDVInvokedUrlCommand*)command +{ + NSString *sessionId = [command.arguments objectAtIndex:0]; + BOOL value = [[command.arguments objectAtIndex:1] boolValue]; + CDVPluginResult *pluginResult = nil; + if (self.activeCalls[sessionId]) { + self.activeCalls[sessionId][@"allowUnmute"] = @(value); + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"allowUnmute Changed Successfully"]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No active call for sessionId"]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (void)setVideo:(CDVInvokedUrlCommand*)command { CDVPluginResult* pluginResult = nil; @@ -512,10 +526,13 @@ - (void)mute:(CDVInvokedUrlCommand*)command CXSetMutedCallAction *muteAction = [[CXSetMutedCallAction alloc] initWithCallUUID:call.UUID muted:YES]; CXTransaction *transaction = [[CXTransaction alloc] initWithAction:muteAction]; [self logMessage:[NSString stringWithFormat:@"Programmatically Muting Call: %@", sessionId]]; + // Pre-populate callbackMap before requestTransaction: performSetMutedCallAction fires + // before the requestTransaction completion block, so the entry must already be present. + self.activeCalls[sessionId][@"callbackMap"][muteAction.UUID.UUIDString] = [@{ @"callbackId": command.callbackId, @"event": @"mute" } mutableCopy]; [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) { - if (error == nil) { - self.activeCalls[sessionId][@"callbackMap"][muteAction.UUID.UUIDString] = [@{ @"callbackId": command.callbackId, @"event": @"mute" } mutableCopy]; - } else { + if (error != nil) { + // Transaction was rejected before reaching performSetMutedCallAction — clean up. + [self.activeCalls[sessionId][@"callbackMap"] removeObjectForKey:muteAction.UUID.UUIDString]; [self logMessage:@"Error occurred muting Call"]; NSDictionary *resultDict = @{ @"message": @"An error occurred", @"sessionId": sessionId }; CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:resultDict]; @@ -538,10 +555,13 @@ - (void)unmute:(CDVInvokedUrlCommand*)command CXSetMutedCallAction *unmuteAction = [[CXSetMutedCallAction alloc] initWithCallUUID:call.UUID muted:NO]; CXTransaction *transaction = [[CXTransaction alloc] initWithAction:unmuteAction]; [self logMessage:[NSString stringWithFormat:@"Programmatically Unmuting Call: %@", sessionId]]; + // Pre-populate callbackMap before requestTransaction: performSetMutedCallAction fires + // before the requestTransaction completion block, so the entry must already be present. + self.activeCalls[sessionId][@"callbackMap"][unmuteAction.UUID.UUIDString] = [@{ @"callbackId": command.callbackId, @"event": @"unmute" } mutableCopy]; [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) { - if (error == nil) { - self.activeCalls[sessionId][@"callbackMap"][unmuteAction.UUID.UUIDString] = [@{ @"callbackId": command.callbackId, @"event": @"unmute" } mutableCopy]; - } else { + if (error != nil) { + // Transaction was rejected before reaching performSetMutedCallAction — clean up. + [self.activeCalls[sessionId][@"callbackMap"] removeObjectForKey:unmuteAction.UUID.UUIDString]; [self logMessage:@"Error occurred unmuting Call"]; NSDictionary *resultDict = @{ @"message": @"An error occurred", @"sessionId": sessionId }; CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:resultDict]; @@ -1113,12 +1133,24 @@ - (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCal } [self logMessage:[NSString stringWithFormat:@"CallKit performSetMutedCallAction received %@ event, sessionId: %@", isMuted ? @"mute" : @"unmute", sessionId]]; - [action fulfill]; - if (self.activeCalls[sessionId][@"callbackMap"][action.UUID.UUIDString]) { + [action fulfill]; + // Programmatic mute/unmute: resolve the JS promise only. [self resolveCommandForSessionId:sessionId actionUUIDString:action.UUID.UUIDString]; + return; + } + + BOOL allowUnmute = [self.activeCalls[sessionId][@"allowUnmute"] boolValue]; + if (!isMuted && !allowUnmute) { + // If this is an unmute request and allowUnmute is NO for this session, fail the action. + [action fail]; + + [self logMessage:@"performSetMutedCallAction: allowUnmute is NO, reject unmute action"]; + return; } else { + [action fulfill]; + // UI-initiated mute/unmute: emit to event listeners only. for (id callbackId in callbackIds[isMuted ? @"mute" : @"unmute"]) { [self logMessage:[NSString stringWithFormat:@"Sending %@ event to JS", isMuted ? @"mute" : @"unmute"]]; @@ -1224,7 +1256,8 @@ - (NSMutableDictionary *)newActiveCallEntryWithUUID:(NSUUID *)callUUID { @"callbackMap": [NSMutableDictionary dictionary], @"pendingActivateAudioSessionEmits": [NSMutableArray array], @"pendingDeactivateAudioSessionEmits": [NSMutableArray array], - @"pendingDismiss": @NO + @"pendingDismiss": @NO, + @"allowUnmute": @YES } mutableCopy]; } diff --git a/www/CordovaCall.js b/www/CordovaCall.js index 6ecbb67..9faba10 100644 --- a/www/CordovaCall.js +++ b/www/CordovaCall.js @@ -32,7 +32,7 @@ exports.setIncludeInRecents = function (value, success, error) { if (typeof value == "boolean") { exec(success, error, "CordovaCall", "setIncludeInRecents", [value]); } else { - error("Value Must Be True Or False"); + if (typeof error == "function") { error("Value Must Be True Or False"); } } }; @@ -40,7 +40,7 @@ exports.setDTMFState = function (value, success, error) { if (typeof value == "boolean") { exec(success, error, "CordovaCall", "setDTMFState", [value]); } else { - error("Value Must Be True Or False"); + if (typeof error == "function") { error("Value Must Be True Or False"); } } }; @@ -48,7 +48,7 @@ exports.setVideo = function (value, success, error) { if (typeof value == "boolean") { exec(success, error, "CordovaCall", "setVideo", [value]); } else { - error("Value Must Be True Or False"); + if (typeof error == "function") { error("Value Must Be True Or False"); } } }; @@ -154,6 +154,14 @@ exports.log = function (message) { exec(null, null, "CordovaCall", "log", [message]); } +exports.setAllowUnmute = function (sessionId, value, success, error) { + if (typeof value == "boolean") { + exec(success, error, "CordovaCall", "setAllowUnmute", [sessionId, value]); + } else { + if (typeof error == "function") { error("Value Must Be True Or False"); } + } +}; + exports.keepAlive = function (callback) { exec(callback, null, "CordovaCall", "keepAlive", []); }