livekit-client 2.5.10 → 2.6.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.
Files changed (51) hide show
  1. package/README.md +54 -0
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  4. package/dist/livekit-client.esm.mjs +431 -45
  5. package/dist/livekit-client.esm.mjs.map +1 -1
  6. package/dist/livekit-client.umd.js +1 -1
  7. package/dist/livekit-client.umd.js.map +1 -1
  8. package/dist/src/api/SignalClient.d.ts.map +1 -1
  9. package/dist/src/index.d.ts +1 -0
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/room/PCTransport.d.ts +2 -0
  12. package/dist/src/room/PCTransport.d.ts.map +1 -1
  13. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  14. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  15. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/errors.d.ts +2 -2
  18. package/dist/src/room/errors.d.ts.map +1 -1
  19. package/dist/src/room/participant/LocalParticipant.d.ts +56 -0
  20. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  21. package/dist/src/room/rpc.d.ts +96 -0
  22. package/dist/src/room/rpc.d.ts.map +1 -0
  23. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  24. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  25. package/dist/src/room/track/RemoteVideoTrack.d.ts +2 -1
  26. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/utils.d.ts +2 -2
  28. package/dist/src/room/track/utils.d.ts.map +1 -1
  29. package/dist/ts4.2/src/index.d.ts +2 -0
  30. package/dist/ts4.2/src/room/PCTransport.d.ts +2 -0
  31. package/dist/ts4.2/src/room/errors.d.ts +2 -2
  32. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +56 -0
  33. package/dist/ts4.2/src/room/rpc.d.ts +96 -0
  34. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  35. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +2 -1
  36. package/dist/ts4.2/src/room/track/utils.d.ts +2 -2
  37. package/package.json +2 -2
  38. package/src/api/SignalClient.ts +19 -3
  39. package/src/index.ts +2 -0
  40. package/src/room/PCTransport.ts +42 -29
  41. package/src/room/PCTransportManager.ts +6 -1
  42. package/src/room/RTCEngine.ts +13 -3
  43. package/src/room/RegionUrlProvider.ts +3 -1
  44. package/src/room/Room.ts +9 -3
  45. package/src/room/errors.ts +2 -2
  46. package/src/room/participant/LocalParticipant.test.ts +304 -0
  47. package/src/room/participant/LocalParticipant.ts +340 -1
  48. package/src/room/rpc.ts +172 -0
  49. package/src/room/track/RemoteAudioTrack.ts +1 -1
  50. package/src/room/track/RemoteVideoTrack.ts +1 -1
  51. package/src/room/track/utils.ts +1 -6
