@audc/rtpengine-client 0.5.0
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/LICENSE +22 -0
- package/README.md +108 -0
- package/index.js +6 -0
- package/lib/BaseClient.js +199 -0
- package/lib/Client.js +107 -0
- package/lib/TcpClient.js +71 -0
- package/lib/WsClient.js +47 -0
- package/lib/constants.js +35 -0
- package/lib/error.js +8 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Dave Horton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# rtpengine-client [](http://travis-ci.org/davehorton/rtpengine-client) [](http://badge.fury.io/js/rtpengine-client) [](https://coveralls.io/github/davehorton/rtpengine-client?branch=master)
|
|
2
|
+
|
|
3
|
+
A Promises-based nodejs client for accessing rtpengine via ng protocol
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const Client = require('rtpengine-client').Client;
|
|
9
|
+
const client = new Client();
|
|
10
|
+
|
|
11
|
+
client.ping(22222, '39.194.250.246')
|
|
12
|
+
.then((res) => {
|
|
13
|
+
console.log(`received ${JSON.stringify(res)}`); // {result: 'pong'}
|
|
14
|
+
})
|
|
15
|
+
.catch((err) => {
|
|
16
|
+
console.log(`Error: ${err}`);
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Constructing a client
|
|
21
|
+
```js
|
|
22
|
+
client = new Client(); // listen on any port and default address
|
|
23
|
+
// or..
|
|
24
|
+
client = new Client(9055); // listen on a specific port
|
|
25
|
+
// or..
|
|
26
|
+
client = new Client(9055, '192.168.1.10'); // listen on a specific port and address
|
|
27
|
+
// or..
|
|
28
|
+
client = new Client({port: 9055, host: '192.168.1.10'}); // listen on a specific port and address
|
|
29
|
+
// or..
|
|
30
|
+
client = new Client({timeout: 1500}); // wait a max of 1500 ms for each command reply, throw error on timeout
|
|
31
|
+
// or..
|
|
32
|
+
client = new Client({rejectOnFailure: true});
|
|
33
|
+
// reject promise on any command if response from rtpengine has error
|
|
34
|
+
// default behavior is to resolve with any response from rtpengine, even errors
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Websocket support
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const client = new Client('ws://<IP>:8080');
|
|
41
|
+
|
|
42
|
+
client.on('listening', () => {
|
|
43
|
+
client.statistics()
|
|
44
|
+
.then((res) => {
|
|
45
|
+
console.log('received data', res);
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
console.log(`Error: ${err}`);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Making requests
|
|
54
|
+
The ng request verbs (`ping`, `offer`, `answer`, `delete`, `query`, `start recording`, `stop recording`, `block DTMF`, `unblock DTMF`, `block media`, `unblock media`) are available as methods on the `client` object. The sytax for each is the same:
|
|
55
|
+
+ the destination of the request comes first, either as `port, host` or `{port, host}`
|
|
56
|
+
+ following that, if any options are required for the request, those come next in an object.
|
|
57
|
+
|
|
58
|
+
The function call returns a promise that is resolved when the response is received.
|
|
59
|
+
|
|
60
|
+
Function names are as follows:
|
|
61
|
+
|
|
62
|
+
| ng verb | function name |
|
|
63
|
+
|------------------|------------------|
|
|
64
|
+
|ping | ping |
|
|
65
|
+
|offer | offer |
|
|
66
|
+
|answer | answer |
|
|
67
|
+
|delete | delete |
|
|
68
|
+
|query | query |
|
|
69
|
+
|start recording | startRecording |
|
|
70
|
+
|stop recording | stopRecording |
|
|
71
|
+
|block DTMF | blockDTMF |
|
|
72
|
+
|unblock DTMF | unblockDTMF |
|
|
73
|
+
|play DTMF | playDTMF |
|
|
74
|
+
|block media | blockMedia |
|
|
75
|
+
|unblock media | unblockMedia |
|
|
76
|
+
|silence media | silenceMedia |
|
|
77
|
+
|unsilence media | unsilenceMedia |
|
|
78
|
+
|start forwarding | startForwarding |
|
|
79
|
+
|stop forwarding | stopForwarding |
|
|
80
|
+
|play media | playMedia |
|
|
81
|
+
|stop media | stopMedia |
|
|
82
|
+
|statistics | statistics |
|
|
83
|
+
|publish | publish |
|
|
84
|
+
|subscribe request | subscribeRequest |
|
|
85
|
+
|subscribe answer | subscribeAnswer |
|
|
86
|
+
|unsubscribe | unsubscribe |
|
|
87
|
+
|
|
88
|
+
For instance
|
|
89
|
+
```
|
|
90
|
+
client.offer(22222, '35.195.250.243', {
|
|
91
|
+
'sdp': ..
|
|
92
|
+
'call-id': ..
|
|
93
|
+
'from-tag': ..
|
|
94
|
+
})
|
|
95
|
+
.then((res) => {
|
|
96
|
+
console.log(res); // { "result": "ok", "sdp": "v=0\r\no=..." }
|
|
97
|
+
})
|
|
98
|
+
.catch((err) => {
|
|
99
|
+
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// or..
|
|
103
|
+
client.offer({port: 22222, host: '35.195.250.243}, {
|
|
104
|
+
'sdp': ..
|
|
105
|
+
'call-id': ..
|
|
106
|
+
'from-tag': ..
|
|
107
|
+
}) // ...etc
|
|
108
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const bencode = require('bencode');
|
|
2
|
+
const v4 = require('uuid-random');
|
|
3
|
+
const Emitter = require('events').EventEmitter;
|
|
4
|
+
const RtpEngineError = require('./error');
|
|
5
|
+
const { getRtpEngineNameForCommand } = require('./constants');
|
|
6
|
+
const debug = require('debug')('rtpengine:baseClient');
|
|
7
|
+
|
|
8
|
+
class BaseClient extends Emitter {
|
|
9
|
+
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super();
|
|
12
|
+
this.type = opts.type;
|
|
13
|
+
this.connected = false;
|
|
14
|
+
this.timers = new Map();
|
|
15
|
+
this.timeout = opts.timeout || 0;
|
|
16
|
+
|
|
17
|
+
this.messages = new Map();
|
|
18
|
+
this.incomingMsgs = [];
|
|
19
|
+
if (this.timeout) {
|
|
20
|
+
this.timers = new Map();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get connectionBased() {
|
|
25
|
+
return ['tcp', 'websocket'].includes(this.type);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
send_internal(name, opts, callback) {
|
|
29
|
+
if (typeof opts === 'function') {
|
|
30
|
+
callback = opts;
|
|
31
|
+
opts = {};
|
|
32
|
+
}
|
|
33
|
+
opts = {
|
|
34
|
+
...opts,
|
|
35
|
+
command: getRtpEngineNameForCommand(name)
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
debug(`RtpEngine: ${name} ${JSON.stringify(opts)}, callback: ${typeof callback}}`);
|
|
39
|
+
|
|
40
|
+
const __x = (opts, callback) => {
|
|
41
|
+
const cookie = v4();
|
|
42
|
+
const message = BaseClient.encodeMessage(cookie, opts);
|
|
43
|
+
this.messages.set(cookie, callback);
|
|
44
|
+
|
|
45
|
+
debug(`RtpEngine: sending command: ${cookie}: ${JSON.stringify(opts)}`);
|
|
46
|
+
|
|
47
|
+
if (this.timeout) {
|
|
48
|
+
debug(`setting timeout: ${this.timeout}ms`);
|
|
49
|
+
this.timers.set(cookie, setTimeout(this._onMessageTimeout.bind(this, cookie), this.timeout));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.type === 'udp') {
|
|
53
|
+
this.socket.send(message, this.remotePort, this.remoteHost, (err) => {
|
|
54
|
+
if (err) {
|
|
55
|
+
console.error(`error sending command to rtpengine over ws at ${this.remoteHost}:${this.remotePort}`);
|
|
56
|
+
this.messages.delete(cookie);
|
|
57
|
+
return callback(err);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} else if (this.type === 'websocket') {
|
|
61
|
+
this.socket.send(message, (err) => {
|
|
62
|
+
if (err) {
|
|
63
|
+
console.error(`error sending command to rtpengine at ${this.url}`);
|
|
64
|
+
this.messages.delete(cookie);
|
|
65
|
+
return callback(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if (this.type === 'tcp') {
|
|
70
|
+
if (!this.connected) return callback('socket to rtpengine is not connected');
|
|
71
|
+
this.socket.write(message, (err) => {
|
|
72
|
+
if (err) {
|
|
73
|
+
console.error(`error sending command to rtpengine over tcp at ${this.hostport}`);
|
|
74
|
+
this.messages.delete(cookie);
|
|
75
|
+
return callback(err);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (callback) {
|
|
82
|
+
__x(opts, callback) ;
|
|
83
|
+
return this ;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
__x(opts, (err, data) => {
|
|
88
|
+
if (err) return reject(err);
|
|
89
|
+
resolve(data);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_handleIncomingMessages() {
|
|
95
|
+
while (this.incomingMsgs.length) {
|
|
96
|
+
const msg = this.incomingMsgs.shift();
|
|
97
|
+
try {
|
|
98
|
+
const obj = BaseClient.decodeMessage(msg);
|
|
99
|
+
this._onParsedMessage(obj);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error({err}, 'error decoding message from rtpengine');
|
|
102
|
+
this.emit('error', new RtpEngineError(`malformed/unexpected message format ${msg}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_onMessage(msg) {
|
|
108
|
+
this.incomingMsgs.push(msg);
|
|
109
|
+
setImmediate(this._handleIncomingMessages.bind(this));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_onParsedMessage(obj) {
|
|
113
|
+
if (!this.messages.has(obj.id)) {
|
|
114
|
+
console.error({data: obj.data}, `received a response from rtpengine with unknown msg id: '${obj.id}'`);
|
|
115
|
+
this.emit('error', new RtpEngineError(
|
|
116
|
+
`received a response that can not be correlated to a request: ${obj.id}: ${obj.data}`));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const callback = this.messages.get(obj.id);
|
|
121
|
+
if (this.timers) {
|
|
122
|
+
const timer = this.timers.get(obj.id);
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
this.timers.delete(obj.id);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.messages.delete(obj.id);
|
|
128
|
+
if (this.rejectOnError && obj.data.result === 'error') {
|
|
129
|
+
return callback(obj.data['error-reason']);
|
|
130
|
+
}
|
|
131
|
+
setImmediate(callback.bind(null, null, obj.data));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_onMessageTimeout(id) {
|
|
135
|
+
this.timers.delete(id);
|
|
136
|
+
const callback = this.messages.get(id);
|
|
137
|
+
if (!callback) {
|
|
138
|
+
this.emit('error', new RtpEngineError(
|
|
139
|
+
`received a timeout that can not be correlated to a request: ${id}`));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.messages.delete(id);
|
|
143
|
+
|
|
144
|
+
let connDetails = '';
|
|
145
|
+
if (this.type === 'udp') {
|
|
146
|
+
connDetails = `host:${this.remoteHost} port:${this.remotePort}`;
|
|
147
|
+
} else if (this.type === 'websocket') {
|
|
148
|
+
connDetails = `url:${this.url}`;
|
|
149
|
+
} else if (this.type === 'tcp') {
|
|
150
|
+
connDetails = `hostport:${this.hostport}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
callback(new RtpEngineError(`rtpengine timeout ${connDetails}`));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_onError(err) {
|
|
157
|
+
console.error(`RtpEngine#_onError: ${JSON.stringify(err)}`);
|
|
158
|
+
this.emit('error', err);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_onListening() {
|
|
162
|
+
this.emit('listening');
|
|
163
|
+
if (this.connectionBased) this.connected = true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_onEnd() {
|
|
167
|
+
this.emit('end');
|
|
168
|
+
if (this.connectionBased) this.connected = false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
close() {
|
|
172
|
+
if ('tcp' === this.type) this.socket.destroy();
|
|
173
|
+
else this.socket.close();
|
|
174
|
+
if (this.connectionBased) this.connected = false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
BaseClient.decodeMessage = function(msg) {
|
|
180
|
+
const idx = msg.indexOf(' ');
|
|
181
|
+
if (idx === 36) {
|
|
182
|
+
const buf1 = msg.subarray(0, idx);
|
|
183
|
+
const buf2 = msg.subarray(idx + 1);
|
|
184
|
+
const data = bencode.decode(buf2, 'utf8');
|
|
185
|
+
const obj = { id: buf1.toString(), data };
|
|
186
|
+
return obj;
|
|
187
|
+
}
|
|
188
|
+
console.error(`no data returned from parsing ${msg}`);
|
|
189
|
+
throw new Error('Error parsing message');
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
BaseClient.encodeMessage = function(id, data) {
|
|
193
|
+
const message = new Buffer.from(
|
|
194
|
+
[id, bencode.encode(data)].join(' ')
|
|
195
|
+
);
|
|
196
|
+
return message;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
module.exports = BaseClient;
|
package/lib/Client.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const dgram = require('dgram');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const BaseClient = require('./BaseClient');
|
|
4
|
+
const RtpEngineError = require('./error');
|
|
5
|
+
const { COMMANDS } = require('./constants');
|
|
6
|
+
const debug = require('debug')('rtpengine:Client');
|
|
7
|
+
|
|
8
|
+
class Client extends BaseClient {
|
|
9
|
+
|
|
10
|
+
constructor(...args) {
|
|
11
|
+
super({type: 'udp', ...args});
|
|
12
|
+
|
|
13
|
+
this.socket = dgram.createSocket('udp4');
|
|
14
|
+
if (typeof args[args.length - 1] === 'function') {
|
|
15
|
+
this.addListener('listening', args.pop());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.socket.on('message', this._onMessage.bind(this));
|
|
19
|
+
this.socket.on('error', this._onError.bind(this));
|
|
20
|
+
this.socket.on('listening', this._onListening.bind(this));
|
|
21
|
+
|
|
22
|
+
if (typeof args[0] === 'object') {
|
|
23
|
+
const localPort = args[0].localPort || 0;
|
|
24
|
+
if (!args[0].localAddress) {
|
|
25
|
+
this.socket.bind(localPort);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.socket.bind(localPort, args[0].localAddress);
|
|
29
|
+
}
|
|
30
|
+
if (args[0].timeout) this.timeout = args[0].timeout;
|
|
31
|
+
this.rejectOnError = args[0].rejectOnError;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
switch (args.length) {
|
|
35
|
+
case 0 :
|
|
36
|
+
this.socket.bind();
|
|
37
|
+
break;
|
|
38
|
+
case 1:
|
|
39
|
+
this.socket.bind(args[0]);
|
|
40
|
+
break;
|
|
41
|
+
case 2:
|
|
42
|
+
this.socket.bind(args[0], args[1]);
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
throw new RtpEngineError('invalid number of arguments to rtpengine-client constructor');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.messages = new Map();
|
|
50
|
+
if (this.timeout) {
|
|
51
|
+
this.timers = new Map();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_onListening() {
|
|
56
|
+
const r = process.env.UDP_RECV_BUFSIZE || process.env.UDP_BUFSIZE;
|
|
57
|
+
const s = process.env.UDP_SEND_BUFSIZE || process.env.UDP_BUFSIZE;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (r) {
|
|
61
|
+
const recvBufSize = parseInt(r, 10);
|
|
62
|
+
if (recvBufSize > 0) {
|
|
63
|
+
this.socket.setRecvBufferSize(recvBufSize);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (s) {
|
|
67
|
+
const sendBufSize = parseInt(s, 10);
|
|
68
|
+
if (sendBufSize > 0) {
|
|
69
|
+
this.socket.setSendBufferSize(sendBufSize);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.log({err, r, s}, 'rtpengine-client: error setting udp buffer size');
|
|
74
|
+
}
|
|
75
|
+
super._onListening();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// add commands
|
|
80
|
+
COMMANDS.forEach((method) => {
|
|
81
|
+
Client.prototype[method] = function(...args) {
|
|
82
|
+
assert.ok(args.length, `must supply destination port and address in call to Client#${method}`);
|
|
83
|
+
|
|
84
|
+
debug(args);
|
|
85
|
+
let idx = 1;
|
|
86
|
+
if (typeof args[0] === 'object') {
|
|
87
|
+
assert(typeof args[0].port === 'number', `must supply 'port' in call to Client#${method}`);
|
|
88
|
+
assert(typeof args[0].host === 'string', `must supply 'host' in call to Client#${method}`);
|
|
89
|
+
this.remotePort = args[0].port;
|
|
90
|
+
this.remoteHost = args[0].host;
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
assert(typeof args[0] === 'number', `must supply port in call to Client#${method}`);
|
|
95
|
+
assert(typeof args[1] === 'string', `must supply host in call to Client#${method}`);
|
|
96
|
+
this.remotePort = args[0];
|
|
97
|
+
this.remoteHost = args[1];
|
|
98
|
+
idx = 2;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const a = [method].concat(args.slice(idx));
|
|
102
|
+
debug(a);
|
|
103
|
+
return this.send_internal(...[method].concat(args.slice(idx)));
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
module.exports = Client;
|
package/lib/TcpClient.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const BaseClient = require('./BaseClient');
|
|
2
|
+
const net = require('net');
|
|
3
|
+
const RtpEngineError = require('./error');
|
|
4
|
+
const { COMMANDS } = require('./constants');
|
|
5
|
+
const debug = require('debug')('rtpengine:baseClient');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const MAX_BUFLEN = 16384;
|
|
9
|
+
|
|
10
|
+
class TcpClient extends BaseClient {
|
|
11
|
+
|
|
12
|
+
constructor(...args) {
|
|
13
|
+
super({type: 'tcp', ...args});
|
|
14
|
+
|
|
15
|
+
if (typeof args[0] === 'object') {
|
|
16
|
+
if (args[0].timeout) this.timeout = args[0].timeout;
|
|
17
|
+
this.hostport = args[0].hostport;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.hostport = args[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const arr = /^(.*):(\d+)$/.exec(this.hostport);
|
|
24
|
+
if (!arr) throw new RtpEngineError(`rtpengine-client: invalid hostport for tcp connection: ${this.hostport}`);
|
|
25
|
+
|
|
26
|
+
this.host = arr[1];
|
|
27
|
+
this.port = arr[2];
|
|
28
|
+
|
|
29
|
+
const socket = this.socket = new net.Socket();
|
|
30
|
+
socket.setKeepAlive(true) ;
|
|
31
|
+
|
|
32
|
+
socket.on('connect', () => {
|
|
33
|
+
this.connected = true;
|
|
34
|
+
this.emit('connect');
|
|
35
|
+
});
|
|
36
|
+
socket.on('data', this._onData.bind(this));
|
|
37
|
+
socket.on('end', this._onEnd.bind(this));
|
|
38
|
+
socket.on('ready', this._onListening.bind(this));
|
|
39
|
+
socket.on('error', this._onError.bind(this));
|
|
40
|
+
|
|
41
|
+
debug(`connecting tcp client to ${this.host}:${this.port}`);
|
|
42
|
+
socket.connect(this.port, this.host);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_onData(msg) {
|
|
46
|
+
let res;
|
|
47
|
+
this.buffer = !this.buffer ? msg : Buffer.concat([this.buffer, msg]);
|
|
48
|
+
if (this.buffer.length > MAX_BUFLEN) {
|
|
49
|
+
this.emit('error', new RtpEngineError(`malformed/unexpected message format ${this.buffer.slice(0, 64)}...`));
|
|
50
|
+
this.buffer = null;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
console.log(`received ${msg.length} bytes`);
|
|
55
|
+
res = BaseClient.decodeMessage(this.buffer);
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this._onParsedMessage(res);
|
|
60
|
+
this.buffer = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// add commands
|
|
65
|
+
COMMANDS.forEach((method) => {
|
|
66
|
+
TcpClient.prototype[method] = function(...args) {
|
|
67
|
+
return this.send_internal(...[method].concat(args));
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
module.exports = TcpClient;
|
package/lib/WsClient.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
const BaseClient = require('./BaseClient');
|
|
3
|
+
const { COMMANDS } = require('./constants');
|
|
4
|
+
|
|
5
|
+
class WsClient extends BaseClient {
|
|
6
|
+
|
|
7
|
+
constructor(...args) {
|
|
8
|
+
super({type: 'websocket', ...args});
|
|
9
|
+
this.connectionCount = 0;
|
|
10
|
+
|
|
11
|
+
if (typeof args[0] === 'object') {
|
|
12
|
+
if (args[0].timeout) this.timeout = args[0].timeout;
|
|
13
|
+
this.url = args[0].url;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
this.url = args[0];
|
|
17
|
+
}
|
|
18
|
+
this._connect(this.url, 'ng.rtpengine.com');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_connect(opts, protocol) {
|
|
22
|
+
this.socket = new WebSocket(opts, protocol);
|
|
23
|
+
this._attachHandlers(this.socket, opts, protocol);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_attachHandlers(socket, opts, protocol) {
|
|
27
|
+
socket.on('message', this._onMessage.bind(this));
|
|
28
|
+
socket.on('error', this._onError.bind(this));
|
|
29
|
+
socket.on('open', () => {
|
|
30
|
+
if (this.connectionCount++ === 0) this._onListening();
|
|
31
|
+
else this.emit('reconnected');
|
|
32
|
+
});
|
|
33
|
+
socket.on('close', () => {
|
|
34
|
+
this.emit('close');
|
|
35
|
+
setTimeout(this._connect.bind(this, opts, protocol), 2000);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// add commands
|
|
41
|
+
COMMANDS.forEach((method) => {
|
|
42
|
+
WsClient.prototype[method] = function(...args) {
|
|
43
|
+
return this.send_internal(...[method].concat(args));
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
module.exports = WsClient;
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const _commands = {
|
|
2
|
+
'answer': 'answer',
|
|
3
|
+
'delete': 'delete',
|
|
4
|
+
'list': 'list',
|
|
5
|
+
'offer': 'offer',
|
|
6
|
+
'ping': 'ping',
|
|
7
|
+
'query': 'query',
|
|
8
|
+
'startRecording': 'start recording',
|
|
9
|
+
'stopRecording': 'stop recording',
|
|
10
|
+
'blockDTMF': 'block DTMF',
|
|
11
|
+
'unblockDTMF': 'unblock DTMF',
|
|
12
|
+
'playDTMF': 'play DTMF',
|
|
13
|
+
'blockMedia': 'block media',
|
|
14
|
+
'unblockMedia': 'unblock media',
|
|
15
|
+
'silenceMedia': 'silence media',
|
|
16
|
+
'unsilenceMedia': 'unsilence media',
|
|
17
|
+
'startForwarding': 'start forwarding',
|
|
18
|
+
'stopForwarding': 'stop forwarding',
|
|
19
|
+
'playMedia': 'play media',
|
|
20
|
+
'stopMedia': 'stop media',
|
|
21
|
+
'statistics': 'statistics',
|
|
22
|
+
'publish': 'publish',
|
|
23
|
+
'subscribeRequest': 'subscribe request',
|
|
24
|
+
'subscribeAnswer': 'subscribe answer',
|
|
25
|
+
'unsubscribe': 'unsubscribe'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const COMMANDS = Object.keys(_commands);
|
|
29
|
+
|
|
30
|
+
const getRtpEngineNameForCommand = (name) => _commands[name];
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
COMMANDS,
|
|
34
|
+
getRtpEngineNameForCommand
|
|
35
|
+
};
|
package/lib/error.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audc/rtpengine-client",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "node client for rtpengine daemon",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test/",
|
|
8
|
+
"coverage": "c8 --reporter html npm run test",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"author": "Dave Horton",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"lib/"
|
|
16
|
+
],
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@eslint/js": "^9.18.0",
|
|
19
|
+
"@types/bencode": "^2.0.4",
|
|
20
|
+
"@types/tape": "^5.8.1",
|
|
21
|
+
"c8": "^10.1.3",
|
|
22
|
+
"debug": "^4.4.0",
|
|
23
|
+
"eslint": "^9.18.0",
|
|
24
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
25
|
+
"globals": "^15.14.0",
|
|
26
|
+
"sinon": "^19.0.2",
|
|
27
|
+
"tape": "^5.9.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"bencode": "^2.0.3",
|
|
31
|
+
"uuid-random": "^1.3.2",
|
|
32
|
+
"ws": "^8.18.0"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"rtpengine"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/davehorton/rtpengine-client"
|
|
40
|
+
}
|
|
41
|
+
}
|