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,71 +0,0 @@
|
|
|
1
|
-
import { RequestMessage } from "../sip-message/index.js";
|
|
2
|
-
import { extractAddress, withoutTag } from "../utils.js";
|
|
3
|
-
import CallSession from "./index.js";
|
|
4
|
-
class OutboundCallSession extends CallSession {
|
|
5
|
-
constructor(softphone, answerMessage) {
|
|
6
|
-
super(softphone, answerMessage);
|
|
7
|
-
this.localPeer = answerMessage.headers.From;
|
|
8
|
-
this.remotePeer = answerMessage.headers.To;
|
|
9
|
-
this.remoteKey = answerMessage.body.match(
|
|
10
|
-
/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
|
|
11
|
-
)[1];
|
|
12
|
-
this.init();
|
|
13
|
-
}
|
|
14
|
-
init() {
|
|
15
|
-
const answerHandler = (message) => {
|
|
16
|
-
if (message.headers.CSeq !== this.sipMessage.headers.CSeq) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (message.subject.startsWith("SIP/2.0 486")) {
|
|
20
|
-
this.softphone.off("message", answerHandler);
|
|
21
|
-
this.emit("busy");
|
|
22
|
-
this.dispose();
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (message.subject.startsWith("SIP/2.0 200")) {
|
|
26
|
-
this.softphone.off("message", answerHandler);
|
|
27
|
-
this.emit("answered");
|
|
28
|
-
const ackMessage = new RequestMessage(
|
|
29
|
-
`ACK ${extractAddress(this.remotePeer)} SIP/2.0`,
|
|
30
|
-
{
|
|
31
|
-
"Call-ID": this.callId,
|
|
32
|
-
From: this.localPeer,
|
|
33
|
-
To: this.remotePeer,
|
|
34
|
-
Via: this.sipMessage.headers.Via,
|
|
35
|
-
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " ACK")
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
this.softphone.send(ackMessage);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
this.softphone.on("message", answerHandler);
|
|
42
|
-
this.once("answered", () => this.startLocalServices());
|
|
43
|
-
}
|
|
44
|
-
async cancel() {
|
|
45
|
-
const requestMessage = new RequestMessage(
|
|
46
|
-
`CANCEL ${extractAddress(this.remotePeer)} SIP/2.0`,
|
|
47
|
-
{
|
|
48
|
-
"Call-ID": this.callId,
|
|
49
|
-
From: this.localPeer,
|
|
50
|
-
To: withoutTag(this.remotePeer),
|
|
51
|
-
Via: this.sipMessage.headers.Via,
|
|
52
|
-
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL")
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
|
-
await this.softphone.send(requestMessage);
|
|
56
|
-
}
|
|
57
|
-
get sessionId() {
|
|
58
|
-
const header = this.sipMessage.headers["p-rc-api-ids"];
|
|
59
|
-
const match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
|
|
60
|
-
return match[2];
|
|
61
|
-
}
|
|
62
|
-
get partyId() {
|
|
63
|
-
const header = this.sipMessage.headers["p-rc-api-ids"];
|
|
64
|
-
const match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
|
|
65
|
-
return match[1];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
var outbound_default = OutboundCallSession;
|
|
69
|
-
export {
|
|
70
|
-
outbound_default as default
|
|
71
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import EventEmitter from "node:events";
|
|
3
|
-
import type CallSession from "./index.js";
|
|
4
|
-
declare class Streamer extends EventEmitter {
|
|
5
|
-
paused: boolean;
|
|
6
|
-
private callSession;
|
|
7
|
-
private buffer;
|
|
8
|
-
private originalBuffer;
|
|
9
|
-
constructor(callSession: CallSession, buffer: Buffer);
|
|
10
|
-
start(): void;
|
|
11
|
-
stop(): void;
|
|
12
|
-
pause(): void;
|
|
13
|
-
resume(): void;
|
|
14
|
-
get finished(): boolean;
|
|
15
|
-
private sendPacket;
|
|
16
|
-
}
|
|
17
|
-
export default Streamer;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import EventEmitter from "node:events";
|
|
3
|
-
import { RtpHeader, RtpPacket } from "werift-rtp";
|
|
4
|
-
class Streamer extends EventEmitter {
|
|
5
|
-
paused = false;
|
|
6
|
-
callSession;
|
|
7
|
-
buffer;
|
|
8
|
-
originalBuffer;
|
|
9
|
-
constructor(callSession, buffer) {
|
|
10
|
-
super();
|
|
11
|
-
this.callSession = callSession;
|
|
12
|
-
this.buffer = buffer;
|
|
13
|
-
this.originalBuffer = buffer;
|
|
14
|
-
}
|
|
15
|
-
start() {
|
|
16
|
-
this.buffer = this.originalBuffer;
|
|
17
|
-
this.paused = false;
|
|
18
|
-
this.sendPacket();
|
|
19
|
-
}
|
|
20
|
-
stop() {
|
|
21
|
-
this.buffer = Buffer.alloc(0);
|
|
22
|
-
}
|
|
23
|
-
pause() {
|
|
24
|
-
this.paused = true;
|
|
25
|
-
}
|
|
26
|
-
resume() {
|
|
27
|
-
this.paused = false;
|
|
28
|
-
this.sendPacket();
|
|
29
|
-
}
|
|
30
|
-
get finished() {
|
|
31
|
-
return this.callSession.disposed || this.buffer.length < this.callSession.softphone.codec.packetSize;
|
|
32
|
-
}
|
|
33
|
-
sendPacket() {
|
|
34
|
-
if (!this.paused && !this.finished) {
|
|
35
|
-
const temp = this.callSession.encoder.encode(
|
|
36
|
-
this.buffer.subarray(0, this.callSession.softphone.codec.packetSize)
|
|
37
|
-
);
|
|
38
|
-
const rtpPacket = new RtpPacket(
|
|
39
|
-
new RtpHeader({
|
|
40
|
-
version: 2,
|
|
41
|
-
padding: false,
|
|
42
|
-
paddingSize: 0,
|
|
43
|
-
extension: false,
|
|
44
|
-
marker: false,
|
|
45
|
-
payloadOffset: 12,
|
|
46
|
-
payloadType: this.callSession.softphone.codec.id,
|
|
47
|
-
sequenceNumber: this.callSession.sequenceNumber,
|
|
48
|
-
timestamp: this.callSession.timestamp,
|
|
49
|
-
ssrc: this.callSession.ssrc,
|
|
50
|
-
csrcLength: 0,
|
|
51
|
-
csrc: [],
|
|
52
|
-
extensionProfile: 48862,
|
|
53
|
-
extensionLength: void 0,
|
|
54
|
-
extensions: []
|
|
55
|
-
}),
|
|
56
|
-
temp
|
|
57
|
-
);
|
|
58
|
-
this.callSession.send(
|
|
59
|
-
this.callSession.srtpSession.encrypt(
|
|
60
|
-
rtpPacket.payload,
|
|
61
|
-
rtpPacket.header
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
|
-
this.callSession.sequenceNumber += 1;
|
|
65
|
-
if (this.callSession.sequenceNumber > 65535) {
|
|
66
|
-
this.callSession.sequenceNumber = 0;
|
|
67
|
-
}
|
|
68
|
-
this.callSession.timestamp += this.callSession.softphone.codec.timestampInterval;
|
|
69
|
-
this.buffer = this.buffer.subarray(
|
|
70
|
-
this.callSession.softphone.codec.packetSize
|
|
71
|
-
);
|
|
72
|
-
if (this.finished) {
|
|
73
|
-
this.emit("finished");
|
|
74
|
-
} else {
|
|
75
|
-
setTimeout(() => this.sendPacket(), 20);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
var streamer_default = Streamer;
|
|
81
|
-
export {
|
|
82
|
-
streamer_default as default
|
|
83
|
-
};
|
package/dist/codec.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
declare class Codec {
|
|
3
|
-
id: number;
|
|
4
|
-
name: "OPUS/16000" | "OPUS/48000/2" | "PCMU/8000";
|
|
5
|
-
packetSize: number;
|
|
6
|
-
timestampInterval: number;
|
|
7
|
-
createEncoder: () => {
|
|
8
|
-
encode: (pcm: Buffer) => Buffer;
|
|
9
|
-
};
|
|
10
|
-
createDecoder: () => {
|
|
11
|
-
decode: (audio: Buffer) => Buffer;
|
|
12
|
-
};
|
|
13
|
-
constructor(name: "OPUS/16000" | "OPUS/48000/2" | "PCMU/8000");
|
|
14
|
-
}
|
|
15
|
-
export default Codec;
|
package/dist/codec.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import { Decoder, Encoder } from "@evan/opus";
|
|
3
|
-
class Codec {
|
|
4
|
-
id;
|
|
5
|
-
name;
|
|
6
|
-
packetSize;
|
|
7
|
-
timestampInterval;
|
|
8
|
-
createEncoder;
|
|
9
|
-
createDecoder;
|
|
10
|
-
constructor(name) {
|
|
11
|
-
this.name = name;
|
|
12
|
-
switch (name) {
|
|
13
|
-
case "OPUS/16000": {
|
|
14
|
-
this.createEncoder = () => {
|
|
15
|
-
const encoder = new Encoder({ channels: 1, sample_rate: 16e3 });
|
|
16
|
-
return { encode: (pcm) => Buffer.from(encoder.encode(pcm)) };
|
|
17
|
-
};
|
|
18
|
-
this.createDecoder = () => {
|
|
19
|
-
const decoder = new Decoder({ channels: 1, sample_rate: 16e3 });
|
|
20
|
-
return {
|
|
21
|
-
decode: (opus) => Buffer.from(decoder.decode(opus))
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
this.id = 109;
|
|
25
|
-
this.packetSize = 640;
|
|
26
|
-
this.timestampInterval = 320;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
case "OPUS/48000/2": {
|
|
30
|
-
this.createEncoder = () => {
|
|
31
|
-
const encoder = new Encoder({ channels: 2, sample_rate: 48e3 });
|
|
32
|
-
return { encode: (pcm) => Buffer.from(encoder.encode(pcm)) };
|
|
33
|
-
};
|
|
34
|
-
this.createDecoder = () => {
|
|
35
|
-
const decoder = new Decoder({ channels: 2, sample_rate: 48e3 });
|
|
36
|
-
return {
|
|
37
|
-
decode: (opus) => Buffer.from(decoder.decode(opus))
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
this.id = 111;
|
|
41
|
-
this.packetSize = 3840;
|
|
42
|
-
this.timestampInterval = 960;
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
case "PCMU/8000": {
|
|
46
|
-
this.createEncoder = () => {
|
|
47
|
-
return { encode: (pcm) => pcm };
|
|
48
|
-
};
|
|
49
|
-
this.createDecoder = () => {
|
|
50
|
-
return { decode: (audio) => audio };
|
|
51
|
-
};
|
|
52
|
-
this.id = 0;
|
|
53
|
-
this.packetSize = 160;
|
|
54
|
-
this.timestampInterval = 160;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
default: {
|
|
58
|
-
throw new Error(`unsupported codec: ${name}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
var codec_default = Codec;
|
|
64
|
-
export {
|
|
65
|
-
codec_default as default
|
|
66
|
-
};
|
package/dist/dtmf.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
declare const DTMF: {
|
|
3
|
-
phoneChars: readonly ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"];
|
|
4
|
-
charToPayloads(char: string): Buffer<ArrayBuffer>[];
|
|
5
|
-
payloadToChar(payload: Buffer): "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#";
|
|
6
|
-
};
|
|
7
|
-
export default DTMF;
|
package/dist/dtmf.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
const phoneChars = [
|
|
3
|
-
"0",
|
|
4
|
-
"1",
|
|
5
|
-
"2",
|
|
6
|
-
"3",
|
|
7
|
-
"4",
|
|
8
|
-
"5",
|
|
9
|
-
"6",
|
|
10
|
-
"7",
|
|
11
|
-
"8",
|
|
12
|
-
"9",
|
|
13
|
-
"*",
|
|
14
|
-
"#"
|
|
15
|
-
];
|
|
16
|
-
const payloads = [
|
|
17
|
-
393216,
|
|
18
|
-
393376,
|
|
19
|
-
393536,
|
|
20
|
-
8782624,
|
|
21
|
-
8782624,
|
|
22
|
-
8782624
|
|
23
|
-
];
|
|
24
|
-
const DTMF = {
|
|
25
|
-
phoneChars,
|
|
26
|
-
charToPayloads(char) {
|
|
27
|
-
const index = phoneChars.indexOf(char[0]);
|
|
28
|
-
if (index === -1) {
|
|
29
|
-
throw new Error("invalid phone char");
|
|
30
|
-
}
|
|
31
|
-
return payloads.map((payload) => {
|
|
32
|
-
const temp = payload + index * 16777216;
|
|
33
|
-
const buffer = Buffer.alloc(4);
|
|
34
|
-
buffer.writeIntBE(temp, 0, 4);
|
|
35
|
-
return buffer;
|
|
36
|
-
});
|
|
37
|
-
},
|
|
38
|
-
payloadToChar(payload) {
|
|
39
|
-
const intBE = payload.readIntBE(0, 4);
|
|
40
|
-
const index = (intBE - 393216) / 16777216;
|
|
41
|
-
return phoneChars[index];
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
var dtmf_default = DTMF;
|
|
45
|
-
export {
|
|
46
|
-
dtmf_default as default
|
|
47
|
-
};
|
package/dist/index.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import { type TLSSocket } from "node:tls";
|
|
3
|
-
import InboundCallSession from "./call-session/inbound.js";
|
|
4
|
-
import OutboundCallSession from "./call-session/outbound.js";
|
|
5
|
-
import Codec from "./codec.js";
|
|
6
|
-
import { InboundMessage, OutboundMessage } from "./sip-message/index.js";
|
|
7
|
-
import type { SoftPhoneOptions } from "./types.js";
|
|
8
|
-
declare class Softphone extends EventEmitter {
|
|
9
|
-
sipInfo: SoftPhoneOptions;
|
|
10
|
-
client: TLSSocket;
|
|
11
|
-
codec: Codec;
|
|
12
|
-
fakeDomain: string;
|
|
13
|
-
fakeEmail: string;
|
|
14
|
-
private intervalHandle?;
|
|
15
|
-
private connected;
|
|
16
|
-
constructor(sipInfo: SoftPhoneOptions);
|
|
17
|
-
private instanceId;
|
|
18
|
-
private registerCallId;
|
|
19
|
-
register(): Promise<void>;
|
|
20
|
-
enableDebugMode(): void;
|
|
21
|
-
revoke(): void;
|
|
22
|
-
send(message: OutboundMessage, waitForReply?: true): Promise<InboundMessage>;
|
|
23
|
-
send(message: OutboundMessage, waitForReply?: false): Promise<undefined>;
|
|
24
|
-
answer(inviteMessage: InboundMessage): Promise<InboundCallSession>;
|
|
25
|
-
decline(inviteMessage: InboundMessage): Promise<void>;
|
|
26
|
-
call(callee: string): Promise<OutboundCallSession>;
|
|
27
|
-
}
|
|
28
|
-
export default Softphone;
|
package/dist/index.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import tls from "node:tls";
|
|
3
|
-
import waitFor from "wait-for-async";
|
|
4
|
-
import InboundCallSession from "./call-session/inbound.js";
|
|
5
|
-
import OutboundCallSession from "./call-session/outbound.js";
|
|
6
|
-
import Codec from "./codec.js";
|
|
7
|
-
import {
|
|
8
|
-
InboundMessage,
|
|
9
|
-
OutboundMessage,
|
|
10
|
-
RequestMessage,
|
|
11
|
-
ResponseMessage
|
|
12
|
-
} from "./sip-message/index.js";
|
|
13
|
-
import {
|
|
14
|
-
branch,
|
|
15
|
-
generateAuthorization,
|
|
16
|
-
localKey,
|
|
17
|
-
randomInt,
|
|
18
|
-
uuid
|
|
19
|
-
} from "./utils.js";
|
|
20
|
-
class Softphone extends EventEmitter {
|
|
21
|
-
sipInfo;
|
|
22
|
-
client;
|
|
23
|
-
codec;
|
|
24
|
-
fakeDomain = `${uuid()}.invalid`;
|
|
25
|
-
fakeEmail = `${uuid()}@${this.fakeDomain}`;
|
|
26
|
-
intervalHandle;
|
|
27
|
-
connected = false;
|
|
28
|
-
constructor(sipInfo) {
|
|
29
|
-
super();
|
|
30
|
-
if (sipInfo.codec === void 0) {
|
|
31
|
-
sipInfo.codec = "OPUS/16000";
|
|
32
|
-
}
|
|
33
|
-
this.codec = new Codec(sipInfo.codec);
|
|
34
|
-
this.sipInfo = sipInfo;
|
|
35
|
-
if (this.sipInfo.domain === void 0) {
|
|
36
|
-
this.sipInfo.domain = "sip.ringcentral.com";
|
|
37
|
-
}
|
|
38
|
-
if (this.sipInfo.outboundProxy === void 0) {
|
|
39
|
-
this.sipInfo.outboundProxy = "sip10.ringcentral.com:5096";
|
|
40
|
-
}
|
|
41
|
-
const tokens = this.sipInfo.outboundProxy.split(":");
|
|
42
|
-
this.client = tls.connect(
|
|
43
|
-
{
|
|
44
|
-
host: tokens[0],
|
|
45
|
-
port: parseInt(tokens[1], 10),
|
|
46
|
-
rejectUnauthorized: !this.sipInfo.ignoreTlsCertErrors
|
|
47
|
-
},
|
|
48
|
-
() => {
|
|
49
|
-
this.connected = true;
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
|
-
let cache = "";
|
|
53
|
-
this.client.on("data", (data) => {
|
|
54
|
-
cache += data.toString("utf-8");
|
|
55
|
-
if (!cache.endsWith("\r\n")) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const tempMessages = cache.split("\r\nContent-Length: 0\r\n\r\n").filter((message) => message.trim() !== "");
|
|
59
|
-
cache = "";
|
|
60
|
-
for (let i = 0; i < tempMessages.length; i++) {
|
|
61
|
-
if (!tempMessages[i].includes("Content-Length: ")) {
|
|
62
|
-
tempMessages[i] = `${tempMessages[i]}\r
|
|
63
|
-
Content-Length: 0`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for (const message of tempMessages) {
|
|
67
|
-
this.emit("message", InboundMessage.fromString(message));
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
instanceId = uuid();
|
|
72
|
-
registerCallId = uuid();
|
|
73
|
-
async register() {
|
|
74
|
-
if (!this.connected) {
|
|
75
|
-
await waitFor({
|
|
76
|
-
interval: 100,
|
|
77
|
-
times: 100,
|
|
78
|
-
condition: () => this.connected
|
|
79
|
-
});
|
|
80
|
-
if (!this.connected) {
|
|
81
|
-
throw new Error("Failed to register: connect to TLS timeout");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const sipRegister = async () => {
|
|
85
|
-
const fromTag = uuid();
|
|
86
|
-
const requestMessage = new RequestMessage(
|
|
87
|
-
`REGISTER sip:${this.sipInfo.domain} SIP/2.0`,
|
|
88
|
-
{
|
|
89
|
-
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${branch()};alias`,
|
|
90
|
-
"Max-Forwards": "70",
|
|
91
|
-
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${fromTag}`,
|
|
92
|
-
To: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>`,
|
|
93
|
-
"Call-ID": this.registerCallId,
|
|
94
|
-
Contact: `<sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>;reg-id=1;+sip.instance="<urn:uuid:${this.instanceId}>"`,
|
|
95
|
-
Expires: 3600,
|
|
96
|
-
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS"
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
const inboundMessage = await this.send(requestMessage, true);
|
|
100
|
-
if (inboundMessage.subject.startsWith("SIP/2.0 200 ")) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const wwwAuth = inboundMessage.getHeader("Www-Authenticate");
|
|
104
|
-
const nonce = wwwAuth.match(/, nonce="(.+?)"/)[1];
|
|
105
|
-
const newMessage = requestMessage.fork();
|
|
106
|
-
newMessage.headers.Authorization = generateAuthorization(
|
|
107
|
-
this.sipInfo,
|
|
108
|
-
nonce,
|
|
109
|
-
"REGISTER"
|
|
110
|
-
);
|
|
111
|
-
const message = await this.send(newMessage, true);
|
|
112
|
-
if (!message.subject.startsWith("SIP/2.0 200 ")) {
|
|
113
|
-
throw new Error(`Failed to register: ${message.subject}`);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
await sipRegister();
|
|
117
|
-
this.intervalHandle = setInterval(
|
|
118
|
-
() => {
|
|
119
|
-
sipRegister().catch((error) => {
|
|
120
|
-
this.emit("registrationError", error);
|
|
121
|
-
});
|
|
122
|
-
},
|
|
123
|
-
30 * 1e3
|
|
124
|
-
// refresh registration every 30 seconds
|
|
125
|
-
);
|
|
126
|
-
this.on("message", (inboundMessage) => {
|
|
127
|
-
if (!inboundMessage.subject.startsWith("INVITE sip:")) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const outboundMessage = new OutboundMessage("SIP/2.0 100 Trying", {
|
|
131
|
-
Via: inboundMessage.headers.Via,
|
|
132
|
-
"Call-ID": inboundMessage.getHeader("Call-ID"),
|
|
133
|
-
From: inboundMessage.headers.From,
|
|
134
|
-
To: inboundMessage.headers.To,
|
|
135
|
-
CSeq: inboundMessage.headers.CSeq,
|
|
136
|
-
"Content-Length": "0"
|
|
137
|
-
});
|
|
138
|
-
this.send(outboundMessage);
|
|
139
|
-
this.emit("invite", inboundMessage);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
enableDebugMode() {
|
|
143
|
-
this.on(
|
|
144
|
-
"message",
|
|
145
|
-
(message) => console.log(`Receiving...(${/* @__PURE__ */ new Date()})
|
|
146
|
-
${message.toString()}`)
|
|
147
|
-
);
|
|
148
|
-
const tlsWrite = this.client.write.bind(this.client);
|
|
149
|
-
this.client.write = (message) => {
|
|
150
|
-
console.log(`Sending...(${/* @__PURE__ */ new Date()})
|
|
151
|
-
${message}`);
|
|
152
|
-
return tlsWrite(message);
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
revoke() {
|
|
156
|
-
clearInterval(this.intervalHandle);
|
|
157
|
-
this.removeAllListeners();
|
|
158
|
-
this.client.removeAllListeners();
|
|
159
|
-
this.client.destroy();
|
|
160
|
-
}
|
|
161
|
-
send(message, waitForReply = false) {
|
|
162
|
-
this.client.write(message.toString());
|
|
163
|
-
if (!waitForReply) {
|
|
164
|
-
return new Promise((resolve) => {
|
|
165
|
-
resolve(void 0);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return new Promise((resolve) => {
|
|
169
|
-
const messageListerner = (inboundMessage) => {
|
|
170
|
-
if (inboundMessage.headers.CSeq.trim().split(/\s+/)[0] !== message.headers.CSeq.trim().split(/\s+/)[0]) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (inboundMessage.subject.startsWith("SIP/2.0 100 ")) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
this.off("message", messageListerner);
|
|
177
|
-
resolve(inboundMessage);
|
|
178
|
-
};
|
|
179
|
-
this.on("message", messageListerner);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
async answer(inviteMessage) {
|
|
183
|
-
const inboundCallSession = new InboundCallSession(this, inviteMessage);
|
|
184
|
-
await inboundCallSession.answer();
|
|
185
|
-
return inboundCallSession;
|
|
186
|
-
}
|
|
187
|
-
// decline an inbound call
|
|
188
|
-
async decline(inviteMessage) {
|
|
189
|
-
const newMessage = new ResponseMessage(inviteMessage, 603);
|
|
190
|
-
await this.send(newMessage);
|
|
191
|
-
}
|
|
192
|
-
async call(callee) {
|
|
193
|
-
const offerSDP = `
|
|
194
|
-
v=0
|
|
195
|
-
o=- ${Date.now()} 0 IN IP4 ${this.client.localAddress}
|
|
196
|
-
s=rc-softphone-ts
|
|
197
|
-
c=IN IP4 ${this.client.localAddress}
|
|
198
|
-
t=0 0
|
|
199
|
-
m=audio ${randomInt()} RTP/SAVP ${this.codec.id} 101
|
|
200
|
-
a=rtpmap:${this.codec.id} ${this.codec.name}
|
|
201
|
-
a=rtpmap:101 telephone-event/8000
|
|
202
|
-
a=fmtp:101 0-15
|
|
203
|
-
a=sendrecv
|
|
204
|
-
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
|
|
205
|
-
`.trim();
|
|
206
|
-
const inviteMessage = new RequestMessage(
|
|
207
|
-
`INVITE sip:${callee}@${this.sipInfo.domain} SIP/2.0`,
|
|
208
|
-
{
|
|
209
|
-
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${branch()};alias`,
|
|
210
|
-
"Max-Forwards": 70,
|
|
211
|
-
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${uuid()}`,
|
|
212
|
-
To: `<sip:${callee}@sip.ringcentral.com>`,
|
|
213
|
-
Contact: ` <sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>`,
|
|
214
|
-
"Call-ID": uuid(),
|
|
215
|
-
Route: `<sip:${this.sipInfo.outboundProxy};transport=tls;lr>`,
|
|
216
|
-
Allow: `PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS`,
|
|
217
|
-
Supported: `replaces, 100rel, timer, norefersub`,
|
|
218
|
-
"Session-Expires": 1800,
|
|
219
|
-
"Min-SE": 90,
|
|
220
|
-
"Content-Type": "application/sdp"
|
|
221
|
-
},
|
|
222
|
-
offerSDP
|
|
223
|
-
);
|
|
224
|
-
const inboundMessage = await this.send(inviteMessage, true);
|
|
225
|
-
const proxyAuthenticate = inboundMessage.getHeader("Proxy-Authenticate");
|
|
226
|
-
const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1];
|
|
227
|
-
const newMessage = inviteMessage.fork();
|
|
228
|
-
newMessage.headers["Proxy-Authorization"] = generateAuthorization(
|
|
229
|
-
this.sipInfo,
|
|
230
|
-
nonce,
|
|
231
|
-
"INVITE"
|
|
232
|
-
);
|
|
233
|
-
const progressMessage = await this.send(newMessage, true);
|
|
234
|
-
const outboundCallSession = new OutboundCallSession(this, progressMessage);
|
|
235
|
-
outboundCallSession.sdp = offerSDP;
|
|
236
|
-
return outboundCallSession;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
var index_default = Softphone;
|
|
240
|
-
export {
|
|
241
|
-
index_default as default
|
|
242
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { uuid } from "../../utils.js";
|
|
2
|
-
import SipMessage from "../sip-message.js";
|
|
3
|
-
class InboundMessage extends SipMessage {
|
|
4
|
-
static fromString(str) {
|
|
5
|
-
const sipMessage = new SipMessage();
|
|
6
|
-
const [init, ...body] = str.split("\r\n\r\n");
|
|
7
|
-
sipMessage.body = body.join("\r\n\r\n");
|
|
8
|
-
const [subject, ...headers] = init.split("\r\n");
|
|
9
|
-
sipMessage.subject = subject;
|
|
10
|
-
sipMessage.headers = Object.fromEntries(
|
|
11
|
-
headers.map((line) => line.split(": "))
|
|
12
|
-
);
|
|
13
|
-
if (sipMessage.headers.To && !sipMessage.headers.To.includes(";tag=")) {
|
|
14
|
-
sipMessage.headers.To += `;tag=${uuid()}`;
|
|
15
|
-
}
|
|
16
|
-
return sipMessage;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
var inbound_default = InboundMessage;
|
|
20
|
-
export {
|
|
21
|
-
inbound_default as default
|
|
22
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as InboundMessage } from "./inbound/index.js";
|
|
2
|
-
export { default as OutboundMessage } from "./outbound/index.js";
|
|
3
|
-
export { default as RequestMessage } from "./outbound/request.js";
|
|
4
|
-
export { default as ResponseMessage } from "./outbound/response.js";
|
|
5
|
-
export { default as SipMessage } from "./sip-message.js";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { default as default2 } from "./inbound/index.js";
|
|
2
|
-
import { default as default3 } from "./outbound/index.js";
|
|
3
|
-
import { default as default4 } from "./outbound/request.js";
|
|
4
|
-
import { default as default5 } from "./outbound/response.js";
|
|
5
|
-
import { default as default6 } from "./sip-message.js";
|
|
6
|
-
export {
|
|
7
|
-
default2 as InboundMessage,
|
|
8
|
-
default3 as OutboundMessage,
|
|
9
|
-
default4 as RequestMessage,
|
|
10
|
-
default5 as ResponseMessage,
|
|
11
|
-
default6 as SipMessage
|
|
12
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import SipMessage from "../sip-message.js";
|
|
2
|
-
class OutboundMessage extends SipMessage {
|
|
3
|
-
constructor(subject = "", headers = {}, body = "") {
|
|
4
|
-
super(subject, headers, body);
|
|
5
|
-
this.headers["Content-Length"] = this.body.length.toString();
|
|
6
|
-
this.headers["User-Agent"] = "ringcentral-softphone-ts";
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
var outbound_default = OutboundMessage;
|
|
10
|
-
export {
|
|
11
|
-
outbound_default as default
|
|
12
|
-
};
|