@@ -0,0 +1,96 @@
1
+ import { RpcError as RpcError_Proto } from '@livekit/protocol';
2
+ /** Parameters for initiating an RPC call */
3
+ export interface PerformRpcParams {
4
+ /** The `identity` of the destination participant */
5
+ destinationIdentity: string;
6
+ /** The method name to call */
7
+ method: string;
8
+ /** The method payload */
9
+ payload: string;
10
+ /** Timeout for receiving a response after initial connection (milliseconds). Default: 10000 */
11
+ responseTimeout?: number;
12
+ }
13
+ /**
14
+ * Data passed to method handler for incoming RPC invocations
15
+ */
16
+ export interface RpcInvocationData {
17
+ /**
18
+ * The unique request ID. Will match at both sides of the call, useful for debugging or logging.
19
+ */
20
+ requestId: string;
21
+ /**
22
+ * The unique participant identity of the caller.
23
+ */
24
+ callerIdentity: string;
25
+ /**
26
+ * The payload of the request. User-definable format, typically JSON.
27
+ */
28
+ payload: string;
29
+ /**
30
+ * The maximum time the caller will wait for a response.
31
+ */
32
+ responseTimeout: number;
33
+ }
34
+ /**
35
+ * Specialized error handling for RPC methods.
36
+ *
37
+ * Instances of this type, when thrown in a method handler, will have their `message`
38
+ * serialized and sent across the wire. The sender will receive an equivalent error on the other side.
39
+ *
40
+ * Built-in types are included but developers may use any string, with a max length of 256 bytes.
41
+ */
42
+ export declare class RpcError extends Error {
43
+ static MAX_MESSAGE_BYTES: number;
44
+ static MAX_DATA_BYTES: number;
45
+ code: number;
46
+ data?: string;
47
+ /**
48
+ * Creates an error object with the given code and message, plus an optional data payload.
49
+ *
50
+ * If thrown in an RPC method handler, the error will be sent back to the caller.
51
+ *
52
+ * Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings).
53
+ */
54
+ constructor(code: number, message: string, data?: string);
55
+ /**
56
+ * @internal
57
+ */
58
+ static fromProto(proto: RpcError_Proto): RpcError;
59
+ /**
60
+ * @internal
61
+ */
62
+ toProto(): RpcError_Proto;
63
+ static ErrorCode: {
64
+ readonly APPLICATION_ERROR: 1500;
65
+ readonly CONNECTION_TIMEOUT: 1501;
66
+ readonly RESPONSE_TIMEOUT: 1502;
67
+ readonly RECIPIENT_DISCONNECTED: 1503;
68
+ readonly RESPONSE_PAYLOAD_TOO_LARGE: 1504;
69
+ readonly SEND_FAILED: 1505;
70
+ readonly UNSUPPORTED_METHOD: 1400;
71
+ readonly RECIPIENT_NOT_FOUND: 1401;
72
+ readonly REQUEST_PAYLOAD_TOO_LARGE: 1402;
73
+ readonly UNSUPPORTED_SERVER: 1403;
74
+ readonly UNSUPPORTED_VERSION: 1404;
75
+ };
76
+ /**
77
+ * @internal
78
+ */
79
+ static ErrorMessage: Record<keyof typeof RpcError.ErrorCode, string>;
80
+ /**
81
+ * Creates an error object from the code, with an auto-populated message.
82
+ *
83
+ * @internal
84
+ */
85
+ static builtIn(key: keyof typeof RpcError.ErrorCode, data?: string): RpcError;
86
+ }
87
+ export declare const MAX_PAYLOAD_BYTES = 15360;
88
+ /**
89
+ * @internal
90
+ */
91
+ export declare function byteLength(str: string): number;
92
+ /**
93
+ * @internal
94
+ */
95
+ export declare function truncateBytes(str: string, maxBytes: number): string;
96
+ //# sourceMappingURL=rpc.d.ts.map
@@ -50,6 +50,6 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
50
50
  private connectWebAudio;
51
51
  private disconnectWebAudio;
52
52
  protected monitorReceiver: () => Promise<void>;
53
- protected getReceiverStats(): Promise<AudioReceiverStats | undefined>;
53
+ getReceiverStats(): Promise<AudioReceiverStats | undefined>;
54
54
  }
55
55
  //# sourceMappingURL=RemoteAudioTrack.d.ts.map
@@ -1,3 +1,4 @@
1
+ import type { VideoReceiverStats } from '../stats';
1
2
  import type { LoggerOptions } from '../types';
2
3
  import RemoteTrack from './RemoteTrack';
3
4
  import { Track } from './Track';
@@ -35,7 +36,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
35
36
  /** @internal */
36
37
  getDecoderImplementation(): string | undefined;
37
38
  protected monitorReceiver: () => Promise<void>;
38
- private getReceiverStats;
39
+ getReceiverStats(): Promise<VideoReceiverStats | undefined>;
39
40
  private stopObservingElement;
40
41
  protected handleAppVisibilityChanged(): Promise<void>;
41
42
  private readonly debouncedHandleResize;
@@ -1,7 +1,7 @@
1
1
  import { TrackPublishedResponse } from '@livekit/protocol';
2
2
  import { Track } from './Track';
3
3
  import type { TrackPublication } from './TrackPublication';
4
- import type { AudioCaptureOptions, CreateLocalTracksOptions, ScreenShareCaptureOptions, VideoCaptureOptions } from './options';
4
+ import type { AudioCaptureOptions, CreateLocalTracksOptions, ScreenShareCaptureOptions, VideoCaptureOptions, VideoCodec } from './options';
5
5
  import type { AudioTrack } from './types';
6
6
  export declare function mergeDefaultOptions(options?: CreateLocalTracksOptions, audioDefaults?: AudioCaptureOptions, videoDefaults?: VideoCaptureOptions): CreateLocalTracksOptions;
7
7
  export declare function constraintsForOptions(options: CreateLocalTracksOptions): MediaStreamConstraints;
@@ -26,7 +26,7 @@ export declare function sourceToKind(source: Track.Source): MediaDeviceKind | un
26
26
  * @internal
