mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-06-19 21:07:35 +08:00
Voice calling is now a capability on the existing TwilioSms rather than a separate Voice model. A single Twilio phone number handles both SMS and voice calls through one inbox. Fixes https://linear.app/chatwoot/issue/CW-6683/add-voice-calling-as-a-capability-on-twilio-sms-channel and https://linear.app/chatwoot/issue/PLA-120/add-the-support-for-sms **What changed** - Replaced Channel::Voice with voice_enabled flag on Channel::TwilioSms - Added voice_enabled, twiml_app_sid, api_key_secret columns to channel_twilio_sms table - Dropped channel_voice table (no production data) - All voice logic lives in Enterprise layer via prepend_mod_with('Channel::TwilioSms') - Added Voice settings tab on Twilio SMS inbox settings to enable/disable voice - Validates Twilio number voice capability before provisioning - Teardown service cleans up TwiML app and credentials when voice is disabled - Frontend voice detection uses isVoiceCallEnabled() / getVoiceCallProvider() helpers — extensible to future providers - Gated by channel_voice feature flag **How to test** 1. Enable feature flag: Account.find(<id>).enable_features('channel_voice') 2. Create voice inbox: Inboxes → Voice tile → enter Twilio credentials → verify incoming/outgoing calls and SMS work 3. Enable voice on existing SMS inbox: Inboxes → select Twilio SMS inbox → Voice tab → toggle on → provide API key credentials → verify calls work 4. Disable voice: Voice tab → toggle off → verify TwiML app is deleted, credentials cleared, SMS still works 5. Re-enable voice: Toggle on again → must provide api_key_secret again → new TwiML app provisioned --------- Co-authored-by: Muhsin <[email protected]>
167 lines
4.4 KiB
JavaScript
167 lines
4.4 KiB
JavaScript
import { computed } from 'vue';
|
|
import { useMapGetter } from 'dashboard/composables/store';
|
|
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
|
import {
|
|
INBOX_TYPES,
|
|
isVoiceCallEnabled,
|
|
getVoiceCallProvider,
|
|
} from 'dashboard/helper/inbox';
|
|
|
|
export const INBOX_FEATURES = {
|
|
REPLY_TO: 'replyTo',
|
|
REPLY_TO_OUTGOING: 'replyToOutgoing',
|
|
};
|
|
|
|
// This is a single source of truth for inbox features
|
|
// This is used to check if a feature is available for a particular inbox or not
|
|
export const INBOX_FEATURE_MAP = {
|
|
[INBOX_FEATURES.REPLY_TO]: [
|
|
INBOX_TYPES.FB,
|
|
INBOX_TYPES.WEB,
|
|
INBOX_TYPES.TWITTER,
|
|
INBOX_TYPES.WHATSAPP,
|
|
INBOX_TYPES.TELEGRAM,
|
|
INBOX_TYPES.TIKTOK,
|
|
INBOX_TYPES.API,
|
|
],
|
|
[INBOX_FEATURES.REPLY_TO_OUTGOING]: [
|
|
INBOX_TYPES.WEB,
|
|
INBOX_TYPES.TWITTER,
|
|
INBOX_TYPES.WHATSAPP,
|
|
INBOX_TYPES.TELEGRAM,
|
|
INBOX_TYPES.TIKTOK,
|
|
INBOX_TYPES.API,
|
|
],
|
|
};
|
|
|
|
/**
|
|
* Composable for handling inbox-related functionality
|
|
* @param {string|null} inboxId - Optional inbox ID. If not provided, uses current chat's inbox
|
|
* @returns {Object} An object containing inbox type checking functions
|
|
*/
|
|
export const useInbox = (inboxId = null) => {
|
|
const currentChat = useMapGetter('getSelectedChat');
|
|
const inboxGetter = useMapGetter('inboxes/getInboxById');
|
|
|
|
const inbox = computed(() => {
|
|
const targetInboxId = inboxId || currentChat.value?.inbox_id;
|
|
|
|
if (!targetInboxId) return null;
|
|
|
|
return useCamelCase(inboxGetter.value(targetInboxId), { deep: true });
|
|
});
|
|
|
|
const channelType = computed(() => {
|
|
return inbox.value?.channelType;
|
|
});
|
|
|
|
const isAPIInbox = computed(() => {
|
|
return channelType.value === INBOX_TYPES.API;
|
|
});
|
|
|
|
const isAFacebookInbox = computed(() => {
|
|
return channelType.value === INBOX_TYPES.FB;
|
|
});
|
|
|
|
const isAWebWidgetInbox = computed(() => {
|
|
return channelType.value === INBOX_TYPES.WEB;
|
|
});
|
|
|
|
const isATwilioChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.TWILIO;
|
|
});
|
|
|
|
const isALineChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.LINE;
|
|
});
|
|
|
|
const isAnEmailChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.EMAIL;
|
|
});
|
|
|
|
const isATelegramChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.TELEGRAM;
|
|
});
|
|
|
|
const whatsAppAPIProvider = computed(() => {
|
|
return inbox.value?.provider || '';
|
|
});
|
|
|
|
const isAMicrosoftInbox = computed(() => {
|
|
return isAnEmailChannel.value && inbox.value?.provider === 'microsoft';
|
|
});
|
|
|
|
const isAGoogleInbox = computed(() => {
|
|
return isAnEmailChannel.value && inbox.value?.provider === 'google';
|
|
});
|
|
|
|
const isATwilioSMSChannel = computed(() => {
|
|
const { medium: medium = '' } = inbox.value || {};
|
|
return isATwilioChannel.value && medium === 'sms';
|
|
});
|
|
|
|
const isASmsInbox = computed(() => {
|
|
return channelType.value === INBOX_TYPES.SMS || isATwilioSMSChannel.value;
|
|
});
|
|
|
|
const isATwilioWhatsAppChannel = computed(() => {
|
|
const { medium: medium = '' } = inbox.value || {};
|
|
return isATwilioChannel.value && medium === 'whatsapp';
|
|
});
|
|
|
|
const isAWhatsAppCloudChannel = computed(() => {
|
|
return (
|
|
channelType.value === INBOX_TYPES.WHATSAPP &&
|
|
whatsAppAPIProvider.value === 'whatsapp_cloud'
|
|
);
|
|
});
|
|
|
|
const is360DialogWhatsAppChannel = computed(() => {
|
|
return (
|
|
channelType.value === INBOX_TYPES.WHATSAPP &&
|
|
whatsAppAPIProvider.value === 'default'
|
|
);
|
|
});
|
|
|
|
const isAWhatsAppChannel = computed(() => {
|
|
return (
|
|
channelType.value === INBOX_TYPES.WHATSAPP ||
|
|
isATwilioWhatsAppChannel.value
|
|
);
|
|
});
|
|
|
|
const isAnInstagramChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.INSTAGRAM;
|
|
});
|
|
|
|
const isATiktokChannel = computed(() => {
|
|
return channelType.value === INBOX_TYPES.TIKTOK;
|
|
});
|
|
|
|
const voiceCallEnabled = computed(() => isVoiceCallEnabled(inbox.value));
|
|
|
|
const voiceCallProvider = computed(() => getVoiceCallProvider(inbox.value));
|
|
|
|
return {
|
|
inbox,
|
|
isAFacebookInbox,
|
|
isALineChannel,
|
|
isAPIInbox,
|
|
isASmsInbox,
|
|
isATelegramChannel,
|
|
isATwilioChannel,
|
|
isAWebWidgetInbox,
|
|
isAWhatsAppChannel,
|
|
isAMicrosoftInbox,
|
|
isAGoogleInbox,
|
|
isATwilioWhatsAppChannel,
|
|
isAWhatsAppCloudChannel,
|
|
is360DialogWhatsAppChannel,
|
|
isAnEmailChannel,
|
|
isAnInstagramChannel,
|
|
isATiktokChannel,
|
|
voiceCallEnabled,
|
|
voiceCallProvider,
|
|
};
|
|
};
|