livekit-client 2.18.10 → 2.19.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 (63) hide show
  1. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  2. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  3. package/dist/livekit-client.esm.mjs +749 -438
  4. package/dist/livekit-client.esm.mjs.map +1 -1
  5. package/dist/livekit-client.pt.worker.js.map +1 -1
  6. package/dist/livekit-client.pt.worker.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/room/RTCEngine.d.ts +0 -3
  10. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  11. package/dist/src/room/Room.d.ts +4 -2
  12. package/dist/src/room/Room.d.ts.map +1 -1
  13. package/dist/src/room/participant/LocalParticipant.d.ts +5 -13
  14. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  15. package/dist/src/room/participant/RemoteParticipant.d.ts +5 -1
  16. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  17. package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
  18. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
  19. package/dist/src/room/rpc/client/events.d.ts +8 -0
  20. package/dist/src/room/rpc/client/events.d.ts.map +1 -0
  21. package/dist/src/room/rpc/index.d.ts +6 -0
  22. package/dist/src/room/rpc/index.d.ts.map +1 -0
  23. package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
  24. package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
  25. package/dist/src/room/rpc/server/events.d.ts +8 -0
  26. package/dist/src/room/rpc/server/events.d.ts.map +1 -0
  27. package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  28. package/dist/src/room/rpc/utils.d.ts.map +1 -0
  29. package/dist/src/room/utils.d.ts +1 -0
  30. package/dist/src/room/utils.d.ts.map +1 -1
  31. package/dist/src/version.d.ts +8 -0
  32. package/dist/src/version.d.ts.map +1 -1
  33. package/dist/ts4.2/room/RTCEngine.d.ts +0 -3
  34. package/dist/ts4.2/room/Room.d.ts +4 -2
  35. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +5 -13
  36. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
  37. package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
  38. package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
  39. package/dist/ts4.2/room/rpc/index.d.ts +7 -0
  40. package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
  41. package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
  42. package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  43. package/dist/ts4.2/room/utils.d.ts +1 -0
  44. package/dist/ts4.2/version.d.ts +8 -0
  45. package/package.json +4 -2
  46. package/src/api/SignalClient.ts +1 -0
  47. package/src/room/RTCEngine.ts +4 -30
  48. package/src/room/Room.test.ts +99 -1
  49. package/src/room/Room.ts +107 -88
  50. package/src/room/participant/LocalParticipant.ts +16 -180
  51. package/src/room/participant/RemoteParticipant.ts +9 -0
  52. package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
  53. package/src/room/rpc/client/RpcClientManager.ts +269 -0
  54. package/src/room/rpc/client/events.ts +9 -0
  55. package/src/room/rpc/index.ts +14 -0
  56. package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
  57. package/src/room/rpc/server/RpcServerManager.ts +293 -0
  58. package/src/room/rpc/server/events.ts +9 -0
  59. package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
  60. package/src/room/utils.ts +7 -1
  61. package/src/version.ts +10 -0
  62. package/dist/src/room/rpc.d.ts.map +0 -1
  63. package/src/room/rpc.test.ts +0 -301
package/src/room/Room.ts CHANGED
@@ -48,6 +48,7 @@ import { PacketTrailerManager } from '../packetTrailer/PacketTrailerManager';
48
48
  import { isPacketTrailerSupported } from '../packetTrailer/utils';
49
49
  import TypedPromise from '../utils/TypedPromise';
50
50
  import { getBrowser } from '../utils/browserParser';
51
+ import { CLIENT_PROTOCOL_DEFAULT } from '../version';
51
52
  import { BackOffStrategy } from './BackOffStrategy';
52
53
  import DeviceManager from './DeviceManager';
53
54
  import RTCEngine, { DataChannelKind, type RegionStrategy } from './RTCEngine';
@@ -81,7 +82,14 @@ import LocalParticipant from './participant/LocalParticipant';
81
82
  import Participant from './participant/Participant';
82
83
  import { type ConnectionQuality, ParticipantKind } from './participant/Participant';
83
84
  import RemoteParticipant from './participant/RemoteParticipant';
