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 <muhsinkeramam@gmail.com>
This commit is contained in:
Tarush Nagpal
2025-02-20 04:17:48 +05:30
committed by GitHub
parent 9a4c1e1fb9
commit 11a7414dc0
10 changed files with 60 additions and 78 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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`;
};

View File

@@ -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 {

View File

@@ -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
}
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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