## Linear Ticket https://linear.app/chatwoot/issue/CW-7264/add-mute-button-in-twilio-calls ## Description Adds mute support for Twilio voice calls. Previously the mute button was shown only for WhatsApp calls (which toggle the local mic track in the browser), so Twilio calls had no way to mute. The call widget now routes mute by provider: WhatsApp continues to toggle the local mic track, while Twilio uses the Voice SDK connection's native `mute()`. The mute button is now shown for any active call, and unmute works symmetrically. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
103 lines
2.4 KiB
JavaScript
103 lines
2.4 KiB
JavaScript
import { Device } from '@twilio/voice-sdk';
|
|
import VoiceAPI from './voiceAPIClient';
|
|
|
|
const createCallDisconnectedEvent = () => new CustomEvent('call:disconnected');
|
|
|
|
class TwilioVoiceClient extends EventTarget {
|
|
constructor() {
|
|
super();
|
|
this.device = null;
|
|
this.activeConnection = null;
|
|
this.initialized = false;
|
|
this.inboxId = null;
|
|
}
|
|
|
|
async initializeDevice(inboxId) {
|
|
this.destroyDevice();
|
|
|
|
const response = await VoiceAPI.getToken(inboxId);
|
|
const { token, account_id } = response || {};
|
|
if (!token) throw new Error('Invalid token');
|
|
|
|
this.device = new Device(token, {
|
|
allowIncomingWhileBusy: true,
|
|
disableAudioContextSounds: true,
|
|
appParams: { account_id },
|
|
});
|
|
|
|
this.device.removeAllListeners();
|
|
this.device.on('connect', conn => {
|
|
this.activeConnection = conn;
|
|
conn.on('disconnect', this.onDisconnect);
|
|
});
|
|
|
|
this.device.on('disconnect', this.onDisconnect);
|
|
|
|
this.device.on('tokenWillExpire', async () => {
|
|
const r = await VoiceAPI.getToken(this.inboxId);
|
|
if (r?.token) this.device.updateToken(r.token);
|
|
});
|
|
|
|
this.initialized = true;
|
|
this.inboxId = inboxId;
|
|
|
|
return this.device;
|
|
}
|
|
|
|
get hasActiveConnection() {
|
|
return !!this.activeConnection;
|
|
}
|
|
|
|
setMuted(shouldMute) {
|
|
if (!this.activeConnection) return false;
|
|
this.activeConnection.mute(shouldMute);
|
|
return shouldMute;
|
|
}
|
|
|
|
endClientCall() {
|
|
if (this.activeConnection) {
|
|
this.activeConnection.disconnect();
|
|
}
|
|
this.activeConnection = null;
|
|
if (this.device) {
|
|
this.device.disconnectAll();
|
|
}
|
|
}
|
|
|
|
destroyDevice() {
|
|
if (this.device) {
|
|
this.device.destroy();
|
|
}
|
|
this.activeConnection = null;
|
|
this.device = null;
|
|
this.initialized = false;
|
|
this.inboxId = null;
|
|
}
|
|
|
|
async joinClientCall({ to, conversationId, callSid }) {
|
|
if (!this.device || !this.initialized || !to) return null;
|
|
if (this.activeConnection) return this.activeConnection;
|
|
|
|
const params = {
|
|
To: to,
|
|
is_agent: 'true',
|
|
conversation_id: conversationId,
|
|
call_sid: callSid,
|
|
};
|
|
|
|
const connection = await this.device.connect({ params });
|
|
this.activeConnection = connection;
|
|
|
|
connection.on('disconnect', this.onDisconnect);
|
|
|
|
return connection;
|
|
}
|
|
|
|
onDisconnect = () => {
|
|
this.activeConnection = null;
|
|
this.dispatchEvent(createCallDisconnectedEvent());
|
|
};
|
|
}
|
|
|
|
export default new TwilioVoiceClient();
|