ringcentral-softphone 1.3.2 → 1.3.4

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.
Files changed (99) hide show
  1. package/README.md +1 -1
  2. package/dist/call-session/inbound.cjs +93 -0
  3. package/dist/call-session/inbound.js +64 -0
  4. package/dist/call-session/index.cjs +274 -0
  5. package/dist/{esm/call-session → call-session}/index.d.ts +6 -4
  6. package/dist/call-session/index.js +248 -0
  7. package/dist/call-session/outbound.cjs +100 -0
  8. package/dist/call-session/outbound.js +71 -0
  9. package/dist/call-session/streamer.cjs +112 -0
  10. package/dist/{esm/call-session → call-session}/streamer.d.ts +2 -2
  11. package/dist/call-session/streamer.js +83 -0
  12. package/dist/codec.cjs +85 -0
  13. package/dist/codec.js +66 -0
  14. package/dist/dtmf.cjs +66 -0
  15. package/dist/dtmf.d.ts +7 -0
  16. package/dist/dtmf.js +47 -0
  17. package/dist/index.cjs +260 -0
  18. package/dist/{cjs/index.d.ts → index.d.ts} +3 -3
  19. package/dist/index.js +242 -0
  20. package/dist/sip-message/inbound/index.cjs +51 -0
  21. package/dist/sip-message/inbound/index.js +22 -0
  22. package/dist/sip-message/index.cjs +49 -0
  23. package/dist/sip-message/index.js +12 -0
  24. package/dist/sip-message/outbound/index.cjs +41 -0
  25. package/dist/sip-message/outbound/index.js +12 -0
  26. package/dist/sip-message/outbound/request.cjs +62 -0
  27. package/dist/sip-message/outbound/request.js +33 -0
  28. package/dist/sip-message/outbound/response.cjs +55 -0
  29. package/dist/sip-message/outbound/response.js +26 -0
  30. package/dist/sip-message/response-codes.cjs +102 -0
  31. package/dist/sip-message/response-codes.js +83 -0
  32. package/dist/sip-message/sip-message.cjs +53 -0
  33. package/dist/sip-message/sip-message.js +34 -0
  34. package/dist/types.cjs +15 -0
  35. package/dist/types.js +0 -0
  36. package/dist/utils.cjs +80 -0
  37. package/dist/{cjs/utils.d.ts → utils.d.ts} +2 -2
  38. package/dist/utils.js +41 -0
  39. package/package.json +19 -13
  40. package/dist/cjs/call-session/inbound.js +0 -57
  41. package/dist/cjs/call-session/index.d.ts +0 -44
  42. package/dist/cjs/call-session/index.js +0 -239
  43. package/dist/cjs/call-session/outbound.js +0 -66
  44. package/dist/cjs/call-session/streamer.d.ts +0 -17
  45. package/dist/cjs/call-session/streamer.js +0 -76
  46. package/dist/cjs/codec.js +0 -65
  47. package/dist/cjs/dtmf.d.ts +0 -8
  48. package/dist/cjs/dtmf.js +0 -45
  49. package/dist/cjs/index.js +0 -209
  50. package/dist/cjs/sip-message/inbound/index.js +0 -22
  51. package/dist/cjs/sip-message/index.d.ts +0 -5
  52. package/dist/cjs/sip-message/index.js +0 -16
  53. package/dist/cjs/sip-message/outbound/index.js +0 -14
  54. package/dist/cjs/sip-message/outbound/request.js +0 -28
  55. package/dist/cjs/sip-message/outbound/response.js +0 -25
  56. package/dist/cjs/sip-message/response-codes.js +0 -83
  57. package/dist/cjs/sip-message/sip-message.js +0 -34
  58. package/dist/cjs/types.js +0 -2
  59. package/dist/cjs/utils.js +0 -40
  60. package/dist/esm/call-session/inbound.d.ts +0 -8
  61. package/dist/esm/call-session/inbound.js +0 -52
  62. package/dist/esm/call-session/index.js +0 -234
  63. package/dist/esm/call-session/outbound.d.ts +0 -11
  64. package/dist/esm/call-session/outbound.js +0 -61
  65. package/dist/esm/call-session/streamer.js +0 -71
  66. package/dist/esm/codec.d.ts +0 -15
  67. package/dist/esm/codec.js +0 -63
  68. package/dist/esm/dtmf.d.ts +0 -8
  69. package/dist/esm/dtmf.js +0 -43
  70. package/dist/esm/index.d.ts +0 -28
  71. package/dist/esm/index.js +0 -204
  72. package/dist/esm/sip-message/inbound/index.d.ts +0 -5
  73. package/dist/esm/sip-message/inbound/index.js +0 -17
  74. package/dist/esm/sip-message/index.js +0 -5
  75. package/dist/esm/sip-message/outbound/index.d.ts +0 -5
  76. package/dist/esm/sip-message/outbound/index.js +0 -9
  77. package/dist/esm/sip-message/outbound/request.d.ts +0 -7
  78. package/dist/esm/sip-message/outbound/request.js +0 -23
  79. package/dist/esm/sip-message/outbound/response.d.ts +0 -6
  80. package/dist/esm/sip-message/outbound/response.js +0 -20
  81. package/dist/esm/sip-message/response-codes.d.ts +0 -4
  82. package/dist/esm/sip-message/response-codes.js +0 -81
  83. package/dist/esm/sip-message/sip-message.d.ts +0 -11
  84. package/dist/esm/sip-message/sip-message.js +0 -32
  85. package/dist/esm/types.d.ts +0 -9
  86. package/dist/esm/types.js +0 -1
  87. package/dist/esm/utils.d.ts +0 -8
  88. package/dist/esm/utils.js +0 -28
  89. package/dist/{cjs/call-session → call-session}/inbound.d.ts +2 -2
  90. package/dist/{cjs/call-session → call-session}/outbound.d.ts +2 -2
  91. package/dist/{cjs/codec.d.ts → codec.d.ts} +0 -0
  92. package/dist/{cjs/sip-message → sip-message}/inbound/index.d.ts +0 -0
  93. package/dist/{esm/sip-message → sip-message}/index.d.ts +2 -2
  94. package/dist/{cjs/sip-message → sip-message}/outbound/index.d.ts +0 -0
  95. package/dist/{cjs/sip-message → sip-message}/outbound/request.d.ts +0 -0
  96. package/dist/{cjs/sip-message → sip-message}/outbound/response.d.ts +1 -1
  97. /package/dist/{cjs/sip-message → sip-message}/response-codes.d.ts +0 -0
  98. /package/dist/{cjs/sip-message → sip-message}/sip-message.d.ts +0 -0
  99. /package/dist/{cjs/types.d.ts → types.d.ts} +0 -0
