Merge branch 'develop' into codex/fix-inbox-finish-setup-guidance
This commit is contained in:
@@ -23,6 +23,8 @@ class BaseRefreshOauthTokenService
|
||||
# Refresh the access tokens using the refresh token
|
||||
# Refer: https://github.com/microsoftgraph/msgraph-sample-rubyrailsapp/tree/b4a6869fe4a438cde42b161196484a929f1bee46
|
||||
def refresh_tokens
|
||||
raise 'A refresh_token is not available' if provider_config[:refresh_token].blank?
|
||||
|
||||
oauth_strategy = build_oauth_strategy
|
||||
token_service = build_token_service(oauth_strategy)
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ class Conversations::UnreadCounts::Counter
|
||||
ensure_base_cache!
|
||||
ensure_assignment_cache! if assignment_mode?
|
||||
|
||||
inbox_counts = unread_inbox_counts
|
||||
|
||||
{
|
||||
inboxes: unread_inbox_counts,
|
||||
all_count: inbox_counts.values.sum,
|
||||
inboxes: inbox_counts,
|
||||
labels: unread_label_counts,
|
||||
teams: unread_team_counts
|
||||
}
|
||||
@@ -191,7 +194,7 @@ class Conversations::UnreadCounts::Counter
|
||||
end
|
||||
|
||||
def empty_counts
|
||||
{ inboxes: {}, labels: {}, teams: {} }
|
||||
{ all_count: 0, inboxes: {}, labels: {}, teams: {} }
|
||||
end
|
||||
|
||||
def store
|
||||
|
||||
@@ -45,12 +45,12 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
|
||||
end
|
||||
|
||||
def fb_text_message_params
|
||||
{
|
||||
params = {
|
||||
recipient: { id: contact.get_source_id(inbox.id) },
|
||||
message: fb_text_message_payload,
|
||||
messaging_type: 'MESSAGE_TAG',
|
||||
tag: message_tag
|
||||
message: fb_text_message_payload
|
||||
}
|
||||
|
||||
merge_human_agent_tag(params)
|
||||
end
|
||||
|
||||
def fb_text_message_payload
|
||||
@@ -79,7 +79,7 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
|
||||
end
|
||||
|
||||
def fb_attachment_message_params(attachment)
|
||||
{
|
||||
params = {
|
||||
recipient: { id: contact.get_source_id(inbox.id) },
|
||||
message: {
|
||||
attachment: {
|
||||
@@ -88,14 +88,21 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
|
||||
url: attachment.download_url
|
||||
}
|
||||
}
|
||||
},
|
||||
messaging_type: 'MESSAGE_TAG',
|
||||
tag: message_tag
|
||||
}
|
||||
}
|
||||
|
||||
merge_human_agent_tag(params)
|
||||
end
|
||||
|
||||
def message_tag
|
||||
@message_tag ||= GlobalConfigService.load('ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT', nil) ? 'HUMAN_AGENT' : 'ACCOUNT_UPDATE'
|
||||
def merge_human_agent_tag(params)
|
||||
unless GlobalConfigService.load('ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT', nil)
|
||||
params[:messaging_type] = 'RESPONSE'
|
||||
return params
|
||||
end
|
||||
|
||||
params[:messaging_type] = 'MESSAGE_TAG'
|
||||
params[:tag] = 'HUMAN_AGENT'
|
||||
params
|
||||
end
|
||||
|
||||
def attachment_type(attachment)
|
||||
@@ -104,11 +111,6 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
|
||||
'file'
|
||||
end
|
||||
|
||||
def sent_first_outgoing_message_after_24_hours?
|
||||
# we can send max 1 message after 24 hour window
|
||||
conversation.messages.outgoing.where('id > ?', conversation.last_incoming_message.id).count == 1
|
||||
end
|
||||
|
||||
def handle_facebook_error(exception)
|
||||
# Refer: https://github.com/jgorset/facebook-messenger/blob/64fe1f5cef4c1e3fca295b205037f64dfebdbcab/lib/facebook/messenger/error.rb
|
||||
return unless exception.to_s.include?('The session has been invalidated') || exception.to_s.include?('Error validating access token')
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class Mailbox::ConversationFinderStrategies::BaseStrategy
|
||||
include MailboxSanitizer
|
||||
|
||||
attr_reader :mail
|
||||
|
||||
def initialize(mail)
|
||||
|
||||
@@ -14,7 +14,7 @@ class Mailbox::ConversationFinderStrategies::InReplyToStrategy < Mailbox::Conver
|
||||
def find
|
||||
return nil if mail.in_reply_to.blank?
|
||||
|
||||
in_reply_to_addresses = Array.wrap(mail.in_reply_to)
|
||||
in_reply_to_addresses = sanitize_mailbox_value(Array.wrap(mail.in_reply_to))
|
||||
|
||||
in_reply_to_addresses.each do |in_reply_to|
|
||||
# Try extracting UUID from patterns
|
||||
|
||||
@@ -45,11 +45,11 @@ class Mailbox::ConversationFinderStrategies::NewConversationStrategy < Mailbox::
|
||||
end
|
||||
|
||||
def original_sender_email
|
||||
@processed_mail.original_sender&.downcase
|
||||
sanitize_mailbox_value(@processed_mail.original_sender)&.downcase
|
||||
end
|
||||
|
||||
def identify_contact_name
|
||||
@processed_mail.sender_name || @processed_mail.from.first.split('@').first
|
||||
sanitize_mailbox_value(@processed_mail.sender_name || @processed_mail.from.first.split('@').first)
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@@ -63,7 +63,7 @@ class Mailbox::ConversationFinderStrategies::NewConversationStrategy < Mailbox::
|
||||
in_reply_to: in_reply_to,
|
||||
source: 'email',
|
||||
auto_reply: @processed_mail.auto_reply?,
|
||||
mail_subject: @processed_mail.subject,
|
||||
mail_subject: sanitize_mailbox_value(@processed_mail.subject),
|
||||
initiated_at: {
|
||||
timestamp: Time.now.utc
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class Mailbox::ConversationFinderStrategies::NewConversationStrategy < Mailbox::
|
||||
end
|
||||
|
||||
def in_reply_to
|
||||
mail['In-Reply-To'].try(:value)
|
||||
sanitize_mailbox_value(mail['In-Reply-To'].try(:value))
|
||||
end
|
||||
|
||||
def find_conversation_by_in_reply_to
|
||||
|
||||
@@ -21,7 +21,7 @@ class Mailbox::ConversationFinderStrategies::ReferencesStrategy < Mailbox::Conve
|
||||
return nil if mail.references.blank?
|
||||
return nil unless @channel # No valid channel found
|
||||
|
||||
references = Array.wrap(mail.references)
|
||||
references = sanitize_mailbox_value(Array.wrap(mail.references))
|
||||
|
||||
references.each do |reference|
|
||||
conversation = find_conversation_from_reference(reference)
|
||||
|
||||
@@ -37,12 +37,18 @@ class WebsiteBrandingService
|
||||
private
|
||||
|
||||
def fetch_page
|
||||
response = HTTParty.get(@url, follow_redirects: true, timeout: 15)
|
||||
@http_status = response.code
|
||||
return nil unless response.success?
|
||||
body = nil
|
||||
SafeFetch.fetch(@url, validate_content_type: false) do |result|
|
||||
body = result.tempfile.read
|
||||
end
|
||||
@http_status = 200
|
||||
return nil if body.blank?
|
||||
|
||||
Nokogiri::HTML(response.body)
|
||||
rescue StandardError => e
|
||||
Nokogiri::HTML(body)
|
||||
rescue SafeFetch::HttpError => e
|
||||
@http_status = e.message.to_i
|
||||
nil
|
||||
rescue SafeFetch::Error => e
|
||||
Rails.logger.error "[WebsiteBranding] Failed to fetch #{@url}: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -13,7 +13,6 @@ class Whatsapp::EmbeddedSignupService
|
||||
|
||||
access_token = exchange_code_for_token
|
||||
phone_info = fetch_phone_info(access_token)
|
||||
validate_token_access(access_token)
|
||||
|
||||
channel = create_or_reauthorize_channel(access_token, phone_info)
|
||||
# NOTE: We call setup_webhooks explicitly here instead of relying on after_commit callback because:
|
||||
@@ -42,10 +41,6 @@ class Whatsapp::EmbeddedSignupService
|
||||
Whatsapp::PhoneInfoService.new(@waba_id, @phone_number_id, access_token).perform
|
||||
end
|
||||
|
||||
def validate_token_access(access_token)
|
||||
Whatsapp::TokenValidationService.new(access_token, @waba_id).perform
|
||||
end
|
||||
|
||||
def create_or_reauthorize_channel(access_token, phone_info)
|
||||
if @inbox_id.present?
|
||||
Whatsapp::ReauthorizationService.new(
|
||||
|
||||
@@ -13,8 +13,17 @@ class Whatsapp::IncomingMessageWhatsappCloudService < Whatsapp::IncomingMessageB
|
||||
inbox.channel.media_url(attachment_payload[:id]),
|
||||
headers: inbox.channel.api_headers
|
||||
)
|
||||
|
||||
# This url response will be failure if the access token has expired.
|
||||
inbox.channel.authorization_error! if url_response.unauthorized?
|
||||
Down.download(url_response.parsed_response['url'], headers: inbox.channel.api_headers) if url_response.success?
|
||||
|
||||
return unless url_response.success?
|
||||
|
||||
downloaded_file = Down.download(url_response.parsed_response['url'], headers: inbox.channel.api_headers)
|
||||
# WhatsApp Cloud sends the original filename in the payload; preserve it so accented
|
||||
# names keep their correct extension instead of relying on the mangled remote metadata.
|
||||
filename = attachment_payload[:filename]
|
||||
downloaded_file.define_singleton_method(:original_filename) { filename } if filename.present?
|
||||
downloaded_file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
class Whatsapp::TokenValidationService
|
||||
def initialize(access_token, waba_id)
|
||||
@access_token = access_token
|
||||
@waba_id = waba_id
|
||||
@api_client = Whatsapp::FacebookApiClient.new(access_token)
|
||||
end
|
||||
|
||||
def perform
|
||||
validate_parameters!
|
||||
validate_token_waba_access
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_parameters!
|
||||
raise ArgumentError, 'Access token is required' if @access_token.blank?
|
||||
raise ArgumentError, 'WABA ID is required' if @waba_id.blank?
|
||||
end
|
||||
|
||||
def validate_token_waba_access
|
||||
token_debug_data = @api_client.debug_token(@access_token)
|
||||
waba_scope = extract_waba_scope(token_debug_data)
|
||||
verify_waba_authorization(waba_scope)
|
||||
end
|
||||
|
||||
def extract_waba_scope(token_data)
|
||||
granular_scopes = token_data.dig('data', 'granular_scopes')
|
||||
waba_scope = granular_scopes&.find { |scope| scope['scope'] == 'whatsapp_business_management' }
|
||||
|
||||
raise 'No WABA scope found in token' unless waba_scope
|
||||
|
||||
waba_scope
|
||||
end
|
||||
|
||||
def verify_waba_authorization(waba_scope)
|
||||
authorized_waba_ids = waba_scope['target_ids'] || []
|
||||
|
||||
return if authorized_waba_ids.include?(@waba_id)
|
||||
|
||||
raise "Token does not have access to WABA #{@waba_id}. Authorized WABAs: #{authorized_waba_ids}"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user