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.
Files changed (118) hide show
  1. package/dist/call-session/inbound.cjs +41 -83
  2. package/dist/call-session/inbound.d.cts +2 -0
  3. package/dist/call-session/inbound.d.mts +2 -0
  4. package/dist/call-session/inbound.mjs +51 -0
  5. package/dist/call-session/index.cjs +212 -273
  6. package/dist/call-session/index.d.cts +2 -0
  7. package/dist/call-session/index.d.mts +2 -0
  8. package/dist/call-session/index.mjs +209 -0
  9. package/dist/call-session/outbound.cjs +56 -99
  10. package/dist/call-session/outbound.d.cts +2 -0
  11. package/dist/call-session/outbound.d.mts +2 -0
  12. package/dist/call-session/outbound.mjs +57 -0
  13. package/dist/call-session/streamer.cjs +66 -111
  14. package/dist/call-session/streamer.d.cts +2 -0
  15. package/dist/call-session/streamer.d.mts +2 -0
  16. package/dist/call-session/streamer.mjs +65 -0
  17. package/dist/chunk-CKQMccvm.cjs +28 -0
  18. package/dist/codec-Bh7v8J-S.d.mts +18 -0
  19. package/dist/codec-C-VrtVkq.d.cts +18 -0
  20. package/dist/codec.cjs +68 -84
  21. package/dist/codec.d.cts +2 -0
  22. package/dist/codec.d.mts +2 -0
  23. package/dist/codec.mjs +68 -0
  24. package/dist/dtmf-B13Fz2VR.d.mts +10 -0
  25. package/dist/dtmf-DcQ-5vSG.d.cts +10 -0
  26. package/dist/dtmf.cjs +37 -60
  27. package/dist/dtmf.d.cts +2 -0
  28. package/dist/dtmf.d.mts +2 -0
  29. package/dist/dtmf.mjs +42 -0
  30. package/dist/inbound--wGoGqLS.d.mts +103 -0
  31. package/dist/inbound-DquvTSj1.d.cts +103 -0
  32. package/dist/index--UjWgLK-.d.mts +8 -0
  33. package/dist/index-BhN2W8AV.d.mts +8 -0
  34. package/dist/index-CEgs-Dz2.d.cts +1 -0
  35. package/dist/index-Cf2Cev52.d.cts +8 -0
  36. package/dist/index-XMDop59x.d.cts +8 -0
  37. package/dist/index-q_LXL61M.d.mts +1 -0
  38. package/dist/index.cjs +166 -250
  39. package/dist/index.d.cts +2 -0
  40. package/dist/index.d.mts +2 -0
  41. package/dist/index.mjs +172 -0
  42. package/dist/request-B_auLSJn.d.cts +10 -0
  43. package/dist/request-pBe7_mYv.d.mts +10 -0
  44. package/dist/response-LRRpY8lX.d.mts +9 -0
  45. package/dist/response-ReKvb5x9.d.cts +9 -0
  46. package/dist/sip-message/inbound/index.cjs +16 -50
  47. package/dist/sip-message/inbound/index.d.cts +2 -0
  48. package/dist/sip-message/inbound/index.d.mts +2 -0
  49. package/dist/sip-message/inbound/index.mjs +17 -0
  50. package/dist/sip-message/index.cjs +11 -49
  51. package/dist/sip-message/index.d.cts +6 -0
  52. package/dist/sip-message/index.d.mts +6 -0
  53. package/dist/sip-message/index.mjs +6 -0
  54. package/dist/sip-message/outbound/index.cjs +10 -40
  55. package/dist/sip-message/outbound/index.d.cts +2 -0
  56. package/dist/sip-message/outbound/index.d.mts +2 -0
  57. package/dist/sip-message/outbound/index.mjs +11 -0
  58. package/dist/sip-message/outbound/request.cjs +20 -61
  59. package/dist/sip-message/outbound/request.d.cts +2 -0
  60. package/dist/sip-message/outbound/request.d.mts +2 -0
  61. package/dist/sip-message/outbound/request.mjs +21 -0
  62. package/dist/sip-message/outbound/response.cjs +25 -54
  63. package/dist/sip-message/outbound/response.d.cts +2 -0
  64. package/dist/sip-message/outbound/response.d.mts +2 -0
  65. package/dist/sip-message/outbound/response.mjs +26 -0
  66. package/dist/sip-message/response-codes.cjs +80 -100
  67. package/dist/sip-message/response-codes.d.cts +5 -0
  68. package/dist/sip-message/response-codes.d.mts +6 -0
  69. package/dist/sip-message/response-codes.mjs +82 -0
  70. package/dist/sip-message/sip-message.cjs +25 -52
  71. package/dist/sip-message/sip-message.d.cts +2 -0
  72. package/dist/sip-message/sip-message.d.mts +2 -0
  73. package/dist/sip-message/sip-message.mjs +26 -0
  74. package/dist/sip-message-B2D5MPBI.d.cts +13 -0
  75. package/dist/sip-message-PaPho4qU.d.mts +13 -0
  76. package/dist/types-DOQ9wmX6.d.mts +12 -0
  77. package/dist/types-DZxCsbZE.d.cts +12 -0
  78. package/dist/types.cjs +0 -15
  79. package/dist/types.d.cts +2 -0
  80. package/dist/types.d.mts +2 -0
  81. package/dist/types.mjs +1 -0
  82. package/dist/utils.cjs +27 -73
  83. package/dist/utils.d.cts +12 -0
  84. package/dist/utils.d.mts +12 -0
  85. package/dist/utils.mjs +25 -0
  86. package/package.json +14 -14
  87. package/dist/call-session/inbound.d.ts +0 -8
  88. package/dist/call-session/inbound.js +0 -64
  89. package/dist/call-session/index.d.ts +0 -46
  90. package/dist/call-session/index.js +0 -248
  91. package/dist/call-session/outbound.d.ts +0 -11
  92. package/dist/call-session/outbound.js +0 -71
  93. package/dist/call-session/streamer.d.ts +0 -17
  94. package/dist/call-session/streamer.js +0 -83
  95. package/dist/codec.d.ts +0 -15
  96. package/dist/codec.js +0 -66
  97. package/dist/dtmf.d.ts +0 -7
  98. package/dist/dtmf.js +0 -47
  99. package/dist/index.d.ts +0 -28
  100. package/dist/index.js +0 -242
  101. package/dist/sip-message/inbound/index.d.ts +0 -5
  102. package/dist/sip-message/inbound/index.js +0 -22
  103. package/dist/sip-message/index.d.ts +0 -5
  104. package/dist/sip-message/index.js +0 -12
  105. package/dist/sip-message/outbound/index.d.ts +0 -5
  106. package/dist/sip-message/outbound/index.js +0 -12
  107. package/dist/sip-message/outbound/request.d.ts +0 -7
  108. package/dist/sip-message/outbound/request.js +0 -33
  109. package/dist/sip-message/outbound/response.d.ts +0 -6
  110. package/dist/sip-message/outbound/response.js +0 -26
  111. package/dist/sip-message/response-codes.d.ts +0 -4
  112. package/dist/sip-message/response-codes.js +0 -83
  113. package/dist/sip-message/sip-message.d.ts +0 -11
  114. package/dist/sip-message/sip-message.js +0 -34
  115. package/dist/types.d.ts +0 -9
  116. package/dist/types.js +0 -0
  117. package/dist/utils.d.ts +0 -8
  118. package/dist/utils.js +0 -41