27
27
  */
28
28
  export declare function screenCaptureToDisplayMediaStreamOptions(options: ScreenShareCaptureOptions): DisplayMediaStreamOptions;
29
- export declare function mimeTypeToVideoCodecString(mimeType: string): "vp8" | "h264" | "vp9" | "av1";
29
+ export declare function mimeTypeToVideoCodecString(mimeType: string): VideoCodec;
30
30
  export declare function getTrackPublicationInfo<T extends TrackPublication>(tracks: T[]): TrackPublishedResponse[];
31
31
  export declare function getLogContextFromTrack(track: Track | TrackPublication): Record<string, unknown>;
32
32
  export declare function supportsSynchronizationSources(): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.5.10",
3
+ "version": "2.6.1",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -88,7 +88,7 @@
88
88
  "build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && pnpm downlevel-dts",
89
89
  "build:watch": "rollup --watch --config --bundleConfigAsCjs",
90
90
  "build:worker:watch": "rollup --watch --config rollup.config.worker.js --bundleConfigAsCjs",
91
- "build-docs": "typedoc",
91
+ "build-docs": "typedoc && mkdir -p docs/assets/github && cp .github/*.png docs/assets/github/ && find docs -name '*.html' -type f -exec sed -i.bak 's|=\"/.github/|=\"assets/github/|g' {} + && find docs -name '*.bak' -delete",
92
92
  "proto": "protoc --es_out src/proto --es_opt target=ts -I./protocol ./protocol/livekit_rtc.proto ./protocol/livekit_models.proto",
93
93
  "examples:demo": "vite examples/demo -c vite.config.mjs",
94
94
  "lint": "eslint src",
