ringcentral-softphone 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/call-session/inbound.cjs +41 -83
- package/dist/call-session/inbound.d.cts +2 -0
- package/dist/call-session/inbound.d.mts +2 -0
- package/dist/call-session/inbound.mjs +51 -0
- package/dist/call-session/index.cjs +212 -273
- package/dist/call-session/index.d.cts +2 -0
- package/dist/call-session/index.d.mts +2 -0
- package/dist/call-session/index.mjs +209 -0
- package/dist/call-session/outbound.cjs +56 -99
- package/dist/call-session/outbound.d.cts +2 -0
- package/dist/call-session/outbound.d.mts +2 -0
- package/dist/call-session/outbound.mjs +57 -0
- package/dist/call-session/streamer.cjs +66 -111
- package/dist/call-session/streamer.d.cts +2 -0
- package/dist/call-session/streamer.d.mts +2 -0
- package/dist/call-session/streamer.mjs +65 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/codec-Bh7v8J-S.d.mts +18 -0
- package/dist/codec-C-VrtVkq.d.cts +18 -0
- package/dist/codec.cjs +68 -84
- package/dist/codec.d.cts +2 -0
- package/dist/codec.d.mts +2 -0
- package/dist/codec.mjs +68 -0
- package/dist/dtmf-B13Fz2VR.d.mts +10 -0
- package/dist/dtmf-DcQ-5vSG.d.cts +10 -0
- package/dist/dtmf.cjs +37 -60
- package/dist/dtmf.d.cts +2 -0
- package/dist/dtmf.d.mts +2 -0
- package/dist/dtmf.mjs +42 -0
- package/dist/inbound--wGoGqLS.d.mts +103 -0
- package/dist/inbound-DquvTSj1.d.cts +103 -0
- package/dist/index--UjWgLK-.d.mts +8 -0
- package/dist/index-BhN2W8AV.d.mts +8 -0
- package/dist/index-CEgs-Dz2.d.cts +1 -0
- package/dist/index-Cf2Cev52.d.cts +8 -0
- package/dist/index-XMDop59x.d.cts +8 -0
- package/dist/index-q_LXL61M.d.mts +1 -0
- package/dist/index.cjs +166 -250
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +172 -0
- package/dist/request-B_auLSJn.d.cts +10 -0
- package/dist/request-pBe7_mYv.d.mts +10 -0
- package/dist/response-LRRpY8lX.d.mts +9 -0
- package/dist/response-ReKvb5x9.d.cts +9 -0
- package/dist/sip-message/inbound/index.cjs +16 -50
- package/dist/sip-message/inbound/index.d.cts +2 -0
- package/dist/sip-message/inbound/index.d.mts +2 -0
- package/dist/sip-message/inbound/index.mjs +17 -0
- package/dist/sip-message/index.cjs +11 -49
- package/dist/sip-message/index.d.cts +6 -0
- package/dist/sip-message/index.d.mts +6 -0
- package/dist/sip-message/index.mjs +6 -0
- package/dist/sip-message/outbound/index.cjs +10 -40
- package/dist/sip-message/outbound/index.d.cts +2 -0
- package/dist/sip-message/outbound/index.d.mts +2 -0
- package/dist/sip-message/outbound/index.mjs +11 -0
- package/dist/sip-message/outbound/request.cjs +20 -61
- package/dist/sip-message/outbound/request.d.cts +2 -0
- package/dist/sip-message/outbound/request.d.mts +2 -0
- package/dist/sip-message/outbound/request.mjs +21 -0
- package/dist/sip-message/outbound/response.cjs +25 -54
- package/dist/sip-message/outbound/response.d.cts +2 -0
- package/dist/sip-message/outbound/response.d.mts +2 -0
- package/dist/sip-message/outbound/response.mjs +26 -0
- package/dist/sip-message/response-codes.cjs +80 -100
- package/dist/sip-message/response-codes.d.cts +5 -0
- package/dist/sip-message/response-codes.d.mts +6 -0
- package/dist/sip-message/response-codes.mjs +82 -0
- package/dist/sip-message/sip-message.cjs +25 -52
- package/dist/sip-message/sip-message.d.cts +2 -0
- package/dist/sip-message/sip-message.d.mts +2 -0
- package/dist/sip-message/sip-message.mjs +26 -0
- package/dist/sip-message-B2D5MPBI.d.cts +13 -0
- package/dist/sip-message-PaPho4qU.d.mts +13 -0
- package/dist/types-DOQ9wmX6.d.mts +12 -0
- package/dist/types-DZxCsbZE.d.cts +12 -0
- package/dist/types.cjs +0 -15
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/dist/utils.cjs +27 -73
- package/dist/utils.d.cts +12 -0
- package/dist/utils.d.mts +12 -0
- package/dist/utils.mjs +25 -0
- package/package.json +14 -14
- package/dist/call-session/inbound.d.ts +0 -8
- package/dist/call-session/inbound.js +0 -64
- package/dist/call-session/index.d.ts +0 -46
- package/dist/call-session/index.js +0 -248
- package/dist/call-session/outbound.d.ts +0 -11
- package/dist/call-session/outbound.js +0 -71
- package/dist/call-session/streamer.d.ts +0 -17
- package/dist/call-session/streamer.js +0 -83
- package/dist/codec.d.ts +0 -15
- package/dist/codec.js +0 -66
- package/dist/dtmf.d.ts +0 -7
- package/dist/dtmf.js +0 -47
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -242
- package/dist/sip-message/inbound/index.d.ts +0 -5
- package/dist/sip-message/inbound/index.js +0 -22
- package/dist/sip-message/index.d.ts +0 -5
- package/dist/sip-message/index.js +0 -12
- package/dist/sip-message/outbound/index.d.ts +0 -5
- package/dist/sip-message/outbound/index.js +0 -12
- package/dist/sip-message/outbound/request.d.ts +0 -7
- package/dist/sip-message/outbound/request.js +0 -33
- package/dist/sip-message/outbound/response.d.ts +0 -6
- package/dist/sip-message/outbound/response.js +0 -26
- package/dist/sip-message/response-codes.d.ts +0 -4
- package/dist/sip-message/response-codes.js +0 -83
- package/dist/sip-message/sip-message.d.ts +0 -11
- package/dist/sip-message/sip-message.js +0 -34
- package/dist/types.d.ts +0 -9
- package/dist/types.js +0 -0
- package/dist/utils.d.ts +0 -8
- package/dist/utils.js +0 -41
package/dist/utils.cjs
CHANGED
|
@@ -1,80 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
var utils_exports = {};
|
|
29
|
-
__export(utils_exports, {
|
|
30
|
-
branch: () => branch,
|
|
31
|
-
extractAddress: () => extractAddress,
|
|
32
|
-
generateAuthorization: () => generateAuthorization,
|
|
33
|
-
localKey: () => localKey,
|
|
34
|
-
randomInt: () => randomInt,
|
|
35
|
-
uuid: () => uuid,
|
|
36
|
-
withoutTag: () => withoutTag
|
|
37
|
-
});
|
|
38
|
-
module.exports = __toCommonJS(utils_exports);
|
|
39
|
-
var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
40
|
-
const md5 = (s) => import_node_crypto.default.createHash("md5").update(s).digest("hex");
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_chunk = require("./chunk-CKQMccvm.cjs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
4
|
+
node_crypto = require_chunk.__toESM(node_crypto, 1);
|
|
5
|
+
//#region src/utils.ts
|
|
6
|
+
const md5 = (s) => node_crypto.default.createHash("md5").update(s).digest("hex");
|
|
41
7
|
const generateResponse = (sipInfo, endpoint, nonce) => {
|
|
42
|
-
|
|
43
|
-
`${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`
|
|
44
|
-
);
|
|
45
|
-
const ha2 = md5(endpoint);
|
|
46
|
-
const response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
47
|
-
return response;
|
|
8
|
+
return md5(`${md5(`${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`)}:${nonce}:${md5(endpoint)}`);
|
|
48
9
|
};
|
|
49
10
|
const generateAuthorization = (sipInfo, nonce, method) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
nonce
|
|
60
|
-
)
|
|
61
|
-
};
|
|
62
|
-
return Object.entries(authObj).map(([key, value]) => `${key}="${value}"`).join(", ");
|
|
11
|
+
const authObj = {
|
|
12
|
+
"Digest algorithm": "MD5",
|
|
13
|
+
username: sipInfo.authorizationId,
|
|
14
|
+
realm: sipInfo.domain,
|
|
15
|
+
nonce,
|
|
16
|
+
uri: `sip:${sipInfo.domain}`,
|
|
17
|
+
response: generateResponse(sipInfo, `${method}:sip:${sipInfo.domain}`, nonce)
|
|
18
|
+
};
|
|
19
|
+
return Object.entries(authObj).map(([key, value]) => `${key}="${value}"`).join(", ");
|
|
63
20
|
};
|
|
64
|
-
const uuid = () =>
|
|
21
|
+
const uuid = () => node_crypto.default.randomUUID();
|
|
65
22
|
const branch = () => `z9hG4bK-${uuid()}`;
|
|
66
|
-
const randomInt = () => Math.floor(Math.random() *
|
|
23
|
+
const randomInt = () => Math.floor(Math.random() * 64512) + 1024;
|
|
67
24
|
const withoutTag = (s) => s.replace(/;tag=.*$/, "");
|
|
68
25
|
const extractAddress = (s) => s.match(/<(sip:.+?)>/)?.[1];
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
uuid,
|
|
79
|
-
withoutTag
|
|
80
|
-
});
|
|
26
|
+
const localKey = node_crypto.default.randomBytes(30).toString("base64").replace(/=+$/, "");
|
|
27
|
+
//#endregion
|
|
28
|
+
exports.branch = branch;
|
|
29
|
+
exports.extractAddress = extractAddress;
|
|
30
|
+
exports.generateAuthorization = generateAuthorization;
|
|
31
|
+
exports.localKey = localKey;
|
|
32
|
+
exports.randomInt = randomInt;
|
|
33
|
+
exports.uuid = uuid;
|
|
34
|
+
exports.withoutTag = withoutTag;
|
package/dist/utils.d.cts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as SoftPhoneOptions } from "./types-DZxCsbZE.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.d.ts
|
|
4
|
+
declare const generateAuthorization: (sipInfo: SoftPhoneOptions, nonce: string, method: "REGISTER" | "INVITE") => string;
|
|
5
|
+
declare const uuid: () => `${string}-${string}-${string}-${string}-${string}`;
|
|
6
|
+
declare const branch: () => string;
|
|
7
|
+
declare const randomInt: () => number;
|
|
8
|
+
declare const withoutTag: (s: string) => string;
|
|
9
|
+
declare const extractAddress: (s: string) => string | undefined;
|
|
10
|
+
declare const localKey: string;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { branch, extractAddress, generateAuthorization, localKey, randomInt, uuid, withoutTag };
|
package/dist/utils.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as SoftPhoneOptions } from "./types-DOQ9wmX6.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.d.ts
|
|
4
|
+
declare const generateAuthorization: (sipInfo: SoftPhoneOptions, nonce: string, method: "REGISTER" | "INVITE") => string;
|
|
5
|
+
declare const uuid: () => `${string}-${string}-${string}-${string}-${string}`;
|
|
6
|
+
declare const branch: () => string;
|
|
7
|
+
declare const randomInt: () => number;
|
|
8
|
+
declare const withoutTag: (s: string) => string;
|
|
9
|
+
declare const extractAddress: (s: string) => string | undefined;
|
|
10
|
+
declare const localKey: string;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { branch, extractAddress, generateAuthorization, localKey, randomInt, uuid, withoutTag };
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
//#region src/utils.ts
|
|
3
|
+
const md5 = (s) => crypto.createHash("md5").update(s).digest("hex");
|
|
4
|
+
const generateResponse = (sipInfo, endpoint, nonce) => {
|
|
5
|
+
return md5(`${md5(`${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`)}:${nonce}:${md5(endpoint)}`);
|
|
6
|
+
};
|
|
7
|
+
const generateAuthorization = (sipInfo, nonce, method) => {
|
|
8
|
+
const authObj = {
|
|
9
|
+
"Digest algorithm": "MD5",
|
|
10
|
+
username: sipInfo.authorizationId,
|
|
11
|
+
realm: sipInfo.domain,
|
|
12
|
+
nonce,
|
|
13
|
+
uri: `sip:${sipInfo.domain}`,
|
|
14
|
+
response: generateResponse(sipInfo, `${method}:sip:${sipInfo.domain}`, nonce)
|
|
15
|
+
};
|
|
16
|
+
return Object.entries(authObj).map(([key, value]) => `${key}="${value}"`).join(", ");
|
|
17
|
+
};
|
|
18
|
+
const uuid = () => crypto.randomUUID();
|
|
19
|
+
const branch = () => `z9hG4bK-${uuid()}`;
|
|
20
|
+
const randomInt = () => Math.floor(Math.random() * 64512) + 1024;
|
|
21
|
+
const withoutTag = (s) => s.replace(/;tag=.*$/, "");
|
|
22
|
+
const extractAddress = (s) => s.match(/<(sip:.+?)>/)?.[1];
|
|
23
|
+
const localKey = crypto.randomBytes(30).toString("base64").replace(/=+$/, "");
|
|
24
|
+
//#endregion
|
|
25
|
+
export { branch, extractAddress, generateAuthorization, localKey, randomInt, uuid, withoutTag };
|
package/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ringcentral-softphone",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"homepage": "https://github.com/ringcentral/ringcentral-softphone-ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"types": "dist/index.d.
|
|
7
|
+
"types": "dist/index.d.mts",
|
|
8
8
|
"main": "dist/index.cjs",
|
|
9
|
-
"module": "dist/index.
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"types": "./dist/index.d.
|
|
16
|
-
"import": "./dist/index.
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
17
|
"require": "./dist/index.cjs",
|
|
18
|
-
"default": "./dist/index.
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
19
19
|
},
|
|
20
20
|
"./*": {
|
|
21
|
-
"types": "./dist/*.d.
|
|
22
|
-
"import": "./dist/*.
|
|
21
|
+
"types": "./dist/*.d.mts",
|
|
22
|
+
"import": "./dist/*.mjs",
|
|
23
23
|
"require": "./dist/*.cjs",
|
|
24
|
-
"default": "./dist/*.
|
|
24
|
+
"default": "./dist/*.mjs"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
@@ -30,24 +30,24 @@
|
|
|
30
30
|
"join": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/join-rcv-meeting.ts",
|
|
31
31
|
"multi": "tsx -r dotenv-override-true/config demos/multiple-calls-sequentially.ts",
|
|
32
32
|
"lint": "biome check --write .",
|
|
33
|
-
"build": "
|
|
33
|
+
"build": "tsdown",
|
|
34
34
|
"prepublishOnly": "yarn build",
|
|
35
35
|
"postpublish": "rm -rf dist"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@evan/opus": "^1.0.3",
|
|
39
39
|
"debug": "^4.4.3",
|
|
40
|
-
"wait-for-async": "^0.8.
|
|
40
|
+
"wait-for-async": "^0.8.1",
|
|
41
41
|
"werift-rtp": "^0.8.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@biomejs/biome": "^2.4.
|
|
44
|
+
"@biomejs/biome": "^2.4.12",
|
|
45
45
|
"@types/node": "^25.6.0",
|
|
46
46
|
"dotenv-override-true": "^6.2.2",
|
|
47
|
-
"
|
|
47
|
+
"tsdown": "^0.21.10",
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^6.0.3",
|
|
50
|
-
"yarn-upgrade-all": "^0.
|
|
50
|
+
"yarn-upgrade-all": "^0.8.1"
|
|
51
51
|
},
|
|
52
52
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
53
53
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type Softphone from "../index.js";
|
|
2
|
-
import { type InboundMessage } from "../sip-message/index.js";
|
|
3
|
-
import CallSession from "./index.js";
|
|
4
|
-
declare class InboundCallSession extends CallSession {
|
|
5
|
-
constructor(softphone: Softphone, inviteMessage: InboundMessage);
|
|
6
|
-
answer(): Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
export default InboundCallSession;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { OutboundMessage } from "../sip-message/index.js";
|
|
2
|
-
import { localKey, randomInt } from "../utils.js";
|
|
3
|
-
import CallSession from "./index.js";
|
|
4
|
-
class InboundCallSession extends CallSession {
|
|
5
|
-
constructor(softphone, inviteMessage) {
|
|
6
|
-
super(softphone, inviteMessage);
|
|
7
|
-
this.localPeer = inviteMessage.headers.To;
|
|
8
|
-
this.remotePeer = inviteMessage.headers.From;
|
|
9
|
-
if (inviteMessage.body.length > 0) {
|
|
10
|
-
this.remoteKey = inviteMessage.body.match(
|
|
11
|
-
/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
|
|
12
|
-
)[1];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
async answer() {
|
|
16
|
-
const answerSDP = `
|
|
17
|
-
v=0
|
|
18
|
-
o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
|
|
19
|
-
s=rc-softphone-ts
|
|
20
|
-
c=IN IP4 ${this.softphone.client.localAddress}
|
|
21
|
-
t=0 0
|
|
22
|
-
m=audio ${randomInt()} RTP/SAVP ${this.softphone.codec.id} 101
|
|
23
|
-
a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
|
|
24
|
-
a=rtpmap:101 telephone-event/8000
|
|
25
|
-
a=fmtp:101 0-15
|
|
26
|
-
a=sendrecv
|
|
27
|
-
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
|
|
28
|
-
`.trim();
|
|
29
|
-
this.sdp = answerSDP;
|
|
30
|
-
const newMessage = new OutboundMessage(
|
|
31
|
-
"SIP/2.0 200 OK",
|
|
32
|
-
{
|
|
33
|
-
Via: this.sipMessage.headers.Via,
|
|
34
|
-
"Call-ID": this.sipMessage.getHeader("Call-ID"),
|
|
35
|
-
From: this.sipMessage.headers.From,
|
|
36
|
-
To: this.sipMessage.headers.To,
|
|
37
|
-
CSeq: this.sipMessage.headers.CSeq,
|
|
38
|
-
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
|
|
39
|
-
Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
|
|
40
|
-
Supported: "replaces, 100rel, timer, norefersub",
|
|
41
|
-
"Session-Expires": "14400;refresher=uac",
|
|
42
|
-
Require: "timer",
|
|
43
|
-
"Content-Type": "application/sdp"
|
|
44
|
-
},
|
|
45
|
-
answerSDP
|
|
46
|
-
);
|
|
47
|
-
const ackMessage = await this.softphone.send(newMessage, true);
|
|
48
|
-
if (ackMessage.body.length > 0) {
|
|
49
|
-
this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
50
|
-
this.remotePort = parseInt(
|
|
51
|
-
ackMessage.body.match(/m=audio (\d+) /)[1],
|
|
52
|
-
10
|
|
53
|
-
);
|
|
54
|
-
this.remoteKey = ackMessage.body.match(
|
|
55
|
-
/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/
|
|
56
|
-
)[1];
|
|
57
|
-
}
|
|
58
|
-
this.startLocalServices();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
var inbound_default = InboundCallSession;
|
|
62
|
-
export {
|
|
63
|
-
inbound_default as default
|
|
64
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import dgram from "node:dgram";
|
|
3
|
-
import EventEmitter from "node:events";
|
|
4
|
-
import { RtpPacket, SrtpSession } from "werift-rtp";
|
|
5
|
-
import DTMF from "../dtmf.js";
|
|
6
|
-
import type Softphone from "../index.js";
|
|
7
|
-
import { type InboundMessage } from "../sip-message/index.js";
|
|
8
|
-
import Streamer from "./streamer.js";
|
|
9
|
-
type DtmfChar = (typeof DTMF.phoneChars)[number];
|
|
10
|
-
declare abstract class CallSession extends EventEmitter {
|
|
11
|
-
softphone: Softphone;
|
|
12
|
-
sipMessage: InboundMessage;
|
|
13
|
-
socket: dgram.Socket;
|
|
14
|
-
localPeer: string;
|
|
15
|
-
remotePeer: string;
|
|
16
|
-
remoteIP: string;
|
|
17
|
-
remotePort: number;
|
|
18
|
-
disposed: boolean;
|
|
19
|
-
srtpSession: SrtpSession;
|
|
20
|
-
encoder: {
|
|
21
|
-
encode: (pcm: Buffer) => Buffer;
|
|
22
|
-
};
|
|
23
|
-
decoder: {
|
|
24
|
-
decode: (audio: Buffer) => Buffer;
|
|
25
|
-
};
|
|
26
|
-
sdp: string;
|
|
27
|
-
ssrc: number;
|
|
28
|
-
sequenceNumber: number;
|
|
29
|
-
timestamp: number;
|
|
30
|
-
constructor(softphone: Softphone, sipMessage: InboundMessage);
|
|
31
|
-
set remoteKey(key: string);
|
|
32
|
-
get callId(): string | undefined;
|
|
33
|
-
send(data: string | Buffer): void;
|
|
34
|
-
hangup(): Promise<void>;
|
|
35
|
-
sendDTMF(char: DtmfChar): void;
|
|
36
|
-
sendDTMFs(s: string, delay?: number): Promise<void>;
|
|
37
|
-
streamAudio(input: Buffer): Streamer;
|
|
38
|
-
sendPacket(rtpPacket: RtpPacket): void;
|
|
39
|
-
protected startLocalServices(): void;
|
|
40
|
-
protected dispose(): void;
|
|
41
|
-
transfer(transferTo: string): Promise<void>;
|
|
42
|
-
toggleReceive(toReceive: boolean): Promise<void>;
|
|
43
|
-
hold(): Promise<void>;
|
|
44
|
-
unhold(): Promise<void>;
|
|
45
|
-
}
|
|
46
|
-
export default CallSession;
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import dgram from "node:dgram";
|
|
3
|
-
import EventEmitter from "node:events";
|
|
4
|
-
import waitFor from "wait-for-async";
|
|
5
|
-
import { RtpHeader, RtpPacket, SrtpSession } from "werift-rtp";
|
|
6
|
-
import DTMF from "../dtmf.js";
|
|
7
|
-
import {
|
|
8
|
-
RequestMessage,
|
|
9
|
-
ResponseMessage
|
|
10
|
-
} from "../sip-message/index.js";
|
|
11
|
-
import { branch, extractAddress, localKey, randomInt } from "../utils.js";
|
|
12
|
-
import Streamer from "./streamer.js";
|
|
13
|
-
const isDtmfChar = (value) => DTMF.phoneChars.includes(value);
|
|
14
|
-
class CallSession extends EventEmitter {
|
|
15
|
-
softphone;
|
|
16
|
-
sipMessage;
|
|
17
|
-
socket;
|
|
18
|
-
localPeer;
|
|
19
|
-
remotePeer;
|
|
20
|
-
remoteIP;
|
|
21
|
-
remotePort;
|
|
22
|
-
disposed = false;
|
|
23
|
-
srtpSession;
|
|
24
|
-
encoder;
|
|
25
|
-
decoder;
|
|
26
|
-
sdp;
|
|
27
|
-
// for audio streaming
|
|
28
|
-
ssrc = randomInt();
|
|
29
|
-
sequenceNumber = randomInt();
|
|
30
|
-
timestamp = randomInt();
|
|
31
|
-
constructor(softphone, sipMessage) {
|
|
32
|
-
super();
|
|
33
|
-
this.softphone = softphone;
|
|
34
|
-
this.encoder = softphone.codec.createEncoder();
|
|
35
|
-
this.decoder = softphone.codec.createDecoder();
|
|
36
|
-
this.sipMessage = sipMessage;
|
|
37
|
-
if (this.sipMessage.body.length > 0) {
|
|
38
|
-
this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
|
|
39
|
-
this.remotePort = parseInt(
|
|
40
|
-
this.sipMessage.body.match(/m=audio (\d+) /)[1],
|
|
41
|
-
10
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
set remoteKey(key) {
|
|
46
|
-
const localKeyBuffer = Buffer.from(localKey, "base64");
|
|
47
|
-
const remoteKeyBuffer = Buffer.from(key, "base64");
|
|
48
|
-
this.srtpSession = new SrtpSession({
|
|
49
|
-
profile: 1,
|
|
50
|
-
keys: {
|
|
51
|
-
localMasterKey: localKeyBuffer.subarray(0, 16),
|
|
52
|
-
localMasterSalt: localKeyBuffer.subarray(16, 30),
|
|
53
|
-
remoteMasterKey: remoteKeyBuffer.subarray(0, 16),
|
|
54
|
-
remoteMasterSalt: remoteKeyBuffer.subarray(16, 30)
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
get callId() {
|
|
59
|
-
return this.sipMessage.getHeader("Call-ID");
|
|
60
|
-
}
|
|
61
|
-
send(data) {
|
|
62
|
-
this.socket.send(data, this.remotePort, this.remoteIP);
|
|
63
|
-
}
|
|
64
|
-
async hangup() {
|
|
65
|
-
const requestMessage = new RequestMessage(
|
|
66
|
-
`BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`,
|
|
67
|
-
{
|
|
68
|
-
"Call-ID": this.callId,
|
|
69
|
-
From: this.localPeer,
|
|
70
|
-
To: this.remotePeer,
|
|
71
|
-
Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${branch()}`
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
await this.softphone.send(requestMessage);
|
|
75
|
-
}
|
|
76
|
-
sendDTMF(char) {
|
|
77
|
-
const payloads = DTMF.charToPayloads(char);
|
|
78
|
-
const timestamp = this.timestamp;
|
|
79
|
-
let first = true;
|
|
80
|
-
for (const payload of payloads) {
|
|
81
|
-
const rtpHeader = new RtpHeader({
|
|
82
|
-
version: 2,
|
|
83
|
-
padding: false,
|
|
84
|
-
paddingSize: 0,
|
|
85
|
-
extension: false,
|
|
86
|
-
marker: first,
|
|
87
|
-
payloadOffset: 12,
|
|
88
|
-
payloadType: 101,
|
|
89
|
-
sequenceNumber: this.sequenceNumber,
|
|
90
|
-
timestamp,
|
|
91
|
-
ssrc: this.ssrc,
|
|
92
|
-
csrcLength: 0,
|
|
93
|
-
csrc: [],
|
|
94
|
-
extensionProfile: 48862,
|
|
95
|
-
extensionLength: void 0,
|
|
96
|
-
extensions: []
|
|
97
|
-
});
|
|
98
|
-
const rtpPacket = new RtpPacket(rtpHeader, payload);
|
|
99
|
-
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
100
|
-
this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
|
|
101
|
-
first = false;
|
|
102
|
-
}
|
|
103
|
-
this.timestamp += 800;
|
|
104
|
-
}
|
|
105
|
-
async sendDTMFs(s, delay = 500) {
|
|
106
|
-
for (const c of s) {
|
|
107
|
-
if (!isDtmfChar(c)) {
|
|
108
|
-
throw new Error(`invalid phone char: ${c}`);
|
|
109
|
-
}
|
|
110
|
-
this.sendDTMF(c);
|
|
111
|
-
await waitFor({ interval: delay });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// buffer is the content of a audio file, it is supposed to be uncompressed PCM data
|
|
115
|
-
// The audio should be playable by command: play -t raw -b 16 -r 16000 -e signed-integer test.wav
|
|
116
|
-
streamAudio(input) {
|
|
117
|
-
const streamer = new Streamer(this, input);
|
|
118
|
-
streamer.start();
|
|
119
|
-
return streamer;
|
|
120
|
-
}
|
|
121
|
-
// send a single rtp packet
|
|
122
|
-
sendPacket(rtpPacket) {
|
|
123
|
-
if (this.disposed) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
|
|
127
|
-
}
|
|
128
|
-
startLocalServices() {
|
|
129
|
-
this.socket = dgram.createSocket("udp4");
|
|
130
|
-
this.socket.on("message", (message) => {
|
|
131
|
-
const rtpPacket = RtpPacket.deSerialize(
|
|
132
|
-
this.srtpSession.decrypt(message)
|
|
133
|
-
);
|
|
134
|
-
this.emit("rtpPacket", rtpPacket);
|
|
135
|
-
if (rtpPacket.header.payloadType === 101) {
|
|
136
|
-
this.emit("dtmfPacket", rtpPacket);
|
|
137
|
-
const char = DTMF.payloadToChar(rtpPacket.payload);
|
|
138
|
-
if (char) {
|
|
139
|
-
this.emit("dtmf", char);
|
|
140
|
-
}
|
|
141
|
-
} else if (rtpPacket.header.payloadType === this.softphone.codec.id) {
|
|
142
|
-
if (rtpPacket.payload.length === 4 && rtpPacket.payload[0] >= 0 && rtpPacket.payload[0] < 12 && rtpPacket.payload[1] === 138 && rtpPacket.payload[2] === 3 && rtpPacket.payload[3] === 192) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
rtpPacket.payload = this.decoder.decode(rtpPacket.payload);
|
|
147
|
-
this.emit("audioPacket", rtpPacket);
|
|
148
|
-
} catch {
|
|
149
|
-
console.error("Audio packet decode failed", rtpPacket);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
this.socket.bind();
|
|
154
|
-
this.send("hello");
|
|
155
|
-
const byeHandler = (inboundMessage) => {
|
|
156
|
-
if (inboundMessage.getHeader("Call-ID") !== this.callId) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
|
|
160
|
-
this.softphone.off("message", byeHandler);
|
|
161
|
-
this.dispose();
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
this.softphone.on("message", byeHandler);
|
|
165
|
-
}
|
|
166
|
-
dispose() {
|
|
167
|
-
this.disposed = true;
|
|
168
|
-
this.emit("disposed");
|
|
169
|
-
this.removeAllListeners();
|
|
170
|
-
this.socket?.removeAllListeners();
|
|
171
|
-
this.socket?.close();
|
|
172
|
-
}
|
|
173
|
-
async transfer(transferTo) {
|
|
174
|
-
const requestMessage = new RequestMessage(
|
|
175
|
-
`REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`,
|
|
176
|
-
{
|
|
177
|
-
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${branch()};alias`,
|
|
178
|
-
"Max-Forwards": 70,
|
|
179
|
-
From: this.localPeer,
|
|
180
|
-
To: this.remotePeer,
|
|
181
|
-
Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
|
|
182
|
-
"Call-ID": this.callId,
|
|
183
|
-
Event: "refer",
|
|
184
|
-
Expires: 600,
|
|
185
|
-
Supported: "replaces, 100rel, timer, norefersub",
|
|
186
|
-
Accept: "message/sipfrag;version=2.0",
|
|
187
|
-
"Allow-Events": "presence, message-summary, refer",
|
|
188
|
-
"Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`,
|
|
189
|
-
"Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
await this.softphone.send(requestMessage);
|
|
193
|
-
return new Promise((resolve) => {
|
|
194
|
-
const notifyHandler = (inboundMessage) => {
|
|
195
|
-
if (!inboundMessage.subject.startsWith("NOTIFY ")) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const responseMessage = new ResponseMessage(inboundMessage, 200);
|
|
199
|
-
this.softphone.send(responseMessage);
|
|
200
|
-
if (inboundMessage.body.trim() === "SIP/2.0 200 OK") {
|
|
201
|
-
this.softphone.off("message", notifyHandler);
|
|
202
|
-
resolve();
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
this.softphone.on("message", notifyHandler);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
async toggleReceive(toReceive) {
|
|
209
|
-
let newSDP = this.sdp;
|
|
210
|
-
if (!toReceive) {
|
|
211
|
-
newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
|
|
212
|
-
}
|
|
213
|
-
const requestMessage = new RequestMessage(
|
|
214
|
-
`INVITE ${extractAddress(this.remotePeer)} SIP/2.0`,
|
|
215
|
-
{
|
|
216
|
-
"Call-Id": this.callId,
|
|
217
|
-
From: this.localPeer,
|
|
218
|
-
To: this.remotePeer,
|
|
219
|
-
Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${branch()};alias`,
|
|
220
|
-
"Content-Type": "application/sdp",
|
|
221
|
-
Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`
|
|
222
|
-
},
|
|
223
|
-
newSDP
|
|
224
|
-
);
|
|
225
|
-
const replyMessage = await this.softphone.send(requestMessage, true);
|
|
226
|
-
const ackMessage = new RequestMessage(
|
|
227
|
-
`ACK ${extractAddress(this.remotePeer)} SIP/2.0`,
|
|
228
|
-
{
|
|
229
|
-
"Call-Id": this.callId,
|
|
230
|
-
From: this.localPeer,
|
|
231
|
-
To: this.remotePeer,
|
|
232
|
-
Via: replyMessage.headers.Via,
|
|
233
|
-
CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK")
|
|
234
|
-
}
|
|
235
|
-
);
|
|
236
|
-
await this.softphone.send(ackMessage);
|
|
237
|
-
}
|
|
238
|
-
async hold() {
|
|
239
|
-
return this.toggleReceive(false);
|
|
240
|
-
}
|
|
241
|
-
async unhold() {
|
|
242
|
-
return this.toggleReceive(true);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
var call_session_default = CallSession;
|
|
246
|
-
export {
|
|
247
|
-
call_session_default as default
|
|
248
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type Softphone from "../index.js";
|
|
2
|
-
import { type InboundMessage } from "../sip-message/index.js";
|
|
3
|
-
import CallSession from "./index.js";
|
|
4
|
-
declare class OutboundCallSession extends CallSession {
|
|
5
|
-
constructor(softphone: Softphone, answerMessage: InboundMessage);
|
|
6
|
-
init(): void;
|
|
7
|
-
cancel(): Promise<void>;
|
|
8
|
-
get sessionId(): string;
|
|
9
|
-
get partyId(): string;
|
|
10
|
-
}
|
|
11
|
-
export default OutboundCallSession;
|