livekit-client 2.22.3 → 2.23.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 (272) hide show
  1. package/README.md +35 -0
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +71 -8
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +1746 -547
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.fm.worker.js +1 -1
  9. package/dist/livekit-client.fm.worker.js.map +1 -1
  10. package/dist/livekit-client.fm.worker.mjs +2 -1
  11. package/dist/livekit-client.fm.worker.mjs.map +1 -1
  12. package/dist/livekit-client.umd.js +1 -1
  13. package/dist/livekit-client.umd.js.map +1 -1
  14. package/dist/src/api/SignalClient.d.ts +4 -2
  15. package/dist/src/api/SignalClient.d.ts.map +1 -1
  16. package/dist/src/api/WebSocketStream.d.ts +1 -0
  17. package/dist/src/api/WebSocketStream.d.ts.map +1 -1
  18. package/dist/src/api/utils.d.ts +1 -1
  19. package/dist/src/api/utils.d.ts.map +1 -1
  20. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  21. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  22. package/dist/src/e2ee/worker/naluUtils.d.ts +12 -1
  23. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
  24. package/dist/src/index.d.ts +2 -2
  25. package/dist/src/index.d.ts.map +1 -1
  26. package/dist/src/options.d.ts +14 -0
  27. package/dist/src/options.d.ts.map +1 -1
  28. package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -1
  29. package/dist/src/room/PCTransport.d.ts +100 -7
  30. package/dist/src/room/PCTransport.d.ts.map +1 -1
  31. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  32. package/dist/src/room/RTCEngine.d.ts +16 -0
  33. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  34. package/dist/src/room/Room.d.ts +54 -1
  35. package/dist/src/room/Room.d.ts.map +1 -1
  36. package/dist/src/room/data-channel/DataChannelManager.d.ts +9 -8
  37. package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -1
  38. package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +25 -12
  39. package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -1
  40. package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -1
  41. package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -1
  42. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  43. package/dist/src/room/defaults.d.ts.map +1 -1
  44. package/dist/src/room/errors.d.ts +18 -0
  45. package/dist/src/room/errors.d.ts.map +1 -1
  46. package/dist/src/room/events.d.ts +3 -2
  47. package/dist/src/room/events.d.ts.map +1 -1
  48. package/dist/src/room/participant/LocalParticipant.d.ts +4 -0
  49. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  50. package/dist/src/room/participant/Participant.d.ts +1 -1
  51. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  52. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  53. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
  54. package/dist/src/room/rpc/utils.d.ts.map +1 -1
  55. package/dist/src/room/track/LocalAudioTrack.d.ts +24 -1
  56. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  57. package/dist/src/room/track/LocalTrack.d.ts +18 -0
  58. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  59. package/dist/src/room/track/LocalVideoTrack.d.ts +9 -0
  60. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  61. package/dist/src/room/track/RemoteAudioTrack.d.ts +6 -0
  62. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  63. package/dist/src/room/track/processor/types.d.ts +11 -0
  64. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  65. package/dist/src/room/track/utils.d.ts +13 -0
  66. package/dist/src/room/track/utils.d.ts.map +1 -1
  67. package/dist/src/room/utils.d.ts +60 -1
  68. package/dist/src/room/utils.d.ts.map +1 -1
  69. package/dist/src/test/compressionStreamStubs.d.ts +12 -0
  70. package/dist/src/test/compressionStreamStubs.d.ts.map +1 -0
  71. package/dist/ts4.2/api/SignalClient.d.ts +187 -185
  72. package/dist/ts4.2/api/SignalClientStateMachine.d.ts +85 -85
  73. package/dist/ts4.2/api/WebSocketStream.d.ts +34 -33
  74. package/dist/ts4.2/api/utils.d.ts +9 -9
  75. package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +30 -30
  76. package/dist/ts4.2/connectionHelper/checks/Checker.d.ts +62 -62
  77. package/dist/ts4.2/connectionHelper/checks/cloudRegion.d.ts +18 -18
  78. package/dist/ts4.2/connectionHelper/checks/connectionProtocol.d.ts +20 -20
  79. package/dist/ts4.2/connectionHelper/checks/publishAudio.d.ts +6 -6
  80. package/dist/ts4.2/connectionHelper/checks/publishVideo.d.ts +7 -7
  81. package/dist/ts4.2/connectionHelper/checks/reconnect.d.ts +6 -6
  82. package/dist/ts4.2/connectionHelper/checks/turn.d.ts +6 -6
  83. package/dist/ts4.2/connectionHelper/checks/webrtc.d.ts +6 -6
  84. package/dist/ts4.2/connectionHelper/checks/websocket.d.ts +6 -6
  85. package/dist/ts4.2/e2ee/E2eeManager.d.ts +111 -111
  86. package/dist/ts4.2/e2ee/KeyProvider.d.ts +54 -54
  87. package/dist/ts4.2/e2ee/constants.d.ts +21 -21
  88. package/dist/ts4.2/e2ee/errors.d.ts +12 -12
  89. package/dist/ts4.2/e2ee/events.d.ts +39 -39
  90. package/dist/ts4.2/e2ee/index.d.ts +6 -6
  91. package/dist/ts4.2/e2ee/types.d.ts +231 -231
  92. package/dist/ts4.2/e2ee/utils.d.ts +32 -32
  93. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +16 -16
  94. package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -21
  95. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +209 -209
  96. package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +81 -84
  97. package/dist/ts4.2/e2ee/worker/e2ee.worker.d.ts +2 -2
  98. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +39 -28
  99. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +23 -23
  100. package/dist/ts4.2/frameMetadata/FrameMetadataManager.d.ts +53 -53
  101. package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +33 -33
  102. package/dist/ts4.2/frameMetadata/types.d.ts +62 -62
  103. package/dist/ts4.2/frameMetadata/utils.d.ts +9 -9
  104. package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -2
  105. package/dist/ts4.2/index.d.ts +76 -76
  106. package/dist/ts4.2/logger.d.ts +66 -66
  107. package/dist/ts4.2/options.d.ts +163 -149
  108. package/dist/ts4.2/room/BackOffStrategy.d.ts +27 -27
  109. package/dist/ts4.2/room/DefaultReconnectPolicy.d.ts +8 -8
  110. package/dist/ts4.2/room/DeviceManager.d.ts +12 -12
  111. package/dist/ts4.2/room/PCTransport.d.ts +268 -175
  112. package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
  113. package/dist/ts4.2/room/RTCEngine.d.ts +277 -262
  114. package/dist/ts4.2/room/ReconnectPolicy.d.ts +24 -24
  115. package/dist/ts4.2/room/RegionUrlProvider.d.ts +36 -36
  116. package/dist/ts4.2/room/Room.d.ts +407 -365
  117. package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
  118. package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +63 -62
  119. package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +107 -95
  120. package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -41
  121. package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -57
  122. package/dist/ts4.2/room/data-channel/types.d.ts +12 -12
  123. package/dist/ts4.2/room/data-stream/compression.d.ts +49 -49
  124. package/dist/ts4.2/room/data-stream/constants.d.ts +18 -18
  125. package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +29 -29
  126. package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +80 -80
  127. package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +73 -73
  128. package/dist/ts4.2/room/data-stream/outgoing/StreamWriter.d.ts +16 -16
  129. package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -18
  130. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +79 -80
  131. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +50 -50
  132. package/dist/ts4.2/room/data-track/depacketizer.d.ts +59 -59
  133. package/dist/ts4.2/room/data-track/frame.d.ts +22 -22
  134. package/dist/ts4.2/room/data-track/handle.d.ts +27 -27
  135. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +124 -124
  136. package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -24
  137. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +41 -41
  138. package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -20
  139. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +126 -126
  140. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +74 -74
  141. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +23 -23
  142. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +48 -48
  143. package/dist/ts4.2/room/data-track/packet/constants.d.ts +20 -20
  144. package/dist/ts4.2/room/data-track/packet/errors.d.ts +40 -40
  145. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +69 -69
  146. package/dist/ts4.2/room/data-track/packet/index.d.ts +98 -98
  147. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +13 -13
  148. package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -43
  149. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -23
  150. package/dist/ts4.2/room/data-track/types.d.ts +22 -22
  151. package/dist/ts4.2/room/data-track/utils.d.ts +60 -60
  152. package/dist/ts4.2/room/debounce.d.ts +11 -11
  153. package/dist/ts4.2/room/defaults.d.ts +9 -9
  154. package/dist/ts4.2/room/errors.d.ts +180 -162
  155. package/dist/ts4.2/room/events.d.ts +638 -637
  156. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +291 -287
  157. package/dist/ts4.2/room/participant/Participant.d.ts +142 -144
  158. package/dist/ts4.2/room/participant/ParticipantTrackPermission.d.ts +26 -26
  159. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +85 -86
  160. package/dist/ts4.2/room/participant/publishUtils.d.ts +53 -53
  161. package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -43
  162. package/dist/ts4.2/room/rpc/client/events.d.ts +8 -8
  163. package/dist/ts4.2/room/rpc/index.d.ts +7 -7
  164. package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -44
  165. package/dist/ts4.2/room/rpc/server/events.d.ts +8 -8
  166. package/dist/ts4.2/room/rpc/utils.d.ts +131 -131
  167. package/dist/ts4.2/room/stats.d.ts +71 -71
  168. package/dist/ts4.2/room/statsSummary.d.ts +13 -13
  169. package/dist/ts4.2/room/timers.d.ts +12 -12
  170. package/dist/ts4.2/room/token-source/TokenSource.d.ts +82 -82
  171. package/dist/ts4.2/room/token-source/types.d.ts +70 -70
  172. package/dist/ts4.2/room/token-source/utils.d.ts +8 -8
  173. package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -19
  174. package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +62 -42
  175. package/dist/ts4.2/room/track/LocalTrack.d.ts +143 -125
  176. package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
  177. package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +109 -100
  178. package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +61 -55
  179. package/dist/ts4.2/room/track/RemoteTrack.d.ts +38 -38
  180. package/dist/ts4.2/room/track/RemoteTrackPublication.d.ts +72 -75
  181. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +73 -76
  182. package/dist/ts4.2/room/track/Track.d.ts +156 -166
  183. package/dist/ts4.2/room/track/TrackPublication.d.ts +75 -81
  184. package/dist/ts4.2/room/track/create.d.ts +25 -25
  185. package/dist/ts4.2/room/track/facingMode.d.ts +41 -41
  186. package/dist/ts4.2/room/track/options.d.ts +403 -403
  187. package/dist/ts4.2/room/track/processor/types.d.ts +42 -31
  188. package/dist/ts4.2/room/track/record.d.ts +13 -13
  189. package/dist/ts4.2/room/track/types.d.ts +28 -28
  190. package/dist/ts4.2/room/track/utils.d.ts +60 -47
  191. package/dist/ts4.2/room/types.d.ts +127 -127
  192. package/dist/ts4.2/room/utils.d.ts +273 -214
  193. package/dist/ts4.2/test/MockMediaStreamTrack.d.ts +26 -26
  194. package/dist/ts4.2/test/compressionStreamStubs.d.ts +12 -0
  195. package/dist/ts4.2/test/mocks.d.ts +12 -12
  196. package/dist/ts4.2/test/promiseState.d.ts +12 -12
  197. package/dist/ts4.2/test/signalToken.d.ts +26 -26
  198. package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -2
  199. package/dist/ts4.2/utils/AsyncQueue.d.ts +23 -23
  200. package/dist/ts4.2/utils/TypedPromise.d.ts +17 -17
  201. package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -13
  202. package/dist/ts4.2/utils/browserParser.d.ts +13 -13
  203. package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -8
  204. package/dist/ts4.2/utils/cloneDeep.d.ts +2 -2
  205. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +34 -34
  206. package/dist/ts4.2/utils/deferrable-map.d.ts +32 -32
  207. package/dist/ts4.2/utils/machineInspector.d.ts +54 -54
  208. package/dist/ts4.2/utils/serializer.d.ts +48 -48
  209. package/dist/ts4.2/utils/subscribeToEvents.d.ts +15 -15
  210. package/dist/ts4.2/utils/ttlmap.d.ts +20 -20
  211. package/dist/ts4.2/version.d.ts +15 -15
  212. package/package.json +5 -4
  213. package/src/api/SignalClient.e2e.test.ts +1 -3
  214. package/src/api/SignalClient.test.ts +394 -10
  215. package/src/api/SignalClient.ts +132 -76
  216. package/src/api/WebSocketStream.test.ts +11 -0
  217. package/src/api/WebSocketStream.ts +22 -1
  218. package/src/api/utils.ts +1 -1
  219. package/src/e2ee/E2eeManager.test.ts +43 -0
  220. package/src/e2ee/E2eeManager.ts +6 -2
  221. package/src/e2ee/worker/FrameCryptor.test.ts +212 -90
  222. package/src/e2ee/worker/FrameCryptor.ts +21 -4
  223. package/src/e2ee/worker/naluUtils.test.ts +123 -0
  224. package/src/e2ee/worker/naluUtils.ts +57 -2
  225. package/src/index.ts +2 -0
  226. package/src/options.ts +16 -0
  227. package/src/room/DefaultReconnectPolicy.test.ts +58 -0
  228. package/src/room/DefaultReconnectPolicy.ts +14 -2
  229. package/src/room/PCTransport.test.ts +327 -9
  230. package/src/room/PCTransport.ts +339 -36
  231. package/src/room/PCTransportManager.ts +3 -1
  232. package/src/room/RTCEngine.test.ts +318 -2
  233. package/src/room/RTCEngine.ts +126 -12
  234. package/src/room/Room.test.ts +148 -6
  235. package/src/room/Room.ts +312 -80
  236. package/src/room/data-channel/DataChannelManager.test.ts +24 -7
  237. package/src/room/data-channel/DataChannelManager.ts +43 -38
  238. package/src/room/data-channel/FlowControlledDataChannel.test.ts +60 -18
  239. package/src/room/data-channel/FlowControlledDataChannel.ts +40 -22
  240. package/src/room/data-channel/LossyDataChannel.test.ts +38 -0
  241. package/src/room/data-channel/LossyDataChannel.ts +10 -1
  242. package/src/room/data-channel/ReliableDataChannel.test.ts +72 -0
  243. package/src/room/data-channel/ReliableDataChannel.ts +16 -5
  244. package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +47 -20
  245. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +16 -5
  246. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +60 -39
  247. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +4 -4
  248. package/src/room/defaults.ts +1 -0
  249. package/src/room/errors.test.ts +36 -0
  250. package/src/room/errors.ts +27 -0
  251. package/src/room/events.ts +2 -1
  252. package/src/room/participant/LocalParticipant.test.ts +136 -1
  253. package/src/room/participant/LocalParticipant.ts +66 -17
  254. package/src/room/participant/Participant.ts +6 -3
  255. package/src/room/participant/publishUtils.ts +0 -19
  256. package/src/room/participant/restartOnDefaultAudioDevice.test.ts +177 -0
  257. package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
  258. package/src/room/rpc/client/RpcClientManager.ts +32 -15
  259. package/src/room/rpc/utils.test.ts +32 -0
  260. package/src/room/rpc/utils.ts +7 -0
  261. package/src/room/track/LocalAudioTrack.ts +101 -10
  262. package/src/room/track/LocalTrack.ts +87 -19
  263. package/src/room/track/LocalVideoTrack.ts +17 -3
  264. package/src/room/track/RemoteAudioTrack.ts +59 -14
  265. package/src/room/track/RemoteVideoTrack.test.ts +210 -0
  266. package/src/room/track/RemoteVideoTrack.ts +29 -3
  267. package/src/room/track/processor/types.ts +11 -0
  268. package/src/room/track/utils.test.ts +206 -2
  269. package/src/room/track/utils.ts +117 -5
  270. package/src/room/utils.test.ts +250 -0
  271. package/src/room/utils.ts +152 -19
  272. package/src/test/compressionStreamStubs.ts +53 -0
