update
This commit is contained in:
19
server/package.json
Normal file
19
server/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "bun run --hot --watch src/index.ts",
|
||||
"pm2": "pm2 start src/index.ts --name dnake-server --interpreter bun --watch"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"devDependencies": {
|
||||
"@kevisual/router": "^0.0.29",
|
||||
"@types/bun": "^1.3.0",
|
||||
"@types/node": "^24.8.1"
|
||||
}
|
||||
}
|
||||
236
server/pnpm-lock.yaml
generated
Normal file
236
server/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,236 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.29
|
||||
version: 0.0.29
|
||||
'@types/bun':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(@types/react@19.2.2)
|
||||
'@types/node':
|
||||
specifier: ^24.8.1
|
||||
version: 24.8.1
|
||||
|
||||
packages:
|
||||
|
||||
'@kevisual/router@0.0.29':
|
||||
resolution: {integrity: sha512-UD2aWgf5yv/HmX3FOCPvvRLaZqYAP1JUsCgswo9BSibrVZYh7ZEnShYYnE+P2mr34UI2xEn+BJVxLMCLXg83sA==}
|
||||
|
||||
'@types/bun@1.3.0':
|
||||
resolution: {integrity: sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA==}
|
||||
|
||||
'@types/node@24.8.1':
|
||||
resolution: {integrity: sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==}
|
||||
|
||||
'@types/react@19.2.2':
|
||||
resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
|
||||
|
||||
bun-types@1.3.0:
|
||||
resolution: {integrity: sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19
|
||||
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fresh@2.0.0:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
mime-db@1.54.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.1:
|
||||
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
path-to-regexp@8.3.0:
|
||||
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
|
||||
|
||||
range-parser@1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
selfsigned@3.0.1:
|
||||
resolution: {integrity: sha512-6U6w6kSLrM9Zxo0D7mC7QdGS6ZZytMWBnj/vhF9p+dAHx6CwGezuRcO4VclTbrrI7mg7SD6zNiqXUuBHOVopNQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
send@1.2.0:
|
||||
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
statuses@2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
undici-types@7.14.0:
|
||||
resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@kevisual/router@0.0.29':
|
||||
dependencies:
|
||||
path-to-regexp: 8.3.0
|
||||
selfsigned: 3.0.1
|
||||
send: 1.2.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@types/bun@1.3.0(@types/react@19.2.2)':
|
||||
dependencies:
|
||||
bun-types: 1.3.0(@types/react@19.2.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@types/node@24.8.1':
|
||||
dependencies:
|
||||
undici-types: 7.14.0
|
||||
|
||||
'@types/react@19.2.2':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
bun-types@1.3.0(@types/react@19.2.2):
|
||||
dependencies:
|
||||
'@types/node': 24.8.1
|
||||
'@types/react': 19.2.2
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
toidentifier: 1.0.1
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
mime-types@3.0.1:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
node-forge@1.3.1: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
||||
path-to-regexp@8.3.0: {}
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
selfsigned@3.0.1:
|
||||
dependencies:
|
||||
node-forge: 1.3.1
|
||||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.0
|
||||
mime-types: 3.0.1
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
statuses@2.0.1: {}
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
undici-types@7.14.0: {}
|
||||
25
server/src/app.ts
Normal file
25
server/src/app.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { App } from '@kevisual/router'
|
||||
|
||||
import { sendDefaultSipMessage } from './lib.ts';
|
||||
|
||||
|
||||
const app = new App();
|
||||
|
||||
// http://localhost:3001/api/router?path=floor
|
||||
app.route({
|
||||
path: 'floor'
|
||||
}).define(async (ctx) => {
|
||||
await sendDefaultSipMessage();
|
||||
ctx.body = 'SIP消息已发送';
|
||||
}).addTo(app)
|
||||
|
||||
app.route({
|
||||
path: 'test'
|
||||
}).define(async (ctx) => {
|
||||
ctx.body = 'SIP消息已发送';
|
||||
}).addTo(app)
|
||||
|
||||
app.listen(3001, "::", () => {
|
||||
console.log('Server is running on http://localhost:3001');
|
||||
})
|
||||
// curl http://localhost:3001/api/router?path=test
|
||||
1
server/src/index.ts
Normal file
1
server/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './app.ts'
|
||||
113
server/src/lib.ts
Normal file
113
server/src/lib.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
interface SipMessageConfig {
|
||||
targetIp?: string;
|
||||
targetPort?: number;
|
||||
macLocalIp?: string;
|
||||
senderSip?: string;
|
||||
senderIp?: string;
|
||||
receiverSip?: string;
|
||||
receiverIp?: string;
|
||||
}
|
||||
|
||||
export async function sendSipMessage(config: SipMessageConfig = {}): Promise<void> {
|
||||
// 配置参数(使用默认值或传入的配置)
|
||||
const targetIp = config.targetIp || "192.168.3.3"; // 设备eth1的IP(转发入口)
|
||||
const targetPort = config.targetPort || 5060; // 转发端口
|
||||
const macLocalIp = config.macLocalIp || "192.168.3.33"; // Mac的本地IP(与Via头部一致)
|
||||
const senderSip = config.senderSip || "3011302"; // 发送方SIP用户
|
||||
const senderIp = config.senderIp || "192.168.9.57"; // 设备eth0的IP(转发后源IP)
|
||||
const receiverSip = config.receiverSip || "3019901"; // 接收方SIP用户
|
||||
const receiverIp = config.receiverIp || "192.168.9.4"; // 目标IP
|
||||
|
||||
// 生成唯一标识
|
||||
const callId = `${Math.floor(Date.now() / 1000)}`; // 基于时间戳的Call-ID
|
||||
const tag = `${Math.floor(Date.now() % 1000000)}`; // 随机tag
|
||||
const branch = `z9hG4bK${Math.floor(Date.now() / 1000)}`; // Via分支标识
|
||||
|
||||
// XML消息体(业务数据)
|
||||
const xmlBody = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<params>
|
||||
<to>sip:${receiverSip}@${receiverIp}:5060</to>
|
||||
<elev>1</elev>
|
||||
<direct>2</direct>
|
||||
<floor>13</floor>
|
||||
<family>2</family>
|
||||
<app>elev</app>
|
||||
<event>appoint</event>
|
||||
<event_url>/elev/appoint</event_url>
|
||||
</params>`;
|
||||
|
||||
// 计算消息体长度(使用字符串长度 + utf8编码估算)
|
||||
const contentLength = new TextEncoder().encode(xmlBody).length;
|
||||
|
||||
// 构造SIP MESSAGE请求(严格使用\r\n换行)
|
||||
const sipMessage = `MESSAGE sip:${receiverSip}@${receiverIp}:5060 SIP/2.0\r\n` +
|
||||
`Via: SIP/2.0/UDP ${macLocalIp}:5060;rport;branch=${branch}\r\n` +
|
||||
`From: <sip:${senderSip}@${senderIp}:5060>;tag=${tag}\r\n` +
|
||||
`To: <sip:${receiverSip}@${receiverIp}:5060>\r\n` +
|
||||
`Call-ID: ${callId}@${macLocalIp}\r\n` +
|
||||
`CSeq: 20 MESSAGE\r\n` +
|
||||
`Content-Type: text/plain\r\n` +
|
||||
`Max-Forwards: 70\r\n` +
|
||||
`User-Agent: DnakeVoip v1.0\r\n` +
|
||||
`Content-Length: ${contentLength}\r\n\r\n` +
|
||||
`${xmlBody}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
console.log(`准备发送SIP消息到 ${targetIp}:${targetPort}`);
|
||||
console.log('\n发送的消息内容:');
|
||||
console.log(sipMessage);
|
||||
resolve();
|
||||
} catch (error: any) {
|
||||
console.log(`处理失败:${error.message}`);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 使用默认配置的便捷函数
|
||||
export function sendDefaultSipMessage(): Promise<void> {
|
||||
return sendSipMessage();
|
||||
}
|
||||
|
||||
// 使用Node.js原生dgram模块的实现(需要@types/node)
|
||||
export function getSipMessageWithDgram(config: SipMessageConfig = {}): string {
|
||||
const targetIp = config.targetIp || "192.168.3.3";
|
||||
const macLocalIp = config.macLocalIp || "192.168.3.33";
|
||||
const senderSip = config.senderSip || "3011302";
|
||||
const senderIp = config.senderIp || "192.168.9.57";
|
||||
const receiverSip = config.receiverSip || "3019901";
|
||||
const receiverIp = config.receiverIp || "192.168.9.4";
|
||||
|
||||
const callId = `${Math.floor(Date.now() / 1000)}`;
|
||||
const tag = `${Math.floor(Date.now() % 1000000)}`;
|
||||
const branch = `z9hG4bK${Math.floor(Date.now() / 1000)}`;
|
||||
|
||||
const xmlBody = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<params>
|
||||
<to>sip:${receiverSip}@${receiverIp}:5060</to>
|
||||
<elev>1</elev>
|
||||
<direct>2</direct>
|
||||
<floor>13</floor>
|
||||
<family>2</family>
|
||||
<app>elev</app>
|
||||
<event>appoint</event>
|
||||
<event_url>/elev/appoint</event_url>
|
||||
</params>`;
|
||||
|
||||
const contentLength = new TextEncoder().encode(xmlBody).length;
|
||||
|
||||
return `MESSAGE sip:${receiverSip}@${receiverIp}:5060 SIP/2.0\r\n` +
|
||||
`Via: SIP/2.0/UDP ${macLocalIp}:5060;rport;branch=${branch}\r\n` +
|
||||
`From: <sip:${senderSip}@${senderIp}:5060>;tag=${tag}\r\n` +
|
||||
`To: <sip:${receiverSip}@${receiverIp}:5060>\r\n` +
|
||||
`Call-ID: ${callId}@${macLocalIp}\r\n` +
|
||||
`CSeq: 20 MESSAGE\r\n` +
|
||||
`Content-Type: text/plain\r\n` +
|
||||
`Max-Forwards: 70\r\n` +
|
||||
`User-Agent: DnakeVoip v1.0\r\n` +
|
||||
`Content-Length: ${contentLength}\r\n\r\n` +
|
||||
`${xmlBody}`;
|
||||
}
|
||||
|
||||
// sendDefaultSipMessage().catch(console.error);
|
||||
Reference in New Issue
Block a user