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:
@@ -2,6 +2,10 @@
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
.margin-right-smaller {
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
.fs-small {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@@ -42,3 +46,7 @@
|
||||
.bg-white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.text-y-800 {
|
||||
color: var(--y-800);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
/>
|
||||
<div class="user--profile__meta">
|
||||
<h3 class="user--name text-truncate">
|
||||
{{ currentContact.name }}
|
||||
<span class="margin-right-smaller">{{ currentContact.name }}</span>
|
||||
<i
|
||||
v-if="!isHMACVerified"
|
||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||
class="ion-android-alert text-y-800 fs-default"
|
||||
/>
|
||||
</h3>
|
||||
<div class="conversation--header--actions">
|
||||
<inbox-name :inbox="inbox" class="margin-right-small" />
|
||||
@@ -73,11 +78,15 @@ export default {
|
||||
uiFlags: 'inboxAssignableAgents/getUIFlags',
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
|
||||
chatMetadata() {
|
||||
return this.chat.meta;
|
||||
},
|
||||
|
||||
isHMACVerified() {
|
||||
if (!this.isAWebWidgetInbox) {
|
||||
return true;
|
||||
}
|
||||
return this.chatMetadata.hmac_verified;
|
||||
},
|
||||
currentContact() {
|
||||
return this.$store.getters['contacts/getContact'](
|
||||
this.chat.meta.sender.id
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"CONVERSATION": {
|
||||
"404": "Please select a conversation from left pane",
|
||||
"UNVERIFIED_SESSION": "The identity of this user is not verified",
|
||||
"NO_MESSAGE_1": "Uh oh! Looks like there are no messages from customers in your inbox.",
|
||||
"NO_MESSAGE_2": " to send a message to your page!",
|
||||
"NO_INBOX_1": "Hola! Looks like you haven't added any inboxes yet.",
|
||||
|
||||
@@ -41,17 +41,17 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="isAnEmailInbox">
|
||||
<div v-if="isAnEmailInbox" class="row">
|
||||
<div class="columns">
|
||||
<label :class="{ error: $v.message.$error }">
|
||||
<label :class="{ error: $v.subject.$error }">
|
||||
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.LABEL') }}
|
||||
<input
|
||||
v-model="subject"
|
||||
type="text"
|
||||
:placeholder="$t('NEW_CONVERSATION.FORM.SUBJECT.PLACEHOLDER')"
|
||||
@input="$v.message.$touch"
|
||||
@input="$v.subject.$touch"
|
||||
/>
|
||||
<span v-if="$v.message.$error" class="message">
|
||||
<span v-if="$v.subject.$error" class="message">
|
||||
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
@@ -93,7 +93,7 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
validations: {
|
||||
subject: {
|
||||
required,
|
||||
required: requiredIf('isAnEmailInbox'),
|
||||
},
|
||||
message: {
|
||||
required,
|
||||
|
||||
@@ -15,18 +15,6 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
};
|
||||
}
|
||||
|
||||
static refreshConnector = pubsubToken => {
|
||||
if (!pubsubToken || window.chatwootPubsubToken === pubsubToken) {
|
||||
return;
|
||||
}
|
||||
window.chatwootPubsubToken = pubsubToken;
|
||||
window.actionCable.disconnect();
|
||||
window.actionCable = new ActionCableConnector(
|
||||
window.WOOT_WIDGET,
|
||||
window.chatwootPubsubToken
|
||||
);
|
||||
};
|
||||
|
||||
onStatusChange = data => {
|
||||
this.app.$store.dispatch('conversationAttributes/update', data);
|
||||
};
|
||||
@@ -57,7 +45,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
|
||||
onTypingOn = data => {
|
||||
if (data.is_private) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.clearTimer();
|
||||
this.app.$store.dispatch('conversation/toggleAgentTyping', {
|
||||
@@ -88,7 +76,4 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
};
|
||||
}
|
||||
|
||||
export const refreshActionCableConnector =
|
||||
ActionCableConnector.refreshConnector;
|
||||
|
||||
export default ActionCableConnector;
|
||||
|
||||
@@ -21,10 +21,16 @@ export const filterCampaigns = ({
|
||||
currentURL,
|
||||
isInBusinessHours,
|
||||
}) => {
|
||||
return campaigns.filter(item =>
|
||||
item.triggerOnlyDuringBusinessHours
|
||||
? isInBusinessHours
|
||||
: stripTrailingSlash({ URL: item.url }) ===
|
||||
stripTrailingSlash({ URL: currentURL })
|
||||
);
|
||||
return campaigns.filter(campaign => {
|
||||
const hasMatchingURL =
|
||||
stripTrailingSlash({ URL: campaign.url }) ===
|
||||
stripTrailingSlash({ URL: currentURL });
|
||||
if (!hasMatchingURL) {
|
||||
return false;
|
||||
}
|
||||
if (campaign.triggerOnlyDuringBusinessHours) {
|
||||
return isInBusinessHours;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,11 +44,13 @@ describe('#Campaigns Helper', () => {
|
||||
id: 1,
|
||||
timeOnPage: 3,
|
||||
url: 'https://www.chatwoot.com/pricing',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
],
|
||||
currentURL: 'https://www.chatwoot.com/about/',
|
||||
@@ -58,8 +60,60 @@ describe('#Campaigns Helper', () => {
|
||||
id: 2,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
it('should return filtered campaigns if formatted campaigns are passed and business hours enabled', () => {
|
||||
expect(
|
||||
filterCampaigns({
|
||||
campaigns: [
|
||||
{
|
||||
id: 1,
|
||||
timeOnPage: 3,
|
||||
url: 'https://www.chatwoot.com/pricing',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
triggerOnlyDuringBusinessHours: true,
|
||||
},
|
||||
],
|
||||
currentURL: 'https://www.chatwoot.com/about/',
|
||||
isInBusinessHours: true,
|
||||
})
|
||||
).toStrictEqual([
|
||||
{
|
||||
id: 2,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
triggerOnlyDuringBusinessHours: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
it('should return empty campaigns if formatted campaigns are passed and business hours disabled', () => {
|
||||
expect(
|
||||
filterCampaigns({
|
||||
campaigns: [
|
||||
{
|
||||
id: 1,
|
||||
timeOnPage: 3,
|
||||
url: 'https://www.chatwoot.com/pricing',
|
||||
triggerOnlyDuringBusinessHours: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
triggerOnlyDuringBusinessHours: true,
|
||||
},
|
||||
],
|
||||
currentURL: 'https://www.chatwoot.com/about/',
|
||||
isInBusinessHours: false,
|
||||
})
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ContactsAPI from '../../api/contacts';
|
||||
import { refreshActionCableConnector } from '../../helpers/actionCable';
|
||||
|
||||
const state = {
|
||||
currentUser: {},
|
||||
@@ -31,17 +30,13 @@ export const actions = {
|
||||
identifier_hash: userObject.identifier_hash,
|
||||
phone_number: userObject.phone_number,
|
||||
};
|
||||
const {
|
||||
data: { pubsub_token: pubsubToken },
|
||||
} = await ContactsAPI.update(identifier, user);
|
||||
await ContactsAPI.update(identifier, user);
|
||||
|
||||
dispatch('get');
|
||||
if (userObject.identifier_hash) {
|
||||
dispatch('conversation/clearConversations', {}, { root: true });
|
||||
dispatch('conversation/fetchOldConversations', {}, { root: true });
|
||||
}
|
||||
|
||||
refreshActionCableConnector(pubsubToken);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
toggleTyping,
|
||||
setUserLastSeenAt,
|
||||
} from 'widget/api/conversation';
|
||||
import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
||||
|
||||
import { createTemporaryMessage, getNonDeletedMessages } from './helpers';
|
||||
|
||||
@@ -15,13 +14,9 @@ export const actions = {
|
||||
commit('setConversationUIFlag', { isCreating: true });
|
||||
try {
|
||||
const { data } = await createConversationAPI(params);
|
||||
const {
|
||||
contact: { pubsub_token: pubsubToken },
|
||||
messages,
|
||||
} = data;
|
||||
const { messages } = data;
|
||||
const [message = {}] = messages;
|
||||
commit('pushMessageToConversation', message);
|
||||
refreshActionCableConnector(pubsubToken);
|
||||
dispatch('conversationAttributes/getAttributes', {}, { root: true });
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import MessageAPI from '../../api/message';
|
||||
import { refreshActionCableConnector } from '../../helpers/actionCable';
|
||||
|
||||
const state = {
|
||||
uiFlags: {
|
||||
@@ -18,9 +17,7 @@ export const actions = {
|
||||
) => {
|
||||
commit('toggleUpdateStatus', true);
|
||||
try {
|
||||
const {
|
||||
data: { contact: { pubsub_token: pubsubToken } = {} },
|
||||
} = await MessageAPI.update({
|
||||
await MessageAPI.update({
|
||||
email,
|
||||
messageId,
|
||||
values: submittedValues,
|
||||
@@ -37,7 +34,6 @@ export const actions = {
|
||||
{ root: true }
|
||||
);
|
||||
dispatch('contacts/get', {}, { root: true });
|
||||
refreshActionCableConnector(pubsubToken);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user