ringcentral-softphone 1.2.4 → 1.3.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.
|
@@ -11,7 +11,10 @@ class InboundCallSession extends index_js_1.default {
|
|
|
11
11
|
super(softphone, inviteMessage);
|
|
12
12
|
this.localPeer = inviteMessage.headers.To;
|
|
13
13
|
this.remotePeer = inviteMessage.headers.From;
|
|
14
|
-
|
|
14
|
+
// inbound call from call queue, invite message may not have body
|
|
15
|
+
if (inviteMessage.body.length > 0) {
|
|
16
|
+
this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
async answer() {
|
|
17
20
|
const answerSDP = `
|
|
@@ -41,7 +44,13 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
|
|
|
41
44
|
Require: "timer",
|
|
42
45
|
"Content-Type": "application/sdp",
|
|
43
46
|
}, answerSDP);
|
|
44
|
-
await this.softphone.send(newMessage);
|
|
47
|
+
const ackMessage = await this.softphone.send(newMessage, true);
|
|
48
|
+
// for inbound call from call queue, ack message may HAVE body (while invite message has no body)
|
|
49
|
+
if (ackMessage.body.length > 0) {
|
|
50
|
+
this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
51
|
+
this.remotePort = parseInt(ackMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
52
|
+
this.remoteKey = ackMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
53
|
+
}
|
|
45
54
|
this.startLocalServices();
|
|
46
55
|
}
|
|
47
56
|
}
|
|
@@ -35,8 +35,11 @@ class CallSession extends node_events_1.default {
|
|
|
35
35
|
this.encoder = softphone.codec.createEncoder();
|
|
36
36
|
this.decoder = softphone.codec.createDecoder();
|
|
37
37
|
this.sipMessage = sipMessage;
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// inbound call from call queue, invite message may not have body
|
|
39
|
+
if (this.sipMessage.body.length > 0) {
|
|
40
|
+
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
41
|
+
this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
set remoteKey(key) {
|
|
42
45
|
const localKeyBuffer = node_buffer_1.Buffer.from(utils_js_1.localKey, "base64");
|
|
@@ -67,30 +70,33 @@ class CallSession extends node_events_1.default {
|
|
|
67
70
|
await this.softphone.send(requestMessage);
|
|
68
71
|
}
|
|
69
72
|
sendDTMF(char) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
const payloads = dtmf_js_1.default.charToPayloads(char);
|
|
74
|
+
const timestamp = this.timestamp;
|
|
75
|
+
let first = true;
|
|
76
|
+
for (const payload of payloads) {
|
|
77
|
+
const rtpHeader = new werift_rtp_1.RtpHeader({
|
|
78
|
+
version: 2,
|
|
79
|
+
padding: false,
|
|
80
|
+
paddingSize: 0,
|
|
81
|
+
extension: false,
|
|
82
|
+
marker: first,
|
|
83
|
+
payloadOffset: 12,
|
|
84
|
+
payloadType: 101,
|
|
85
|
+
sequenceNumber: this.sequenceNumber,
|
|
86
|
+
timestamp,
|
|
87
|
+
ssrc: this.ssrc,
|
|
88
|
+
csrcLength: 0,
|
|
89
|
+
csrc: [],
|
|
90
|
+
extensionProfile: 48862,
|
|
91
|
+
extensionLength: undefined,
|
|
92
|
+
extensions: [],
|
|
93
|
+
});
|
|
91
94
|
const rtpPacket = new werift_rtp_1.RtpPacket(rtpHeader, payload);
|
|
92
95
|
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
96
|
+
this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
|
|
97
|
+
first = false;
|
|
93
98
|
}
|
|
99
|
+
this.timestamp += 800;
|
|
94
100
|
}
|
|
95
101
|
async sendDTMFs(s, delay = 500) {
|
|
96
102
|
for (const c of s) {
|
package/dist/cjs/index.js
CHANGED
|
@@ -6,13 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_events_1 = __importDefault(require("node:events"));
|
|
7
7
|
const node_tls_1 = __importDefault(require("node:tls"));
|
|
8
8
|
const wait_for_async_1 = __importDefault(require("wait-for-async"));
|
|
9
|
-
const mixpanel_1 = __importDefault(require("mixpanel"));
|
|
10
9
|
const inbound_js_1 = __importDefault(require("./call-session/inbound.js"));
|
|
11
10
|
const outbound_js_1 = __importDefault(require("./call-session/outbound.js"));
|
|
12
11
|
const index_js_1 = require("./sip-message/index.js");
|
|
13
12
|
const utils_js_1 = require("./utils.js");
|
|
14
13
|
const codec_js_1 = __importDefault(require("./codec.js"));
|
|
15
|
-
const mp = mixpanel_1.default.init("0a22181a3a5f16b7938e5d3277b7f96a");
|
|
16
14
|
class Softphone extends node_events_1.default {
|
|
17
15
|
sipInfo;
|
|
18
16
|
client;
|
|
@@ -22,10 +20,6 @@ class Softphone extends node_events_1.default {
|
|
|
22
20
|
intervalHandle;
|
|
23
21
|
connected = false;
|
|
24
22
|
constructor(sipInfo) {
|
|
25
|
-
mp.track("init", {
|
|
26
|
-
distinct_id: sipInfo.username,
|
|
27
|
-
version: "1.1.11",
|
|
28
|
-
});
|
|
29
23
|
super();
|
|
30
24
|
if (sipInfo.codec === undefined) {
|
|
31
25
|
sipInfo.codec = "OPUS/16000";
|
|
@@ -149,7 +143,9 @@ class Softphone extends node_events_1.default {
|
|
|
149
143
|
}
|
|
150
144
|
return new Promise((resolve) => {
|
|
151
145
|
const messageListerner = (inboundMessage) => {
|
|
152
|
-
|
|
146
|
+
// "12563 INVITE" vs "12563 ACK"
|
|
147
|
+
if (inboundMessage.headers.CSeq.trim().split(/\s+/)[0] !==
|
|
148
|
+
message.headers.CSeq.trim().split(/\s+/)[0]) {
|
|
153
149
|
return;
|
|
154
150
|
}
|
|
155
151
|
if (inboundMessage.subject.startsWith("SIP/2.0 100 ")) {
|
|
@@ -6,7 +6,10 @@ class InboundCallSession extends CallSession {
|
|
|
6
6
|
super(softphone, inviteMessage);
|
|
7
7
|
this.localPeer = inviteMessage.headers.To;
|
|
8
8
|
this.remotePeer = inviteMessage.headers.From;
|
|
9
|
-
|
|
9
|
+
// inbound call from call queue, invite message may not have body
|
|
10
|
+
if (inviteMessage.body.length > 0) {
|
|
11
|
+
this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
12
|
+
}
|
|
10
13
|
}
|
|
11
14
|
async answer() {
|
|
12
15
|
const answerSDP = `
|
|
@@ -36,7 +39,13 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
|
|
|
36
39
|
Require: "timer",
|
|
37
40
|
"Content-Type": "application/sdp",
|
|
38
41
|
}, answerSDP);
|
|
39
|
-
await this.softphone.send(newMessage);
|
|
42
|
+
const ackMessage = await this.softphone.send(newMessage, true);
|
|
43
|
+
// for inbound call from call queue, ack message may HAVE body (while invite message has no body)
|
|
44
|
+
if (ackMessage.body.length > 0) {
|
|
45
|
+
this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
46
|
+
this.remotePort = parseInt(ackMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
47
|
+
this.remoteKey = ackMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
48
|
+
}
|
|
40
49
|
this.startLocalServices();
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -30,8 +30,11 @@ class CallSession extends EventEmitter {
|
|
|
30
30
|
this.encoder = softphone.codec.createEncoder();
|
|
31
31
|
this.decoder = softphone.codec.createDecoder();
|
|
32
32
|
this.sipMessage = sipMessage;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// inbound call from call queue, invite message may not have body
|
|
34
|
+
if (this.sipMessage.body.length > 0) {
|
|
35
|
+
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
36
|
+
this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
set remoteKey(key) {
|
|
37
40
|
const localKeyBuffer = Buffer.from(localKey, "base64");
|
|
@@ -62,30 +65,33 @@ class CallSession extends EventEmitter {
|
|
|
62
65
|
await this.softphone.send(requestMessage);
|
|
63
66
|
}
|
|
64
67
|
sendDTMF(char) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
const payloads = DTMF.charToPayloads(char);
|
|
69
|
+
const timestamp = this.timestamp;
|
|
70
|
+
let first = true;
|
|
71
|
+
for (const payload of payloads) {
|
|
72
|
+
const rtpHeader = new RtpHeader({
|
|
73
|
+
version: 2,
|
|
74
|
+
padding: false,
|
|
75
|
+
paddingSize: 0,
|
|
76
|
+
extension: false,
|
|
77
|
+
marker: first,
|
|
78
|
+
payloadOffset: 12,
|
|
79
|
+
payloadType: 101,
|
|
80
|
+
sequenceNumber: this.sequenceNumber,
|
|
81
|
+
timestamp,
|
|
82
|
+
ssrc: this.ssrc,
|
|
83
|
+
csrcLength: 0,
|
|
84
|
+
csrc: [],
|
|
85
|
+
extensionProfile: 48862,
|
|
86
|
+
extensionLength: undefined,
|
|
87
|
+
extensions: [],
|
|
88
|
+
});
|
|
86
89
|
const rtpPacket = new RtpPacket(rtpHeader, payload);
|
|
87
90
|
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
91
|
+
this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
|
|
92
|
+
first = false;
|
|
88
93
|
}
|
|
94
|
+
this.timestamp += 800;
|
|
89
95
|
}
|
|
90
96
|
async sendDTMFs(s, delay = 500) {
|
|
91
97
|
for (const c of s) {
|
package/dist/esm/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import tls from "node:tls";
|
|
3
3
|
import waitFor from "wait-for-async";
|
|
4
|
-
import Mixpanel from "mixpanel";
|
|
5
4
|
import InboundCallSession from "./call-session/inbound.js";
|
|
6
5
|
import OutboundCallSession from "./call-session/outbound.js";
|
|
7
6
|
import { InboundMessage, OutboundMessage, RequestMessage, ResponseMessage, } from "./sip-message/index.js";
|
|
8
7
|
import { branch, generateAuthorization, localKey, randomInt, uuid, } from "./utils.js";
|
|
9
8
|
import Codec from "./codec.js";
|
|
10
|
-
const mp = Mixpanel.init("0a22181a3a5f16b7938e5d3277b7f96a");
|
|
11
9
|
class Softphone extends EventEmitter {
|
|
12
10
|
sipInfo;
|
|
13
11
|
client;
|
|
@@ -17,10 +15,6 @@ class Softphone extends EventEmitter {
|
|
|
17
15
|
intervalHandle;
|
|
18
16
|
connected = false;
|
|
19
17
|
constructor(sipInfo) {
|
|
20
|
-
mp.track("init", {
|
|
21
|
-
distinct_id: sipInfo.username,
|
|
22
|
-
version: "1.1.11",
|
|
23
|
-
});
|
|
24
18
|
super();
|
|
25
19
|
if (sipInfo.codec === undefined) {
|
|
26
20
|
sipInfo.codec = "OPUS/16000";
|
|
@@ -144,7 +138,9 @@ class Softphone extends EventEmitter {
|
|
|
144
138
|
}
|
|
145
139
|
return new Promise((resolve) => {
|
|
146
140
|
const messageListerner = (inboundMessage) => {
|
|
147
|
-
|
|
141
|
+
// "12563 INVITE" vs "12563 ACK"
|
|
142
|
+
if (inboundMessage.headers.CSeq.trim().split(/\s+/)[0] !==
|
|
143
|
+
message.headers.CSeq.trim().split(/\s+/)[0]) {
|
|
148
144
|
return;
|
|
149
145
|
}
|
|
150
146
|
if (inboundMessage.subject.startsWith("SIP/2.0 100 ")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ringcentral-softphone",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"homepage": "https://github.com/ringcentral/ringcentral-softphone-ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -24,20 +24,22 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"in": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/inbound-call.ts",
|
|
26
26
|
"out": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/outbound-call.ts",
|
|
27
|
+
"join": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/join-rcv-meeting.ts",
|
|
28
|
+
"multi": "tsx -r dotenv-override-true/config demos/multiple-calls-sequentially.ts",
|
|
27
29
|
"build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json",
|
|
28
30
|
"prepublishOnly": "yarn build",
|
|
29
31
|
"postpublish": "rm -rf dist"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"@evan/opus": "^1.0.3",
|
|
33
|
-
"
|
|
34
|
-
"wait-for-async": "^0.
|
|
35
|
+
"debug": "^4.4.3",
|
|
36
|
+
"wait-for-async": "^0.8.0",
|
|
35
37
|
"werift-rtp": "^0.8.8"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^
|
|
40
|
+
"@types/node": "^25.2.0",
|
|
39
41
|
"dotenv-override-true": "^6.2.2",
|
|
40
|
-
"tsx": "^4.
|
|
42
|
+
"tsx": "^4.21.0",
|
|
41
43
|
"typescript": "^5.9.3",
|
|
42
44
|
"yarn-upgrade-all": "^0.7.5"
|
|
43
45
|
},
|