@@ -272,12 +272,22 @@ export class SignalClient {
272
272
  const abortHandler = async () => {
273
273
  this.close();
274
274
  clearTimeout(wsTimeout);
275
- reject(new ConnectionError('room connection has been cancelled (signal)'));
275
+ reject(
276
+ new ConnectionError(
277
+ 'room connection has been cancelled (signal)',
278
+ ConnectionErrorReason.Cancelled,
279
+ ),
280
+ );
276
281
  };
277
282
 
278
283
  const wsTimeout = setTimeout(() => {
279
284
  this.close();
280
- reject(new ConnectionError('room connection has timed out (signal)'));
285
+ reject(
286
+ new ConnectionError(
287
+ 'room connection has timed out (signal)',
288
+ ConnectionErrorReason.ServerUnreachable,
289
+ ),
290
+ );
281
291
  }, opts.websocketTimeout);
282
292
 
283
293
  if (abortSignal?.aborted) {
@@ -391,6 +401,7 @@ export class SignalClient {
391
401
  reject(
392
402
  new ConnectionError(
393
403
  `did not receive join response, got ${resp.message?.case} instead`,
404
+ ConnectionErrorReason.InternalError,
394
405
  ),
395
406
  );
396
407
  }
@@ -407,7 +418,12 @@ export class SignalClient {
407
418
 
408
419
  this.ws.onclose = (ev: CloseEvent) => {
409
420
  if (this.isEstablishingConnection) {
410
- reject(new ConnectionError('Websocket got closed during a (re)connection attempt'));
421
+ reject(
422
+ new ConnectionError(
423
+ 'Websocket got closed during a (re)connection attempt',
424
+ ConnectionErrorReason.InternalError,
425
+ ),
426
+ );
411
427
  }
412
428
 
413
429
  this.log.warn(`websocket closed`, {
package/src/index.ts CHANGED
@@ -39,6 +39,8 @@ import {
39
39
  } from './room/utils';
40
40
  import { getBrowser } from './utils/browserParser';
41
41
 
42
+ export { RpcError, type RpcInvocationData, type PerformRpcParams } from './room/rpc';
43
+
42
44
  export * from './connectionHelper/ConnectionCheck';
43
45
  export * from './connectionHelper/checks/Checker';
44
46
  export * from './e2ee';
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
- import type { MediaDescription } from 'sdp-transform';
2
+ import type { MediaDescription, SessionDescription } from 'sdp-transform';
3
3
  import { parse, write } from 'sdp-transform';
4
4
  import { debounce } from 'ts-debounce';
5
5
  import log, { LoggerNames, getLogger } from '../logger';
@@ -48,6 +48,8 @@ export default class PCTransport extends EventEmitter {
48
48
 
49
49
  private loggerOptions: LoggerOptions;
50
50
 
51
+ private ddExtID = 0;
52
+
51
53
  pendingCandidates: RTCIceCandidateInit[] = [];
52
54
 
53
55
  restartingIce: boolean = false;
@@ -288,7 +290,7 @@ export default class PCTransport extends EventEmitter {
288
290
  }
289
291
 
290
292
  if (isSVCCodec(trackbr.codec)) {
291
- ensureVideoDDExtensionForSVC(media);
293
+ this.ensureVideoDDExtensionForSVC(media, sdpParsed);
292
294
  }
293
295
 
294
296
  // TODO: av1 slow starting issue already fixed in chrome 124, clean this after some versions
@@ -503,6 +505,44 @@ export default class PCTransport extends EventEmitter {
503
505
  throw new NegotiationError(msg);
504
506
  }
505
507
  }
508
+
509
+ private ensureVideoDDExtensionForSVC(
510
+ media: {
511
+ type: string;
512
+ port: number;
513
+ protocol: string;
514
+ payloads?: string | undefined;
515
+ } & MediaDescription,
516
+ sdp: SessionDescription,
517
+ ) {
518
+ const ddFound = media.ext?.some((ext): boolean => {
519
+ if (ext.uri === ddExtensionURI) {
520
+ return true;
521
+ }
522
+ return false;
523
+ });
524
+
525
+ if (!ddFound) {
526
+ if (this.ddExtID === 0) {
527
+ let maxID = 0;
528
+ sdp.media.forEach((m) => {
529
+ if (m.type !== 'video') {
530
+ return;
531
+ }
532
+ m.ext?.forEach((ext) => {
533
+ if (ext.value > maxID) {
534
+ maxID = ext.value;
535
+ }
536
+ });
537
+ });
538
+ this.ddExtID = maxID + 1;
539
+ }
540
+ media.ext?.push({
541
+ value: this.ddExtID,
542
+ uri: ddExtensionURI,
543
+ });
544
+ }
545
+ }
506
546
  }
507
547
 
508
548
  function ensureAudioNackAndStereo(
@@ -555,33 +595,6 @@ function ensureAudioNackAndStereo(
555
595
  }
556
596
  }
557
597
 
558
- function ensureVideoDDExtensionForSVC(
559
- media: {
560
- type: string;
561
- port: number;
562
- protocol: string;
563
- payloads?: string | undefined;
564
- } & MediaDescription,
565
- ) {
566
- let maxID = 0;
567
- const ddFound = media.ext?.some((ext): boolean => {
568
- if (ext.uri === ddExtensionURI) {
569
- return true;
570
- }
571
- if (ext.value > maxID) {
572
- maxID = ext.value;
573
- }
574
- return false;
575
- });
576
-
577
- if (!ddFound) {
578
- media.ext?.push({
579
- value: maxID + 1,
580
- uri: ddExtensionURI,
581
- });
582
- }
583
- }
584
-
585
598
  function extractStereoAndNackAudioFromOffer(offer: RTCSessionDescriptionInit): {
586
599
  stereoMids: string[];
587
600
  nackMids: string[];
@@ -342,7 +342,12 @@ export class PCTransportManager {
342
342
 
343
343
  const connectTimeout = CriticalTimers.setTimeout(() => {
344
344
  abortController?.signal.removeEventListener('abort', abortHandler);
345
- reject(new ConnectionError('could not establish pc connection'));
345
+ reject(
346
+ new ConnectionError(
347
+ 'could not establish pc connection',
348
+ ConnectionErrorReason.InternalError,
349
+ ),
350
+ );
346
351
  }, timeout);
347
352
 
348
353
  while (this.state !== PCTransportState.CONNECTED) {
@@ -313,7 +313,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
313
313
  const publicationTimeout = setTimeout(() => {
314
314
  delete this.pendingTrackResolvers[req.cid];
315
315
  reject(
316
- new ConnectionError('publication of local track timed out, no response from server'),
316
+ new ConnectionError(
317
+ 'publication of local track timed out, no response from server',
318
+ ConnectionErrorReason.InternalError,
319
+ ),
317
320
  );
318
321
  }, 10_000);
319
322
  this.pendingTrackResolvers[req.cid] = {
@@ -1061,7 +1064,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1061
1064
  } catch (e: any) {
1062
1065
  // TODO do we need a `failed` state here for the PC?
1063
1066
  this.pcState = PCState.Disconnected;
1064
- throw new ConnectionError(`could not establish PC connection, ${e.message}`);
1067
+ throw new ConnectionError(
1068
+ `could not establish PC connection, ${e.message}`,
1069
+ ConnectionErrorReason.InternalError,
1070
+ );
1065
1071
  }
1066
1072
  }
1067
1073
 
@@ -1126,7 +1132,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1126
1132
  const transport = subscriber ? this.pcManager.subscriber : this.pcManager.publisher;
1127
1133
  const transportName = subscriber ? 'Subscriber' : 'Publisher';
1128
1134
  if (!transport) {
1129
- throw new ConnectionError(`${transportName} connection not set`);
1135
+ throw new ConnectionError(
1136
+ `${transportName} connection not set`,
1137
+ ConnectionErrorReason.InternalError,
1138
+ );
1130
1139
  }
1131
1140
 
1132
1141
  let needNegotiation = false;
@@ -1167,6 +1176,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1167
1176
 
1168
1177
  throw new ConnectionError(
1169
1178
  `could not establish ${transportName} connection, state: ${transport.getICEConnectionState()}`,
1179
+ ConnectionErrorReason.InternalError,
1170
1180
  );
1171
1181
  }
1172
1182
 
@@ -70,7 +70,9 @@ export class RegionUrlProvider {
70
70
  } else {
71
71
  throw new ConnectionError(
72
72
  `Could not fetch region settings: ${regionSettingsResponse.statusText}`,
73
- regionSettingsResponse.status === 401 ? ConnectionErrorReason.NotAllowed : undefined,
73
+ regionSettingsResponse.status === 401
74
+ ? ConnectionErrorReason.NotAllowed
75
+ : ConnectionErrorReason.InternalError,
74
76
  regionSettingsResponse.status,
75
77
  );
76
78
  }
package/src/room/Room.ts CHANGED
@@ -714,7 +714,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
714
714
  } catch (err) {
715
715
  await this.engine.close();
716
716
  this.recreateEngine();
717
- const resultingError = new ConnectionError(`could not establish signal connection`);
717
+ const resultingError = new ConnectionError(
718
+ `could not establish signal connection`,
719
+ ConnectionErrorReason.ServerUnreachable,
720
+ );
718
721
  if (err instanceof Error) {
719
722
  resultingError.message = `${resultingError.message}: ${err.message}`;
720
723
  }
@@ -732,7 +735,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
732
735
  if (abortController.signal.aborted) {
733
736
  await this.engine.close();
734
737
  this.recreateEngine();
735
- throw new ConnectionError(`Connection attempt aborted`);
738
+ throw new ConnectionError(`Connection attempt aborted`, ConnectionErrorReason.Cancelled);
736
739
  }
737
740
 
738
741
  try {
@@ -783,7 +786,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
783
786
  this.log.warn('abort connection attempt', this.logContext);
784
787
  this.abortController?.abort();
785
788
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
786
- this.connectFuture?.reject?.(new ConnectionError('Client initiated disconnect'));
789
+ this.connectFuture?.reject?.(
790
+ new ConnectionError('Client initiated disconnect', ConnectionErrorReason.Cancelled),
791
+ );
787
792
  this.connectFuture = undefined;
788
793
  }
789
794
  // send leave
@@ -1414,6 +1419,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1414
1419
  participant.unpublishTrack(publication.trackSid, true);
1415
1420
  });
1416
1421
  this.emit(RoomEvent.ParticipantDisconnected, participant);
1422
+ this.localParticipant?.handleParticipantDisconnected(participant.identity);
1417
1423
  }
1418
1424
 
1419
1425
  // updates are sent only when there's a change to speaker ordering
@@ -20,9 +20,9 @@ export const enum ConnectionErrorReason {
20
20
  export class ConnectionError extends LivekitError {
21
21
  status?: number;
22
22
 
23
- reason?: ConnectionErrorReason;
23
+ reason: ConnectionErrorReason;
24
24
 
25
- constructor(message?: string, reason?: ConnectionErrorReason, status?: number) {
25
+ constructor(message: string, reason: ConnectionErrorReason, status?: number) {
26
26
  super(1, message);
27
27
  this.status = status;
28
28
  this.reason = reason;