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
22 changes: 16 additions & 6 deletions lib/livekit/connector_service_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "livekit/auth_mixin"
require 'livekit/utils'
require 'livekit/failover'
require 'livekit/dial_timeout'

module LiveKit
# Client for LiveKit's Connector service, bridging WhatsApp and Twilio calls
Expand Down Expand Up @@ -33,13 +34,22 @@ def dial_whatsapp_call(request)

# Accepts an inbound WhatsApp call.
# @param request [Proto::AcceptWhatsAppCallRequest]
# @param timeout [Numeric, nil] optional request timeout in seconds. When the
# request waits for an answer, it defaults to a longer value (dialing takes
# time) and is raised, if needed, to stay above the request's ringing_timeout.
# @return [Proto::AcceptWhatsAppCallResponse]
def accept_whatsapp_call(request)
self.rpc(
:AcceptWhatsAppCall,
request,
headers: auth_header(video_grant: VideoGrant.new(roomCreate: true)),
)
def accept_whatsapp_call(request, timeout: nil)
headers = auth_header(video_grant: VideoGrant.new(roomCreate: true))
# Accept can block until the call is answered, so default the request timeout
# to the standard ring window; otherwise honor any user timeout. The caller
# overrides via +timeout+ and should set it above the ringing_timeout passed
# to dial_whatsapp_call (the two calls are separate).
if request.wait_until_answered
headers[Failover::TIMEOUT_HEADER] = (timeout || DialTimeout::DEFAULT_RINGING_TIMEOUT).to_s
elsif timeout
headers[Failover::TIMEOUT_HEADER] = timeout.to_s
end
self.rpc(:AcceptWhatsAppCall, request, headers: headers)
end

# Connects an established WhatsApp call (used for business-initiated calls).
Expand Down
26 changes: 26 additions & 0 deletions lib/livekit/dial_timeout.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module LiveKit
# Request-timeout handling shared by calls that dial a phone and wait for an
# answer (SIP CreateSIPParticipant/TransferSIPParticipant, WhatsApp
# AcceptWhatsAppCall). These take longer than a normal request, and the request
# must outlast ringing or it would abort before the call can be answered.
module DialTimeout
# Ring window (seconds) assumed when a request doesn't set a ringing timeout;
# matches the server default. A dialing request must outlast it.
DEFAULT_RINGING_TIMEOUT = 30
# Keep the request timeout at least this many seconds above the ringing
# timeout, so the request doesn't abort before the call can be answered.
RINGING_TIMEOUT_MARGIN = 2

# Request timeout (seconds): the ring window plus a margin, so the request
# doesn't abort before the call can be answered. The ring window is
# +ringing_timeout+ (seconds) when set, else DEFAULT_RINGING_TIMEOUT. A longer
# user-supplied +timeout+ is honored; a shorter one is raised to the floor.
def self.resolve(timeout, ringing_timeout)
ring = ringing_timeout || DEFAULT_RINGING_TIMEOUT
floor = ring + RINGING_TIMEOUT_MARGIN
[timeout || floor, floor].max
end
end
end
33 changes: 23 additions & 10 deletions lib/livekit/sip_service_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
require "livekit/auth_mixin"
require 'livekit/utils'
require 'livekit/failover'
require 'livekit/dial_timeout'

module LiveKit
class SIPServiceClient < Twirp::Client
client_for Proto::SIPService
include AuthMixin
attr_accessor :api_key, :api_secret

# Calls that dial a phone (CreateSIPParticipant with wait_until_answered,
# TransferSIPParticipant) take longer than a normal request.
SIP_DIAL_TIMEOUT = 30

def initialize(base_url, api_key: nil, api_secret: nil, failover: true)
super(LiveKit::Failover.connection(base_url, failover))
@api_key = api_key
Expand Down Expand Up @@ -205,8 +202,14 @@ def create_sip_participant(
# Optional, enable Krisp for this call
krisp_enabled: false,
# Optional, wait for the call to be answered before returning
wait_until_answered: false
wait_until_answered: false,
# Optional, request timeout in seconds. Defaults to a longer value when
# wait_until_answered is set (dialing takes time).
timeout: nil
)
# When waiting for an answer, pin the ring window explicitly so our request
# timeout doesn't depend on the server's default (which could change).
ringing_timeout = DialTimeout::DEFAULT_RINGING_TIMEOUT if wait_until_answered && ringing_timeout.nil?
request = Proto::CreateSIPParticipantRequest.new(
sip_trunk_id: sip_trunk_id,
sip_call_to: sip_call_to,
Expand All @@ -224,27 +227,37 @@ def create_sip_participant(
wait_until_answered: wait_until_answered
)
headers = auth_header(sip_grant: SIPGrant.new(call: true))
# Dialing a phone and waiting for an answer takes longer than a normal request.
headers[Failover::TIMEOUT_HEADER] = SIP_DIAL_TIMEOUT.to_s if wait_until_answered
# When waiting for an answer, dialing takes longer than a normal request
# and the request must outlast ringing; otherwise honor any user timeout.
effective_timeout = wait_until_answered ? DialTimeout.resolve(timeout, ringing_timeout) : timeout
headers[Failover::TIMEOUT_HEADER] = effective_timeout.to_s if effective_timeout
self.rpc(:CreateSIPParticipant, request, headers: headers)
end

def transfer_sip_participant(
room_name,
participant_identity,
transfer_to,
play_dialtone: nil
play_dialtone: nil,
# Optional, max time for the transfer destination to answer, in seconds.
ringing_timeout: nil,
# Optional, request timeout in seconds. Defaults to a longer value since
# transferring dials a phone.
timeout: nil
)

# Transferring a call dials a phone and must outlast ringing. Pin the ring
# window explicitly so our request timeout doesn't depend on the server default.
ringing_timeout ||= DialTimeout::DEFAULT_RINGING_TIMEOUT
request = Proto::TransferSIPParticipantRequest.new(
room_name: room_name,
participant_identity: participant_identity,
transfer_to: transfer_to,
play_dialtone: play_dialtone,
ringing_timeout: ringing_timeout,
)
headers = auth_header(video_grant: VideoGrant.new(roomAdmin: true, room: room_name), sip_grant: SIPGrant.new(call: true))
# Transferring a call dials a phone, which takes longer than a normal request.
headers[Failover::TIMEOUT_HEADER] = SIP_DIAL_TIMEOUT.to_s
headers[Failover::TIMEOUT_HEADER] = DialTimeout.resolve(timeout, ringing_timeout).to_s
self.rpc(:TransferSIPParticipant, request, headers: headers)
end

Expand Down
Loading