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
- this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
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
- this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
39
- this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
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 timestamp = Math.floor(Date.now() / 1000);
71
- let sequenceNumber = timestamp % 65536;
72
- const rtpHeader = new werift_rtp_1.RtpHeader({
73
- version: 2,
74
- padding: false,
75
- paddingSize: 0,
76
- extension: false,
77
- marker: false,
78
- payloadOffset: 12,
79
- payloadType: 101,
80
- sequenceNumber,
81
- timestamp,
82
- ssrc: (0, utils_js_1.randomInt)(),
83
- csrcLength: 0,
84
- csrc: [],
85
- extensionProfile: 48862,
86
- extensionLength: undefined,
87
- extensions: [],
88
- });
89
- for (const payload of dtmf_js_1.default.charToPayloads(char)) {
90
- rtpHeader.sequenceNumber = sequenceNumber++;
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
- if (inboundMessage.headers.CSeq !== message.headers.CSeq) {
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
- this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
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
- this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
34
- this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
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 timestamp = Math.floor(Date.now() / 1000);
66
- let sequenceNumber = timestamp % 65536;
67
- const rtpHeader = new RtpHeader({
68
- version: 2,
69
- padding: false,
70
- paddingSize: 0,
71
- extension: false,
72
- marker: false,
73
- payloadOffset: 12,
74
- payloadType: 101,
75
- sequenceNumber,
76
- timestamp,
77
- ssrc: randomInt(),
78
- csrcLength: 0,
79
- csrc: [],
80
- extensionProfile: 48862,
81
- extensionLength: undefined,
82
- extensions: [],
83
- });
84
- for (const payload of DTMF.charToPayloads(char)) {
85
- rtpHeader.sequenceNumber = sequenceNumber++;
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
- if (inboundMessage.headers.CSeq !== message.headers.CSeq) {
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.2.4",
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
- "mixpanel": "^0.18.1",
34
- "wait-for-async": "^0.7.13",
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": "^24.7.1",
40
+ "@types/node": "^25.2.0",
39
41
  "dotenv-override-true": "^6.2.2",
40
- "tsx": "^4.20.6",
42
+ "tsx": "^4.21.0",
41
43
  "typescript": "^5.9.3",
42
44
  "yarn-upgrade-all": "^0.7.5"
43
45
  },