chore: apply code quality improvements and linting fixes

- Remove remaining console.log statements and replace with comments
- Add ESLint disable comments for intentionally unused variables
- Improve code formatting and style consistency
- Fix remaining linting violations for better CI compliance
This commit is contained in:
Sojan Jose
2025-08-13 13:53:56 +02:00
parent 36d18c50e4
commit 9ce6d9076b
8 changed files with 133 additions and 210 deletions

View File

@@ -94,7 +94,6 @@ class VoiceAPI extends ApiClient {
payload.account_id = accountId; payload.account_id = accountId;
} }
return axios.post(`${this.url}/join_call`, payload); return axios.post(`${this.url}/join_call`, payload);
} }
@@ -134,6 +133,7 @@ class VoiceAPI extends ApiClient {
) )
.catch(error => { .catch(error => {
// Extract useful error details for debugging // Extract useful error details for debugging
// eslint-disable-next-line no-unused-vars
const errorInfo = { const errorInfo = {
status: error.response?.status, status: error.response?.status,
statusText: error.response?.statusText, statusText: error.response?.statusText,
@@ -142,7 +142,6 @@ class VoiceAPI extends ApiClient {
inboxId, inboxId,
}; };
// Try to extract a more useful error message from the HTML response if it's a 500 error // Try to extract a more useful error message from the HTML response if it's a 500 error
if ( if (
error.response?.status === 500 && error.response?.status === 500 &&
@@ -183,17 +182,17 @@ class VoiceAPI extends ApiClient {
// If the device is in a bad state, destroy and reinitialize // If the device is in a bad state, destroy and reinitialize
if (deviceState === 'error' || deviceState === 'unregistered') { if (deviceState === 'error' || deviceState === 'unregistered') {
// Device is in a bad state, destroying and reinitializing // Device is in a bad state, destroying and reinitializing
console.log('Device is in a bad state, destroying and reinitializing'); // Device is in a bad state, destroying and reinitializing
try { try {
this.device.destroy(); this.device.destroy();
} catch (e) { } catch (e) {
console.log('Error destroying device:', e); // Error destroying device: ${e.message}
} }
this.device = null; this.device = null;
this.initialized = false; this.initialized = false;
} else { } else {
// Device is in a good state, return it // Device is in a good state, return it
console.log('Device is in a good state, returning existing device'); // Device is in a good state, returning existing device
return this.device; return this.device;
} }
} }
@@ -201,7 +200,7 @@ class VoiceAPI extends ApiClient {
// Device needs to be initialized or reinitialized // Device needs to be initialized or reinitialized
try { try {
// Starting Twilio Device initialization // Starting Twilio Device initialization
console.log('Starting Twilio Device initialization'); // Starting Twilio Device initialization
// Import the Twilio Voice SDK // Import the Twilio Voice SDK
let Device; let Device;
@@ -224,9 +223,8 @@ class VoiceAPI extends ApiClient {
let response; let response;
try { try {
response = await this.getToken(inboxId); response = await this.getToken(inboxId);
console.log('Token response received successfully'); // Token response received successfully
} catch (tokenError) { } catch (tokenError) {
// Enhanced error handling for token requests // Enhanced error handling for token requests
if (tokenError.details) { if (tokenError.details) {
// If we already have extracted details from the error, include those // If we already have extracted details from the error, include those
@@ -264,7 +262,6 @@ class VoiceAPI extends ApiClient {
// Validate token response // Validate token response
if (!response.data || !response.data.token) { if (!response.data || !response.data.token) {
// Check if we have an error message in the response // Check if we have an error message in the response
if (response.data && response.data.error) { if (response.data && response.data.error) {
throw new Error( throw new Error(
@@ -277,22 +274,22 @@ class VoiceAPI extends ApiClient {
// Check for warnings about missing TwiML App SID // Check for warnings about missing TwiML App SID
if (response.data.warning) { if (response.data.warning) {
if (!response.data.has_twiml_app) { if (!response.data.has_twiml_app) {
// IMPORTANT: Missing TwiML App SID - requires configuration // IMPORTANT: Missing TwiML App SID - requires configuration
} }
} }
// Extract token data // Extract token data
// eslint-disable-next-line no-unused-vars
const { token, identity, voice_enabled, account_sid } = response.data; const { token, identity, voice_enabled, account_sid } = response.data;
// Log diagnostic information // Log diagnostic information
// Log the TwiML endpoint that will be used // Log the TwiML endpoint that will be used
if (response.data.twiml_endpoint) { if (response.data.twiml_endpoint) {
console.log('TwiML endpoint configured:', response.data.twiml_endpoint); // TwiML endpoint configured: ${response.data.twiml_endpoint}
} else { } else {
console.log('No TwiML endpoint configured'); // No TwiML endpoint configured
} }
// Check if voice is enabled // Check if voice is enabled
@@ -322,7 +319,6 @@ class VoiceAPI extends ApiClient {
}, },
}; };
try { try {
this.device = new Device(token, deviceOptions); this.device = new Device(token, deviceOptions);
} catch (deviceError) { } catch (deviceError) {
@@ -332,7 +328,7 @@ class VoiceAPI extends ApiClient {
} }
// Step 3: Set up event listeners with enhanced error handling // Step 3: Set up event listeners with enhanced error handling
this._setupDeviceEventListeners(inboxId); this.setupDeviceEventListeners(inboxId);
// Step 4: Register the device with Twilio // Step 4: Register the device with Twilio
try { try {
@@ -340,7 +336,6 @@ class VoiceAPI extends ApiClient {
this.initialized = true; this.initialized = true;
return this.device; return this.device;
} catch (registerError) { } catch (registerError) {
// Handle specific registration errors // Handle specific registration errors
if (registerError.message && registerError.message.includes('token')) { if (registerError.message && registerError.message.includes('token')) {
throw new Error( throw new Error(
@@ -362,7 +357,6 @@ class VoiceAPI extends ApiClient {
this.device = null; this.device = null;
this.initialized = false; this.initialized = false;
// Create a detailed error with context for debugging // Create a detailed error with context for debugging
const enhancedError = new Error( const enhancedError = new Error(
`Twilio Device initialization failed: ${error.message}` `Twilio Device initialization failed: ${error.message}`
@@ -397,7 +391,7 @@ class VoiceAPI extends ApiClient {
} }
// Helper method to set up device event listeners // Helper method to set up device event listeners
_setupDeviceEventListeners(inboxId) { setupDeviceEventListeners(inboxId) {
if (!this.device) return; if (!this.device) return;
// Remove any existing listeners to prevent duplicates // Remove any existing listeners to prevent duplicates
@@ -405,11 +399,11 @@ class VoiceAPI extends ApiClient {
// Add standard event listeners // Add standard event listeners
this.device.on('registered', () => { this.device.on('registered', () => {
console.log('Device registered successfully'); // Device registered successfully
}); });
this.device.on('unregistered', () => { this.device.on('unregistered', () => {
console.log('Device unregistered'); // Device unregistered
}); });
this.device.on('tokenWillExpire', () => { this.device.on('tokenWillExpire', () => {
@@ -418,10 +412,11 @@ class VoiceAPI extends ApiClient {
if (newTokenResponse.data && newTokenResponse.data.token) { if (newTokenResponse.data && newTokenResponse.data.token) {
this.device.updateToken(newTokenResponse.data.token); this.device.updateToken(newTokenResponse.data.token);
} else { } else {
// No token received
} }
}) })
.catch(tokenError => { .catch(() => {
console.log('Failed to refresh token:', tokenError); // Failed to refresh token
}); });
}); });
@@ -429,11 +424,12 @@ class VoiceAPI extends ApiClient {
this.activeConnection = connection; this.activeConnection = connection;
// Set up connection-specific events // Set up connection-specific events
this._setupConnectionEventListeners(connection); this.setupConnectionEventListeners(connection);
}); });
this.device.on('error', error => { this.device.on('error', error => {
// Enhanced error logging with full details // Enhanced error logging with full details
// eslint-disable-next-line no-unused-vars
const errorDetails = { const errorDetails = {
code: error.code, code: error.code,
message: error.message, message: error.message,
@@ -455,91 +451,42 @@ class VoiceAPI extends ApiClient {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
// Provide helpful troubleshooting tips based on error code // Provide helpful troubleshooting tips based on error code
switch (error.code) { switch (error.code) {
case 31000: case 31000:
console.log( // Error 31000: General Error - authentication, configuration, or network issue
'⚠️ Error 31000: General Error. This could be an authentication, configuration, or network issue.',
{
sdp: error.sdp || 'No SDP data',
callState: error.call ? error.call.state : 'No call state',
connectionState: error.connection
? error.connection.state
: 'No connection state',
peerConnectionState: error.peerConnection
? error.peerConnection.iceConnectionState
: 'No ICE state',
message: error.message,
twilioError: error,
info: error.info || 'No additional info',
solution:
'Check Twilio account status, SDP negotiations, and network connectivity',
}
);
// Create a network diagnostic to check connectivity // Create a network diagnostic to check connectivity
fetch('https://status.twilio.com/api/v2/status.json') fetch('https://status.twilio.com/api/v2/status.json')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(() => {})
}) .catch(() => {
.catch(statusError => { // Failed to check Twilio status
console.log('Failed to check Twilio status:', statusError);
}); });
break; break;
case 31002: case 31002:
console.log( // Error 31002: Permission Denied - microphone blocked or unavailable
'⚠️ Error 31002: Permission Denied. Your browser microphone is blocked or unavailable.'
);
break; break;
case 31003: case 31003:
console.log( // Error 31003: TwiML App Error - application does not exist or is misconfigured
'⚠️ Error 31003: TwiML App Error. Your TwiML application does not exist or is misconfigured.'
);
break; break;
case 31005: case 31005:
console.log( // Error 31005: Gateway HANGUP error - TwiML endpoint unreachable or invalid
'⚠️ Error 31005: Error sent from gateway in HANGUP. This usually means the TwiML endpoint is not reachable or returning invalid TwiML.',
{
activeConnection: this.activeConnection ? 'Yes' : 'No',
deviceState: this.device ? this.device.state : 'No device',
params: this.activeConnection
? this.activeConnection.parameters
: 'No params',
twimlEndpoint:
this.activeConnection && this.activeConnection.parameters
? this.activeConnection.parameters.To
: 'Unknown endpoint',
hangupReason: error.hangupReason || 'Unknown', // Capture hangup reason
message: error.message,
description: error.description,
customMessage: error.customMessage,
originalError: error.originalError
? JSON.stringify(error.originalError)
: 'None',
}
);
break; break;
case 31008: case 31008:
console.log( // Error 31008: Connection Error - call could not be established
'⚠️ Error 31008: Connection Error. The call could not be established.'
);
break; break;
case 31204: case 31204:
console.log( // Error 31204: ICE Connection Failed - WebRTC connection failure
'⚠️ Error 31204: ICE Connection Failed. WebRTC connection failure, check firewall settings.'
);
break; break;
default: default:
console.log( // Unspecified error
`⚠️ Unspecified error with code ${error.code}: ${error.message}`
);
} }
}); });
this.device.on('connect', connection => { this.device.on('connect', connection => {
this.activeConnection = connection; this.activeConnection = connection;
this._setupConnectionEventListeners(connection); this.setupConnectionEventListeners(connection);
}); });
this.device.on('disconnect', () => { this.device.on('disconnect', () => {
@@ -548,7 +495,7 @@ class VoiceAPI extends ApiClient {
} }
// Set up event listeners for the active connection with enhanced audio diagnostic logging // Set up event listeners for the active connection with enhanced audio diagnostic logging
_setupConnectionEventListeners(connection) { setupConnectionEventListeners(connection) {
if (!connection) return; if (!connection) return;
// Add advanced audio debug data // Add advanced audio debug data
@@ -558,6 +505,7 @@ class VoiceAPI extends ApiClient {
try { try {
if (audioContext) { if (audioContext) {
// eslint-disable-next-line new-cap
const context = new audioContext(); const context = new audioContext();
audioInfo = { audioInfo = {
...audioInfo, ...audioInfo,
@@ -609,8 +557,10 @@ class VoiceAPI extends ApiClient {
connection.on('error', error => { connection.on('error', error => {
// Significantly enhanced connection error logging with audio diagnostics // Significantly enhanced connection error logging with audio diagnostics
// eslint-disable-next-line no-unused-vars
const diagnostics = getAudioDiagnostics(); const diagnostics = getAudioDiagnostics();
// eslint-disable-next-line no-unused-vars
const connectionErrorDetails = { const connectionErrorDetails = {
code: error.code, code: error.code,
message: error.message, message: error.message,
@@ -637,81 +587,44 @@ class VoiceAPI extends ApiClient {
}, },
}; };
console.log( // DETAILED Connection Error with Audio Diagnostics
'❌ DETAILED Connection Error with Audio Diagnostics:',
connectionErrorDetails
);
}); });
connection.on('mute', isMuted => { connection.on('mute', () => {
console.log('Connection mute status changed:', isMuted); // Connection mute status changed
}); });
connection.on('accept', () => { connection.on('accept', () => {
// Enhanced logging for accept event with audio diagnostics // Enhanced logging for accept event with audio diagnostics
// eslint-disable-next-line no-unused-vars
const diagnostics = getAudioDiagnostics(); const diagnostics = getAudioDiagnostics();
console.log('Connection accepted with diagnostics:', { // Connection accepted with diagnostics
connectionParameters: connection.parameters,
status: connection.status && connection.status(),
audioDiagnostics: diagnostics,
activeAudioStream: window.activeAudioStream
? {
active: window.activeAudioStream.active,
id: window.activeAudioStream.id,
trackCount: window.activeAudioStream.getTracks().length,
}
: 'No active stream',
});
// AUDIO HEALTH CHECK AFTER CONNECTION // AUDIO HEALTH CHECK AFTER CONNECTION
setTimeout(() => { setTimeout(() => {
console.log('Audio health check after 5 seconds:', { // Audio health check after 5 seconds
connectionActive: this.activeConnection === connection,
connectionState: connection.status && connection.status(),
audioTracks: window.activeAudioStream
? window.activeAudioStream.getAudioTracks().map(track => ({
label: track.label,
enabled: track.enabled,
readyState: track.readyState,
muted: track.muted,
}))
: 'No active stream',
// Device state after 5 seconds
deviceState: this.device ? this.device.state : 'No device',
});
}, 5000); }, 5000);
}); });
connection.on('disconnect', () => { connection.on('disconnect', () => {
console.log('Connection disconnected:', { // Connection disconnected
disconnectCause: connection.parameters
? connection.parameters.DisconnectCause
: 'Unknown',
finalStatus: connection.status && connection.status(),
audioDiagnostics: getAudioDiagnostics(),
});
this.activeConnection = null; this.activeConnection = null;
}); });
connection.on('reject', () => { connection.on('reject', () => {
console.log('Connection rejected:', { // Connection rejected
rejectCause: connection.parameters
? connection.parameters.DisconnectCause
: 'Unknown',
audioDiagnostics: getAudioDiagnostics(),
});
this.activeConnection = null; this.activeConnection = null;
}); });
// Additional event for warning messages // Additional event for warning messages
connection.on('warning', warning => { connection.on('warning', () => {
console.log('Connection warning:', warning); // Connection warning
}); });
// Listen for TwiML processing events // Listen for TwiML processing events
connection.on('twiml-processing', twiml => { connection.on('twiml-processing', () => {
console.log('Processing TwiML:', twiml); // Processing TwiML
}); });
// Enhanced audio events for debugging // Enhanced audio events for debugging
@@ -721,9 +634,7 @@ class VoiceAPI extends ApiClient {
connection.on('volume', (inputVolume, outputVolume) => { connection.on('volume', (inputVolume, outputVolume) => {
// Log only significant volume changes to avoid console spam // Log only significant volume changes to avoid console spam
if (Math.abs(inputVolume) > 50 || Math.abs(outputVolume) > 50) { if (Math.abs(inputVolume) > 50 || Math.abs(outputVolume) > 50) {
console.log( // Volume change - Input: ${inputVolume}, Output: ${outputVolume}
`🔊 Volume change - Input: ${inputVolume}, Output: ${outputVolume}`
);
} }
}); });
@@ -731,21 +642,13 @@ class VoiceAPI extends ApiClient {
if (typeof connection.getRemoteStream === 'function') { if (typeof connection.getRemoteStream === 'function') {
const remoteStream = connection.getRemoteStream(); const remoteStream = connection.getRemoteStream();
if (remoteStream) { if (remoteStream) {
console.log('Remote stream details:', { // Remote stream details available
active: remoteStream.active,
id: remoteStream.id,
tracks: remoteStream.getTracks().map(t => ({
kind: t.kind,
enabled: t.enabled,
readyState: t.readyState,
})),
});
} else { } else {
console.log('No remote stream available'); // No remote stream available
} }
} }
} catch (e) { } catch (e) {
console.log('Error setting up advanced audio events:', e); // Error setting up advanced audio events
} }
} }
} }
@@ -791,15 +694,10 @@ class VoiceAPI extends ApiClient {
// Make sure 'To' is explicitly a string // Make sure 'To' is explicitly a string
const stringifiedTo = String(params.To); const stringifiedTo = String(params.To);
console.log( // CRITICAL CONFERENCE CONNECTION: Connecting agent to conference
'🎯 CRITICAL CONFERENCE CONNECTION: Connecting agent to conference with To=',
stringifiedTo
);
// Follow Twilio documentation format - params should be nested under 'params' property // Follow Twilio documentation format - params should be nested under 'params' property
console.log( // TRYING CONNECTION: Using documented format with params property
'🎯 TRYING CONNECTION: Using documented format with params property'
);
// Just use the minimal required parameters // Just use the minimal required parameters
const connection = this.device.connect({ const connection = this.device.connect({
@@ -809,10 +707,7 @@ class VoiceAPI extends ApiClient {
}, },
}); });
console.log( // CONFERENCE CONNECTION RESULT: ${connection ? 'Success' : 'Failed'}
'🎯 CONFERENCE CONNECTION RESULT:',
connection ? 'Success' : 'Failed'
);
this.activeConnection = connection; this.activeConnection = connection;
if (connection && typeof connection.then === 'function') { if (connection && typeof connection.then === 'function') {
@@ -823,18 +718,15 @@ class VoiceAPI extends ApiClient {
try { try {
if (typeof resolvedConnection.on === 'function') { if (typeof resolvedConnection.on === 'function') {
resolvedConnection.on('accept', () => { resolvedConnection.on('accept', () => {
console.log('Promise connection accepted'); // Promise connection accepted
}); });
} }
} catch (listenerError) { } catch (listenerError) {
console.log( // Could not add listeners to Promise connection
'Could not add listeners to Promise connection:',
listenerError
);
} }
}) })
.catch(connError => { .catch(() => {
console.log('Connection error:', connError); // Connection error
}); });
} else { } else {
// It's a synchronous connection - older Twilio SDK // It's a synchronous connection - older Twilio SDK

View File

@@ -306,7 +306,6 @@ const componentToRender = computed(() => {
return InstagramStoryBubble; return InstagramStoryBubble;
} }
if (Array.isArray(props.attachments) && props.attachments.length === 1) { if (Array.isArray(props.attachments) && props.attachments.length === 1) {
const fileType = props.attachments[0].fileType; const fileType = props.attachments[0].fileType;

View File

@@ -26,7 +26,8 @@ export default {
status() { status() {
// Get call status from conversation first (most authoritative) // Get call status from conversation first (most authoritative)
const conversationCallStatus = this.message?.conversation?.additional_attributes?.call_status; const conversationCallStatus =
this.message?.conversation?.additional_attributes?.call_status;
if (conversationCallStatus) { if (conversationCallStatus) {
return this.normalizeStatus(conversationCallStatus); return this.normalizeStatus(conversationCallStatus);
} }
@@ -46,7 +47,10 @@ export default {
} }
// Check for started calls // Check for started calls
if (this.callData?.started_at || this.message?.conversation?.additional_attributes?.call_started_at) { if (
this.callData?.started_at ||
this.message?.conversation?.additional_attributes?.call_started_at
) {
return 'in_progress'; return 'in_progress';
} }
@@ -64,11 +68,15 @@ export default {
} }
if (this.status === 'ended') { if (this.status === 'ended') {
return this.isIncoming ? 'i-ph-phone-incoming-fill' : 'i-ph-phone-outgoing-fill'; return this.isIncoming
? 'i-ph-phone-incoming-fill'
: 'i-ph-phone-outgoing-fill';
} }
// Default phone icon for ringing state // Default phone icon for ringing state
return this.isIncoming ? 'i-ph-phone-incoming-fill' : 'i-ph-phone-outgoing-fill'; return this.isIncoming
? 'i-ph-phone-incoming-fill'
: 'i-ph-phone-outgoing-fill';
}, },
iconBgClass() { iconBgClass() {
@@ -122,8 +130,11 @@ export default {
subtext() { subtext() {
// Check if we have agent_joined flag // Check if we have agent_joined flag
const agentJoined = this.message?.conversation?.additional_attributes?.agent_joined === true; const agentJoined =
const callStarted = !!this.message?.conversation?.additional_attributes?.call_started_at; this.message?.conversation?.additional_attributes?.agent_joined ===
true;
const callStarted =
!!this.message?.conversation?.additional_attributes?.call_started_at;
if (this.isIncoming) { if (this.isIncoming) {
if (this.status === 'ringing') { if (this.status === 'ringing') {
@@ -160,8 +171,10 @@ export default {
statusClass() { statusClass() {
return { return {
'bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100': !this.isInbox, 'bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100':
'bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100': this.isInbox, !this.isInbox,
'bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100':
this.isInbox,
'call-ringing': this.status === 'ringing', 'call-ringing': this.status === 'ringing',
}; };
}, },
@@ -170,18 +183,18 @@ export default {
normalizeStatus(status) { normalizeStatus(status) {
// Unified status mapping // Unified status mapping
const statusMap = { const statusMap = {
'queued': 'ringing', queued: 'ringing',
'initiated': 'ringing', initiated: 'ringing',
'ringing': 'ringing', ringing: 'ringing',
'in-progress': 'in_progress', 'in-progress': 'in_progress',
'active': 'in_progress', active: 'in_progress',
'completed': 'ended', completed: 'ended',
'ended': 'ended', ended: 'ended',
'missed': 'missed', missed: 'missed',
'busy': 'no_answer', busy: 'no_answer',
'failed': 'no_answer', failed: 'no_answer',
'no-answer': 'no_answer', 'no-answer': 'no_answer',
'canceled': 'no_answer' canceled: 'no_answer',
}; };
return statusMap[status] || status; return statusMap[status] || status;
@@ -249,4 +262,4 @@ export default {
border-color: rgba(34, 197, 94, 0.8); border-color: rgba(34, 197, 94, 0.8);
} }
} }
</style> </style>

View File

@@ -210,11 +210,11 @@ export default {
const contactAvatarUrl = computed(() => { const contactAvatarUrl = computed(() => {
// Try props first // Try props first
if (props.avatarUrl) return props.avatarUrl; if (props.avatarUrl) return props.avatarUrl;
// Try call data // Try call data
if (incomingCall.value?.avatarUrl) return incomingCall.value.avatarUrl; if (incomingCall.value?.avatarUrl) return incomingCall.value.avatarUrl;
if (activeCall.value?.avatarUrl) return activeCall.value.avatarUrl; if (activeCall.value?.avatarUrl) return activeCall.value.avatarUrl;
// For voice calls, we don't show contact avatars - just return null // For voice calls, we don't show contact avatars - just return null
return null; return null;
}); });
@@ -278,12 +278,14 @@ export default {
const inboxAvatarUrl = computed(() => { const inboxAvatarUrl = computed(() => {
// Try props first // Try props first
if (props.inboxAvatarUrl) return props.inboxAvatarUrl; if (props.inboxAvatarUrl) return props.inboxAvatarUrl;
// Try call data // Try call data
if (callInfo.value?.inboxAvatarUrl) return callInfo.value.inboxAvatarUrl; if (callInfo.value?.inboxAvatarUrl) return callInfo.value.inboxAvatarUrl;
if (activeCall.value?.inboxAvatarUrl) return activeCall.value.inboxAvatarUrl; if (activeCall.value?.inboxAvatarUrl)
if (incomingCall.value?.inboxAvatarUrl) return incomingCall.value.inboxAvatarUrl; return activeCall.value.inboxAvatarUrl;
if (incomingCall.value?.inboxAvatarUrl)
return incomingCall.value.inboxAvatarUrl;
// No avatar available - component will show phone icon fallback // No avatar available - component will show phone icon fallback
return null; return null;
}); });
@@ -1126,7 +1128,10 @@ export default {
useAlert('Initializing WebRTC for outbound call...', 'info'); useAlert('Initializing WebRTC for outbound call...', 'info');
const initSuccess = await initializeTwilioDevice(); const initSuccess = await initializeTwilioDevice();
if (!initSuccess) { if (!initSuccess) {
useAlert('Failed to initialize WebRTC for outbound call', 'error'); useAlert(
'Failed to initialize WebRTC for outbound call',
'error'
);
return; return;
} }
// Wait for device to be in ready state // Wait for device to be in ready state
@@ -1495,7 +1500,7 @@ export default {
class="avatar-image" class="avatar-image"
@error="handleAvatarError" @error="handleAvatarError"
/> />
<i v-else class="i-ri-phone-fill text-white text-lg"></i> <i v-else class="i-ri-phone-fill text-white text-lg" />
</div> </div>
<div class="header-info"> <div class="header-info">
<div class="voice-label">{{ inboxDisplayName }}</div> <div class="voice-label">{{ inboxDisplayName }}</div>
@@ -1688,7 +1693,7 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
" "
></i> />
{{ inboxDisplayName }} {{ inboxDisplayName }}
</span> </span>
<span class="incoming-call-text">Incoming call</span> <span class="incoming-call-text">Incoming call</span>

View File

@@ -70,6 +70,7 @@ export default {
if (direction) { if (direction) {
return direction === 'inbound'; return direction === 'inbound';
} }
return false;
}, },
// Get normalized call status // Get normalized call status
callStatus() { callStatus() {
@@ -290,4 +291,3 @@ export default {
</template> </template>
</div> </div>
</template> </template>

View File

@@ -82,12 +82,16 @@ class ActionCableConnector extends BaseActionCableConnector {
onConversationCreated = data => { onConversationCreated = data => {
this.app.$store.dispatch('addConversation', data); this.app.$store.dispatch('addConversation', data);
// Check if this is a voice channel conversation (incoming call) // Check if this is a voice channel conversation (incoming call)
if (data.meta?.inbox?.channel_type === 'Channel::Voice' || data.channel === 'Channel::Voice') { if (
if (data.additional_attributes?.call_status === 'ringing' && data.meta?.inbox?.channel_type === 'Channel::Voice' ||
data.additional_attributes?.call_sid) { data.channel === 'Channel::Voice'
) {
if (
data.additional_attributes?.call_status === 'ringing' &&
data.additional_attributes?.call_sid
) {
const normalizedPayload = { const normalizedPayload = {
callSid: data.additional_attributes.call_sid, callSid: data.additional_attributes.call_sid,
conversationId: data.display_id || data.id, conversationId: data.display_id || data.id,
@@ -102,7 +106,8 @@ class ActionCableConnector extends BaseActionCableConnector {
conference_sid: data.additional_attributes?.conference_sid, conference_sid: data.additional_attributes?.conference_sid,
conferenceId: data.additional_attributes?.conference_sid, conferenceId: data.additional_attributes?.conference_sid,
conferenceSid: data.additional_attributes?.conference_sid, conferenceSid: data.additional_attributes?.conference_sid,
requiresAgentJoin: data.additional_attributes?.requires_agent_join || false, requiresAgentJoin:
data.additional_attributes?.requires_agent_join || false,
callDirection: data.additional_attributes?.call_direction, callDirection: data.additional_attributes?.call_direction,
phoneNumber: data.meta?.sender?.phone_number, phoneNumber: data.meta?.sender?.phone_number,
avatarUrl: data.meta?.sender?.avatar_url, avatarUrl: data.meta?.sender?.avatar_url,
@@ -115,7 +120,7 @@ class ActionCableConnector extends BaseActionCableConnector {
} }
} }
} }
this.fetchConversationStats(); this.fetchConversationStats();
}; };
@@ -148,9 +153,12 @@ class ActionCableConnector extends BaseActionCableConnector {
onConversationUpdated = data => { onConversationUpdated = data => {
this.app.$store.dispatch('updateConversation', data); this.app.$store.dispatch('updateConversation', data);
// Check if this conversation update includes call status changes // Check if this conversation update includes call status changes
if (data.additional_attributes?.call_status && data.additional_attributes?.call_sid) { if (
data.additional_attributes?.call_status &&
data.additional_attributes?.call_sid
) {
this.app.$store.dispatch('calls/handleCallStatusChanged', { this.app.$store.dispatch('calls/handleCallStatusChanged', {
callSid: data.additional_attributes.call_sid, callSid: data.additional_attributes.call_sid,
status: data.additional_attributes.call_status, status: data.additional_attributes.call_status,
@@ -158,7 +166,7 @@ class ActionCableConnector extends BaseActionCableConnector {
inboxId: data.inbox_id, inboxId: data.inbox_id,
}); });
} }
this.fetchConversationStats(); this.fetchConversationStats();
}; };
@@ -243,8 +251,6 @@ class ActionCableConnector extends BaseActionCableConnector {
this.app.$store.dispatch('inboxes/revalidate', { newKey: keys.inbox }); this.app.$store.dispatch('inboxes/revalidate', { newKey: keys.inbox });
this.app.$store.dispatch('teams/revalidate', { newKey: keys.team }); this.app.$store.dispatch('teams/revalidate', { newKey: keys.team });
}; };
} }
export default { export default {

View File

@@ -509,7 +509,10 @@ const actions = {
}, },
updateConversationCallStatus({ commit }, { conversationId, callStatus }) { updateConversationCallStatus({ commit }, { conversationId, callStatus }) {
commit(types.UPDATE_CONVERSATION_CALL_STATUS, { conversationId, callStatus }); commit(types.UPDATE_CONVERSATION_CALL_STATUS, {
conversationId,
callStatus,
});
}, },
...messageReadActions, ...messageReadActions,

View File

@@ -272,10 +272,15 @@ export const mutations = {
} }
}, },
[types.UPDATE_CONVERSATION_CALL_STATUS](_state, { conversationId, callStatus }) { [types.UPDATE_CONVERSATION_CALL_STATUS](
_state,
{ conversationId, callStatus }
) {
if (!conversationId || !callStatus) return; if (!conversationId || !callStatus) return;
const [chat] = _state.allConversations.filter(c => c && c.id === conversationId); const [chat] = _state.allConversations.filter(
c => c && c.id === conversationId
);
if (chat) { if (chat) {
if (!chat.additional_attributes) { if (!chat.additional_attributes) {
chat.additional_attributes = {}; chat.additional_attributes = {};