ringcentral-softphone 1.3.2 → 1.3.3

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 (98) hide show
  1. package/README.md +1 -1
  2. package/dist/call-session/inbound.cjs +93 -0
  3. package/dist/call-session/inbound.js +64 -0
  4. package/dist/call-session/index.cjs +270 -0
  5. package/dist/{esm/call-session → call-session}/index.d.ts +3 -3
  6. package/dist/call-session/index.js +244 -0
  7. package/dist/call-session/outbound.cjs +100 -0
  8. package/dist/call-session/outbound.js +71 -0
  9. package/dist/call-session/streamer.cjs +112 -0
  10. package/dist/call-session/streamer.js +83 -0
  11. package/dist/codec.cjs +85 -0
  12. package/dist/codec.js +66 -0
  13. package/dist/dtmf.cjs +65 -0
  14. package/dist/dtmf.js +46 -0
  15. package/dist/index.cjs +258 -0
  16. package/dist/{cjs/index.d.ts → index.d.ts} +3 -3
  17. package/dist/index.js +240 -0
  18. package/dist/sip-message/inbound/index.cjs +51 -0
  19. package/dist/sip-message/inbound/index.js +22 -0
  20. package/dist/sip-message/index.cjs +49 -0
  21. package/dist/sip-message/index.js +12 -0
  22. package/dist/sip-message/outbound/index.cjs +41 -0
  23. package/dist/sip-message/outbound/index.js +12 -0
  24. package/dist/sip-message/outbound/request.cjs +62 -0
  25. package/dist/sip-message/outbound/request.js +33 -0
  26. package/dist/sip-message/outbound/response.cjs +55 -0
  27. package/dist/sip-message/outbound/response.js +26 -0
  28. package/dist/sip-message/response-codes.cjs +102 -0
  29. package/dist/sip-message/response-codes.js +83 -0
  30. package/dist/sip-message/sip-message.cjs +53 -0
  31. package/dist/sip-message/sip-message.js +34 -0
  32. package/dist/types.cjs +15 -0
  33. package/dist/types.js +0 -0
  34. package/dist/utils.cjs +80 -0
  35. package/dist/{cjs/utils.d.ts → utils.d.ts} +2 -2
  36. package/dist/utils.js +41 -0
  37. package/package.json +19 -13
  38. package/dist/cjs/call-session/inbound.js +0 -57
  39. package/dist/cjs/call-session/index.d.ts +0 -44
  40. package/dist/cjs/call-session/index.js +0 -239
  41. package/dist/cjs/call-session/outbound.js +0 -66
  42. package/dist/cjs/call-session/streamer.d.ts +0 -17
  43. package/dist/cjs/call-session/streamer.js +0 -76
  44. package/dist/cjs/codec.js +0 -65
  45. package/dist/cjs/dtmf.js +0 -45
  46. package/dist/cjs/index.js +0 -209
  47. package/dist/cjs/sip-message/inbound/index.js +0 -22
  48. package/dist/cjs/sip-message/index.d.ts +0 -5
  49. package/dist/cjs/sip-message/index.js +0 -16
  50. package/dist/cjs/sip-message/outbound/index.js +0 -14
  51. package/dist/cjs/sip-message/outbound/request.js +0 -28
  52. package/dist/cjs/sip-message/outbound/response.js +0 -25
  53. package/dist/cjs/sip-message/response-codes.js +0 -83
  54. package/dist/cjs/sip-message/sip-message.js +0 -34
  55. package/dist/cjs/types.js +0 -2
  56. package/dist/cjs/utils.js +0 -40
  57. package/dist/esm/call-session/inbound.d.ts +0 -8
  58. package/dist/esm/call-session/inbound.js +0 -52
  59. package/dist/esm/call-session/index.js +0 -234
  60. package/dist/esm/call-session/outbound.d.ts +0 -11
  61. package/dist/esm/call-session/outbound.js +0 -61
  62. package/dist/esm/call-session/streamer.js +0 -71
  63. package/dist/esm/codec.d.ts +0 -15
  64. package/dist/esm/codec.js +0 -63
  65. package/dist/esm/dtmf.d.ts +0 -8
  66. package/dist/esm/dtmf.js +0 -43
  67. package/dist/esm/index.d.ts +0 -28
  68. package/dist/esm/index.js +0 -204
  69. package/dist/esm/sip-message/inbound/index.d.ts +0 -5
  70. package/dist/esm/sip-message/inbound/index.js +0 -17
  71. package/dist/esm/sip-message/index.js +0 -5
  72. package/dist/esm/sip-message/outbound/index.d.ts +0 -5
  73. package/dist/esm/sip-message/outbound/index.js +0 -9
  74. package/dist/esm/sip-message/outbound/request.d.ts +0 -7
  75. package/dist/esm/sip-message/outbound/request.js +0 -23
  76. package/dist/esm/sip-message/outbound/response.d.ts +0 -6
  77. package/dist/esm/sip-message/outbound/response.js +0 -20
  78. package/dist/esm/sip-message/response-codes.d.ts +0 -4
  79. package/dist/esm/sip-message/response-codes.js +0 -81
  80. package/dist/esm/sip-message/sip-message.d.ts +0 -11
  81. package/dist/esm/sip-message/sip-message.js +0 -32
  82. package/dist/esm/types.d.ts +0 -9
  83. package/dist/esm/types.js +0 -1
  84. package/dist/esm/utils.d.ts +0 -8
  85. package/dist/esm/utils.js +0 -28
  86. package/dist/{cjs/call-session → call-session}/inbound.d.ts +2 -2
  87. package/dist/{cjs/call-session → call-session}/outbound.d.ts +2 -2
  88. package/dist/{esm/call-session → call-session}/streamer.d.ts +1 -1
  89. package/dist/{cjs/codec.d.ts → codec.d.ts} +0 -0
  90. package/dist/{cjs/dtmf.d.ts → dtmf.d.ts} +0 -0
  91. package/dist/{cjs/sip-message → sip-message}/inbound/index.d.ts +0 -0
  92. package/dist/{esm/sip-message → sip-message}/index.d.ts +2 -2
  93. package/dist/{cjs/sip-message → sip-message}/outbound/index.d.ts +0 -0
  94. package/dist/{cjs/sip-message → sip-message}/outbound/request.d.ts +0 -0
  95. package/dist/{cjs/sip-message → sip-message}/outbound/response.d.ts +1 -1
  96. /package/dist/{cjs/sip-message → sip-message}/response-codes.d.ts +0 -0
  97. /package/dist/{cjs/sip-message → sip-message}/sip-message.d.ts +0 -0
  98. /package/dist/{cjs/types.d.ts → types.d.ts} +0 -0
