fix: strip markdown hard-break backslashes from webhook payloads (#13950)
This commit is contained in:
@@ -10,6 +10,6 @@ module PushDataHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def webhook_data
|
def webhook_data
|
||||||
Conversations::EventDataPresenter.new(self).push_data
|
Conversations::EventDataPresenter.new(self).webhook_data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -170,6 +170,13 @@ class Message < ApplicationRecord
|
|||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_push_event_data
|
||||||
|
push_event_data.merge(
|
||||||
|
content: Messages::WebhookContentNormalizer.normalize(content),
|
||||||
|
processed_message_content: Messages::WebhookContentNormalizer.normalize(processed_message_content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def webhook_data
|
def webhook_data
|
||||||
data = {
|
data = {
|
||||||
account: account.webhook_data,
|
account: account.webhook_data,
|
||||||
|
|||||||
@@ -21,12 +21,21 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Like #push_data but with message text normalized for external integrations (webhooks).
|
||||||
|
def webhook_data
|
||||||
|
push_data.merge(messages: webhook_push_messages)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def push_messages
|
def push_messages
|
||||||
[messages.where(account_id: account_id).chat.last&.push_event_data].compact
|
[messages.where(account_id: account_id).chat.last&.push_event_data].compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_push_messages
|
||||||
|
[messages.where(account_id: account_id).chat.last&.webhook_push_event_data].compact
|
||||||
|
end
|
||||||
|
|
||||||
def push_meta
|
def push_meta
|
||||||
{
|
{
|
||||||
sender: contact.push_event_data,
|
sender: contact.push_event_data,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class MessageContentPresenter < SimpleDelegator
|
|||||||
end
|
end
|
||||||
|
|
||||||
def webhook_content
|
def webhook_content
|
||||||
content_with_survey_link
|
Messages::WebhookContentNormalizer.normalize(content_with_survey_link)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
10
app/services/messages/webhook_content_normalizer.rb
Normal file
10
app/services/messages/webhook_content_normalizer.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Strips CommonMark hard line breaks from stored markdown source (backslash before newline).
|
||||||
|
# ProseMirror / the dashboard editor emits this form so soft breaks survive as markdown;
|
||||||
|
# webhook consumers expect plain newlines without a visible backslash (e.g. WhatsApp gateways).
|
||||||
|
class Messages::WebhookContentNormalizer
|
||||||
|
def self.normalize(text)
|
||||||
|
return text if text.blank?
|
||||||
|
|
||||||
|
text.gsub(/\\\r?\n/, "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -44,4 +44,29 @@ RSpec.describe Conversations::EventDataPresenter do
|
|||||||
expect(presenter.push_data.except(:applied_sla, :sla_events)).to include(expected_data)
|
expect(presenter.push_data.except(:applied_sla, :sla_events)).to include(expected_data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#webhook_data' do
|
||||||
|
it 'normalizes hard-break backslashes in message content' do
|
||||||
|
message = create(:message, conversation: conversation, account: conversation.account,
|
||||||
|
message_type: :outgoing, content: "Hello\\\nWorld")
|
||||||
|
data = presenter.webhook_data
|
||||||
|
webhook_message = data[:messages].first
|
||||||
|
|
||||||
|
expect(webhook_message).to be_present
|
||||||
|
expect(webhook_message[:content]).to eq("Hello\nWorld")
|
||||||
|
expect(webhook_message[:id]).to eq(message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'preserves normal newlines in message content' do
|
||||||
|
create(:message, conversation: conversation, account: conversation.account,
|
||||||
|
message_type: :outgoing, content: "Line one\n\nLine two")
|
||||||
|
webhook_message = presenter.webhook_data[:messages].first
|
||||||
|
|
||||||
|
expect(webhook_message[:content]).to eq("Line one\n\nLine two")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty messages when conversation has no chat messages' do
|
||||||
|
expect(presenter.webhook_data[:messages]).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -63,6 +63,26 @@ RSpec.describe MessageContentPresenter do
|
|||||||
it 'returns raw content without markdown rendering' do
|
it 'returns raw content without markdown rendering' do
|
||||||
expect(presenter.webhook_content).to eq('Regular **bold** message')
|
expect(presenter.webhook_content).to eq('Regular **bold** message')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'strips CommonMark hard-break backslashes before newlines' do
|
||||||
|
message.update!(content: "First\\\nSecond\\\nThird\\\nFourth")
|
||||||
|
expect(presenter.webhook_content).to eq("First\nSecond\nThird\nFourth")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'preserves backslashes that are not followed by a newline' do
|
||||||
|
message.update!(content: "path\\to\\file\\\nNext line")
|
||||||
|
expect(presenter.webhook_content).to eq("path\\to\\file\nNext line")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles carriage return and newline pairs' do
|
||||||
|
message.update!(content: "Line one\\\r\nLine two\\\r\nLine three")
|
||||||
|
expect(presenter.webhook_content).to eq("Line one\nLine two\nLine three")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'preserves normal newlines and only strips hard-break newlines' do
|
||||||
|
message.update!(content: "line one\\\nline two\n\nline three")
|
||||||
|
expect(presenter.webhook_content).to eq("line one\nline two\n\nline three")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when message is input_csat and inbox is not web widget' do
|
context 'when message is input_csat and inbox is not web widget' do
|
||||||
|
|||||||
Reference in New Issue
Block a user