chore: Migrate PubSub Token to contact inbox (#3434)

At present, the websocket pubsub tokens are present at the contact objects in chatwoot. A better approach would be to have these tokens at the contact_inbox object instead. This helps chatwoot to deliver the websocket events targetted to the specific widget connection, stop contact events from leaking into other chat sessions from the same contact.

Fixes #1682
Fixes #1664

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sojan Jose
2021-11-22 23:32:17 +05:30
committed by GitHub
parent 01577acb2e
commit 791d90c6b7
38 changed files with 211 additions and 95 deletions

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
RSpec.describe RoomChannel, type: :channel do
let!(:contact) { create(:contact) }
let!(:contact_inbox) { create(:contact_inbox) }
before do
stub_connection
end
it 'subscribes to a stream when pubsub_token is provided' do
subscribe(pubsub_token: contact.pubsub_token)
subscribe(pubsub_token: contact_inbox.pubsub_token)
expect(subscription).to be_confirmed
expect(subscription).to have_stream_for(contact.pubsub_token)
expect(subscription).to have_stream_for(contact_inbox.pubsub_token)
end
end

View File

@@ -45,7 +45,7 @@ RSpec.describe '/api/v1/widget/config', type: :request do
expect(response).to have_http_status(:success)
response_data = JSON.parse(response.body)
expect(response_data.keys).to include(*response_keys)
expect(response_data['contact']['pubsub_token']).to eq(contact.pubsub_token)
expect(response_data['contact']['pubsub_token']).to eq(contact_inbox.pubsub_token)
end
end

View File

@@ -23,7 +23,7 @@ RSpec.describe 'Public Inbox Contacts API', type: :request do
expect(response).to have_http_status(:success)
data = JSON.parse(response.body)
expect(data['source_id']).to eq contact_inbox.source_id
expect(data['pubsub_token']).to eq contact.pubsub_token
expect(data['pubsub_token']).to eq contact_inbox.pubsub_token
end
end

View File

@@ -24,7 +24,23 @@ describe ActionCableListener do
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token],
[agent.pubsub_token, admin.pubsub_token, conversation.contact_inbox.pubsub_token],
'message.created',
message.push_event_data.merge(account_id: account.id)
)
listener.message_created(event)
end
it 'sends message to all hmac verified contact inboxes' do
# HACK: to reload conversation inbox members
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
conversation.contact_inbox.update(hmac_verified: true)
# creating a non verified contact inbox to ensure the events are not sent to it
create(:contact_inbox, contact: conversation.contact, inbox: inbox)
verified_contact_inbox = create(:contact_inbox, contact: conversation.contact, inbox: inbox, hmac_verified: true)
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[agent.pubsub_token, admin.pubsub_token, conversation.contact_inbox.pubsub_token, verified_contact_inbox.pubsub_token],
'message.created',
message.push_event_data.merge(account_id: account.id)
)

View File

@@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ContactInbox do
describe 'pubsub_token' do
let(:contact_inbox) { create(:contact_inbox) }
it 'gets created on object create' do
obj = contact_inbox
expect(obj.pubsub_token).not_to eq(nil)
end
it 'does not get updated on object update' do
obj = contact_inbox
old_token = obj.pubsub_token
obj.update(source_id: '234234323')
expect(obj.pubsub_token).to eq(old_token)
end
it 'backfills pubsub_token on call for older objects' do
obj = create(:contact_inbox)
# to replicate an object with out pubsub_token
# rubocop:disable Rails/SkipsModelValidations
obj.update_column(:pubsub_token, nil)
# rubocop:enable Rails/SkipsModelValidations
obj.reload
# ensure the column is nil in database
results = ActiveRecord::Base.connection.execute('Select * from contact_inboxes;')
expect(results.first['pubsub_token']).to eq(nil)
new_token = obj.pubsub_token
obj.update(source_id: '234234323')
# the generated token shoul be persisted in db
expect(obj.pubsub_token).to eq(new_token)
end
end
end

View File

@@ -11,20 +11,4 @@ RSpec.describe Contact do
it { is_expected.to belong_to(:account) }
it { is_expected.to have_many(:conversations).dependent(:destroy) }
end
describe 'pubsub_token' do
let(:user) { create(:user) }
it 'gets created on object create' do
obj = user
expect(obj.pubsub_token).not_to eq(nil)
end
it 'does not get updated on object update' do
obj = user
old_token = obj.pubsub_token
obj.update(name: 'test')
expect(obj.pubsub_token).to eq(old_token)
end
end
end

View File

@@ -375,7 +375,8 @@ RSpec.describe Conversation, type: :model do
additional_attributes: {},
meta: {
sender: conversation.contact.push_event_data,
assignee: conversation.assignee
assignee: conversation.assignee,
hmac_verified: conversation.contact_inbox.hmac_verified
},
id: conversation.display_id,
messages: [],

View File

@@ -12,7 +12,8 @@ RSpec.describe Conversations::EventDataPresenter do
additional_attributes: {},
meta: {
sender: conversation.contact.push_event_data,
assignee: conversation.assignee
assignee: conversation.assignee,
hmac_verified: conversation.contact_inbox.hmac_verified
},
id: conversation.display_id,
messages: [],