ringcentral-softphone 1.2.0 → 1.2.2

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/README.md CHANGED
@@ -138,6 +138,20 @@ For complete examples, see [demos/](demos/)
138
138
  - receive audio stream from peer
139
139
  - stream local audio to remote peer
140
140
  - call transfer
141
+ - hold / unhold
142
+
143
+ ## call transfer
144
+
145
+ ```ts
146
+ await callSession.transfer("12345678987");
147
+ ```
148
+
149
+ ## hold / unhold
150
+
151
+ ```ts
152
+ await callSession.hold();
153
+ await callSession.unhold();
154
+ ```
141
155
 
142
156
  ## Audio codec
143
157
 
@@ -267,15 +281,16 @@ callSession1.on("rtpPacket", (rtpPacket: RtpPacket) => {
267
281
  });
268
282
  ```
269
283
 
270
- ## Telephony Session ID
284
+ ## Telephony Session ID (& Call Party ID)
271
285
 
272
286
  For outbound calls, you will be able to find header like this
273
287
  `p-rc-api-ids: party-id=p-a0d17e323f0fez1953f50f90dz296e3440000-1;session-id=s-a0d17e323f0fez1953f50f90dz296e3440000`
274
- from `callSession.sipMessage.headers`.
288
+ from `outbounCallSession.sipMessage.headers`. I have added two sugar methods:
289
+ `outboundCallSession.sessionId` and `outboundCallSession.partyId`.
275
290
 
276
- However, for inbound calls, the server doesn't tell us anything about the
277
- Telephony Session ID. Here is a workaround solution:
278
- https://github.com/tylerlong/rc-softphone-call-id-test
291
+ However, for inbound calls, the SIP server doesn't tell us anything about the
292
+ Telephony Session ID. You may use
293
+ [this workaround](https://github.com/tylerlong/rc-softphone-call-id-test).
279
294
 
280
295
  ## 🔧 `ignoreTlsCertErrors` (optional)
281
296
 
@@ -288,7 +303,7 @@ certificate validation errors when establishing a TLS connection.
288
303
  To bypass these errors, you can set the `ignoreTlsCertErrors` flag to `true`:
289
304
 
290
305
  ```ts
291
- const phone = new SoftPhone({
306
+ const softphone = new Softphone({
292
307
  ...
293
308
  ignoreTlsCertErrors: true
294
309
  });
@@ -324,6 +339,14 @@ Content below is for the maintainer/contributor of this SDK.
324
339
  - Caller Id feature is not supported. `P-Asserted-Identity` doesn't work. I
325
340
  think it is by design, since hardphone cannot support it.
326
341
 
342
+ ## Conferences
343
+
344
+ Conference involves RESTful API which is out of scope of this SDK. With this
345
+ being said, this SDK works well with conferences. Here is a
346
+ [demo project for this SDK work with conferences](https://github.com/tylerlong/softphone-invite-agent-to-conference-demo).
347
+ The demo is about making a call to a call queue number, it would be even simpler
348
+ if there is no call queue.
349
+
327
350
  #### Code style
328
351
 
329
352
  We use `deno fmt && deno lint --fix` to format and lint all code.
@@ -27,6 +27,7 @@ a=fmtp:101 0-15
27
27
  a=sendrecv
28
28
  a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
29
29
  `.trim();
30
+ this.sdp = answerSDP;
30
31
  const newMessage = new index_js_2.OutboundMessage("SIP/2.0 200 OK", {
31
32
  Via: this.sipMessage.headers.Via,
32
33
  "Call-ID": this.sipMessage.getHeader("Call-ID"),
@@ -21,6 +21,7 @@ declare abstract class CallSession extends EventEmitter {
21
21
  decoder: {
22
22
  decode: (audio: Buffer) => Buffer;
23
23
  };
24
+ sdp: string;
24
25
  ssrc: number;
25
26
  sequenceNumber: number;
26
27
  timestamp: number;
@@ -35,5 +36,8 @@ declare abstract class CallSession extends EventEmitter {
35
36
  protected startLocalServices(): void;
36
37
  protected dispose(): void;
37
38
  transfer(transferTo: string): Promise<void>;
39
+ toggleReceive(toReceive: boolean): Promise<void>;
40
+ hold(): Promise<void>;
41
+ unhold(): Promise<void>;
38
42
  }
39
43
  export default CallSession;
@@ -23,6 +23,7 @@ class CallSession extends node_events_1.default {
23
23
  srtpSession;
24
24
  encoder;
25
25
  decoder;
26
+ sdp;
26
27
  // for audio streaming
27
28
  ssrc = (0, utils_js_1.randomInt)();
28
29
  sequenceNumber = (0, utils_js_1.randomInt)();
@@ -192,5 +193,34 @@ class CallSession extends node_events_1.default {
192
193
  this.softphone.on("message", notifyHandler);
193
194
  });
194
195
  }
196
+ async toggleReceive(toReceive) {
197
+ let newSDP = this.sdp;
198
+ if (!toReceive) {
199
+ newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
200
+ }
201
+ const requestMessage = new index_js_1.RequestMessage(`INVITE ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
202
+ "Call-Id": this.callId,
203
+ From: this.localPeer,
204
+ To: this.remotePeer,
205
+ Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, utils_js_1.branch)()};alias`,
206
+ "Content-Type": "application/sdp",
207
+ Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
208
+ }, newSDP);
209
+ const replyMessage = await this.softphone.send(requestMessage, true);
210
+ const ackMessage = new index_js_1.RequestMessage(`ACK ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
211
+ "Call-Id": this.callId,
212
+ From: this.localPeer,
213
+ To: this.remotePeer,
214
+ Via: replyMessage.headers.Via,
215
+ CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK"),
216
+ });
217
+ await this.softphone.send(ackMessage);
218
+ }
219
+ async hold() {
220
+ return this.toggleReceive(false);
221
+ }
222
+ async unhold() {
223
+ return this.toggleReceive(true);
224
+ }
195
225
  }
