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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 0.3.1

* Feat: Add raw `Connect({Map<String, dynamic>?})` for all platforms
* Refactor: [Web] Removed unused `web_callkit` event listeners.
* Fix: [Web] Check if call SID is present when call is disconnected (this occurs if the call ends abruptly after starting, and `params` does not contain `CallSid`).
* Fix: [iOS] unregister removes device push token preventing new access token registration (i.e. user 1 logs out, user 2 and more won't receive any calls). Thanks to [@VinceDollo](https://github.com/VinceDollo) & [@Erchil66](https://github.com/Erchil66) [Issue #273](https://github.com/cybex-dev/twilio_voice/issues/273)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,57 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
}
}

TVMethodChannels.CONNECT -> {
val args = call.arguments as? Map<*, *> ?: run {
result.error(
FlutterErrorCodes.MALFORMED_ARGUMENTS,
"Arguments should be a Map<*, *>",
null
)
return@onMethodCall
}

Log.d(TAG, "Making new call via connect")
logEvent("Making new call via connect")
val params = HashMap<String, String>()
for ((key, value) in args) {
when (key) {
Constants.PARAM_TO, Constants.PARAM_FROM -> {}
else -> {
params[key.toString()] = value.toString()
}
}
}
// callOutgoing = true
val from = call.argument<String>(Constants.PARAM_FROM) ?: run {
logEvent("No 'from' provided or invalid type, ignoring.")
""
}

val to = call.argument<String>(Constants.PARAM_TO) ?: run {
logEvent("No 'to' provided or invalid type, ignoring.")
""
}
val paramsStringify = JSONObject(args).toString()
Log.d(TAG, "calling with parameters: from: '$from' -> to: '$to', params: $paramsStringify")

accessToken?.let { token ->
context?.let { ctx ->
val success = placeCall(ctx, token, from, to, params, connect = true)
result.success(success)
} ?: run {
Log.e(TAG, "Context is null, cannot place call")
result.success(false)
}
} ?: run {
result.error(
FlutterErrorCodes.MALFORMED_ARGUMENTS,
"No accessToken set, are you registered?",
null
)
}
}

