@phonghq/go-chat 1.0.10 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/icons/IconAiCheck.vue.d.ts +2 -0
- package/dist/assets/icons/call/IconClose.vue.d.ts +2 -0
- package/dist/assets/icons/call/IconSoundDownload.vue.d.ts +2 -0
- package/dist/chat/App.vue.d.ts +7 -2
- package/dist/chat/page/customer-detail/CustomerDetail.vue.d.ts +1 -1
- package/dist/chat/page/home/ChatList.vue.d.ts +29 -1
- package/dist/chat/page/home/ChatMessage.vue.d.ts +1 -1
- package/dist/chat/page/home/Home.vue.d.ts +1 -1
- package/dist/components/chat/ScrollEvent/ScrollEvent.vue.d.ts +1 -0
- package/dist/components/chat/call/Calling.vue.d.ts +8 -2
- package/dist/components/chat/common/input/InputSearch.vue.d.ts +1 -1
- package/dist/components/chat/select/SelectBase.vue.d.ts +22 -0
- package/dist/composable/useCallHelper.d.ts +6 -2
- package/dist/composable/useInitData.d.ts +2 -4
- package/dist/go-chat.es.js +24960 -23685
- package/dist/go-chat.umd.js +42 -16
- package/dist/plugins/websocket.d.ts +12 -2
- package/dist/router/index.d.ts +2 -0
- package/dist/style.css +1 -1
- package/dist/test/assets/icons/IconAiCheck.vue.js +28 -0
- package/dist/test/assets/icons/call/IconClose.vue.js +26 -0
- package/dist/test/assets/icons/call/IconSoundDownload.vue.js +50 -0
- package/dist/test/chat/App.vue.js +145 -90
- package/dist/test/chat/page/customer-detail/CustomerDetail.vue.js +6 -5
- package/dist/test/chat/page/home/ChatList.vue.js +30 -9
- package/dist/test/chat/page/home/ChatMessage.vue.js +17 -9
- package/dist/test/chat/page/home/Home.vue.js +3 -3
- package/dist/test/chat/page/home/NewCustomer.vue.js +0 -12
- package/dist/test/components/chat/ScrollEvent/ScrollEvent.vue.js +7 -1
- package/dist/test/components/chat/call/Calling.vue.js +198 -76
- package/dist/test/components/chat/common/input/InputSearch.vue.js +2 -2
- package/dist/test/components/chat/select/SelectBase.vue.js +98 -0
- package/dist/test/components/common/drawer/DrawerBaseCustom.vue.js +0 -1
- package/dist/test/composable/data.json +32 -0
- package/dist/test/composable/useCallHelper.js +143 -33
- package/dist/test/composable/useDigibot.js +1 -1
- package/dist/test/composable/useInitData.js +17 -12
- package/dist/test/constant/color.js +1 -1
- package/dist/test/plugins/axios.js +2 -1
- package/dist/test/plugins/mqtt.js +11 -5
- package/dist/test/plugins/websocket.js +84 -4
- package/dist/test/router/index.js +39 -0
- package/dist/test/utils/chat/auth.js +10 -2
- package/dist/test/utils/chat/call.js +32 -5
- package/dist/test/utils/chat/phone-string.js +4 -0
- package/dist/test/utils/chat/user.js +7 -2
- package/dist/test/views/NotFound.vue.js +47 -0
- package/dist/test/views/TenantPhone.vue.js +270 -0
- package/dist/types/chat/global.d.ts +4 -0
- package/dist/utils/chat/auth.d.ts +5 -1
- package/dist/utils/chat/call.d.ts +5 -2
- package/dist/utils/chat/phone-string.d.ts +1 -0
- package/dist/utils/chat/user.d.ts +4 -0
- package/dist/views/NotFound.vue.d.ts +2 -0
- package/dist/views/TenantPhone.vue.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { plivoCall, plivoEndCall } from '../utils/chat/call';
|
|
1
|
+
import { callClient, plivoCall, plivoEndCall } from '../utils/chat/call';
|
|
2
|
+
import { dataProfile } from '../utils/chat/auth';
|
|
2
3
|
import { ref } from 'vue';
|
|
3
|
-
import {
|
|
4
|
+
import { WebSocketClient } from '../plugins/websocket';
|
|
5
|
+
import dataJson from './data.json';
|
|
4
6
|
export function useCallHelper() {
|
|
5
|
-
function _getAudioContext() {
|
|
7
|
+
function _getAudioContext(sampleRate) {
|
|
6
8
|
return new (window.AudioContext || window.webkitAudioContext)({
|
|
7
|
-
sampleRate:
|
|
9
|
+
sampleRate: sampleRate ?? PLIVO_SAMPLE_RATE
|
|
8
10
|
});
|
|
9
11
|
}
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
+
const PLIVO_SAMPLE_RATE = 8000;
|
|
13
|
+
const SAMPLE_RATE = 48000;
|
|
14
|
+
const CHUNK_LENGTH = 160;
|
|
15
|
+
const uuid = ref('');
|
|
12
16
|
let audioCtxListen = _getAudioContext();
|
|
13
17
|
let audioCtxCall = _getAudioContext();
|
|
14
18
|
let prebuffer = [];
|
|
@@ -17,7 +21,10 @@ export function useCallHelper() {
|
|
|
17
21
|
let sourceCall;
|
|
18
22
|
let loopTimer = null;
|
|
19
23
|
let running = false;
|
|
24
|
+
let loopId = null;
|
|
20
25
|
const userRemoter = ref(null);
|
|
26
|
+
let websocket = null;
|
|
27
|
+
let stream;
|
|
21
28
|
function _int16ToFloat32(int16Array) {
|
|
22
29
|
const float32 = new Float32Array(int16Array.length);
|
|
23
30
|
for (let i = 0; i < int16Array.length; i++) {
|
|
@@ -114,34 +121,54 @@ export function useCallHelper() {
|
|
|
114
121
|
}
|
|
115
122
|
return int16Array;
|
|
116
123
|
}
|
|
124
|
+
function _resample8kTo48k(float8k) {
|
|
125
|
+
const ratio = 48000 / 8000;
|
|
126
|
+
const float48k = new Float32Array(float8k.length * ratio);
|
|
127
|
+
for (let i = 0; i < float48k.length; i++) {
|
|
128
|
+
float48k[i] = float8k[Math.floor(i / ratio)] || 0;
|
|
129
|
+
}
|
|
130
|
+
return float48k;
|
|
131
|
+
}
|
|
117
132
|
const call = async (user) => {
|
|
118
133
|
userRemoter.value = user;
|
|
119
|
-
uuid = '';
|
|
134
|
+
uuid.value = '';
|
|
120
135
|
const res = await plivoCall(user);
|
|
121
|
-
|
|
122
|
-
uuid = res?.call?.requestUuid;
|
|
136
|
+
uuid.value = res?.call?.requestUuid;
|
|
123
137
|
};
|
|
124
|
-
const end = async (
|
|
138
|
+
const end = async () => {
|
|
125
139
|
processorCall?.disconnect?.();
|
|
126
140
|
sourceCall?.disconnect?.(processorCall);
|
|
127
141
|
stopQueue();
|
|
128
|
-
|
|
142
|
+
websocket?.disconnect();
|
|
143
|
+
stream?.getTracks?.()?.forEach((track) => track?.stop?.());
|
|
129
144
|
if (!uuid)
|
|
130
145
|
return;
|
|
131
|
-
await plivoEndCall(uuid);
|
|
146
|
+
await plivoEndCall(uuid.value);
|
|
132
147
|
return;
|
|
133
148
|
};
|
|
134
149
|
const startPeerConnection = async () => {
|
|
135
150
|
try {
|
|
136
|
-
|
|
151
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
137
152
|
sourceCall = audioCtxCall.createMediaStreamSource(stream);
|
|
138
|
-
processorCall
|
|
153
|
+
processorCall = audioCtxCall.createScriptProcessor(256, 1, 1);
|
|
139
154
|
processorCall.onaudioprocess = (e) => {
|
|
140
155
|
const input = e.inputBuffer.getChannelData(0);
|
|
141
|
-
for (let i = 0; i < input.length; i +=
|
|
142
|
-
const slice = input.slice(i, i +
|
|
156
|
+
for (let i = 0; i < input.length; i += CHUNK_LENGTH) {
|
|
157
|
+
const slice = input.slice(i, i + CHUNK_LENGTH);
|
|
143
158
|
const binaryChunk = _float32ToMuLaw8(slice);
|
|
144
|
-
socketSend(binaryChunk)
|
|
159
|
+
// socketSend(binaryChunk)
|
|
160
|
+
// const data = {
|
|
161
|
+
// event: 'testPlaySound',
|
|
162
|
+
// media: {
|
|
163
|
+
// payload: binaryChunk
|
|
164
|
+
// }
|
|
165
|
+
// }
|
|
166
|
+
// console.log(data)
|
|
167
|
+
// addQueueListen(binaryChunk)
|
|
168
|
+
// socketSend(binaryChunk)
|
|
169
|
+
// websocket.binaryType = "arraybuffer";
|
|
170
|
+
// console.log(binaryChunk)
|
|
171
|
+
websocket.sendDefault(binaryChunk);
|
|
145
172
|
}
|
|
146
173
|
};
|
|
147
174
|
sourceCall.connect(processorCall);
|
|
@@ -165,54 +192,133 @@ export function useCallHelper() {
|
|
|
165
192
|
try {
|
|
166
193
|
const chunk = prebuffer.shift();
|
|
167
194
|
if (chunk) {
|
|
168
|
-
const
|
|
195
|
+
const float48k = _resample8kTo48k(chunk);
|
|
196
|
+
const audioBuffer = audioCtxListen.createBuffer(1, chunk.length, PLIVO_SAMPLE_RATE);
|
|
169
197
|
audioBuffer.getChannelData(0).set(chunk);
|
|
170
198
|
const source = audioCtxListen.createBufferSource();
|
|
171
199
|
source.buffer = audioBuffer;
|
|
172
200
|
source.connect(audioCtxListen.destination);
|
|
201
|
+
// console.log(
|
|
202
|
+
// 'Scheduling chunk',
|
|
203
|
+
// 'nextPlayTime:', nextPlayTime,
|
|
204
|
+
// 'currentTime:', audioCtxListen.currentTime
|
|
205
|
+
// );
|
|
206
|
+
if (nextPlayTime < audioCtxListen.currentTime + (CHUNK_LENGTH / PLIVO_SAMPLE_RATE)) {
|
|
207
|
+
nextPlayTime = audioCtxListen.currentTime + (CHUNK_LENGTH / PLIVO_SAMPLE_RATE);
|
|
208
|
+
}
|
|
173
209
|
source.start(nextPlayTime);
|
|
174
|
-
|
|
210
|
+
// console.log(
|
|
211
|
+
// 'Started chunk',
|
|
212
|
+
// 'sourceStartTime:', nextPlayTime,
|
|
213
|
+
// 'audioCtxCurrentTime:', audioCtxListen.currentTime
|
|
214
|
+
// );
|
|
215
|
+
// nextPlayTime += audioBuffer.duration
|
|
216
|
+
nextPlayTime += audioBuffer.duration;
|
|
175
217
|
}
|
|
176
218
|
}
|
|
177
219
|
catch (e) {
|
|
178
220
|
console.log(e);
|
|
179
221
|
}
|
|
180
222
|
}
|
|
223
|
+
let startTime = 0;
|
|
181
224
|
const playQueueLoop = () => {
|
|
182
225
|
if (!running)
|
|
183
226
|
return;
|
|
184
227
|
while (prebuffer.length > 0 && nextPlayTime < audioCtxListen.currentTime + 0.1) {
|
|
185
228
|
scheduleNext();
|
|
186
229
|
}
|
|
187
|
-
|
|
230
|
+
loopId = requestAnimationFrame(playQueueLoop);
|
|
188
231
|
};
|
|
232
|
+
async function processSpeakerQueue() {
|
|
233
|
+
try {
|
|
234
|
+
// console.log(prebuffer)
|
|
235
|
+
if (prebuffer.length > 0) {
|
|
236
|
+
const chunk = prebuffer.shift();
|
|
237
|
+
if (chunk) {
|
|
238
|
+
// console.log(chunk.length)
|
|
239
|
+
const audioBuffer = audioCtxListen.createBuffer(1, chunk.length, 8000);
|
|
240
|
+
audioBuffer.getChannelData(0).set(chunk);
|
|
241
|
+
const source = audioCtxListen.createBufferSource();
|
|
242
|
+
source.buffer = audioBuffer;
|
|
243
|
+
source.connect(audioCtxListen.destination);
|
|
244
|
+
if (nextPlayTime < audioCtxListen.currentTime + audioBuffer.duration) {
|
|
245
|
+
nextPlayTime = audioCtxListen.currentTime + audioBuffer.duration;
|
|
246
|
+
}
|
|
247
|
+
source.start(nextPlayTime);
|
|
248
|
+
// console.log(`🎧 Played samples (${audioBuffer.duration.toFixed(3)}s)`);
|
|
249
|
+
nextPlayTime += audioBuffer.duration;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
console.log(e);
|
|
255
|
+
}
|
|
256
|
+
requestAnimationFrame(processSpeakerQueue);
|
|
257
|
+
}
|
|
189
258
|
function stopQueue() {
|
|
190
259
|
running = false;
|
|
191
260
|
if (loopTimer)
|
|
192
261
|
clearTimeout(loopTimer);
|
|
262
|
+
if (loopId !== null) {
|
|
263
|
+
cancelAnimationFrame(loopId);
|
|
264
|
+
loopId = null;
|
|
265
|
+
}
|
|
193
266
|
prebuffer.length = 0;
|
|
194
267
|
}
|
|
195
268
|
const handleMedia = async (message) => {
|
|
196
269
|
addQueueListen(message);
|
|
197
270
|
};
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
271
|
+
const handleMqttMessage = async (message) => {
|
|
272
|
+
handleMedia(message);
|
|
273
|
+
};
|
|
274
|
+
const startIncomingCall = async (uuid_request) => {
|
|
275
|
+
try {
|
|
276
|
+
if (uuid_request && uuid_request != uuid.value) {
|
|
277
|
+
throw new Error('This call isn’t yours');
|
|
278
|
+
}
|
|
279
|
+
websocket = new WebSocketClient(uuid_request, handleMqttMessage);
|
|
280
|
+
await websocket?.init();
|
|
281
|
+
if (!audioCtxCall) {
|
|
282
|
+
audioCtxCall = _getAudioContext();
|
|
283
|
+
}
|
|
284
|
+
if (audioCtxCall.state === 'suspended')
|
|
285
|
+
await audioCtxCall.resume();
|
|
286
|
+
await startPeerConnection();
|
|
287
|
+
if (running)
|
|
288
|
+
return;
|
|
289
|
+
running = true;
|
|
290
|
+
if (!audioCtxListen) {
|
|
291
|
+
audioCtxListen = _getAudioContext();
|
|
292
|
+
}
|
|
293
|
+
if (audioCtxListen.state === 'suspended')
|
|
294
|
+
await audioCtxListen.resume();
|
|
295
|
+
nextPlayTime = audioCtxListen.currentTime;
|
|
296
|
+
playQueueLoop();
|
|
297
|
+
// processSpeakerQueue()
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
end();
|
|
301
|
+
throw new Error(e);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const testPlay = () => {
|
|
305
|
+
for (let i = 0; i < dataJson.length; i++) {
|
|
306
|
+
addQueueListen(dataJson[i]);
|
|
201
307
|
}
|
|
202
|
-
if (audioCtxCall.state === 'suspended')
|
|
203
|
-
await audioCtxCall.resume();
|
|
204
|
-
await startPeerConnection();
|
|
205
308
|
if (running)
|
|
206
309
|
return;
|
|
207
310
|
running = true;
|
|
208
|
-
if (!audioCtxListen) {
|
|
209
|
-
audioCtxListen = _getAudioContext();
|
|
210
|
-
}
|
|
211
|
-
if (audioCtxListen.state === 'suspended')
|
|
212
|
-
await audioCtxListen.resume();
|
|
213
|
-
nextPlayTime = audioCtxListen.currentTime;
|
|
214
311
|
playQueueLoop();
|
|
215
312
|
};
|
|
313
|
+
const handleCallAnswer = (data) => {
|
|
314
|
+
uuid.value = data?.data?.call_uuid ?? '';
|
|
315
|
+
};
|
|
316
|
+
const callAnswer = async (uuid_request) => {
|
|
317
|
+
if (uuid_request && uuid_request != uuid.value) {
|
|
318
|
+
throw new Error('This call isn’t yours');
|
|
319
|
+
}
|
|
320
|
+
await callClient({ call_uuid: uuid.value, clientId: dataProfile.value?.tenant_id ?? '' });
|
|
321
|
+
};
|
|
216
322
|
return {
|
|
217
323
|
call,
|
|
218
324
|
end,
|
|
@@ -221,6 +327,10 @@ export function useCallHelper() {
|
|
|
221
327
|
sendOfferOk,
|
|
222
328
|
handleMedia,
|
|
223
329
|
userRemoter,
|
|
224
|
-
|
|
330
|
+
testPlay,
|
|
331
|
+
handleCallAnswer,
|
|
332
|
+
callAnswer,
|
|
333
|
+
startIncomingCall,
|
|
334
|
+
uuid
|
|
225
335
|
};
|
|
226
336
|
}
|
|
@@ -5,7 +5,8 @@ import { routerPush } from '../utils/chat/chat-router';
|
|
|
5
5
|
import { PAGE } from '../constant/general';
|
|
6
6
|
import { subscribeToTopic, unsubscribeFromTopic } from '../plugins/mqtt';
|
|
7
7
|
import { TOPIC_DETAIL_CALL } from '../constant/mqtt';
|
|
8
|
-
import { getWebSocket } from '../plugins/websocket';
|
|
8
|
+
import { getWebSocket, initWebSocket } from '../plugins/websocket';
|
|
9
|
+
import router from '../router';
|
|
9
10
|
//PINIA
|
|
10
11
|
export const isRouterReady = ref(false);
|
|
11
12
|
export function useInitData() {
|
|
@@ -13,7 +14,7 @@ export function useInitData() {
|
|
|
13
14
|
const initPage = async (data) => {
|
|
14
15
|
try {
|
|
15
16
|
isRouterReady.value = false;
|
|
16
|
-
const sdk_res = await sdkInit(
|
|
17
|
+
const sdk_res = await sdkInit(data.props);
|
|
17
18
|
if (sdk_res.tenant_error) {
|
|
18
19
|
const api_link_res = await loginApiLink();
|
|
19
20
|
// if (api_link_res.client_error) {
|
|
@@ -22,13 +23,7 @@ export function useInitData() {
|
|
|
22
23
|
// }
|
|
23
24
|
}
|
|
24
25
|
// connectMqtt()
|
|
25
|
-
await initData();
|
|
26
|
-
if (data.response == 'mobile') {
|
|
27
|
-
routerPush(PAGE.CHAT_LIST);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
routerPush(PAGE.HOME);
|
|
31
|
-
}
|
|
26
|
+
await initData(data.props, data.response);
|
|
32
27
|
}
|
|
33
28
|
catch (error) {
|
|
34
29
|
console.log(error);
|
|
@@ -37,11 +32,21 @@ export function useInitData() {
|
|
|
37
32
|
isRouterReady.value = true;
|
|
38
33
|
}
|
|
39
34
|
};
|
|
40
|
-
const initData = async () => {
|
|
41
|
-
await getProfile();
|
|
42
|
-
getWebSocket();
|
|
35
|
+
const initData = async (props, response) => {
|
|
36
|
+
const res = await getProfile();
|
|
37
|
+
await getWebSocket();
|
|
38
|
+
initWebSocket();
|
|
43
39
|
unsubscribeFromTopic(TOPIC_DETAIL_CALL + dataProfile.value?.id);
|
|
44
40
|
subscribeToTopic(TOPIC_DETAIL_CALL + dataProfile.value?.id);
|
|
41
|
+
if ((!res?.phone && res.tenant_id && !props.isLib)) {
|
|
42
|
+
router.push({ name: 'tenant-phone' });
|
|
43
|
+
}
|
|
44
|
+
else if (response == 'mobile') {
|
|
45
|
+
routerPush(PAGE.CHAT_LIST);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
routerPush(PAGE.HOME);
|
|
49
|
+
}
|
|
45
50
|
};
|
|
46
51
|
const loginApiLink = async () => {
|
|
47
52
|
try {
|
|
@@ -27,7 +27,7 @@ export function defineRootColor() {
|
|
|
27
27
|
root.style?.setProperty('--chat-color-primary', Color.Primary);
|
|
28
28
|
// root.style?.setProperty('--chat-color-primary_hover', Color.Primary_Hover)
|
|
29
29
|
// root.style?.setProperty('--chat-color-primary_rgb', Color.Primary_RGB)
|
|
30
|
-
|
|
30
|
+
root.style?.setProperty('--chat-color-error', Color.Error);
|
|
31
31
|
// root.style?.setProperty('--chat-color-error_hover', Color.Error_Hover)
|
|
32
32
|
root.style?.setProperty('--chat-color-success', Color.Success);
|
|
33
33
|
// root.style?.setProperty('--chat-color-success-bg', Color.Success_Bg)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
const baseURL = 'https://go-chat.dev01.dtsmart.dev/';
|
|
3
|
-
// const baseURL = '
|
|
3
|
+
// const baseURL = 'https://go-chat-test.dev01.dtsmart.dev'
|
|
4
|
+
// const baseURL = '192.168.1.162:3000'
|
|
4
5
|
const instance = axios.create({
|
|
5
6
|
baseURL,
|
|
6
7
|
timeout: 20000,
|
|
@@ -4,9 +4,11 @@ const mqttOptions = { qos: 1, retain: false };
|
|
|
4
4
|
let mqttClient = null;
|
|
5
5
|
const subscribedTopics = new Set();
|
|
6
6
|
let dataCallBack = [];
|
|
7
|
+
let reconnectCount = 0;
|
|
8
|
+
const MAX_RECONNECT = 5;
|
|
7
9
|
export const connectMqtt = () => {
|
|
8
10
|
return new Promise((resolve, reject) => {
|
|
9
|
-
|
|
11
|
+
reconnectCount = 0;
|
|
10
12
|
if (mqttClient && mqttClient?.connected) {
|
|
11
13
|
console.log('MQTT already connected');
|
|
12
14
|
return resolve();
|
|
@@ -49,11 +51,17 @@ export const connectMqtt = () => {
|
|
|
49
51
|
});
|
|
50
52
|
resolve();
|
|
51
53
|
});
|
|
54
|
+
mqttClient?.on('reconnect', () => {
|
|
55
|
+
reconnectCount++;
|
|
56
|
+
if (reconnectCount >= MAX_RECONNECT) {
|
|
57
|
+
mqttClient?.end(true); // true = force close
|
|
58
|
+
mqttClient = null;
|
|
59
|
+
resolve();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
52
62
|
mqttClient?.on('close', () => {
|
|
53
|
-
console.log('MQTT Disconnected');
|
|
54
63
|
});
|
|
55
64
|
mqttClient?.on('error', (err) => {
|
|
56
|
-
console.error('MQTT Error:', err);
|
|
57
65
|
reject(err);
|
|
58
66
|
});
|
|
59
67
|
});
|
|
@@ -61,7 +69,6 @@ export const connectMqtt = () => {
|
|
|
61
69
|
export const disconnectMqtt = () => {
|
|
62
70
|
if (mqttClient) {
|
|
63
71
|
mqttClient?.end(false, {}, () => {
|
|
64
|
-
console.log('MQTT Disconnected');
|
|
65
72
|
mqttClient = null;
|
|
66
73
|
});
|
|
67
74
|
}
|
|
@@ -104,7 +111,6 @@ export const publishMessage = (topic, payload) => {
|
|
|
104
111
|
const message = typeof payload !== 'string' ? JSON.stringify(payload) : payload;
|
|
105
112
|
mqttClient?.publish(topic, message, { qos: 1, retain: false }, (err) => {
|
|
106
113
|
if (!err) {
|
|
107
|
-
// console.log(`Published message to ${topic}`)
|
|
108
114
|
}
|
|
109
115
|
else {
|
|
110
116
|
console.error(`Failed to publish message to ${topic}`, err);
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { tryParseJson } from '../utils/json';
|
|
2
|
+
import { dataProfile } from '../utils/chat/auth';
|
|
2
3
|
let socket = null;
|
|
3
4
|
let dataCallBack = [];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
let callId = '';
|
|
6
|
+
const BASE_URL = 'wss://web-socket.dev01.dtsmart.dev/web-stream';
|
|
7
|
+
// const BASE_URL = 'wss://web-socket-test.dev01.dtsmart.dev/web-stream'
|
|
8
|
+
const BARE_URL_INBOUND = 'https://web-socket.dev01.dtsmart.dev';
|
|
9
|
+
export function initWebSocket() {
|
|
10
|
+
if (socket && socket.readyState === 1) {
|
|
11
|
+
console.log("WebSocket đã kết nối, bỏ qua connect.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// socket = new WebSocket('wss://' + callId)
|
|
15
|
+
socket = new WebSocket(BASE_URL);
|
|
16
|
+
socket.binaryType = 'arraybuffer';
|
|
7
17
|
socket.onopen = (event) => {
|
|
8
18
|
console.log('Connected!');
|
|
9
19
|
};
|
|
@@ -35,9 +45,10 @@ export const getWebSocket = async () => {
|
|
|
35
45
|
const response = await fetch(url, {
|
|
36
46
|
method: 'POST',
|
|
37
47
|
body: JSON.stringify({
|
|
38
|
-
clientId:
|
|
48
|
+
clientId: dataProfile.value?.tenant_id
|
|
39
49
|
}),
|
|
40
50
|
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
41
52
|
Authorization: 'Bearer -Qixt-ZztiBA5Dxl-EIWG7f2TDhK-ZCj'
|
|
42
53
|
}
|
|
43
54
|
});
|
|
@@ -45,6 +56,8 @@ export const getWebSocket = async () => {
|
|
|
45
56
|
throw new Error(`Response status: ${response.status}`);
|
|
46
57
|
}
|
|
47
58
|
const result = await response.json();
|
|
59
|
+
callId = result?.wsUrl;
|
|
60
|
+
return result;
|
|
48
61
|
}
|
|
49
62
|
catch (error) {
|
|
50
63
|
console.error(error.message);
|
|
@@ -60,3 +73,70 @@ export const removeHandleWebSK = (key) => {
|
|
|
60
73
|
dataCallBack.splice(index, 1);
|
|
61
74
|
}
|
|
62
75
|
};
|
|
76
|
+
export class WebSocketClient {
|
|
77
|
+
socket = null;
|
|
78
|
+
dataCallbacks = null;
|
|
79
|
+
server;
|
|
80
|
+
constructor(server, callBack) {
|
|
81
|
+
this.server = server;
|
|
82
|
+
this.dataCallbacks = callBack;
|
|
83
|
+
}
|
|
84
|
+
async init() {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
if (!this.server)
|
|
87
|
+
reject(new Error("Missing uuid!"));
|
|
88
|
+
this.socket = new WebSocket(BASE_URL + '?call_uuid=' + this.server);
|
|
89
|
+
console.log(BASE_URL + '?call_uuid=' + this.server);
|
|
90
|
+
this.socket.binaryType = "arraybuffer";
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
reject(new Error("⏰ WebSocket connection timeout"));
|
|
93
|
+
}, 5000); // 5s timeout
|
|
94
|
+
this.socket.onopen = () => {
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
resolve(); // Kết nối thành công
|
|
97
|
+
};
|
|
98
|
+
this.socket.onmessage = (event) => {
|
|
99
|
+
const data = tryParseJson(event.data);
|
|
100
|
+
console.log(data);
|
|
101
|
+
this.dataCallbacks?.(data);
|
|
102
|
+
};
|
|
103
|
+
this.socket.onclose = (event) => {
|
|
104
|
+
};
|
|
105
|
+
this.socket.onerror = (event) => {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
reject(new Error("WebSocket connection error"));
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
send(data) {
|
|
112
|
+
try {
|
|
113
|
+
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
114
|
+
this.socket?.send(message);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
console.error('Send error:', e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
sendDefault(data) {
|
|
121
|
+
try {
|
|
122
|
+
this.socket?.send(data);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
console.error('Send error:', e);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
disconnect() {
|
|
129
|
+
if (this.socket) {
|
|
130
|
+
this.socket.onopen = null;
|
|
131
|
+
this.socket.onmessage = null;
|
|
132
|
+
this.socket.onclose = null;
|
|
133
|
+
this.socket.onerror = null;
|
|
134
|
+
if (this.socket.readyState === WebSocket.OPEN) {
|
|
135
|
+
this.socket.close(1000, "Manual disconnect");
|
|
136
|
+
console.log("🔌 WebSocket closed manually");
|
|
137
|
+
}
|
|
138
|
+
this.socket = null;
|
|
139
|
+
}
|
|
140
|
+
this.dataCallbacks = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRouter, createWebHistory } from "vue-router";
|
|
2
|
+
import Index from "../chat/App.vue";
|
|
3
|
+
import NotFound from "../views/NotFound.vue";
|
|
4
|
+
import TenantPhone from "../views/TenantPhone.vue";
|
|
5
|
+
const router = createRouter({
|
|
6
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
7
|
+
routes: [
|
|
8
|
+
{
|
|
9
|
+
path: '/',
|
|
10
|
+
alias: '/index.html',
|
|
11
|
+
name: "home",
|
|
12
|
+
component: Index,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
path: '/tenant',
|
|
16
|
+
alias: '/index.html',
|
|
17
|
+
name: "client-home",
|
|
18
|
+
component: Index,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: '/tenant-phone',
|
|
22
|
+
alias: '/index.html',
|
|
23
|
+
name: "tenant-phone",
|
|
24
|
+
component: TenantPhone,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: '/:pathMatch(.*)*',
|
|
28
|
+
alias: '/index.html',
|
|
29
|
+
name: "not-found",
|
|
30
|
+
component: NotFound,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
// router.beforeEach(async (to, from): Promise<any> => {
|
|
35
|
+
// if (to.name != "home") {
|
|
36
|
+
// return { name: "home" };
|
|
37
|
+
// }
|
|
38
|
+
// });
|
|
39
|
+
export default router;
|
|
@@ -31,21 +31,29 @@ export const loginLink = async (params) => {
|
|
|
31
31
|
};
|
|
32
32
|
export const getProfile = async () => {
|
|
33
33
|
const res = await axios.get('/api/v1/message/user/me');
|
|
34
|
-
localStorage.setItem('chat_user', JSON.stringify(res
|
|
34
|
+
localStorage.setItem('chat_user', JSON.stringify(res));
|
|
35
35
|
dataProfile.value = res;
|
|
36
36
|
return res;
|
|
37
37
|
};
|
|
38
|
+
export const submitTenantPhone = async (body) => {
|
|
39
|
+
const res = await axios.post('/api/v1/message/tenant/update-phone', body);
|
|
40
|
+
dataLoginLink.value = res;
|
|
41
|
+
console.log(res);
|
|
42
|
+
return res;
|
|
43
|
+
};
|
|
38
44
|
export const logout = async () => {
|
|
39
45
|
dataLogin = { id: '', token: '', domain: '' };
|
|
40
46
|
dataProfile.value = null;
|
|
41
47
|
localStorage.removeItem('chat_accessToken');
|
|
42
48
|
localStorage.removeItem('chat_domain');
|
|
43
49
|
localStorage.removeItem('chat_id');
|
|
44
|
-
window.location.href = '/login';
|
|
45
50
|
try {
|
|
46
51
|
if (gapMiniAppSdk?.getInstance()?.getBridge()) {
|
|
47
52
|
gapMiniAppSdk?.getInstance()?.closeApp();
|
|
48
53
|
}
|
|
54
|
+
else {
|
|
55
|
+
window.location.href = '/login';
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
catch (error) { }
|
|
51
59
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from '../../plugins/axios';
|
|
2
2
|
import { dataProfile } from '../../utils/chat/auth';
|
|
3
3
|
const BARE_WEBSOCKET_URL = 'https://web-socket.dev01.dtsmart.dev';
|
|
4
|
+
// const BARE_WEBSOCKET_URL = 'https://web-socket-test.dev01.dtsmart.dev'
|
|
4
5
|
// const BARE_WEBSOCKET_URL = 'http://192.168.1.173:3000'
|
|
5
6
|
export const getIceService = async () => {
|
|
6
7
|
const res = await axios.post('/api/v1/message/call/ice-servers', {
|
|
@@ -16,12 +17,12 @@ export const getIceService = async () => {
|
|
|
16
17
|
// })
|
|
17
18
|
// return res
|
|
18
19
|
// }
|
|
19
|
-
export const callClient = async (
|
|
20
|
-
const url = BARE_WEBSOCKET_URL + '/
|
|
20
|
+
export const callClient = async (data) => {
|
|
21
|
+
const url = BARE_WEBSOCKET_URL + '/ws/answer';
|
|
21
22
|
try {
|
|
22
23
|
const response = await fetch(url, {
|
|
23
24
|
method: 'POST',
|
|
24
|
-
body: JSON.stringify(
|
|
25
|
+
body: JSON.stringify(data),
|
|
25
26
|
headers: {
|
|
26
27
|
['Content-Type']: 'application/json'
|
|
27
28
|
}
|
|
@@ -64,8 +65,10 @@ export const plivoCall = async (user) => {
|
|
|
64
65
|
const response = await fetch(url, {
|
|
65
66
|
method: 'POST',
|
|
66
67
|
body: JSON.stringify({
|
|
68
|
+
// from: '18668259612' || dataProfile.value?.phone,
|
|
67
69
|
from: '18668259612' || dataProfile.value?.phone,
|
|
68
|
-
to: user?.phone || ''
|
|
70
|
+
to: user?.phone || '',
|
|
71
|
+
// clientId: dataProfile.value?.tenant_id
|
|
69
72
|
}),
|
|
70
73
|
headers: {
|
|
71
74
|
['Content-Type']: 'application/json'
|
|
@@ -80,7 +83,7 @@ export const plivoCall = async (user) => {
|
|
|
80
83
|
return result;
|
|
81
84
|
};
|
|
82
85
|
export const plivoEndCall = async (uuid) => {
|
|
83
|
-
const url = BARE_WEBSOCKET_URL + '/
|
|
86
|
+
const url = BARE_WEBSOCKET_URL + '/ws/end-call';
|
|
84
87
|
const response = await fetch(url, {
|
|
85
88
|
method: 'POST',
|
|
86
89
|
body: JSON.stringify({
|
|
@@ -96,3 +99,27 @@ export const plivoEndCall = async (uuid) => {
|
|
|
96
99
|
const result = await response.json();
|
|
97
100
|
console.log(result);
|
|
98
101
|
};
|
|
102
|
+
export const downloadRecord = async (url_pub) => {
|
|
103
|
+
const url = BARE_WEBSOCKET_URL + '/ws/download-record';
|
|
104
|
+
const response = await fetch(url, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
url: url_pub
|
|
108
|
+
}),
|
|
109
|
+
headers: {
|
|
110
|
+
['Content-Type']: 'application/json'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
throw new Error(`Response status: ${response.status}`);
|
|
115
|
+
}
|
|
116
|
+
const blob = await response.blob();
|
|
117
|
+
const url_blob = window.URL.createObjectURL(blob);
|
|
118
|
+
const a = document.createElement("a");
|
|
119
|
+
a.href = url_blob;
|
|
120
|
+
a.download = "recording.mp3";
|
|
121
|
+
a.click();
|
|
122
|
+
a.remove();
|
|
123
|
+
// const result = await response.json()
|
|
124
|
+
console.log(a);
|
|
125
|
+
};
|