livekit-server-sdk 2.16.0 → 2.18.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/README.md +82 -15
- 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 +19 -3
- package/dist/EgressClient.cjs.map +1 -1
- package/dist/EgressClient.d.cts +7 -1
- package/dist/EgressClient.d.ts +7 -1
- package/dist/EgressClient.d.ts.map +1 -1
- package/dist/EgressClient.js +20 -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 +40 -19
- package/dist/SipClient.cjs.map +1 -1
- package/dist/SipClient.d.cts +4 -2
- package/dist/SipClient.d.ts +4 -2
- package/dist/SipClient.d.ts.map +1 -1
- package/dist/SipClient.js +41 -20
- 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 +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -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 +21 -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 +55 -23
- 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 +11 -1
- package/src/version.ts +1 -0
package/src/EgressClient.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ListEgressResponse,
|
|
19
19
|
ParticipantEgressRequest,
|
|
20
20
|
RoomCompositeEgressRequest,
|
|
21
|
+
StartEgressRequest,
|
|
21
22
|
StopEgressRequest,
|
|
22
23
|
TrackCompositeEgressRequest,
|
|
23
24
|
TrackEgressRequest,
|
|
@@ -141,11 +142,11 @@ export class EgressClient extends ServiceBase {
|
|
|
141
142
|
* @param options - client options
|
|
142
143
|
*/
|
|
143
144
|
constructor(host: string, apiKey?: string, secret?: string, options?: ClientOptions) {
|
|
144
|
-
super(apiKey, secret);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
:
|
|
148
|
-
|
|
145
|
+
super({ apiKey, secret, token: options?.token });
|
|
146
|
+
this.rpc = new TwirpRpc(host, livekitPackage, {
|
|
147
|
+
requestTimeout: options?.requestTimeout,
|
|
148
|
+
failover: options?.failover,
|
|
149
|
+
});
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
/**
|
|
@@ -580,6 +581,21 @@ export class EgressClient extends ServiceBase {
|
|
|
580
581
|
return EgressInfo.fromJson(data, { ignoreUnknownFields: true });
|
|
581
582
|
}
|
|
582
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Starts an egress using the unified v2 request.
|
|
586
|
+
*
|
|
587
|
+
* @param request - the egress request
|
|
588
|
+
*/
|
|
589
|
+
async startEgress(request: StartEgressRequest): Promise<EgressInfo> {
|
|
590
|
+
const data = await this.rpc.request(
|
|
591
|
+
svc,
|
|
592
|
+
'StartEgress',
|
|
593
|
+
request.toJson(),
|
|
594
|
+
await this.authHeader({ roomRecord: true }),
|
|
595
|
+
);
|
|
596
|
+
return EgressInfo.fromJson(data, { ignoreUnknownFields: true });
|
|
597
|
+
}
|
|
598
|
+
|
|
583
599
|
/**
|
|
584
600
|
* @param egressId -
|
|
585
601
|
* @param layout -
|
package/src/IngressClient.ts
CHANGED
|
@@ -128,11 +128,11 @@ export class IngressClient extends ServiceBase {
|
|
|
128
128
|
* @param options - client options
|
|
129
129
|
*/
|
|
130
130
|
constructor(host: string, apiKey?: string, secret?: string, options?: ClientOptions) {
|
|
131
|
-
super(apiKey, secret);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
:
|
|
135
|
-
|
|
131
|
+
super({ apiKey, secret, token: options?.token });
|
|
132
|
+
this.rpc = new TwirpRpc(host, livekitPackage, {
|
|
133
|
+
requestTimeout: options?.requestTimeout,
|
|
134
|
+
failover: options?.failover,
|
|
135
|
+
});
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -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
|
@@ -45,7 +45,17 @@ import {
|
|
|
45
45
|
import type { ClientOptions } from './ClientOptions.js';
|
|
46
46
|
import { ServiceBase } from './ServiceBase.js';
|
|
47
47
|
import type { Rpc } from './TwirpRPC.js';
|
|
48
|
-
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
|
+
}
|
|
49
59
|
|
|
50
60
|
const svc = 'SIP';
|
|
51
61
|
|
|
@@ -170,7 +180,7 @@ export interface CreateSipParticipantOptions {
|
|
|
170
180
|
krispEnabled?: boolean;
|
|
171
181
|
/** If `true`, this will wait until the call is answered before returning. */
|
|
172
182
|
waitUntilAnswered?: boolean;
|
|
173
|
-
/** 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. */
|
|
174
184
|
timeout?: number;
|
|
175
185
|
media?: SIPMediaConfig;
|
|
176
186
|
}
|
|
@@ -233,6 +243,8 @@ export interface TransferSipParticipantOptions {
|
|
|
233
243
|
headers?: { [key: string]: string };
|
|
234
244
|
/** Maximum time for the transfer destination to answer the call, in seconds. */
|
|
235
245
|
ringingTimeout?: number;
|
|
246
|
+
/** Optional request timeout in seconds. Defaults to 30s (dialing takes time). */
|
|
247
|
+
timeout?: number;
|
|
236
248
|
}
|
|
237
249
|
|
|
238
250
|
/**
|
|
@@ -248,11 +260,11 @@ export class SipClient extends ServiceBase {
|
|
|
248
260
|
* @param options - client options
|
|
249
261
|
*/
|
|
250
262
|
constructor(host: string, apiKey?: string, secret?: string, options?: ClientOptions) {
|
|
251
|
-
super(apiKey, secret);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
:
|
|
255
|
-
|
|
263
|
+
super({ apiKey, secret, token: options?.token });
|
|
264
|
+
this.rpc = new TwirpRpc(host, livekitPackage, {
|
|
265
|
+
requestTimeout: options?.requestTimeout,
|
|
266
|
+
failover: options?.failover,
|
|
267
|
+
});
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
/**
|
|
@@ -764,8 +776,13 @@ export class SipClient extends ServiceBase {
|
|
|
764
776
|
opts = {};
|
|
765
777
|
}
|
|
766
778
|
|
|
767
|
-
|
|
768
|
-
|
|
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);
|
|
769
786
|
}
|
|
770
787
|
|
|
771
788
|
const req = new CreateSIPParticipantRequest({
|
|
@@ -795,14 +812,18 @@ export class SipClient extends ServiceBase {
|
|
|
795
812
|
media: opts.media,
|
|
796
813
|
}).toJson();
|
|
797
814
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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
|
+
}
|
|
806
827
|
}
|
|
807
828
|
|
|
808
829
|
/**
|
|
@@ -823,6 +844,12 @@ export class SipClient extends ServiceBase {
|
|
|
823
844
|
opts = {};
|
|
824
845
|
}
|
|
825
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
|
+
|
|
826
853
|
const req = new TransferSIPParticipantRequest({
|
|
827
854
|
participantIdentity: participantIdentity,
|
|
828
855
|
roomName: roomName,
|
|
@@ -834,11 +861,16 @@ export class SipClient extends ServiceBase {
|
|
|
834
861
|
: undefined,
|
|
835
862
|
}).toJson();
|
|
836
863
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
+
}
|
|
843
875
|
}
|
|
844
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
|
+
});
|