## Linear Ticket - https://linear.app/chatwoot/issue/CW-7276/bsuid-support-to-whatsapp-voice-calling ## Description Keeps WhatsApp voice calls in the same thread as the chat when a caller has adopted a **WhatsApp username** and hidden their phone number. This makes the inbound-call path BSUID-aware, reusing the same identifier the messaging pipeline keys on so calls land on the existing `ContactInbox`/conversation. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Locally via UI ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] 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 --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
49 lines
2.0 KiB
Ruby
49 lines
2.0 KiB
Ruby
class Whatsapp::InboundCallIdentityBuilder
|
|
pattr_initialize [:inbox!, :params!]
|
|
|
|
# Build the message path's source_id set (phone wa_id -> user_id -> parent_user_id) plus
|
|
# contact attributes, so the resolver lands a call on the same ContactInbox a message would.
|
|
# BSUIDs ride in from_user_id/from_parent_user_id (or the contact's user_id/parent_user_id),
|
|
# never in `from` (the phone wa_id).
|
|
def perform(payload)
|
|
contact = caller_contact(payload)
|
|
phone = contact[:wa_id].presence || payload[:from].presence
|
|
source_ids = [
|
|
phone_source_id(phone),
|
|
payload[:from_user_id].presence || contact[:user_id].presence,
|
|
payload[:from_parent_user_id].presence || contact[:parent_user_id].presence
|
|
].compact_blank.uniq
|
|
{ source_ids: source_ids, contact_attributes: contact_attributes(contact, phone, source_ids.first) }
|
|
end
|
|
|
|
private
|
|
|
|
# Normalize the wa_id the same way messaging does so a call matches its stored source_id.
|
|
def phone_source_id(phone)
|
|
return unless phone.to_s.match?(/\A\d{1,15}\z/)
|
|
|
|
Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_and_find_contact_by_provider(phone.to_s, :cloud)
|
|
end
|
|
|
|
def contact_attributes(contact, phone, source_identifier)
|
|
name = contact.dig(:profile, :name).presence || source_identifier
|
|
return { name: name } unless phone.to_s.match?(/\A\d{1,15}\z/)
|
|
|
|
formatted = "+#{phone}"
|
|
{ name: name == phone ? formatted : name, phone_number: formatted }
|
|
end
|
|
|
|
# Match the contacts entry to THIS caller so batched payloads don't borrow another's identity.
|
|
def caller_contact(payload)
|
|
Array(params[:contacts]).map(&:with_indifferent_access).find do |c|
|
|
identifier_match?(c[:wa_id], payload[:from]) ||
|
|
identifier_match?(c[:user_id], payload[:from_user_id]) ||
|
|
identifier_match?(c[:parent_user_id], payload[:from_parent_user_id])
|
|
end || {}.with_indifferent_access
|
|
end
|
|
|
|
def identifier_match?(left, right)
|
|
left.present? && right.present? && left.to_s == right.to_s
|
|
end
|
|
end
|