84
- import { MAX_PAYLOAD_BYTES, RpcError, type RpcInvocationData, byteLength } from './rpc';
85
+ import {
86
+ RPC_REQUEST_DATA_STREAM_TOPIC,
87
+ RPC_RESPONSE_DATA_STREAM_TOPIC,
88
+ RpcClientManager,
89
+ RpcError,
90
+ type RpcInvocationData,
91
+ RpcServerManager,
92
+ } from './rpc';
85
93
  import CriticalTimers from './timers';
86
94
  import LocalAudioTrack from './track/LocalAudioTrack';
87
95
  import type LocalTrack from './track/LocalTrack';
@@ -221,7 +229,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
221
229
 
222
230
  private outgoingDataTrackManager: OutgoingDataTrackManager;
223
231
 
224
- private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
232
+ private rpcClientManager: RpcClientManager;
233
+
234
+ private rpcServerManager: RpcServerManager;
225
235
 
226
236
  get hasE2EESetup(): boolean {
227
237
  return this.e2eeManager !== undefined;
@@ -301,15 +311,36 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
301
311
  .finally(() => this.outgoingDataTrackManager.handlePacketSendComplete(handle));
302
312
  });
303
313
 
314
+ this.registerRpcDataStreamHandler();
315
+
316
+ this.rpcClientManager = new RpcClientManager(
317
+ this.log,
318
+ this.outgoingDataStreamManager,
319
+ this.getRemoteParticipantClientProtocol,
320
+ () => this.engine.latestJoinResponse?.serverInfo?.version,
321
+ );
322
+ this.rpcClientManager.on('sendDataPacket', ({ packet }) => {
323
+ this.engine?.sendDataPacket(packet, DataChannelKind.RELIABLE);
324
+ });
325
+ this.rpcServerManager = new RpcServerManager(
326
+ this.log,
327
+ this.outgoingDataStreamManager,
328
+ this.getRemoteParticipantClientProtocol,
329
+ );
330
+ this.rpcServerManager.on('sendDataPacket', ({ packet }) => {
331
+ this.engine?.sendDataPacket(packet, DataChannelKind.RELIABLE);
332
+ });
333
+
304
334
  this.disconnectLock = new Mutex();
305
335
  this.localParticipant = new LocalParticipant(
306
336
  '',
307
337
  '',
308
338
  this.engine,
309
339
  this.options,
310
- this.rpcHandlers,
311
340
  this.outgoingDataStreamManager,
312
341
  this.outgoingDataTrackManager,
342
+ this.rpcClientManager,
343
+ this.rpcServerManager,
313
344
  );
314
345
 
315
346
  this.setupPacketTrailer();
@@ -342,18 +373,34 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
342
373
  }
343
374
 
344
375
  if (isWeb()) {
345
- const abortController = new AbortController();
346
-
347
- // in order to catch device changes prior to room connection we need to register the event in the constructor
348
- navigator.mediaDevices?.addEventListener?.('devicechange', this.handleDeviceChange, {
349
- signal: abortController.signal,
350
- });
376
+ const cleanupController = new AbortController();
377
+ let onDeviceChange: () => void;
351
378
 
352
379
  if (Room.cleanupRegistry) {
380
+ // Wrap the listener in a WeakRef closure so navigator.mediaDevices does not
381
+ // strongly retain the Room. When the user drops their Room ref, the
382
+ // FinalizationRegistry callback aborts the controller and removes the listener.
383
+ const roomRef = new WeakRef(this);
384
+ onDeviceChange = () => {
385
+ const self = roomRef.deref();
386
+ if (!self) {
387
+ return;
388
+ }
389
+ self.handleDeviceChange();
390
+ };
353
391
  Room.cleanupRegistry.register(this, () => {
354
- abortController.abort();
392
+ cleanupController.abort();
355
393
  });
394
+ } else {
395
+ // Legacy browsers without WeakRef/FinalizationRegistry: fall back to a
396
+ // direct listener (matches pre-#1944 behavior).
397
+ onDeviceChange = this.handleDeviceChange;
356
398
  }
399
+
400
+ // in order to catch device changes prior to room connection we need to register the event in the constructor
401
+ navigator.mediaDevices?.addEventListener?.('devicechange', onDeviceChange, {
402
+ signal: cleanupController.signal,
403
+ });
357
404
  }
358
405
  }
359
406
 
@@ -400,12 +447,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
400
447
  * Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error").
