livekit-server-sdk 2.15.5 → 2.17.0
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/AgentDispatchClient.cjs +5 -3
- package/dist/AgentDispatchClient.cjs.map +1 -1
- package/dist/AgentDispatchClient.js +5 -3
- package/dist/AgentDispatchClient.js.map +1 -1
- package/dist/ClientOptions.cjs.map +1 -1
- package/dist/ClientOptions.d.cts +12 -0
- package/dist/ClientOptions.d.ts +12 -0
- package/dist/ClientOptions.d.ts.map +1 -1
- package/dist/ConnectorClient.cjs +9 -5
- package/dist/ConnectorClient.cjs.map +1 -1
- package/dist/ConnectorClient.d.cts +6 -3
- package/dist/ConnectorClient.d.ts +6 -3
- package/dist/ConnectorClient.d.ts.map +1 -1
- package/dist/ConnectorClient.js +9 -5
- package/dist/ConnectorClient.js.map +1 -1
- package/dist/EgressClient.cjs +5 -3
- package/dist/EgressClient.cjs.map +1 -1
- package/dist/EgressClient.js +5 -3
- package/dist/EgressClient.js.map +1 -1
- package/dist/IngressClient.cjs +5 -3
- package/dist/IngressClient.cjs.map +1 -1
- package/dist/IngressClient.js +5 -3
- package/dist/IngressClient.js.map +1 -1
- package/dist/LiveKitAPI.cjs +80 -0
- package/dist/LiveKitAPI.cjs.map +1 -0
- package/dist/LiveKitAPI.d.cts +77 -0
- package/dist/LiveKitAPI.d.ts +77 -0
- package/dist/LiveKitAPI.d.ts.map +1 -0
- package/dist/LiveKitAPI.js +56 -0
- package/dist/LiveKitAPI.js.map +1 -0
- package/dist/RoomServiceClient.cjs +5 -3
- package/dist/RoomServiceClient.cjs.map +1 -1
- package/dist/RoomServiceClient.js +5 -3
- package/dist/RoomServiceClient.js.map +1 -1
- package/dist/ServiceBase.cjs +9 -9
- package/dist/ServiceBase.cjs.map +1 -1
- package/dist/ServiceBase.d.cts +20 -1
- package/dist/ServiceBase.d.ts +20 -1
- package/dist/ServiceBase.d.ts.map +1 -1
- package/dist/ServiceBase.js +9 -9
- package/dist/ServiceBase.js.map +1 -1
- package/dist/SipClient.cjs +56 -21
- package/dist/SipClient.cjs.map +1 -1
- package/dist/SipClient.d.cts +22 -4
- package/dist/SipClient.d.ts +22 -4
- package/dist/SipClient.d.ts.map +1 -1
- package/dist/SipClient.js +58 -22
- package/dist/SipClient.js.map +1 -1
- package/dist/TwirpRPC.cjs +135 -48
- package/dist/TwirpRPC.cjs.map +1 -1
- package/dist/TwirpRPC.d.cts +38 -2
- package/dist/TwirpRPC.d.ts +38 -2
- package/dist/TwirpRPC.d.ts.map +1 -1
- package/dist/TwirpRPC.js +140 -38
- package/dist/TwirpRPC.js.map +1 -1
- package/dist/dialTimeout.cjs +39 -0
- package/dist/dialTimeout.cjs.map +1 -0
- package/dist/dialTimeout.d.cts +21 -0
- package/dist/dialTimeout.d.ts +21 -0
- package/dist/dialTimeout.d.ts.map +1 -0
- package/dist/dialTimeout.js +13 -0
- package/dist/dialTimeout.js.map +1 -0
- package/dist/failover.cjs +139 -0
- package/dist/failover.cjs.map +1 -0
- package/dist/failover.d.cts +30 -0
- package/dist/failover.d.ts +30 -0
- package/dist/failover.d.ts.map +1 -0
- package/dist/failover.js +107 -0
- package/dist/failover.js.map +1 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/version.cjs +29 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.d.cts +3 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -3
- package/src/AgentDispatchClient.ts +5 -5
- package/src/ClientOptions.ts +12 -0
- package/src/ConnectorClient.ts +20 -11
- package/src/EgressClient.ts +5 -5
- package/src/IngressClient.ts +5 -5
- package/src/LiveKitAPI.ts +126 -0
- package/src/RoomServiceClient.ts +5 -5
- package/src/ServiceBase.ts +35 -4
- package/src/SipClient.ts +89 -24
- package/src/TwirpRPC.test.ts +42 -0
- package/src/TwirpRPC.ts +187 -39
- package/src/dialTimeout.test.ts +41 -0
- package/src/dialTimeout.ts +37 -0
- package/src/failover.test.ts +188 -0
- package/src/failover.ts +170 -0
- package/src/index.ts +4 -1
- package/src/version.ts +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { AgentDispatchClient } from './AgentDispatchClient.js';
|
|
5
|
+
import type { ClientOptions } from './ClientOptions.js';
|
|
6
|
+
import { ConnectorClient } from './ConnectorClient.js';
|
|
7
|
+
import { EgressClient } from './EgressClient.js';
|
|
8
|
+
import { IngressClient } from './IngressClient.js';
|
|
9
|
+
import { RoomServiceClient } from './RoomServiceClient.js';
|
|
10
|
+
import { SipClient } from './SipClient.js';
|
|
11
|
+
|
|
12
|
+
/** Server host and non-auth options, shared by both authentication modes. */
|
|
13
|
+
interface LiveKitAPICommonOptions {
|
|
14
|
+
/** Server host, including protocol. Falls back to the `LIVEKIT_URL` env var. */
|
|
15
|
+
host?: string;
|
|
16
|
+
/** Optional timeout, in seconds, for all server requests. */
|
|
17
|
+
requestTimeout?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Whether to fail over to alternative regions on retryable errors (LiveKit
|
|
20
|
+
* Cloud hosts only). Defaults to true; set to false to disable.
|
|
21
|
+
*/
|
|
22
|
+
failover?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** API key and secret authentication (recommended for backend use). */
|
|
26
|
+
interface ApiKeyAuth {
|
|
27
|
+
/** API key. Falls back to the `LIVEKIT_API_KEY` env var. */
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** API secret. Falls back to the `LIVEKIT_API_SECRET` env var. */
|
|
30
|
+
secret?: string;
|
|
31
|
+
token?: never;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Pre-signed token authentication (client-side use; no secret required). */
|
|
35
|
+
interface TokenAuth {
|
|
36
|
+
/** Pre-signed token, sent verbatim. Falls back to the `LIVEKIT_TOKEN` env var. */
|
|
37
|
+
token: string;
|
|
38
|
+
apiKey?: never;
|
|
39
|
+
secret?: never;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Options for {@link LiveKitAPI}. Provide either an `apiKey` and `secret` or a
|
|
44
|
+
* pre-signed `token` — the two modes are mutually exclusive. Any omitted value
|
|
45
|
+
* falls back to its environment variable (`LIVEKIT_URL`, `LIVEKIT_API_KEY`,
|
|
46
|
+
* `LIVEKIT_API_SECRET`, `LIVEKIT_TOKEN`).
|
|
47
|
+
*/
|
|
48
|
+
export type LiveKitAPIOptions = LiveKitAPICommonOptions & (ApiKeyAuth | TokenAuth);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A single entry point to every LiveKit server API, exposing each service
|
|
52
|
+
* through a property, e.g. `api.room.createRoom(...)`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const api = new LiveKitAPI({ apiKey, secret }); // or new LiveKitAPI() to read from env
|
|
57
|
+
* await api.room.createRoom({ name: 'my-room' });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export class LiveKitAPI {
|
|
61
|
+
private readonly _room: RoomServiceClient;
|
|
62
|
+
|
|
63
|
+
private readonly _egress: EgressClient;
|
|
64
|
+
|
|
65
|
+
private readonly _ingress: IngressClient;
|
|
66
|
+
|
|
67
|
+
private readonly _sip: SipClient;
|
|
68
|
+
|
|
69
|
+
private readonly _agentDispatch: AgentDispatchClient;
|
|
70
|
+
|
|
71
|
+
private readonly _connector: ConnectorClient;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param options - server host, credentials, and client options; each value
|
|
75
|
+
* falls back to its environment variable when omitted.
|
|
76
|
+
*/
|
|
77
|
+
constructor(options: LiveKitAPIOptions = {}) {
|
|
78
|
+
const host = options.host || process.env.LIVEKIT_URL;
|
|
79
|
+
if (!host) {
|
|
80
|
+
throw new Error('host is required (pass it or set LIVEKIT_URL)');
|
|
81
|
+
}
|
|
82
|
+
const { apiKey, secret } = options;
|
|
83
|
+
// Only fall back to LIVEKIT_TOKEN when no explicit credentials were given, so
|
|
84
|
+
// an ambient token can't silently override a passed-in api key and secret.
|
|
85
|
+
const token = options.token || (apiKey || secret ? undefined : process.env.LIVEKIT_TOKEN);
|
|
86
|
+
if (!token && !(apiKey ?? process.env.LIVEKIT_API_KEY)) {
|
|
87
|
+
throw new Error('either a token or an API key and secret are required');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const clientOptions: ClientOptions = {
|
|
91
|
+
requestTimeout: options.requestTimeout,
|
|
92
|
+
failover: options.failover,
|
|
93
|
+
token,
|
|
94
|
+
};
|
|
95
|
+
this._room = new RoomServiceClient(host, apiKey, secret, clientOptions);
|
|
96
|
+
this._egress = new EgressClient(host, apiKey, secret, clientOptions);
|
|
97
|
+
this._ingress = new IngressClient(host, apiKey, secret, clientOptions);
|
|
98
|
+
this._sip = new SipClient(host, apiKey, secret, clientOptions);
|
|
99
|
+
this._agentDispatch = new AgentDispatchClient(host, apiKey, secret, clientOptions);
|
|
100
|
+
this._connector = new ConnectorClient(host, apiKey, secret, clientOptions);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get room(): RoomServiceClient {
|
|
104
|
+
return this._room;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get egress(): EgressClient {
|
|
108
|
+
return this._egress;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get ingress(): IngressClient {
|
|
112
|
+
return this._ingress;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get sip(): SipClient {
|
|
116
|
+
return this._sip;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get agentDispatch(): AgentDispatchClient {
|
|
120
|
+
return this._agentDispatch;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get connector(): ConnectorClient {
|
|
124
|
+
return this._connector;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/RoomServiceClient.ts
CHANGED
|
@@ -131,11 +131,11 @@ export class RoomServiceClient extends ServiceBase {
|
|
|
131
131
|
* @param options - client options
|
|
132
132
|
*/
|
|
133
133
|
constructor(host: string, apiKey?: string, secret?: string, options?: ClientOptions) {
|
|
134
|
-
super(apiKey, secret);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
:
|
|
138
|
-
|
|
134
|
+
super({ apiKey, secret, token: options?.token });
|
|
135
|
+
this.rpc = new TwirpRpc(host, livekitPackage, {
|
|
136
|
+
requestTimeout: options?.requestTimeout,
|
|
137
|
+
failover: options?.failover,
|
|
138
|
+
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
package/src/ServiceBase.ts
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
import { AccessToken } from './AccessToken.js';
|
|
5
5
|
import type { SIPGrant, VideoGrant } from './grants.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Authentication options for a service client.
|
|
9
|
+
*/
|
|
10
|
+
export interface ServiceBaseOptions {
|
|
11
|
+
/** API Key. */
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
/** API Secret. */
|
|
14
|
+
secret?: string;
|
|
15
|
+
/** Token TTL. Defaults to `10m`. */
|
|
16
|
+
ttl?: string;
|
|
17
|
+
/** Pre-signed token; sent verbatim, skipping per-call signing. */
|
|
18
|
+
token?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
/**
|
|
8
22
|
* Utilities to handle authentication
|
|
9
23
|
*/
|
|
@@ -12,20 +26,37 @@ export class ServiceBase {
|
|
|
12
26
|
|
|
13
27
|
private readonly secret?: string;
|
|
14
28
|
|
|
29
|
+
private readonly token?: string;
|
|
30
|
+
|
|
15
31
|
private readonly ttl: string;
|
|
16
32
|
|
|
17
33
|
/**
|
|
34
|
+
* @param options - authentication options
|
|
35
|
+
*/
|
|
36
|
+
constructor(options?: ServiceBaseOptions);
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated pass a {@link ServiceBaseOptions} object instead.
|
|
18
39
|
* @param apiKey - API Key.
|
|
19
40
|
* @param secret - API Secret.
|
|
20
41
|
* @param ttl - token TTL
|
|
21
42
|
*/
|
|
22
|
-
constructor(apiKey?: string, secret?: string, ttl?: string)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
constructor(apiKey?: string, secret?: string, ttl?: string);
|
|
44
|
+
constructor(apiKeyOrOptions?: string | ServiceBaseOptions, secret?: string, ttl?: string) {
|
|
45
|
+
const options: ServiceBaseOptions =
|
|
46
|
+
typeof apiKeyOrOptions === 'object'
|
|
47
|
+
? apiKeyOrOptions
|
|
48
|
+
: { apiKey: apiKeyOrOptions, secret, ttl };
|
|
49
|
+
this.apiKey = options.apiKey;
|
|
50
|
+
this.secret = options.secret;
|
|
51
|
+
this.ttl = options.ttl || '10m';
|
|
52
|
+
this.token = options.token;
|
|
26
53
|
}
|
|
27
54
|
|
|
28
55
|
async authHeader(grant: VideoGrant, sip?: SIPGrant): Promise<Record<string, string>> {
|
|
56
|
+
// A pre-signed token is sent verbatim; the caller is responsible for its grants.
|
|
57
|
+
if (this.token) {
|
|
58
|
+
return { Authorization: `Bearer ${this.token}` };
|
|
59
|
+
}
|
|
29
60
|
const at = new AccessToken(this.apiKey, this.secret, { ttl: this.ttl });
|
|
30
61
|
if (grant) {
|
|
31
62
|
at.addGrant(grant);
|
package/src/SipClient.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
Pagination,
|
|
8
8
|
RoomConfiguration,
|
|
9
9
|
SIPHeaderOptions,
|
|
10
|
+
SIPMediaConfig,
|
|
10
11
|
SIPMediaEncryption,
|
|
11
12
|
SIPOutboundConfig,
|
|
12
13
|
} from '@livekit/protocol';
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
ListSIPTrunkRequest,
|
|
28
29
|
ListSIPTrunkResponse,
|
|
29
30
|
SIPDispatchRule,
|
|
31
|
+
SIPDispatchRuleCallee,
|
|
30
32
|
SIPDispatchRuleDirect,
|
|
31
33
|
SIPDispatchRuleIndividual,
|
|
32
34
|
SIPDispatchRuleInfo,
|
|
@@ -43,7 +45,17 @@ import {
|
|
|
43
45
|
import type { ClientOptions } from './ClientOptions.js';
|
|
44
46
|
import { ServiceBase } from './ServiceBase.js';
|
|
45
47
|
import type { Rpc } from './TwirpRPC.js';
|
|
46
|
-
import { TwirpRpc, livekitPackage } from './TwirpRPC.js';
|
|
48
|
+
import { ServerError, SipCallError, TwirpRpc, livekitPackage } from './TwirpRPC.js';
|
|
49
|
+
import { DEFAULT_RINGING_TIMEOUT_SECONDS, dialRequestTimeout } from './dialTimeout.js';
|
|
50
|
+
|
|
51
|
+
// A SIP dialing failure carries a SIP status in its error metadata; surface it as
|
|
52
|
+
// a SipCallError so callers can branch on the SIP code.
|
|
53
|
+
function asSipCallError(e: unknown): unknown {
|
|
54
|
+
if (e instanceof ServerError && e.metadata && 'sip_status_code' in e.metadata) {
|
|
55
|
+
return SipCallError.fromServerError(e);
|
|
56
|
+
}
|
|
57
|
+
return e;
|
|
58
|
+
}
|
|
47
59
|
|
|
48
60
|
const svc = 'SIP';
|
|
49
61
|
|
|
@@ -80,7 +92,9 @@ export interface CreateSipInboundTrunkOptions {
|
|
|
80
92
|
// Map SIP response headers from INVITE to sip.h.* participant attributes automatically.
|
|
81
93
|
includeHeaders?: SIPHeaderOptions;
|
|
82
94
|
krispEnabled?: boolean;
|
|
95
|
+
/** @deprecated - use `media.encryption` instead */
|
|
83
96
|
mediaEncryption?: SIPMediaEncryption;
|
|
97
|
+
media?: SIPMediaConfig;
|
|
84
98
|
/** Maximum time for a call to ring in seconds. */
|
|
85
99
|
ringingTimeout?: number;
|
|
86
100
|
}
|
|
@@ -98,7 +112,9 @@ export interface CreateSipOutboundTrunkOptions {
|
|
|
98
112
|
headersToAttributes?: { [key: string]: string };
|
|
99
113
|
// Map SIP response headers from INVITE to sip.h.* participant attributes automatically.
|
|
100
114
|
includeHeaders?: SIPHeaderOptions;
|
|
115
|
+
/** @deprecated - use `media.encryption` instead */
|
|
101
116
|
mediaEncryption?: SIPMediaEncryption;
|
|
117
|
+
media?: SIPMediaConfig;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
120
|
export interface SipDispatchRuleDirect {
|
|
@@ -113,6 +129,14 @@ export interface SipDispatchRuleIndividual {
|
|
|
113
129
|
pin?: string;
|
|
114
130
|
}
|
|
115
131
|
|
|
132
|
+
export interface SipDispatchRuleCallee {
|
|
133
|
+
type: 'callee';
|
|
134
|
+
roomPrefix: string;
|
|
135
|
+
pin?: string;
|
|
136
|
+
/** Optionally append a random suffix to the room name. */
|
|
137
|
+
randomize?: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
116
140
|
export interface CreateSipDispatchRuleOptions {
|
|
117
141
|
name?: string;
|
|
118
142
|
metadata?: string;
|
|
@@ -156,8 +180,9 @@ export interface CreateSipParticipantOptions {
|
|
|
156
180
|
krispEnabled?: boolean;
|
|
157
181
|
/** If `true`, this will wait until the call is answered before returning. */
|
|
158
182
|
waitUntilAnswered?: boolean;
|
|
159
|
-
/** Optional request timeout in seconds.
|
|
183
|
+
/** Optional request timeout in seconds. Defaults to 30s when waitUntilAnswered is true (dialing takes time), otherwise the client default. */
|
|
160
184
|
timeout?: number;
|
|
185
|
+
media?: SIPMediaConfig;
|
|
161
186
|
}
|
|
162
187
|
|
|
163
188
|
export interface ListSipDispatchRuleOptions {
|
|
@@ -194,7 +219,9 @@ export interface SipInboundTrunkUpdateOptions {
|
|
|
194
219
|
authPassword?: string;
|
|
195
220
|
name?: string;
|
|
196
221
|
metadata?: string;
|
|
222
|
+
/** @deprecated - use `media.encryption` instead */
|
|
197
223
|
mediaEncryption?: SIPMediaEncryption;
|
|
224
|
+
media?: SIPMediaConfig;
|
|
198
225
|
}
|
|
199
226
|
|
|
200
227
|
export interface SipOutboundTrunkUpdateOptions {
|
|
@@ -206,7 +233,9 @@ export interface SipOutboundTrunkUpdateOptions {
|
|
|
206
233
|
destinationCountry?: string;
|
|
207
234
|
name?: string;
|
|
208
235
|
metadata?: string;
|
|
236
|
+
/** @deprecated - use `media.encryption` instead */
|
|
209
237
|
mediaEncryption?: SIPMediaEncryption;
|
|
238
|
+
media?: SIPMediaConfig;
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
export interface TransferSipParticipantOptions {
|
|
@@ -214,6 +243,8 @@ export interface TransferSipParticipantOptions {
|
|
|
214
243
|
headers?: { [key: string]: string };
|
|
215
244
|
/** Maximum time for the transfer destination to answer the call, in seconds. */
|
|
216
245
|
ringingTimeout?: number;
|
|
246
|
+
/** Optional request timeout in seconds. Defaults to 30s (dialing takes time). */
|
|
247
|
+
timeout?: number;
|
|
217
248
|
}
|
|
218
249
|
|
|
219
250
|
/**
|
|
@@ -229,11 +260,11 @@ export class SipClient extends ServiceBase {
|
|
|
229
260
|
* @param options - client options
|
|
230
261
|
*/
|
|
231
262
|
constructor(host: string, apiKey?: string, secret?: string, options?: ClientOptions) {
|
|
232
|
-
super(apiKey, secret);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
:
|
|
236
|
-
|
|
263
|
+
super({ apiKey, secret, token: options?.token });
|
|
264
|
+
this.rpc = new TwirpRpc(host, livekitPackage, {
|
|
265
|
+
requestTimeout: options?.requestTimeout,
|
|
266
|
+
failover: options?.failover,
|
|
267
|
+
});
|
|
237
268
|
}
|
|
238
269
|
|
|
239
270
|
/**
|
|
@@ -316,6 +347,7 @@ export class SipClient extends ServiceBase {
|
|
|
316
347
|
includeHeaders: opts.includeHeaders,
|
|
317
348
|
krispEnabled: opts.krispEnabled,
|
|
318
349
|
mediaEncryption: opts.mediaEncryption,
|
|
350
|
+
media: opts.media,
|
|
319
351
|
ringingTimeout: opts.ringingTimeout
|
|
320
352
|
? new Duration({ seconds: BigInt(opts.ringingTimeout) })
|
|
321
353
|
: undefined,
|
|
@@ -366,6 +398,7 @@ export class SipClient extends ServiceBase {
|
|
|
366
398
|
includeHeaders: opts.includeHeaders,
|
|
367
399
|
destinationCountry: opts.destinationCountry,
|
|
368
400
|
mediaEncryption: opts.mediaEncryption,
|
|
401
|
+
media: opts.media,
|
|
369
402
|
}),
|
|
370
403
|
}).toJson();
|
|
371
404
|
|
|
@@ -450,7 +483,7 @@ export class SipClient extends ServiceBase {
|
|
|
450
483
|
* @returns Created SIP dispatch rule
|
|
451
484
|
*/
|
|
452
485
|
async createSipDispatchRule(
|
|
453
|
-
rule: SipDispatchRuleDirect | SipDispatchRuleIndividual,
|
|
486
|
+
rule: SipDispatchRuleDirect | SipDispatchRuleIndividual | SipDispatchRuleCallee,
|
|
454
487
|
opts?: CreateSipDispatchRuleOptions,
|
|
455
488
|
): Promise<SIPDispatchRuleInfo> {
|
|
456
489
|
if (opts === undefined) {
|
|
@@ -477,6 +510,17 @@ export class SipClient extends ServiceBase {
|
|
|
477
510
|
}),
|
|
478
511
|
},
|
|
479
512
|
});
|
|
513
|
+
} else if (rule.type == 'callee') {
|
|
514
|
+
ruleProto = new SIPDispatchRule({
|
|
515
|
+
rule: {
|
|
516
|
+
case: 'dispatchRuleCallee',
|
|
517
|
+
value: new SIPDispatchRuleCallee({
|
|
518
|
+
roomPrefix: rule.roomPrefix,
|
|
519
|
+
pin: rule.pin || '',
|
|
520
|
+
randomize: rule.randomize || false,
|
|
521
|
+
}),
|
|
522
|
+
},
|
|
523
|
+
});
|
|
480
524
|
}
|
|
481
525
|
|
|
482
526
|
const req = new CreateSIPDispatchRuleRequest({
|
|
@@ -732,8 +776,13 @@ export class SipClient extends ServiceBase {
|
|
|
732
776
|
opts = {};
|
|
733
777
|
}
|
|
734
778
|
|
|
735
|
-
|
|
736
|
-
|
|
779
|
+
// Dialing a phone and waiting for an answer takes longer than a normal call,
|
|
780
|
+
// and the request must outlast ringing so the call can be answered. Pin the
|
|
781
|
+
// ring window explicitly so our request timeout doesn't depend on the server's
|
|
782
|
+
// default (which could change out from under us).
|
|
783
|
+
if (opts.waitUntilAnswered) {
|
|
784
|
+
opts.ringingTimeout ??= DEFAULT_RINGING_TIMEOUT_SECONDS;
|
|
785
|
+
opts.timeout = dialRequestTimeout(opts.timeout, opts.ringingTimeout);
|
|
737
786
|
}
|
|
738
787
|
|
|
739
788
|
const req = new CreateSIPParticipantRequest({
|
|
@@ -760,16 +809,21 @@ export class SipClient extends ServiceBase {
|
|
|
760
809
|
: undefined,
|
|
761
810
|
krispEnabled: opts.krispEnabled,
|
|
762
811
|
waitUntilAnswered: opts.waitUntilAnswered,
|
|
812
|
+
media: opts.media,
|
|
763
813
|
}).toJson();
|
|
764
814
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
815
|
+
try {
|
|
816
|
+
const data = await this.rpc.request(
|
|
817
|
+
svc,
|
|
818
|
+
'CreateSIPParticipant',
|
|
819
|
+
req,
|
|
820
|
+
await this.authHeader({}, { call: true }),
|
|
821
|
+
opts.timeout,
|
|
822
|
+
);
|
|
823
|
+
return SIPParticipantInfo.fromJson(data, { ignoreUnknownFields: true });
|
|
824
|
+
} catch (e) {
|
|
825
|
+
throw asSipCallError(e);
|
|
826
|
+
}
|
|
773
827
|
}
|
|
774
828
|
|
|
775
829
|
/**
|
|
@@ -790,6 +844,12 @@ export class SipClient extends ServiceBase {
|
|
|
790
844
|
opts = {};
|
|
791
845
|
}
|
|
792
846
|
|
|
847
|
+
// Transferring a call dials a phone, which takes longer than a normal call,
|
|
848
|
+
// so keep the request alive past ringing. Pin the ring window explicitly so
|
|
849
|
+
// our request timeout doesn't depend on the server's default.
|
|
850
|
+
opts.ringingTimeout ??= DEFAULT_RINGING_TIMEOUT_SECONDS;
|
|
851
|
+
opts.timeout = dialRequestTimeout(opts.timeout, opts.ringingTimeout);
|
|
852
|
+
|
|
793
853
|
const req = new TransferSIPParticipantRequest({
|
|
794
854
|
participantIdentity: participantIdentity,
|
|
795
855
|
roomName: roomName,
|
|
@@ -801,11 +861,16 @@ export class SipClient extends ServiceBase {
|
|
|
801
861
|
: undefined,
|
|
802
862
|
}).toJson();
|
|
803
863
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
864
|
+
try {
|
|
865
|
+
await this.rpc.request(
|
|
866
|
+
svc,
|
|
867
|
+
'TransferSIPParticipant',
|
|
868
|
+
req,
|
|
869
|
+
await this.authHeader({ roomAdmin: true, room: roomName }, { call: true }),
|
|
870
|
+
opts.timeout,
|
|
871
|
+
);
|
|
872
|
+
} catch (e) {
|
|
873
|
+
throw asSipCallError(e);
|
|
874
|
+
}
|
|
810
875
|
}
|
|
811
876
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { ServerError, SipCallError } from './TwirpRPC.js';
|
|
6
|
+
|
|
7
|
+
describe('SipCallError', () => {
|
|
8
|
+
it('renders the SIP status, Twirp code, and extra metadata', () => {
|
|
9
|
+
const err = SipCallError.fromServerError(
|
|
10
|
+
new ServerError(
|
|
11
|
+
'Too Many Requests',
|
|
12
|
+
'twirp error: sip status 486',
|
|
13
|
+
429,
|
|
14
|
+
'resource_exhausted',
|
|
15
|
+
{
|
|
16
|
+
sip_status_code: '486',
|
|
17
|
+
sip_status: 'Busy Here',
|
|
18
|
+
error_details: 'CAgS...base64...',
|
|
19
|
+
region: 'us-east',
|
|
20
|
+
},
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(err).toBeInstanceOf(ServerError);
|
|
25
|
+
expect(err.name).toBe('SipCallError');
|
|
26
|
+
expect(err.sipStatusCode).toBe(486);
|
|
27
|
+
expect(err.sipStatus).toBe('Busy Here');
|
|
28
|
+
|
|
29
|
+
const printed = err.toString();
|
|
30
|
+
expect(printed).toContain('SipCallError');
|
|
31
|
+
expect(printed).toContain('486');
|
|
32
|
+
expect(printed).toContain('Busy Here');
|
|
33
|
+
expect(printed).toContain('resource_exhausted');
|
|
34
|
+
expect(printed).toContain('region=us-east'); // other metadata is surfaced
|
|
35
|
+
expect(printed).not.toContain('error_details'); // opaque blob is omitted
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('falls back to the original message when there is no SIP status', () => {
|
|
39
|
+
const err = SipCallError.fromServerError(new ServerError('Internal', 'boom', 500, 'internal'));
|
|
40
|
+
expect(err.message).toBe('boom');
|
|
41
|
+
});
|
|
42
|
+
});
|