feat: vonage 语音 测试账户

dev/full-email
Lei OT 2 months ago
parent 6abf31325a
commit 9d7cd401d5

3
.gitignore vendored

@ -35,3 +35,6 @@ schema*
*.zip
.env.*
vonage-client*
**/test

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

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

@ -328,6 +328,13 @@ const Conversations = () => {
<Button onClick={toggleClosedConversationsList} icon={activeList ? '🗂' : '📌'} type='text' />
</Tooltip>
}
{mobile && (
<AudioTwoTone className='px-2'
onClick={() => {
navigate(`/callcenter/call`)
}}
/>
)}
</div>
<ChatListFilter key='chat-list-filter'
onFilterChange={(d) => {

@ -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 = () => {
</Typography.Text>
<Typography.Text>
<PhoneOutlined className=" pr-1" />
{customerDetail.phone}
<Button
type="link"
size={"small"}
onClick={() => {
navigate(`/callcenter/call/` + customerDetail.phone);
}}>
{customerDetail.phone}
</Button>
</Typography.Text>
<Typography.Text>
<MailOutlined className=" pr-1" />

@ -173,6 +173,10 @@ function DesktopApp() {
</Link>
),
},
{
key: '/callcenter/call',
label: <Link to='/callcenter/call'>语音通话</Link>,
},
{
key: '/chat/history',
label: <Link to='/chat/history'>聊天记录</Link>,

@ -1 +1,42 @@
vonage的服务端用于生成jwt和应答语音视频请求
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"
}
}
}
]
```

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

@ -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-----
Loading…
Cancel
Save