livekit-server-sdk 2.11.0 → 2.13.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/src/SipClient.ts CHANGED
@@ -2,7 +2,12 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { Duration } from '@bufbuild/protobuf';
5
- import type { RoomConfiguration, SIPHeaderOptions } from '@livekit/protocol';
5
+ import type {
6
+ ListUpdate,
7
+ Pagination,
8
+ RoomConfiguration,
9
+ SIPHeaderOptions,
10
+ } from '@livekit/protocol';
6
11
  import {
7
12
  CreateSIPDispatchRuleRequest,
8
13
  CreateSIPInboundTrunkRequest,
@@ -29,6 +34,9 @@ import {
29
34
  SIPTransport,
30
35
  SIPTrunkInfo,
31
36
  TransferSIPParticipantRequest,
37
+ UpdateSIPDispatchRuleRequest,
38
+ UpdateSIPInboundTrunkRequest,
39
+ UpdateSIPOutboundTrunkRequest,
32
40
  } from '@livekit/protocol';
33
41
  import { ServiceBase } from './ServiceBase.js';
34
42
  import type { Rpc } from './TwirpRPC.js';
@@ -108,30 +116,74 @@ export interface CreateSipDispatchRuleOptions {
108
116
  }
109
117
 
110
118
  export interface CreateSipParticipantOptions {
111
- // Optional SIP From number to use. If empty, trunk number is used.
119
+ /** Optional SIP From number to use. If empty, trunk number is used. */
112
120
  fromNumber?: string;
113
- // Optional identity of the SIP participant
121
+ /** Optional identity of the SIP participant */
114
122
  participantIdentity?: string;
115
- // Optional name of the participant
123
+ /** Optional name of the participant */
116
124
  participantName?: string;
117
- // Optional metadata to attach to the participant
125
+ /** Optional metadata to attach to the participant */
118
126
  participantMetadata?: string;
119
- // Optional attributes to attach to the participant
127
+ /** Optional attributes to attach to the participant */
120
128
  participantAttributes?: { [key: string]: string };
121
- // Optionally send following DTMF digits (extension codes) when making a call.
122
- // Character 'w' can be used to add a 0.5 sec delay.
129
+ /** Optionally send following DTMF digits (extension codes) when making a call.
130
+ * Character 'w' can be used to add a 0.5 sec delay. */
123
131
  dtmf?: string;
124
- /** @deprecated - use `playDialtone` instead */
125
- playRingtone?: boolean; // Deprecated, use playDialtone instead
132
+ /** @deprecated use `playDialtone` instead */
133
+ playRingtone?: boolean;
134
+ /** If `true`, the SIP Participant plays a dial tone to the room until the phone is picked up. */
126
135
  playDialtone?: boolean;
127
- // These headers are sent as-is and may help identify this call as coming from LiveKit for the other SIP endpoint.
136
+ /** These headers are sent as-is and may help identify this call as coming from LiveKit for the other SIP endpoint. */
128
137
  headers?: { [key: string]: string };
129
- // Map SIP response headers from INVITE to sip.h.* participant attributes automatically.
138
+ /** Map SIP response headers from INVITE to sip.h.* participant attributes automatically. */
130
139
  includeHeaders?: SIPHeaderOptions;
131
140
  hidePhoneNumber?: boolean;
132
- ringingTimeout?: number; // Duration in seconds
133
- maxCallDuration?: number; // Duration in seconds
141
+ /** Maximum time for the call to ring in seconds. */
142
+ ringingTimeout?: number;
143
+ /** Maximum call duration in seconds. */
144
+ maxCallDuration?: number;
145
+ /** If `true`, Krisp noise cancellation will be enabled for the caller. */
134
146
  krispEnabled?: boolean;
147
+ /** If `true`, this will wait until the call is answered before returning. */
148
+ waitUntilAnswered?: boolean;
149
+ /** Optional request timeout in seconds. */
150
+ timeout?: number;
151
+ }
152
+
153
+ export interface ListSipDispatchRuleOptions {
154
+ /** Pagination options. */
155
+ page?: Pagination;
156
+ /** Rule IDs to list. If this option is set, the response will contains rules in the same order. If any of the rules is missing, a nil item in that position will be sent in the response. */
157
+ dispatchRuleIds?: string[];
158
+ /** Only list rules that contain one of the Trunk IDs, including wildcard rules. */
159
+ trunkIds?: string[];
160
+ }
161
+
162
+ export interface ListSipTrunkOptions {
163
+ /** Pagination options. */
164
+ page?: Pagination;
165
+ /** Trunk IDs to list. If this option is set, the response will contains trunks in the same order. If any of the trunks is missing, a nil item in that position will be sent in the response. */
166
+ trunkIds?: string[];
167
+ /** Only list trunks that contain one of the numbers, including wildcard trunks. */
168
+ numbers?: string[];
169
+ }
170
+
171
+ export interface SipDispatchRuleUpdateOptions {
172
+ trunkIds?: ListUpdate;
173
+ rule?: SIPDispatchRule;
174
+ name?: string;
175
+ metadata?: string;
176
+ attributes?: { [key: string]: string };
177
+ }
178
+
179
+ export interface SipTrunkUpdateOptions {
180
+ numbers?: ListUpdate;
181
+ allowedAddresses?: ListUpdate;
182
+ allowedNumbers?: ListUpdate;
183
+ authUsername?: string;
184
+ authPassword?: string;
185
+ name?: string;
186
+ metadata?: string;
135
187
  }
136
188
 
137
189
  export interface TransferSipParticipantOptions {
@@ -206,9 +258,12 @@ export class SipClient extends ServiceBase {
206
258
  }
207
259
 
208
260
  /**
261
+ * Create a new SIP inbound trunk.
262
+ *
209
263
  * @param name - human-readable name of the trunk
210
264
  * @param numbers - phone numbers of the trunk
211
265
  * @param opts - CreateSipTrunkOptions
266
+ * @returns Created SIP inbound trunk
212
267
  */
213
268
  async createSipInboundTrunk(
214
269
  name: string,
@@ -244,10 +299,13 @@ export class SipClient extends ServiceBase {
244
299
  }
245
300
 
246
301
  /**
302
+ * Create a new SIP outbound trunk.
303
+ *
247
304
  * @param name - human-readable name of the trunk
248
305
  * @param address - hostname and port of the SIP server to dial
249
306
  * @param numbers - phone numbers of the trunk
250
307
  * @param opts - CreateSipTrunkOptions
308
+ * @returns Created SIP outbound trunk
251
309
  */
252
310
  async createSipOutboundTrunk(
253
311
  name: string,
@@ -299,30 +357,45 @@ export class SipClient extends ServiceBase {
299
357
  return ListSIPTrunkResponse.fromJson(data, { ignoreUnknownFields: true }).items ?? [];
300
358
  }
301
359
 
302
- async listSipInboundTrunk(): Promise<Array<SIPInboundTrunkInfo>> {
303
- const req: Partial<ListSIPInboundTrunkRequest> = {};
360
+ /**
361
+ * List SIP inbound trunks with optional filtering.
362
+ *
363
+ * @param list - Request with optional filtering parameters
364
+ * @returns Response containing list of SIP inbound trunks
365
+ */
366
+ async listSipInboundTrunk(list: ListSipTrunkOptions = {}): Promise<Array<SIPInboundTrunkInfo>> {
367
+ const req = new ListSIPInboundTrunkRequest(list).toJson();
304
368
  const data = await this.rpc.request(
305
369
  svc,
306
370
  'ListSIPInboundTrunk',
307
- new ListSIPInboundTrunkRequest(req).toJson(),
371
+ req,
308
372
  await this.authHeader({}, { admin: true }),
309
373
  );
310
374
  return ListSIPInboundTrunkResponse.fromJson(data, { ignoreUnknownFields: true }).items ?? [];
311
375
  }
312
376
 
313
- async listSipOutboundTrunk(): Promise<Array<SIPOutboundTrunkInfo>> {
314
- const req: Partial<ListSIPOutboundTrunkRequest> = {};
377
+ /**
378
+ * List SIP outbound trunks with optional filtering.
379
+ *
380
+ * @param list - Request with optional filtering parameters
381
+ * @returns Response containing list of SIP outbound trunks
382
+ */
383
+ async listSipOutboundTrunk(list: ListSipTrunkOptions = {}): Promise<Array<SIPOutboundTrunkInfo>> {
384
+ const req = new ListSIPOutboundTrunkRequest(list).toJson();
315
385
  const data = await this.rpc.request(
316
386
  svc,
317
387
  'ListSIPOutboundTrunk',
318
- new ListSIPOutboundTrunkRequest(req).toJson(),
388
+ req,
319
389
  await this.authHeader({}, { admin: true }),
320
390
  );
321
391
  return ListSIPOutboundTrunkResponse.fromJson(data, { ignoreUnknownFields: true }).items ?? [];
322
392
  }
323
393
 
324
394
  /**
325
- * @param sipTrunkId - sip trunk to delete
395
+ * Delete a SIP trunk.
396
+ *
397
+ * @param sipTrunkId - ID of the SIP trunk to delete
398
+ * @returns Deleted trunk information
326
399
  */
327
400
  async deleteSipTrunk(sipTrunkId: string): Promise<SIPTrunkInfo> {
328
401
  const data = await this.rpc.request(
@@ -335,8 +408,11 @@ export class SipClient extends ServiceBase {
335
408
  }
336
409
 
337
410
  /**
338
- * @param rule - sip dispatch rule
411
+ * Create a new SIP dispatch rule.
412
+ *
413
+ * @param rule - SIP dispatch rule to create
339
414
  * @param opts - CreateSipDispatchRuleOptions
415
+ * @returns Created SIP dispatch rule
340
416
  */
341
417
  async createSipDispatchRule(
342
418
  rule: SipDispatchRuleDirect | SipDispatchRuleIndividual,
@@ -388,19 +464,207 @@ export class SipClient extends ServiceBase {
388
464
  return SIPDispatchRuleInfo.fromJson(data, { ignoreUnknownFields: true });
389
465
  }
390
466
 
391
- async listSipDispatchRule(): Promise<Array<SIPDispatchRuleInfo>> {
392
- const req: Partial<ListSIPDispatchRuleRequest> = {};
467
+ /**
468
+ * Updates an existing SIP dispatch rule by replacing it entirely.
469
+ *
470
+ * @param sipDispatchRuleId - ID of the SIP dispatch rule to update
471
+ * @param rule - new SIP dispatch rule
472
+ * @returns Updated SIP dispatch rule
473
+ */
474
+ async updateSipDispatchRule(
475
+ sipDispatchRuleId: string,
476
+ rule: SIPDispatchRuleInfo,
477
+ ): Promise<SIPDispatchRuleInfo> {
478
+ const req = new UpdateSIPDispatchRuleRequest({
479
+ sipDispatchRuleId: sipDispatchRuleId,
480
+ action: {
481
+ case: 'replace',
482
+ value: rule,
483
+ },
484
+ }).toJson();
485
+
486
+ const data = await this.rpc.request(
487
+ svc,
488
+ 'UpdateSIPDispatchRule',
489
+ req,
490
+ await this.authHeader({}, { admin: true }),
491
+ );
492
+
493
+ return SIPDispatchRuleInfo.fromJson(data, { ignoreUnknownFields: true });
494
+ }
495
+
496
+ /**
497
+ * Updates specific fields of an existing SIP dispatch rule.
498
+ * Only provided fields will be updated.
499
+ *
500
+ * @param sipDispatchRuleId - ID of the SIP dispatch rule to update
501
+ * @param fields - Fields of the dispatch rule to update
502
+ * @returns Updated SIP dispatch rule
503
+ */
504
+ async updateSipDispatchRuleFields(
505
+ sipDispatchRuleId: string,
506
+ fields: SipDispatchRuleUpdateOptions = {},
507
+ ): Promise<SIPDispatchRuleInfo> {
508
+ const req = new UpdateSIPDispatchRuleRequest({
509
+ sipDispatchRuleId: sipDispatchRuleId,
510
+ action: {
511
+ case: 'update',
512
+ value: fields,
513
+ },
514
+ }).toJson();
515
+
516
+ const data = await this.rpc.request(
517
+ svc,
518
+ 'UpdateSIPDispatchRule',
519
+ req,
520
+ await this.authHeader({}, { admin: true }),
521
+ );
522
+
523
+ return SIPDispatchRuleInfo.fromJson(data, { ignoreUnknownFields: true });
524
+ }
525
+
526
+ /**
527
+ * Updates an existing SIP inbound trunk by replacing it entirely.
528
+ *
529
+ * @param sipTrunkId - ID of the SIP inbound trunk to update
530
+ * @param trunk - SIP inbound trunk to update with
531
+ * @returns Updated SIP inbound trunk
532
+ */
533
+ async updateSipInboundTrunk(
534
+ sipTrunkId: string,
535
+ trunk: SIPInboundTrunkInfo,
536
+ ): Promise<SIPInboundTrunkInfo> {
537
+ const req = new UpdateSIPInboundTrunkRequest({
538
+ sipTrunkId,
539
+ action: {
540
+ case: 'replace',
541
+ value: trunk,
542
+ },
543
+ }).toJson();
544
+
545
+ const data = await this.rpc.request(
546
+ svc,
547
+ 'UpdateSIPInboundTrunk',
548
+ req,
549
+ await this.authHeader({}, { admin: true }),
550
+ );
551
+
552
+ return SIPInboundTrunkInfo.fromJson(data, { ignoreUnknownFields: true });
553
+ }
554
+
555
+ /**
556
+ * Updates specific fields of an existing SIP inbound trunk.
557
+ * Only provided fields will be updated.
558
+ *
559
+ * @param sipTrunkId - ID of the SIP inbound trunk to update
560
+ * @param fields - Fields of the inbound trunk to update
561
+ * @returns Updated SIP inbound trunk
562
+ */
563
+ async updateSipInboundTrunkFields(
564
+ sipTrunkId: string,
565
+ fields: SipTrunkUpdateOptions,
566
+ ): Promise<SIPInboundTrunkInfo> {
567
+ const req = new UpdateSIPInboundTrunkRequest({
568
+ sipTrunkId,
569
+ action: {
570
+ case: 'update',
571
+ value: fields,
572
+ },
573
+ }).toJson();
574
+
575
+ const data = await this.rpc.request(
576
+ svc,
577
+ 'UpdateSIPInboundTrunk',
578
+ req,
579
+ await this.authHeader({}, { admin: true }),
580
+ );
581
+
582
+ return SIPInboundTrunkInfo.fromJson(data, { ignoreUnknownFields: true });
583
+ }
584
+
585
+ /**
586
+ * Updates an existing SIP outbound trunk by replacing it entirely.
587
+ *
588
+ * @param sipTrunkId - ID of the SIP outbound trunk to update
589
+ * @param trunk - SIP outbound trunk to update with
590
+ * @returns Updated SIP outbound trunk
591
+ */
592
+ async updateSipOutboundTrunk(
593
+ sipTrunkId: string,
594
+ trunk: SIPOutboundTrunkInfo,
595
+ ): Promise<SIPOutboundTrunkInfo> {
596
+ const req = new UpdateSIPOutboundTrunkRequest({
597
+ sipTrunkId,
598
+ action: {
599
+ case: 'replace',
600
+ value: trunk,
601
+ },
602
+ }).toJson();
603
+
604
+ const data = await this.rpc.request(
605
+ svc,
606
+ 'UpdateSIPOutboundTrunk',
607
+ req,
608
+ await this.authHeader({}, { admin: true }),
609
+ );
610
+
611
+ return SIPOutboundTrunkInfo.fromJson(data, { ignoreUnknownFields: true });
612
+ }
613
+
614
+ /**
615
+ * Updates specific fields of an existing SIP outbound trunk.
616
+ * Only provided fields will be updated.
617
+ *
618
+ * @param sipTrunkId - ID of the SIP outbound trunk to update
619
+ * @param fields - Fields of the outbound trunk to update
620
+ * @returns Updated SIP outbound trunk
621
+ */
622
+ async updateSipOutboundTrunkFields(
623
+ sipTrunkId: string,
624
+ fields: SipTrunkUpdateOptions,
625
+ ): Promise<SIPOutboundTrunkInfo> {
626
+ const req = new UpdateSIPOutboundTrunkRequest({
627
+ sipTrunkId,
628
+ action: {
629
+ case: 'update',
630
+ value: fields,
631
+ },
632
+ }).toJson();
633
+
634
+ const data = await this.rpc.request(
635
+ svc,
636
+ 'UpdateSIPOutboundTrunk',
637
+ req,
638
+ await this.authHeader({}, { admin: true }),
639
+ );
640
+
641
+ return SIPOutboundTrunkInfo.fromJson(data, { ignoreUnknownFields: true });
642
+ }
643
+
644
+ /**
645
+ * List SIP dispatch rules with optional filtering.
646
+ *
647
+ * @param list - Request with optional filtering parameters
648
+ * @returns Response containing list of SIP dispatch rules
649
+ */
650
+ async listSipDispatchRule(
651
+ list: ListSipDispatchRuleOptions = {},
652
+ ): Promise<Array<SIPDispatchRuleInfo>> {
653
+ const req = new ListSIPDispatchRuleRequest(list).toJson();
393
654
  const data = await this.rpc.request(
394
655
  svc,
395
656
  'ListSIPDispatchRule',
396
- new ListSIPDispatchRuleRequest(req).toJson(),
657
+ req,
397
658
  await this.authHeader({}, { admin: true }),
398
659
  );
399
660
  return ListSIPDispatchRuleResponse.fromJson(data, { ignoreUnknownFields: true }).items ?? [];
400
661
  }
401
662
 
402
663
  /**
403
- * @param sipDispatchRuleId - sip trunk to delete
664
+ * Delete a SIP dispatch rule.
665
+ *
666
+ * @param sipDispatchRuleId - ID of the SIP dispatch rule to delete
667
+ * @returns Deleted rule information
404
668
  */
405
669
  async deleteSipDispatchRule(sipDispatchRuleId: string): Promise<SIPDispatchRuleInfo> {
406
670
  const data = await this.rpc.request(
@@ -413,10 +677,13 @@ export class SipClient extends ServiceBase {
413
677
  }
414
678
 
415
679
  /**
680
+ * Create a new SIP participant.
681
+ *
416
682
  * @param sipTrunkId - sip trunk to use for the call
417
683
  * @param number - number to dial
418
684
  * @param roomName - room to attach the call to
419
685
  * @param opts - CreateSipParticipantOptions
686
+ * @returns Created SIP participant
420
687
  */
421
688
  async createSipParticipant(
422
689
  sipTrunkId: string,
@@ -449,6 +716,7 @@ export class SipClient extends ServiceBase {
449
716
  ? new Duration({ seconds: BigInt(opts.maxCallDuration) })
450
717
  : undefined,
451
718
  krispEnabled: opts.krispEnabled,
719
+ waitUntilAnswered: opts.waitUntilAnswered,
452
720
  }).toJson();
453
721
 
454
722
  const data = await this.rpc.request(
@@ -456,14 +724,18 @@ export class SipClient extends ServiceBase {
456
724
  'CreateSIPParticipant',
457
725
  req,
458
726
  await this.authHeader({}, { call: true }),
727
+ opts.timeout,
459
728
  );
460
729
  return SIPParticipantInfo.fromJson(data, { ignoreUnknownFields: true });
461
730
  }
462
731
 
463
732
  /**
733
+ * Transfer a SIP participant to a different room.
734
+ *
464
735
  * @param roomName - room the SIP participant to transfer is connectd to
465
736
  * @param participantIdentity - identity of the SIP participant to transfer
466
737
  * @param transferTo - SIP URL to transfer the participant to
738
+ * @param opts - TransferSipParticipantOptions
467
739
  */
468
740
  async transferSipParticipant(
469
741
  roomName: string,
package/src/TwirpRPC.ts CHANGED
@@ -9,19 +9,32 @@ const defaultPrefix = '/twirp';
9
9
 
10
10
  export const livekitPackage = 'livekit';
11
11
  export interface Rpc {
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- request(service: string, method: string, data: JsonValue, headers?: any): Promise<string>;
12
+ request(
13
+ service: string,
14
+ method: string,
15
+ data: JsonValue,
16
+ headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
17
+ timeout?: number,
18
+ ): Promise<string>;
14
19
  }
15
20
 
16
21
  export class TwirpError extends Error {
17
22
  status: number;
18
23
  code?: string;
24
+ metadata?: Record<string, string>;
19
25
 
20
- constructor(name: string, message: string, status: number, code?: string) {
26
+ constructor(
27
+ name: string,
28
+ message: string,
29
+ status: number,
30
+ code?: string,
31
+ metadata?: Record<string, string>,
32
+ ) {
21
33
  super(message);
22
34
  this.name = name;
23
35
  this.status = status;
24
36
  this.code = code;
37
+ this.metadata = metadata;
25
38
  }
26
39
  }
27
40
 
@@ -44,24 +57,36 @@ export class TwirpRpc {
44
57
  this.prefix = prefix || defaultPrefix;
45
58
  }
46
59
 
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
- async request(service: string, method: string, data: any, headers?: any): Promise<any> {
60
+ async request(
61
+ service: string,
62
+ method: string,
63
+ data: any, // eslint-disable-line @typescript-eslint/no-explicit-any
64
+ headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
65
+ timeout = 60,
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ ): Promise<any> {
49
68
  const path = `${this.prefix}/${this.pkg}.${service}/${method}`;
50
69
  const url = new URL(path, this.host);
51
-
52
- const response = await fetch(url, {
70
+ const init: RequestInit = {
53
71
  method: 'POST',
54
72
  headers: {
55
73
  'Content-Type': 'application/json;charset=UTF-8',
56
74
  ...headers,
57
75
  },
58
76
  body: JSON.stringify(data),
59
- });
77
+ };
78
+
79
+ if (timeout) {
80
+ init.signal = AbortSignal.timeout(timeout * 1000);
81
+ }
82
+
83
+ const response = await fetch(url, init);
60
84
 
61
85
  if (!response.ok) {
62
86
  const isJson = response.headers.get('content-type') === 'application/json';
63
87
  let errorMessage = 'Unknown internal error';
64
88
  let errorCode: string | undefined = undefined;
89
+ let metadata: Record<string, string> | undefined = undefined;
65
90
  try {
66
91
  if (isJson) {
67
92
  const parsedError = (await response.json()) as Record<string, unknown>;
@@ -71,6 +96,9 @@ export class TwirpRpc {
71
96
  if ('code' in parsedError) {
72
97
  errorCode = <string>parsedError.code;
73
98
  }
99
+ if ('meta' in parsedError) {
100
+ metadata = <Record<string, string>>parsedError.meta;
101
+ }
74
102
  } else {
75
103
  errorMessage = await response.text();
76
104
  }
@@ -79,7 +107,7 @@ export class TwirpRpc {
79
107
  console.debug(`Error when trying to parse error message, using defaults`, e);
80
108
  }
81
109
 
82
- throw new TwirpError(response.statusText, errorMessage, response.status, errorCode);
110
+ throw new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);
83
111
  }
84
112
  const parsedResp = (await response.json()) as Record<string, unknown>;
85
113
 
package/src/grants.ts CHANGED
@@ -90,6 +90,9 @@ export interface VideoGrant {
90
90
 
91
91
  /** allow participant to subscribe to metrics */
92
92
  canSubscribeMetrics?: boolean;
93
+
94
+ /** destination room which this participant can forward to */
95
+ destinationRoom?: string;
93
96
  }
94
97
 
95
98
  export interface SIPGrant {