feat(channels): align whatsapp calling toggles
This commit is contained in:
@@ -25,16 +25,22 @@ const InboxLimitExceededMessage = "Account limit exceeded. Upgrade to a higher p
|
||||
const InboxHealthWhatsAppCloudOnlyMessage = "Health data only available for WhatsApp Cloud API channels"
|
||||
const InboxTemplateSyncInitiatedMessage = "Template sync initiated successfully"
|
||||
const InboxTemplateSyncWhatsAppOnlyMessage = "Template sync is only available for WhatsApp channels"
|
||||
const InboxWhatsAppCallingUnsupportedMessage = "Inbox does not support WhatsApp calling"
|
||||
const InboxWhatsAppCallingFeatureRequiredMessage = "WhatsApp calling requires the channel_voice feature"
|
||||
const TaskTypeInboxSyncTemplates = "inbox:sync_templates"
|
||||
|
||||
var ErrInboxLimitExceeded = errors.New(InboxLimitExceededMessage)
|
||||
var ErrInboxHealthWhatsAppCloudOnly = errors.New(InboxHealthWhatsAppCloudOnlyMessage)
|
||||
var ErrInboxTemplateSyncWhatsAppOnly = errors.New(InboxTemplateSyncWhatsAppOnlyMessage)
|
||||
var ErrInboxWhatsAppCallingUnsupported = errors.New(InboxWhatsAppCallingUnsupportedMessage)
|
||||
var ErrInboxWhatsAppCallingFeatureRequired = errors.New(InboxWhatsAppCallingFeatureRequiredMessage)
|
||||
|
||||
type WhatsAppChannelService interface {
|
||||
FetchMessageTemplates(ctx context.Context, channel *channelmodel.ChannelWhatsApp) ([]interface{}, error)
|
||||
FetchHealthStatus(ctx context.Context, channel *channelmodel.ChannelWhatsApp) (map[string]interface{}, error)
|
||||
SetupWebhook(ctx context.Context, channel *channelmodel.ChannelWhatsApp, webhookURL string) error
|
||||
SetupWebhookFields(ctx context.Context, channel *channelmodel.ChannelWhatsApp, webhookURL string, fields []string) error
|
||||
UpdateCallingStatus(ctx context.Context, channel *channelmodel.ChannelWhatsApp, status string) error
|
||||
}
|
||||
|
||||
// InboxService implements business logic for Inbox operations.
|
||||
@@ -1725,6 +1731,84 @@ func (s *InboxService) RegisterWebhook(ctx context.Context, accountID, inboxID u
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableWhatsAppCalling matches Chatwoot Enterprise InboxesController#enable_whatsapp_calling.
|
||||
// It enables Meta calling remotely, registers the calls webhook field, then persists calling_enabled.
|
||||
func (s *InboxService) EnableWhatsAppCalling(ctx context.Context, accountID, inboxID uint) error {
|
||||
account, inbox, waChannel, err := s.whatsAppCallingPrereqs(ctx, accountID, inboxID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !featureFlagStringEnabled(account.FeatureFlags, "channel_voice") {
|
||||
return ErrInboxWhatsAppCallingFeatureRequired
|
||||
}
|
||||
if s.whatsappService == nil {
|
||||
return fmt.Errorf("WhatsApp service not available")
|
||||
}
|
||||
if err := s.whatsappService.UpdateCallingStatus(ctx, waChannel, "ENABLED"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.setupWhatsAppWebhookFields(ctx, waChannel, whatsappWebhookCallbackURL(waChannel.PhoneNumber), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
setWhatsAppCallingEnabled(waChannel, true)
|
||||
if err := s.whatsappRepo.Update(ctx, waChannel); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.refreshWhatsAppInboxConfig(ctx, inbox, waChannel, true)
|
||||
}
|
||||
|
||||
// DisableWhatsAppCalling matches Chatwoot Enterprise InboxesController#disable_whatsapp_calling.
|
||||
// It only gates calling locally; webhook re-registration without calls is best-effort.
|
||||
func (s *InboxService) DisableWhatsAppCalling(ctx context.Context, accountID, inboxID uint) error {
|
||||
_, inbox, waChannel, err := s.whatsAppCallingPrereqs(ctx, accountID, inboxID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setWhatsAppCallingEnabled(waChannel, false)
|
||||
if err := s.whatsappRepo.Update(ctx, waChannel); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.refreshWhatsAppInboxConfig(ctx, inbox, waChannel, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.setupWhatsAppWebhookFields(ctx, waChannel, whatsappWebhookCallbackURL(waChannel.PhoneNumber), []string{"messages", "smb_message_echoes"}); err != nil {
|
||||
applogger.L().Warnf("WhatsApp calling disable webhook re-subscribe failed for inbox %d: %v", inbox.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboxService) whatsAppCallingPrereqs(ctx context.Context, accountID, inboxID uint) (*model.Account, *model.Inbox, *channelmodel.ChannelWhatsApp, error) {
|
||||
inbox, err := s.repo.FindByAccountAndID(ctx, accountID, inboxID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("inbox not found: %w", err)
|
||||
}
|
||||
if inbox.ChannelType != "whatsapp" {
|
||||
return nil, nil, nil, ErrInboxWhatsAppCallingUnsupported
|
||||
}
|
||||
waChannel, err := s.getWhatsAppChannel(ctx, inbox.ID)
|
||||
if err != nil || !waChannel.IsCloudAPI() {
|
||||
return nil, nil, nil, ErrInboxWhatsAppCallingUnsupported
|
||||
}
|
||||
var account model.Account
|
||||
if err := s.repo.DB().WithContext(ctx).First(&account, accountID).Error; err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("account not found: %w", err)
|
||||
}
|
||||
return &account, inbox, waChannel, nil
|
||||
}
|
||||
|
||||
func setWhatsAppCallingEnabled(channel *channelmodel.ChannelWhatsApp, enabled bool) {
|
||||
config := parseJSONMap(channel.ProviderConfig)
|
||||
config["calling_enabled"] = enabled
|
||||
channel.ProviderConfig = marshalInboxJSON(config)
|
||||
}
|
||||
|
||||
func (s *InboxService) refreshWhatsAppInboxConfig(ctx context.Context, inbox *model.Inbox, channel *channelmodel.ChannelWhatsApp, voiceEnabled bool) error {
|
||||
config := whatsappInboxChannelConfig(channel)
|
||||
config["voice_enabled"] = voiceEnabled
|
||||
inbox.ChannelConfig = marshalInboxJSON(config)
|
||||
return s.repo.Update(ctx, inbox)
|
||||
}
|
||||
|
||||
// DeleteAvatar removes the avatar URL from an inbox and dispatches an update event.
|
||||
// Reference: Chatwoot InboxesController#destroy_avatar
|
||||
func (s *InboxService) DeleteAvatar(ctx context.Context, accountID, inboxID uint) (*model.Inbox, error) {
|
||||
@@ -1842,6 +1926,13 @@ func (s *InboxService) setupWhatsAppWebhook(ctx context.Context, waChannel *chan
|
||||
return s.whatsappService.SetupWebhook(ctx, waChannel, webhookURL)
|
||||
}
|
||||
|
||||
func (s *InboxService) setupWhatsAppWebhookFields(ctx context.Context, waChannel *channelmodel.ChannelWhatsApp, webhookURL string, fields []string) error {
|
||||
if s.whatsappService == nil {
|
||||
return fmt.Errorf("WhatsApp service not available")
|
||||
}
|
||||
return s.whatsappService.SetupWebhookFields(ctx, waChannel, webhookURL, fields)
|
||||
}
|
||||
|
||||
// generateInboxSecret creates a random HMAC secret for webhook verification.
|
||||
func generateInboxSecret() string {
|
||||
b := make([]byte, 32)
|
||||
|
||||
Reference in New Issue
Block a user