chore: clean up twilio tokens controller
This commit is contained in:
@@ -1,136 +1,80 @@
|
||||
require 'twilio-ruby'
|
||||
|
||||
class Api::V1::Accounts::Voice::TokensController < Api::V1::Accounts::BaseController
|
||||
before_action :set_voice_inbox
|
||||
|
||||
def create
|
||||
# 1. Find inbox
|
||||
inbox = Current.account.inboxes.find_by(id: params[:inbox_id])
|
||||
|
||||
unless inbox
|
||||
render json: { error: 'Inbox not found' }, status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
# 2. Get Twilio credentials from channel
|
||||
channel = inbox.channel
|
||||
config = channel.provider_config_hash || {}
|
||||
|
||||
# Get Twilio credentials from channel config
|
||||
account_sid = config['account_sid']
|
||||
auth_token = config['auth_token']
|
||||
api_key_sid = config['api_key_sid']
|
||||
api_key_secret = config['api_key_secret']
|
||||
phone_number = channel.phone_number
|
||||
|
||||
# 3. Create a unique client identifier
|
||||
client_identity = "agent-#{current_user.id}-#{Current.account.id}"
|
||||
render json: build_response
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Voice::TokensController#create: #{e.class} - #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
||||
render json: { error: 'Failed to generate token', details: e.message }, status: :internal_server_error
|
||||
end
|
||||
|
||||
# 4. Generate Twilio Access Token
|
||||
begin
|
||||
# Simple log for debugging
|
||||
Rails.logger.debug "Generating Twilio token for identity: #{client_identity} using API key: #{api_key_sid}"
|
||||
|
||||
# Create Twilio JWT AccessToken with API key credentials
|
||||
token = Twilio::JWT::AccessToken.new(
|
||||
account_sid,
|
||||
api_key_sid,
|
||||
api_key_secret,
|
||||
identity: client_identity,
|
||||
ttl: 3600
|
||||
)
|
||||
|
||||
# Create Voice grant for the token
|
||||
voice_grant = Twilio::JWT::AccessToken::VoiceGrant.new
|
||||
|
||||
# 1. Always enable incoming calls
|
||||
voice_grant.incoming_allow = true
|
||||
|
||||
# 2. CRITICAL: For outgoing WebRTC calls, we need an application SID (TwiML App SID)
|
||||
# This is required to fix error 31002: "Token does not allow outgoing calls"
|
||||
outgoing_application_sid = config['outgoing_application_sid']
|
||||
|
||||
# For WebRTC calls with Twilio Voice SDK, the outgoing_application_sid is mandatory
|
||||
# Without it, browser-based calling won't work properly
|
||||
|
||||
if outgoing_application_sid.present?
|
||||
# We have a configured TwiML App SID - use it
|
||||
voice_grant.outgoing_application_sid = outgoing_application_sid
|
||||
|
||||
# Set additional parameters that will be passed to the TwiML app
|
||||
# CRITICAL: Include is_agent=true to help identify agent connections on the server side
|
||||
voice_grant.outgoing_application_params = {
|
||||
'account_id' => Current.account.id.to_s,
|
||||
'agent_id' => current_user.id.to_s,
|
||||
'identity' => client_identity,
|
||||
'client_name' => client_identity,
|
||||
'accountSid' => account_sid,
|
||||
'is_agent' => 'true' # CRITICAL: Identify agent connections
|
||||
}
|
||||
|
||||
Rails.logger.info("Using configured TwiML App SID: #{outgoing_application_sid}")
|
||||
else
|
||||
# Log a clear error message - TwiML App SID is essential for browser-based calling
|
||||
twiml_url = "#{ENV.fetch('FRONTEND_URL', '')}/api/v1/accounts/#{Current.account.id}/voice/twiml_for_client"
|
||||
|
||||
# Return detailed instructions for setting up TwiML App
|
||||
setup_instructions = <<~INSTRUCTIONS
|
||||
No TwiML App SID configured! Browser-based calling requires a Twilio TwiML App.
|
||||
|
||||
To create a TwiML App:
|
||||
1. Go to Twilio Console > Voice > TwiML Apps
|
||||
2. Create a new TwiML app
|
||||
3. Set the Voice Request URL to: #{twiml_url}
|
||||
4. Save and copy the new TwiML App SID
|
||||
5. Update your voice channel configuration with this SID
|
||||
INSTRUCTIONS
|
||||
|
||||
Rails.logger.error(setup_instructions)
|
||||
|
||||
# Still try to create a token, but with a warning that it will likely fail
|
||||
# Use a fake App SID to avoid immediate rejection
|
||||
voice_grant.outgoing_application_sid = "AP00000000000000000000000000000000"
|
||||
|
||||
# Set parameters that would be used if it were a real App SID
|
||||
voice_grant.outgoing_application_params = {
|
||||
'account_id' => Current.account.id.to_s,
|
||||
'agent_id' => current_user.id.to_s,
|
||||
'missing_twiml_app' => true # Flag to indicate the problem
|
||||
}
|
||||
end
|
||||
|
||||
# Log info for detailed debugging
|
||||
Rails.logger.info("Voice token created for identity: #{client_identity}")
|
||||
Rails.logger.info("Outgoing params: #{voice_grant.outgoing_application_params.inspect}")
|
||||
|
||||
# Add the grant to the token
|
||||
token.add_grant(voice_grant)
|
||||
private
|
||||
|
||||
# Add a warning message if no TwiML App SID is configured
|
||||
twiml_url = "#{ENV.fetch('FRONTEND_URL', '')}/api/v1/accounts/#{Current.account.id}/voice/twiml_for_client"
|
||||
warning_message = !outgoing_application_sid.present? ?
|
||||
"Browser calling requires a Twilio TwiML App SID. Configure one in the voice channel settings." : nil
|
||||
|
||||
# Return token response with additional debugging info
|
||||
render json: {
|
||||
token: token.to_jwt,
|
||||
identity: client_identity,
|
||||
voice_enabled: true,
|
||||
account_sid: account_sid,
|
||||
agent_id: current_user.id,
|
||||
account_id: Current.account.id,
|
||||
inbox_id: inbox.id,
|
||||
phone_number: phone_number,
|
||||
twiml_endpoint: twiml_url,
|
||||
has_twiml_app: outgoing_application_sid.present?,
|
||||
warning: warning_message
|
||||
}
|
||||
rescue => e
|
||||
Rails.logger.error("Error generating token: #{e.message}")
|
||||
Rails.logger.error("Backtrace: #{e.backtrace[0..5].join("\n")}")
|
||||
|
||||
render json: {
|
||||
error: 'Failed to generate token',
|
||||
details: e.message
|
||||
}, status: :internal_server_error
|
||||
def build_response
|
||||
{
|
||||
token: twilio_token.to_jwt,
|
||||
identity: client_identity,
|
||||
voice_enabled: true,
|
||||
account_sid: twilio_config[:account_sid],
|
||||
agent_id: Current.user.id,
|
||||
account_id: Current.account.id,
|
||||
inbox_id: @voice_inbox.id,
|
||||
phone_number: twilio_config[:phone_number],
|
||||
twiml_endpoint: twilio_config[:twiml_url],
|
||||
has_twiml_app: twilio_config[:outgoing_app_sid].present?
|
||||
}
|
||||
end
|
||||
|
||||
def twilio_token
|
||||
Twilio::JWT::AccessToken.new(
|
||||
*twilio_credentials,
|
||||
identity: client_identity,
|
||||
ttl: 1.hour.to_i
|
||||
).tap { |t| t.add_grant(voice_grant) }
|
||||
end
|
||||
|
||||
def twilio_credentials
|
||||
twilio_config.values_at(:account_sid, :api_key_sid, :api_key_secret)
|
||||
end
|
||||
|
||||
def voice_grant
|
||||
Twilio::JWT::AccessToken::VoiceGrant.new.tap do |grant|
|
||||
grant.incoming_allow = true
|
||||
grant.outgoing_application_sid = twilio_config[:outgoing_app_sid]
|
||||
grant.outgoing_application_params = outgoing_params
|
||||
end
|
||||
end
|
||||
|
||||
def outgoing_params
|
||||
{
|
||||
account_id: Current.account.id,
|
||||
agent_id: Current.user.id,
|
||||
identity: client_identity,
|
||||
client_name: client_identity,
|
||||
accountSid: twilio_config[:account_sid],
|
||||
is_agent: 'true'
|
||||
}
|
||||
end
|
||||
|
||||
def twilio_config
|
||||
@twilio_config ||= begin
|
||||
cfg = @voice_inbox.channel.provider_config_hash || {}
|
||||
{
|
||||
account_sid: cfg['account_sid'],
|
||||
api_key_sid: cfg['api_key_sid'],
|
||||
api_key_secret: cfg['api_key_secret'],
|
||||
outgoing_app_sid: cfg['outgoing_application_sid'],
|
||||
phone_number: @voice_inbox.channel.phone_number,
|
||||
twiml_url: "#{ENV.fetch('FRONTEND_URL', '')}/api/v1/accounts/#{Current.account.id}/voice/twiml_for_client"
|
||||
}.with_indifferent_access.merge(client_identity:)
|
||||
end
|
||||
end
|
||||
|
||||
def client_identity
|
||||
@client_identity ||= "agent-#{Current.user.id}-account-#{Current.account.id}"
|
||||
end
|
||||
|
||||
def set_voice_inbox
|
||||
@voice_inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user