package/dist/utils.cjs CHANGED
@@ -1,80 +1,34 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
- var 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
- const ha1 = md5(
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
- const authObj = {
51
- "Digest algorithm": "MD5",
52
- username: sipInfo.authorizationId,
53
- realm: sipInfo.domain,
54
- nonce,
55
- uri: `sip:${sipInfo.domain}`,
56
- response: generateResponse(
57
- sipInfo,
58
- `${method}:sip:${sipInfo.domain}`,
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 = () => import_node_crypto.default.randomUUID();
21
+ const uuid = () => node_crypto.default.randomUUID();
65
22
  const branch = () => `z9hG4bK-${uuid()}`;
66
- const randomInt = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024;
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 keyAndSalt = import_node_crypto.default.randomBytes(30);
70
- const localKey = keyAndSalt.toString("base64").replace(/=+$/, "");
71
- // Annotate the CommonJS export names for ESM import in node:
72
- 0 && (module.exports = {
73
- branch,
74
- extractAddress,
75
- generateAuthorization,
76
- localKey,
77
- randomInt,
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;
@@ -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 };
@@ -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.4",
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.ts",
7
+ "types": "dist/index.d.mts",
8
8
  "main": "dist/index.cjs",
9
- "module": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
10
  "files": [
11
11
  "dist"
12
12
  ],
13
13
  "exports": {
14
14
  ".": {
15
- "types": "./dist/index.d.ts",
16
- "import": "./dist/index.js",
15
+ "types": "./dist/index.d.mts",
16
+ "import": "./dist/index.mjs",
17
17
  "require": "./dist/index.cjs",
18
- "default": "./dist/index.js"
18
+ "default": "./dist/index.mjs"
19
19
  },
20
20
  "./*": {
21
- "types": "./dist/*.d.ts",
22
- "import": "./dist/*.js",
21
+ "types": "./dist/*.d.mts",
22
+ "import": "./dist/*.mjs",
23
23
  "require": "./dist/*.cjs",
24
- "default": "./dist/*.js"
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": "tsup && tsc -p tsconfig.json --emitDeclarationOnly",
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.0",
40
+ "wait-for-async": "^0.8.1",
41
41
  "werift-rtp": "^0.8.8"
42
42
  },
43
43
  "devDependencies": {
44
- "@biomejs/biome": "^2.4.1",
44
+ "@biomejs/biome": "^2.4.12",
45
45
  "@types/node": "^25.6.0",
46
46
  "dotenv-override-true": "^6.2.2",
47
- "tsup": "^8.5.1",
47
+ "tsdown": "^0.21.10",
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^6.0.3",
50
- "yarn-upgrade-all": "^0.7.5"
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;