livekit-client 2.3.1 → 2.4.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.
Files changed (71) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +14 -7
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +325 -175
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/api/SignalClient.d.ts +5 -2
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  13. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  14. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  15. package/dist/src/e2ee/errors.d.ts +2 -1
  16. package/dist/src/e2ee/errors.d.ts.map +1 -1
  17. package/dist/src/e2ee/index.d.ts +1 -0
  18. package/dist/src/e2ee/index.d.ts.map +1 -1
  19. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  20. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  21. package/dist/src/logger.d.ts.map +1 -1
  22. package/dist/src/room/PCTransport.d.ts +1 -2
  23. package/dist/src/room/PCTransport.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts +2 -1
  25. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  26. package/dist/src/room/Room.d.ts +1 -0
  27. package/dist/src/room/Room.d.ts.map +1 -1
  28. package/dist/src/room/errors.d.ts +5 -0
  29. package/dist/src/room/errors.d.ts.map +1 -1
  30. package/dist/src/room/events.d.ts +15 -2
  31. package/dist/src/room/events.d.ts.map +1 -1
  32. package/dist/src/room/participant/LocalParticipant.d.ts +14 -6
  33. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  34. package/dist/src/room/participant/Participant.d.ts +8 -0
  35. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  36. package/dist/src/room/timers.d.ts +4 -4
  37. package/dist/src/room/timers.d.ts.map +1 -1
  38. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  41. package/dist/src/room/track/utils.d.ts +1 -0
  42. package/dist/src/room/track/utils.d.ts.map +1 -1
  43. package/dist/ts4.2/src/api/SignalClient.d.ts +5 -2
  44. package/dist/ts4.2/src/e2ee/errors.d.ts +2 -1
  45. package/dist/ts4.2/src/e2ee/index.d.ts +1 -0
  46. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
  47. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -1
  48. package/dist/ts4.2/src/room/Room.d.ts +1 -0
  49. package/dist/ts4.2/src/room/errors.d.ts +5 -0
  50. package/dist/ts4.2/src/room/events.d.ts +15 -2
  51. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -6
  52. package/dist/ts4.2/src/room/participant/Participant.d.ts +8 -0
  53. package/dist/ts4.2/src/room/timers.d.ts +4 -4
  54. package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
  55. package/package.json +12 -12
  56. package/src/api/SignalClient.ts +24 -2
  57. package/src/e2ee/errors.ts +8 -1
  58. package/src/e2ee/index.ts +1 -0
  59. package/src/e2ee/worker/FrameCryptor.ts +18 -4
  60. package/src/e2ee/worker/e2ee.worker.ts +5 -1
  61. package/src/logger.ts +4 -3
  62. package/src/room/DeviceManager.ts +1 -1
  63. package/src/room/RTCEngine.ts +3 -0
  64. package/src/room/Room.ts +11 -3
  65. package/src/room/errors.ts +11 -0
  66. package/src/room/events.ts +15 -0
  67. package/src/room/participant/LocalParticipant.ts +102 -10
  68. package/src/room/participant/Participant.ts +23 -0
  69. package/src/room/track/Track.ts +1 -1
  70. package/src/room/track/utils.test.ts +35 -1
  71. package/src/room/track/utils.ts +22 -0
@@ -1,4 +1,4 @@
1
- import { AddTrackRequest, AudioTrackFeature, ConnectionQualityUpdate, JoinResponse, LeaveRequest, ParticipantInfo, ReconnectReason, ReconnectResponse, Room, SessionDescription, SignalRequest, SignalTarget, SimulateScenario, SpeakerInfo, StreamStateUpdate, SubscribedQualityUpdate, SubscriptionPermissionUpdate, SubscriptionResponse, SyncState, TrackPermission, TrackPublishedResponse, TrackUnpublishedResponse, UpdateSubscription, UpdateTrackSettings, VideoLayer } from '@livekit/protocol';
1
+ import { AddTrackRequest, AudioTrackFeature, ConnectionQualityUpdate, ErrorResponse, JoinResponse, LeaveRequest, ParticipantInfo, ReconnectReason, ReconnectResponse, Room, SessionDescription, SignalRequest, SignalTarget, SimulateScenario, SpeakerInfo, StreamStateUpdate, SubscribedQualityUpdate, SubscriptionPermissionUpdate, SubscriptionResponse, SyncState, TrackPermission, TrackPublishedResponse, TrackUnpublishedResponse, UpdateSubscription, UpdateTrackSettings, VideoLayer } from '@livekit/protocol';
2
2
  import type { LoggerOptions } from '../room/types';