package/dist/utils.cjs ADDED
@@ -0,0 +1,80 @@
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");
41
+ 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;
48
+ };
49
+ 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(", ");
63
+ };
64
+ const uuid = () => import_node_crypto.default.randomUUID();
65
+ const branch = () => `z9hG4bK-${uuid()}`;
66
+ const randomInt = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024;
67
+ const withoutTag = (s) => s.replace(/;tag=.*$/, "");
68
+ 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
+ });
@@ -1,8 +1,8 @@
1
- import { SoftPhoneOptions } from "./types.js";
1
+ import type { SoftPhoneOptions } from "./types.js";
2
2
  export declare const generateAuthorization: (sipInfo: SoftPhoneOptions, nonce: string, method: "REGISTER" | "INVITE") => string;
3
3
  export declare const uuid: () => `${string}-${string}-${string}-${string}-${string}`;
4
4
  export declare const branch: () => string;
5
5
  export declare const randomInt: () => number;
6
6
  export declare const withoutTag: (s: string) => string;
7
- export declare const extractAddress: (s: string) => string;
7
+ export declare const extractAddress: (s: string) => string | undefined;
8
8
  export declare const localKey: string;
package/dist/utils.js ADDED
@@ -0,0 +1,41 @@
1
+ import crypto from "node:crypto";
2
+ const md5 = (s) => crypto.createHash("md5").update(s).digest("hex");
3
+ const generateResponse = (sipInfo, endpoint, nonce) => {
4
+ const ha1 = md5(
5
+ `${sipInfo.authorizationId}:${sipInfo.domain}:${sipInfo.password}`
6
+ );
7
+ const ha2 = md5(endpoint);
8
+ const response = md5(`${ha1}:${nonce}:${ha2}`);
9
+ return response;
10
+ };
11
+ const generateAuthorization = (sipInfo, nonce, method) => {
12
+ const authObj = {
13
+ "Digest algorithm": "MD5",
14
+ username: sipInfo.authorizationId,
15
+ realm: sipInfo.domain,
16
+ nonce,
17
+ uri: `sip:${sipInfo.domain}`,
18
+ response: generateResponse(
19
+ sipInfo,
20
+ `${method}:sip:${sipInfo.domain}`,
21
+ nonce
22
+ )
23
+ };
24
+ return Object.entries(authObj).map(([key, value]) => `${key}="${value}"`).join(", ");
25
+ };
26
+ const uuid = () => crypto.randomUUID();
27
+ const branch = () => `z9hG4bK-${uuid()}`;
28
+ const randomInt = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024;
29
+ const withoutTag = (s) => s.replace(/;tag=.*$/, "");
30
+ const extractAddress = (s) => s.match(/<(sip:.+?)>/)?.[1];
31
+ const keyAndSalt = crypto.randomBytes(30);
32
+ const localKey = keyAndSalt.toString("base64").replace(/=+$/, "");
33
+ export {
34
+ branch,
35
+ extractAddress,
36
+ generateAuthorization,
37
+ localKey,
38
+ randomInt,
39
+ uuid,
40
+ withoutTag
41
+ };
package/package.json CHANGED
@@ -1,24 +1,27 @@
1
1
  {
2
2
  "name": "ringcentral-softphone",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "homepage": "https://github.com/ringcentral/ringcentral-softphone-ts",
5
5
  "license": "MIT",
6
- "types": "dist/esm/index.d.ts",
7
- "module": "dist/esm/index.js",
8
- "main": "dist/cjs/index.js",
6
+ "type": "module",
7
+ "types": "dist/index.d.ts",
8
+ "main": "dist/index.cjs",
9
+ "module": "dist/index.js",
9
10
  "files": [
10
11
  "dist"
11
12
  ],
12
13
  "exports": {
13
14
  ".": {
14
- "types": "./dist/esm/index.d.ts",
15
- "import": "./dist/esm/index.js",
16
- "require": "./dist/cjs/index.js"
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs",
18
+ "default": "./dist/index.js"
17
19
  },
18
20
  "./*": {
19
- "types": "./dist/esm/*.d.ts",
20
- "import": "./dist/esm/*.js",
21
- "require": "./dist/cjs/*.js"
21
+ "types": "./dist/*.d.ts",
22
+ "import": "./dist/*.js",
23
+ "require": "./dist/*.cjs",
24
+ "default": "./dist/*.js"
22
25
  }
23
26
  },
24
27
  "scripts": {
@@ -26,7 +29,8 @@
26
29
  "out": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/outbound-call.ts",
27
30
  "join": "rm -rf *.wav && tsx -r dotenv-override-true/config demos/join-rcv-meeting.ts",
28
31
  "multi": "tsx -r dotenv-override-true/config demos/multiple-calls-sequentially.ts",
29
- "build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json",
32
+ "lint": "biome check --write .",
33
+ "build": "tsup && tsc -p tsconfig.json --emitDeclarationOnly",
30
34
  "prepublishOnly": "yarn build",
31
35
  "postpublish": "rm -rf dist"
32
36
  },
@@ -37,10 +41,12 @@
37
41
  "werift-rtp": "^0.8.8"
38
42
  },
