ringcentral-softphone 1.1.7 → 1.1.9

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/README.md CHANGED
@@ -6,6 +6,9 @@ the
6
6
 
7
7
  Users are recommended to use this SDK instead of the JavaScript SDK.
8
8
 
9
+ This SDK allows you to create a softphone without GUI that runs on server-side
10
+ without a web browser.
11
+
9
12
  ## Installation
10
13
 
11
14
  ```
@@ -19,19 +22,39 @@ yarn install ringcentral-softphone
19
22
  1. Login to https://service.ringcentral.com
20
23
  2. Find the user/extension you want to use
21
24
  3. Check the user's "Devices & Numbers"
22
- 4. Find a phone/device that you want to use
23
- 5. if there is none, you need to create one. Check steps below for more details
24
- 6. Click the "Set Up and Provision" button
25
- 7. Click the link "Set up manually using SIP"
26
- 8. You will find "SIP Domain", "Outbound Proxy", "User Name", "Password" and
25
+ 4. Find a phone/device that you want to use (Phone type **must** be "Existing
26
+ Phone"), if there is none, you need to create one.
27
+ 5. Click the "Set Up and Provision" button
28
+ 6. Click the link "Set up manually using SIP"
29
+ 7. You will find "SIP Domain", "Outbound Proxy", "User Name", "Password" and
27
30
  "Authorization ID"
28
31
 
29
32
  Please note that, "SIP Domain" name should come without port number. I don't
30
33
  know why it shows a port number on the page. This SDK requires a "domain" which
31
34
  is "SIP Domain" but without the port number.
32
35
 
36
+ Please also note that, not every device/phone can be used with the softphone
37
+ SDK. Some phones/devices with type "RingCentral Phone app" cannot be used with
38
+ the softphone SDK. You will need to have a device/phone with type **"Exsting
39
+ Phone"**.
40
+
33
41
  ### Programmatically
34
42
 
43
+ Invoke this API to list all devices under an extension:
44
+ https://developers.ringcentral.com/api-reference/Devices/listExtensionDevices
45
+
46
+ Please note that, not every device can be used for this softphone SDK. You will
47
+ need to find an device with **`type: 'OtherPhone'`**. Devices with
48
+ `type: 'SoftPhone'` can **NOT** be used for this softphone SDK.
49
+
50
+ I know this is confusing. `type: 'SoftPhone'` in API response is the same as
51
+ `type = "RingCentral Phone app"` in the GUI (mentioned in the Manually section
52
+ above). `type: 'OtherPhone'` in API response is the same as
53
+ `type = "Exiting Phone"` in the GUI.
54
+
55
+ If you cannot find an appropriate device, you will need to create a device
56
+ manually. Please refer to the previous section.
57
+
35
58
  Invoke this RESTful API:
36
59
  https://developers.ringcentral.com/api-reference/Devices/readDeviceSipInfo
37
60
 
@@ -246,6 +269,18 @@ However, for inbound calls, the server doesn't tell us anything about the
246
269
  Telephony Session ID. Here is a workaround solution:
247
270
  https://github.com/tylerlong/rc-softphone-call-id-test
248
271
 
272
+ ## Troubleshooting (Common issues)
273
+
274
+ ### `SIP/2.0 486 Busy Here` for outbound call
275
+
276
+ First of all, make sure that the target number is valid. If the target number is
277
+ invalid, you will get `SIP/2.0 486 Busy Here`.
278
+
279
+ Secondly, make sure that the device has a "Emergency Address" configured and
280
+ there is no complains about Emergency address by checking the details of the
281
+ device on https://service.ringcentral.com. It is an known issue that, if the
282
+ Emergency Address is not configured properly, outbound call will not work.
283
+
249
284
  ---
250
285
 
251
286
  ## Dev Notes
@@ -29,7 +29,7 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
29
29
  `.trim();
30
30
  const newMessage = new index_js_2.OutboundMessage("SIP/2.0 200 OK", {
31
31
  Via: this.sipMessage.headers.Via,
32
- "Call-ID": this.sipMessage.headers["Call-ID"],
32
+ "Call-ID": this.sipMessage.getHeader("Call-ID"),
33
33
  From: this.sipMessage.headers.From,
34
34
  To: this.sipMessage.headers.To,
35
35
  CSeq: this.sipMessage.headers.CSeq,
@@ -50,7 +50,7 @@ class CallSession extends node_events_1.default {
50
50
  });
51
51
  }