package/README.md CHANGED
@@ -463,7 +463,7 @@ if there is no call queue.
463
463
 
464
464
  #### Code style
465
465
 
466
- We use `deno fmt && deno lint --fix` to format and lint all code.
466
+ We use `yarn lint` to format and lint all code.
467
467
 
468
468
  #### Docs
469
469
 
@@ -0,0 +1,93 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var inbound_exports = {};
29
+ __export(inbound_exports, {
30
+ default: () => inbound_default
31
+ });
32
+ module.exports = __toCommonJS(inbound_exports);
33
+ var import_sip_message = require("../sip-message/index.js");
34
+ var import_utils = require("../utils.js");
35
+ var import_index = __toESM(require("./index.js"), 1);
36
+ class InboundCallSession extends import_index.default {
37
+ constructor(softphone, inviteMessage) {
38
+ super(softphone, inviteMessage);
39
+ this.localPeer = inviteMessage.headers.To;
40
+ this.remotePeer = inviteMessage.headers.From;
41
+ if (inviteMessage.body.length > 0) {
42
+ this.remoteKey = inviteMessage.body.match(
43
+ /AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
44
+ )[1];
45
+ }
46
+ }
47
+ async answer() {
48
+ const answerSDP = `
49
+ v=0
50
+ o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
51
+ s=rc-softphone-ts
52
+ c=IN IP4 ${this.softphone.client.localAddress}
53
+ t=0 0
54
+ m=audio ${(0, import_utils.randomInt)()} RTP/SAVP ${this.softphone.codec.id} 101
55
+ a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
56
+ a=rtpmap:101 telephone-event/8000
57
+ a=fmtp:101 0-15
58
+ a=sendrecv
59
+ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${import_utils.localKey}
60
+ `.trim();
61
+ this.sdp = answerSDP;
62
+ const newMessage = new import_sip_message.OutboundMessage(
63
+ "SIP/2.0 200 OK",
64
+ {
65
+ Via: this.sipMessage.headers.Via,
66
+ "Call-ID": this.sipMessage.getHeader("Call-ID"),
67
+ From: this.sipMessage.headers.From,
68
+ To: this.sipMessage.headers.To,
69
+ CSeq: this.sipMessage.headers.CSeq,
70
+ Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
71
+ Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
72
+ Supported: "replaces, 100rel, timer, norefersub",
73
+ "Session-Expires": "14400;refresher=uac",
74
+ Require: "timer",
75
+ "Content-Type": "application/sdp"
76
+ },
77
+ answerSDP
78
+ );
79
+ const ackMessage = await this.softphone.send(newMessage, true);
80
+ if (ackMessage.body.length > 0) {
81
+ this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
82
+ this.remotePort = parseInt(
83
+ ackMessage.body.match(/m=audio (\d+) /)[1],
84
+ 10
85
+ );
86
+ this.remoteKey = ackMessage.body.match(
87
+ /AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
88
+ )[1];
89
+ }
90
+ this.startLocalServices();
91
+ }
92
+ }
93
+ var inbound_default = InboundCallSession;
@@ -0,0 +1,64 @@
1
+ import { OutboundMessage } from "../sip-message/index.js";
2
+ import { localKey, randomInt } from "../utils.js";
3
+ import CallSession from "./index.js";
4
+ class InboundCallSession extends CallSession {
5
+ constructor(softphone, inviteMessage) {
6
+ super(softphone, inviteMessage);
7
+ this.localPeer = inviteMessage.headers.To;
8
+ this.remotePeer = inviteMessage.headers.From;
9
+ if (inviteMessage.body.length > 0) {
10
+ this.remoteKey = inviteMessage.body.match(
11
+ /AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
12
+ )[1];
13
+ }
14
+ }
15
+ async answer() {
16
+ const answerSDP = `
17
+ v=0
18
+ o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
19
+ s=rc-softphone-ts
20
+ c=IN IP4 ${this.softphone.client.localAddress}
21
+ t=0 0
22
+ m=audio ${randomInt()} RTP/SAVP ${this.softphone.codec.id} 101
23
+ a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
24
+ a=rtpmap:101 telephone-event/8000
25
+ a=fmtp:101 0-15
26
+ a=sendrecv
27
+ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
28
+ `.trim();
29
+ this.sdp = answerSDP;
30
+ const newMessage = new OutboundMessage(
31
+ "SIP/2.0 200 OK",
32
+ {
33
+ Via: this.sipMessage.headers.Via,
34
+ "Call-ID": this.sipMessage.getHeader("Call-ID"),
35
+ From: this.sipMessage.headers.From,
36
+ To: this.sipMessage.headers.To,
37
+ CSeq: this.sipMessage.headers.CSeq,
38
+ Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
39
+ Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
40
+ Supported: "replaces, 100rel, timer, norefersub",
41
+ "Session-Expires": "14400;refresher=uac",
42
+ Require: "timer",
43
+ "Content-Type": "application/sdp"
44
+ },
45
+ answerSDP
46
+ );
47
+ const ackMessage = await this.softphone.send(newMessage, true);
48
+ if (ackMessage.body.length > 0) {
49
+ this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
50
+ this.remotePort = parseInt(
51
+ ackMessage.body.match(/m=audio (\d+) /)[1],
52
+ 10
53
+ );
54
+ this.remoteKey = ackMessage.body.match(
55
+ /AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
56
+ )[1];
57
+ }
58
+ this.startLocalServices();
59
+ }
60
+ }
61
+ var inbound_default = InboundCallSession;
62
+ export {
63
+ inbound_default as default
64
+ };
@@ -0,0 +1,274 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var call_session_exports = {};
29
+ __export(call_session_exports, {
30
+ default: () => call_session_default
31
+ });
32
+ module.exports = __toCommonJS(call_session_exports);
33
+ var import_node_buffer = require("node:buffer");
34
+ var import_node_dgram = __toESM(require("node:dgram"), 1);
35
+ var import_node_events = __toESM(require("node:events"), 1);
36
+ var import_wait_for_async = __toESM(require("wait-for-async"), 1);
37
+ var import_werift_rtp = require("werift-rtp");
38
+ var import_dtmf = __toESM(require("../dtmf.js"), 1);
39
+ var import_sip_message = require("../sip-message/index.js");
40
+ var import_utils = require("../utils.js");
41
+ var import_streamer = __toESM(require("./streamer.js"), 1);
42
+ const isDtmfChar = (value) => import_dtmf.default.phoneChars.includes(value);
43
+ class CallSession extends import_node_events.default {
44
+ softphone;
45
+ sipMessage;
46
+ socket;
47
+ localPeer;
48
+ remotePeer;
49
+ remoteIP;
50
+ remotePort;
51
+ disposed = false;
52
+ srtpSession;
53
+ encoder;
54
+ decoder;
55
+ sdp;
56
+ // for audio streaming
57
+ ssrc = (0, import_utils.randomInt)();
58
+ sequenceNumber = (0, import_utils.randomInt)();
59
+ timestamp = (0, import_utils.randomInt)();
60
+ constructor(softphone, sipMessage) {
61
+ super();
62
+ this.softphone = softphone;
63
+ this.encoder = softphone.codec.createEncoder();
64
+ this.decoder = softphone.codec.createDecoder();
65
+ this.sipMessage = sipMessage;
66
+ if (this.sipMessage.body.length > 0) {
67
+ this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
68
+ this.remotePort = parseInt(
69
+ this.sipMessage.body.match(/m=audio (\d+) /)[1],
70
+ 10
71
+ );
72
+ }
73
+ }
74
+ set remoteKey(key) {
75
+ const localKeyBuffer = import_node_buffer.Buffer.from(import_utils.localKey, "base64");
76
+ const remoteKeyBuffer = import_node_buffer.Buffer.from(key, "base64");
77
+ this.srtpSession = new import_werift_rtp.SrtpSession({
78
+ profile: 1,
79
+ keys: {
80
+ localMasterKey: localKeyBuffer.subarray(0, 16),
81
+ localMasterSalt: localKeyBuffer.subarray(16, 30),
82
+ remoteMasterKey: remoteKeyBuffer.subarray(0, 16),
83
+ remoteMasterSalt: remoteKeyBuffer.subarray(16, 30)
84
+ }
85
+ });
86
+ }
87
+ get callId() {
88
+ return this.sipMessage.getHeader("Call-ID");
89
+ }
90
+ send(data) {
91
+ this.socket.send(data, this.remotePort, this.remoteIP);
92
+ }
93
+ async hangup() {
94
+ const requestMessage = new import_sip_message.RequestMessage(
95
+ `BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`,
96
+ {
97
+ "Call-ID": this.callId,
98
+ From: this.localPeer,
99
+ To: this.remotePeer,
100
+ Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${(0, import_utils.branch)()}`
101
+ }
102
+ );
103
+ await this.softphone.send(requestMessage);
104
+ }
105
+ sendDTMF(char) {
106
+ const payloads = import_dtmf.default.charToPayloads(char);
107
+ const timestamp = this.timestamp;
108
+ let first = true;
109
+ for (const payload of payloads) {
110
+ const rtpHeader = new import_werift_rtp.RtpHeader({
111
+ version: 2,
112
+ padding: false,
113
+ paddingSize: 0,
114
+ extension: false,
115
+ marker: first,
116
+ payloadOffset: 12,
117
+ payloadType: 101,
118
+ sequenceNumber: this.sequenceNumber,
119
+ timestamp,
120
+ ssrc: this.ssrc,
121
+ csrcLength: 0,
122
+ csrc: [],
123
+ extensionProfile: 48862,
124
+ extensionLength: void 0,
125
+ extensions: []
126
+ });
127
+ const rtpPacket = new import_werift_rtp.RtpPacket(rtpHeader, payload);
128
+ this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
129
+ this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
130
+ first = false;
131
+ }
132
+ this.timestamp += 800;
133
+ }
134
+ async sendDTMFs(s, delay = 500) {
135
+ for (const c of s) {
136
+ if (!isDtmfChar(c)) {
137
+ throw new Error(`invalid phone char: ${c}`);
138
+ }
139
+ this.sendDTMF(c);
140
+ await (0, import_wait_for_async.default)({ interval: delay });
141
+ }
142
+ }
143
+ // buffer is the content of a audio file, it is supposed to be uncompressed PCM data
144
+ // The audio should be playable by command: play -t raw -b 16 -r 16000 -e signed-integer test.wav
145
+ streamAudio(input) {
146
+ const streamer = new import_streamer.default(this, input);
147
+ streamer.start();
148
+ return streamer;
149
+ }
150
+ // send a single rtp packet
151
+ sendPacket(rtpPacket) {
152
+ if (this.disposed) {
153
+ return;
154
+ }
155
+ this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
156
+ }
157
+ startLocalServices() {
158
+ this.socket = import_node_dgram.default.createSocket("udp4");
159
+ this.socket.on("message", (message) => {
160
+ const rtpPacket = import_werift_rtp.RtpPacket.deSerialize(
161
+ this.srtpSession.decrypt(message)
162
+ );
163
+ this.emit("rtpPacket", rtpPacket);
164
+ if (rtpPacket.header.payloadType === 101) {
165
+ this.emit("dtmfPacket", rtpPacket);
166
+ const char = import_dtmf.default.payloadToChar(rtpPacket.payload);
167
+ if (char) {
168
+ this.emit("dtmf", char);
169
+ }
170
+ } else if (rtpPacket.header.payloadType === this.softphone.codec.id) {
171
+ if (rtpPacket.payload.length === 4 && rtpPacket.payload[0] >= 0 && rtpPacket.payload[0] < 12 && rtpPacket.payload[1] === 138 && rtpPacket.payload[2] === 3 && rtpPacket.payload[3] === 192) {
172
+ return;
173
+ }
174
+ try {
175
+ rtpPacket.payload = this.decoder.decode(rtpPacket.payload);
176
+ this.emit("audioPacket", rtpPacket);
177
+ } catch {
178
+ console.error("Audio packet decode failed", rtpPacket);
179
+ }
180
+ }
181
+ });
182
+ this.socket.bind();
183
+ this.send("hello");
184
+ const byeHandler = (inboundMessage) => {
185
+ if (inboundMessage.getHeader("Call-ID") !== this.callId) {
186
+ return;
187
+ }
188
+ if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
189
+ this.softphone.off("message", byeHandler);
190
+ this.dispose();
191
+ }
192
+ };
193
+ this.softphone.on("message", byeHandler);
194
+ }
195
+ dispose() {
196
+ this.disposed = true;
197
+ this.emit("disposed");
198
+ this.removeAllListeners();
199
+ this.socket?.removeAllListeners();
200
+ this.socket?.close();
201
+ }
202
+ async transfer(transferTo) {
203
+ const requestMessage = new import_sip_message.RequestMessage(
204
+ `REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`,
205
+ {
206
+ Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, import_utils.branch)()};alias`,
207
+ "Max-Forwards": 70,
208
+ From: this.localPeer,
209
+ To: this.remotePeer,
210
+ Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
211
+ "Call-ID": this.callId,
212
+ Event: "refer",
213
+ Expires: 600,
214
+ Supported: "replaces, 100rel, timer, norefersub",
215
+ Accept: "message/sipfrag;version=2.0",
216
+ "Allow-Events": "presence, message-summary, refer",
217
+ "Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`,
218
+ "Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`
219
+ }
220
+ );
221
+ await this.softphone.send(requestMessage);
222
+ return new Promise((resolve) => {
223
+ const notifyHandler = (inboundMessage) => {
224
+ if (!inboundMessage.subject.startsWith("NOTIFY ")) {
225
+ return;
226
+ }
227
+ const responseMessage = new import_sip_message.ResponseMessage(inboundMessage, 200);
228
+ this.softphone.send(responseMessage);
229
+ if (inboundMessage.body.trim() === "SIP/2.0 200 OK") {
230
+ this.softphone.off("message", notifyHandler);
231
+ resolve();
232
+ }
233
+ };
234
+ this.softphone.on("message", notifyHandler);
235
+ });
236
+ }
237
+ async toggleReceive(toReceive) {
238
+ let newSDP = this.sdp;
239
+ if (!toReceive) {
240
+ newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
241
+ }
242
+ const requestMessage = new import_sip_message.RequestMessage(
243
+ `INVITE ${(0, import_utils.extractAddress)(this.remotePeer)} SIP/2.0`,
244
+ {
245
+ "Call-Id": this.callId,
246
+ From: this.localPeer,
247
+ To: this.remotePeer,
248
+ Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, import_utils.branch)()};alias`,
249
+ "Content-Type": "application/sdp",
250
+ Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`
251
+ },
252
+ newSDP
253
+ );
254
+ const replyMessage = await this.softphone.send(requestMessage, true);
255
+ const ackMessage = new import_sip_message.RequestMessage(
256
+ `ACK ${(0, import_utils.extractAddress)(this.remotePeer)} SIP/2.0`,
257
+ {
258
+ "Call-Id": this.callId,
259
+ From: this.localPeer,
260
+ To: this.remotePeer,
261
+ Via: replyMessage.headers.Via,
262
+ CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK")
263
+ }
264
+ );
265
+ await this.softphone.send(ackMessage);
266
+ }
267
+ async hold() {
268
+ return this.toggleReceive(false);
269
+ }
270
+ async unhold() {
271
+ return this.toggleReceive(true);
272
+ }
273
+ }
274
+ var call_session_default = CallSession;
@@ -1,10 +1,12 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import dgram from "node:dgram";
2
3
  import EventEmitter from "node:events";
