livekit-client 2.3.1 → 2.4.0

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