@@ -63,6 +63,7 @@ import type { LoggerOptions } from '../room/types';
63
63
  import { getClientInfo, isCompressionStreamSupported, isReactNative, sleep } from '../room/utils';
64
64
  import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
65
65
  import { AsyncQueue } from '../utils/AsyncQueue';
66
+ import { abortSignalTimeout } from '../utils/abort-signal-polyfill';
66
67
  import { announceMachine } from '../utils/machineInspector';
67
68
  import {
68
69
  type SignalLifecycleState,
@@ -92,6 +93,8 @@ interface ConnectOpts extends SignalOptions {
92
93
  export interface SignalOptions {
93
94
  autoSubscribe: boolean;
94
95
  adaptiveStream?: boolean;
96
+ disableIceLite?: boolean;
97
+ subscriberAllowPause?: boolean;
95
98
  clientInfoCapabilities?: ClientInfo_Capability[];
96
99
  maxRetries: number;
97
100
  e2eeEnabled: boolean;
@@ -432,15 +435,29 @@ export class SignalClient {
432
435
  ): Promise<JoinResponse | ReconnectResponse | undefined> {
433
436
  const unlock = await this.connectionLock.lock();
434
437
 
435
- this.connectOptions = opts;
436
- this.useV0SignalPath = useV0Path;
438
+ let rtcUrl: string;
439
+ let validateUrl: string;
440
+ try {
441
+ this.connectOptions = opts;
442
+ this.useV0SignalPath = useV0Path;
443
+
444
+ const clientInfo = getClientInfo(opts.clientInfoCapabilities);
445
+ const params = useV0Path
446
+ ? createConnectionParams(token, clientInfo, opts)
447
+ : await createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer);
448
+ rtcUrl = createRtcUrl(url, params, useV0Path).toString();
449
+ validateUrl = createValidateUrl(rtcUrl).toString();
450
+ } catch (e) {
451
+ // the executor below owns the lock from here on
452
+ unlock();
453
+ throw e;
454
+ }
437
455
 
438
- const clientInfo = getClientInfo(opts.clientInfoCapabilities);
439
- const params = useV0Path
440
- ? createConnectionParams(token, clientInfo, opts)
441
- : await createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer);
442
- const rtcUrl = createRtcUrl(url, params, useV0Path).toString();
443
- const validateUrl = createValidateUrl(rtcUrl).toString();
456
+ if (abortSignal?.aborted) {
457
+ unlock();
458
+ this.log.debug('signal connect abandoned before opening a transport');
459
+ throw ConnectionError.cancelled('Connection aborted');
460
+ }
444
461
 
445
462
  return new Promise<JoinResponse | ReconnectResponse | undefined>(async (resolve, reject) => {
446
463
  try {
@@ -455,13 +472,13 @@ export class SignalClient {
455
472
  // send leave if we have an active stream writer (connection is open)
456
473
  if (this.streamWriter && !this.isDisconnected) {
457
474
  this.sendLeave()
458
- .then(() => this.close(reason))
475
+ .then(() => this.close(true, reason))
459
476
  .catch((e) => {
460
477
  this.log.error(e);
461
- this.close();
478
+ this.close(true, reason);
462
479
  });
463
480
  } else {
464
- this.close();
481
+ this.close(true, reason);
465
482
  }
466
483
  cleanupAbortHandlers();
467
484
  // The caller may already have classified this failure: the connect timeout hands us a
@@ -496,6 +513,10 @@ export class SignalClient {
496
513
  await this.teardownTransport('replaced by a new connection attempt');
497
514
  this.log.debug(`closed previous ws connection in ${performance.now() - startClose}ms`);
498
515
  }
516
+ if (alreadyAborted) {
517
+ this.log.debug('signal connect abandoned after tearing down the previous transport');
518
+ return;
519
+ }
499
520
 
500
521
  // the transport created below belongs to this attempt; events arriving from it after a
501
522
  // newer attempt has started are dropped by the machine
@@ -560,6 +581,12 @@ export class SignalClient {
560
581
  }
561
582
  });
562
583
  const connection = await this.ws.opened.catch(async (reason: unknown) => {
584
+ if (alreadyAborted) {
585
+ // the abort handler has already rejected this attempt; don't probe the validate
586
+ // endpoint for a failure nobody is waiting on
587
+ this.log.debug('signal connect abandoned while the websocket was opening');
588
+ return;
589
+ }
563
590
  if (this.lifecycleState !== 'connected') {
564
591
  // claimed synchronously, before the await below: the socket's close event is already
565
592
  // on its way and would otherwise reject first with a less useful error
@@ -581,39 +608,69 @@ export class SignalClient {
581
608
  this.streamWriter = connection.writable.getWriter();
582
609
 
583
610
  // wsTimeout only guarded the upgrade; guard the first-message read with
584
- // its own timeout so a silent server can't hang join() forever.
585
- let firstMessage: ReadableStreamReadResult<string | ArrayBuffer>;
586
- let firstMessageTimeout: ReturnType<typeof setTimeout> | undefined;
611
+ // its own deadline so a silent server can't hang join() forever.
612
+ // During reconnect, drop any stray messages that arrive before the
613
+ // ReconnectResponse so we do not declare the channel reconnected on the
614
+ // wrong signal.
615
+ let firstSignalResponse: SignalResponse;
587
616
  try {
588
- firstMessage = await Promise.race([
589
- signalReader.read(),
590
- new Promise<never>((_, rejectRead) => {
591
- firstMessageTimeout = setTimeout(() => {
592
- rejectRead(
593
- ConnectionError.timeout(
594
- 'signal connection timed out while waiting for the first message',
595
- ),
596
- );
597
- }, JOIN_RESPONSE_TIMEOUT);
598
- }),
599
- ]);
617
+ const deadline = Date.now() + JOIN_RESPONSE_TIMEOUT;
618
+ // eslint-disable-next-line no-constant-condition
619
+ while (true) {
620
+ const remaining = deadline - Date.now();
621
+ if (remaining <= 0) {
622
+ throw ConnectionError.timeout(
623
+ 'signal connection timed out while waiting for the first message',
624
+ );
625
+ }
626
+ let readTimeout: ReturnType<typeof setTimeout> | undefined;
627
+ const readResult = await Promise.race([
628
+ signalReader.read(),
629
+ new Promise<never>((_, rejectRead) => {
630
+ readTimeout = setTimeout(() => {
631
+ rejectRead(
632
+ ConnectionError.timeout(
633
+ 'signal connection timed out while waiting for the first message',
634
+ ),
635
+ );
636
+ }, remaining);
637
+ }),
638
+ ]);
639
+ clearTimeout(readTimeout);
640
+ if (!readResult.value) {
641
+ throw ConnectionError.internal('no message received as first message');
642
+ }
643
+ const parsed = parseSignalResponse(readResult.value);
644
+ if (
645
+ this.lifecycleState === 'reconnecting' &&
646
+ parsed.message?.case !== 'reconnect' &&
647
+ parsed.message?.case !== 'leave'
648
+ ) {
649
+ this.log.warn('dropping signal message while awaiting reconnect response', {
650
+ messageCase: parsed.message?.case,
651
+ });
652
+ continue;
653
+ }
654
+ firstSignalResponse = parsed;
655
+ break;
656
+ }
600
657
  } catch (e) {
601
- // No first message in time: release the reader and tear down the ws
658
+ // No usable first message in time: release the reader and tear down the ws
602
659
  // so we surface the timeout instead of leaking an open connection.
603
660
  signalReader.releaseLock();
604
661
  reject(e);
605
662
  this.close();
606
663
  return;
607
- } finally {
608
- clearTimeout(firstMessageTimeout);
609
664
  }
610
665
  signalReader.releaseLock();
611
- if (!firstMessage.value) {
612
- throw ConnectionError.internal('no message received as first message');
666
+ if (alreadyAborted) {
667
+ // The abort handler has already rejected this attempt and is closing the transport.
668
+ // Don't act on a join that raced it: `onJoined` would publish an abandoned session's
669
+ // state to the room.
670
+ this.log.debug('signal connect abandoned while waiting for the first message');
671
+ return;
613
672
  }
614
673
 
615
- const firstSignalResponse = parseSignalResponse(firstMessage.value);
616
-
617
674
  // Validate the first message
618
675
  const validation = this.validateFirstMessage(
619
676
  firstSignalResponse,
@@ -643,11 +700,7 @@ export class SignalClient {
643
700
  }
644
701
  }
645
702
 
646
- // Handle successful connection
647
- const firstMessageToProcess = validation.shouldProcessFirstMessage
648
- ? firstSignalResponse
649
- : undefined;
650
- this.handleSignalConnected(connection, wsTimeout, attemptId, firstMessageToProcess);
703
+ this.handleSignalConnected(connection, wsTimeout, attemptId);
651
704
  resolve(validation.response);
652
705
  } catch (e) {
653
706
  reject(e);
@@ -660,13 +713,7 @@ export class SignalClient {
660
713
  });
661
714
  }
662
715
 
663
- async startReadingLoop(
664
- signalReader: ReadableStreamDefaultReader<string | ArrayBuffer>,
665
- firstMessage?: SignalResponse,
666
- ) {
667
- if (firstMessage) {
668
- this.handleSignalResponse(firstMessage);
669
- }
716
+ async startReadingLoop(signalReader: ReadableStreamDefaultReader<string | ArrayBuffer>) {
670
717
  const attemptId = this.attemptId;
671
718
  while (true) {
672
719
  if (this.signalLatency) {
@@ -726,14 +773,15 @@ export class SignalClient {
726
773
  private async teardownTransport(reason: string) {
727
774
  try {
728
775
  this.clearPingInterval();
729
- if (this.ws) {
730
- this.ws.close({ closeCode: 1000, reason });
731
-
732
- // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
733
- const closePromise = this.ws.closed;
776
+ const ws = this.ws;
777
+ if (ws) {
778
+ // released before closing, so a close that throws cannot leave the transport attached
734
779
  this.ws = undefined;
735
780
  this.streamWriter = undefined;
736
- await Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]);
781
+ ws.close({ closeCode: 1000, reason });
782
+
783
+ // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
784
+ await Promise.race([ws.closed, sleep(MAX_WS_CLOSE_TIME)]);
737
785
  }
738
786
  } catch (e) {
739
787
  this.log.debug('websocket error while closing', { error: e });
@@ -1099,7 +1147,11 @@ export class SignalClient {
1099
1147
  * Handles a transport we lost without asking to. The client goes to `offline` rather than
1100
1148
  * `closed`: whether this session gets resumed, restarted or given up on is the engine's call.
1101
1149
  */
1102
- private async handleOnClose(reason: string, attemptId: number = this.attemptId) {
1150
+ private async handleOnClose(
1151
+ reason: string,
1152
+ attemptId: number = this.attemptId,
1153
+ simulateReconnectDelay?: number,
1154
+ ) {
1103
1155
  const onCloseCallback = this.onClose;
1104
1156
  if (!this.sendLifecycleInput({ type: 'transportFailed', attemptId, reason })) {
1105
1157
  // a close we caused ourselves, or one from a transport that has since been replaced: logged
@@ -1114,6 +1166,10 @@ export class SignalClient {
1114
1166
  }
1115
1167
  await this.teardownTransport(reason);
1116
1168
  this.log.info(`websocket connection closed: ${reason}`, { reason });
1169
+ if (simulateReconnectDelay) {
1170
+ this.log.info(`waiting for ${simulateReconnectDelay}ms until reconnecting signal`);
1171
+ await sleep(simulateReconnectDelay);
1172
+ }
1117
1173
  if (onCloseCallback) {
1118
1174
  onCloseCallback(reason);
1119
1175
  }
@@ -1184,7 +1240,6 @@ export class SignalClient {
1184
1240
  connection: WebSocketConnection,
1185
1241
  timeoutHandle: ReturnType<typeof setTimeout>,
1186
1242
  attemptId: number,
1187
- firstMessage?: SignalResponse,
1188
1243
  ) {
1189
1244
  clearTimeout(timeoutHandle);
1190
1245
  const established = this.sendLifecycleInput(
@@ -1205,7 +1260,7 @@ export class SignalClient {
1205
1260
  }
1206
1261
  this.log.info('signal connected');
1207
1262
  this.startPingInterval();
1208
- this.startReadingLoop(connection.readable.getReader(), firstMessage);
1263
+ this.startReadingLoop(connection.readable.getReader());
1209
1264
  }
1210
1265
 
1211
1266
  /**
@@ -1222,7 +1277,6 @@ export class SignalClient {
1222
1277
  isValid: boolean;
1223
1278
  response?: JoinResponse | ReconnectResponse;
1224
1279
  error?: ConnectionError;
1225
- shouldProcessFirstMessage?: boolean;
1226
1280
  } {
1227
1281
  if (firstSignalResponse.message?.case === 'join') {
1228
1282
  return {
@@ -1231,22 +1285,12 @@ export class SignalClient {
1231
1285
  };
1232
1286
  } else if (
1233
1287
  this.lifecycleState === 'reconnecting' &&
1234
- firstSignalResponse.message?.case !== 'leave'
1288
+ firstSignalResponse.message?.case === 'reconnect'
1235
1289
  ) {
1236
- if (firstSignalResponse.message?.case === 'reconnect') {
1237
- return {
1238
- isValid: true,
1239
- response: firstSignalResponse.message.value,
1240
- };
1241
- } else {
1242
- // in reconnecting, any message received means signal reconnected and we still need to process it
1243
- this.log.debug('declaring signal reconnected without reconnect response received');
1244
- return {
1245
- isValid: true,
1246
- response: undefined,
1247
- shouldProcessFirstMessage: true,
1248
- };
1249
- }
1290
+ return {
1291
+ isValid: true,
1292
+ response: firstSignalResponse.message.value,
1293
+ };
1250
1294
  } else if (this.isEstablishingConnection && firstSignalResponse.message?.case === 'leave') {
1251
1295
  return {
1252
1296
  isValid: false,
@@ -1283,7 +1327,7 @@ export class SignalClient {
1283
1327
  validateUrl: string,
1284
1328
  ): Promise<ConnectionError> {
1285
1329
  try {
1286
- const resp = await fetch(validateUrl);
1330
+ const resp = await fetch(validateUrl, { signal: abortSignalTimeout(3000) });
1287
1331
 
1288
1332
  switch (resp.status) {
1289
1333
  case 404:
@@ -1312,11 +1356,13 @@ export class SignalClient {
1312
1356
  );
1313
1357
  }
1314
1358
  } catch (e) {
1315
- return e instanceof ConnectionError
1316
- ? e
1317
- : ConnectionError.serverUnreachable(
1318
- e instanceof Error ? e.message : 'server was not reachable',
1319
- );
1359
+ if (e instanceof ConnectionError) {
1360
+ return e;
1361
+ } else {
1362
+ return ConnectionError.serverUnreachable(
1363
+ e instanceof Error ? e.message : 'server was not reachable',
1364
+ );
1365
+ }
1320
1366
  }
1321
1367
  }
1322
1368
  }
@@ -1394,6 +1440,14 @@ function createConnectionParams(
1394
1440
  params.set('adaptive_stream', '1');
1395
1441
  }
1396
1442
 
1443
+ if (opts.disableIceLite) {
1444
+ params.set('disable_ice_lite', '1');
1445
+ }
1446
+
1447
+ if (typeof opts.subscriberAllowPause !== 'undefined') {
1448
+ params.set('subscriber_allow_pause', opts.subscriberAllowPause ? '1' : '0');
1449
+ }
1450
+
1397
1451
  if (opts.reconnectReason) {
1398
1452
  params.set('reconnect_reason', opts.reconnectReason.toString());
1399
1453
  }
@@ -1421,6 +1475,8 @@ async function createJoinRequestConnectionParams(
1421
1475
  connectionSettings: new ConnectionSettings({
1422
1476
  autoSubscribe: !!opts.autoSubscribe,
1423
1477
  adaptiveStream: !!opts.adaptiveStream,
1478
+ disableIceLite: !!opts.disableIceLite,
1479
+ subscriberAllowPause: opts.subscriberAllowPause,
1424
1480
  }),
1425
1481
  reconnect: !!opts.reconnect,
1426
1482
  participantSid: opts.sid ? opts.sid : undefined,
@@ -407,6 +407,17 @@ describe('WebSocketStream', () => {
407
407
  wsStream.close();
408
408
  expect(closeSpy).toHaveBeenCalledWith(undefined, undefined);
409
409
  });
410
+
411
+ it('truncates a reason longer than WebSocket.close accepts', () => {
412
+ const wsStream = new WebSocketStream('wss://test.example.com');
413
+
414
+ const closeSpy = vi.spyOn(mockWebSocket, 'close');
415
+
416
+ // 'é' is two UTF-8 bytes, so the 123-byte limit falls in the middle of a character
417
+ wsStream.close({ closeCode: 1000, reason: 'é'.repeat(100) });
418
+
419
+ expect(closeSpy).toHaveBeenCalledWith(1000, 'é'.repeat(61));
420
+ });
410
421
  });
411
422
 
412
423
  describe('AbortSignal integration', () => {
@@ -55,7 +55,7 @@ export class WebSocketStream<T extends ArrayBuffer | string = ArrayBuffer | stri
55
55
  this.ws = ws;
56
56
 
57
57
  const closeWithInfo = ({ closeCode: code, reason }: WebSocketCloseInfo = {}) =>
58
- ws.close(code, reason);
58
+ ws.close(code, reason === undefined ? undefined : truncateCloseReason(reason));
59
59
 
60
60
  this.opened = new TypedPromise<WebSocketConnection<T>, WebsocketError>((resolve, reject) => {
61
61
  const rejectHandler = () => {
@@ -140,3 +140,24 @@ export class WebSocketStream<T extends ArrayBuffer | string = ArrayBuffer | stri
140
140
  this.close = closeWithInfo;
141
141
  }
142
142
  }
143
+
144
+ /** `WebSocket.close` throws a `SyntaxError` for a reason longer than this many UTF-8 bytes. */
145
+ const MAX_CLOSE_REASON_BYTES = 123;
146
+
147
+ export function truncateCloseReason(reason: string): string {
148
+ const encoder = new TextEncoder();
149
+ if (encoder.encode(reason).length <= MAX_CLOSE_REASON_BYTES) {
150
+ return reason;
151
+ }
152
+ // cut on code point boundaries, so a multi-byte character is never split
153
+ let truncated = '';
154
+ let bytes = 0;
155
+ for (const char of reason) {
156
+ bytes += encoder.encode(char).length;
157
+ if (bytes > MAX_CLOSE_REASON_BYTES) {
158
+ break;
159
+ }
160
+ truncated += char;
161
+ }
162
+ return truncated;
163
+ }
package/src/api/utils.ts CHANGED
@@ -44,7 +44,7 @@ export function parseSignalResponse(value: ArrayBuffer | string) {
44
44
  export function getAbortReasonAsString(
45
45
  signal: AbortSignal | Error | unknown,
46
46
  defaultMessage = 'Unknown reason',
47
- ) {
47
+ ): string {
48
48
  // the connect timeout hands this an Error rather than a signal, and its message is the only
49
49
  // description of what went wrong — without this a timeout reports itself as a generic abort
50
50
  if (signal instanceof Error) {
@@ -1,6 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { LogLevel, getWorkerLogLevelListenerCount, setLogLevel, workerLogger } from '../logger';
3
3
  import Room from '../room/Room';
4
+ import { ParticipantEvent } from '../room/events';
5
+ import { Track } from '../room/track/Track';
6
+ import type { VideoCodec } from '../room/track/options';
4
7
  import { E2EEManager } from './E2eeManager';
5
8
  import { BaseKeyProvider } from './KeyProvider';
6
9
 
@@ -194,3 +197,43 @@ describe('E2EEManager GC cleanup', () => {
194
197
  },
195
198
  );
196
199
  });
200
+
201
+ describe('E2EEManager sender codec seeding', () => {
202
+ function publishVideo(videoCodec: VideoCodec | undefined) {
203
+ const { room, worker, manager } = makeManager();
204
+ room.localParticipant.identity = 'me';
205
+ manager.setup(room);
206
+
207
+ const sender = {
208
+ createEncodedStreams: () => ({
209
+ readable: new ReadableStream(),
210
+ writable: new WritableStream(),
211
+ }),
212
+ } as unknown as RTCRtpSender;
213
+ const track = {
214
+ isLocal: true,
215
+ kind: Track.Kind.Video,
216
+ mediaStreamID: 'ms1',
217
+ publishOptions: { videoCodec },
218
+ };
219
+
220
+ room.localParticipant.emit(ParticipantEvent.LocalSenderCreated, sender, track as any);
221
+ manager.dispose();
222
+
223
+ return worker.postMessage.mock.calls.map(([m]) => m).find((m: any) => m?.kind === 'encode');
224
+ }
225
+
226
+ it('seeds the publish codec for h264', () => {
227
+ expect(publishVideo('h264')?.data.codec).toBe('h264');
228
+ });
229
+
230
+ it('seeds the publish codec for h265', () => {
231
+ expect(publishVideo('h265')?.data.codec).toBe('h265');
232
+ });
233
+
234
+ it('leaves vp8 unseeded so byte detection still decides', () => {
235
+ const encode = publishVideo('vp8');
236
+ expect(encode).toBeDefined();
237
+ expect(encode.data.codec).toBeUndefined();
238
+ });
239
+ });
@@ -620,11 +620,15 @@ export class E2EEManager
620
620
  if (!sender) this.log.warn('early return because sender is not ready');
621
621
  return;
622
622
  }
623
+ const isVideo = isVideoTrack(track);
624
+ // Safari exposes no per-frame codec info until trackInfo arrives. Seed only h264/h265:
625
+ // a wrong vp8 seed would skip the NALU path, a wrong NALU seed only costs a logged fallback.
626
+ const publishedCodec = isVideo ? track.publishOptions?.videoCodec : undefined;
623
627
  this.handleSender(
624
628
  sender,
625
629
  track.mediaStreamID,
626
- undefined,
627
- isVideoTrack(track)
630
+ publishedCodec === 'h264' || publishedCodec === 'h265' ? publishedCodec : undefined,
631
+ isVideo
628
632
  ? (track.publishOptions?.frameMetadata ?? track.publishOptions?.packetTrailer)
629
633
  : undefined,
630
634
  );