401
448
  */
402
449
  registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
403
- if (this.rpcHandlers.has(method)) {
404
- throw Error(
405
- `RPC handler already registered for method ${method}, unregisterRpcMethod before trying to register again`,
406
- );
407
- }
408
- this.rpcHandlers.set(method, handler);
450
+ this.rpcServerManager.registerRpcMethod(method, handler);
409
451
  }
410
452
 
411
453
  /**
@@ -414,7 +456,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
414
456
  * @param method - The name of the RPC method to unregister
415
457
  */
416
458
  unregisterRpcMethod(method: string) {
417
- this.rpcHandlers.delete(method);
459
+ this.rpcServerManager.unregisterRpcMethod(method);
418
460
  }
419
461
 
420
462
  /**
@@ -729,6 +771,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
729
771
 
730
772
  static cleanupRegistry =
731
773
  typeof FinalizationRegistry !== 'undefined' &&
774
+ typeof WeakRef !== 'undefined' &&
732
775
  new FinalizationRegistry((cleanup: () => void) => {
733
776
  cleanup();
734
777
  });
@@ -1873,7 +1916,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1873
1916
  });
1874
1917
  this.emit(RoomEvent.ParticipantDisconnected, participant);
1875
1918
  participant.setDisconnected();
1876
- this.localParticipant?.handleParticipantDisconnected(participant.identity);
1919
+ this.rpcClientManager.handleParticipantDisconnected(participant.identity);
1877
1920
  }
1878
1921
 
1879
1922
  // updates are sent only when there's a change to speaker ordering
@@ -2017,14 +2060,31 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2017
2060
  this.handleDataStream(packet, encryptionType);
2018
2061
  } else if (packet.value.case === 'rpcRequest') {
2019
2062
  const rpc = packet.value.value;
2020
- this.handleIncomingRpcRequest(
2021
- packet.participantIdentity,
2022
- rpc.id,
2023
- rpc.method,
2024
- rpc.payload,
2025
- rpc.responseTimeoutMs,
2026
- rpc.version,
2027
- );
2063
+ this.rpcServerManager.handleIncomingRpcRequest(packet.participantIdentity, rpc);
2064
+ } else if (packet.value.case === 'rpcResponse') {
2065
+ const rpcResponse = packet.value.value;
2066
+ switch (rpcResponse.value.case) {
2067
+ case 'payload':
2068
+ this.rpcClientManager.handleIncomingRpcResponseSuccess(
2069
+ rpcResponse.requestId,
2070
+ rpcResponse.value.value,
2071
+ );
2072
+ break;
2073
+ case 'error':
2074
+ this.rpcClientManager.handleIncomingRpcResponseFailure(
2075
+ rpcResponse.requestId,
2076
+ RpcError.fromProto(rpcResponse.value.value),
2077
+ );
2078
+ break;
2079
+ default:
2080
+ this.log.warn(
2081
+ `Unknown rpcResponse.value.case: ${rpcResponse.value.case}`,
2082
+ this.logContext,
2083
+ );
2084
+ break;
2085
+ }
2086
+ } else if (packet.value.case === 'rpcAck') {
2087
+ this.rpcClientManager.handleIncomingRpcAck(packet.value.value.requestId);
2028
2088
  }
2029
2089
  };
2030
2090
 
@@ -2093,68 +2153,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2093
2153
  this.incomingDataStreamManager.handleDataStreamPacket(packet, encryptionType);
2094
2154
  };
2095
2155
 
2096
- private async handleIncomingRpcRequest(
2097
- callerIdentity: string,
2098
- requestId: string,
2099
- method: string,
2100
- payload: string,
2101
- responseTimeout: number,
2102
- version: number,
2103
- ) {
2104
- await this.engine.publishRpcAck(callerIdentity, requestId);
2105
-
2106
- if (version !== 1) {
2107
- await this.engine.publishRpcResponse(
2108
- callerIdentity,
2109
- requestId,
2110
- null,
2111
- RpcError.builtIn('UNSUPPORTED_VERSION'),
2112
- );
2113
- return;
2114
- }
2115
-
2116
- const handler = this.rpcHandlers.get(method);
2117
-
2118
- if (!handler) {
2119
- await this.engine.publishRpcResponse(
2120
- callerIdentity,
2121
- requestId,
2122
- null,
2123
- RpcError.builtIn('UNSUPPORTED_METHOD'),
2124
- );
2125
- return;
2126
- }
2127
-
2128
- let responseError: RpcError | null = null;
2129
- let responsePayload: string | null = null;
2130
-
2131
- try {
2132
- const response = await handler({
2133
- requestId,
2134
- callerIdentity,
2135
- payload,
2136
- responseTimeout,
2137
- });
2138
- if (byteLength(response) > MAX_PAYLOAD_BYTES) {
2139
- responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
2140
- this.log.warn(`RPC Response payload too large for ${method}`);
2141
- } else {
2142
- responsePayload = response;
2143
- }
2144
- } catch (error) {
2145
- if (error instanceof RpcError) {
2146
- responseError = error;
2147
- } else {
2148
- this.log.warn(
2149
- `Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
2150
- error,
2151
- );
2152
- responseError = RpcError.builtIn('APPLICATION_ERROR');
2153
- }
2154
- }
2155
- await this.engine.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
2156
- }
2157
-
2158
2156
  bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
2159
2157
 
2160
2158
  private handleAudioPlaybackStarted = () => {
@@ -2500,6 +2498,27 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2500
2498
  }
2501
2499
  }
2502
2500
 
2501
+ private getRemoteParticipantClientProtocol = (identity: Participant['identity']) => {
2502
+ return this.remoteParticipants.get(identity)?.clientProtocol ?? CLIENT_PROTOCOL_DEFAULT;
2503
+ };
2504
+
2505
+ private registerRpcDataStreamHandler() {
2506
+ this.incomingDataStreamManager.registerTextStreamHandler(
2507
+ RPC_REQUEST_DATA_STREAM_TOPIC,
2508
+ async (reader, { identity }) => {
2509
+ const attributes = reader.info.attributes ?? {};
2510
+ await this.rpcServerManager.handleIncomingDataStream(reader, identity, attributes);
2511
+ },
2512
+ );
2513
+ this.incomingDataStreamManager.registerTextStreamHandler(
2514
+ RPC_RESPONSE_DATA_STREAM_TOPIC,
2515
+ async (reader, { identity }) => {
2516
+ const attributes = reader.info.attributes ?? {};
2517
+ await this.rpcClientManager.handleIncomingDataStream(reader, identity, attributes);
2518
+ },
2519
+ );
2520
+ }
2521
+
2503
2522
  private registerConnectionReconcile() {
2504
2523
  this.clearConnectionReconcile();
2505
2524
  let consecutiveFailures = 0;
@@ -13,9 +13,6 @@ import {
13
13
  ParticipantInfo,
14
14
  RequestResponse,
15
15
  RequestResponse_Reason,
16
- RpcAck,
17
- RpcRequest,
18
- RpcResponse,
19
16
  SimulcastCodec,
20
17
  SipDTMF,
21
18
  SubscribedQualityUpdate,
@@ -54,11 +51,11 @@ import {
54
51
  } from '../errors';
55
52
  import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
56
53
  import {
57
- MAX_PAYLOAD_BYTES,
58
54
  type PerformRpcParams,
55
+ RpcClientManager,
59
56
  RpcError,
60
57
  type RpcInvocationData,
61
- byteLength,
58
+ RpcServerManager,
62
59
  } from '../rpc';
63
60
  import LocalAudioTrack from '../track/LocalAudioTrack';
64
61
  import LocalTrack from '../track/LocalTrack';
@@ -94,7 +91,6 @@ import {
94
91
  } from '../types';
95
92
  import {
96
93
  Future,
97
- compareVersions,
98
94
  isAudioTrack,
99
95
  isE2EESimulcastSupported,
100
96
  isFireFox,
@@ -162,12 +158,14 @@ export default class LocalParticipant extends Participant {
162
158
 
163
159
  private firstActiveAgent?: RemoteParticipant;
164
160
 
165
- private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>;
166
-
167
161
  private roomOutgoingDataStreamManager: OutgoingDataStreamManager;
168
162
 
169
163
  private roomOutgoingDataTrackManager: OutgoingDataTrackManager;
170
164
 
165
+ private rpcClientManager: RpcClientManager;
166
+
167
+ private rpcServerManager: RpcServerManager;
168
+
171
169
  private pendingSignalRequests: Map<
172
170
  number,
173
171
  {
@@ -179,25 +177,16 @@ export default class LocalParticipant extends Participant {
179
177
 
180
178
  private enabledPublishVideoCodecs: Codec[] = [];
181
179
 
182
- private pendingAcks = new Map<string, { resolve: () => void; participantIdentity: string }>();
183
-
184
- private pendingResponses = new Map<
185
- string,
186
- {
187
- resolve: (payload: string | null, error: RpcError | null) => void;
188
- participantIdentity: string;
189
- }
190
- >();
191
-
192
180
  /** @internal */
