From 6b767dbe94fd0346085a41e92fa2786e54867405 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 15 May 2025 04:10:11 +0530 Subject: [PATCH 1/3] fix: Duplicate attachments when navigating back to a conversation (#11472) # Pull Request Template ## Description Fixes [CW-4330](https://linear.app/chatwoot/issue/CW-4330/bug-attachments-in-gallery-view-gets-duplicated-when-you-navigate-back) **Cause** When navigating back to a previously viewed conversation, attachments were being duplicated in the Gallery View. This occurred because the `SET_ALL_ATTACHMENTS` mutation was appending new attachments to existing ones `(attachments.push(...data))` instead of replacing them. Each time a user returned to a conversation, the attachment list would grow, showing duplicate images. **Solution** Updated the `SET_ALL_ATTACHMENTS` mutation to replace the entire attachments array instead of appending to it. This change ensures attachments are cleared before adding new ones when returning to a previously viewed conversation, preventing duplicates. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/b2472c07e95843f6879724b19ef89311?sid=eed42238-b4bc-4dbe-8ede-a25a13b4610d ## Checklist: - [x] My code follows the style guidelines of this project - [x] 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 - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- .../dashboard/store/modules/conversations/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index ca0759320..fc6f10979 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -78,10 +78,7 @@ export const mutations = { } }, [types.SET_ALL_ATTACHMENTS](_state, { id, data }) { - const attachments = _state.attachments[id] || []; - - attachments.push(...data); - _state.attachments[id] = [...attachments]; + _state.attachments[id] = [...data]; }, [types.SET_MISSING_MESSAGES](_state, { id, data }) { const [chat] = _state.allConversations.filter(c => c.id === id); From ac884f58f5f19a84a5d374467876feae6165e8a5 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 15 May 2025 12:02:53 +0530 Subject: [PATCH 2/3] fix: Handle Instagram user consent error for first-time message recipients (#11484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Issue** When attempting to send a message to Instagram users who have never previously messaged the connected Instagram account, we encounter a "User consent is required to access user profile" error⁠ ⁠ **Fix** This PR implements proper error handling for cases where user consent is required. We can only access a user's profile after they have initiated contact with the Instagram account⁠ --- app/services/instagram/message_text.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/services/instagram/message_text.rb b/app/services/instagram/message_text.rb index 66445f5a5..e4b88c64d 100644 --- a/app/services/instagram/message_text.rb +++ b/app/services/instagram/message_text.rb @@ -41,10 +41,20 @@ class Instagram::MessageText < Instagram::BaseMessageText # Access token has expired or become invalid. channel.authorization_error! if error_code == 190 + # TODO: Remove this once we have a better way to handle this error. + # https://developers.facebook.com/docs/messenger-platform/instagram/features/user-profile/#user-consent + # The error typically occurs when the connected Instagram account attempts to send a message to a user + # who has never messaged this Instagram account before. + # We can only get consent to access a user's profile if they have previously sent a message to the connected Instagram account. + # In such cases, we receive the error "User consent is required to access user profile". + # We can safely ignore this error. + return if error_code == 230 + Rails.logger.warn("[InstagramUserFetchError]: account_id #{@inbox.account_id} inbox_id #{@inbox.id}") Rails.logger.warn("[InstagramUserFetchError]: #{error_message} #{error_code}") - ChatwootExceptionTracker.new(parsed_response, account: @inbox.account).capture_exception + exception = StandardError.new("#{error_message} (Code: #{error_code})") + ChatwootExceptionTracker.new(exception, account: @inbox.account).capture_exception end def base_uri From a4c7b738885fac80a16aeef70a32cb6ff3fe49b0 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 14 May 2025 23:58:54 -0700 Subject: [PATCH 3/3] fix: Manage Twilio SMS channel via inbox API (#11457) - Currently Chatwoot manages Twilio Inbox creation in a separate controller , We want to move this to inboxes api so that we have a unified API for inbox related changes --------- Co-authored-by: Muhsin Keloth --- .../channels/twilio_channels_controller.rb | 3 ++ app/models/channel/twilio_sms.rb | 5 +++ app/views/api/v1/models/_inbox.json.jbuilder | 4 ++ .../v1/accounts/inboxes_controller_spec.rb | 42 +++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb index ebf8e49dd..58ec3bfca 100644 --- a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb +++ b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb @@ -1,3 +1,6 @@ +# TODO : Move this to inboxes controller and deprecate this controller +# No need to retain this controller as we could handle everything centrally in inboxes controller + class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts::BaseController before_action :authorize_request diff --git a/app/models/channel/twilio_sms.rb b/app/models/channel/twilio_sms.rb index 8dbe47703..e4575f58a 100644 --- a/app/models/channel/twilio_sms.rb +++ b/app/models/channel/twilio_sms.rb @@ -30,6 +30,11 @@ class Channel::TwilioSms < ApplicationRecord # The same parameter is used to store api_key_secret if api_key authentication is opted validates :auth_token, presence: true + EDITABLE_ATTRS = [ + :account_sid, + :auth_token + ].freeze + # Must have _one_ of messaging_service_sid _or_ phone_number, and messaging_service_sid is preferred validates :messaging_service_sid, uniqueness: true, presence: true, unless: :phone_number? validates :phone_number, absence: true, if: :messaging_service_sid? diff --git a/app/views/api/v1/models/_inbox.json.jbuilder b/app/views/api/v1/models/_inbox.json.jbuilder index 42a1a7370..0747924d9 100644 --- a/app/views/api/v1/models/_inbox.json.jbuilder +++ b/app/views/api/v1/models/_inbox.json.jbuilder @@ -62,6 +62,10 @@ json.instagram_id resource.channel.try(:instagram_id) if resource.instagram? json.messaging_service_sid resource.channel.try(:messaging_service_sid) json.phone_number resource.channel.try(:phone_number) json.medium resource.channel.try(:medium) if resource.twilio? +if resource.twilio? && Current.account_user&.administrator? + json.auth_token resource.channel.try(:auth_token) + json.account_sid resource.channel.try(:account_sid) +end if resource.email? ## Email Channel Attributes diff --git a/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb b/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb index 7b3dafccb..f8655da44 100644 --- a/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/inboxes_controller_spec.rb @@ -147,6 +147,32 @@ RSpec.describe 'Inboxes API', type: :request do expect(data[:imap_login]).to eq('test@test.com') end + context 'when it is a Twilio inbox' do + let(:twilio_channel) { create(:channel_twilio_sms, account: account, account_sid: 'AC123', auth_token: 'secrettoken') } + let(:twilio_inbox) { create(:inbox, channel: twilio_channel, account: account) } + + it 'returns auth_token and account_sid for admin' do + get "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}", + headers: admin.create_new_auth_token, + as: :json + expect(response).to have_http_status(:success) + data = JSON.parse(response.body, symbolize_names: true) + expect(data[:auth_token]).to eq('secrettoken') + expect(data[:account_sid]).to eq('AC123') + end + + it "doesn't return auth_token and account_sid for agent" do + create(:inbox_member, user: agent, inbox: twilio_inbox) + get "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}", + headers: agent.create_new_auth_token, + as: :json + expect(response).to have_http_status(:success) + data = JSON.parse(response.body, symbolize_names: true) + expect(data[:auth_token]).to be_nil + expect(data[:account_sid]).to be_nil + end + end + it 'fetch API inbox without hmac token when agent' do api_channel = create(:channel_api, account: account) api_inbox = create(:inbox, channel: api_channel, account: account) @@ -518,6 +544,22 @@ RSpec.describe 'Inboxes API', type: :request do expect(email_channel.reload.email).to eq('emailtest@email.test') end + it 'updates twilio sms inbox when administrator' do + twilio_sms_channel = create(:channel_twilio_sms, account: account) + twilio_sms_inbox = create(:inbox, channel: twilio_sms_channel, account: account) + expect(twilio_sms_inbox.reload.channel.account_sid).not_to eq('account_sid') + expect(twilio_sms_inbox.reload.channel.auth_token).not_to eq('new_auth_token') + + patch "/api/v1/accounts/#{account.id}/inboxes/#{twilio_sms_inbox.id}", + headers: admin.create_new_auth_token, + params: { channel: { account_sid: 'account_sid', auth_token: 'new_auth_token' } }, + as: :json + + expect(response).to have_http_status(:success) + expect(twilio_sms_inbox.reload.channel.account_sid).to eq('account_sid') + expect(twilio_sms_inbox.reload.channel.auth_token).to eq('new_auth_token') + end + it 'updates email inbox with imap when administrator' do email_channel = create(:channel_email, account: account) email_inbox = create(:inbox, channel: email_channel, account: account)