livekit-server-sdk 2.12.0 → 2.13.1

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/TwirpRPC.js CHANGED
@@ -1,11 +1,12 @@
1
1
  const defaultPrefix = "/twirp";
2
2
  const livekitPackage = "livekit";
3
3
  class TwirpError extends Error {
4
- constructor(name, message, status, code) {
4
+ constructor(name, message, status, code, metadata) {
5
5
  super(message);
6
6
  this.name = name;
7
7
  this.status = status;
8
8
  this.code = code;
9
+ this.metadata = metadata;
9
10
  }
10
11
  }
11
12
  class TwirpRpc {
@@ -17,22 +18,26 @@ class TwirpRpc {
17
18
  this.pkg = pkg;
18
19
  this.prefix = prefix || defaultPrefix;
19
20
  }
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- async request(service, method, data, headers) {
21
+ async request(service, method, data, headers, timeout = 60) {
22
22
  const path = `${this.prefix}/${this.pkg}.${service}/${method}`;
23
23
  const url = new URL(path, this.host);
24
- const response = await fetch(url, {
24
+ const init = {
25
25
  method: "POST",
26
26
  headers: {
27
27
  "Content-Type": "application/json;charset=UTF-8",
28
28
  ...headers
29
29
  },
30
30
  body: JSON.stringify(data)
31
- });
31
+ };
32
+ if (timeout) {
33
+ init.signal = AbortSignal.timeout(timeout * 1e3);
34
+ }
35
+ const response = await fetch(url, init);
32
36
  if (!response.ok) {
33
37
  const isJson = response.headers.get("content-type") === "application/json";
34
38
  let errorMessage = "Unknown internal error";
35
39
  let errorCode = void 0;
40
+ let metadata = void 0;
36
41
  try {
37
42
  if (isJson) {
38
43
  const parsedError = await response.json();
@@ -42,13 +47,16 @@ class TwirpRpc {
42
47
  if ("code" in parsedError) {
43
48
  errorCode = parsedError.code;
44
49
  }
50
+ if ("meta" in parsedError) {
51
+ metadata = parsedError.meta;
52
+ }
45
53
  } else {
46
54
  errorMessage = await response.text();
47
55
  }
48
56
  } catch (e) {
49
57
  console.debug(`Error when trying to parse error message, using defaults`, e);
50
58
  }
51
- throw new TwirpError(response.statusText, errorMessage, response.status, errorCode);
59
+ throw new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);
52
60
  }
53
61
  const parsedResp = await response.json();
54
62
  const camelcaseKeys = await import("camelcase-keys").then((mod) => mod.default);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\n\n// twirp RPC adapter for client implementation\n\nconst defaultPrefix = '/twirp';\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n request(service: string, method: string, data: JsonValue, headers?: any): Promise<string>;\n}\n\nexport class TwirpError extends Error {\n status: number;\n code?: string;\n\n constructor(name: string, message: string, status: number, code?: string) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n constructor(host: string, pkg: string, prefix?: string) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.prefix = prefix || defaultPrefix;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async request(service: string, method: string, data: any, headers?: any): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const url = new URL(path, this.host);\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json;charset=UTF-8',\n ...headers,\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n\n throw new TwirpError(response.statusText, errorMessage, response.status, errorCode);\n }\n const parsedResp = (await response.json()) as Record<string, unknown>;\n\n const camelcaseKeys = await import('camelcase-keys').then((mod) => mod.default);\n return camelcaseKeys(parsedResp, { deep: true });\n }\n}\n"],"mappings":"AAOA,MAAM,gBAAgB;AAEf,MAAM,iBAAiB;AAMvB,MAAM,mBAAmB,MAAM;AAAA,EAIpC,YAAY,MAAc,SAAiB,QAAgB,MAAe;AACxE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,SAAS;AAAA,EAOpB,YAAY,MAAc,KAAa,QAAiB;AACtD,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,aAAO,KAAK,QAAQ,MAAM,MAAM;AAAA,IAClC;AACA,SAAK,OAAO;AACZ,SAAK,MAAM;AACX,SAAK,SAAS,UAAU;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,QAAQ,SAAiB,QAAgB,MAAW,SAA6B;AACrF,UAAM,OAAO,GAAG,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,OAAO,IAAI,MAAM;AAC5D,UAAM,MAAM,IAAI,IAAI,MAAM,KAAK,IAAI;AAEnC,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG;AAAA,MACL;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,SAAS,QAAQ,IAAI,cAAc,MAAM;AACxD,UAAI,eAAe;AACnB,UAAI,YAAgC;AACpC,UAAI;AACF,YAAI,QAAQ;AACV,gBAAM,cAAe,MAAM,SAAS,KAAK;AACzC,cAAI,SAAS,aAAa;AACxB,2BAAuB,YAAY;AAAA,UACrC;AACA,cAAI,UAAU,aAAa;AACzB,wBAAoB,YAAY;AAAA,UAClC;AAAA,QACF,OAAO;AACL,yBAAe,MAAM,SAAS,KAAK;AAAA,QACrC;AAAA,MACF,SAAS,GAAG;AAEV,gBAAQ,MAAM,4DAA4D,CAAC;AAAA,MAC7E;AAEA,YAAM,IAAI,WAAW,SAAS,YAAY,cAAc,SAAS,QAAQ,SAAS;AAAA,IACpF;AACA,UAAM,aAAc,MAAM,SAAS,KAAK;AAExC,UAAM,gBAAgB,MAAM,OAAO,gBAAgB,EAAE,KAAK,CAAC,QAAQ,IAAI,OAAO;AAC9E,WAAO,cAAc,YAAY,EAAE,MAAM,KAAK,CAAC;AAAA,EACjD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\n\n// twirp RPC adapter for client implementation\n\nconst defaultPrefix = '/twirp';\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n request(\n service: string,\n method: string,\n data: JsonValue,\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout?: number,\n ): Promise<string>;\n}\n\nexport class TwirpError extends Error {\n status: number;\n code?: string;\n metadata?: Record<string, string>;\n\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n constructor(host: string, pkg: string, prefix?: string) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.prefix = prefix || defaultPrefix;\n }\n\n async request(\n service: string,\n method: string,\n data: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout = 60,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const url = new URL(path, this.host);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json;charset=UTF-8',\n ...headers,\n },\n body: JSON.stringify(data),\n };\n\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n const response = await fetch(url, init);\n\n if (!response.ok) {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n let metadata: Record<string, string> | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n if ('meta' in parsedError) {\n metadata = <Record<string, string>>parsedError.meta;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n\n throw new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n }\n const parsedResp = (await response.json()) as Record<string, unknown>;\n\n const camelcaseKeys = await import('camelcase-keys').then((mod) => mod.default);\n return camelcaseKeys(parsedResp, { deep: true });\n }\n}\n"],"mappings":"AAOA,MAAM,gBAAgB;AAEf,MAAM,iBAAiB;AAWvB,MAAM,mBAAmB,MAAM;AAAA,EAKpC,YACE,MACA,SACA,QACA,MACA,UACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAKO,MAAM,SAAS;AAAA,EAOpB,YAAY,MAAc,KAAa,QAAiB;AACtD,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,aAAO,KAAK,QAAQ,MAAM,MAAM;AAAA,IAClC;AACA,SAAK,OAAO;AACZ,SAAK,MAAM;AACX,SAAK,SAAS,UAAU;AAAA,EAC1B;AAAA,EAEA,MAAM,QACJ,SACA,QACA,MACA,SACA,UAAU,IAEI;AACd,UAAM,OAAO,GAAG,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,OAAO,IAAI,MAAM;AAC5D,UAAM,MAAM,IAAI,IAAI,MAAM,KAAK,IAAI;AACnC,UAAM,OAAoB;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG;AAAA,MACL;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAEA,QAAI,SAAS;AACX,WAAK,SAAS,YAAY,QAAQ,UAAU,GAAI;AAAA,IAClD;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAEtC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,SAAS,QAAQ,IAAI,cAAc,MAAM;AACxD,UAAI,eAAe;AACnB,UAAI,YAAgC;AACpC,UAAI,WAA+C;AACnD,UAAI;AACF,YAAI,QAAQ;AACV,gBAAM,cAAe,MAAM,SAAS,KAAK;AACzC,cAAI,SAAS,aAAa;AACxB,2BAAuB,YAAY;AAAA,UACrC;AACA,cAAI,UAAU,aAAa;AACzB,wBAAoB,YAAY;AAAA,UAClC;AACA,cAAI,UAAU,aAAa;AACzB,uBAAmC,YAAY;AAAA,UACjD;AAAA,QACF,OAAO;AACL,yBAAe,MAAM,SAAS,KAAK;AAAA,QACrC;AAAA,MACF,SAAS,GAAG;AAEV,gBAAQ,MAAM,4DAA4D,CAAC;AAAA,MAC7E;AAEA,YAAM,IAAI,WAAW,SAAS,YAAY,cAAc,SAAS,QAAQ,WAAW,QAAQ;AAAA,IAC9F;AACA,UAAM,aAAc,MAAM,SAAS,KAAK;AAExC,UAAM,gBAAgB,MAAM,OAAO,gBAAgB,EAAE,KAAK,CAAC,QAAQ,IAAI,OAAO;AAC9E,WAAO,cAAc,YAAY,EAAE,MAAM,KAAK,CAAC;AAAA,EACjD;AACF;","names":[]}
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ export { EgressClient, EncodedOutputs, ListEgressOptions, ParticipantEgressOptio
5
5
  export { ClaimGrants, SIPGrant, VideoGrant, claimsToJwtPayload, trackSourceToString } from './grants.cjs';
6
6
  export { CreateIngressOptions, IngressClient, ListIngressOptions, UpdateIngressOptions } from './IngressClient.cjs';
7
7
  export { CreateOptions, RoomServiceClient, SendDataOptions, UpdateParticipantOptions } from './RoomServiceClient.cjs';
8
- export { CreateSipDispatchRuleOptions, CreateSipInboundTrunkOptions, CreateSipOutboundTrunkOptions, CreateSipParticipantOptions, CreateSipTrunkOptions, SipClient, SipDispatchRuleDirect, SipDispatchRuleIndividual, TransferSipParticipantOptions } from './SipClient.cjs';
8
+ export { CreateSipDispatchRuleOptions, CreateSipInboundTrunkOptions, CreateSipOutboundTrunkOptions, CreateSipParticipantOptions, CreateSipTrunkOptions, ListSipDispatchRuleOptions, ListSipTrunkOptions, SipClient, SipDispatchRuleDirect, SipDispatchRuleIndividual, SipDispatchRuleUpdateOptions, SipInboundTrunkUpdateOptions, SipOutboundTrunkUpdateOptions, TransferSipParticipantOptions } from './SipClient.cjs';
9
9
  export { WebhookEvent, WebhookEventNames, WebhookReceiver, authorizeHeader } from './WebhookReceiver.cjs';
10
10
  import './ServiceBase.cjs';
11
11
  import 'jose';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-server-sdk",
3
- "version": "2.12.0",
3
+ "version": "2.13.1",
4
4
  "description": "Server-side SDK for LiveKit",
5
5
  "main": "dist/index.js",
6
6
  "require": "dist/index.cjs",
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@bufbuild/protobuf": "^1.7.2",
33
- "@livekit/protocol": "^1.36.1",
33
+ "@livekit/protocol": "^1.39.0",
34
34
  "camelcase-keys": "^9.0.0",
35
35
  "jose": "^5.1.2"
36
36
  },
@@ -30,12 +30,12 @@ export interface AccessTokenOptions {
30
30
  identity?: string;
31
31
 
32
32
  /**
33
- * custom metadata to be passed to participants
33
+ * custom participant metadata
34
34
  */
35
35
  metadata?: string;
36
36
 
37
37
  /**
38
- * custom attributes to be passed to participants
38
+ * custom participant attributes
39
39
  */
40
40
  attributes?: Record<string, string>;
41
41
  }
@@ -11,6 +11,7 @@ import type {
11
11
  StreamOutput,
12
12
  } from '@livekit/protocol';
13
13
  import {
14
+ AudioMixing,
14
15
  EgressInfo,
15
16
  ListEgressRequest,
16
17
  ListEgressResponse,
@@ -50,6 +51,10 @@ export interface RoomCompositeOptions {
50
51
  * custom template url. optional
51
52
  */
52
53
  customBaseUrl?: string;
54
+ /**
55
+ * audio mixing options. optional
56
+ */
57
+ audioMixing?: AudioMixing;
53
58
  }
54
59
 
55
60
  export interface WebOptions {
@@ -151,6 +156,7 @@ export class EgressClient extends ServiceBase {
151
156
  audioOnly?: boolean,
152
157
  videoOnly?: boolean,
153
158
  customBaseUrl?: string,
159
+ audioMixing?: AudioMixing,
154
160
  ): Promise<EgressInfo>;
155
161
  async startRoomCompositeEgress(
156
162
  roomName: string,
@@ -160,6 +166,7 @@ export class EgressClient extends ServiceBase {
160
166
  audioOnly?: boolean,
161
167
  videoOnly?: boolean,
162
168
  customBaseUrl?: string,
169
+ audioMixing?: AudioMixing,
163
170
  ): Promise<EgressInfo> {
164
171
  let layout: string | undefined;
165
172
  if (optsOrLayout !== undefined) {
@@ -172,6 +179,7 @@ export class EgressClient extends ServiceBase {
172
179
  audioOnly = opts.audioOnly;
173
180
  videoOnly = opts.videoOnly;
174
181
  customBaseUrl = opts.customBaseUrl;
182
+ audioMixing = opts.audioMixing;
175
183
  }
176
184
  }
177
185
 
@@ -179,6 +187,7 @@ export class EgressClient extends ServiceBase {
179
187
  audioOnly ??= false;
180
188
  videoOnly ??= false;
181
189
  customBaseUrl ??= '';
190
+ audioMixing ??= AudioMixing.DEFAULT_MIXING;
182
191
 
183
192
  const {
184
193
  output: legacyOutput,
@@ -193,6 +202,7 @@ export class EgressClient extends ServiceBase {
193
202
  roomName,
194
203
  layout,
195
204
  audioOnly,
205
+ audioMixing,
196
206
  videoOnly,
197
207
  customBaseUrl,
198
208
  output: legacyOutput,
@@ -10,6 +10,7 @@ import {
10
10
  ListParticipantsResponse,
11
11
  ListRoomsRequest,
12
12
  ListRoomsResponse,
13
+ MoveParticipantRequest,
13
14
  MuteRoomTrackRequest,
14
15
  MuteRoomTrackResponse,
15
16
  ParticipantInfo,
@@ -233,6 +234,9 @@ export class RoomServiceClient extends ServiceBase {
233
234
  * participant. When the source participant disconnects or the
234
235
  * `RemoveParticipant` method is called in the destination room, the
235
236
  * forwarding will be stopped.
237
+ * @param room -
238
+ * @param identity -
239
+ * @param destinationRoom - the room to forward the participant to
236
240
  */
237
241
  async forwardParticipant(room: string, identity: string, destinationRoom: string): Promise<void> {
238
242
  await this.rpc.request(
@@ -243,6 +247,23 @@ export class RoomServiceClient extends ServiceBase {
243
247
  );
244
248
  }
245
249
 
250
+ /**
251
+ * Move a connected participant to a different room. Requires `roomAdmin` and `destinationRoom`.
252
+ * The participant will be removed from the current room and added to the destination room.
253
+ * From the other observers' perspective, the participant would've disconnected from the previous room and joined the new one.
254
+ * @param room -
255
+ * @param identity -
256
+ * @param destinationRoom - the room to move the participant to
257
+ */
258
+ async moveParticipant(room: string, identity: string, destinationRoom: string): Promise<void> {
259
+ await this.rpc.request(
260
+ svc,
261
+ 'MoveParticipant',
262
+ new MoveParticipantRequest({ room, identity, destinationRoom }).toJson(),
263
+ await this.authHeader({ roomAdmin: true, room, destinationRoom }),
264
+ );
265
+ }
266
+
246
267
  /**
247
268
  * Mutes a track that the participant has published.
248
269
  * @param room -