TVMethodChannels.REGISTER_CLIENT -> {

val clientId = call.argument<String>("id") ?: run {
Expand Down Expand Up @@ -1040,13 +1091,14 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
private fun placeCall(
ctx: Context,
accessToken: String,
from: String,
to: String,
params: Map<String, String>
from: String?,
to: String?,
params: Map<String, String>,
connect: Boolean = false
): Boolean {
assert(accessToken.isNotEmpty()) { "Twilio Access Token cannot be empty" }
assert(to.isNotEmpty()) { "To cannot be empty" }
assert(from.isNotEmpty()) { "From cannot be empty" }
assert(!connect && (to == null || to.isNotEmpty())) { "To cannot be empty" }
assert(!connect && (from == null || from.isNotEmpty())) { "From cannot be empty" }

telecomManager?.let { tm ->
if (!tm.hasCallCapableAccount(ctx, TVConnectionService::class.java.name)) {
Expand Down Expand Up @@ -1083,6 +1135,9 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
Intent(ctx, TVConnectionService::class.java).apply {
action = TVConnectionService.ACTION_PLACE_OUTGOING_CALL
putExtra(TVConnectionService.EXTRA_TOKEN, accessToken)
if(connect) {
putExtra(TVConnectionService.EXTRA_CONNECT_RAW, true)
}
putExtra(TVConnectionService.EXTRA_TO, to)
putExtra(TVConnectionService.EXTRA_FROM, from)
putExtra(TVConnectionService.EXTRA_OUTGOING_PARAMS, Bundle().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ class TVConnectionService : ConnectionService() {
*/
const val EXTRA_TOKEN: String = "EXTRA_TOKEN"

/**
* Extra used with [ACTION_PLACE_OUTGOING_CALL] to place an outgoing call connection, denotes the call parameters treated as a Bundle.
*/
const val EXTRA_CONNECT_RAW: String = "EXTRA_CONNECT_RAW"

/**
* Extra used with [ACTION_PLACE_OUTGOING_CALL] to place an outgoing call connection. Denotes the recipient's identity.
*/
Expand Down Expand Up @@ -334,40 +339,38 @@ class TVConnectionService : ConnectionService() {
}

ACTION_PLACE_OUTGOING_CALL -> {
// check required EXTRA_TOKEN, EXTRA_TO, EXTRA_FROM
val token = it.getStringExtra(EXTRA_TOKEN) ?: run {
Log.e(TAG, "onStartCommand: ACTION_PLACE_OUTGOING_CALL is missing String EXTRA_TOKEN")
return@let
}
val to = it.getStringExtra(EXTRA_TO) ?: run {
Log.e(TAG, "onStartCommand: ACTION_PLACE_OUTGOING_CALL is missing String EXTRA_TO")
return@let
}
val from = it.getStringExtra(EXTRA_FROM) ?: run {
Log.e(TAG, "onStartCommand: ACTION_PLACE_OUTGOING_CALL is missing String EXTRA_FROM")
return@let
}

// Get all params from bundle
val params = HashMap<String, String>()
val outGoingParams = it.getParcelableExtraSafe<Bundle>(EXTRA_OUTGOING_PARAMS)
outGoingParams?.keySet()?.forEach { key ->
outGoingParams.getString(key)?.let { value ->
params[key] = value
val rawConnect = it.getBooleanExtra(EXTRA_CONNECT_RAW, false)

fun getRequiredString(key: String, allowNullIfRaw: Boolean = false): String? {
val value = it.getStringExtra(key)
if (value == null) {
Log.e(TAG, "onStartCommand: ACTION_PLACE_OUTGOING_CALL is missing String $key")
if (!rawConnect || !allowNullIfRaw) return null
}
return value
}

// Add required params
params[EXTRA_FROM] = from
params[EXTRA_TO] = to
params[EXTRA_TOKEN] = token
val token = getRequiredString(EXTRA_TOKEN) ?: return@let
val to = getRequiredString(EXTRA_TO, allowNullIfRaw = true)
val from = getRequiredString(EXTRA_FROM, allowNullIfRaw = true)

val params = buildMap {
it.getParcelableExtraSafe<Bundle>(EXTRA_OUTGOING_PARAMS)?.let { bundle ->
for (key in bundle.keySet()) {
bundle.getString(key)?.let { value -> put(key, value) }
}
}
put(EXTRA_TOKEN, token)
if (!rawConnect) {
to?.let { v -> put(EXTRA_TO, v) }
from?.let { v -> put(EXTRA_FROM, v) }
}
}

// Create Twilio Param bundles
val myBundle = Bundle().apply {
putBundle(EXTRA_OUTGOING_PARAMS, Bundle().apply {
params.forEach { (key, value) ->
putString(key, value)
}
params.forEach { (key, value) -> putString(key, value) }
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ enum class TVMethodChannels(val method: String) {
IS_PHONE_ACCOUNT_ENABLED("isPhoneAccountEnabled"),
REJECT_CALL_ON_NO_PERMISSIONS("rejectCallOnNoPermissions"),
IS_REJECTING_CALL_ON_NO_PERMISSIONS("isRejectingCallOnNoPermissions"),
UPDATE_CALLKIT_ICON("updateCallKitIcon");
UPDATE_CALLKIT_ICON("updateCallKitIcon"),
CONNECT("connect");

companion object {
private val map = TVMethodChannels.values().associateBy(TVMethodChannels::method)
Expand Down
15 changes: 15 additions & 0 deletions ios/Classes/SwiftTwilioVoicePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ public class SwiftTwilioVoicePlugin: NSObject, FlutterPlugin, FlutterStreamHand
self.callTo = callTo
self.identity = callFrom
makeCall(to: callTo)
} else if flutterCall.method == "connect" {
guard let callTo = arguments["To"] as? String? else {
return
}
guard let callFrom = arguments["From"] as? String? else {
return
}
self.callArgs = arguments
self.callOutgoing = true
if let accessToken = arguments["accessToken"] as? String{
self.accessToken = accessToken
}
self.callTo = callTo ?? ""
self.identity = callFrom ?? ""
makeCall(to: self.callTo)
}
else if flutterCall.method == "toggleMute"
{
Expand Down
7 changes: 5 additions & 2 deletions lib/_internal/method_channel/twilio_call_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,12 @@ class MethodChannelTwilioCall extends TwilioCallPlatform {
return _channel.invokeMethod('isBluetoothOn', <String, dynamic>{});
}

/// Only web supported for now.
@override
Future<bool?> connect({Map<String, dynamic>? extraOptions}) {
return Future.value(false);
_activeCall = ActiveCall(from: "", to: "", callDirection: CallDirection.outgoing);
final options = {
...?extraOptions,
};
return _channel.invokeMethod('connect', options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,9 @@ abstract class TwilioCallPlatform extends SharedPlatformInterface {
/// [extraOptions] will be added to the callPayload sent to your server
Future<bool?> place({required String from, required String to, Map<String, dynamic>? extraOptions});

/// Place outgoing call with raw parameters. Returns true if successful, false otherwise.
/// Parameters send to Twilio's REST API endpoint 'makeCall' can be passed in [extraOptions];
/// Parameters are reduced to this format
/// <code>
/// {
/// ...extraOptions
/// }
/// </code>
/// [extraOptions] will be added to the call payload sent to your server
/// Places new call using raw parameters passed directly to Twilio's REST API endpoint 'makeCall'. Returns true if successful, false otherwise.
///
/// [extraOptions] will be added to the callPayload sent to your server
Future<bool?> connect({Map<String, dynamic>? extraOptions});

/// Hangs up active call
Expand Down
11 changes: 3 additions & 8 deletions lib/_internal/twilio_voice_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -786,14 +786,9 @@ class Call extends MethodChannelTwilioCall {
return true;
}

/// Place outgoing call with raw parameters. Returns true if successful, false otherwise.
/// Parameters send to Twilio's REST API endpoint 'makeCall' can be passed in [extraOptions];
/// Parameters are reduced to this format
/// <code>
/// {
/// ...extraOptions
/// }
/// </code>
/// Places new call using raw parameters passed directly to Twilio's REST API endpoint 'makeCall'. Returns true if successful, false otherwise.
///
/// [extraOptions] will be added to the callPayload sent to your server
/// See [twilio_js.Device.connect]
@override
Future<bool?> connect({Map<String, dynamic>? extraOptions}) async {
Expand Down
6 changes: 3 additions & 3 deletions macos/Classes/JsInterop/Device/TVDeviceConnectOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public class TVDeviceConnectOptions: JSONArgumentSerializer {
// TODO(cybex-dev) - add region, edge information, etc.
var params: [String: String] = [:]

init(to: String, from: String, customParameters: [String:Any]) {
params[Constants.PARAM_TO] = to
params[Constants.PARAM_FROM] = from
init(to: String?, from: String?, customParameters: [String:Any]) {
if(to != nil) params[Constants.PARAM_TO] = to
if(from != nil) params[Constants.PARAM_FROM] = from
let stringMap = customParameters.map({ (key, value) -> (String, String) in
(key, String(describing: value))
});
Expand Down
1 change: 1 addition & 0 deletions macos/Classes/TwilioVoiceChannelMethods.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Foundation
public enum TwilioVoiceChannelMethods: String {
case tokens = "tokens"
case makeCall = "makeCall"
case connect = "connect"
case toggleMute = "toggleMute"
case isMuted = "isMuted"
case toggleSpeaker = "toggleSpeaker"
Expand Down
22 changes: 20 additions & 2 deletions macos/Classes/TwilioVoicePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public class TwilioVoicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler, T
/// - to: recipient
/// - extraOptions: extra options
/// - completionHandler: completion handler -> (Bool?)
private func place(from: String, to: String, extraOptions: [String: Any]?, completionHandler: @escaping OnCompletionValueHandler<Bool>) -> Void {
private func place(from: String?, to: String?, extraOptions: [String: Any]?, completionHandler: @escaping OnCompletionValueHandler<Bool>) -> Void {
assert(from.isNotEmpty(), "\(Constants.PARAM_FROM) cannot be empty")
assert(to.isNotEmpty(), "\(Constants.PARAM_TO) cannot be empty")
// assert(extraOptions?.keys.contains(Constants.PARAM_FROM) ?? true, "\(Constants.PARAM_FROM) cannot be passed in extraOptions")
Expand All @@ -218,7 +218,10 @@ public class TwilioVoicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler, T

logEvent(description: "Making new call")

var params: [String: Any] = [Constants.PARAM_FROM: from, Constants.PARAM_TO: to]
var params: [String: Any] = [
if(from != nil) Constants.PARAM_FROM: from,
if(to != nil) Constants.PARAM_TO: to,
]
if let extraOptions = extraOptions {
params.merge(extraOptions) { (_, new) in
new
Expand Down Expand Up @@ -591,6 +594,21 @@ public class TwilioVoicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler, T
}
}

place(from: from, to: to, extraOptions: params) { success in
result(success ?? false)
}
break
case .connect:
guard let to = arguments[Constants.PARAM_TO] as? String?
guard let from = arguments[Constants.PARAM_FROM] as? String

var params: [String: Any] = [:]
arguments.forEach { (key, value) in
if key != Constants.PARAM_TO && key != Constants.PARAM_FROM {
params[key] = value
}
}

place(from: from, to: to, extraOptions: params) { success in
result(success ?? false)
}
Expand Down