196
226
  exports.default = CallSession;
@@ -5,5 +5,7 @@ declare class OutboundCallSession extends CallSession {
5
5
  constructor(softphone: Softphone, answerMessage: InboundMessage);
6
6
  init(): void;
7
7
  cancel(): Promise<void>;
8
+ get sessionId(): string;
9
+ get partyId(): string;
8
10
  }
9
11
  export default OutboundCallSession;
@@ -52,5 +52,15 @@ class OutboundCallSession extends index_js_1.default {
52
52
  });
53
53
  await this.softphone.send(requestMessage);
54
54
  }
55
+ get sessionId() {
56
+ const header = this.sipMessage.headers["p-rc-api-ids"];
57
+ let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
58
+ return match[2];
59
+ }
60
+ get partyId() {
61
+ const header = this.sipMessage.headers["p-rc-api-ids"];
62
+ let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
63
+ return match[1];
64
+ }
55
65
  }
56
66
  exports.default = OutboundCallSession;
package/dist/cjs/index.js CHANGED
@@ -198,7 +198,9 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
198
198
  const newMessage = inviteMessage.fork();
199
199
  newMessage.headers["Proxy-Authorization"] = (0, utils_js_1.generateAuthorization)(this.sipInfo, nonce, "INVITE");
200
200
  const progressMessage = await this.send(newMessage, true);
201
- return new outbound_js_1.default(this, progressMessage);
201
+ const outboundCallSession = new outbound_js_1.default(this, progressMessage);
202
+ outboundCallSession.sdp = offerSDP;
203
+ return outboundCallSession;
202
204
  }
203
205
  }
204
206
  exports.default = Softphone;
@@ -22,6 +22,7 @@ a=fmtp:101 0-15
22
22
  a=sendrecv
23
23
  a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
