ringcentral-softphone
Advanced tools
Comparing version 1.1.2 to 1.1.3
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -57,45 +6,43 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var index_1 = __importDefault(require("./index")); | ||
var index_2 = require("../sip-message/index"); | ||
var utils_1 = require("../utils"); | ||
var InboundCallSession = /** @class */ (function (_super) { | ||
__extends(InboundCallSession, _super); | ||
function InboundCallSession(softphone, inviteMessage) { | ||
var _this = _super.call(this, softphone, inviteMessage) || this; | ||
_this.localPeer = inviteMessage.headers.To; | ||
_this.remotePeer = inviteMessage.headers.From; | ||
_this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
return _this; | ||
const index_1 = __importDefault(require("./index")); | ||
const index_2 = require("../sip-message/index"); | ||
const utils_1 = require("../utils"); | ||
class InboundCallSession extends index_1.default { | ||
constructor(softphone, inviteMessage) { | ||
super(softphone, inviteMessage); | ||
this.localPeer = inviteMessage.headers.To; | ||
this.remotePeer = inviteMessage.headers.From; | ||
this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
} | ||
InboundCallSession.prototype.answer = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var answerSDP, newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
answerSDP = "\nv=0\no=- ".concat(Date.now(), " 0 IN IP4 ").concat(this.softphone.client.localAddress, "\ns=rc-softphone-ts\nc=IN IP4 ").concat(this.softphone.client.localAddress, "\nt=0 0\nm=audio ").concat((0, utils_1.randomInt)(), " RTP/SAVP ").concat(this.softphone.codec.id, " 101\na=rtpmap:").concat(this.softphone.codec.id, " ").concat(this.softphone.codec.name, "\na=rtpmap:101 telephone-event/8000\na=fmtp:101 0-15\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:").concat(utils_1.localKey, "\n").trim(); | ||
newMessage = new index_2.OutboundMessage("SIP/2.0 200 OK", { | ||
Via: this.sipMessage.headers.Via, | ||
"Call-ID": this.sipMessage.headers["Call-ID"], | ||
From: this.sipMessage.headers.From, | ||
To: this.sipMessage.headers.To, | ||
CSeq: this.sipMessage.headers.CSeq, | ||
Contact: "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";transport=TLS;ob>"), | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": "14400;refresher=uac", | ||
Require: "timer", | ||
"Content-Type": "application/sdp", | ||
}, answerSDP); | ||
return [4 /*yield*/, this.softphone.send(newMessage)]; | ||
case 1: | ||
_a.sent(); | ||
this.startLocalServices(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return InboundCallSession; | ||
}(index_1.default)); | ||
async answer() { | ||
const answerSDP = ` | ||
v=0 | ||
o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress} | ||
s=rc-softphone-ts | ||
c=IN IP4 ${this.softphone.client.localAddress} | ||
t=0 0 | ||
m=audio ${(0, utils_1.randomInt)()} RTP/SAVP ${this.softphone.codec.id} 101 | ||
a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name} | ||
a=rtpmap:101 telephone-event/8000 | ||
a=fmtp:101 0-15 | ||
a=sendrecv | ||
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_1.localKey} | ||
`.trim(); | ||
const newMessage = new index_2.OutboundMessage("SIP/2.0 200 OK", { | ||
Via: this.sipMessage.headers.Via, | ||
"Call-ID": this.sipMessage.headers["Call-ID"], | ||
From: this.sipMessage.headers.From, | ||
To: this.sipMessage.headers.To, | ||
CSeq: this.sipMessage.headers.CSeq, | ||
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": "14400;refresher=uac", | ||
Require: "timer", | ||
"Content-Type": "application/sdp", | ||
}, answerSDP); | ||
await this.softphone.send(newMessage); | ||
this.startLocalServices(); | ||
} | ||
} | ||
exports.default = InboundCallSession; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -57,78 +6,67 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var node_dgram_1 = __importDefault(require("node:dgram")); | ||
var node_events_1 = __importDefault(require("node:events")); | ||
var node_buffer_1 = require("node:buffer"); | ||
var werift_rtp_1 = require("werift-rtp"); | ||
var dtmf_1 = __importDefault(require("../dtmf")); | ||
var index_1 = require("../sip-message/index"); | ||
var utils_1 = require("../utils"); | ||
var streamer_1 = __importDefault(require("./streamer")); | ||
var CallSession = /** @class */ (function (_super) { | ||
__extends(CallSession, _super); | ||
function CallSession(softphone, sipMessage) { | ||
var _this = _super.call(this) || this; | ||
_this.disposed = false; | ||
// for audio streaming | ||
_this.ssrc = (0, utils_1.randomInt)(); | ||
_this.sequenceNumber = (0, utils_1.randomInt)(); | ||
_this.timestamp = (0, utils_1.randomInt)(); | ||
_this.softphone = softphone; | ||
_this.encoder = softphone.codec.createEncoder(); | ||
_this.decoder = softphone.codec.createDecoder(); | ||
_this.sipMessage = sipMessage; | ||
_this.remoteIP = _this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1]; | ||
_this.remotePort = parseInt(_this.sipMessage.body.match(/m=audio (\d+) /)[1], 10); | ||
return _this; | ||
const node_dgram_1 = __importDefault(require("node:dgram")); | ||
const node_events_1 = __importDefault(require("node:events")); | ||
const node_buffer_1 = require("node:buffer"); | ||
const werift_rtp_1 = require("werift-rtp"); | ||
const dtmf_1 = __importDefault(require("../dtmf")); | ||
const index_1 = require("../sip-message/index"); | ||
const utils_1 = require("../utils"); | ||
const streamer_1 = __importDefault(require("./streamer")); | ||
class CallSession extends node_events_1.default { | ||
softphone; | ||
sipMessage; | ||
socket; | ||
localPeer; | ||
remotePeer; | ||
remoteIP; | ||
remotePort; | ||
disposed = false; | ||
srtpSession; | ||
encoder; | ||
decoder; | ||
// for audio streaming | ||
ssrc = (0, utils_1.randomInt)(); | ||
sequenceNumber = (0, utils_1.randomInt)(); | ||
timestamp = (0, utils_1.randomInt)(); | ||
constructor(softphone, sipMessage) { | ||
super(); | ||
this.softphone = softphone; | ||
this.encoder = softphone.codec.createEncoder(); | ||
this.decoder = softphone.codec.createDecoder(); | ||
this.sipMessage = sipMessage; | ||
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1]; | ||
this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10); | ||
} | ||
Object.defineProperty(CallSession.prototype, "remoteKey", { | ||
set: function (key) { | ||
var localKeyBuffer = node_buffer_1.Buffer.from(utils_1.localKey, "base64"); | ||
var remoteKeyBuffer = node_buffer_1.Buffer.from(key, "base64"); | ||
this.srtpSession = new werift_rtp_1.SrtpSession({ | ||
profile: 0x0001, | ||
keys: { | ||
localMasterKey: localKeyBuffer.subarray(0, 16), | ||
localMasterSalt: localKeyBuffer.subarray(16, 30), | ||
remoteMasterKey: remoteKeyBuffer.subarray(0, 16), | ||
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30), | ||
}, | ||
}); | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Object.defineProperty(CallSession.prototype, "callId", { | ||
get: function () { | ||
return this.sipMessage.headers["Call-ID"]; | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
CallSession.prototype.send = function (data) { | ||
set remoteKey(key) { | ||
const localKeyBuffer = node_buffer_1.Buffer.from(utils_1.localKey, "base64"); | ||
const remoteKeyBuffer = node_buffer_1.Buffer.from(key, "base64"); | ||
this.srtpSession = new werift_rtp_1.SrtpSession({ | ||
profile: 0x0001, | ||
keys: { | ||
localMasterKey: localKeyBuffer.subarray(0, 16), | ||
localMasterSalt: localKeyBuffer.subarray(16, 30), | ||
remoteMasterKey: remoteKeyBuffer.subarray(0, 16), | ||
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30), | ||
}, | ||
}); | ||
} | ||
get callId() { | ||
return this.sipMessage.headers["Call-ID"]; | ||
} | ||
send(data) { | ||
this.socket.send(data, this.remotePort, this.remoteIP); | ||
}; | ||
CallSession.prototype.hangup = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var requestMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
requestMessage = new index_1.RequestMessage("BYE sip:".concat(this.softphone.sipInfo.domain, " SIP/2.0"), { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: "SIP/2.0/TLS ".concat(this.softphone.fakeDomain, ";branch=").concat((0, utils_1.branch)()), | ||
}); | ||
return [4 /*yield*/, this.softphone.send(requestMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
} | ||
async hangup() { | ||
const requestMessage = new index_1.RequestMessage(`BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${(0, utils_1.branch)()}`, | ||
}); | ||
}; | ||
CallSession.prototype.sendDTMF = function (char) { | ||
var timestamp = Math.floor(Date.now() / 1000); | ||
var sequenceNumber = timestamp % 65536; | ||
var rtpHeader = new werift_rtp_1.RtpHeader({ | ||
await this.softphone.send(requestMessage); | ||
} | ||
sendDTMF(char) { | ||
const timestamp = Math.floor(Date.now() / 1000); | ||
let sequenceNumber = timestamp % 65536; | ||
const rtpHeader = new werift_rtp_1.RtpHeader({ | ||
version: 2, | ||
@@ -141,4 +79,4 @@ padding: false, | ||
payloadType: 101, | ||
sequenceNumber: sequenceNumber, | ||
timestamp: timestamp, | ||
sequenceNumber, | ||
timestamp, | ||
ssrc: (0, utils_1.randomInt)(), | ||
@@ -151,18 +89,17 @@ csrcLength: 0, | ||
}); | ||
for (var _i = 0, _a = dtmf_1.default.charToPayloads(char); _i < _a.length; _i++) { | ||
var payload = _a[_i]; | ||
for (const payload of dtmf_1.default.charToPayloads(char)) { | ||
rtpHeader.sequenceNumber = sequenceNumber++; | ||
var rtpPacket = new werift_rtp_1.RtpPacket(rtpHeader, payload); | ||
const rtpPacket = new werift_rtp_1.RtpPacket(rtpHeader, payload); | ||
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header)); | ||
} | ||
}; | ||
} | ||
// buffer is the content of a audio file, it is supposed to be uncompressed PCM data | ||
// The audio should be playable by command: play -t raw -b 16 -r 16000 -e signed-integer test.wav | ||
CallSession.prototype.streamAudio = function (input) { | ||
var streamer = new streamer_1.default(this, input); | ||
streamAudio(input) { | ||
const streamer = new streamer_1.default(this, input); | ||
streamer.start(); | ||
return streamer; | ||
}; | ||
} | ||
// send a single rtp packet | ||
CallSession.prototype.sendPacket = function (rtpPacket) { | ||
sendPacket(rtpPacket) { | ||
if (this.disposed) { | ||
@@ -172,17 +109,16 @@ return; | ||
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header)); | ||
}; | ||
CallSession.prototype.startLocalServices = function () { | ||
var _this = this; | ||
} | ||
startLocalServices() { | ||
this.socket = node_dgram_1.default.createSocket("udp4"); | ||
this.socket.on("message", function (message) { | ||
var rtpPacket = werift_rtp_1.RtpPacket.deSerialize(_this.srtpSession.decrypt(message)); | ||
_this.emit("rtpPacket", rtpPacket); | ||
this.socket.on("message", (message) => { | ||
const rtpPacket = werift_rtp_1.RtpPacket.deSerialize(this.srtpSession.decrypt(message)); | ||
this.emit("rtpPacket", rtpPacket); | ||
if (rtpPacket.header.payloadType === 101) { | ||
_this.emit("dtmfPacket", rtpPacket); | ||
var char = dtmf_1.default.payloadToChar(rtpPacket.payload); | ||
this.emit("dtmfPacket", rtpPacket); | ||
const char = dtmf_1.default.payloadToChar(rtpPacket.payload); | ||
if (char) { | ||
_this.emit("dtmf", char); | ||
this.emit("dtmf", char); | ||
} | ||
} | ||
else if (rtpPacket.header.payloadType === _this.softphone.codec.id) { | ||
else if (rtpPacket.header.payloadType === this.softphone.codec.id) { | ||
if (rtpPacket.payload.length === 4 && | ||
@@ -200,6 +136,6 @@ rtpPacket.payload[0] >= 0x00 && | ||
try { | ||
rtpPacket.payload = _this.decoder.decode(rtpPacket.payload); | ||
_this.emit("audioPacket", rtpPacket); | ||
rtpPacket.payload = this.decoder.decode(rtpPacket.payload); | ||
this.emit("audioPacket", rtpPacket); | ||
} | ||
catch (_a) { | ||
catch { | ||
console.error("opus decode failed", rtpPacket); | ||
@@ -214,29 +150,27 @@ } | ||
this.send("hello"); | ||
var byeHandler = function (inboundMessage) { | ||
if (inboundMessage.headers["Call-ID"] !== _this.callId) { | ||
const byeHandler = (inboundMessage) => { | ||
if (inboundMessage.headers["Call-ID"] !== this.callId) { | ||
return; | ||
} | ||
if (inboundMessage.headers.CSeq.endsWith(" BYE")) { | ||
_this.softphone.off("message", byeHandler); | ||
_this.dispose(); | ||
this.softphone.off("message", byeHandler); | ||
this.dispose(); | ||
} | ||
}; | ||
this.softphone.on("message", byeHandler); | ||
}; | ||
CallSession.prototype.dispose = function () { | ||
var _a, _b; | ||
} | ||
dispose() { | ||
this.disposed = true; | ||
this.emit("disposed"); | ||
this.removeAllListeners(); | ||
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.removeAllListeners(); | ||
(_b = this.socket) === null || _b === void 0 ? void 0 : _b.close(); | ||
}; | ||
CallSession.prototype.transfer = function (transferTo) { | ||
var _this = this; | ||
var requestMessage = new index_1.RequestMessage("REFER sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.sipInfo.outboundProxy, ";transport=tls SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";rport;branch=").concat((0, utils_1.branch)(), ";alias"), | ||
this.socket?.removeAllListeners(); | ||
this.socket?.close(); | ||
} | ||
transfer(transferTo) { | ||
const requestMessage = new index_1.RequestMessage(`REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, utils_1.branch)()};alias`, | ||
"Max-Forwards": 70, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Contact: "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";transport=TLS;ob>"), | ||
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`, | ||
"Call-ID": this.callId, | ||
@@ -248,21 +182,20 @@ Event: "refer", | ||
"Allow-Events": "presence, message-summary, refer", | ||
"Refer-To": "sip:".concat(transferTo, "@").concat(this.softphone.sipInfo.domain), | ||
"Referred-By": "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.sipInfo.domain, ">"), | ||
"Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`, | ||
"Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`, | ||
}); | ||
this.softphone.send(requestMessage); | ||
// reply to those NOTIFY messages | ||
var notifyHandler = function (inboundMessage) { | ||
const notifyHandler = (inboundMessage) => { | ||
if (!inboundMessage.subject.startsWith("NOTIFY ")) { | ||
return; | ||
} | ||
var responseMessage = new index_1.ResponseMessage(inboundMessage, 200); | ||
_this.softphone.send(responseMessage); | ||
const responseMessage = new index_1.ResponseMessage(inboundMessage, 200); | ||
this.softphone.send(responseMessage); | ||
if (inboundMessage.body.trim() === "SIP/2.0 200 OK") { | ||
_this.softphone.off("message", notifyHandler); | ||
this.softphone.off("message", notifyHandler); | ||
} | ||
}; | ||
this.softphone.on("message", notifyHandler); | ||
}; | ||
return CallSession; | ||
}(node_events_1.default)); | ||
} | ||
} | ||
exports.default = CallSession; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -57,67 +6,52 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var index_1 = __importDefault(require("./index")); | ||
var index_2 = require("../sip-message/index"); | ||
var utils_1 = require("../utils"); | ||
var OutboundCallSession = /** @class */ (function (_super) { | ||
__extends(OutboundCallSession, _super); | ||
function OutboundCallSession(softphone, answerMessage) { | ||
var _this = _super.call(this, softphone, answerMessage) || this; | ||
_this.localPeer = answerMessage.headers.From; | ||
_this.remotePeer = answerMessage.headers.To; | ||
_this.remoteKey = answerMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
_this.init(); | ||
return _this; | ||
const index_1 = __importDefault(require("./index")); | ||
const index_2 = require("../sip-message/index"); | ||
const utils_1 = require("../utils"); | ||
class OutboundCallSession extends index_1.default { | ||
constructor(softphone, answerMessage) { | ||
super(softphone, answerMessage); | ||
this.localPeer = answerMessage.headers.From; | ||
this.remotePeer = answerMessage.headers.To; | ||
this.remoteKey = answerMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
this.init(); | ||
} | ||
OutboundCallSession.prototype.init = function () { | ||
var _this = this; | ||
init() { | ||
// wait for user to answer the call | ||
var answerHandler = function (message) { | ||
if (message.headers.CSeq !== _this.sipMessage.headers.CSeq) { | ||
const answerHandler = (message) => { | ||
if (message.headers.CSeq !== this.sipMessage.headers.CSeq) { | ||
return; | ||
} | ||
if (message.subject.startsWith("SIP/2.0 486")) { | ||
_this.softphone.off("message", answerHandler); | ||
_this.emit("busy"); | ||
_this.dispose(); | ||
this.softphone.off("message", answerHandler); | ||
this.emit("busy"); | ||
this.dispose(); | ||
return; | ||
} | ||
if (message.subject.startsWith("SIP/2.0 200")) { | ||
_this.softphone.off("message", answerHandler); | ||
_this.emit("answered"); | ||
var ackMessage = new index_2.RequestMessage("ACK ".concat((0, utils_1.extractAddress)(_this.remotePeer), " SIP/2.0"), { | ||
"Call-ID": _this.callId, | ||
From: _this.localPeer, | ||
To: _this.remotePeer, | ||
Via: _this.sipMessage.headers.Via, | ||
CSeq: _this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"), | ||
this.softphone.off("message", answerHandler); | ||
this.emit("answered"); | ||
const ackMessage = new index_2.RequestMessage(`ACK ${(0, utils_1.extractAddress)(this.remotePeer)} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"), | ||
}); | ||
_this.softphone.send(ackMessage); | ||
this.softphone.send(ackMessage); | ||
} | ||
}; | ||
this.softphone.on("message", answerHandler); | ||
this.once("answered", function () { return _this.startLocalServices(); }); | ||
}; | ||
OutboundCallSession.prototype.cancel = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var requestMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
requestMessage = new index_2.RequestMessage("CANCEL ".concat((0, utils_1.extractAddress)(this.remotePeer), " SIP/2.0"), { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: (0, utils_1.withoutTag)(this.remotePeer), | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"), | ||
}); | ||
return [4 /*yield*/, this.softphone.send(requestMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
this.once("answered", () => this.startLocalServices()); | ||
} | ||
async cancel() { | ||
const requestMessage = new index_2.RequestMessage(`CANCEL ${(0, utils_1.extractAddress)(this.remotePeer)} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: (0, utils_1.withoutTag)(this.remotePeer), | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"), | ||
}); | ||
}; | ||
return OutboundCallSession; | ||
}(index_1.default)); | ||
await this.softphone.send(requestMessage); | ||
} | ||
} | ||
exports.default = OutboundCallSession; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -21,42 +6,38 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var node_events_1 = __importDefault(require("node:events")); | ||
var node_buffer_1 = require("node:buffer"); | ||
var werift_rtp_1 = require("werift-rtp"); | ||
var Streamer = /** @class */ (function (_super) { | ||
__extends(Streamer, _super); | ||
function Streamer(callSesstion, buffer) { | ||
var _this = _super.call(this) || this; | ||
_this.paused = false; | ||
_this.callSession = callSesstion; | ||
_this.buffer = buffer; | ||
_this.originalBuffer = buffer; | ||
return _this; | ||
const node_events_1 = __importDefault(require("node:events")); | ||
const node_buffer_1 = require("node:buffer"); | ||
const werift_rtp_1 = require("werift-rtp"); | ||
class Streamer extends node_events_1.default { | ||
paused = false; | ||
callSession; | ||
buffer; | ||
originalBuffer; | ||
constructor(callSesstion, buffer) { | ||
super(); | ||
this.callSession = callSesstion; | ||
this.buffer = buffer; | ||
this.originalBuffer = buffer; | ||
} | ||
Streamer.prototype.start = function () { | ||
start() { | ||
this.buffer = this.originalBuffer; | ||
this.paused = false; | ||
this.sendPacket(); | ||
}; | ||
Streamer.prototype.stop = function () { | ||
} | ||
stop() { | ||
this.buffer = node_buffer_1.Buffer.alloc(0); | ||
}; | ||
Streamer.prototype.pause = function () { | ||
} | ||
pause() { | ||
this.paused = true; | ||
}; | ||
Streamer.prototype.resume = function () { | ||
} | ||
resume() { | ||
this.paused = false; | ||
this.sendPacket(); | ||
}; | ||
Object.defineProperty(Streamer.prototype, "finished", { | ||
get: function () { | ||
return this.buffer.length < this.callSession.softphone.codec.packetSize; | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Streamer.prototype.sendPacket = function () { | ||
var _this = this; | ||
} | ||
get finished() { | ||
return this.buffer.length < this.callSession.softphone.codec.packetSize; | ||
} | ||
sendPacket() { | ||
if (!this.callSession.disposed && !this.paused && !this.finished) { | ||
var temp = this.callSession.encoder.encode(this.buffer.subarray(0, this.callSession.softphone.codec.packetSize)); | ||
var rtpPacket = new werift_rtp_1.RtpPacket(new werift_rtp_1.RtpHeader({ | ||
const temp = this.callSession.encoder.encode(this.buffer.subarray(0, this.callSession.softphone.codec.packetSize)); | ||
const rtpPacket = new werift_rtp_1.RtpPacket(new werift_rtp_1.RtpHeader({ | ||
version: 2, | ||
@@ -90,8 +71,7 @@ padding: false, | ||
else { | ||
setTimeout(function () { return _this.sendPacket(); }, 20); | ||
setTimeout(() => this.sendPacket(), 20); | ||
} | ||
} | ||
}; | ||
return Streamer; | ||
}(node_events_1.default)); | ||
} | ||
} | ||
exports.default = Streamer; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var node_buffer_1 = require("node:buffer"); | ||
var opus_1 = require("@evan/opus"); | ||
var Codec = /** @class */ (function () { | ||
function Codec(name) { | ||
const node_buffer_1 = require("node:buffer"); | ||
const opus_1 = require("@evan/opus"); | ||
class Codec { | ||
id; | ||
name; | ||
packetSize; | ||
timestampInterval; | ||
createEncoder; | ||
createDecoder; | ||
constructor(name) { | ||
this.name = name; | ||
switch (name) { | ||
case "OPUS/16000": { | ||
this.createEncoder = function () { | ||
var encoder = new opus_1.Encoder({ channels: 1, sample_rate: 16000 }); | ||
return { encode: function (pcm) { return node_buffer_1.Buffer.from(encoder.encode(pcm)); } }; | ||
this.createEncoder = () => { | ||
const encoder = new opus_1.Encoder({ channels: 1, sample_rate: 16000 }); | ||
return { encode: (pcm) => node_buffer_1.Buffer.from(encoder.encode(pcm)) }; | ||
}; | ||
this.createDecoder = function () { | ||
var decoder = new opus_1.Decoder({ channels: 1, sample_rate: 16000 }); | ||
this.createDecoder = () => { | ||
const decoder = new opus_1.Decoder({ channels: 1, sample_rate: 16000 }); | ||
return { | ||
decode: function (opus) { return node_buffer_1.Buffer.from(decoder.decode(opus)); }, | ||
decode: (opus) => node_buffer_1.Buffer.from(decoder.decode(opus)), | ||
}; | ||
@@ -26,10 +32,10 @@ }; | ||
case "OPUS/48000/2": { | ||
this.createEncoder = function () { | ||
var encoder = new opus_1.Encoder({ channels: 2, sample_rate: 48000 }); | ||
return { encode: function (pcm) { return node_buffer_1.Buffer.from(encoder.encode(pcm)); } }; | ||
this.createEncoder = () => { | ||
const encoder = new opus_1.Encoder({ channels: 2, sample_rate: 48000 }); | ||
return { encode: (pcm) => node_buffer_1.Buffer.from(encoder.encode(pcm)) }; | ||
}; | ||
this.createDecoder = function () { | ||
var decoder = new opus_1.Decoder({ channels: 2, sample_rate: 48000 }); | ||
this.createDecoder = () => { | ||
const decoder = new opus_1.Decoder({ channels: 2, sample_rate: 48000 }); | ||
return { | ||
decode: function (opus) { return node_buffer_1.Buffer.from(decoder.decode(opus)); }, | ||
decode: (opus) => node_buffer_1.Buffer.from(decoder.decode(opus)), | ||
}; | ||
@@ -43,7 +49,7 @@ }; | ||
case "PCMU/8000": { | ||
this.createEncoder = function () { | ||
return { encode: function (pcm) { return pcm; } }; | ||
this.createEncoder = () => { | ||
return { encode: (pcm) => pcm }; | ||
}; | ||
this.createDecoder = function () { | ||
return { decode: function (audio) { return audio; } }; | ||
this.createDecoder = () => { | ||
return { decode: (audio) => audio }; | ||
}; | ||
@@ -56,8 +62,7 @@ this.id = 0; | ||
default: { | ||
throw new Error("unsupported codec: ".concat(name)); | ||
throw new Error(`unsupported codec: ${name}`); | ||
} | ||
} | ||
} | ||
return Codec; | ||
}()); | ||
} | ||
exports.default = Codec; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var node_buffer_1 = require("node:buffer"); | ||
var DTMF = /** @class */ (function () { | ||
function DTMF() { | ||
} | ||
DTMF.phoneChars = [ | ||
const node_buffer_1 = require("node:buffer"); | ||
class DTMF { | ||
static phoneChars = [ | ||
"0", | ||
@@ -21,3 +19,3 @@ "1", | ||
]; | ||
DTMF.payloads = [ | ||
static payloads = [ | ||
0x00060000, | ||
@@ -30,10 +28,10 @@ 0x000600a0, | ||
]; | ||
DTMF.charToPayloads = function (char) { | ||
var index = DTMF.phoneChars.indexOf(char[0]); | ||
static charToPayloads = (char) => { | ||
const index = DTMF.phoneChars.indexOf(char[0]); | ||
if (index === -1) { | ||
throw new Error("invalid phone char"); | ||
} | ||
return DTMF.payloads.map(function (payload) { | ||
var temp = payload + index * 0x01000000; | ||
var buffer = node_buffer_1.Buffer.alloc(4); | ||
return DTMF.payloads.map((payload) => { | ||
const temp = payload + index * 0x01000000; | ||
const buffer = node_buffer_1.Buffer.alloc(4); | ||
buffer.writeIntBE(temp, 0, 4); | ||
@@ -43,9 +41,8 @@ return buffer; | ||
}; | ||
DTMF.payloadToChar = function (payload) { | ||
var intBE = payload.readIntBE(0, 4); | ||
var index = (intBE - 0x00060000) / 0x01000000; | ||
static payloadToChar = (payload) => { | ||
const intBE = payload.readIntBE(0, 4); | ||
const index = (intBE - 0x00060000) / 0x01000000; | ||
return DTMF.phoneChars[index]; | ||
}; | ||
return DTMF; | ||
}()); | ||
} | ||
exports.default = DTMF; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -57,36 +6,37 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var node_events_1 = __importDefault(require("node:events")); | ||
var node_tls_1 = __importDefault(require("node:tls")); | ||
var wait_for_async_1 = __importDefault(require("wait-for-async")); | ||
var inbound_1 = __importDefault(require("./call-session/inbound")); | ||
var outbound_1 = __importDefault(require("./call-session/outbound")); | ||
var index_1 = require("./sip-message/index"); | ||
var utils_1 = require("./utils"); | ||
var codec_1 = __importDefault(require("./codec")); | ||
var Softphone = /** @class */ (function (_super) { | ||
__extends(Softphone, _super); | ||
function Softphone(sipInfo) { | ||
var _this = _super.call(this) || this; | ||
_this.fakeDomain = (0, utils_1.uuid)() + ".invalid"; | ||
_this.fakeEmail = (0, utils_1.uuid)() + "@" + _this.fakeDomain; | ||
_this.connected = false; | ||
_this.instanceId = (0, utils_1.uuid)(); | ||
_this.registerCallId = (0, utils_1.uuid)(); | ||
const node_events_1 = __importDefault(require("node:events")); | ||
const node_tls_1 = __importDefault(require("node:tls")); | ||
const wait_for_async_1 = __importDefault(require("wait-for-async")); | ||
const inbound_1 = __importDefault(require("./call-session/inbound")); | ||
const outbound_1 = __importDefault(require("./call-session/outbound")); | ||
const index_1 = require("./sip-message/index"); | ||
const utils_1 = require("./utils"); | ||
const codec_1 = __importDefault(require("./codec")); | ||
class Softphone extends node_events_1.default { | ||
sipInfo; | ||
client; | ||
codec; | ||
fakeDomain = (0, utils_1.uuid)() + ".invalid"; | ||
fakeEmail = (0, utils_1.uuid)() + "@" + this.fakeDomain; | ||
intervalHandle; | ||
connected = false; | ||
constructor(sipInfo) { | ||
super(); | ||
if (sipInfo.codec === undefined) { | ||
sipInfo.codec = "OPUS/16000"; | ||
} | ||
_this.codec = new codec_1.default(sipInfo.codec); | ||
_this.sipInfo = sipInfo; | ||
if (_this.sipInfo.domain === undefined) { | ||
_this.sipInfo.domain = "sip.ringcentral.com"; | ||
this.codec = new codec_1.default(sipInfo.codec); | ||
this.sipInfo = sipInfo; | ||
if (this.sipInfo.domain === undefined) { | ||
this.sipInfo.domain = "sip.ringcentral.com"; | ||
} | ||
if (_this.sipInfo.outboundProxy === undefined) { | ||
_this.sipInfo.outboundProxy = "sip10.ringcentral.com:5096"; | ||
if (this.sipInfo.outboundProxy === undefined) { | ||
this.sipInfo.outboundProxy = "sip10.ringcentral.com:5096"; | ||
} | ||
var tokens = _this.sipInfo.outboundProxy.split(":"); | ||
_this.client = node_tls_1.default.connect({ host: tokens[0], port: parseInt(tokens[1], 10) }, function () { | ||
_this.connected = true; | ||
const tokens = this.sipInfo.outboundProxy.split(":"); | ||
this.client = node_tls_1.default.connect({ host: tokens[0], port: parseInt(tokens[1], 10) }, () => { | ||
this.connected = true; | ||
}); | ||
var cache = ""; | ||
_this.client.on("data", function (data) { | ||
let cache = ""; | ||
this.client.on("data", (data) => { | ||
cache += data.toString("utf-8"); | ||
@@ -97,7 +47,7 @@ if (!cache.endsWith("\r\n")) { | ||
// received two empty body messages | ||
var tempMessages = cache | ||
const tempMessages = cache | ||
.split("\r\nContent-Length: 0\r\n\r\n") | ||
.filter(function (message) { return message.trim() !== ""; }); | ||
.filter((message) => message.trim() !== ""); | ||
cache = ""; | ||
for (var i = 0; i < tempMessages.length; i++) { | ||
for (let i = 0; i < tempMessages.length; i++) { | ||
if (!tempMessages[i].includes("Content-Length: ")) { | ||
@@ -107,92 +57,68 @@ tempMessages[i] = tempMessages[i] + "\r\nContent-Length: 0"; | ||
} | ||
for (var _i = 0, tempMessages_1 = tempMessages; _i < tempMessages_1.length; _i++) { | ||
var message = tempMessages_1[_i]; | ||
_this.emit("message", index_1.InboundMessage.fromString(message)); | ||
for (const message of tempMessages) { | ||
this.emit("message", index_1.InboundMessage.fromString(message)); | ||
} | ||
}); | ||
return _this; | ||
} | ||
Softphone.prototype.register = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var sipRegister; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!!this.connected) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, (0, wait_for_async_1.default)({ interval: 100, condition: function () { return _this.connected; } })]; | ||
case 1: | ||
_a.sent(); | ||
_a.label = 2; | ||
case 2: | ||
sipRegister = function () { return __awaiter(_this, void 0, void 0, function () { | ||
var fromTag, requestMessage, inboundMessage, wwwAuth, nonce, newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
fromTag = (0, utils_1.uuid)(); | ||
requestMessage = new index_1.RequestMessage("REGISTER sip:".concat(this.sipInfo.domain, " SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.client.localAddress, ":").concat(this.client.localPort, ";rport;branch=").concat((0, utils_1.branch)(), ";alias"), | ||
Route: "<sip:".concat(this.sipInfo.outboundProxy, ";transport=tls;lr>"), | ||
"Max-Forwards": "70", | ||
From: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">;tag=").concat(fromTag), | ||
To: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">"), | ||
"Call-ID": this.registerCallId, | ||
Supported: "outbound, path", | ||
Contact: "<sip:".concat(this.sipInfo.username, "@").concat(this.client.localAddress, ":").concat(this.client.localPort, ";transport=TLS;ob>;reg-id=1;+sip.instance=\"<urn:uuid:").concat(this.instanceId, ">\""), | ||
Expires: 300, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
}); | ||
return [4 /*yield*/, this.send(requestMessage, true)]; | ||
case 1: | ||
inboundMessage = _a.sent(); | ||
if (inboundMessage.subject.startsWith("SIP/2.0 200 ")) { | ||
// sometimes the server will return 200 OK directly | ||
return [2 /*return*/]; | ||
} | ||
wwwAuth = inboundMessage.headers["Www-Authenticate"] || | ||
inboundMessage.headers["WWW-Authenticate"]; | ||
nonce = wwwAuth.match(/, nonce="(.+?)"/)[1]; | ||
newMessage = requestMessage.fork(); | ||
newMessage.headers.Authorization = (0, utils_1.generateAuthorization)(this.sipInfo, nonce, "REGISTER"); | ||
this.send(newMessage); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }; | ||
sipRegister(); | ||
this.intervalHandle = setInterval(function () { | ||
sipRegister(); | ||
}, 3 * 60 * 1000); | ||
this.on("message", function (inboundMessage) { | ||
if (!inboundMessage.subject.startsWith("INVITE sip:")) { | ||
return; | ||
} | ||
var outboundMessage = new index_1.OutboundMessage("SIP/2.0 100 Trying", { | ||
Via: inboundMessage.headers.Via, | ||
"Call-ID": inboundMessage.headers["Call-ID"], | ||
From: inboundMessage.headers.From, | ||
To: inboundMessage.headers.To, | ||
CSeq: inboundMessage.headers.CSeq, | ||
"Content-Length": "0", | ||
}); | ||
_this.send(outboundMessage); | ||
_this.emit("invite", inboundMessage); | ||
}); | ||
return [2 /*return*/]; | ||
} | ||
instanceId = (0, utils_1.uuid)(); | ||
registerCallId = (0, utils_1.uuid)(); | ||
async register() { | ||
if (!this.connected) { | ||
await (0, wait_for_async_1.default)({ interval: 100, condition: () => this.connected }); | ||
} | ||
const sipRegister = async () => { | ||
const fromTag = (0, utils_1.uuid)(); | ||
const requestMessage = new index_1.RequestMessage(`REGISTER sip:${this.sipInfo.domain} SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${(0, utils_1.branch)()};alias`, | ||
Route: `<sip:${this.sipInfo.outboundProxy};transport=tls;lr>`, | ||
"Max-Forwards": "70", | ||
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${fromTag}`, | ||
To: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>`, | ||
"Call-ID": this.registerCallId, | ||
Supported: "outbound, path", | ||
Contact: `<sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>;reg-id=1;+sip.instance="<urn:uuid:${this.instanceId}>"`, | ||
Expires: 300, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
}); | ||
const inboundMessage = await this.send(requestMessage, true); | ||
if (inboundMessage.subject.startsWith("SIP/2.0 200 ")) { | ||
// sometimes the server will return 200 OK directly | ||
return; | ||
} | ||
const wwwAuth = inboundMessage.headers["Www-Authenticate"] || | ||
inboundMessage.headers["WWW-Authenticate"]; | ||
const nonce = wwwAuth.match(/, nonce="(.+?)"/)[1]; | ||
const newMessage = requestMessage.fork(); | ||
newMessage.headers.Authorization = (0, utils_1.generateAuthorization)(this.sipInfo, nonce, "REGISTER"); | ||
this.send(newMessage); | ||
}; | ||
sipRegister(); | ||
this.intervalHandle = setInterval(() => { | ||
sipRegister(); | ||
}, 3 * 60 * 1000); | ||
this.on("message", (inboundMessage) => { | ||
if (!inboundMessage.subject.startsWith("INVITE sip:")) { | ||
return; | ||
} | ||
const outboundMessage = new index_1.OutboundMessage("SIP/2.0 100 Trying", { | ||
Via: inboundMessage.headers.Via, | ||
"Call-ID": inboundMessage.headers["Call-ID"], | ||
From: inboundMessage.headers.From, | ||
To: inboundMessage.headers.To, | ||
CSeq: inboundMessage.headers.CSeq, | ||
"Content-Length": "0", | ||
}); | ||
this.send(outboundMessage); | ||
this.emit("invite", inboundMessage); | ||
}); | ||
}; | ||
Softphone.prototype.enableDebugMode = function () { | ||
this.on("message", function (message) { | ||
return console.log("Receiving...(".concat(new Date(), ")\n") + message.toString()); | ||
}); | ||
var tlsWrite = this.client.write.bind(this.client); | ||
this.client.write = function (message) { | ||
console.log("Sending...(".concat(new Date(), ")\n") + message); | ||
} | ||
enableDebugMode() { | ||
this.on("message", (message) => console.log(`Receiving...(${new Date()})\n` + message.toString())); | ||
const tlsWrite = this.client.write.bind(this.client); | ||
this.client.write = (message) => { | ||
console.log(`Sending...(${new Date()})\n` + message); | ||
return tlsWrite(message); | ||
}; | ||
}; | ||
Softphone.prototype.revoke = function () { | ||
} | ||
revoke() { | ||
clearInterval(this.intervalHandle); | ||
@@ -202,14 +128,12 @@ this.removeAllListeners(); | ||
this.client.destroy(); | ||
}; | ||
Softphone.prototype.send = function (message, waitForReply) { | ||
var _this = this; | ||
if (waitForReply === void 0) { waitForReply = false; } | ||
} | ||
send(message, waitForReply = false) { | ||
this.client.write(message.toString()); | ||
if (!waitForReply) { | ||
return new Promise(function (resolve) { | ||
return new Promise((resolve) => { | ||
resolve(undefined); | ||
}); | ||
} | ||
return new Promise(function (resolve) { | ||
var messageListerner = function (inboundMessage) { | ||
return new Promise((resolve) => { | ||
const messageListerner = (inboundMessage) => { | ||
if (inboundMessage.headers.CSeq !== message.headers.CSeq) { | ||
@@ -221,77 +145,55 @@ return; | ||
} | ||
_this.off("message", messageListerner); | ||
this.off("message", messageListerner); | ||
resolve(inboundMessage); | ||
}; | ||
_this.on("message", messageListerner); | ||
this.on("message", messageListerner); | ||
}); | ||
}; | ||
Softphone.prototype.answer = function (inviteMessage) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var inboundCallSession; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
inboundCallSession = new inbound_1.default(this, inviteMessage); | ||
return [4 /*yield*/, inboundCallSession.answer()]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/, inboundCallSession]; | ||
} | ||
}); | ||
}); | ||
}; | ||
} | ||
async answer(inviteMessage) { | ||
const inboundCallSession = new inbound_1.default(this, inviteMessage); | ||
await inboundCallSession.answer(); | ||
return inboundCallSession; | ||
} | ||
// decline an inbound call | ||
Softphone.prototype.decline = function (inviteMessage) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
newMessage = new index_1.ResponseMessage(inviteMessage, 603); | ||
return [4 /*yield*/, this.send(newMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
Softphone.prototype.call = function (callee) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var offerSDP, inviteMessage, inboundMessage, proxyAuthenticate, nonce, newMessage, progressMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
offerSDP = "\nv=0\no=- ".concat(Date.now(), " 0 IN IP4 ").concat(this.client.localAddress, "\ns=rc-softphone-ts\nc=IN IP4 ").concat(this.client.localAddress, "\nt=0 0\nm=audio ").concat((0, utils_1.randomInt)(), " RTP/SAVP ").concat(this.codec.id, " 101\na=rtpmap:").concat(this.codec.id, " ").concat(this.codec.name, "\na=rtpmap:101 telephone-event/8000\na=fmtp:101 0-15\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:").concat(utils_1.localKey, "\n ").trim(); | ||
inviteMessage = new index_1.RequestMessage("INVITE sip:".concat(callee, " SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.client.localAddress, ":").concat(this.client.localPort, ";rport;branch=").concat((0, utils_1.branch)(), ";alias"), | ||
"Max-Forwards": 70, | ||
From: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">;tag=").concat((0, utils_1.uuid)()), | ||
To: "<sip:".concat(callee, "@sip.ringcentral.com>"), | ||
Contact: " <sip:".concat(this.sipInfo.username, "@").concat(this.client.localAddress, ":").concat(this.client.localPort, ";transport=TLS;ob>"), | ||
"Call-ID": (0, utils_1.uuid)(), | ||
Route: "<sip:".concat(this.sipInfo.outboundProxy, ";transport=tls;lr>"), | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": 1800, | ||
"Min-SE": 90, | ||
"Content-Type": "application/sdp", | ||
}, offerSDP); | ||
return [4 /*yield*/, this.send(inviteMessage, true)]; | ||
case 1: | ||
inboundMessage = _a.sent(); | ||
proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"]; | ||
nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1]; | ||
newMessage = inviteMessage.fork(); | ||
newMessage.headers["Proxy-Authorization"] = (0, utils_1.generateAuthorization)(this.sipInfo, nonce, "INVITE"); | ||
return [4 /*yield*/, this.send(newMessage, true)]; | ||
case 2: | ||
progressMessage = _a.sent(); | ||
return [2 /*return*/, new outbound_1.default(this, progressMessage)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return Softphone; | ||
}(node_events_1.default)); | ||
async decline(inviteMessage) { | ||
const newMessage = new index_1.ResponseMessage(inviteMessage, 603); | ||
await this.send(newMessage); | ||
} | ||
async call(callee) { | ||
const offerSDP = ` | ||
v=0 | ||
o=- ${Date.now()} 0 IN IP4 ${this.client.localAddress} | ||
s=rc-softphone-ts | ||
c=IN IP4 ${this.client.localAddress} | ||
t=0 0 | ||
m=audio ${(0, utils_1.randomInt)()} RTP/SAVP ${this.codec.id} 101 | ||
a=rtpmap:${this.codec.id} ${this.codec.name} | ||
a=rtpmap:101 telephone-event/8000 | ||
a=fmtp:101 0-15 | ||
a=sendrecv | ||
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_1.localKey} | ||
`.trim(); | ||
const inviteMessage = new index_1.RequestMessage(`INVITE sip:${callee} SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${(0, utils_1.branch)()};alias`, | ||
"Max-Forwards": 70, | ||
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${(0, utils_1.uuid)()}`, | ||
To: `<sip:${callee}@sip.ringcentral.com>`, | ||
Contact: ` <sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>`, | ||
"Call-ID": (0, utils_1.uuid)(), | ||
Route: `<sip:${this.sipInfo.outboundProxy};transport=tls;lr>`, | ||
Allow: `PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS`, | ||
Supported: `replaces, 100rel, timer, norefersub`, | ||
"Session-Expires": 1800, | ||
"Min-SE": 90, | ||
"Content-Type": "application/sdp", | ||
}, offerSDP); | ||
const inboundMessage = await this.send(inviteMessage, true); | ||
const proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"]; | ||
const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1]; | ||
const newMessage = inviteMessage.fork(); | ||
newMessage.headers["Proxy-Authorization"] = (0, utils_1.generateAuthorization)(this.sipInfo, nonce, "INVITE"); | ||
const progressMessage = await this.send(newMessage, true); | ||
return new outbound_1.default(this, progressMessage); | ||
} | ||
} | ||
exports.default = Softphone; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -21,16 +6,12 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var utils_1 = require("../../utils"); | ||
var sip_message_1 = __importDefault(require("../sip-message")); | ||
var InboundMessage = /** @class */ (function (_super) { | ||
__extends(InboundMessage, _super); | ||
function InboundMessage() { | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
InboundMessage.fromString = function (str) { | ||
var sipMessage = new sip_message_1.default(); | ||
var _a = str.split("\r\n\r\n"), init = _a[0], body = _a.slice(1); | ||
const utils_1 = require("../../utils"); | ||
const sip_message_1 = __importDefault(require("../sip-message")); | ||
class InboundMessage extends sip_message_1.default { | ||
static fromString(str) { | ||
const sipMessage = new sip_message_1.default(); | ||
const [init, ...body] = str.split("\r\n\r\n"); | ||
sipMessage.body = body.join("\r\n\r\n"); | ||
var _b = init.split("\r\n"), subject = _b[0], headers = _b.slice(1); | ||
const [subject, ...headers] = init.split("\r\n"); | ||
sipMessage.subject = subject; | ||
sipMessage.headers = Object.fromEntries(headers.map(function (line) { return line.split(": "); })); | ||
sipMessage.headers = Object.fromEntries(headers.map((line) => line.split(": "))); | ||
if (sipMessage.headers.To && !sipMessage.headers.To.includes(";tag=")) { | ||
@@ -40,5 +21,4 @@ sipMessage.headers.To += ";tag=" + (0, utils_1.uuid)(); // generate local tag | ||
return sipMessage; | ||
}; | ||
return InboundMessage; | ||
}(sip_message_1.default)); | ||
} | ||
} | ||
exports.default = InboundMessage; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -21,16 +6,10 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var sip_message_1 = __importDefault(require("../sip-message")); | ||
var OutboundMessage = /** @class */ (function (_super) { | ||
__extends(OutboundMessage, _super); | ||
function OutboundMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, subject, headers, body) || this; | ||
_this.headers["Content-Length"] = _this.body.length.toString(); | ||
_this.headers["User-Agent"] = "ringcentral-softphone-ts"; | ||
return _this; | ||
const sip_message_1 = __importDefault(require("../sip-message")); | ||
class OutboundMessage extends sip_message_1.default { | ||
constructor(subject = "", headers = {}, body = "") { | ||
super(subject, headers, body); | ||
this.headers["Content-Length"] = this.body.length.toString(); | ||
this.headers["User-Agent"] = "ringcentral-softphone-ts"; | ||
} | ||
return OutboundMessage; | ||
}(sip_message_1.default)); | ||
} | ||
exports.default = OutboundMessage; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -32,30 +6,24 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var index_1 = __importDefault(require("./index")); | ||
var utils_1 = require("../../utils"); | ||
var cseq = Math.floor(Math.random() * 10000); | ||
var RequestMessage = /** @class */ (function (_super) { | ||
__extends(RequestMessage, _super); | ||
function RequestMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, subject, headers, body) || this; | ||
if (_this.headers.CSeq === undefined) { | ||
_this.newCseq(); | ||
const index_1 = __importDefault(require("./index")); | ||
const utils_1 = require("../../utils"); | ||
let cseq = Math.floor(Math.random() * 10000); | ||
class RequestMessage extends index_1.default { | ||
constructor(subject = "", headers = {}, body = "") { | ||
super(subject, headers, body); | ||
if (this.headers.CSeq === undefined) { | ||
this.newCseq(); | ||
} | ||
return _this; | ||
} | ||
RequestMessage.prototype.newCseq = function () { | ||
this.headers.CSeq = "".concat(++cseq, " ").concat(this.subject.split(" ")[0]); | ||
}; | ||
RequestMessage.prototype.fork = function () { | ||
var newMessage = new RequestMessage(this.subject, __assign({}, this.headers), this.body); | ||
newCseq() { | ||
this.headers.CSeq = `${++cseq} ${this.subject.split(" ")[0]}`; | ||
} | ||
fork() { | ||
const newMessage = new RequestMessage(this.subject, { ...this.headers }, this.body); | ||
newMessage.newCseq(); | ||
if (newMessage.headers.Via) { | ||
newMessage.headers.Via = newMessage.headers.Via.replace(/;branch=.+?$/, ";branch=".concat((0, utils_1.branch)())); | ||
newMessage.headers.Via = newMessage.headers.Via.replace(/;branch=.+?$/, `;branch=${(0, utils_1.branch)()}`); | ||
} | ||
return newMessage; | ||
}; | ||
return RequestMessage; | ||
}(index_1.default)); | ||
} | ||
} | ||
exports.default = RequestMessage; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -32,22 +6,16 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var index_1 = __importDefault(require("./index")); | ||
var response_codes_1 = __importDefault(require("../response-codes")); | ||
var ResponseMessage = /** @class */ (function (_super) { | ||
__extends(ResponseMessage, _super); | ||
function ResponseMessage(inboundMessage, responseCode, headers, body) { | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, undefined, __assign({}, headers), body) || this; | ||
_this.subject = "SIP/2.0 ".concat(responseCode, " ").concat(response_codes_1.default[responseCode]); | ||
var keys = ["Via", "From", "To", "Call-ID", "CSeq"]; | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var key = keys_1[_i]; | ||
const index_1 = __importDefault(require("./index")); | ||
const response_codes_1 = __importDefault(require("../response-codes")); | ||
class ResponseMessage extends index_1.default { | ||
constructor(inboundMessage, responseCode, headers = {}, body = "") { | ||
super(undefined, { ...headers }, body); | ||
this.subject = `SIP/2.0 ${responseCode} ${response_codes_1.default[responseCode]}`; | ||
const keys = ["Via", "From", "To", "Call-ID", "CSeq"]; | ||
for (const key of keys) { | ||
if (inboundMessage.headers[key]) { | ||
_this.headers[key] = inboundMessage.headers[key]; | ||
this.headers[key] = inboundMessage.headers[key]; | ||
} | ||
} | ||
return _this; | ||
} | ||
return ResponseMessage; | ||
}(index_1.default)); | ||
} | ||
exports.default = ResponseMessage; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// Ref: https://en.wikipedia.org/wiki/List_of_SIP_response_codes' | ||
var responseCodes = { | ||
const responseCodes = { | ||
100: "Trying", | ||
@@ -6,0 +6,0 @@ 180: "Ringing", |
"use strict"; | ||
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { | ||
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { | ||
if (ar || !(i in from)) { | ||
if (!ar) ar = Array.prototype.slice.call(from, 0, i); | ||
ar[i] = from[i]; | ||
} | ||
} | ||
return to.concat(ar || Array.prototype.slice.call(from)); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var SipMessage = /** @class */ (function () { | ||
function SipMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
class SipMessage { | ||
subject; | ||
headers; | ||
body; | ||
constructor(subject = "", headers = {}, body = "") { | ||
this.subject = subject; | ||
@@ -27,14 +18,12 @@ this.headers = headers; | ||
} | ||
SipMessage.prototype.toString = function () { | ||
var _this = this; | ||
var r = __spreadArray(__spreadArray([ | ||
this.subject | ||
], Object.keys(this.headers).map(function (key) { return "".concat(key, ": ").concat(_this.headers[key]); }), true), [ | ||
toString() { | ||
const r = [ | ||
this.subject, | ||
...Object.keys(this.headers).map((key) => `${key}: ${this.headers[key]}`), | ||
"", | ||
this.body, | ||
], false).join("\r\n"); | ||
].join("\r\n"); | ||
return r; | ||
}; | ||
return SipMessage; | ||
}()); | ||
} | ||
} | ||
exports.default = SipMessage; |
@@ -7,37 +7,35 @@ "use strict"; | ||
exports.localKey = exports.extractAddress = exports.withoutTag = exports.randomInt = exports.branch = exports.uuid = exports.generateAuthorization = void 0; | ||
var node_crypto_1 = __importDefault(require("node:crypto")); | ||
var md5 = function (s) { return node_crypto_1.default.createHash("md5").update(s).digest("hex"); }; | ||
var generateResponse = function (sipInfo, endpoint, nonce) { | ||
var ha1 = md5("".concat(sipInfo.authorizationId, ":").concat(sipInfo.domain, ":").concat(sipInfo.password)); | ||
var ha2 = md5(endpoint); | ||
var response = md5("".concat(ha1, ":").concat(nonce, ":").concat(ha2)); | ||
const node_crypto_1 = __importDefault(require("node:crypto")); | ||
const md5 = (s) => node_crypto_1.default.createHash("md5").update(s).digest("hex"); | ||
const generateResponse = (sipInfo, endpoint, nonce) => { | ||
const ha1 = md5(`${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`); | ||
const ha2 = md5(endpoint); | ||
const response = md5(`${ha1}:${nonce}:${ha2}`); | ||
return response; | ||
}; | ||
var generateAuthorization = function (sipInfo, nonce, method) { | ||
var authObj = { | ||
const generateAuthorization = (sipInfo, nonce, method) => { | ||
const authObj = { | ||
"Digest algorithm": "MD5", | ||
username: sipInfo.authorizationId, | ||
realm: sipInfo.domain, | ||
nonce: nonce, | ||
uri: "sip:".concat(sipInfo.domain), | ||
response: generateResponse(sipInfo, "".concat(method, ":sip:").concat(sipInfo.domain), nonce), | ||
nonce, | ||
uri: `sip:${sipInfo.domain}`, | ||
response: generateResponse(sipInfo, `${method}:sip:${sipInfo.domain}`, nonce), | ||
}; | ||
return Object.keys(authObj) | ||
.map(function (key) { return "".concat(key, "=\"").concat(authObj[key], "\""); }) | ||
.map((key) => `${key}="${authObj[key]}"`) | ||
.join(", "); | ||
}; | ||
exports.generateAuthorization = generateAuthorization; | ||
var uuid = function () { return node_crypto_1.default.randomUUID(); }; | ||
const uuid = () => node_crypto_1.default.randomUUID(); | ||
exports.uuid = uuid; | ||
var branch = function () { return "z9hG4bK-" + (0, exports.uuid)(); }; | ||
const branch = () => "z9hG4bK-" + (0, exports.uuid)(); | ||
exports.branch = branch; | ||
var randomInt = function () { | ||
return Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024; | ||
}; | ||
const randomInt = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024; | ||
exports.randomInt = randomInt; | ||
var withoutTag = function (s) { return s.replace(/;tag=.*$/, ""); }; | ||
const withoutTag = (s) => s.replace(/;tag=.*$/, ""); | ||
exports.withoutTag = withoutTag; | ||
var extractAddress = function (s) { var _a; return (_a = s.match(/<(sip:.+?)>/)) === null || _a === void 0 ? void 0 : _a[1]; }; | ||
const extractAddress = (s) => s.match(/<(sip:.+?)>/)?.[1]; | ||
exports.extractAddress = extractAddress; | ||
var keyAndSalt = node_crypto_1.default.randomBytes(30); | ||
const keyAndSalt = node_crypto_1.default.randomBytes(30); | ||
exports.localKey = keyAndSalt.toString("base64").replace(/=+$/, ""); |
@@ -1,95 +0,42 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
import CallSession from "./index"; | ||
import { OutboundMessage } from "../sip-message/index"; | ||
import { localKey, randomInt } from "../utils"; | ||
var InboundCallSession = /** @class */ (function (_super) { | ||
__extends(InboundCallSession, _super); | ||
function InboundCallSession(softphone, inviteMessage) { | ||
var _this = _super.call(this, softphone, inviteMessage) || this; | ||
_this.localPeer = inviteMessage.headers.To; | ||
_this.remotePeer = inviteMessage.headers.From; | ||
_this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
return _this; | ||
class InboundCallSession extends CallSession { | ||
constructor(softphone, inviteMessage) { | ||
super(softphone, inviteMessage); | ||
this.localPeer = inviteMessage.headers.To; | ||
this.remotePeer = inviteMessage.headers.From; | ||
this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
} | ||
InboundCallSession.prototype.answer = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var answerSDP, newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
answerSDP = "\nv=0\no=- ".concat(Date.now(), " 0 IN IP4 ").concat(this.softphone.client.localAddress, "\ns=rc-softphone-ts\nc=IN IP4 ").concat(this.softphone.client.localAddress, "\nt=0 0\nm=audio ").concat(randomInt(), " RTP/SAVP ").concat(this.softphone.codec.id, " 101\na=rtpmap:").concat(this.softphone.codec.id, " ").concat(this.softphone.codec.name, "\na=rtpmap:101 telephone-event/8000\na=fmtp:101 0-15\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:").concat(localKey, "\n").trim(); | ||
newMessage = new OutboundMessage("SIP/2.0 200 OK", { | ||
Via: this.sipMessage.headers.Via, | ||
"Call-ID": this.sipMessage.headers["Call-ID"], | ||
From: this.sipMessage.headers.From, | ||
To: this.sipMessage.headers.To, | ||
CSeq: this.sipMessage.headers.CSeq, | ||
Contact: "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";transport=TLS;ob>"), | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": "14400;refresher=uac", | ||
Require: "timer", | ||
"Content-Type": "application/sdp", | ||
}, answerSDP); | ||
return [4 /*yield*/, this.softphone.send(newMessage)]; | ||
case 1: | ||
_a.sent(); | ||
this.startLocalServices(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return InboundCallSession; | ||
}(CallSession)); | ||
async answer() { | ||
const answerSDP = ` | ||
v=0 | ||
o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress} | ||
s=rc-softphone-ts | ||
c=IN IP4 ${this.softphone.client.localAddress} | ||
t=0 0 | ||
m=audio ${randomInt()} RTP/SAVP ${this.softphone.codec.id} 101 | ||
a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name} | ||
a=rtpmap:101 telephone-event/8000 | ||
a=fmtp:101 0-15 | ||
a=sendrecv | ||
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey} | ||
`.trim(); | ||
const newMessage = new OutboundMessage("SIP/2.0 200 OK", { | ||
Via: this.sipMessage.headers.Via, | ||
"Call-ID": this.sipMessage.headers["Call-ID"], | ||
From: this.sipMessage.headers.From, | ||
To: this.sipMessage.headers.To, | ||
CSeq: this.sipMessage.headers.CSeq, | ||
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": "14400;refresher=uac", | ||
Require: "timer", | ||
"Content-Type": "application/sdp", | ||
}, answerSDP); | ||
await this.softphone.send(newMessage); | ||
this.startLocalServices(); | ||
} | ||
} | ||
export default InboundCallSession; |
@@ -1,52 +0,1 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
import dgram from "node:dgram"; | ||
@@ -60,70 +9,59 @@ import EventEmitter from "node:events"; | ||
import Streamer from "./streamer"; | ||
var CallSession = /** @class */ (function (_super) { | ||
__extends(CallSession, _super); | ||
function CallSession(softphone, sipMessage) { | ||
var _this = _super.call(this) || this; | ||
_this.disposed = false; | ||
// for audio streaming | ||
_this.ssrc = randomInt(); | ||
_this.sequenceNumber = randomInt(); | ||
_this.timestamp = randomInt(); | ||
_this.softphone = softphone; | ||
_this.encoder = softphone.codec.createEncoder(); | ||
_this.decoder = softphone.codec.createDecoder(); | ||
_this.sipMessage = sipMessage; | ||
_this.remoteIP = _this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1]; | ||
_this.remotePort = parseInt(_this.sipMessage.body.match(/m=audio (\d+) /)[1], 10); | ||
return _this; | ||
class CallSession extends EventEmitter { | ||
softphone; | ||
sipMessage; | ||
socket; | ||
localPeer; | ||
remotePeer; | ||
remoteIP; | ||
remotePort; | ||
disposed = false; | ||
srtpSession; | ||
encoder; | ||
decoder; | ||
// for audio streaming | ||
ssrc = randomInt(); | ||
sequenceNumber = randomInt(); | ||
timestamp = randomInt(); | ||
constructor(softphone, sipMessage) { | ||
super(); | ||
this.softphone = softphone; | ||
this.encoder = softphone.codec.createEncoder(); | ||
this.decoder = softphone.codec.createDecoder(); | ||
this.sipMessage = sipMessage; | ||
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1]; | ||
this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10); | ||
} | ||
Object.defineProperty(CallSession.prototype, "remoteKey", { | ||
set: function (key) { | ||
var localKeyBuffer = Buffer.from(localKey, "base64"); | ||
var remoteKeyBuffer = Buffer.from(key, "base64"); | ||
this.srtpSession = new SrtpSession({ | ||
profile: 0x0001, | ||
keys: { | ||
localMasterKey: localKeyBuffer.subarray(0, 16), | ||
localMasterSalt: localKeyBuffer.subarray(16, 30), | ||
remoteMasterKey: remoteKeyBuffer.subarray(0, 16), | ||
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30), | ||
}, | ||
}); | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Object.defineProperty(CallSession.prototype, "callId", { | ||
get: function () { | ||
return this.sipMessage.headers["Call-ID"]; | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
CallSession.prototype.send = function (data) { | ||
set remoteKey(key) { | ||
const localKeyBuffer = Buffer.from(localKey, "base64"); | ||
const remoteKeyBuffer = Buffer.from(key, "base64"); | ||
this.srtpSession = new SrtpSession({ | ||
profile: 0x0001, | ||
keys: { | ||
localMasterKey: localKeyBuffer.subarray(0, 16), | ||
localMasterSalt: localKeyBuffer.subarray(16, 30), | ||
remoteMasterKey: remoteKeyBuffer.subarray(0, 16), | ||
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30), | ||
}, | ||
}); | ||
} | ||
get callId() { | ||
return this.sipMessage.headers["Call-ID"]; | ||
} | ||
send(data) { | ||
this.socket.send(data, this.remotePort, this.remoteIP); | ||
}; | ||
CallSession.prototype.hangup = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var requestMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
requestMessage = new RequestMessage("BYE sip:".concat(this.softphone.sipInfo.domain, " SIP/2.0"), { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: "SIP/2.0/TLS ".concat(this.softphone.fakeDomain, ";branch=").concat(branch()), | ||
}); | ||
return [4 /*yield*/, this.softphone.send(requestMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
} | ||
async hangup() { | ||
const requestMessage = new RequestMessage(`BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${branch()}`, | ||
}); | ||
}; | ||
CallSession.prototype.sendDTMF = function (char) { | ||
var timestamp = Math.floor(Date.now() / 1000); | ||
var sequenceNumber = timestamp % 65536; | ||
var rtpHeader = new RtpHeader({ | ||
await this.softphone.send(requestMessage); | ||
} | ||
sendDTMF(char) { | ||
const timestamp = Math.floor(Date.now() / 1000); | ||
let sequenceNumber = timestamp % 65536; | ||
const rtpHeader = new RtpHeader({ | ||
version: 2, | ||
@@ -136,4 +74,4 @@ padding: false, | ||
payloadType: 101, | ||
sequenceNumber: sequenceNumber, | ||
timestamp: timestamp, | ||
sequenceNumber, | ||
timestamp, | ||
ssrc: randomInt(), | ||
@@ -146,18 +84,17 @@ csrcLength: 0, | ||
}); | ||
for (var _i = 0, _a = DTMF.charToPayloads(char); _i < _a.length; _i++) { | ||
var payload = _a[_i]; | ||
for (const payload of DTMF.charToPayloads(char)) { | ||
rtpHeader.sequenceNumber = sequenceNumber++; | ||
var rtpPacket = new RtpPacket(rtpHeader, payload); | ||
const rtpPacket = new RtpPacket(rtpHeader, payload); | ||
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header)); | ||
} | ||
}; | ||
} | ||
// buffer is the content of a audio file, it is supposed to be uncompressed PCM data | ||
// The audio should be playable by command: play -t raw -b 16 -r 16000 -e signed-integer test.wav | ||
CallSession.prototype.streamAudio = function (input) { | ||
var streamer = new Streamer(this, input); | ||
streamAudio(input) { | ||
const streamer = new Streamer(this, input); | ||
streamer.start(); | ||
return streamer; | ||
}; | ||
} | ||
// send a single rtp packet | ||
CallSession.prototype.sendPacket = function (rtpPacket) { | ||
sendPacket(rtpPacket) { | ||
if (this.disposed) { | ||
@@ -167,17 +104,16 @@ return; | ||
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header)); | ||
}; | ||
CallSession.prototype.startLocalServices = function () { | ||
var _this = this; | ||
} | ||
startLocalServices() { | ||
this.socket = dgram.createSocket("udp4"); | ||
this.socket.on("message", function (message) { | ||
var rtpPacket = RtpPacket.deSerialize(_this.srtpSession.decrypt(message)); | ||
_this.emit("rtpPacket", rtpPacket); | ||
this.socket.on("message", (message) => { | ||
const rtpPacket = RtpPacket.deSerialize(this.srtpSession.decrypt(message)); | ||
this.emit("rtpPacket", rtpPacket); | ||
if (rtpPacket.header.payloadType === 101) { | ||
_this.emit("dtmfPacket", rtpPacket); | ||
var char = DTMF.payloadToChar(rtpPacket.payload); | ||
this.emit("dtmfPacket", rtpPacket); | ||
const char = DTMF.payloadToChar(rtpPacket.payload); | ||
if (char) { | ||
_this.emit("dtmf", char); | ||
this.emit("dtmf", char); | ||
} | ||
} | ||
else if (rtpPacket.header.payloadType === _this.softphone.codec.id) { | ||
else if (rtpPacket.header.payloadType === this.softphone.codec.id) { | ||
if (rtpPacket.payload.length === 4 && | ||
@@ -195,6 +131,6 @@ rtpPacket.payload[0] >= 0x00 && | ||
try { | ||
rtpPacket.payload = _this.decoder.decode(rtpPacket.payload); | ||
_this.emit("audioPacket", rtpPacket); | ||
rtpPacket.payload = this.decoder.decode(rtpPacket.payload); | ||
this.emit("audioPacket", rtpPacket); | ||
} | ||
catch (_a) { | ||
catch { | ||
console.error("opus decode failed", rtpPacket); | ||
@@ -209,29 +145,27 @@ } | ||
this.send("hello"); | ||
var byeHandler = function (inboundMessage) { | ||
if (inboundMessage.headers["Call-ID"] !== _this.callId) { | ||
const byeHandler = (inboundMessage) => { | ||
if (inboundMessage.headers["Call-ID"] !== this.callId) { | ||
return; | ||
} | ||
if (inboundMessage.headers.CSeq.endsWith(" BYE")) { | ||
_this.softphone.off("message", byeHandler); | ||
_this.dispose(); | ||
this.softphone.off("message", byeHandler); | ||
this.dispose(); | ||
} | ||
}; | ||
this.softphone.on("message", byeHandler); | ||
}; | ||
CallSession.prototype.dispose = function () { | ||
var _a, _b; | ||
} | ||
dispose() { | ||
this.disposed = true; | ||
this.emit("disposed"); | ||
this.removeAllListeners(); | ||
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.removeAllListeners(); | ||
(_b = this.socket) === null || _b === void 0 ? void 0 : _b.close(); | ||
}; | ||
CallSession.prototype.transfer = function (transferTo) { | ||
var _this = this; | ||
var requestMessage = new RequestMessage("REFER sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.sipInfo.outboundProxy, ";transport=tls SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";rport;branch=").concat(branch(), ";alias"), | ||
this.socket?.removeAllListeners(); | ||
this.socket?.close(); | ||
} | ||
transfer(transferTo) { | ||
const requestMessage = new RequestMessage(`REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${branch()};alias`, | ||
"Max-Forwards": 70, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Contact: "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.client.localAddress, ":").concat(this.softphone.client.localPort, ";transport=TLS;ob>"), | ||
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`, | ||
"Call-ID": this.callId, | ||
@@ -243,21 +177,20 @@ Event: "refer", | ||
"Allow-Events": "presence, message-summary, refer", | ||
"Refer-To": "sip:".concat(transferTo, "@").concat(this.softphone.sipInfo.domain), | ||
"Referred-By": "<sip:".concat(this.softphone.sipInfo.username, "@").concat(this.softphone.sipInfo.domain, ">"), | ||
"Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`, | ||
"Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`, | ||
}); | ||
this.softphone.send(requestMessage); | ||
// reply to those NOTIFY messages | ||
var notifyHandler = function (inboundMessage) { | ||
const notifyHandler = (inboundMessage) => { | ||
if (!inboundMessage.subject.startsWith("NOTIFY ")) { | ||
return; | ||
} | ||
var responseMessage = new ResponseMessage(inboundMessage, 200); | ||
_this.softphone.send(responseMessage); | ||
const responseMessage = new ResponseMessage(inboundMessage, 200); | ||
this.softphone.send(responseMessage); | ||
if (inboundMessage.body.trim() === "SIP/2.0 200 OK") { | ||
_this.softphone.off("message", notifyHandler); | ||
this.softphone.off("message", notifyHandler); | ||
} | ||
}; | ||
this.softphone.on("message", notifyHandler); | ||
}; | ||
return CallSession; | ||
}(EventEmitter)); | ||
} | ||
} | ||
export default CallSession; |
@@ -1,117 +0,51 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
import CallSession from "./index"; | ||
import { RequestMessage } from "../sip-message/index"; | ||
import { extractAddress, withoutTag } from "../utils"; | ||
var OutboundCallSession = /** @class */ (function (_super) { | ||
__extends(OutboundCallSession, _super); | ||
function OutboundCallSession(softphone, answerMessage) { | ||
var _this = _super.call(this, softphone, answerMessage) || this; | ||
_this.localPeer = answerMessage.headers.From; | ||
_this.remotePeer = answerMessage.headers.To; | ||
_this.remoteKey = answerMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
_this.init(); | ||
return _this; | ||
class OutboundCallSession extends CallSession { | ||
constructor(softphone, answerMessage) { | ||
super(softphone, answerMessage); | ||
this.localPeer = answerMessage.headers.From; | ||
this.remotePeer = answerMessage.headers.To; | ||
this.remoteKey = answerMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1]; | ||
this.init(); | ||
} | ||
OutboundCallSession.prototype.init = function () { | ||
var _this = this; | ||
init() { | ||
// wait for user to answer the call | ||
var answerHandler = function (message) { | ||
if (message.headers.CSeq !== _this.sipMessage.headers.CSeq) { | ||
const answerHandler = (message) => { | ||
if (message.headers.CSeq !== this.sipMessage.headers.CSeq) { | ||
return; | ||
} | ||
if (message.subject.startsWith("SIP/2.0 486")) { | ||
_this.softphone.off("message", answerHandler); | ||
_this.emit("busy"); | ||
_this.dispose(); | ||
this.softphone.off("message", answerHandler); | ||
this.emit("busy"); | ||
this.dispose(); | ||
return; | ||
} | ||
if (message.subject.startsWith("SIP/2.0 200")) { | ||
_this.softphone.off("message", answerHandler); | ||
_this.emit("answered"); | ||
var ackMessage = new RequestMessage("ACK ".concat(extractAddress(_this.remotePeer), " SIP/2.0"), { | ||
"Call-ID": _this.callId, | ||
From: _this.localPeer, | ||
To: _this.remotePeer, | ||
Via: _this.sipMessage.headers.Via, | ||
CSeq: _this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"), | ||
this.softphone.off("message", answerHandler); | ||
this.emit("answered"); | ||
const ackMessage = new RequestMessage(`ACK ${extractAddress(this.remotePeer)} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: this.remotePeer, | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"), | ||
}); | ||
_this.softphone.send(ackMessage); | ||
this.softphone.send(ackMessage); | ||
} | ||
}; | ||
this.softphone.on("message", answerHandler); | ||
this.once("answered", function () { return _this.startLocalServices(); }); | ||
}; | ||
OutboundCallSession.prototype.cancel = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var requestMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
requestMessage = new RequestMessage("CANCEL ".concat(extractAddress(this.remotePeer), " SIP/2.0"), { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: withoutTag(this.remotePeer), | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"), | ||
}); | ||
return [4 /*yield*/, this.softphone.send(requestMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
this.once("answered", () => this.startLocalServices()); | ||
} | ||
async cancel() { | ||
const requestMessage = new RequestMessage(`CANCEL ${extractAddress(this.remotePeer)} SIP/2.0`, { | ||
"Call-ID": this.callId, | ||
From: this.localPeer, | ||
To: withoutTag(this.remotePeer), | ||
Via: this.sipMessage.headers.Via, | ||
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"), | ||
}); | ||
}; | ||
return OutboundCallSession; | ||
}(CallSession)); | ||
await this.softphone.send(requestMessage); | ||
} | ||
} | ||
export default OutboundCallSession; |
@@ -1,56 +0,37 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
import EventEmitter from "node:events"; | ||
import { Buffer } from "node:buffer"; | ||
import { RtpHeader, RtpPacket } from "werift-rtp"; | ||
var Streamer = /** @class */ (function (_super) { | ||
__extends(Streamer, _super); | ||
function Streamer(callSesstion, buffer) { | ||
var _this = _super.call(this) || this; | ||
_this.paused = false; | ||
_this.callSession = callSesstion; | ||
_this.buffer = buffer; | ||
_this.originalBuffer = buffer; | ||
return _this; | ||
class Streamer extends EventEmitter { | ||
paused = false; | ||
callSession; | ||
buffer; | ||
originalBuffer; | ||
constructor(callSesstion, buffer) { | ||
super(); | ||
this.callSession = callSesstion; | ||
this.buffer = buffer; | ||
this.originalBuffer = buffer; | ||
} | ||
Streamer.prototype.start = function () { | ||
start() { | ||
this.buffer = this.originalBuffer; | ||
this.paused = false; | ||
this.sendPacket(); | ||
}; | ||
Streamer.prototype.stop = function () { | ||
} | ||
stop() { | ||
this.buffer = Buffer.alloc(0); | ||
}; | ||
Streamer.prototype.pause = function () { | ||
} | ||
pause() { | ||
this.paused = true; | ||
}; | ||
Streamer.prototype.resume = function () { | ||
} | ||
resume() { | ||
this.paused = false; | ||
this.sendPacket(); | ||
}; | ||
Object.defineProperty(Streamer.prototype, "finished", { | ||
get: function () { | ||
return this.buffer.length < this.callSession.softphone.codec.packetSize; | ||
}, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Streamer.prototype.sendPacket = function () { | ||
var _this = this; | ||
} | ||
get finished() { | ||
return this.buffer.length < this.callSession.softphone.codec.packetSize; | ||
} | ||
sendPacket() { | ||
if (!this.callSession.disposed && !this.paused && !this.finished) { | ||
var temp = this.callSession.encoder.encode(this.buffer.subarray(0, this.callSession.softphone.codec.packetSize)); | ||
var rtpPacket = new RtpPacket(new RtpHeader({ | ||
const temp = this.callSession.encoder.encode(this.buffer.subarray(0, this.callSession.softphone.codec.packetSize)); | ||
const rtpPacket = new RtpPacket(new RtpHeader({ | ||
version: 2, | ||
@@ -84,8 +65,7 @@ padding: false, | ||
else { | ||
setTimeout(function () { return _this.sendPacket(); }, 20); | ||
setTimeout(() => this.sendPacket(), 20); | ||
} | ||
} | ||
}; | ||
return Streamer; | ||
}(EventEmitter)); | ||
} | ||
} | ||
export default Streamer; |
import { Buffer } from "node:buffer"; | ||
import { Decoder, Encoder } from "@evan/opus"; | ||
var Codec = /** @class */ (function () { | ||
function Codec(name) { | ||
class Codec { | ||
id; | ||
name; | ||
packetSize; | ||
timestampInterval; | ||
createEncoder; | ||
createDecoder; | ||
constructor(name) { | ||
this.name = name; | ||
switch (name) { | ||
case "OPUS/16000": { | ||
this.createEncoder = function () { | ||
var encoder = new Encoder({ channels: 1, sample_rate: 16000 }); | ||
return { encode: function (pcm) { return Buffer.from(encoder.encode(pcm)); } }; | ||
this.createEncoder = () => { | ||
const encoder = new Encoder({ channels: 1, sample_rate: 16000 }); | ||
return { encode: (pcm) => Buffer.from(encoder.encode(pcm)) }; | ||
}; | ||
this.createDecoder = function () { | ||
var decoder = new Decoder({ channels: 1, sample_rate: 16000 }); | ||
this.createDecoder = () => { | ||
const decoder = new Decoder({ channels: 1, sample_rate: 16000 }); | ||
return { | ||
decode: function (opus) { return Buffer.from(decoder.decode(opus)); }, | ||
decode: (opus) => Buffer.from(decoder.decode(opus)), | ||
}; | ||
@@ -24,10 +30,10 @@ }; | ||
case "OPUS/48000/2": { | ||
this.createEncoder = function () { | ||
var encoder = new Encoder({ channels: 2, sample_rate: 48000 }); | ||
return { encode: function (pcm) { return Buffer.from(encoder.encode(pcm)); } }; | ||
this.createEncoder = () => { | ||
const encoder = new Encoder({ channels: 2, sample_rate: 48000 }); | ||
return { encode: (pcm) => Buffer.from(encoder.encode(pcm)) }; | ||
}; | ||
this.createDecoder = function () { | ||
var decoder = new Decoder({ channels: 2, sample_rate: 48000 }); | ||
this.createDecoder = () => { | ||
const decoder = new Decoder({ channels: 2, sample_rate: 48000 }); | ||
return { | ||
decode: function (opus) { return Buffer.from(decoder.decode(opus)); }, | ||
decode: (opus) => Buffer.from(decoder.decode(opus)), | ||
}; | ||
@@ -41,7 +47,7 @@ }; | ||
case "PCMU/8000": { | ||
this.createEncoder = function () { | ||
return { encode: function (pcm) { return pcm; } }; | ||
this.createEncoder = () => { | ||
return { encode: (pcm) => pcm }; | ||
}; | ||
this.createDecoder = function () { | ||
return { decode: function (audio) { return audio; } }; | ||
this.createDecoder = () => { | ||
return { decode: (audio) => audio }; | ||
}; | ||
@@ -54,8 +60,7 @@ this.id = 0; | ||
default: { | ||
throw new Error("unsupported codec: ".concat(name)); | ||
throw new Error(`unsupported codec: ${name}`); | ||
} | ||
} | ||
} | ||
return Codec; | ||
}()); | ||
} | ||
export default Codec; |
import { Buffer } from "node:buffer"; | ||
var DTMF = /** @class */ (function () { | ||
function DTMF() { | ||
} | ||
DTMF.phoneChars = [ | ||
class DTMF { | ||
static phoneChars = [ | ||
"0", | ||
@@ -19,3 +17,3 @@ "1", | ||
]; | ||
DTMF.payloads = [ | ||
static payloads = [ | ||
0x00060000, | ||
@@ -28,10 +26,10 @@ 0x000600a0, | ||
]; | ||
DTMF.charToPayloads = function (char) { | ||
var index = DTMF.phoneChars.indexOf(char[0]); | ||
static charToPayloads = (char) => { | ||
const index = DTMF.phoneChars.indexOf(char[0]); | ||
if (index === -1) { | ||
throw new Error("invalid phone char"); | ||
} | ||
return DTMF.payloads.map(function (payload) { | ||
var temp = payload + index * 0x01000000; | ||
var buffer = Buffer.alloc(4); | ||
return DTMF.payloads.map((payload) => { | ||
const temp = payload + index * 0x01000000; | ||
const buffer = Buffer.alloc(4); | ||
buffer.writeIntBE(temp, 0, 4); | ||
@@ -41,9 +39,8 @@ return buffer; | ||
}; | ||
DTMF.payloadToChar = function (payload) { | ||
var intBE = payload.readIntBE(0, 4); | ||
var index = (intBE - 0x00060000) / 0x01000000; | ||
static payloadToChar = (payload) => { | ||
const intBE = payload.readIntBE(0, 4); | ||
const index = (intBE - 0x00060000) / 0x01000000; | ||
return DTMF.phoneChars[index]; | ||
}; | ||
return DTMF; | ||
}()); | ||
} | ||
export default DTMF; |
@@ -1,52 +0,1 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); | ||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
import EventEmitter from "node:events"; | ||
@@ -60,28 +9,29 @@ import tls from "node:tls"; | ||
import Codec from "./codec"; | ||
var Softphone = /** @class */ (function (_super) { | ||
__extends(Softphone, _super); | ||
function Softphone(sipInfo) { | ||
var _this = _super.call(this) || this; | ||
_this.fakeDomain = uuid() + ".invalid"; | ||
_this.fakeEmail = uuid() + "@" + _this.fakeDomain; | ||
_this.connected = false; | ||
_this.instanceId = uuid(); | ||
_this.registerCallId = uuid(); | ||
class Softphone extends EventEmitter { | ||
sipInfo; | ||
client; | ||
codec; | ||
fakeDomain = uuid() + ".invalid"; | ||
fakeEmail = uuid() + "@" + this.fakeDomain; | ||
intervalHandle; | ||
connected = false; | ||
constructor(sipInfo) { | ||
super(); | ||
if (sipInfo.codec === undefined) { | ||
sipInfo.codec = "OPUS/16000"; | ||
} | ||
_this.codec = new Codec(sipInfo.codec); | ||
_this.sipInfo = sipInfo; | ||
if (_this.sipInfo.domain === undefined) { | ||
_this.sipInfo.domain = "sip.ringcentral.com"; | ||
this.codec = new Codec(sipInfo.codec); | ||
this.sipInfo = sipInfo; | ||
if (this.sipInfo.domain === undefined) { | ||
this.sipInfo.domain = "sip.ringcentral.com"; | ||
} | ||
if (_this.sipInfo.outboundProxy === undefined) { | ||
_this.sipInfo.outboundProxy = "sip10.ringcentral.com:5096"; | ||
if (this.sipInfo.outboundProxy === undefined) { | ||
this.sipInfo.outboundProxy = "sip10.ringcentral.com:5096"; | ||
} | ||
var tokens = _this.sipInfo.outboundProxy.split(":"); | ||
_this.client = tls.connect({ host: tokens[0], port: parseInt(tokens[1], 10) }, function () { | ||
_this.connected = true; | ||
const tokens = this.sipInfo.outboundProxy.split(":"); | ||
this.client = tls.connect({ host: tokens[0], port: parseInt(tokens[1], 10) }, () => { | ||
this.connected = true; | ||
}); | ||
var cache = ""; | ||
_this.client.on("data", function (data) { | ||
let cache = ""; | ||
this.client.on("data", (data) => { | ||
cache += data.toString("utf-8"); | ||
@@ -92,7 +42,7 @@ if (!cache.endsWith("\r\n")) { | ||
// received two empty body messages | ||
var tempMessages = cache | ||
const tempMessages = cache | ||
.split("\r\nContent-Length: 0\r\n\r\n") | ||
.filter(function (message) { return message.trim() !== ""; }); | ||
.filter((message) => message.trim() !== ""); | ||
cache = ""; | ||
for (var i = 0; i < tempMessages.length; i++) { | ||
for (let i = 0; i < tempMessages.length; i++) { | ||
if (!tempMessages[i].includes("Content-Length: ")) { | ||
@@ -102,92 +52,68 @@ tempMessages[i] = tempMessages[i] + "\r\nContent-Length: 0"; | ||
} | ||
for (var _i = 0, tempMessages_1 = tempMessages; _i < tempMessages_1.length; _i++) { | ||
var message = tempMessages_1[_i]; | ||
_this.emit("message", InboundMessage.fromString(message)); | ||
for (const message of tempMessages) { | ||
this.emit("message", InboundMessage.fromString(message)); | ||
} | ||
}); | ||
return _this; | ||
} | ||
Softphone.prototype.register = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var sipRegister; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!!this.connected) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, waitFor({ interval: 100, condition: function () { return _this.connected; } })]; | ||
case 1: | ||
_a.sent(); | ||
_a.label = 2; | ||
case 2: | ||
sipRegister = function () { return __awaiter(_this, void 0, void 0, function () { | ||
var fromTag, requestMessage, inboundMessage, wwwAuth, nonce, newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
fromTag = uuid(); | ||
requestMessage = new RequestMessage("REGISTER sip:".concat(this.sipInfo.domain, " SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.client.localAddress, ":").concat(this.client.localPort, ";rport;branch=").concat(branch(), ";alias"), | ||
Route: "<sip:".concat(this.sipInfo.outboundProxy, ";transport=tls;lr>"), | ||
"Max-Forwards": "70", | ||
From: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">;tag=").concat(fromTag), | ||
To: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">"), | ||
"Call-ID": this.registerCallId, | ||
Supported: "outbound, path", | ||
Contact: "<sip:".concat(this.sipInfo.username, "@").concat(this.client.localAddress, ":").concat(this.client.localPort, ";transport=TLS;ob>;reg-id=1;+sip.instance=\"<urn:uuid:").concat(this.instanceId, ">\""), | ||
Expires: 300, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
}); | ||
return [4 /*yield*/, this.send(requestMessage, true)]; | ||
case 1: | ||
inboundMessage = _a.sent(); | ||
if (inboundMessage.subject.startsWith("SIP/2.0 200 ")) { | ||
// sometimes the server will return 200 OK directly | ||
return [2 /*return*/]; | ||
} | ||
wwwAuth = inboundMessage.headers["Www-Authenticate"] || | ||
inboundMessage.headers["WWW-Authenticate"]; | ||
nonce = wwwAuth.match(/, nonce="(.+?)"/)[1]; | ||
newMessage = requestMessage.fork(); | ||
newMessage.headers.Authorization = generateAuthorization(this.sipInfo, nonce, "REGISTER"); | ||
this.send(newMessage); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }; | ||
sipRegister(); | ||
this.intervalHandle = setInterval(function () { | ||
sipRegister(); | ||
}, 3 * 60 * 1000); | ||
this.on("message", function (inboundMessage) { | ||
if (!inboundMessage.subject.startsWith("INVITE sip:")) { | ||
return; | ||
} | ||
var outboundMessage = new OutboundMessage("SIP/2.0 100 Trying", { | ||
Via: inboundMessage.headers.Via, | ||
"Call-ID": inboundMessage.headers["Call-ID"], | ||
From: inboundMessage.headers.From, | ||
To: inboundMessage.headers.To, | ||
CSeq: inboundMessage.headers.CSeq, | ||
"Content-Length": "0", | ||
}); | ||
_this.send(outboundMessage); | ||
_this.emit("invite", inboundMessage); | ||
}); | ||
return [2 /*return*/]; | ||
} | ||
instanceId = uuid(); | ||
registerCallId = uuid(); | ||
async register() { | ||
if (!this.connected) { | ||
await waitFor({ interval: 100, condition: () => this.connected }); | ||
} | ||
const sipRegister = async () => { | ||
const fromTag = uuid(); | ||
const requestMessage = new RequestMessage(`REGISTER sip:${this.sipInfo.domain} SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${branch()};alias`, | ||
Route: `<sip:${this.sipInfo.outboundProxy};transport=tls;lr>`, | ||
"Max-Forwards": "70", | ||
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${fromTag}`, | ||
To: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>`, | ||
"Call-ID": this.registerCallId, | ||
Supported: "outbound, path", | ||
Contact: `<sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>;reg-id=1;+sip.instance="<urn:uuid:${this.instanceId}>"`, | ||
Expires: 300, | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
}); | ||
const inboundMessage = await this.send(requestMessage, true); | ||
if (inboundMessage.subject.startsWith("SIP/2.0 200 ")) { | ||
// sometimes the server will return 200 OK directly | ||
return; | ||
} | ||
const wwwAuth = inboundMessage.headers["Www-Authenticate"] || | ||
inboundMessage.headers["WWW-Authenticate"]; | ||
const nonce = wwwAuth.match(/, nonce="(.+?)"/)[1]; | ||
const newMessage = requestMessage.fork(); | ||
newMessage.headers.Authorization = generateAuthorization(this.sipInfo, nonce, "REGISTER"); | ||
this.send(newMessage); | ||
}; | ||
sipRegister(); | ||
this.intervalHandle = setInterval(() => { | ||
sipRegister(); | ||
}, 3 * 60 * 1000); | ||
this.on("message", (inboundMessage) => { | ||
if (!inboundMessage.subject.startsWith("INVITE sip:")) { | ||
return; | ||
} | ||
const outboundMessage = new OutboundMessage("SIP/2.0 100 Trying", { | ||
Via: inboundMessage.headers.Via, | ||
"Call-ID": inboundMessage.headers["Call-ID"], | ||
From: inboundMessage.headers.From, | ||
To: inboundMessage.headers.To, | ||
CSeq: inboundMessage.headers.CSeq, | ||
"Content-Length": "0", | ||
}); | ||
this.send(outboundMessage); | ||
this.emit("invite", inboundMessage); | ||
}); | ||
}; | ||
Softphone.prototype.enableDebugMode = function () { | ||
this.on("message", function (message) { | ||
return console.log("Receiving...(".concat(new Date(), ")\n") + message.toString()); | ||
}); | ||
var tlsWrite = this.client.write.bind(this.client); | ||
this.client.write = function (message) { | ||
console.log("Sending...(".concat(new Date(), ")\n") + message); | ||
} | ||
enableDebugMode() { | ||
this.on("message", (message) => console.log(`Receiving...(${new Date()})\n` + message.toString())); | ||
const tlsWrite = this.client.write.bind(this.client); | ||
this.client.write = (message) => { | ||
console.log(`Sending...(${new Date()})\n` + message); | ||
return tlsWrite(message); | ||
}; | ||
}; | ||
Softphone.prototype.revoke = function () { | ||
} | ||
revoke() { | ||
clearInterval(this.intervalHandle); | ||
@@ -197,14 +123,12 @@ this.removeAllListeners(); | ||
this.client.destroy(); | ||
}; | ||
Softphone.prototype.send = function (message, waitForReply) { | ||
var _this = this; | ||
if (waitForReply === void 0) { waitForReply = false; } | ||
} | ||
send(message, waitForReply = false) { | ||
this.client.write(message.toString()); | ||
if (!waitForReply) { | ||
return new Promise(function (resolve) { | ||
return new Promise((resolve) => { | ||
resolve(undefined); | ||
}); | ||
} | ||
return new Promise(function (resolve) { | ||
var messageListerner = function (inboundMessage) { | ||
return new Promise((resolve) => { | ||
const messageListerner = (inboundMessage) => { | ||
if (inboundMessage.headers.CSeq !== message.headers.CSeq) { | ||
@@ -216,77 +140,55 @@ return; | ||
} | ||
_this.off("message", messageListerner); | ||
this.off("message", messageListerner); | ||
resolve(inboundMessage); | ||
}; | ||
_this.on("message", messageListerner); | ||
this.on("message", messageListerner); | ||
}); | ||
}; | ||
Softphone.prototype.answer = function (inviteMessage) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var inboundCallSession; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
inboundCallSession = new InboundCallSession(this, inviteMessage); | ||
return [4 /*yield*/, inboundCallSession.answer()]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/, inboundCallSession]; | ||
} | ||
}); | ||
}); | ||
}; | ||
} | ||
async answer(inviteMessage) { | ||
const inboundCallSession = new InboundCallSession(this, inviteMessage); | ||
await inboundCallSession.answer(); | ||
return inboundCallSession; | ||
} | ||
// decline an inbound call | ||
Softphone.prototype.decline = function (inviteMessage) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var newMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
newMessage = new ResponseMessage(inviteMessage, 603); | ||
return [4 /*yield*/, this.send(newMessage)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
Softphone.prototype.call = function (callee) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var offerSDP, inviteMessage, inboundMessage, proxyAuthenticate, nonce, newMessage, progressMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
offerSDP = "\nv=0\no=- ".concat(Date.now(), " 0 IN IP4 ").concat(this.client.localAddress, "\ns=rc-softphone-ts\nc=IN IP4 ").concat(this.client.localAddress, "\nt=0 0\nm=audio ").concat(randomInt(), " RTP/SAVP ").concat(this.codec.id, " 101\na=rtpmap:").concat(this.codec.id, " ").concat(this.codec.name, "\na=rtpmap:101 telephone-event/8000\na=fmtp:101 0-15\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:").concat(localKey, "\n ").trim(); | ||
inviteMessage = new RequestMessage("INVITE sip:".concat(callee, " SIP/2.0"), { | ||
Via: "SIP/2.0/TLS ".concat(this.client.localAddress, ":").concat(this.client.localPort, ";rport;branch=").concat(branch(), ";alias"), | ||
"Max-Forwards": 70, | ||
From: "<sip:".concat(this.sipInfo.username, "@").concat(this.sipInfo.domain, ">;tag=").concat(uuid()), | ||
To: "<sip:".concat(callee, "@sip.ringcentral.com>"), | ||
Contact: " <sip:".concat(this.sipInfo.username, "@").concat(this.client.localAddress, ":").concat(this.client.localPort, ";transport=TLS;ob>"), | ||
"Call-ID": uuid(), | ||
Route: "<sip:".concat(this.sipInfo.outboundProxy, ";transport=tls;lr>"), | ||
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS", | ||
Supported: "replaces, 100rel, timer, norefersub", | ||
"Session-Expires": 1800, | ||
"Min-SE": 90, | ||
"Content-Type": "application/sdp", | ||
}, offerSDP); | ||
return [4 /*yield*/, this.send(inviteMessage, true)]; | ||
case 1: | ||
inboundMessage = _a.sent(); | ||
proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"]; | ||
nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1]; | ||
newMessage = inviteMessage.fork(); | ||
newMessage.headers["Proxy-Authorization"] = generateAuthorization(this.sipInfo, nonce, "INVITE"); | ||
return [4 /*yield*/, this.send(newMessage, true)]; | ||
case 2: | ||
progressMessage = _a.sent(); | ||
return [2 /*return*/, new OutboundCallSession(this, progressMessage)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return Softphone; | ||
}(EventEmitter)); | ||
async decline(inviteMessage) { | ||
const newMessage = new ResponseMessage(inviteMessage, 603); | ||
await this.send(newMessage); | ||
} | ||
async call(callee) { | ||
const offerSDP = ` | ||
v=0 | ||
o=- ${Date.now()} 0 IN IP4 ${this.client.localAddress} | ||
s=rc-softphone-ts | ||
c=IN IP4 ${this.client.localAddress} | ||
t=0 0 | ||
m=audio ${randomInt()} RTP/SAVP ${this.codec.id} 101 | ||
a=rtpmap:${this.codec.id} ${this.codec.name} | ||
a=rtpmap:101 telephone-event/8000 | ||
a=fmtp:101 0-15 | ||
a=sendrecv | ||
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey} | ||
`.trim(); | ||
const inviteMessage = new RequestMessage(`INVITE sip:${callee} SIP/2.0`, { | ||
Via: `SIP/2.0/TLS ${this.client.localAddress}:${this.client.localPort};rport;branch=${branch()};alias`, | ||
"Max-Forwards": 70, | ||
From: `<sip:${this.sipInfo.username}@${this.sipInfo.domain}>;tag=${uuid()}`, | ||
To: `<sip:${callee}@sip.ringcentral.com>`, | ||
Contact: ` <sip:${this.sipInfo.username}@${this.client.localAddress}:${this.client.localPort};transport=TLS;ob>`, | ||
"Call-ID": uuid(), | ||
Route: `<sip:${this.sipInfo.outboundProxy};transport=tls;lr>`, | ||
Allow: `PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS`, | ||
Supported: `replaces, 100rel, timer, norefersub`, | ||
"Session-Expires": 1800, | ||
"Min-SE": 90, | ||
"Content-Type": "application/sdp", | ||
}, offerSDP); | ||
const inboundMessage = await this.send(inviteMessage, true); | ||
const proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"]; | ||
const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1]; | ||
const newMessage = inviteMessage.fork(); | ||
newMessage.headers["Proxy-Authorization"] = generateAuthorization(this.sipInfo, nonce, "INVITE"); | ||
const progressMessage = await this.send(newMessage, true); | ||
return new OutboundCallSession(this, progressMessage); | ||
} | ||
} | ||
export default Softphone; |
@@ -1,30 +0,11 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
import { uuid } from "../../utils"; | ||
import SipMessage from "../sip-message"; | ||
var InboundMessage = /** @class */ (function (_super) { | ||
__extends(InboundMessage, _super); | ||
function InboundMessage() { | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
InboundMessage.fromString = function (str) { | ||
var sipMessage = new SipMessage(); | ||
var _a = str.split("\r\n\r\n"), init = _a[0], body = _a.slice(1); | ||
class InboundMessage extends SipMessage { | ||
static fromString(str) { | ||
const sipMessage = new SipMessage(); | ||
const [init, ...body] = str.split("\r\n\r\n"); | ||
sipMessage.body = body.join("\r\n\r\n"); | ||
var _b = init.split("\r\n"), subject = _b[0], headers = _b.slice(1); | ||
const [subject, ...headers] = init.split("\r\n"); | ||
sipMessage.subject = subject; | ||
sipMessage.headers = Object.fromEntries(headers.map(function (line) { return line.split(": "); })); | ||
sipMessage.headers = Object.fromEntries(headers.map((line) => line.split(": "))); | ||
if (sipMessage.headers.To && !sipMessage.headers.To.includes(";tag=")) { | ||
@@ -34,5 +15,4 @@ sipMessage.headers.To += ";tag=" + uuid(); // generate local tag | ||
return sipMessage; | ||
}; | ||
return InboundMessage; | ||
}(SipMessage)); | ||
} | ||
} | ||
export default InboundMessage; |
@@ -1,30 +0,9 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
import SipMessage from "../sip-message"; | ||
var OutboundMessage = /** @class */ (function (_super) { | ||
__extends(OutboundMessage, _super); | ||
function OutboundMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, subject, headers, body) || this; | ||
_this.headers["Content-Length"] = _this.body.length.toString(); | ||
_this.headers["User-Agent"] = "ringcentral-softphone-ts"; | ||
return _this; | ||
class OutboundMessage extends SipMessage { | ||
constructor(subject = "", headers = {}, body = "") { | ||
super(subject, headers, body); | ||
this.headers["Content-Length"] = this.body.length.toString(); | ||
this.headers["User-Agent"] = "ringcentral-softphone-ts"; | ||
} | ||
return OutboundMessage; | ||
}(SipMessage)); | ||
} | ||
export default OutboundMessage; |
@@ -1,55 +0,23 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
import OutboundMessage from "./index"; | ||
import { branch } from "../../utils"; | ||
var cseq = Math.floor(Math.random() * 10000); | ||
var RequestMessage = /** @class */ (function (_super) { | ||
__extends(RequestMessage, _super); | ||
function RequestMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, subject, headers, body) || this; | ||
if (_this.headers.CSeq === undefined) { | ||
_this.newCseq(); | ||
let cseq = Math.floor(Math.random() * 10000); | ||
class RequestMessage extends OutboundMessage { | ||
constructor(subject = "", headers = {}, body = "") { | ||
super(subject, headers, body); | ||
if (this.headers.CSeq === undefined) { | ||
this.newCseq(); | ||
} | ||
return _this; | ||
} | ||
RequestMessage.prototype.newCseq = function () { | ||
this.headers.CSeq = "".concat(++cseq, " ").concat(this.subject.split(" ")[0]); | ||
}; | ||
RequestMessage.prototype.fork = function () { | ||
var newMessage = new RequestMessage(this.subject, __assign({}, this.headers), this.body); | ||
newCseq() { | ||
this.headers.CSeq = `${++cseq} ${this.subject.split(" ")[0]}`; | ||
} | ||
fork() { | ||
const newMessage = new RequestMessage(this.subject, { ...this.headers }, this.body); | ||
newMessage.newCseq(); | ||
if (newMessage.headers.Via) { | ||
newMessage.headers.Via = newMessage.headers.Via.replace(/;branch=.+?$/, ";branch=".concat(branch())); | ||
newMessage.headers.Via = newMessage.headers.Via.replace(/;branch=.+?$/, `;branch=${branch()}`); | ||
} | ||
return newMessage; | ||
}; | ||
return RequestMessage; | ||
}(OutboundMessage)); | ||
} | ||
} | ||
export default RequestMessage; |
@@ -1,47 +0,15 @@ | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
import OutboundMessage from "./index"; | ||
import responseCodes from "../response-codes"; | ||
var ResponseMessage = /** @class */ (function (_super) { | ||
__extends(ResponseMessage, _super); | ||
function ResponseMessage(inboundMessage, responseCode, headers, body) { | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
var _this = _super.call(this, undefined, __assign({}, headers), body) || this; | ||
_this.subject = "SIP/2.0 ".concat(responseCode, " ").concat(responseCodes[responseCode]); | ||
var keys = ["Via", "From", "To", "Call-ID", "CSeq"]; | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var key = keys_1[_i]; | ||
class ResponseMessage extends OutboundMessage { | ||
constructor(inboundMessage, responseCode, headers = {}, body = "") { | ||
super(undefined, { ...headers }, body); | ||
this.subject = `SIP/2.0 ${responseCode} ${responseCodes[responseCode]}`; | ||
const keys = ["Via", "From", "To", "Call-ID", "CSeq"]; | ||
for (const key of keys) { | ||
if (inboundMessage.headers[key]) { | ||
_this.headers[key] = inboundMessage.headers[key]; | ||
this.headers[key] = inboundMessage.headers[key]; | ||
} | ||
} | ||
return _this; | ||
} | ||
return ResponseMessage; | ||
}(OutboundMessage)); | ||
} | ||
export default ResponseMessage; |
// Ref: https://en.wikipedia.org/wiki/List_of_SIP_response_codes' | ||
var responseCodes = { | ||
const responseCodes = { | ||
100: "Trying", | ||
@@ -4,0 +4,0 @@ 180: "Ringing", |
@@ -1,15 +0,6 @@ | ||
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { | ||
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { | ||
if (ar || !(i in from)) { | ||
if (!ar) ar = Array.prototype.slice.call(from, 0, i); | ||
ar[i] = from[i]; | ||
} | ||
} | ||
return to.concat(ar || Array.prototype.slice.call(from)); | ||
}; | ||
var SipMessage = /** @class */ (function () { | ||
function SipMessage(subject, headers, body) { | ||
if (subject === void 0) { subject = ""; } | ||
if (headers === void 0) { headers = {}; } | ||
if (body === void 0) { body = ""; } | ||
class SipMessage { | ||
subject; | ||
headers; | ||
body; | ||
constructor(subject = "", headers = {}, body = "") { | ||
this.subject = subject; | ||
@@ -25,14 +16,12 @@ this.headers = headers; | ||
} | ||
SipMessage.prototype.toString = function () { | ||
var _this = this; | ||
var r = __spreadArray(__spreadArray([ | ||
this.subject | ||
], Object.keys(this.headers).map(function (key) { return "".concat(key, ": ").concat(_this.headers[key]); }), true), [ | ||
toString() { | ||
const r = [ | ||
this.subject, | ||
...Object.keys(this.headers).map((key) => `${key}: ${this.headers[key]}`), | ||
"", | ||
this.body, | ||
], false).join("\r\n"); | ||
].join("\r\n"); | ||
return r; | ||
}; | ||
return SipMessage; | ||
}()); | ||
} | ||
} | ||
export default SipMessage; |
import crypto from "node:crypto"; | ||
var md5 = function (s) { return crypto.createHash("md5").update(s).digest("hex"); }; | ||
var generateResponse = function (sipInfo, endpoint, nonce) { | ||
var ha1 = md5("".concat(sipInfo.authorizationId, ":").concat(sipInfo.domain, ":").concat(sipInfo.password)); | ||
var ha2 = md5(endpoint); | ||
var response = md5("".concat(ha1, ":").concat(nonce, ":").concat(ha2)); | ||
const md5 = (s) => crypto.createHash("md5").update(s).digest("hex"); | ||
const generateResponse = (sipInfo, endpoint, nonce) => { | ||
const ha1 = md5(`${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`); | ||
const ha2 = md5(endpoint); | ||
const response = md5(`${ha1}:${nonce}:${ha2}`); | ||
return response; | ||
}; | ||
export var generateAuthorization = function (sipInfo, nonce, method) { | ||
var authObj = { | ||
export const generateAuthorization = (sipInfo, nonce, method) => { | ||
const authObj = { | ||
"Digest algorithm": "MD5", | ||
username: sipInfo.authorizationId, | ||
realm: sipInfo.domain, | ||
nonce: nonce, | ||
uri: "sip:".concat(sipInfo.domain), | ||
response: generateResponse(sipInfo, "".concat(method, ":sip:").concat(sipInfo.domain), nonce), | ||
nonce, | ||
uri: `sip:${sipInfo.domain}`, | ||
response: generateResponse(sipInfo, `${method}:sip:${sipInfo.domain}`, nonce), | ||
}; | ||
return Object.keys(authObj) | ||
.map(function (key) { return "".concat(key, "=\"").concat(authObj[key], "\""); }) | ||
.map((key) => `${key}="${authObj[key]}"`) | ||
.join(", "); | ||
}; | ||
export var uuid = function () { return crypto.randomUUID(); }; | ||
export var branch = function () { return "z9hG4bK-" + uuid(); }; | ||
export var randomInt = function () { | ||
return Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024; | ||
}; | ||
export var withoutTag = function (s) { return s.replace(/;tag=.*$/, ""); }; | ||
export var extractAddress = function (s) { var _a; return (_a = s.match(/<(sip:.+?)>/)) === null || _a === void 0 ? void 0 : _a[1]; }; | ||
var keyAndSalt = crypto.randomBytes(30); | ||
export var localKey = keyAndSalt.toString("base64").replace(/=+$/, ""); | ||
export const uuid = () => crypto.randomUUID(); | ||
export const branch = () => "z9hG4bK-" + uuid(); | ||
export const randomInt = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024; | ||
export const withoutTag = (s) => s.replace(/;tag=.*$/, ""); | ||
export const extractAddress = (s) => s.match(/<(sip:.+?)>/)?.[1]; | ||
const keyAndSalt = crypto.randomBytes(30); | ||
export const localKey = keyAndSalt.toString("base64").replace(/=+$/, ""); |
{ | ||
"name": "ringcentral-softphone", | ||
"version": "1.1.2", | ||
"version": "1.1.3", | ||
"homepage": "https://github.com/ringcentral/ringcentral-softphone-ts", | ||
@@ -9,2 +9,5 @@ "license": "MIT", | ||
"main": "dist/cjs/index.js", | ||
"files": [ | ||
"dist" | ||
], | ||
"exports": { | ||
@@ -25,3 +28,3 @@ ".": { | ||
"out": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/outbound-call.ts", | ||
"build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json", | ||
"build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json", | ||
"prepublishOnly": "yarn build", | ||
@@ -35,5 +38,2 @@ "postpublish": "rm -rf dist" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"devDependencies": { | ||
@@ -40,0 +40,0 @@ "@types/node": "^22.12.0", |
92035
2145