ringcentral-softphone 1.3.4 → 1.3.6
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/dist/call-session/inbound.cjs +41 -83
- package/dist/call-session/inbound.d.cts +2 -0
- package/dist/call-session/inbound.d.mts +2 -0
- package/dist/call-session/inbound.mjs +51 -0
- package/dist/call-session/index.cjs +212 -273
- package/dist/call-session/index.d.cts +2 -0
- package/dist/call-session/index.d.mts +2 -0
- package/dist/call-session/index.mjs +209 -0
- package/dist/call-session/outbound.cjs +56 -99
- package/dist/call-session/outbound.d.cts +2 -0
- package/dist/call-session/outbound.d.mts +2 -0
- package/dist/call-session/outbound.mjs +57 -0
- package/dist/call-session/streamer.cjs +66 -111
- package/dist/call-session/streamer.d.cts +2 -0
- package/dist/call-session/streamer.d.mts +2 -0
- package/dist/call-session/streamer.mjs +65 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/codec-Bh7v8J-S.d.mts +18 -0
- package/dist/codec-C-VrtVkq.d.cts +18 -0
- package/dist/codec.cjs +68 -84
- package/dist/codec.d.cts +2 -0
- package/dist/codec.d.mts +2 -0
- package/dist/codec.mjs +68 -0
- package/dist/dtmf-B13Fz2VR.d.mts +10 -0
- package/dist/dtmf-DcQ-5vSG.d.cts +10 -0
- package/dist/dtmf.cjs +37 -60
- package/dist/dtmf.d.cts +2 -0
- package/dist/dtmf.d.mts +2 -0
- package/dist/dtmf.mjs +42 -0
- package/dist/inbound--wGoGqLS.d.mts +103 -0
- package/dist/inbound-DquvTSj1.d.cts +103 -0
- package/dist/index--UjWgLK-.d.mts +8 -0
- package/dist/index-BhN2W8AV.d.mts +8 -0
- package/dist/index-CEgs-Dz2.d.cts +1 -0
- package/dist/index-Cf2Cev52.d.cts +8 -0
- package/dist/index-XMDop59x.d.cts +8 -0
- package/dist/index-q_LXL61M.d.mts +1 -0
- package/dist/index.cjs +166 -250
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +172 -0
- package/dist/request-B_auLSJn.d.cts +10 -0
- package/dist/request-pBe7_mYv.d.mts +10 -0
- package/dist/response-LRRpY8lX.d.mts +9 -0
- package/dist/response-ReKvb5x9.d.cts +9 -0
- package/dist/sip-message/inbound/index.cjs +16 -50
- package/dist/sip-message/inbound/index.d.cts +2 -0
- package/dist/sip-message/inbound/index.d.mts +2 -0
- package/dist/sip-message/inbound/index.mjs +17 -0
- package/dist/sip-message/index.cjs +11 -49
- package/dist/sip-message/index.d.cts +6 -0
- package/dist/sip-message/index.d.mts +6 -0
- package/dist/sip-message/index.mjs +6 -0
- package/dist/sip-message/outbound/index.cjs +10 -40
- package/dist/sip-message/outbound/index.d.cts +2 -0
- package/dist/sip-message/outbound/index.d.mts +2 -0
- package/dist/sip-message/outbound/index.mjs +11 -0
- package/dist/sip-message/outbound/request.cjs +20 -61
- package/dist/sip-message/outbound/request.d.cts +2 -0
- package/dist/sip-message/outbound/request.d.mts +2 -0
- package/dist/sip-message/outbound/request.mjs +21 -0
- package/dist/sip-message/outbound/response.cjs +25 -54
- package/dist/sip-message/outbound/response.d.cts +2 -0
- package/dist/sip-message/outbound/response.d.mts +2 -0
- package/dist/sip-message/outbound/response.mjs +26 -0
- package/dist/sip-message/response-codes.cjs +80 -100
- package/dist/sip-message/response-codes.d.cts +5 -0
- package/dist/sip-message/response-codes.d.mts +6 -0
- package/dist/sip-message/response-codes.mjs +82 -0
- package/dist/sip-message/sip-message.cjs +25 -52
- package/dist/sip-message/sip-message.d.cts +2 -0
- package/dist/sip-message/sip-message.d.mts +2 -0
- package/dist/sip-message/sip-message.mjs +26 -0
- package/dist/sip-message-B2D5MPBI.d.cts +13 -0
- package/dist/sip-message-PaPho4qU.d.mts +13 -0
- package/dist/types-DOQ9wmX6.d.mts +12 -0
- package/dist/types-DZxCsbZE.d.cts +12 -0
- package/dist/types.cjs +0 -15
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/dist/utils.cjs +27 -73
- package/dist/utils.d.cts +12 -0
- package/dist/utils.d.mts +12 -0
- package/dist/utils.mjs +25 -0
- package/package.json +14 -14
- package/dist/call-session/inbound.d.ts +0 -8
- package/dist/call-session/inbound.js +0 -64
- package/dist/call-session/index.d.ts +0 -46
- package/dist/call-session/index.js +0 -248
- package/dist/call-session/outbound.d.ts +0 -11
- package/dist/call-session/outbound.js +0 -71
- package/dist/call-session/streamer.d.ts +0 -17
- package/dist/call-session/streamer.js +0 -83
- package/dist/codec.d.ts +0 -15
- package/dist/codec.js +0 -66
- package/dist/dtmf.d.ts +0 -7
- package/dist/dtmf.js +0 -47
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -242
- package/dist/sip-message/inbound/index.d.ts +0 -5
- package/dist/sip-message/inbound/index.js +0 -22
- package/dist/sip-message/index.d.ts +0 -5
- package/dist/sip-message/index.js +0 -12
- package/dist/sip-message/outbound/index.d.ts +0 -5
- package/dist/sip-message/outbound/index.js +0 -12
- package/dist/sip-message/outbound/request.d.ts +0 -7
- package/dist/sip-message/outbound/request.js +0 -33
- package/dist/sip-message/outbound/response.d.ts +0 -6
- package/dist/sip-message/outbound/response.js +0 -26
- package/dist/sip-message/response-codes.d.ts +0 -4
- package/dist/sip-message/response-codes.js +0 -83
- package/dist/sip-message/sip-message.d.ts +0 -11
- package/dist/sip-message/sip-message.js +0 -34
- package/dist/types.d.ts +0 -9
- package/dist/types.js +0 -0
- package/dist/utils.d.ts +0 -8
- package/dist/utils.js +0 -41
|
@@ -1,93 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 = `
|
|
1
|
+
const require_utils = require("../utils.cjs");
|
|
2
|
+
const require_sip_message_outbound_index = require("../sip-message/outbound/index.cjs");
|
|
3
|
+
require("../sip-message/index.cjs");
|
|
4
|
+
const require_call_session_index = require("./index.cjs");
|
|
5
|
+
//#region src/call-session/inbound.ts
|
|
6
|
+
var InboundCallSession = class extends require_call_session_index {
|
|
7
|
+
constructor(softphone, inviteMessage) {
|
|
8
|
+
super(softphone, inviteMessage);
|
|
9
|
+
this.localPeer = inviteMessage.headers.To;
|
|
10
|
+
this.remotePeer = inviteMessage.headers.From;
|
|
11
|
+
if (inviteMessage.body.length > 0) this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
12
|
+
}
|
|
13
|
+
async answer() {
|
|
14
|
+
const answerSDP = `
|
|
49
15
|
v=0
|
|
50
16
|
o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
|
|
51
17
|
s=rc-softphone-ts
|
|
52
18
|
c=IN IP4 ${this.softphone.client.localAddress}
|
|
53
19
|
t=0 0
|
|
54
|
-
m=audio ${
|
|
20
|
+
m=audio ${require_utils.randomInt()} RTP/SAVP ${this.softphone.codec.id} 101
|
|
55
21
|
a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
|
|
56
22
|
a=rtpmap:101 telephone-event/8000
|
|
57
23
|
a=fmtp:101 0-15
|
|
58
24
|
a=sendrecv
|
|
59
|
-
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${
|
|
25
|
+
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${require_utils.localKey}
|
|
60
26
|
`.trim();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
27
|
+
this.sdp = answerSDP;
|
|
28
|
+
const newMessage = new require_sip_message_outbound_index("SIP/2.0 200 OK", {
|
|
29
|
+
Via: this.sipMessage.headers.Via,
|
|
30
|
+
"Call-ID": this.sipMessage.getHeader("Call-ID"),
|
|
31
|
+
From: this.sipMessage.headers.From,
|
|
32
|
+
To: this.sipMessage.headers.To,
|
|
33
|
+
CSeq: this.sipMessage.headers.CSeq,
|
|
34
|
+
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
|
|
35
|
+
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
|
|
36
|
+
Supported: "replaces, 100rel, timer, norefersub",
|
|
37
|
+
"Session-Expires": "14400;refresher=uac",
|
|
38
|
+
Require: "timer",
|
|
39
|
+
"Content-Type": "application/sdp"
|
|
40
|
+
}, answerSDP);
|
|
41
|
+
const ackMessage = await this.softphone.send(newMessage, true);
|
|
42
|
+
if (ackMessage.body.length > 0) {
|
|
43
|
+
this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
44
|
+
this.remotePort = parseInt(ackMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
45
|
+
this.remoteKey = ackMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
46
|
+
}
|
|
47
|
+
this.startLocalServices();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
module.exports = InboundCallSession;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { localKey, randomInt } from "../utils.mjs";
|
|
2
|
+
import OutboundMessage from "../sip-message/outbound/index.mjs";
|
|
3
|
+
import "../sip-message/index.mjs";
|
|
4
|
+
import CallSession from "./index.mjs";
|
|
5
|
+
//#region src/call-session/inbound.ts
|
|
6
|
+
var InboundCallSession = class extends CallSession {
|
|
7
|
+
constructor(softphone, inviteMessage) {
|
|
8
|
+
super(softphone, inviteMessage);
|
|
9
|
+
this.localPeer = inviteMessage.headers.To;
|
|
10
|
+
this.remotePeer = inviteMessage.headers.From;
|
|
11
|
+
if (inviteMessage.body.length > 0) this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
12
|
+
}
|
|
13
|
+
async answer() {
|
|
14
|
+
const answerSDP = `
|
|
15
|
+
v=0
|
|
16
|
+
o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
|
|
17
|
+
s=rc-softphone-ts
|
|
18
|
+
c=IN IP4 ${this.softphone.client.localAddress}
|
|
19
|
+
t=0 0
|
|
20
|
+
m=audio ${randomInt()} RTP/SAVP ${this.softphone.codec.id} 101
|
|
21
|
+
a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
|
|
22
|
+
a=rtpmap:101 telephone-event/8000
|
|
23
|
+
a=fmtp:101 0-15
|
|
24
|
+
a=sendrecv
|
|
25
|
+
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
|
|
26
|
+
`.trim();
|
|
27
|
+
this.sdp = answerSDP;
|
|
28
|
+
const newMessage = new OutboundMessage("SIP/2.0 200 OK", {
|
|
29
|
+
Via: this.sipMessage.headers.Via,
|
|
30
|
+
"Call-ID": this.sipMessage.getHeader("Call-ID"),
|
|
31
|
+
From: this.sipMessage.headers.From,
|
|
32
|
+
To: this.sipMessage.headers.To,
|
|
33
|
+
CSeq: this.sipMessage.headers.CSeq,
|
|
34
|
+
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
|
|
35
|
+
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
|
|
36
|
+
Supported: "replaces, 100rel, timer, norefersub",
|
|
37
|
+
"Session-Expires": "14400;refresher=uac",
|
|
38
|
+
Require: "timer",
|
|
39
|
+
"Content-Type": "application/sdp"
|
|
40
|
+
}, answerSDP);
|
|
41
|
+
const ackMessage = await this.softphone.send(newMessage, true);
|
|
42
|
+
if (ackMessage.body.length > 0) {
|
|
43
|
+
this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
44
|
+
this.remotePort = parseInt(ackMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
45
|
+
this.remoteKey = ackMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
|
|
46
|
+
}
|
|
47
|
+
this.startLocalServices();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { InboundCallSession as default };
|
|
@@ -1,274 +1,213 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
const require_chunk = require("../chunk-CKQMccvm.cjs");
|
|
2
|
+
const require_dtmf = require("../dtmf.cjs");
|
|
3
|
+
const require_utils = require("../utils.cjs");
|
|
4
|
+
const require_sip_message_outbound_request = require("../sip-message/outbound/request.cjs");
|
|
5
|
+
const require_sip_message_outbound_response = require("../sip-message/outbound/response.cjs");
|
|
6
|
+
require("../sip-message/index.cjs");
|
|
7
|
+
const require_call_session_streamer = require("./streamer.cjs");
|
|
8
|
+
let node_buffer = require("node:buffer");
|
|
9
|
+
let node_events = require("node:events");
|
|
10
|
+
node_events = require_chunk.__toESM(node_events, 1);
|
|
11
|
+
let wait_for_async = require("wait-for-async");
|
|
12
|
+
wait_for_async = require_chunk.__toESM(wait_for_async, 1);
|
|
13
|
+
let node_dgram = require("node:dgram");
|
|
14
|
+
node_dgram = require_chunk.__toESM(node_dgram, 1);
|
|
15
|
+
let werift_rtp = require("werift-rtp");
|
|
16
|
+
//#region src/call-session/index.ts
|
|
17
|
+
const isDtmfChar = (value) => require_dtmf.phoneChars.includes(value);
|
|
18
|
+
var CallSession = class extends node_events.default {
|
|
19
|
+
softphone;
|
|
20
|
+
sipMessage;
|
|
21
|
+
socket;
|
|
22
|
+
localPeer;
|
|
23
|
+
remotePeer;
|
|
24
|
+
remoteIP;
|
|
25
|
+
remotePort;
|
|
26
|
+
disposed = false;
|
|
27
|
+
srtpSession;
|
|
28
|
+
encoder;
|
|
29
|
+
decoder;
|
|
30
|
+
sdp;
|
|
31
|
+
ssrc = require_utils.randomInt();
|
|
32
|
+
sequenceNumber = require_utils.randomInt();
|
|
33
|
+
timestamp = require_utils.randomInt();
|
|
34
|
+
constructor(softphone, sipMessage) {
|
|
35
|
+
super();
|
|
36
|
+
this.softphone = softphone;
|
|
37
|
+
this.encoder = softphone.codec.createEncoder();
|
|
38
|
+
this.decoder = softphone.codec.createDecoder();
|
|
39
|
+
this.sipMessage = sipMessage;
|
|
40
|
+
if (this.sipMessage.body.length > 0) {
|
|
41
|
+
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
42
|
+
this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
set remoteKey(key) {
|
|
46
|
+
const localKeyBuffer = node_buffer.Buffer.from(require_utils.localKey, "base64");
|
|
47
|
+
const remoteKeyBuffer = node_buffer.Buffer.from(key, "base64");
|
|
48
|
+
this.srtpSession = new werift_rtp.SrtpSession({
|
|
49
|
+
profile: 1,
|
|
50
|
+
keys: {
|
|
51
|
+
localMasterKey: localKeyBuffer.subarray(0, 16),
|
|
52
|
+
localMasterSalt: localKeyBuffer.subarray(16, 30),
|
|
53
|
+
remoteMasterKey: remoteKeyBuffer.subarray(0, 16),
|
|
54
|
+
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30)
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
get callId() {
|
|
59
|
+
return this.sipMessage.getHeader("Call-ID");
|
|
60
|
+
}
|
|
61
|
+
send(data) {
|
|
62
|
+
this.socket.send(data, this.remotePort, this.remoteIP);
|
|
63
|
+
}
|
|
64
|
+
async hangup() {
|
|
65
|
+
const requestMessage = new require_sip_message_outbound_request(`BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`, {
|
|
66
|
+
"Call-ID": this.callId,
|
|
67
|
+
From: this.localPeer,
|
|
68
|
+
To: this.remotePeer,
|
|
69
|
+
Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${require_utils.branch()}`
|
|
70
|
+
});
|
|
71
|
+
await this.softphone.send(requestMessage);
|
|
72
|
+
}
|
|
73
|
+
sendDTMF(char) {
|
|
74
|
+
const payloads = require_dtmf.charToPayloads(char);
|
|
75
|
+
const timestamp = this.timestamp;
|
|
76
|
+
let first = true;
|
|
77
|
+
for (const payload of payloads) {
|
|
78
|
+
const rtpPacket = new werift_rtp.RtpPacket(new werift_rtp.RtpHeader({
|
|
79
|
+
version: 2,
|
|
80
|
+
padding: false,
|
|
81
|
+
paddingSize: 0,
|
|
82
|
+
extension: false,
|
|
83
|
+
marker: first,
|
|
84
|
+
payloadOffset: 12,
|
|
85
|
+
payloadType: 101,
|
|
86
|
+
sequenceNumber: this.sequenceNumber,
|
|
87
|
+
timestamp,
|
|
88
|
+
ssrc: this.ssrc,
|
|
89
|
+
csrcLength: 0,
|
|
90
|
+
csrc: [],
|
|
91
|
+
extensionProfile: 48862,
|
|
92
|
+
extensionLength: void 0,
|
|
93
|
+
extensions: []
|
|
94
|
+
}), payload);
|
|
95
|
+
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
96
|
+
this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
|
|
97
|
+
first = false;
|
|
98
|
+
}
|
|
99
|
+
this.timestamp += 800;
|
|
100
|
+
}
|
|
101
|
+
async sendDTMFs(s, delay = 500) {
|
|
102
|
+
for (const c of s) {
|
|
103
|
+
if (!isDtmfChar(c)) throw new Error(`invalid phone char: ${c}`);
|
|
104
|
+
this.sendDTMF(c);
|
|
105
|
+
await (0, wait_for_async.default)({ interval: delay });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
streamAudio(input) {
|
|
109
|
+
const streamer = new require_call_session_streamer(this, input);
|
|
110
|
+
streamer.start();
|
|
111
|
+
return streamer;
|
|
112
|
+
}
|
|
113
|
+
sendPacket(rtpPacket) {
|
|
114
|
+
if (this.disposed) return;
|
|
115
|
+
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
116
|
+
}
|
|
117
|
+
startLocalServices() {
|
|
118
|
+
this.socket = node_dgram.default.createSocket("udp4");
|
|
119
|
+
this.socket.on("message", (message) => {
|
|
120
|
+
const rtpPacket = werift_rtp.RtpPacket.deSerialize(this.srtpSession.decrypt(message));
|
|
121
|
+
this.emit("rtpPacket", rtpPacket);
|
|
122
|
+
if (rtpPacket.header.payloadType === 101) {
|
|
123
|
+
this.emit("dtmfPacket", rtpPacket);
|
|
124
|
+
const char = require_dtmf.payloadToChar(rtpPacket.payload);
|
|
125
|
+
if (char) this.emit("dtmf", char);
|
|
126
|
+
} else if (rtpPacket.header.payloadType === this.softphone.codec.id) {
|
|
127
|
+
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) return;
|
|
128
|
+
try {
|
|
129
|
+
rtpPacket.payload = this.decoder.decode(rtpPacket.payload);
|
|
130
|
+
this.emit("audioPacket", rtpPacket);
|
|
131
|
+
} catch {
|
|
132
|
+
console.error("Audio packet decode failed", rtpPacket);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
this.socket.bind();
|
|
137
|
+
this.send("hello");
|
|
138
|
+
const byeHandler = (inboundMessage) => {
|
|
139
|
+
if (inboundMessage.getHeader("Call-ID") !== this.callId) return;
|
|
140
|
+
if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
|
|
141
|
+
this.softphone.off("message", byeHandler);
|
|
142
|
+
this.dispose();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
this.softphone.on("message", byeHandler);
|
|
146
|
+
}
|
|
147
|
+
dispose() {
|
|
148
|
+
this.disposed = true;
|
|
149
|
+
this.emit("disposed");
|
|
150
|
+
this.removeAllListeners();
|
|
151
|
+
this.socket?.removeAllListeners();
|
|
152
|
+
this.socket?.close();
|
|
153
|
+
}
|
|
154
|
+
async transfer(transferTo) {
|
|
155
|
+
const requestMessage = new require_sip_message_outbound_request(`REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`, {
|
|
156
|
+
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${require_utils.branch()};alias`,
|
|
157
|
+
"Max-Forwards": 70,
|
|
158
|
+
From: this.localPeer,
|
|
159
|
+
To: this.remotePeer,
|
|
160
|
+
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
|
|
161
|
+
"Call-ID": this.callId,
|
|
162
|
+
Event: "refer",
|
|
163
|
+
Expires: 600,
|
|
164
|
+
Supported: "replaces, 100rel, timer, norefersub",
|
|
165
|
+
Accept: "message/sipfrag;version=2.0",
|
|
166
|
+
"Allow-Events": "presence, message-summary, refer",
|
|
167
|
+
"Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`,
|
|
168
|
+
"Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`
|
|
169
|
+
});
|
|
170
|
+
await this.softphone.send(requestMessage);
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
const notifyHandler = (inboundMessage) => {
|
|
173
|
+
if (!inboundMessage.subject.startsWith("NOTIFY ")) return;
|
|
174
|
+
const responseMessage = new require_sip_message_outbound_response(inboundMessage, 200);
|
|
175
|
+
this.softphone.send(responseMessage);
|
|
176
|
+
if (inboundMessage.body.trim() === "SIP/2.0 200 OK") {
|
|
177
|
+
this.softphone.off("message", notifyHandler);
|
|
178
|
+
resolve();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
this.softphone.on("message", notifyHandler);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async toggleReceive(toReceive) {
|
|
185
|
+
let newSDP = this.sdp;
|
|
186
|
+
if (!toReceive) newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
|
|
187
|
+
const requestMessage = new require_sip_message_outbound_request(`INVITE ${require_utils.extractAddress(this.remotePeer)} SIP/2.0`, {
|
|
188
|
+
"Call-Id": this.callId,
|
|
189
|
+
From: this.localPeer,
|
|
190
|
+
To: this.remotePeer,
|
|
191
|
+
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${require_utils.branch()};alias`,
|
|
192
|
+
"Content-Type": "application/sdp",
|
|
193
|
+
Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`
|
|
194
|
+
}, newSDP);
|
|
195
|
+
const replyMessage = await this.softphone.send(requestMessage, true);
|
|
196
|
+
const ackMessage = new require_sip_message_outbound_request(`ACK ${require_utils.extractAddress(this.remotePeer)} SIP/2.0`, {
|
|
197
|
+
"Call-Id": this.callId,
|
|
198
|
+
From: this.localPeer,
|
|
199
|
+
To: this.remotePeer,
|
|
200
|
+
Via: replyMessage.headers.Via,
|
|
201
|
+
CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK")
|
|
202
|
+
});
|
|
203
|
+
await this.softphone.send(ackMessage);
|
|
204
|
+
}
|
|
205
|
+
async hold() {
|
|
206
|
+
return this.toggleReceive(false);
|
|
207
|
+
}
|
|
208
|
+
async unhold() {
|
|
209
|
+
return this.toggleReceive(true);
|
|
210
|
+
}
|
|
10
211
|
};
|
|
11
|
-
|
|
12
|
-
|
|
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;
|
|
212
|
+
//#endregion
|
|
213
|
+
module.exports = CallSession;
|