193
181
  constructor(
194
182
  sid: string,
195
183
  identity: string,
196
184
  engine: RTCEngine,
197
185
  options: InternalRoomOptions,
198
- roomRpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>,
199
186
  roomOutgoingDataStreamManager: OutgoingDataStreamManager,
200
187
  roomOutgoingDataTrackManager: OutgoingDataTrackManager,
188
+ rpcClientManager: RpcClientManager,
189
+ rpcServerManager: RpcServerManager,
201
190
  ) {
202
191
  super(sid, identity, undefined, undefined, undefined, {
203
192
  loggerName: options.loggerName,
@@ -215,9 +204,10 @@ export default class LocalParticipant extends Participant {
215
204
  ['audiooutput', 'default'],
216
205
  ]);
217
206
  this.pendingSignalRequests = new Map();
218
- this.rpcHandlers = roomRpcHandlers;
219
207
  this.roomOutgoingDataStreamManager = roomOutgoingDataStreamManager;
220
208
  this.roomOutgoingDataTrackManager = roomOutgoingDataTrackManager;
209
+ this.rpcClientManager = rpcClientManager;
210
+ this.rpcServerManager = rpcServerManager;
221
211
  }
222
212
 
223
213
  get lastCameraError(): Error | undefined {
@@ -277,8 +267,7 @@ export default class LocalParticipant extends Participant {
277
267
  .on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
278
268
  .on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
279
269
  .on(EngineEvent.Closing, this.handleClosing)
280
- .on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse)
281
- .on(EngineEvent.DataPacketReceived, this.handleDataPacket);
270
+ .on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse);
282
271
  }