3
- import { Buffer } from "node:buffer";
4
4
  import { RtpPacket, SrtpSession } from "werift-rtp";
5
- import { type InboundMessage } from "../sip-message/index.js";
5
+ import DTMF from "../dtmf.js";
6
6
  import type Softphone from "../index.js";
7
+ import { type InboundMessage } from "../sip-message/index.js";
7
8
  import Streamer from "./streamer.js";
9
+ type DtmfChar = (typeof DTMF.phoneChars)[number];
8
10
  declare abstract class CallSession extends EventEmitter {
9
11
  softphone: Softphone;
10
12
  sipMessage: InboundMessage;
@@ -27,10 +29,10 @@ declare abstract class CallSession extends EventEmitter {
27
29
  timestamp: number;
28
30
  constructor(softphone: Softphone, sipMessage: InboundMessage);
29
31
  set remoteKey(key: string);
30
- get callId(): string;
32
+ get callId(): string | undefined;
31
33
  send(data: string | Buffer): void;
32
34
  hangup(): Promise<void>;
33
- sendDTMF(char: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#"): void;
35
+ sendDTMF(char: DtmfChar): void;
34
36
  sendDTMFs(s: string, delay?: number): Promise<void>;
35
37
  streamAudio(input: Buffer): Streamer;
36
38
  sendPacket(rtpPacket: RtpPacket): void;