39
43
  "devDependencies": {
40
- "@types/node": "^25.2.0",
44
+ "@biomejs/biome": "^2.4.1",
45
+ "@types/node": "^25.6.0",
41
46
  "dotenv-override-true": "^6.2.2",
47
+ "tsup": "^8.5.1",
42
48
  "tsx": "^4.21.0",
43
- "typescript": "^5.9.3",
49
+ "typescript": "^6.0.3",
44
50
  "yarn-upgrade-all": "^0.7.5"
45
51
  },
46
52
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
@@ -1,57 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = __importDefault(require("./index.js"));
7
- const index_js_2 = require("../sip-message/index.js");
8
- const utils_js_1 = require("../utils.js");
9
- class InboundCallSession extends index_js_1.default {
10
- constructor(softphone, inviteMessage) {
11
- super(softphone, inviteMessage);
12
- this.localPeer = inviteMessage.headers.To;
13
- this.remotePeer = inviteMessage.headers.From;
14
- // inbound call from call queue, invite message may not have body
15
- if (inviteMessage.body.length > 0) {
16
- this.remoteKey = inviteMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
17
- }
18
- }
19
- async answer() {
20
- const answerSDP = `
21
- v=0
22
- o=- ${Date.now()} 0 IN IP4 ${this.softphone.client.localAddress}
23
- s=rc-softphone-ts
24
- c=IN IP4 ${this.softphone.client.localAddress}
25
- t=0 0
26
- m=audio ${(0, utils_js_1.randomInt)()} RTP/SAVP ${this.softphone.codec.id} 101
27
- a=rtpmap:${this.softphone.codec.id} ${this.softphone.codec.name}
28
- a=rtpmap:101 telephone-event/8000
29
- a=fmtp:101 0-15
30
- a=sendrecv
31
- a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
32
- `.trim();
33
- this.sdp = answerSDP;
34
- const newMessage = new index_js_2.OutboundMessage("SIP/2.0 200 OK", {
35
- Via: this.sipMessage.headers.Via,
36
- "Call-ID": this.sipMessage.getHeader("Call-ID"),
37
- From: this.sipMessage.headers.From,
38
- To: this.sipMessage.headers.To,
39
- CSeq: this.sipMessage.headers.CSeq,
40
- Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
41
- Allow: "PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS",
42
- Supported: "replaces, 100rel, timer, norefersub",
43
- "Session-Expires": "14400;refresher=uac",
44
- Require: "timer",
45
- "Content-Type": "application/sdp",
46
- }, answerSDP);
47
- const ackMessage = await this.softphone.send(newMessage, true);
48
- // for inbound call from call queue, ack message may HAVE body (while invite message has no body)
49
- if (ackMessage.body.length > 0) {
50
- this.remoteIP = ackMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
51
- this.remotePort = parseInt(ackMessage.body.match(/m=audio (\d+) /)[1], 10);
52
- this.remoteKey = ackMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
53
- }
54
- this.startLocalServices();
55
- }
56
- }
57
- exports.default = InboundCallSession;
@@ -1,44 +0,0 @@
1
- import dgram from "node:dgram";
2
- import EventEmitter from "node:events";
3
- import { Buffer } from "node:buffer";
4
- import { RtpPacket, SrtpSession } from "werift-rtp";
5
- import { type InboundMessage } from "../sip-message/index.js";
6
- import type Softphone from "../index.js";
7
- import Streamer from "./streamer.js";
8
- declare abstract class CallSession extends EventEmitter {
9
- softphone: Softphone;
10
- sipMessage: InboundMessage;
11
- socket: dgram.Socket;
12
- localPeer: string;
13
- remotePeer: string;
14
- remoteIP: string;
15
- remotePort: number;
16
- disposed: boolean;
17
- srtpSession: SrtpSession;
18
- encoder: {
19
- encode: (pcm: Buffer) => Buffer;
20
- };
21
- decoder: {
22
- decode: (audio: Buffer) => Buffer;
23
- };
24
- sdp: string;
25
- ssrc: number;
26
- sequenceNumber: number;
27
- timestamp: number;
28
- constructor(softphone: Softphone, sipMessage: InboundMessage);
29
- set remoteKey(key: string);
30
- get callId(): string;
31
- send(data: string | Buffer): void;
32
- hangup(): Promise<void>;
33
- sendDTMF(char: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "*" | "#"): void;
34
- sendDTMFs(s: string, delay?: number): Promise<void>;
35
- streamAudio(input: Buffer): Streamer;
36
- sendPacket(rtpPacket: RtpPacket): void;
37
- protected startLocalServices(): void;
38
- protected dispose(): void;
39
- transfer(transferTo: string): Promise<void>;
40
- toggleReceive(toReceive: boolean): Promise<void>;
41
- hold(): Promise<void>;
42
- unhold(): Promise<void>;
43
- }
44
- export default CallSession;
@@ -1,239 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_dgram_1 = __importDefault(require("node:dgram"));
7
- const node_events_1 = __importDefault(require("node:events"));
8
- const node_buffer_1 = require("node:buffer");
9
- const werift_rtp_1 = require("werift-rtp");
10
- const dtmf_js_1 = __importDefault(require("../dtmf.js"));
11
- const index_js_1 = require("../sip-message/index.js");
12
- const utils_js_1 = require("../utils.js");
13
- const streamer_js_1 = __importDefault(require("./streamer.js"));
14
- const wait_for_async_1 = __importDefault(require("wait-for-async"));
15
- class CallSession extends node_events_1.default {
16
- softphone;
17
- sipMessage;
18
- socket;
19
- localPeer;
20
- remotePeer;
21
- remoteIP;
22
- remotePort;
23
- disposed = false;
24
- srtpSession;
25
- encoder;
26
- decoder;
27
- sdp;
28
- // for audio streaming
29
- ssrc = (0, utils_js_1.randomInt)();
30
- sequenceNumber = (0, utils_js_1.randomInt)();
31
- timestamp = (0, utils_js_1.randomInt)();
32
- constructor(softphone, sipMessage) {
33
- super();
34
- this.softphone = softphone;
35
- this.encoder = softphone.codec.createEncoder();
36
- this.decoder = softphone.codec.createDecoder();
37
- this.sipMessage = sipMessage;
38
- // inbound call from call queue, invite message may not have body
39
- if (this.sipMessage.body.length > 0) {
40
- this.remoteIP = this.sipMessage.body.match(/c=IN IP4 ([\d.]+)/)[1];
41
- this.remotePort = parseInt(this.sipMessage.body.match(/m=audio (\d+) /)[1], 10);
42
- }
43
- }
44
- set remoteKey(key) {
45
- const localKeyBuffer = node_buffer_1.Buffer.from(utils_js_1.localKey, "base64");
46
- const remoteKeyBuffer = node_buffer_1.Buffer.from(key, "base64");
47
- this.srtpSession = new werift_rtp_1.SrtpSession({
48
- profile: 0x0001,
49
- keys: {
50
- localMasterKey: localKeyBuffer.subarray(0, 16),
51
- localMasterSalt: localKeyBuffer.subarray(16, 30),
52
- remoteMasterKey: remoteKeyBuffer.subarray(0, 16),
53
- remoteMasterSalt: remoteKeyBuffer.subarray(16, 30),
54
- },
55
- });
56
- }
57
- get callId() {
58
- return this.sipMessage.getHeader("Call-ID");
59
- }
60
- send(data) {
61
- this.socket.send(data, this.remotePort, this.remoteIP);
62
- }
63
- async hangup() {
64
- const requestMessage = new index_js_1.RequestMessage(`BYE sip:${this.softphone.sipInfo.domain} SIP/2.0`, {
65
- "Call-ID": this.callId,
66
- From: this.localPeer,
67
- To: this.remotePeer,
68
- Via: `SIP/2.0/TLS ${this.softphone.fakeDomain};branch=${(0, utils_js_1.branch)()}`,
69
- });
70
- await this.softphone.send(requestMessage);
71
- }
72
- sendDTMF(char) {
73
- const payloads = dtmf_js_1.default.charToPayloads(char);
74
- const timestamp = this.timestamp;
75
- let first = true;
76
- for (const payload of payloads) {
77
- const rtpHeader = new werift_rtp_1.RtpHeader({
78
- version: 2,
79
- padding: false,
80
- paddingSize: 0,
81
- extension: false,
82
- marker: first,
83
- payloadOffset: 12,
84
- payloadType: 101,
85
- sequenceNumber: this.sequenceNumber,
86
- timestamp,
87
- ssrc: this.ssrc,
88
- csrcLength: 0,
89
- csrc: [],
90
- extensionProfile: 48862,
91
- extensionLength: undefined,
92
- extensions: [],
93
- });
94
- const rtpPacket = new werift_rtp_1.RtpPacket(rtpHeader, payload);
95
- this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
96
- this.sequenceNumber = (this.sequenceNumber + 1) % 65536;
97
- first = false;
98
- }
99
- this.timestamp += 800;
100
- }
101
- async sendDTMFs(s, delay = 500) {
102
- for (const c of s) {
103
- this.sendDTMF(c);
104
- await (0, wait_for_async_1.default)({ interval: delay });
105
- }
106
- }
107
- // buffer is the content of a audio file, it is supposed to be uncompressed PCM data
108
- // The audio should be playable by command: play -t raw -b 16 -r 16000 -e signed-integer test.wav
109
- streamAudio(input) {
110
- const streamer = new streamer_js_1.default(this, input);
111
- streamer.start();
112
- return streamer;
113
- }
114
- // send a single rtp packet
115
- sendPacket(rtpPacket) {
116
- if (this.disposed) {
117
- return;
118
- }
119
- this.send(this.srtpSession.encrypt(rtpPacket.payload, rtpPacket.header));
120
- }
121
- startLocalServices() {
122
- this.socket = node_dgram_1.default.createSocket("udp4");
123
- this.socket.on("message", (message) => {
124
- const rtpPacket = werift_rtp_1.RtpPacket.deSerialize(this.srtpSession.decrypt(message));
125
- this.emit("rtpPacket", rtpPacket);
126
- if (rtpPacket.header.payloadType === 101) {
127
- this.emit("dtmfPacket", rtpPacket);
128
- const char = dtmf_js_1.default.payloadToChar(rtpPacket.payload);
129
- if (char) {
130
- this.emit("dtmf", char);
131
- }
132
- }
133
- else if (rtpPacket.header.payloadType === this.softphone.codec.id) {
134
- if (rtpPacket.payload.length === 4 &&
135
- rtpPacket.payload[0] >= 0x00 &&
136
- rtpPacket.payload[0] < 0x0c &&
137
- rtpPacket.payload[1] === 0x8a &&
138
- rtpPacket.payload[2] === 0x03 &&
139
- rtpPacket.payload[3] === 0xc0) {
140
- // special DTMF packet in audio format
141
- // first byte 0x00 to 0x0c means DTMF 0 to 9, *, #
142
- // we ignore it since DTMF is handled by `if (rtpPacket.header.payloadType === 101) {`
143
- return; // ignore it
144
- }
145
- try {
146
- rtpPacket.payload = this.decoder.decode(rtpPacket.payload);
147
- this.emit("audioPacket", rtpPacket);
148
- }
149
- catch {
150
- console.error("Audio packet decode failed", rtpPacket);
151
- }
152
- }
153
- });
154
- // as I tested, we can use a random port here and it still works
155
- // but it seems that in SDP we need to tell remote our local IP Address, not 127.0.0.1
156
- this.socket.bind(); // random port
157
- // send a message to remote server so that it knows where to reply
158
- this.send("hello");
159
- const byeHandler = (inboundMessage) => {
160
- if (inboundMessage.getHeader("Call-ID") !== this.callId) {
161
- return;
162
- }
163
- if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
164
- this.softphone.off("message", byeHandler);
165
- this.dispose();
166
- }
167
- };
168
- this.softphone.on("message", byeHandler);
169
- }
170
- dispose() {
171
- this.disposed = true;
172
- this.emit("disposed");
173
- this.removeAllListeners();
174
- this.socket?.removeAllListeners();
175
- this.socket?.close();
176
- }
177
- async transfer(transferTo) {
178
- const requestMessage = new index_js_1.RequestMessage(`REFER sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.outboundProxy};transport=tls SIP/2.0`, {
179
- Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, utils_js_1.branch)()};alias`,
180
- "Max-Forwards": 70,
181
- From: this.localPeer,
182
- To: this.remotePeer,
183
- Contact: `<sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
184
- "Call-ID": this.callId,
185
- Event: "refer",
186
- Expires: 600,
187
- Supported: "replaces, 100rel, timer, norefersub",
188
- Accept: "message/sipfrag;version=2.0",
189
- "Allow-Events": "presence, message-summary, refer",
190
- "Refer-To": `sip:${transferTo}@${this.softphone.sipInfo.domain}`,
191
- "Referred-By": `<sip:${this.softphone.sipInfo.username}@${this.softphone.sipInfo.domain}>`,
192
- });
193
- await this.softphone.send(requestMessage);
194
- return new Promise((resolve) => {
195
- const notifyHandler = (inboundMessage) => {
196
- if (!inboundMessage.subject.startsWith("NOTIFY ")) {
197
- return;
198
- }
199
- const responseMessage = new index_js_1.ResponseMessage(inboundMessage, 200);
200
- this.softphone.send(responseMessage);
201
- if (inboundMessage.body.trim() === "SIP/2.0 200 OK") {
202
- this.softphone.off("message", notifyHandler);
203
- resolve();
204
- }
205
- };
206
- this.softphone.on("message", notifyHandler);
207
- });
208
- }
209
- async toggleReceive(toReceive) {
210
- let newSDP = this.sdp;
211
- if (!toReceive) {
212
- newSDP = newSDP.replace(/a=sendrecv/, "a=sendonly");
213
- }
214
- const requestMessage = new index_js_1.RequestMessage(`INVITE ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
215
- "Call-Id": this.callId,
216
- From: this.localPeer,
217
- To: this.remotePeer,
218
- Via: `SIP/2.0/TLS ${this.softphone.client.localAddress}:${this.softphone.client.localPort};rport;branch=${(0, utils_js_1.branch)()};alias`,
219
- "Content-Type": "application/sdp",
220
- Contact: ` <sip:${this.softphone.sipInfo.username}@${this.softphone.client.localAddress}:${this.softphone.client.localPort};transport=TLS;ob>`,
221
- }, newSDP);
222
- const replyMessage = await this.softphone.send(requestMessage, true);
223
- const ackMessage = new index_js_1.RequestMessage(`ACK ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
224
- "Call-Id": this.callId,
225
- From: this.localPeer,
226
- To: this.remotePeer,
227
- Via: replyMessage.headers.Via,
228
- CSeq: replyMessage.headers.CSeq.replace(" INVITE", " ACK"),
229
- });
230
- await this.softphone.send(ackMessage);
231
- }
232
- async hold() {
233
- return this.toggleReceive(false);
234
- }
235
- async unhold() {
236
- return this.toggleReceive(true);
237
- }
238
- }
239
- exports.default = CallSession;
@@ -1,66 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = __importDefault(require("./index.js"));
7
- const index_js_2 = require("../sip-message/index.js");
8
- const utils_js_1 = require("../utils.js");
9
- class OutboundCallSession extends index_js_1.default {
10
- constructor(softphone, answerMessage) {
11
- super(softphone, answerMessage);
12
- this.localPeer = answerMessage.headers.From;
13
- this.remotePeer = answerMessage.headers.To;
14
- this.remoteKey = answerMessage.body.match(/AES_CM_128_HMAC_SHA1_80 inline:([\w+/]+)/)[1];
15
- this.init();
16
- }
17
- init() {
18
- // wait for user to answer the call
19
- const answerHandler = (message) => {
20
- if (message.headers.CSeq !== this.sipMessage.headers.CSeq) {
21
- return;
22
- }
23
- if (message.subject.startsWith("SIP/2.0 486")) {
24
- this.softphone.off("message", answerHandler);
25
- this.emit("busy");
26
- this.dispose();
27
- return;
28
- }
29
- if (message.subject.startsWith("SIP/2.0 200")) {
30
- this.softphone.off("message", answerHandler);
31
- this.emit("answered");
32
- const ackMessage = new index_js_2.RequestMessage(`ACK ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
33
- "Call-ID": this.callId,
34
- From: this.localPeer,
35
- To: this.remotePeer,
36
- Via: this.sipMessage.headers.Via,
37
- CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"),
38
- });
39
- this.softphone.send(ackMessage);
40
- }
41
- };
42
- this.softphone.on("message", answerHandler);
43
- this.once("answered", () => this.startLocalServices());
44
- }
45
- async cancel() {
46
- const requestMessage = new index_js_2.RequestMessage(`CANCEL ${(0, utils_js_1.extractAddress)(this.remotePeer)} SIP/2.0`, {
47
- "Call-ID": this.callId,
48
- From: this.localPeer,
49
- To: (0, utils_js_1.withoutTag)(this.remotePeer),
50
- Via: this.sipMessage.headers.Via,
51
- CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"),
52
- });
53
- await this.softphone.send(requestMessage);
54
- }
55
- get sessionId() {
56
- const header = this.sipMessage.headers["p-rc-api-ids"];
57
- let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
58
- return match[2];
59
- }
60
- get partyId() {
61
- const header = this.sipMessage.headers["p-rc-api-ids"];
62
- let match = header.match(/party-id=([^;]+);session-id=([^;]+)/);
63
- return match[1];
64
- }
65
- }
66
- exports.default = OutboundCallSession;
@@ -1,17 +0,0 @@
1
- import EventEmitter from "node:events";
2
- import { Buffer } from "node:buffer";
3
- import type CallSession from "./index.js";
4
- declare class Streamer extends EventEmitter {
5
- paused: boolean;
6
- private callSession;
7
- private buffer;
8
- private originalBuffer;
9
- constructor(callSesstion: CallSession, buffer: Buffer);
10
- start(): void;
11
- stop(): void;
12
- pause(): void;
13
- resume(): void;
14
- get finished(): boolean;
15
- private sendPacket;
16
- }
17
- export default Streamer;