283
272
 
284
273
  private handleReconnecting = () => {
@@ -362,27 +351,6 @@ export default class LocalParticipant extends Participant {
362
351
  }
363
352
  };
364
353
 
365
- private handleDataPacket = (packet: DataPacket) => {
366
- switch (packet.value.case) {
367
- case 'rpcResponse':
368
- let rpcResponse = packet.value.value as RpcResponse;
369
- let payload: string | null = null;
370
- let error: RpcError | null = null;
371
-
372
- if (rpcResponse.value.case === 'payload') {
373
- payload = rpcResponse.value.value;
374
- } else if (rpcResponse.value.case === 'error') {
375
- error = RpcError.fromProto(rpcResponse.value.value);
376
- }
377
- this.handleIncomingRpcResponse(rpcResponse.requestId, payload, error);
378
- break;
379
- case 'rpcAck':
380
- let rpcAck = packet.value.value as RpcAck;
381
- this.handleIncomingRpcAck(rpcAck.requestId);
382
- break;
383
- }
384
- };
385
-
386
354
  /**
387
355
  * Sets and updates the metadata of the local participant.
388
356
  * Note: this requires `canUpdateOwnMetadata` permission.
@@ -1883,69 +1851,9 @@ export default class LocalParticipant extends Participant {
1883
1851
  * @returns A promise that resolves with the response payload or rejects with an error.
1884
1852
  * @throws Error on failure. Details in `message`.
1885
1853
  */