24
24
  `.trim();
25
+ this.sdp = answerSDP;
25
26
  const newMessage = new OutboundMessage("SIP/2.0 200 OK", {
26
27
  Via: this.sipMessage.headers.Via,
27
28
  "Call-ID": this.sipMessage.getHeader("Call-ID"),
@@ -21,6 +21,7 @@ declare abstract class CallSession extends EventEmitter {
21
21
  decoder: {
22
22
  decode: (audio: Buffer) => Buffer;
23
23
  };
24
+ sdp: string;
24
25
  ssrc: number;
25
26
  sequenceNumber: number;
26
27
  timestamp: number;
@@ -35,5 +36,8 @@ declare abstract class CallSession extends EventEmitter {
35
36
  protected startLocalServices(): void;
36
37
  protected dispose(): void;
37
38
  transfer(transferTo: string): Promise<void>;
39
+ toggleReceive(toReceive: boolean): Promise<void>;
40
+ hold(): Promise<void>;
41
+ unhold(): Promise<void>;
38
42
  }
39
43
  export default CallSession;
@@ -4,7 +4,7 @@ import { Buffer } from "node:buffer";
4
4
  import { RtpHeader, RtpPacket, SrtpSession } from "werift-rtp";
5
5
  import DTMF from "../dtmf.js";
6
6
  import { RequestMessage, ResponseMessage, } from "../sip-message/index.js";
7
- import { branch, localKey, randomInt } from "../utils.js";
7
+ import { branch, extractAddress, localKey, randomInt } from "../utils.js";
8
8
  import Streamer from "./streamer.js";
9
9
  class CallSession extends EventEmitter {
10
10
  softphone;
@@ -18,6 +18,7 @@ class CallSession extends EventEmitter {
18
18
  srtpSession;
19
19
  encoder;
20
20
  decoder;
21
+ sdp;
21
22
  // for audio streaming
22
23
  ssrc = randomInt();
23
24
  sequenceNumber = randomInt();
@@ -187,5 +188,34 @@ class CallSession extends EventEmitter {
187
188
  this.softphone.on("message", notifyHandler);
188
189
  });
189
190
  }
191
+ async toggleReceive(toReceive) {
192
+ let newSDP = this.sdp;
193
+ if (!toReceive) {
194
+ newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
195
+ }
196
+ const requestMessage = new RequestMessage(`INVITE ${extractAddress(this.remotePeer)} SIP/2.0`, {
197
+ "Call-Id": this.callId,
198
+ From: this.localPeer,
199
+ To: this.remotePeer,
200
+ Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${branch()};alias`,
201
+ "Content-Type": "application/sdp",
202
+ Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
203
+ }, newSDP);
204
+ const replyMessage = await this.softphone.send(requestMessage, true);
205
+ const ackMessage = new RequestMessage(`ACK ${extractAddress(this.remotePeer)} SIP/2.0`, {
206
+ "Call-Id": this.callId,
207
+ From: this.localPeer,
208
+ To: this.remotePeer,
209
+ Via: replyMessage.headers.Via,
210
+ CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK"),
211
+ });
212
+ await this.softphone.send(ackMessage);
213
+ }
214
+ async hold() {
215
+ return this.toggleReceive(false);
216
+ }
217
+ async unhold() {
218
+ return this.toggleReceive(true);
219
+ }
190
220
  }
191
221
  export default CallSession;
@@ -5,5 +5,7 @@ declare class OutboundCallSession extends CallSession {
5
5
  constructor(softphone: Softphone, answerMessage: InboundMessage);
6
6
  init(): void;
7
7
  cancel(): Promise<void>;
8
+ get sessionId(): string;
9
+ get partyId(): string;
8
10
  }
9
11
  export default OutboundCallSession;
@@ -47,5 +47,15 @@ class OutboundCallSession extends CallSession {
47
47
  });
48
48
  await this.softphone.send(requestMessage);
49
49
  }
50
+ get sessionId() {
51
+ const header = this.sipMessage.headers["p-rc-api-ids"];
52
+ let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
53
+ return match[2];
54
+ }
55
+ get partyId() {
56
+ const header = this.sipMessage.headers["p-rc-api-ids"];
57
+ let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
58
+ return match[1];
59
+ }
50
60
  }
51
61
  export default OutboundCallSession;
package/dist/esm/index.js CHANGED
@@ -193,7 +193,9 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
193
193
  const newMessage = inviteMessage.fork();
194
194
  newMessage.headers["Proxy-Authorization"] = generateAuthorization(this.sipInfo, nonce, "INVITE");
195
195
  const progressMessage = await this.send(newMessage, true);
196
- return new OutboundCallSession(this, progressMessage);
196
+ const outboundCallSession = new OutboundCallSession(this, progressMessage);
197
+ outboundCallSession.sdp = offerSDP;
198
+ return outboundCallSession;
197
199
  }
198
200
  }
199
201
  export default Softphone;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ringcentral-softphone",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "homepage": "https://github.com/ringcentral/ringcentral-softphone-ts",
5
5
  "license": "MIT",
6
6
  "types": "dist/esm/index.d.ts",