diff --git a/.gitignore b/.gitignore
index 1e13aea..a8dcb06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,6 @@ schema*
*.zip
.env.*
+
+vonage-client*
+**/test
diff --git a/package.json b/package.json
index d8e58c3..78579c2 100644
--- a/package.json
+++ b/package.json
@@ -12,25 +12,24 @@
"dependencies": {
"@dckj/react-better-modal": "^0.1.2",
"@lexical/react": "^0.20.0",
- "@vonage/client-sdk": "^1.7.2",
+ "@vonage/client-sdk": "^2.0.0",
"antd": "^5.22.2",
"dayjs": "^1.11.13",
"dingtalk-jsapi": "^3.0.41",
"emoji-picker-react": "^4.12.0",
"lexical": "^0.20.0",
"react": "^18.3.1",
+ "react-chat-elements": "^12.0.17",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
- "zustand": "^4.5.5",
- "react-chat-elements": "^12.0.17",
"rxjs": "^7.8.1",
- "uuid": "^9.0.1"
+ "uuid": "^9.0.1",
+ "zustand": "^4.5.5"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
- "@vonage/client-sdk": "^1.7.2",
"autoprefixer": "^10.4.20",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.37.2",
diff --git a/src/stores/CallCenterStore.js b/src/stores/CallCenterStore.js
index 19a33e1..99ffa81 100644
--- a/src/stores/CallCenterStore.js
+++ b/src/stores/CallCenterStore.js
@@ -1,12 +1,16 @@
import { create } from "zustand";
-import { VonageClient } from "@vonage/client-sdk";
+import { VonageClient, ClientConfig, ConfigRegion, LoggingLevel } from '@vonage/client-sdk'
import { fetchJSON } from "@/utils/request";
-import { prepareUrl, isNotEmpty } from "@/utils/commons";
+import { prepareUrl, isNotEmpty, } from "@/utils/commons";
import { VONAGE_URL, DATETIME_FORMAT } from "@/config";
import dayjs from "dayjs";
const callCenterStore = create((set, get) => ({
- client: new VonageClient({ apiUrl: "https://api-ap-3.vonage.com", websocketUrl: "wss://ws-ap-3.vonage.com" }),
+ client: new VonageClient({
+ region: ConfigRegion.AP,
+ // apiUrl: "https://api-ap-3.vonage.com", websocketUrl: "wss://ws-ap-3.vonage.com",
+ // loggingLevel: LoggingLevel.Debug,
+ }),
call_id: 0,
loading: false,
logs: "",
@@ -15,11 +19,20 @@ const callCenterStore = create((set, get) => ({
init_vonage: user_id => {
const { client, log } = get();
set({ loading: true });
+ // const client = new VonageClient({
+ // loggingLevel: LoggingLevel.DEBUG,
+ // region: ConfigRegion.AP,
+ // })
+ // update some options after initialization.
+ // const vonageConfig = new ClientConfig(ConfigRegion.AP);
+ // client.setConfig(vonageConfig);
+
const fetchUrl = prepareUrl(VONAGE_URL + "/jwt")
.append("user_id", user_id)
.build();
- return fetchJSON(fetchUrl).then(json => {
- if (json.status === 200) {
+ return fetchJSON(fetchUrl).then(res => {
+ const json = res.result;
+ if (json?.status === 200) {
let jwt = json.token;
client
@@ -30,6 +43,11 @@ const callCenterStore = create((set, get) => ({
.catch(error => {
log("Error creating session: ", error);
});
+ // debug:
+ // client
+ // .getUser('me')
+ // .then((user) => log('getUser --me', user))
+ // .catch((error) => log);
client.on("sessionError", reason => {
// After creating a session
@@ -56,7 +74,7 @@ const callCenterStore = create((set, get) => ({
} else {
throw new Error("请求jwt失败");
}
- set({ loading: false });
+ set({ loading: false, client });
});
},
@@ -77,7 +95,7 @@ const callCenterStore = create((set, get) => ({
if (client) {
set({ loading: true });
client
- .serverCall({ to: phone_number })
+ .serverCall({ to: phone_number, })
.then(callId => {
log("Id of created call: ", callId);
set({ call_id: callId });
diff --git a/src/views/Conversations/Online/ConversationsList.jsx b/src/views/Conversations/Online/ConversationsList.jsx
index ba163b6..904cc37 100644
--- a/src/views/Conversations/Online/ConversationsList.jsx
+++ b/src/views/Conversations/Online/ConversationsList.jsx
@@ -328,6 +328,13 @@ const Conversations = () => {
}
+ {mobile && (
+ {
+ navigate(`/callcenter/call`)
+ }}
+ />
+ )}
{
diff --git a/src/views/Conversations/Online/order/CustomerProfile.jsx b/src/views/Conversations/Online/order/CustomerProfile.jsx
index 2e3401a..63041d0 100644
--- a/src/views/Conversations/Online/order/CustomerProfile.jsx
+++ b/src/views/Conversations/Online/order/CustomerProfile.jsx
@@ -1,7 +1,7 @@
import { LinkOutlined, MailOutlined, PhoneOutlined, UserOutlined, WhatsAppOutlined, FieldNumberOutlined } from "@ant-design/icons";
import { App, Button, Card, Empty, Flex, Select, Spin, Typography, Divider, Modal, List, Row, Col, Tag, Checkbox, Drawer, Input } from "antd";
import { useEffect, useState, useRef, useCallback } from "react";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, } from "react-router-dom";
import { useShallow } from 'zustand/react/shallow';
import { copy, isEmpty } from "@/utils/commons";
@@ -21,6 +21,7 @@ import EmailDetailInline from "../Components/EmailDetailInline";
import SupplierEmailDrawer from "../Components/SupplierEmailDrawer";
const CustomerProfile = () => {
+ const navigate = useNavigate();
const { notification, message } = App.useApp();
const [loading, setLoading] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -247,7 +248,14 @@ const CustomerProfile = () => {
- {customerDetail.phone}
+
diff --git a/src/views/DesktopApp.jsx b/src/views/DesktopApp.jsx
index e4990ac..2aa2da7 100644
--- a/src/views/DesktopApp.jsx
+++ b/src/views/DesktopApp.jsx
@@ -173,6 +173,10 @@ function DesktopApp() {
),
},
+ {
+ key: '/callcenter/call',
+ label: 语音通话,
+ },
{
key: '/chat/history',
label: 聊天记录,
diff --git a/vonage-server/README.md b/vonage-server/README.md
index bcded1a..6739022 100644
--- a/vonage-server/README.md
+++ b/vonage-server/README.md
@@ -1 +1,42 @@
-vonage的服务端,用于生成jwt和应答语音视频请求
\ No newline at end of file
+vonage的服务端,用于生成jwt和应答语音视频请求
+
+```sh
+> vonage users create --display-name Highlights
+✅ Creating User
+
+User ID: USR-a778518f-6d94-43b8-ab3c-413e6af8f10a
+Name: NAM-3f97db93-da89-4937-b457-14be72b7b62b
+Display Name: Highlights
+Image URL: Not Set
+Time to Live: Not Set
+
+Channels:
+ None Set
+> vonage users list
+✅ Fetching users
+
+User ID Name Display Name
+---------------------------------------- ---------------------------------------- ------------
+USR-4476dbcc-8e2a-4048-ac8a-8ab49345167a HighlightsTravel
+USR-a778518f-6d94-43b8-ab3c-413e6af8f10a NAM-3f97db93-da89-4937-b457-14be72b7b62b
+USR-9f60dff4-78c3-40c8-9aad-f762df371728 NAM-c54db270-643a-43a6-883e-b8c28d292dbe
+
+Done Listing users
+```
+
+region AP 下的用户:
+
+```json
+"users": [
+ {
+ "id": "USR-2e2e267c-e718-4114-b488-bc6677541fd4",
+ "name": "Highlights",
+ "display_name": "Highlights",
+ "_links": {
+ "self": {
+ "href": "https://api-ap-3.vonage.com/v1/users/USR-2e2e267c-e718-4114-b488-bc6677541fd4"
+ }
+ }
+ }
+]
+```
diff --git a/vonage-server/index.js b/vonage-server/index.js
index f254987..4921715 100644
--- a/vonage-server/index.js
+++ b/vonage-server/index.js
@@ -3,7 +3,7 @@
const express = require("express");
const fs = require("fs");
const { tokenGenerate } = require("@vonage/jwt");
-const privateKey = fs.readFileSync("gh-private.key");
+const privateKey = fs.readFileSync("private20250417.key");
const aclPaths = {
paths: {
"/*/rtc/**": {},
@@ -21,7 +21,19 @@ const aclPaths = {
const app = express();
app.use(express.json());
-const vonageNumber = "12052553394"; //用于通话的号码,今后根据前端提交来做号码切换
+const app_id = '503c548f-cb61-4a3a-8599-cd815680b390';
+const api_key = '197c68c9';
+const api_secret = 'Aa11be17f298dfd4118bdf23';
+const vonageNumber = "12019751815"; //用于通话的号码,今后根据前端提交来做号码切换
+const vonageUser = "Highlights"; // user name
+
+const response = (data) => {
+ return {
+ errcode: 0,
+ errmsg: '',
+ result: data,
+ }
+}
//设置跨域访问
app.all("*", function (req, res, next) {
@@ -36,7 +48,7 @@ app.get("/voice/answer", (req, res) => {
console.log("NCCO request:");
console.log(` - callee: ${req.query.to}`);
console.log("---");
- res.json([
+ const result = [
{
action: "talk",
text: "Please wait while we connect you.",
@@ -44,30 +56,31 @@ app.get("/voice/answer", (req, res) => {
{
action: "record",
eventUrl: [`${req.protocol}://${req.get("host")}/vonage-server/voice/recordings`],
+ // eventUrl: [`${req.protocol}://${req.get("host")}/voice/recordings`], // test: 0
},
{
action: "connect",
from: vonageNumber,
endpoint: [{ type: "phone", number: req.query.to }],
},
- ]);
+ ];
+ res.json(result);
});
app.get("/jwt", (req, res) => {
console.log("jwt Generate:");
const exp_time = new Date(Date.now() + 3 * 24 * 3600 * 1000);
- const token = tokenGenerate("39f32a2a-e343-4ca0-9d92-5946573af5ea", privateKey, {
+ const token = tokenGenerate(app_id, privateKey, {
exp: Math.round(exp_time.getTime() / 1000),
- sub: "hainatravel",
+ sub: vonageUser,
acl: aclPaths,
});
console.log(Math.round(exp_time.getTime() / 1000));
- console.log(token);
+ // console.log(token);
+ console.log('token ✅');
console.log("---");
- res.json({
- token: token,
- status: 200,
- });
+ const result = response({ token, status: 200});
+ res.json(result);
});
app.all("/voice/event", (req, res) => {
diff --git a/vonage-server/private20250417.key b/vonage-server/private20250417.key
new file mode 100644
index 0000000..1e1af70
--- /dev/null
+++ b/vonage-server/private20250417.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCXaDD9rLbsAWpr
+r0Nz3vc04Q/w9WgSsmLW8fg08+4V604QQUwbyXYPQACIzNguEB5ggbvbyxL7j5Bf
++bXW6HCu5Gxw0+ciMyhaymR7aGrbzIWOP8abTQnb6+ljZWeCvD+R8Ow6xJrhTACJ
+X48IrLPDN3kqFQWVjm0vV9GiviPF4ifDCmNW+78oPrEo5YHKwyoYrGBkr6pKMiER
+m9pSu++owv8XUV/hQVTkq20khlvRy35bwx2qmbGbE9BgQsS4KNri9jHOIB413sFg
+p8zcvdKTxtje99M5iPHUKgF8WrzRHFqvKFRyg90p22twNAUnvgc3eiUoW2Qv+A78
+X9ZaaEQTAgMBAAECggEACEwzKyPfs9gcBKIf+JCxTETtV7pLUC2rPGEZxjp8GNgY
+I3dApuEynCwDcNExI80fAmZPF6uYcxBEzE27c8yx/ZO9Ma3F+VouYv4ROwYs8mDS
+DPVdJWkNGkVijrT2/Z44KapiIpJgc+pzY76d8Hluh0tZ/j7ABe3pTp0/JZkgDW8o
+Q92TObC/cN5ZguIpNWlW2lQhKiJkwkcLFpxzm5QhStiQaahjWJBye6tvc9M3CSxm
+69e9pPW0ZBTW6wyIU6OQHzfnT6+bwo3kbmV2Svx3OHL1yiJV1p4rbuH3XJKQ6d2O
+zh2mew7zqXOuMggLotevDJXmRcrgmo4kxoxW6BKMuQKBgQDHkxlapdGuH1rZwPZj
+KcQvy29Z0xRP5wbUlTRkthHPXUeUonZTeKfw/oSfz81rHV2z046Bp7tE5qshzLiS
+RMl9U68t36t+i3vNiUFNtNT4Uu9w4KL2dwi0onBauc9m2xNSR0YHJlWTHkgauQaq
+XoV/rQca6nBagXxQ9qeoJr4m1wKBgQDCNsuhm53503i74HYoVEOM3noAuqVW3usl
+4QHsQsGuItFC5/sTLudvJv6wTZItDEf15eLqge3j6tjxG8+2RoYiCTSKdW848fLi
+PUkIyCfMYmA3L7eIwJmDwPxG80E4tQWKPyENvXSKNmNS+Cnu7fPl9mOJIqHncC6r
+UowBID6xJQKBgQCe59sqOBmqQMD/3QrRjjHttFem99CWhmcD4QFkpyurJqSWDn2U
+nN9rndxPuw/el/VB99LiHYGYrOnZ8b2MiUS9i2JSbmOIUNt0njLnAnMIflC0Wcin
+4cOGwEghlQ004n6R5ro1eypsB5J15JkQEk7NiCG+JqjrB2rKtHpuAtso5QKBgDLs
+azhUtXdsG5wnntO0RIILU7IdPn0otj+YYAiy+FXQi04fxZWiFszuTJmtvUZSkgvH
+21fh+Z5pVbjisfP5SfJit4QWhrNHvYfUyfGjicvtf4z41gbleVsynvN7lP5peKpn
+IyOXKZeT6zc2GsirW+hQUokCq7EjmRkS6+LfsZCBAoGBAMAfhVcSz73yDqTR6YR3
++kzpm8+bEOBhUERxtidyvEdaF1255BoeDigFVgSophdk+tWdcIMFLmg8VkVD9FCa
+3mVbmD6lNm1eJfUtsMQYja0x338PpSFmd6whBbWRb/8CUbyFJSP5wFlPe9G9qxN8
+6HrZzqr1ydp2MWMoKFYCiiQR
+-----END PRIVATE KEY-----