fix(zapi): first connection issues, and merge existing contact on received message (#138)

* fix(zapi): pairing issues on first connection

* fix(zapi): handle existing contact when receiving new message

* refactor: simplify logic
This commit is contained in:
Gabriel Jablonski 2025-11-06 23:41:58 -03:00 committed by GitHub
parent 16d7e68176
commit 63a2c70667
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 102 additions and 5 deletions

View File

@ -2,7 +2,7 @@ class Channels::Whatsapp::ZapiQrCodeJob < ApplicationJob
queue_as :default
def perform(whatsapp_channel, attempt = 1)
return if attempt == 1 && whatsapp_channel.provider_connection['connection'] != 'close'
return if attempt == 1 && whatsapp_channel.provider_connection.present? && whatsapp_channel.provider_connection['connection'] != 'close'
return if attempt > 1 && whatsapp_channel.provider_connection['connection'] != 'connecting'
if attempt > 3

View File

@ -46,7 +46,9 @@ class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseServic
raise ProviderUnavailableError unless process_response(response)
Channels::Whatsapp::ZapiQrCodeJob.perform_later(whatsapp_channel) if whatsapp_channel.provider_connection['connection'] == 'close'
if whatsapp_channel.provider_connection.blank? || whatsapp_channel.provider_connection['connection'] == 'close'
Channels::Whatsapp::ZapiQrCodeJob.perform_later(whatsapp_channel)
end
true
end

View File

@ -73,12 +73,18 @@ module Whatsapp::ZapiHandlers::ReceivedCallback # rubocop:disable Metrics/Module
def set_contact
push_name = contact_name
source_id = @raw_message[:chatLid].to_s.gsub(/[^\d]/, '')
identifier = @raw_message[:chatLid]
contact_attributes = { name: push_name, identifier: @raw_message[:chatLid] }
contact_attributes[:phone_number] = "+#{@raw_message[:phone]}" unless @raw_message[:phone].ends_with?('@lid')
contact_attributes = { name: push_name, identifier: identifier }
unless @raw_message[:phone].ends_with?('@lid')
contact_attributes[:phone_number] = "+#{@raw_message[:phone]}"
update_existing_contact_inbox(@raw_message[:phone], source_id, identifier)
end
contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: @raw_message[:chatLid].to_s.gsub(/[^\d]/, ''),
source_id: source_id,
inbox: inbox,
contact_attributes: contact_attributes
).perform
@ -91,6 +97,18 @@ module Whatsapp::ZapiHandlers::ReceivedCallback # rubocop:disable Metrics/Module
try_update_contact_avatar
end
def update_existing_contact_inbox(phone, source_id, identifier)
# NOTE: This is useful when we create a new contact manually, so we don't have information about contact LID;
# With this, when we receive a message from that contact, we can link it properly.
existing_contact_inbox = inbox.contact_inboxes.find_by(source_id: phone)
return unless existing_contact_inbox
ActiveRecord::Base.transaction do
existing_contact_inbox.update!(source_id: source_id)
existing_contact_inbox.contact.update!(identifier: identifier)
end
end
def update_contact_phone_number
return if @contact.phone_number.present?
return if @raw_message[:phone].ends_with?('@lid')

View File

@ -37,6 +37,18 @@ RSpec.describe Channels::Whatsapp::ZapiQrCodeJob do
described_class.perform_now(whatsapp_channel, 2)
end
it 'fetches QR code and schedules next attempt on first attempt with blank connection' do
allow(zapi_service).to receive(:qr_code_image).and_return('data:image/png;base64,test-qr-code')
expect(described_class).to receive(:set).with(wait: 30.seconds).and_call_original
expect do
described_class.perform_now(whatsapp_channel, 1)
end.to have_enqueued_job(described_class).with(whatsapp_channel, 2)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('connecting')
expect(whatsapp_channel.provider_connection['qr_data_url']).to eq('data:image/png;base64,test-qr-code')
end
it 'fetches QR code and schedules next attempt on first attempt with close connection' do
whatsapp_channel.update_provider_connection!(connection: 'close')
allow(zapi_service).to receive(:qr_code_image).and_return('data:image/png;base64,test-qr-code')

View File

@ -70,6 +70,18 @@ describe Whatsapp::Providers::WhatsappZapiService do
expect(service.setup_channel_provider).to be(true)
end
it 'schedules QR code job when connection is blank' do
whatsapp_channel.update!(provider_connection: {})
stub_request(:put, "#{api_instance_path_with_token}/update-every-webhooks")
.with(headers: stub_headers)
.to_return(status: 200)
service.setup_channel_provider
expect(Channels::Whatsapp::ZapiQrCodeJob).to have_been_enqueued.with(whatsapp_channel)
end
it 'schedules QR code job when connection is closed' do
whatsapp_channel.update!(provider_connection: { 'connection' => 'close' })

View File

@ -376,6 +376,59 @@ describe Whatsapp::ZapiHandlers::ReceivedCallback do
end
end
context 'when contact_inbox exists with phone as source_id (manual creation)' do
let!(:existing_contact) do
create(:contact,
account: inbox.account,
identifier: nil,
phone_number: '+5511987654321',
name: 'Manually Created Contact')
end
let!(:existing_contact_inbox) do
create(:contact_inbox,
inbox: inbox,
contact: existing_contact,
source_id: '5511987654321')
end
let(:params) do
base_params.merge(
phone: '5511987654321',
chatLid: '123456789@lid'
)
end
it 'updates existing contact_inbox source_id from phone to LID' do
service.perform
expect(existing_contact_inbox.reload.source_id).to eq('123456789')
end
it 'updates existing contact identifier with LID' do
service.perform
expect(existing_contact.reload.identifier).to eq('123456789@lid')
end
it 'does not create a new contact_inbox' do
expect do
service.perform
end.not_to change(ContactInbox, :count)
end
it 'does not create a new contact' do
expect do
service.perform
end.not_to change(Contact, :count)
end
it 'reuses the existing contact for message' do
service.perform
message = Message.last
expect(message.sender).to eq(existing_contact)
end
end
context 'when handling avatar' do
let(:params) do
base_params.merge(