livekit-client 2.5.10 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
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;