52
52
  get callId() {
53
- return this.sipMessage.headers["Call-ID"];
53
+ return this.sipMessage.getHeader("Call-ID");
54
54
  }
55
55
  send(data) {
56
56
  this.socket.send(data, this.remotePort, this.remoteIP);
@@ -143,7 +143,7 @@ class CallSession extends node_events_1.default {
143
143
  // send a message to remote server so that it knows where to reply
144
144
  this.send("hello");
145
145
  const byeHandler = (inboundMessage) => {
146
- if (inboundMessage.headers["Call-ID"] !== this.callId) {
146
+ if (inboundMessage.getHeader("Call-ID") !== this.callId) {
147
147
  return;
148
148
  }
149
149
  if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
package/dist/cjs/index.js CHANGED
@@ -80,8 +80,7 @@ class Softphone extends node_events_1.default {
80
80
  // sometimes the server will return 200 OK directly
81
81
  return;
82
82
  }
83
- const wwwAuth = inboundMessage.headers["Www-Authenticate"] ||
84
- inboundMessage.headers["WWW-Authenticate"];
83
+ const wwwAuth = inboundMessage.getHeader("Www-Authenticate");
85
84
  const nonce = wwwAuth.match(/, nonce="(.+?)"/)[1];
86
85
  const newMessage = requestMessage.fork();
87
86
  newMessage.headers.Authorization = (0, utils_js_1.generateAuthorization)(this.sipInfo, nonce, "REGISTER");
@@ -90,14 +89,14 @@ class Softphone extends node_events_1.default {
90
89
  await sipRegister();
91
90
  this.intervalHandle = setInterval(() => {
92
91
  sipRegister();
93
- }, 3 * 60 * 1000);
92
+ }, 30 * 1000);
94
93
  this.on("message", (inboundMessage) => {
95
94
  if (!inboundMessage.subject.startsWith("INVITE sip:")) {
96
95
  return;
97
96
  }
98
97
  const outboundMessage = new index_js_1.OutboundMessage("SIP/2.0 100 Trying", {
99
98
  Via: inboundMessage.headers.Via,
100
- "Call-ID": inboundMessage.headers["Call-ID"],
99
+ "Call-ID": inboundMessage.getHeader("Call-ID"),
101
100
  From: inboundMessage.headers.From,
102
101
  To: inboundMessage.headers.To,
103
102
  CSeq: inboundMessage.headers.CSeq,
@@ -181,7 +180,7 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${utils_js_1.localKey}
181
180
  "Content-Type": "application/sdp",
182
181
  }, offerSDP);
183
182
  const inboundMessage = await this.send(inviteMessage, true);
184
- const proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"];
183
+ const proxyAuthenticate = inboundMessage.getHeader("Proxy-Authenticate");
185
184
  const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1];
186
185
  const newMessage = inviteMessage.fork();
187
186
  newMessage.headers["Proxy-Authorization"] = (0, utils_js_1.generateAuthorization)(this.sipInfo, nonce, "INVITE");
@@ -6,5 +6,6 @@ declare class SipMessage {
6
6
  body: string;
7
7
  constructor(subject?: string, headers?: {}, body?: string);
8
8
  toString(): string;
9
+ getHeader(key: string): string | undefined;
9
10
  }
10
11
  export default SipMessage;
@@ -24,5 +24,11 @@ class SipMessage {
24
24
  ].join("\r\n");
25
25
  return r;
26
26
  }
27
+ getHeader(key) {
28
+ const foundKey = Object.keys(this.headers).find((k) => k.toLowerCase() === key.toLowerCase());
29
+ if (foundKey) {
30
+ return this.headers[foundKey];
31
+ }
32
+ }
27
33
  }
28
34
  exports.default = SipMessage;
@@ -24,7 +24,7 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
24
24
  `.trim();
25
25
  const newMessage = new OutboundMessage("SIP/2.0 200 OK", {
26
26
  Via: this.sipMessage.headers.Via,
27
- "Call-ID": this.sipMessage.headers["Call-ID"],
27
+ "Call-ID": this.sipMessage.getHeader("Call-ID"),
28
28
  From: this.sipMessage.headers.From,
29
29
  To: this.sipMessage.headers.To,
30
30
  CSeq: this.sipMessage.headers.CSeq,
@@ -45,7 +45,7 @@ class CallSession extends EventEmitter {
45
45
  });
46
46
  }
47
47
  get callId() {
48
- return this.sipMessage.headers["Call-ID"];
48
+ return this.sipMessage.getHeader("Call-ID");
49
49
  }
50
50
  send(data) {
51
51
  this.socket.send(data, this.remotePort, this.remoteIP);
@@ -138,7 +138,7 @@ class CallSession extends EventEmitter {
138
138
  // send a message to remote server so that it knows where to reply
139
139
  this.send("hello");
140
140
  const byeHandler = (inboundMessage) => {
141
- if (inboundMessage.headers["Call-ID"] !== this.callId) {
141
+ if (inboundMessage.getHeader("Call-ID") !== this.callId) {
142
142
  return;
143
143
  }
144
144
  if (inboundMessage.headers.CSeq.endsWith(" BYE")) {
package/dist/esm/index.js CHANGED
@@ -75,8 +75,7 @@ class Softphone extends EventEmitter {
75
75
  // sometimes the server will return 200 OK directly
76
76
  return;
77
77
  }
78
- const wwwAuth = inboundMessage.headers["Www-Authenticate"] ||
79
- inboundMessage.headers["WWW-Authenticate"];
78
+ const wwwAuth = inboundMessage.getHeader("Www-Authenticate");
80
79
  const nonce = wwwAuth.match(/, nonce="(.+?)"/)[1];
81
80
  const newMessage = requestMessage.fork();
82
81
  newMessage.headers.Authorization = generateAuthorization(this.sipInfo, nonce, "REGISTER");
@@ -85,14 +84,14 @@ class Softphone extends EventEmitter {
85
84
  await sipRegister();
86
85
  this.intervalHandle = setInterval(() => {
87
86
  sipRegister();
88
- }, 3 * 60 * 1000);
87
+ }, 30 * 1000);
89
88
  this.on("message", (inboundMessage) => {
90
89
  if (!inboundMessage.subject.startsWith("INVITE sip:")) {
91
90
  return;
92
91
  }
93
92
  const outboundMessage = new OutboundMessage("SIP/2.0 100 Trying", {
94
93
  Via: inboundMessage.headers.Via,
95
- "Call-ID": inboundMessage.headers["Call-ID"],
94
+ "Call-ID": inboundMessage.getHeader("Call-ID"),
96
95
  From: inboundMessage.headers.From,
97
96
  To: inboundMessage.headers.To,
98
97
  CSeq: inboundMessage.headers.CSeq,
@@ -176,7 +175,7 @@ a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${localKey}
176
175
  "Content-Type": "application/sdp",
177
176
  }, offerSDP);
178
177
  const inboundMessage = await this.send(inviteMessage, true);
179
- const proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"];
178
+ const proxyAuthenticate = inboundMessage.getHeader("Proxy-Authenticate");
180
179
  const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)[1];
181
180
  const newMessage = inviteMessage.fork();
182
181
  newMessage.headers["Proxy-Authorization"] = generateAuthorization(this.sipInfo, nonce, "INVITE");
@@ -6,5 +6,6 @@ declare class SipMessage {
6
6
  body: string;
7
7
  constructor(subject?: string, headers?: {}, body?: string);
8
8
  toString(): string;
9
+ getHeader(key: string): string | undefined;
9
10
  }
10
11
  export default SipMessage;
@@ -22,5 +22,11 @@ class SipMessage {
22
22
  ].join("\r\n");
23
23
  return r;
24
24
  }
25
+ getHeader(key) {
26
+ const foundKey = Object.keys(this.headers).find((k) => k.toLowerCase() === key.toLowerCase());
27
+ if (foundKey) {
28
+ return this.headers[foundKey];
29
+ }
30
+ }
25
31
  }
26
32
  export default SipMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ringcentral-softphone",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "homepage": "https://github.com/ringcentral/ringcentral-softphone-ts",
5
5
  "license": "MIT",
6
6
  "types": "dist/esm/index.d.ts",
@@ -34,10 +34,11 @@
34
34
  "werift-rtp": "^0.8.4"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/node": "^22.13.14",
37
+ "@types/node": "^22.15.18",
38
38
  "dotenv-override-true": "^6.2.2",
39
- "tsx": "^4.19.3",
40
- "typescript": "^5.8.2",
39
+ "tsx": "^4.19.4",
40
+ "typescript": "^5.8.3",
41
41
  "yarn-upgrade-all": "^0.7.5"
42
- }
42
+ },
43
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
43
44
  }