feat: whatsapp cloud service typing status and read messages (#106)
This commit is contained in:
parent
dbb41df67e
commit
eaf2f99520
@ -84,7 +84,8 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
def toggle_typing_status(typing_status, conversation:)
|
||||
return unless provider_service.respond_to?(:toggle_typing_status)
|
||||
|
||||
provider_service.toggle_typing_status(conversation.contact.phone_number, typing_status)
|
||||
last_message = conversation.messages.last
|
||||
provider_service.toggle_typing_status(typing_status, last_message: last_message, phone_number: conversation.contact.phone_number)
|
||||
end
|
||||
|
||||
def update_presence(status)
|
||||
@ -98,7 +99,7 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
# NOTE: This is the default behavior, so `mark_as_read` being `nil` is the same as `true`.
|
||||
return if provider_config&.dig('mark_as_read') == false
|
||||
|
||||
provider_service.read_messages(conversation.contact.phone_number, messages)
|
||||
provider_service.read_messages(messages, phone_number: conversation.contact.phone_number)
|
||||
end
|
||||
|
||||
def unread_conversation(conversation)
|
||||
|
||||
@ -94,7 +94,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def toggle_typing_status(phone_number, typing_status)
|
||||
def toggle_typing_status(typing_status, phone_number:, **)
|
||||
@phone_number = phone_number
|
||||
status_map = {
|
||||
Events::Types::CONVERSATION_TYPING_ON => 'composing',
|
||||
@ -136,7 +136,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def read_messages(phone_number, messages)
|
||||
def read_messages(messages, phone_number:, **)
|
||||
@phone_number = phone_number
|
||||
|
||||
response = HTTParty.post(
|
||||
@ -319,12 +319,12 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
method_names.each do |method_name|
|
||||
original_method = instance_method(method_name)
|
||||
|
||||
define_method("#{method_name}_without_error_handling") do |*args, &block|
|
||||
original_method.bind_call(self, *args, &block)
|
||||
define_method("#{method_name}_without_error_handling") do |*args, **kwargs, &block|
|
||||
original_method.bind_call(self, *args, **kwargs, &block)
|
||||
end
|
||||
|
||||
define_method(method_name) do |*args, &block|
|
||||
original_method.bind_call(self, *args, &block)
|
||||
define_method(method_name) do |*args, **kwargs, &block|
|
||||
original_method.bind_call(self, *args, **kwargs, &block)
|
||||
rescue StandardError => e
|
||||
handle_channel_error
|
||||
raise e
|
||||
|
||||
@ -71,8 +71,8 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
end
|
||||
|
||||
# TODO: See if we can unify the API versions and for both paths and make it consistent with out facebook app API versions
|
||||
def phone_id_path
|
||||
"#{api_base_path}/v13.0/#{whatsapp_channel.provider_config['phone_number_id']}"
|
||||
def phone_id_path(version = 'v13.0')
|
||||
"#{api_base_path}/#{version}/#{whatsapp_channel.provider_config['phone_number_id']}"
|
||||
end
|
||||
|
||||
def business_account_path
|
||||
@ -181,4 +181,41 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
|
||||
process_response(response, message)
|
||||
end
|
||||
|
||||
def toggle_typing_status(typing_status, last_message:, **)
|
||||
return false unless [Events::Types::CONVERSATION_TYPING_ON, Events::Types::CONVERSATION_RECORDING].include?(typing_status)
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path('v23.0')}/messages",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: last_message.source_id,
|
||||
# NOTE: API currently only supports "typing", no "recording" status.
|
||||
typing_indicator: { type: 'text' }
|
||||
}.to_json
|
||||
)
|
||||
|
||||
Rails.logger.error(response.parsed_response) unless response.success?
|
||||
|
||||
response.success?
|
||||
end
|
||||
|
||||
def read_messages(messages, **)
|
||||
# NOTE: Marking the last message as read automatically applies to all previous ones.
|
||||
message = messages.last
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path('v23.0')}/messages",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
status: 'read'
|
||||
}.to_json
|
||||
)
|
||||
|
||||
Rails.logger.error(response.parsed_response) unless response.success?
|
||||
|
||||
response.success?
|
||||
end
|
||||
end
|
||||
|
||||
@ -186,7 +186,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
it 'calls provider service method' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, toggle_typing_status: nil)
|
||||
allow(provider_double).to receive(:toggle_typing_status)
|
||||
.with(conversation.contact.phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
||||
.with(Events::Types::CONVERSATION_TYPING_ON, phone_number: conversation.contact.phone_number)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -246,7 +246,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
|
||||
it 'calls provider service method' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||
allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
|
||||
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -259,7 +259,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
it 'call method when the provider config mark_as_read is nil' do
|
||||
channel.update!(provider_config: {})
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||
allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
|
||||
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
@ -270,7 +270,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
end
|
||||
|
||||
it 'does not call method if provider service does not implement it' do
|
||||
channel.update!(provider: 'whatsapp_cloud')
|
||||
channel.update!(provider: 'default')
|
||||
|
||||
expect do
|
||||
channel.read_messages([message], conversation: conversation)
|
||||
|
||||
@ -477,7 +477,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
|
||||
).to_return(status: 200, body: '', headers: {})
|
||||
|
||||
result = service.read_messages(test_send_phone_number, [message])
|
||||
result = service.read_messages([message], phone_number: test_send_phone_number)
|
||||
|
||||
expect(result).to be(true)
|
||||
end
|
||||
@ -495,7 +495,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(status: 200)
|
||||
|
||||
expect do
|
||||
service.read_messages(test_send_phone_number, [message])
|
||||
service.read_messages([message], phone_number: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
end
|
||||
end
|
||||
@ -605,7 +605,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -621,7 +621,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_RECORDING)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, phone_number: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -637,7 +637,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
)
|
||||
.to_return(status: 200)
|
||||
|
||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_OFF)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, phone_number: test_send_phone_number)
|
||||
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
@ -664,7 +664,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
allow(Rails.logger).to receive(:error)
|
||||
|
||||
expect do
|
||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
|
||||
expect(Rails.logger).to have_received(:error).with('error message')
|
||||
@ -916,7 +916,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
||||
|
||||
expect do
|
||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||
|
||||
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
|
||||
|
||||
@ -312,4 +312,91 @@ describe Whatsapp::Providers::WhatsappCloudService do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#toggle_typing_status' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
|
||||
it 'calls messages endpoint with typing indicator for "conversation.typing_on"' do
|
||||
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||
.with(
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
typing_indicator: { type: 'text' }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||
|
||||
expect(service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, last_message: message)).to be(true)
|
||||
end
|
||||
|
||||
it 'calls messages endpoint with typing indicator for "conversation.recording"' do
|
||||
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||
.with(
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
typing_indicator: { type: 'text' }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||
|
||||
expect(service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, last_message: message)).to be(true)
|
||||
end
|
||||
|
||||
it 'does not call messages endpoint with typing indicator for "conversation.typing_off"' do
|
||||
expect(service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, last_message: message)).to be(false)
|
||||
end
|
||||
|
||||
it 'logs error on failure' do
|
||||
allow(Rails.logger).to receive(:error).with('Request failed')
|
||||
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||
.with(
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
typing_indicator: { type: 'text' }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(status: 500, body: 'Request failed')
|
||||
|
||||
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, last_message: message)
|
||||
|
||||
expect(Rails.logger).to have_received(:error)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#read_messages' do
|
||||
it 'calls messages endpoint to mark last message as read' do
|
||||
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||
.with(
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
status: 'read'
|
||||
}.to_json
|
||||
)
|
||||
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||
|
||||
messages = [create(:message), message]
|
||||
expect(service.read_messages(messages)).to be(true)
|
||||
end
|
||||
|
||||
it 'logs error on failure' do
|
||||
allow(Rails.logger).to receive(:error).with('Request failed')
|
||||
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||
.with(
|
||||
body: {
|
||||
messaging_product: 'whatsapp',
|
||||
message_id: message.source_id,
|
||||
status: 'read'
|
||||
}.to_json
|
||||
)
|
||||
.to_return(status: 500, body: 'Request failed')
|
||||
|
||||
service.read_messages([message])
|
||||
|
||||
expect(Rails.logger).to have_received(:error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user