mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	feat: do not send contact details to the widget (#9223)
* refactor: use has_email instead of email * feat: remove usage of details directly in forms * test: update payload * test: fix transcript test * refactor: use computed hasEmail --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		@@ -33,10 +33,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def transcript
 | 
					  def transcript
 | 
				
			||||||
    if permitted_params[:email].present? && conversation.present?
 | 
					    if conversation.present? && conversation.contact.present? && conversation.contact.email.present?
 | 
				
			||||||
      ConversationReplyMailer.with(account: conversation.account).conversation_transcript(
 | 
					      ConversationReplyMailer.with(account: conversation.account).conversation_transcript(
 | 
				
			||||||
        conversation,
 | 
					        conversation,
 | 
				
			||||||
        permitted_params[:email]
 | 
					        conversation.contact.email
 | 
				
			||||||
      )&.deliver_later
 | 
					      )&.deliver_later
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    head :ok
 | 
					    head :ok
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,10 +38,9 @@ const setUserLastSeenAt = async ({ lastSeen }) => {
 | 
				
			|||||||
    { contact_last_seen_at: lastSeen }
 | 
					    { contact_last_seen_at: lastSeen }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const sendEmailTranscript = async ({ email }) => {
 | 
					const sendEmailTranscript = async () => {
 | 
				
			||||||
  return API.post(
 | 
					  return API.post(
 | 
				
			||||||
    `/api/v1/widget/conversations/transcript${window.location.search}`,
 | 
					    `/api/v1/widget/conversations/transcript${window.location.search}`
 | 
				
			||||||
    { email }
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const toggleStatus = async () => {
 | 
					const toggleStatus = async () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,7 +87,10 @@ export default {
 | 
				
			|||||||
      return !allowMessagesAfterResolved && status === 'resolved';
 | 
					      return !allowMessagesAfterResolved && status === 'resolved';
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    showEmailTranscriptButton() {
 | 
					    showEmailTranscriptButton() {
 | 
				
			||||||
      return this.currentUser && this.currentUser.email;
 | 
					      return this.hasEmail;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    hasEmail() {
 | 
				
			||||||
 | 
					      return this.currentUser && this.currentUser.has_email;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    hasReplyTo() {
 | 
					    hasReplyTo() {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
@@ -141,12 +144,9 @@ export default {
 | 
				
			|||||||
      this.inReplyTo = message;
 | 
					      this.inReplyTo = message;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    async sendTranscript() {
 | 
					    async sendTranscript() {
 | 
				
			||||||
      const { email } = this.currentUser;
 | 
					      if (this.hasEmail) {
 | 
				
			||||||
      if (email) {
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          await sendEmailTranscript({
 | 
					          await sendEmailTranscript();
 | 
				
			||||||
            email,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          window.bus.$emit(BUS_EVENTS.SHOW_ALERT, {
 | 
					          window.bus.$emit(BUS_EVENTS.SHOW_ALERT, {
 | 
				
			||||||
            message: this.$t('EMAIL_TRANSCRIPT.SEND_EMAIL_SUCCESS'),
 | 
					            message: this.$t('EMAIL_TRANSCRIPT.SEND_EMAIL_SUCCESS'),
 | 
				
			||||||
            type: 'success',
 | 
					            type: 'success',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <FormulateForm
 | 
					  <FormulateForm
 | 
				
			||||||
    v-model="formValues"
 | 
					    v-model="formValues"
 | 
				
			||||||
    class="flex flex-1 flex-col p-6 overflow-y-auto"
 | 
					    class="flex flex-col flex-1 p-6 overflow-y-auto"
 | 
				
			||||||
    @submit="onSubmit"
 | 
					    @submit="onSubmit"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
@@ -49,7 +49,7 @@
 | 
				
			|||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <custom-button
 | 
					    <custom-button
 | 
				
			||||||
      class="font-medium mt-2 mb-5"
 | 
					      class="mt-2 mb-5 font-medium"
 | 
				
			||||||
      block
 | 
					      block
 | 
				
			||||||
      :bg-color="widgetColor"
 | 
					      :bg-color="widgetColor"
 | 
				
			||||||
      :text-color="textColor"
 | 
					      :text-color="textColor"
 | 
				
			||||||
@@ -133,9 +133,10 @@ export default {
 | 
				
			|||||||
      return this.preChatFormEnabled ? this.options.preChatFields : [];
 | 
					      return this.preChatFormEnabled ? this.options.preChatFields : [];
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    filteredPreChatFields() {
 | 
					    filteredPreChatFields() {
 | 
				
			||||||
      const isUserEmailAvailable = !!this.currentUser.email;
 | 
					      const isUserEmailAvailable = this.currentUser.has_email;
 | 
				
			||||||
      const isUserPhoneNumberAvailable = !!this.currentUser.phone_number;
 | 
					      const isUserPhoneNumberAvailable = this.currentUser.has_phone_number;
 | 
				
			||||||
      const isUserIdentifierAvailable = !!this.currentUser.identifier;
 | 
					      const isUserIdentifierAvailable = !!this.currentUser.identifier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const isUserNameAvailable = !!(
 | 
					      const isUserNameAvailable = !!(
 | 
				
			||||||
        isUserIdentifierAvailable ||
 | 
					        isUserIdentifierAvailable ||
 | 
				
			||||||
        isUserEmailAvailable ||
 | 
					        isUserEmailAvailable ||
 | 
				
			||||||
@@ -302,11 +303,10 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    onSubmit() {
 | 
					    onSubmit() {
 | 
				
			||||||
      const { emailAddress, fullName, phoneNumber, message } = this.formValues;
 | 
					      const { emailAddress, fullName, phoneNumber, message } = this.formValues;
 | 
				
			||||||
      const { email } = this.currentUser;
 | 
					 | 
				
			||||||
      this.$emit('submit', {
 | 
					      this.$emit('submit', {
 | 
				
			||||||
        fullName,
 | 
					        fullName,
 | 
				
			||||||
        phoneNumber,
 | 
					        phoneNumber,
 | 
				
			||||||
        emailAddress: emailAddress || email,
 | 
					        emailAddress,
 | 
				
			||||||
        message,
 | 
					        message,
 | 
				
			||||||
        activeCampaignId: this.activeCampaign.id,
 | 
					        activeCampaignId: this.activeCampaign.id,
 | 
				
			||||||
        conversationCustomAttributes: this.conversationCustomAttributes,
 | 
					        conversationCustomAttributes: this.conversationCustomAttributes,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,8 @@ import { getters } from '../../contacts';
 | 
				
			|||||||
describe('#getters', () => {
 | 
					describe('#getters', () => {
 | 
				
			||||||
  it('getCurrentUser', () => {
 | 
					  it('getCurrentUser', () => {
 | 
				
			||||||
    const user = {
 | 
					    const user = {
 | 
				
			||||||
      email: 'thoma@sphadikam.com',
 | 
					      has_email: true,
 | 
				
			||||||
      name: 'Adu Thoma',
 | 
					      has_name: true,
 | 
				
			||||||
      avatar_url: '',
 | 
					      avatar_url: '',
 | 
				
			||||||
      identifier_hash: 'malana_hash',
 | 
					      identifier_hash: 'malana_hash',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -12,8 +12,8 @@ describe('#getters', () => {
 | 
				
			|||||||
      currentUser: user,
 | 
					      currentUser: user,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    expect(getters.getCurrentUser(state)).toEqual({
 | 
					    expect(getters.getCurrentUser(state)).toEqual({
 | 
				
			||||||
      email: 'thoma@sphadikam.com',
 | 
					      has_email: true,
 | 
				
			||||||
      name: 'Adu Thoma',
 | 
					      has_name: true,
 | 
				
			||||||
      avatar_url: '',
 | 
					      avatar_url: '',
 | 
				
			||||||
      identifier_hash: 'malana_hash',
 | 
					      identifier_hash: 'malana_hash',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ describe('#mutations', () => {
 | 
				
			|||||||
  describe('#SET_CURRENT_USER', () => {
 | 
					  describe('#SET_CURRENT_USER', () => {
 | 
				
			||||||
    it('set current user', () => {
 | 
					    it('set current user', () => {
 | 
				
			||||||
      const user = {
 | 
					      const user = {
 | 
				
			||||||
        email: 'thoma@sphadikam.com',
 | 
					        has_email: true,
 | 
				
			||||||
        name: 'Adu Thoma',
 | 
					        has_name: true,
 | 
				
			||||||
        avatar_url: '',
 | 
					        avatar_url: '',
 | 
				
			||||||
        identifier_hash: 'malana_hash',
 | 
					        identifier_hash: 'malana_hash',
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
json.id @contact.id
 | 
					json.id @contact.id
 | 
				
			||||||
json.name @contact.name
 | 
					json.has_email @contact.email.present?
 | 
				
			||||||
json.email @contact.email
 | 
					json.has_name @contact.name.present?
 | 
				
			||||||
json.phone_number @contact.phone_number
 | 
					json.has_phone_number @contact.phone_number.present?
 | 
				
			||||||
json.widget_auth_token @widget_auth_token if @widget_auth_token.present?
 | 
					json.widget_auth_token @widget_auth_token if @widget_auth_token.present?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
json.id @contact.id
 | 
					json.id @contact.id
 | 
				
			||||||
json.name @contact.name
 | 
					json.has_email @contact.email.present?
 | 
				
			||||||
json.email @contact.email
 | 
					json.has_name @contact.name.present?
 | 
				
			||||||
json.phone_number @contact.phone_number
 | 
					json.has_phone_number @contact.phone_number.present?
 | 
				
			||||||
json.identifier @contact.identifier
 | 
					json.identifier @contact.identifier
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
json.id @contact.id
 | 
					json.id @contact.id
 | 
				
			||||||
json.name @contact.name
 | 
					json.has_email @contact.email.present?
 | 
				
			||||||
json.email @contact.email
 | 
					json.has_name @contact.name.present?
 | 
				
			||||||
json.phone_number @contact.phone_number
 | 
					json.has_phone_number @contact.phone_number.present?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,9 @@ RSpec.describe '/api/v1/widget/contacts', type: :request do
 | 
				
			|||||||
              headers: { 'X-Auth-Token' => token },
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
              as: :json
 | 
					              as: :json
 | 
				
			||||||
        body = response.parsed_body
 | 
					        body = response.parsed_body
 | 
				
			||||||
        expect(body['phone_number']).to eq('+745623239')
 | 
					        expect(body['has_phone_number']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.phone_number).to eq('+745623239')
 | 
				
			||||||
        expect(response).to have_http_status(:success)
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +60,9 @@ RSpec.describe '/api/v1/widget/contacts', type: :request do
 | 
				
			|||||||
              headers: { 'X-Auth-Token' => token },
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
              as: :json
 | 
					              as: :json
 | 
				
			||||||
        body = response.parsed_body
 | 
					        body = response.parsed_body
 | 
				
			||||||
        expect(body['phone_number']).to eq('+245623239')
 | 
					        expect(body['has_phone_number']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.phone_number).to eq('+245623239')
 | 
				
			||||||
        expect(response).to have_http_status(:success)
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,7 +72,33 @@ RSpec.describe '/api/v1/widget/contacts', type: :request do
 | 
				
			|||||||
              headers: { 'X-Auth-Token' => token },
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
              as: :json
 | 
					              as: :json
 | 
				
			||||||
        body = response.parsed_body
 | 
					        body = response.parsed_body
 | 
				
			||||||
        expect(body['email']).to eq('test@test.com')
 | 
					        expect(body['has_email']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.email).to eq('test@test.com')
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'dont update email if empty value email passed' do
 | 
				
			||||||
 | 
					        patch '/api/v1/widget/contact',
 | 
				
			||||||
 | 
					              params: params.merge({ email: '' }),
 | 
				
			||||||
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
 | 
					              as: :json
 | 
				
			||||||
 | 
					        body = response.parsed_body
 | 
				
			||||||
 | 
					        expect(body['has_email']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.email).to eq('test@test.com')
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'dont update email if nil value email passed' do
 | 
				
			||||||
 | 
					        patch '/api/v1/widget/contact',
 | 
				
			||||||
 | 
					              params: params.merge({ email: nil }),
 | 
				
			||||||
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
 | 
					              as: :json
 | 
				
			||||||
 | 
					        body = response.parsed_body
 | 
				
			||||||
 | 
					        expect(body['has_email']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.email).to eq('test@test.com')
 | 
				
			||||||
        expect(response).to have_http_status(:success)
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,7 +108,9 @@ RSpec.describe '/api/v1/widget/contacts', type: :request do
 | 
				
			|||||||
              headers: { 'X-Auth-Token' => token },
 | 
					              headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
              as: :json
 | 
					              as: :json
 | 
				
			||||||
        body = response.parsed_body
 | 
					        body = response.parsed_body
 | 
				
			||||||
        expect(body['email']).to eq('test-1@test.com')
 | 
					        expect(body['has_email']).to be true
 | 
				
			||||||
 | 
					        contact.reload
 | 
				
			||||||
 | 
					        expect(contact.email).to eq('test-1@test.com')
 | 
				
			||||||
        expect(response).to have_http_status(:success)
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -190,17 +190,19 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
 | 
				
			|||||||
  describe 'POST /api/v1/widget/conversations/transcript' do
 | 
					  describe 'POST /api/v1/widget/conversations/transcript' do
 | 
				
			||||||
    context 'with a conversation' do
 | 
					    context 'with a conversation' do
 | 
				
			||||||
      it 'sends transcript email' do
 | 
					      it 'sends transcript email' do
 | 
				
			||||||
 | 
					        contact.update(email: 'test@test.com')
 | 
				
			||||||
        mailer = double
 | 
					        mailer = double
 | 
				
			||||||
        allow(ConversationReplyMailer).to receive(:with).and_return(mailer)
 | 
					        allow(ConversationReplyMailer).to receive(:with).and_return(mailer)
 | 
				
			||||||
        allow(mailer).to receive(:conversation_transcript)
 | 
					        allow(mailer).to receive(:conversation_transcript)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        post '/api/v1/widget/conversations/transcript',
 | 
					        post '/api/v1/widget/conversations/transcript',
 | 
				
			||||||
             headers: { 'X-Auth-Token' => token },
 | 
					             headers: { 'X-Auth-Token' => token },
 | 
				
			||||||
             params: { website_token: web_widget.website_token, email: 'test@test.com' },
 | 
					             params: { website_token: web_widget.website_token },
 | 
				
			||||||
             as: :json
 | 
					             as: :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(response).to have_http_status(:success)
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
        expect(mailer).to have_received(:conversation_transcript).with(conversation, 'test@test.com')
 | 
					        expect(mailer).to have_received(:conversation_transcript).with(conversation, 'test@test.com')
 | 
				
			||||||
 | 
					        contact.update(email: nil)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user