1886
- performRpc({
1887
- destinationIdentity,
1888
- method,
1889
- payload,
1890
- responseTimeout = 15000,
1891
- }: PerformRpcParams): TypedPromise<string, RpcError> {
1892
- const maxRoundTripLatency = 7000;
1893
- const minEffectiveTimeout = maxRoundTripLatency + 1000;
1894
-
1895
- return new TypedPromise<string, RpcError>(async (resolve, reject) => {
1896
- if (byteLength(payload) > MAX_PAYLOAD_BYTES) {
1897
- reject(RpcError.builtIn('REQUEST_PAYLOAD_TOO_LARGE'));
1898
- return;
1899
- }
1900
-
1901
- if (
1902
- this.engine.latestJoinResponse?.serverInfo?.version &&
1903
- compareVersions(this.engine.latestJoinResponse?.serverInfo?.version, '1.8.0') < 0
1904
- ) {
1905
- reject(RpcError.builtIn('UNSUPPORTED_SERVER'));
1906
- return;
1907
- }
1908
-
1909
- const effectiveTimeout = Math.max(responseTimeout, minEffectiveTimeout);
1910
- const id = crypto.randomUUID();
1911
- await this.publishRpcRequest(destinationIdentity, id, method, payload, effectiveTimeout);
1912
-
1913
- const ackTimeoutId = setTimeout(() => {
1914
- this.pendingAcks.delete(id);
1915
- reject(RpcError.builtIn('CONNECTION_TIMEOUT'));
1916
- this.pendingResponses.delete(id);
1917
- clearTimeout(responseTimeoutId);
1918
- }, maxRoundTripLatency);
1919
-
1920
- this.pendingAcks.set(id, {
1921
- resolve: () => {
1922
- clearTimeout(ackTimeoutId);
1923
- },
1924
- participantIdentity: destinationIdentity,
1925
- });
1926
-
1927
- const responseTimeoutId = setTimeout(() => {
1928
- this.pendingResponses.delete(id);
1929
- reject(RpcError.builtIn('RESPONSE_TIMEOUT'));
1930
- }, responseTimeout);
1931
-
1932
- this.pendingResponses.set(id, {
1933
- resolve: (responsePayload: string | null, responseError: RpcError | null) => {
1934
- clearTimeout(responseTimeoutId);
1935
- if (this.pendingAcks.has(id)) {
1936
- this.log.warn('RPC response received before ack', id);
1937
- this.pendingAcks.delete(id);
1938
- clearTimeout(ackTimeoutId);
1939
- }
1940
-
1941
- if (responseError) {
1942
- reject(responseError);
1943
- } else {
1944
- resolve(responsePayload ?? '');
1945
- }
1946
- },
1947
- participantIdentity: destinationIdentity,
1948
- });
1854
+ performRpc(params: PerformRpcParams): TypedPromise<string, RpcError> {
1855
+ return this.rpcClientManager.performRpc(params).then(([_id, completionPromise]) => {
1856
+ return completionPromise;
1949
1857
  });
1950
1858
  }
1951
1859
 
@@ -1953,20 +1861,14 @@ export default class LocalParticipant extends Participant {
1953
1861
  * @deprecated use `room.registerRpcMethod` instead
1954
1862
  */
1955
1863
  registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
1956
- if (this.rpcHandlers.has(method)) {
1957
- this.log.warn(
1958
- `you're overriding the RPC handler for method ${method}, in the future this will throw an error`,
1959
- );
1960
- }
1961
-
1962
- this.rpcHandlers.set(method, handler);
1864
+ this.rpcServerManager.registerRpcMethod(method, handler);
1963
1865
  }
1964
1866
 
1965
1867
  /**
1966
1868
  * @deprecated use `room.unregisterRpcMethod` instead
1967
1869
  */
1968
1870
  unregisterRpcMethod(method: string) {
1969
- this.rpcHandlers.delete(method);
1871
+ this.rpcServerManager.unregisterRpcMethod(method);
1970
1872
  }
1971
1873
 
1972
1874
  /**
@@ -1997,72 +1899,6 @@ export default class LocalParticipant extends Participant {
1997
1899
  }
1998
1900
  }
1999
1901
 
2000
- private handleIncomingRpcAck(requestId: string) {
2001
- const handler = this.pendingAcks.get(requestId);
2002
- if (handler) {
2003
- handler.resolve();
2004
- this.pendingAcks.delete(requestId);
2005
- } else {
2006
- console.error('Ack received for unexpected RPC request', requestId);
2007
- }
2008
- }
2009
-
2010
- private handleIncomingRpcResponse(
2011
- requestId: string,
2012
- payload: string | null,
2013
- error: RpcError | null,
2014
- ) {
2015
- const handler = this.pendingResponses.get(requestId);
2016
- if (handler) {
2017
- handler.resolve(payload, error);
2018
- this.pendingResponses.delete(requestId);
2019
- } else {
2020
- console.error('Response received for unexpected RPC request', requestId);
2021
- }
2022
- }
2023
-
2024
- /** @internal */
2025
- private async publishRpcRequest(
2026
- destinationIdentity: string,
2027
- requestId: string,
2028
- method: string,
2029
- payload: string,
2030
- responseTimeout: number,
2031
- ) {
2032
- const packet = new DataPacket({
2033
- destinationIdentities: [destinationIdentity],
2034
- kind: DataPacket_Kind.RELIABLE,
2035
- value: {
2036
- case: 'rpcRequest',
2037
- value: new RpcRequest({
2038
- id: requestId,
2039
- method,
2040
- payload,
2041
- responseTimeoutMs: responseTimeout,
2042
- version: 1,
2043
- }),
2044
- },
2045
- });
2046
-
2047
- await this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
2048
- }
2049
-
2050
- /** @internal */
2051
- handleParticipantDisconnected(participantIdentity: string) {
2052
- for (const [id, { participantIdentity: pendingIdentity }] of this.pendingAcks) {
2053
- if (pendingIdentity === participantIdentity) {
2054
- this.pendingAcks.delete(id);
2055
- }
2056
- }
2057
-
2058
- for (const [id, { participantIdentity: pendingIdentity, resolve }] of this.pendingResponses) {
2059
- if (pendingIdentity === participantIdentity) {
2060
- resolve(null, RpcError.builtIn('RECIPIENT_DISCONNECTED'));
2061
- this.pendingResponses.delete(id);
2062
- }
2063
- }
2064
- }
2065
-
2066
1902
  /** @internal */
2067
1903
  setEnabledPublishCodecs(codecs: Codec[]) {
2068
1904
  this.enabledPublishVideoCodecs = codecs.filter(
@@ -6,6 +6,7 @@ import type {
6
6
  } from '@livekit/protocol';
7
7
  import type { SignalClient } from '../../api/SignalClient';
8
8
  import { DeferrableMap } from '../../utils/deferrable-map';
9
+ import { CLIENT_PROTOCOL_DEFAULT } from '../../version';
9
10
  import RemoteDataTrack from '../data-track/RemoteDataTrack';
10
11
  import type IncomingDataTrackManager from '../data-track/incoming/IncomingDataTrackManager';
11
12
  import { DataTrackInfo } from '../data-track/types';
@@ -41,6 +42,11 @@ export default class RemoteParticipant extends Participant {
41
42
 
42
43
  signalClient: SignalClient;
43
44
 
45
+ /** A version number indicating the set of features that the report participant's client supports.
46
+ * @internal
47
+ **/
48
+ clientProtocol: number;
49
+
44
50
  private volumeMap: Map<Track.Source, number>;
45
51
 
46
52
  private audioOutput?: AudioOutputOptions;
@@ -65,6 +71,7 @@ export default class RemoteParticipant extends Participant {
65
71
  const info = DataTrackInfo.from(dti);
66
72
  return new RemoteDataTrack(info, manager, { publisherIdentity: pi.identity });
67
73
  }),
74
+ pi.clientProtocol,
68
75
  );
69
76
  }
70
77
 
@@ -87,6 +94,7 @@ export default class RemoteParticipant extends Participant {
87
94
  loggerOptions?: LoggerOptions,
88
95
  kind: ParticipantKind = ParticipantKind.STANDARD,
89
96
  remoteDataTracks: Array<RemoteDataTrack> = [],
97
+ clientProtocol: number = CLIENT_PROTOCOL_DEFAULT,
90
98
  ) {
91
99
  super(sid, identity || '', name, metadata, attributes, loggerOptions, kind);
92
100
  this.signalClient = signalClient;
@@ -99,6 +107,7 @@ export default class RemoteParticipant extends Participant {
99
107
  }),
100
108
  );
101
109
  this.volumeMap = new Map();
110
+ this.clientProtocol = clientProtocol;
102
111
  }
103
112
 
104
113
  protected addTrackPublication(publication: RemoteTrackPublication) {