3
3
  import { AsyncQueue } from '../utils/AsyncQueue';
4
4
  interface ConnectOpts extends SignalOptions {
@@ -51,11 +51,13 @@ export declare class SignalClient {
51
51
  onLocalTrackUnpublished?: (res: TrackUnpublishedResponse) => void;
52
52
  onTokenRefresh?: (token: string) => void;
53
53
  onLeave?: (leave: LeaveRequest) => void;
54
+ onErrorResponse?: (error: ErrorResponse) => void;
54
55
  connectOptions?: ConnectOpts;
55
56
  ws?: WebSocket;
56
57
  get currentState(): SignalConnectionState;
57
58
  get isDisconnected(): boolean;
58
59
  private get isEstablishingConnection();
60
+ private getNextRequestId;
59
61
  private options?;
60
62
  private pingTimeout;
61
63
  private pingTimeoutDuration;
@@ -66,6 +68,7 @@ export declare class SignalClient {
66
68
  private connectionLock;
67
69
  private log;
68
70
  private loggerContextCb?;
71
+ private _requestId;
69
72
  constructor(useJSON?: boolean, loggerOptions?: LoggerOptions);
70
73
  private get logContext();
71
74
  join(url: string, token: string, opts: SignalOptions, abortSignal?: AbortSignal): Promise<JoinResponse>;
@@ -79,7 +82,7 @@ export declare class SignalClient {
79
82
  sendIceCandidate(candidate: RTCIceCandidateInit, target: SignalTarget): Promise<void>;
80
83
  sendMuteTrack(trackSid: string, muted: boolean): Promise<void>;
81
84
  sendAddTrack(req: AddTrackRequest): Promise<void>;
82
- sendUpdateLocalMetadata(metadata: string, name: string): Promise<void>;
85
+ sendUpdateLocalMetadata(metadata: string, name: string, attributes?: Record<string, string>): Promise<number>;
83
86
  sendUpdateTrackSettings(settings: UpdateTrackSettings): void;
84
87
  sendUpdateSubscription(sub: UpdateSubscription): Promise<void>;
85
88
  sendSyncState(sync: SyncState): Promise<void>;
@@ -6,6 +6,7 @@ export declare enum CryptorErrorReason {
6
6
  }
7
7
  export declare class CryptorError extends LivekitError {
8
8
  reason: CryptorErrorReason;
9
- constructor(message?: string, reason?: CryptorErrorReason);
9
+ participantIdentity?: string;
10
+ constructor(message?: string, reason?: CryptorErrorReason, participantIdentity?: string);
10
11
  }
11
12
  //# sourceMappingURL=errors.d.ts.map
@@ -2,4 +2,5 @@ export * from './KeyProvider';
2
2
  export * from './utils';
3
3
  export * from './types';
4
4
  export * from './events';
5
+ export * from './errors';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { EventEmitter } from 'events';
3
2
  import type { LoggerOptions } from './types';
4
3
  /** @internal */
@@ -43,7 +42,7 @@ export default class PCTransport extends EventEmitter {
43
42
  negotiate: {
44
43
  (this: unknown, ...args: [
45
44
  onError?: ((e: Error) => void) | undefined
46
- ] & any[]): Promise<Promise<void>>;
45
+ ] & any[]): Promise<ReturnType<(onError?: (e: Error) => void) => Promise<void>>>;
47
46
  cancel: (reason?: any) => void;
48
47
  };
49
48
  createAndSendOffer(options?: RTCOfferOptions): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { AddTrackRequest, ConnectionQualityUpdate, JoinResponse, StreamStateUpdate, SubscriptionPermissionUpdate, SubscriptionResponse } from '@livekit/protocol';
2
- import { DataPacket, DataPacket_Kind, DisconnectReason, ParticipantInfo, Room as RoomModel, SpeakerInfo, SubscribedQualityUpdate, TrackInfo, TrackUnpublishedResponse, Transcription } from '@livekit/protocol';
2
+ import { DataPacket, DataPacket_Kind, DisconnectReason, ErrorResponse, ParticipantInfo, Room as RoomModel, SpeakerInfo, SubscribedQualityUpdate, TrackInfo, TrackUnpublishedResponse, Transcription } from '@livekit/protocol';
3
3
  import type TypedEventEmitter from 'typed-emitter';
4
4
  import type { SignalOptions } from '../api/SignalClient';
5
5
  import { SignalClient } from '../api/SignalClient';
@@ -156,6 +156,7 @@ export type EngineEventCallbacks = {
156
156
  localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
157
157
  remoteMute: (trackSid: string, muted: boolean) => void;
158
158
  offline: () => void;
159
+ signalRequestError: (error: ErrorResponse) => void;
159
160
  };
160
161
  export {};
161
162
  //# sourceMappingURL=RTCEngine.d.ts.map
@@ -244,6 +244,7 @@ export type RoomEventCallbacks = {
244
244
  participantMetadataChanged: (metadata: string | undefined, participant: RemoteParticipant | LocalParticipant) => void;
245
245
  participantNameChanged: (name: string, participant: RemoteParticipant | LocalParticipant) => void;
246
246
  participantPermissionsChanged: (prevPermissions: ParticipantPermission | undefined, participant: RemoteParticipant | LocalParticipant) => void;
247
+ participantAttributesChanged: (changedAttributes: Record<string, string>, participant: RemoteParticipant | LocalParticipant) => void;
247
248
  activeSpeakersChanged: (speakers: Array<Participant>) => void;
248
249
  roomMetadataChanged: (metadata: string) => void;
249
250
  dataReceived: (payload: Uint8Array, participant?: RemoteParticipant, kind?: DataPacket_Kind, topic?: string) => void;
@@ -1,3 +1,4 @@
1
+ import { ErrorResponse_Reason } from '@livekit/protocol';
1
2
  export declare class LivekitError extends Error {
2
3
  code: number;
3
4
  constructor(code: number, message?: string);
@@ -32,6 +33,10 @@ export declare class NegotiationError extends LivekitError {
32
33
  export declare class PublishDataError extends LivekitError {
33
34
  constructor(message?: string);
34
35
  }
36
+ export declare class SignalRequestError extends LivekitError {
37
+ reason: ErrorResponse_Reason;
38
+ constructor(message: string, reason?: ErrorResponse_Reason);
39
+ }
35
40
  export declare enum MediaDeviceFailure {
36
41
  PermissionDenied = "PermissionDenied",
37
42
  NotFound = "NotFound",
@@ -162,6 +162,12 @@ export declare enum RoomEvent {
162
162
  *
163
163
  */
164
164
  ParticipantNameChanged = "participantNameChanged",
165
+ /**
166
+ * Participant attributes is an app-specific key value state to be pushed to
167
+ * all users.
168
+ * When a participant's attributes changed, this event will be emitted with the changed attributes and the participant
169
+ */
170
+ ParticipantAttributesChanged = "participantAttributesChanged",
165
171
  /**
166
172
  * Room metadata is a simple way for app-specific state to be pushed to
167
173
  * all users.
@@ -430,7 +436,13 @@ export declare enum ParticipantEvent {
430
436
  */
431
437
  ParticipantPermissionsChanged = "participantPermissionsChanged",
432
438
  /** @internal */
433
- PCTrackAdded = "pcTrackAdded"
439
+ PCTrackAdded = "pcTrackAdded",
440
+ /**
441
+ * Participant attributes is an app-specific key value state to be pushed to
442
+ * all users.
443
+ * When a participant's attributes changed, this event will be emitted with the changed attributes
444
+ */
445
+ AttributesChanged = "attributesChanged"
434
446
  }
435
447
  /** @internal */
436
448
  export declare enum EngineEvent {
@@ -459,7 +471,8 @@ export declare enum EngineEvent {
459
471
  RemoteMute = "remoteMute",
460
472
  SubscribedQualityUpdate = "subscribedQualityUpdate",
461
473
  LocalTrackUnpublished = "localTrackUnpublished",
462
- Offline = "offline"
474
+ Offline = "offline",
475
+ SignalRequestError = "signalRequestError"
463
476
  }
464
477
  export declare enum TrackEvent {
465
478
  Message = "message",
@@ -26,6 +26,7 @@ export default class LocalParticipant extends Participant {
26
26
  private roomOptions;
27
27
  private encryptionType;
28
28
  private reconnectFuture?;
29
+ private pendingSignalRequests;
29
30
  /** @internal */
30
31
  constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions);
31
32
  get lastCameraError(): Error | undefined;
@@ -40,22 +41,29 @@ export default class LocalParticipant extends Participant {
40
41
  private handleReconnecting;
41
42
  private handleReconnected;
42
43
  private handleDisconnected;
44
+ private handleSignalRequestError;
43
45
  /**
44
46
  * Sets and updates the metadata of the local participant.
45
- * The change does not take immediate effect.
46
- * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
47
47
  * Note: this requires `canUpdateOwnMetadata` permission.
48
+ * method will throw if the user doesn't have the required permissions
48
49
  * @param metadata
49
50
  */
50
- setMetadata(metadata: string): void;
51
+ setMetadata(metadata: string): Promise<void>;
51
52
  /**
52
53
  * Sets and updates the name of the local participant.
53
- * The change does not take immediate effect.
54
- * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
55
54
  * Note: this requires `canUpdateOwnMetadata` permission.
55
+ * method will throw if the user doesn't have the required permissions
56
56
  * @param metadata
57
57
  */
58
- setName(name: string): void;
58
+ setName(name: string): Promise<void>;
59
+ /**
60
+ * Set or update participant attributes. It will make updates only to keys that
61
+ * are present in `attributes`, and will not override others.
62
+ * Note: this requires `canUpdateOwnMetadata` permission.
63
+ * @param attributes attributes to update
64
+ */
65
+ setAttributes(attributes: Record<string, string>): Promise<void>;
66
+ private requestMetadataUpdate;
59
67
  /**
60
68
  * Enable or disable a participant's camera track.
61
69
  *
@@ -39,6 +39,7 @@ export default class Participant extends Participant_base {
39
39
  name?: string;
40
40
  /** client metadata, opaque to livekit */
41
41
  metadata?: string;
42
+ private _attributes;
42
43
  lastSpokeAt?: Date | undefined;
43
44
  permissions?: ParticipantPermission;
44
45
  protected _kind: ParticipantKind;
@@ -52,6 +53,8 @@ export default class Participant extends Participant_base {
52
53
  get isEncrypted(): boolean;
53
54
  get isAgent(): boolean;
54
55
  get kind(): ParticipantKind;
56
+ /** participant attributes, similar to metadata, but as a key/value map */
57
+ get attributes(): Readonly<Record<string, string>>;
55
58
  /** @internal */
56
59
  constructor(sid: string, identity: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions, kind?: ParticipantKind);
57
60
  getTrackPublications(): TrackPublication[];
@@ -78,6 +81,10 @@ export default class Participant extends Participant_base {
78
81
  **/
79
82
  private _setMetadata;
80
83
  private _setName;
84
+ /**
85
+ * Updates metadata from server
86
+ **/
87
+ private _setAttributes;
81
88
  /** @internal */
82
89
  setPermissions(permissions: ParticipantPermission): boolean;
83
90
  /** @internal */
@@ -113,5 +120,6 @@ export type ParticipantEventCallbacks = {
113
120
  audioStreamAcquired: () => void;
114
121
  participantPermissionsChanged: (prevPermissions?: ParticipantPermission) => void;
115
122
  trackSubscriptionStatusChanged: (publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus) => void;
123
+ attributesChanged: (changedAttributes: Record<string, string>) => void;
116
124
  };
117
125
  //# sourceMappingURL=Participant.d.ts.map
@@ -4,9 +4,9 @@
4
4
  * that the timer fires on time.
5
5
  */
6
6
  export default class CriticalTimers {
7
- static setTimeout: (handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]) => number;
8
- static setInterval: (handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]) => number;
9
- static clearTimeout: (id: number | undefined) => void;
10
- static clearInterval: (id: number | undefined) => void;
7
+ static setTimeout: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
8
+ static setInterval: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
9
+ static clearTimeout: (timeoutId: string | number | NodeJS.Timeout | undefined) => void;
10
+ static clearInterval: (intervalId: string | number | NodeJS.Timeout | undefined) => void;
11
11
  }
12
12
  //# sourceMappingURL=timers.d.ts.map
@@ -30,4 +30,5 @@ export declare function mimeTypeToVideoCodecString(mimeType: string): "vp8" | "h
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;
33
+ export declare function diffAttributes(oldValues: Record<string, string> | undefined, newValues: Record<string, string> | undefined): Record<string, string>;
33
34
  //# sourceMappingURL=utils.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
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",
@@ -36,20 +36,20 @@
36
36
  "author": "David Zhao <david@davidzhao.com>",
37
37
  "license": "Apache-2.0",
38
38
  "dependencies": {
39
- "@livekit/protocol": "1.16.0",
39
+ "@livekit/protocol": "1.19.1",
40
40
  "events": "^3.3.0",
41
41
  "loglevel": "^1.8.0",
42
42
  "sdp-transform": "^2.14.1",
43
43
  "ts-debounce": "^4.0.0",
44
- "tslib": "2.6.2",
44
+ "tslib": "2.6.3",
45
45
  "typed-emitter": "^2.1.0",
46
- "webrtc-adapter": "^8.1.1"
46
+ "webrtc-adapter": "^9.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@babel/core": "7.24.6",
50
- "@babel/preset-env": "7.24.6",
49
+ "@babel/core": "7.24.7",
50
+ "@babel/preset-env": "7.24.7",
51
51
  "@bufbuild/protoc-gen-es": "^1.3.0",
52
- "@changesets/cli": "2.27.5",
52
+ "@changesets/cli": "2.27.7",
53
53
  "@livekit/changesets-changelog-github": "^0.0.4",
54
54
  "@rollup/plugin-babel": "6.0.4",
55
55
  "@rollup/plugin-commonjs": "25.0.8",
@@ -62,8 +62,8 @@
62
62
  "@types/events": "^3.0.0",
63
63
  "@types/sdp-transform": "2.4.9",
64
64
  "@types/ua-parser-js": "0.7.39",
65
- "@typescript-eslint/eslint-plugin": "7.12.0",
66
- "@typescript-eslint/parser": "7.12.0",
65
+ "@typescript-eslint/eslint-plugin": "7.15.0",
66
+ "@typescript-eslint/parser": "7.15.0",
67
67
  "downlevel-dts": "^0.11.0",
68
68
  "eslint": "8.57.0",
69
69
  "eslint-config-airbnb-typescript": "18.0.0",
@@ -78,10 +78,10 @@
78
78
  "rollup-plugin-re": "1.0.7",
79
79
  "rollup-plugin-typescript2": "0.36.0",
80
80
  "size-limit": "^8.2.4",
81
- "typedoc": "0.25.13",
81
+ "typedoc": "0.26.3",
82
82
  "typedoc-plugin-no-inherit": "1.4.0",
83
- "typescript": "5.4.5",
84
- "vite": "5.2.12",
83
+ "typescript": "5.5.3",
84
+ "vite": "5.3.2",
85
85
  "vitest": "^1.0.0"
86
86
  },
87
87
  "scripts": {
@@ -4,6 +4,7 @@ import {
4
4
  ClientInfo,
5
5
  ConnectionQualityUpdate,
6
6
  DisconnectReason,
7
+ ErrorResponse,
7
8
  JoinResponse,
8
9
  LeaveRequest,
9
10
  LeaveRequest_Action,
@@ -141,6 +142,8 @@ export class SignalClient {
141
142
 
142
143
  onLeave?: (leave: LeaveRequest) => void;
143
144
 
145
+ onErrorResponse?: (error: ErrorResponse) => void;
146
+
144
147
  connectOptions?: ConnectOpts;
145
148
 
146
149
  ws?: WebSocket;
@@ -163,6 +166,11 @@ export class SignalClient {
163
166
  );
164
167
  }
165
168
 
169
+ private getNextRequestId() {
170
+ this._requestId += 1;
171
+ return this._requestId;
172
+ }
173
+
166
174
  private options?: SignalOptions;
167
175
 
168
176
  private pingTimeout: ReturnType<typeof setTimeout> | undefined;
@@ -183,6 +191,8 @@ export class SignalClient {
183
191
 
184
192
  private loggerContextCb?: LoggerOptions['loggerContextCb'];
185
193
 
194
+ private _requestId = 0;
195
+
186
196
  constructor(useJSON: boolean = false, loggerOptions: LoggerOptions = {}) {
187
197
  this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.Signal);
188
198
  this.loggerContextCb = loggerOptions.loggerContextCb;
@@ -511,14 +521,22 @@ export class SignalClient {
511
521
  });
512
522
  }
513
523
 
514
- sendUpdateLocalMetadata(metadata: string, name: string) {
515
- return this.sendRequest({
524
+ async sendUpdateLocalMetadata(
525
+ metadata: string,
526
+ name: string,
527
+ attributes: Record<string, string> = {},
528
+ ) {
529
+ const requestId = this.getNextRequestId();
530
+ await this.sendRequest({
516
531
  case: 'updateMetadata',
517
532
  value: new UpdateParticipantMetadata({
533
+ requestId,
518
534
  metadata,
519
535
  name,
536
+ attributes,
520
537
  }),
521
538
  });
539
+ return requestId;
522
540
  }
523
541
 
524
542
  sendUpdateTrackSettings(settings: UpdateTrackSettings) {
@@ -721,6 +739,10 @@ export class SignalClient {
721
739
  this.rtt = Date.now() - Number.parseInt(msg.value.lastPingTimestamp.toString());
722
740
  this.resetPingTimeout();
723
741
  pingHandled = true;
742
+ } else if (msg.case === 'errorResponse') {
743
+ if (this.onErrorResponse) {
744
+ this.onErrorResponse(msg.value);
745
+ }
724
746
  } else {
725
747
  this.log.debug('unsupported message', { ...this.logContext, msgCase: msg.case });
726
748
  }
@@ -9,8 +9,15 @@ export enum CryptorErrorReason {
9
9
  export class CryptorError extends LivekitError {
10
10
  reason: CryptorErrorReason;
11
11
 
12
- constructor(message?: string, reason: CryptorErrorReason = CryptorErrorReason.InternalError) {
12
+ participantIdentity?: string;
13
+
14
+ constructor(
15
+ message?: string,
16
+ reason: CryptorErrorReason = CryptorErrorReason.InternalError,
17
+ participantIdentity?: string,
18
+ ) {
13
19
  super(40, message);
14
20
  this.reason = reason;
21
+ this.participantIdentity = participantIdentity;
15
22
  }
16
23
  }
package/src/e2ee/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './KeyProvider';
2
2
  export * from './utils';
3
3
  export * from './types';
4
4
  export * from './events';
5
+ export * from './errors';
@@ -183,7 +183,12 @@ export class FrameCryptor extends BaseFrameCryptor {
183
183
  .pipeTo(writable)
184
184
  .catch((e) => {
185
185
  workerLogger.warn(e);
186
- this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message));
186
+ this.emit(
187
+ CryptorEvent.Error,
188
+ e instanceof CryptorError
189
+ ? e
190
+ : new CryptorError(e.message, undefined, this.participantIdentity),
191
+ );
187
192
  });
188
193
  this.trackId = trackId;
189
194
  }
@@ -294,10 +299,14 @@ export class FrameCryptor extends BaseFrameCryptor {
294
299
  workerLogger.error(e);
295
300
  }
296
301
  } else {
297
- workerLogger.debug('failed to decrypt, emitting error', this.logContext);
302
+ workerLogger.debug('failed to encrypt, emitting error', this.logContext);
298
303
  this.emit(
299
304
  CryptorEvent.Error,
300
- new CryptorError(`encryption key missing for encoding`, CryptorErrorReason.MissingKey),
305
+ new CryptorError(
306
+ `encryption key missing for encoding`,
307
+ CryptorErrorReason.MissingKey,
308
+ this.participantIdentity,
309
+ ),
301
310
  );
302
311
  }
303
312
  }
@@ -367,6 +376,7 @@ export class FrameCryptor extends BaseFrameCryptor {
367
376
  new CryptorError(
368
377
  `missing key at index ${keyIndex} for participant ${this.participantIdentity}`,
369
378
  CryptorErrorReason.MissingKey,
379
+ this.participantIdentity,
370
380
  ),
371
381
  );
372
382
  }
@@ -487,12 +497,14 @@ export class FrameCryptor extends BaseFrameCryptor {
487
497
  throw new CryptorError(
488
498
  `valid key missing for participant ${this.participantIdentity}`,
489
499
  CryptorErrorReason.InvalidKey,
500
+ this.participantIdentity,
490
501
  );
491
502
  }
492
503
  } else {
493
504
  throw new CryptorError(
494
505
  `Decryption failed: ${error.message}`,
495
506
  CryptorErrorReason.InvalidKey,
507
+ this.participantIdentity,
496
508
  );
497
509
  }
498
510
  }
@@ -554,12 +566,14 @@ export class FrameCryptor extends BaseFrameCryptor {
554
566
  this.detectedCodec = detectedCodec;
555
567
  }
556
568
 
557
- if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
569
+ if (detectedCodec === 'av1') {
558
570
  throw new Error(`${detectedCodec} is not yet supported for end to end encryption`);
559
571
  }
560
572
 
561
573
  if (detectedCodec === 'vp8') {
562
574
  frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
575
+ } else if (detectedCodec === 'vp9') {
576
+ frameInfo.unencryptedBytes = 0;
563
577
  return frameInfo;
564
578
  }
565
579
 
@@ -1,4 +1,5 @@
1
1
  import { workerLogger } from '../../logger';
2
+ import { VideoCodec } from '../../room/track/options';
2
3
  import { KEY_PROVIDER_DEFAULTS } from '../constants';
3
4
  import { CryptorErrorReason } from '../errors';
4
5
  import { CryptorEvent, KeyHandlerEvent } from '../events';
@@ -25,6 +26,8 @@ let sifTrailer: Uint8Array | undefined;
25
26
 
26
27
  let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
27
28
 
29
+ let rtpMap: Map<number, VideoCodec> = new Map();
30
+
28
31
  workerLogger.setDefaultLevel('info');
29
32
 
30
33
  onmessage = (ev) => {
@@ -91,6 +94,7 @@ onmessage = (ev) => {
91
94
  break;
92
95
  case 'setRTPMap':
93
96
  // this is only used for the local participant
97
+ rtpMap = data.map;
94
98
  participantCryptors.forEach((cr) => {
95
99
  if (cr.getParticipantIdentity() === data.participantIdentity) {
96
100
  cr.setRtpMap(data.map);
@@ -149,7 +153,7 @@ function getTrackCryptor(participantIdentity: string, trackId: string) {
149
153
  keyProviderOptions,
150
154
  sifTrailer,
151
155
  });
152
-
156
+ cryptor.setRtpMap(rtpMap);
153
157
  setupCryptorErrorEvents(cryptor);
154
158
  participantCryptors.push(cryptor);
155
159
  } else if (participantIdentity !== cryptor.getParticipantIdentity()) {
package/src/logger.ts CHANGED
@@ -54,9 +54,10 @@ export function getLogger(name: string) {
54
54
  export function setLogLevel(level: LogLevel | LogLevelString, loggerName?: LoggerNames) {
55
55
  if (loggerName) {
56
56
  log.getLogger(loggerName).setLevel(level);
57
- }
58
- for (const logger of livekitLoggers) {
59
- logger.setLevel(level);
57
+ } else {
58
+ for (const logger of livekitLoggers) {
59
+ logger.setLevel(level);
60
+ }
60
61
  }
61
62
  }
62
63
 
@@ -41,7 +41,7 @@ export default class DeviceManager {
41
41
  !(isSafari() && this.hasDeviceInUse(kind))
42
42
  ) {
43
43
  const isDummyDeviceOrEmpty =
44
- devices.length === 0 ||
44
+ devices.filter((d) => d.kind === kind).length === 0 ||
45
45
  devices.some((device) => {
46
46
  const noLabel = device.label === '';
47
47
  const isRelevant = kind ? device.kind === kind : true;
@@ -7,6 +7,7 @@ import {
7
7
  DataPacket,
8
8
  DataPacket_Kind,
9
9
  DisconnectReason,
10
+ ErrorResponse,
10
11
  type JoinResponse,
11
12
  type LeaveRequest,
12
13
  LeaveRequest_Action,
@@ -193,6 +194,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
193
194
  this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
194
195
  this.client.onSpeakersChanged = (update) => this.emit(EngineEvent.SpeakersChanged, update);
195
196
  this.client.onStreamStateUpdate = (update) => this.emit(EngineEvent.StreamStateChanged, update);
197
+ this.client.onErrorResponse = (error) => this.emit(EngineEvent.SignalRequestError, error);
196
198
  }
197
199
 
198
200
  /** @internal */
@@ -1412,6 +1414,7 @@ export type EngineEventCallbacks = {
1412
1414
  localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
1413
1415
  remoteMute: (trackSid: string, muted: boolean) => void;
1414
1416
  offline: () => void;
1417
+ signalRequestError: (error: ErrorResponse) => void;
1415
1418
  };
1416
1419
 
1417
1420
  function supportOptionalDatachannel(protocol: number | undefined): boolean {
package/src/room/Room.ts CHANGED
@@ -1407,6 +1407,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1407
1407
  private handleSpeakersChanged = (speakerUpdates: SpeakerInfo[]) => {
1408
1408
  const lastSpeakers = new Map<string, Participant>();
1409
1409
  this.activeSpeakers.forEach((p) => {
1410
+ const remoteParticipant = this.remoteParticipants.get(p.identity);
1411
+ if (remoteParticipant && remoteParticipant.sid !== p.sid) {
1412
+ return;
1413
+ }
1410
1414
  lastSpeakers.set(p.sid, p);
1411
1415
  });
1412
1416
  speakerUpdates.forEach((speaker) => {
@@ -1514,14 +1518,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1514
1518
  bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
1515
1519
 
1516
1520
  private handleTranscription = (
1517
- remoteParticipant: RemoteParticipant | undefined,
1521
+ _remoteParticipant: RemoteParticipant | undefined,
1518
1522
  transcription: TranscriptionModel,
1519
1523
  ) => {
1520
1524
  // find the participant
1521
1525
  const participant =
1522
- transcription.participantIdentity === this.localParticipant.identity
1526
+ transcription.transcribedParticipantIdentity === this.localParticipant.identity
1523
1527
  ? this.localParticipant
1524
- : remoteParticipant;
1528
+ : this.getParticipantByIdentity(transcription.transcribedParticipantIdentity);
1525
1529
  const publication = participant?.trackPublications.get(transcription.trackId);
1526
1530
 
1527
1531
  const segments = extractTranscriptionSegments(transcription);
@@ -2127,6 +2131,10 @@ export type RoomEventCallbacks = {
2127
2131
  prevPermissions: ParticipantPermission | undefined,
2128
2132
  participant: RemoteParticipant | LocalParticipant,
2129
2133
  ) => void;
2134
+ participantAttributesChanged: (
2135
+ changedAttributes: Record<string, string>,
2136
+ participant: RemoteParticipant | LocalParticipant,
2137
+ ) => void;
2130
2138
  activeSpeakersChanged: (speakers: Array<Participant>) => void;
2131
2139
  roomMetadataChanged: (metadata: string) => void;
2132
2140
  dataReceived: (
@@ -1,3 +1,5 @@
1
+ import { ErrorResponse_Reason } from '@livekit/protocol';
2
+
1
3
  export class LivekitError extends Error {
2
4
  code: number;
3
5
 
@@ -63,6 +65,15 @@ export class PublishDataError extends LivekitError {
63
65
  }
64
66
  }
65
67
 
68
+ export class SignalRequestError extends LivekitError {
69
+ reason: ErrorResponse_Reason;
70
+
71
+ constructor(message: string, reason: ErrorResponse_Reason = ErrorResponse_Reason.UNKNOWN) {
72
+ super(15, message);
73
+ this.reason = reason;
74
+ }
75
+ }
76
+
66
77
  export enum MediaDeviceFailure {
67
78
  // user rejected permissions
68
79
  PermissionDenied = 'PermissionDenied',
@@ -185,6 +185,13 @@ export enum RoomEvent {
185
185
  */
186
186
  ParticipantNameChanged = 'participantNameChanged',
187
187
 
188
+ /**
189
+ * Participant attributes is an app-specific key value state to be pushed to
190
+ * all users.
191
+ * When a participant's attributes changed, this event will be emitted with the changed attributes and the participant
192
+ */
193
+ ParticipantAttributesChanged = 'participantAttributesChanged',
194
+
188
195
  /**
189
196
  * Room metadata is a simple way for app-specific state to be pushed to
190
197
  * all users.
@@ -495,6 +502,13 @@ export enum ParticipantEvent {
495
502
 
496
503
  /** @internal */
497
504
  PCTrackAdded = 'pcTrackAdded',
505
+
506
+ /**
507
+ * Participant attributes is an app-specific key value state to be pushed to
508
+ * all users.
509
+ * When a participant's attributes changed, this event will be emitted with the changed attributes
510
+ */
511
+ AttributesChanged = 'attributesChanged',
498
512
  }
499
513
 
500
514
  /** @internal */
@@ -525,6 +539,7 @@ export enum EngineEvent {
525
539
  SubscribedQualityUpdate = 'subscribedQualityUpdate',
526
540
  LocalTrackUnpublished = 'localTrackUnpublished',
527
541
  Offline = 'offline',
542
+ SignalRequestError = 'signalRequestError',
528
543
  }
529
544
 
530
545
  export enum TrackEvent {