diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb
index 1f59deadb..24b6d9e70 100644
--- a/app/builders/messages/facebook/message_builder.rb
+++ b/app/builders/messages/facebook/message_builder.rb
@@ -92,10 +92,18 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
def fallback_params(attachment)
{
fallback_title: attachment['title'],
- external_url: attachment['url']
+ external_url: attachment['url'] || attachment.dig('payload', 'url')
}
end
+ # Facebook shared posts point to page URLs, not downloadable media URLs.
+ # Keep this Facebook-only so Messenger/Instagram share attachments still use the parent media handling.
+ def normalize_file_type(type)
+ return :fallback if type.to_sym == :share
+
+ super
+ end
+
def conversation_params
{
account_id: @inbox.account_id,
diff --git a/app/javascript/dashboard/components-next/message/Message.vue b/app/javascript/dashboard/components-next/message/Message.vue
index 72477bbd8..0ef64eedf 100644
--- a/app/javascript/dashboard/components-next/message/Message.vue
+++ b/app/javascript/dashboard/components-next/message/Message.vue
@@ -31,6 +31,7 @@ import FileBubble from './bubbles/File.vue';
import AudioBubble from './bubbles/Audio.vue';
import VideoBubble from './bubbles/Video.vue';
import EmbedBubble from './bubbles/Embed.vue';
+import FallbackBubble from './bubbles/Fallback.vue';
import InstagramStoryBubble from './bubbles/InstagramStory.vue';
import EmailBubble from './bubbles/Email/Index.vue';
import UnsupportedBubble from './bubbles/Unsupported.vue';
@@ -328,6 +329,8 @@ const componentToRender = computed(() => {
if (Array.isArray(props.attachments) && props.attachments.length === 1) {
const fileType = props.attachments[0].fileType;
+ if (fileType === ATTACHMENT_TYPES.FALLBACK) return FallbackBubble;
+
if (!props.content) {
if (fileType === ATTACHMENT_TYPES.IMAGE) return ImageBubble;
if (fileType === ATTACHMENT_TYPES.FILE) return FileBubble;
diff --git a/app/javascript/dashboard/components-next/message/bubbles/Fallback.vue b/app/javascript/dashboard/components-next/message/bubbles/Fallback.vue
new file mode 100644
index 000000000..44d9fbe11
--- /dev/null
+++ b/app/javascript/dashboard/components-next/message/bubbles/Fallback.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ {{ title }}
+
+
+ {{ title }}
+
+
+
diff --git a/spec/builders/messages/facebook/message_builder_spec.rb b/spec/builders/messages/facebook/message_builder_spec.rb
index f3244bc21..afa9d5f34 100644
--- a/spec/builders/messages/facebook/message_builder_spec.rb
+++ b/spec/builders/messages/facebook/message_builder_spec.rb
@@ -140,6 +140,45 @@ describe Messages::Facebook::MessageBuilder do
end
end
+ [
+ {
+ source_id: 'm_fallback_test',
+ attachment: { type: 'fallback', title: 'Shared link', url: 'https://www.example.com/shared-link' },
+ title: 'Shared link',
+ url: 'https://www.example.com/shared-link'
+ },
+ {
+ source_id: 'm_share_test',
+ attachment: { type: 'share', title: 'Shared Facebook post', payload: { url: 'https://www.facebook.com/example/posts/123' } },
+ title: 'Shared Facebook post',
+ url: 'https://www.facebook.com/example/posts/123'
+ }
+ ].each do |message_data|
+ it "stores #{message_data[:attachment][:type]} attachments as fallback links" do
+ allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
+ allow(fb_object).to receive(:get_object).and_return(
+ { first_name: 'Jane', last_name: 'Dae', profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access
+ )
+ expect(Down).not_to receive(:download)
+
+ message_object = {
+ messaging: {
+ sender: { id: '3383290475046708' },
+ recipient: { id: facebook_channel.page_id },
+ message: { mid: message_data[:source_id], attachments: [message_data[:attachment]] }
+ }
+ }.to_json
+ message = Integrations::Facebook::MessageParser.new(message_object)
+
+ described_class.new(message, facebook_channel.inbox).perform
+
+ attachment = facebook_channel.inbox.messages.find_by(source_id: message_data[:source_id]).attachments.first
+ expect(attachment.file_type).to eq('fallback')
+ expect(attachment.fallback_title).to eq(message_data[:title])
+ expect(attachment.external_url).to eq(message_data[:url])
+ end
+ end
+
context 'when lock to single conversation' do
subject(:mocked_message_builder) do
described_class.new(mocked_incoming_fb_text_message, facebook_channel.inbox).perform