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 = () => { 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-----