From 11a7414dc098549f179bac4522b0de6449c28be7 Mon Sep 17 00:00:00 2001 From: Tarush Nagpal Date: Thu, 20 Feb 2025 04:17:48 +0530 Subject: [PATCH] feat: Upgrade Dyte apis to v2 (#10706) # Pull Request Template ## Description Dyte V1 API's are soon going to be deprecated, hence making sure we update Chatwoot before that happens Fixes #10704 ## Type of change Please delete options that are not relevant. - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? 1. Open a new or existing conversation from the inbox 2. Press the video call icon on the message composer 3. Verify that the message dialog shows up with the join video call button 4. Verify that clicking on join call does join the call ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] My changes generate no new warnings - [ ] New and existing unit tests pass locally with my changes (Unable to run this locally) --------- Co-authored-by: Muhsin Keloth --- .../components-next/message/bubbles/Dyte.vue | 16 ++++------- .../conversation/bubble/integrations/Dyte.vue | 13 ++++----- .../shared/helpers/IntegrationHelper.js | 6 ++-- .../components/template/IntegrationCard.vue | 16 ++++------- lib/dyte.rb | 28 ++++++++----------- lib/integrations/dyte/processor_service.rb | 5 ++-- .../integrations/dyte_controller_spec.rb | 16 +++++------ .../integrations/dyte_controller_spec.rb | 10 +++---- spec/lib/dyte_spec.rb | 16 +++++------ .../dyte/processor_service_spec.rb | 12 ++++---- 10 files changed, 60 insertions(+), 78 deletions(-) diff --git a/app/javascript/dashboard/components-next/message/bubbles/Dyte.vue b/app/javascript/dashboard/components-next/message/bubbles/Dyte.vue index 71d60b28f..c84552cb6 100644 --- a/app/javascript/dashboard/components-next/message/bubbles/Dyte.vue +++ b/app/javascript/dashboard/components-next/message/bubbles/Dyte.vue @@ -2,34 +2,30 @@ import { computed, ref } from 'vue'; import DyteAPI from 'dashboard/api/integrations/dyte'; import { buildDyteURL } from 'shared/helpers/IntegrationHelper'; -import { useCamelCase } from 'dashboard/composables/useTransformKeys'; import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; import { useMessageContext } from '../provider.js'; import BaseAttachmentBubble from './BaseAttachment.vue'; -const { content, sender, contentAttributes, id } = useMessageContext(); +const { content, sender, id } = useMessageContext(); const { t } = useI18n(); -const meetingData = computed(() => { - return useCamelCase(contentAttributes.value.data); -}); - const isLoading = ref(false); const dyteAuthToken = ref(''); const meetingLink = computed(() => { - return buildDyteURL(meetingData.value.roomName, dyteAuthToken.value); + return buildDyteURL(dyteAuthToken.value); }); const joinTheCall = async () => { isLoading.value = true; try { - const { data: { authResponse: { authToken } = {} } = {} } = - await DyteAPI.addParticipantToMeeting(id.value); - dyteAuthToken.value = authToken; + const { data: { token } = {} } = await DyteAPI.addParticipantToMeeting( + id.value + ); + dyteAuthToken.value = token; } catch (err) { useAlert(t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR')); } finally { diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/integrations/Dyte.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/integrations/Dyte.vue index a75731183..b8368e422 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/integrations/Dyte.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/integrations/Dyte.vue @@ -9,26 +9,23 @@ export default { type: Number, required: true, }, - meetingData: { - type: Object, - default: () => ({}), - }, }, data() { return { isLoading: false, dyteAuthToken: '', isSDKMounted: false }; }, computed: { meetingLink() { - return buildDyteURL(this.meetingData.room_name, this.dyteAuthToken); + return buildDyteURL(this.dyteAuthToken); }, }, methods: { async joinTheCall() { this.isLoading = true; try { - const { data: { authResponse: { authToken } = {} } = {} } = - await DyteAPI.addParticipantToMeeting(this.messageId); - this.dyteAuthToken = authToken; + const { data: { token } = {} } = await DyteAPI.addParticipantToMeeting( + this.messageId + ); + this.dyteAuthToken = token; } catch (err) { useAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR')); } finally { diff --git a/app/javascript/shared/helpers/IntegrationHelper.js b/app/javascript/shared/helpers/IntegrationHelper.js index 442a78c66..8d8be822b 100644 --- a/app/javascript/shared/helpers/IntegrationHelper.js +++ b/app/javascript/shared/helpers/IntegrationHelper.js @@ -1,5 +1,5 @@ -const DYTE_MEETING_LINK = 'https://app.dyte.in/meeting/stage/'; +const DYTE_MEETING_LINK = 'https://app.dyte.io/v2/meeting'; -export const buildDyteURL = (roomName, dyteAuthToken) => { - return `${DYTE_MEETING_LINK}${roomName}?authToken=${dyteAuthToken}&showSetupScreen=true&disableVideoBackground=true`; +export const buildDyteURL = dyteAuthToken => { + return `${DYTE_MEETING_LINK}?authToken=${dyteAuthToken}&showSetupScreen=true&disableVideoBackground=true`; }; diff --git a/app/javascript/widget/components/template/IntegrationCard.vue b/app/javascript/widget/components/template/IntegrationCard.vue index 9a6cdb784..e8e3db5d2 100644 --- a/app/javascript/widget/components/template/IntegrationCard.vue +++ b/app/javascript/widget/components/template/IntegrationCard.vue @@ -14,10 +14,6 @@ export default { type: Number, required: true, }, - meetingData: { - type: Object, - default: () => ({}), - }, }, data() { return { isLoading: false, dyteAuthToken: '', isSDKMounted: false }; @@ -28,18 +24,18 @@ export default { return getContrastingTextColor(this.widgetColor); }, meetingLink() { - return buildDyteURL(this.meetingData.room_name, this.dyteAuthToken); + return buildDyteURL(this.dyteAuthToken); }, }, methods: { async joinTheCall() { this.isLoading = true; try { - const { data: { authResponse: { authToken = '' } = {} } = {} } = - await IntegrationAPIClient.addParticipantToDyteMeeting( - this.messageId - ); - this.dyteAuthToken = authToken; + const response = await IntegrationAPIClient.addParticipantToDyteMeeting( + this.messageId + ); + const { data: { token } = {} } = response; + this.dyteAuthToken = token; } catch (error) { // Ignore Error for now } finally { diff --git a/lib/dyte.rb b/lib/dyte.rb index e090dbc5e..96750da08 100644 --- a/lib/dyte.rb +++ b/lib/dyte.rb @@ -1,9 +1,10 @@ class Dyte - BASE_URL = 'https://api.cluster.dyte.in/v1'.freeze + BASE_URL = 'https://api.dyte.io/v2'.freeze API_KEY_HEADER = 'Authorization'.freeze + PRESET_NAME = 'group_call_host'.freeze def initialize(organization_id, api_key) - @api_key = api_key + @api_key = Base64.strict_encode64("#{organization_id}:#{api_key}") @organization_id = organization_id raise ArgumentError, 'Missing Credentials' if @api_key.blank? || @organization_id.blank? @@ -11,15 +12,9 @@ class Dyte def create_a_meeting(title) payload = { - 'title': title, - 'authorization': { - 'waitingRoom': false, - 'closed': false - }, - 'recordOnStart': false, - 'liveStreamOnStart': false + 'title': title } - path = "organizations/#{@organization_id}/meeting" + path = 'meetings' response = post(path, payload) process_response(response) end @@ -28,13 +23,12 @@ class Dyte raise ArgumentError, 'Missing information' if meeting_id.blank? || client_id.blank? || name.blank? || avatar_url.blank? payload = { - 'clientSpecificId': client_id.to_s, - 'userDetails': { - 'name': name, - 'picture': avatar_url - } + 'custom_participant_id': client_id.to_s, + 'name': name, + 'picture': avatar_url, + 'preset_name': PRESET_NAME } - path = "organizations/#{@organization_id}/meetings/#{meeting_id}/participant" + path = "meetings/#{meeting_id}/participants" response = post(path, payload) process_response(response) end @@ -50,7 +44,7 @@ class Dyte def post(path, payload) HTTParty.post( "#{BASE_URL}/#{path}", { - headers: { API_KEY_HEADER => @api_key, 'Content-Type' => 'application/json' }, + headers: { API_KEY_HEADER => "Basic #{@api_key}", 'Content-Type' => 'application/json' }, body: payload.to_json } ) diff --git a/lib/integrations/dyte/processor_service.rb b/lib/integrations/dyte/processor_service.rb index e33731738..dbe429776 100644 --- a/lib/integrations/dyte/processor_service.rb +++ b/lib/integrations/dyte/processor_service.rb @@ -7,7 +7,7 @@ class Integrations::Dyte::ProcessorService return response if response[:error].present? - meeting = response['meeting'] + meeting = response message = create_a_dyte_integration_message(meeting, title, agent) message.push_event_data end @@ -29,8 +29,7 @@ class Integrations::Dyte::ProcessorService content_attributes: { type: 'dyte', data: { - meeting_id: meeting['id'], - room_name: meeting['roomName'] + meeting_id: meeting['id'] } }, sender: agent diff --git a/spec/controllers/api/v1/accounts/integrations/dyte_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/dyte_controller_spec.rb index c70ecc845..3182402f3 100644 --- a/spec/controllers/api/v1/accounts/integrations/dyte_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/dyte_controller_spec.rb @@ -39,10 +39,10 @@ RSpec.describe 'Dyte Integration API', type: :request do context 'when it is an agent with inbox access and the Dyte API is a success' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return( status: 200, - body: { success: true, data: { meeting: { id: 'meeting_id', roomName: 'room_name' } } }.to_json, + body: { success: true, data: { id: 'meeting_id' } }.to_json, headers: headers ) end @@ -62,7 +62,7 @@ RSpec.describe 'Dyte Integration API', type: :request do context 'when it is an agent with inbox access and the Dyte API is errored' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return( status: 422, body: { success: false, data: { message: 'Title is required' } }.to_json, @@ -112,24 +112,24 @@ RSpec.describe 'Dyte Integration API', type: :request do context 'when it is an agent with inbox access and message_type is integrations' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant') + stub_request(:post, 'https://api.dyte.io/v2/meetings/m_id/participants') .to_return( status: 200, - body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json, + body: { success: true, data: { id: 'random_uuid', auth_token: 'json-web-token' } }.to_json, headers: headers ) end - it 'returns authResponse' do + it 'returns auth_token' do post add_participant_to_meeting_api_v1_account_integrations_dyte_url(account), params: { message_id: integration_message.id }, headers: agent.create_new_auth_token, as: :json expect(response).to have_http_status(:success) response_body = response.parsed_body - expect(response_body['authResponse']).to eq( + expect(response_body).to eq( { - 'userAdded' => true, 'id' => 'random_uuid', 'auth_token' => 'json-web-token' + 'id' => 'random_uuid', 'auth_token' => 'json-web-token' } ) end diff --git a/spec/controllers/api/v1/widget/integrations/dyte_controller_spec.rb b/spec/controllers/api/v1/widget/integrations/dyte_controller_spec.rb index 8479fcda5..01585cee3 100644 --- a/spec/controllers/api/v1/widget/integrations/dyte_controller_spec.rb +++ b/spec/controllers/api/v1/widget/integrations/dyte_controller_spec.rb @@ -46,15 +46,15 @@ RSpec.describe '/api/v1/widget/integrations/dyte', type: :request do context 'when message is an integration message' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant') + stub_request(:post, 'https://api.dyte.io/v2/meetings/m_id/participants') .to_return( status: 200, - body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json, + body: { success: true, data: { id: 'random_uuid', auth_token: 'json-web-token' } }.to_json, headers: { 'Content-Type' => 'application/json' } ) end - it 'returns authResponse' do + it 'returns auth_token' do post add_participant_to_meeting_api_v1_widget_integrations_dyte_url, headers: { 'X-Auth-Token' => token }, params: { website_token: web_widget.website_token, message_id: integration_message.id }, @@ -62,9 +62,9 @@ RSpec.describe '/api/v1/widget/integrations/dyte', type: :request do expect(response).to have_http_status(:success) response_body = response.parsed_body - expect(response_body['authResponse']).to eq( + expect(response_body).to eq( { - 'userAdded' => true, 'id' => 'random_uuid', 'auth_token' => 'json-web-token' + 'id' => 'random_uuid', 'auth_token' => 'json-web-token' } ) end diff --git a/spec/lib/dyte_spec.rb b/spec/lib/dyte_spec.rb index 56b29b133..0963bfffe 100644 --- a/spec/lib/dyte_spec.rb +++ b/spec/lib/dyte_spec.rb @@ -11,23 +11,23 @@ describe Dyte do context 'when create_a_meeting is called' do context 'when API response is success' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return( status: 200, - body: { success: true, data: { meeting: { id: 'meeting_id' } } }.to_json, + body: { success: true, data: { id: 'meeting_id' } }.to_json, headers: headers ) end it 'returns api response' do response = dyte_client.create_a_meeting('title_of_the_meeting') - expect(response).to eq({ 'meeting' => { 'id' => 'meeting_id' } }) + expect(response).to eq({ 'id' => 'meeting_id' }) end end context 'when API response is invalid' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return(status: 422, body: { message: 'Title is required' }.to_json, headers: headers) end @@ -47,23 +47,23 @@ describe Dyte do context 'when API response is success' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant') + stub_request(:post, 'https://api.dyte.io/v2/meetings/m_id/participants') .to_return( status: 200, - body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json, + body: { success: true, data: { id: 'random_uuid', auth_token: 'json-web-token' } }.to_json, headers: headers ) end it 'returns api response' do response = dyte_client.add_participant_to_meeting('m_id', 'c_id', 'name', 'https://avatar.url') - expect(response).to eq({ 'authResponse' => { 'userAdded' => true, 'id' => 'random_uuid', 'auth_token' => 'json-web-token' } }) + expect(response).to eq({ 'id' => 'random_uuid', 'auth_token' => 'json-web-token' }) end end context 'when API response is invalid' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant') + stub_request(:post, 'https://api.dyte.io/v2/meetings/m_id/participants') .to_return(status: 422, body: { message: 'Meeting ID is invalid' }.to_json, headers: headers) end diff --git a/spec/lib/integrations/dyte/processor_service_spec.rb b/spec/lib/integrations/dyte/processor_service_spec.rb index 586190b8d..e914ce4cf 100644 --- a/spec/lib/integrations/dyte/processor_service_spec.rb +++ b/spec/lib/integrations/dyte/processor_service_spec.rb @@ -15,10 +15,10 @@ describe Integrations::Dyte::ProcessorService do describe '#create_a_meeting' do context 'when the API response is success' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return( status: 200, - body: { success: true, data: { meeting: { id: 'meeting_id', roomName: 'room_name' } } }.to_json, + body: { success: true, data: { id: 'meeting_id' } }.to_json, headers: headers ) end @@ -32,7 +32,7 @@ describe Integrations::Dyte::ProcessorService do context 'when the API response is errored' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting') + stub_request(:post, 'https://api.dyte.io/v2/meetings') .to_return( status: 422, body: { success: false, data: { message: 'Title is required' } }.to_json, @@ -51,17 +51,17 @@ describe Integrations::Dyte::ProcessorService do describe '#add_participant_to_meeting' do context 'when the API response is success' do before do - stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant') + stub_request(:post, 'https://api.dyte.io/v2/meetings/m_id/participants') .to_return( status: 200, - body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json, + body: { success: true, data: { id: 'random_uuid', auth_token: 'json-web-token' } }.to_json, headers: headers ) end it 'return the authResponse' do response = processor.add_participant_to_meeting('m_id', agent) - expect(response[:authResponse]).not_to be_nil + expect(response).not_to be_nil end end end