@stream-io/video-client 1.42.1 → 1.42.3
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.
- package/CHANGELOG.md +12 -0
- package/dist/index.browser.es.js +125 -52
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +125 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +125 -52
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -0
- package/dist/src/StreamSfuClient.d.ts +1 -6
- package/dist/src/errors/SfuJoinError.d.ts +7 -0
- package/dist/src/errors/index.d.ts +1 -0
- package/dist/src/gen/coordinator/index.d.ts +6 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +1 -0
- package/dist/src/rtc/Dispatcher.d.ts +36 -2
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +36 -14
- package/src/StreamSfuClient.ts +36 -37
- package/src/__tests__/Call.test.ts +22 -15
- package/src/errors/SfuJoinError.ts +26 -0
- package/src/errors/index.ts +1 -0
- package/src/events/internal.ts +4 -4
- package/src/events/speaker.ts +2 -2
- package/src/gen/coordinator/index.ts +6 -0
- package/src/rtc/BasePeerConnection.ts +3 -1
- package/src/rtc/Dispatcher.ts +55 -13
- package/src/rtc/__tests__/Dispatcher.test.ts +28 -0
- package/src/rtc/__tests__/Publisher.test.ts +3 -0
- package/src/rtc/__tests__/Subscriber.test.ts +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './src/CallType';
|
|
|
10
10
|
export * from './src/StreamVideoClient';
|
|
11
11
|
export * from './src/StreamSfuClient';
|
|
12
12
|
export * from './src/devices';
|
|
13
|
+
export * from './src/errors';
|
|
13
14
|
export * from './src/store';
|
|
14
15
|
export * from './src/sorting';
|
|
15
16
|
export * from './src/helpers/client-details';
|
package/dist/index.es.js
CHANGED
|
@@ -4272,6 +4272,12 @@ const sfuEventKinds = {
|
|
|
4272
4272
|
changePublishOptions: undefined,
|
|
4273
4273
|
inboundStateNotification: undefined,
|
|
4274
4274
|
};
|
|
4275
|
+
/**
|
|
4276
|
+
* Determines if a given event name belongs to the category of SFU events.
|
|
4277
|
+
*
|
|
4278
|
+
* @param eventName the name of the event to check.
|
|
4279
|
+
* @returns true if the event name is an SFU event, otherwise false.
|
|
4280
|
+
*/
|
|
4275
4281
|
const isSfuEvent = (eventName) => {
|
|
4276
4282
|
return Object.prototype.hasOwnProperty.call(sfuEventKinds, eventName);
|
|
4277
4283
|
};
|
|
@@ -4279,33 +4285,70 @@ class Dispatcher {
|
|
|
4279
4285
|
constructor() {
|
|
4280
4286
|
this.logger = videoLoggerSystem.getLogger('Dispatcher');
|
|
4281
4287
|
this.subscribers = {};
|
|
4282
|
-
|
|
4288
|
+
/**
|
|
4289
|
+
* Dispatch an event to all subscribers.
|
|
4290
|
+
*
|
|
4291
|
+
* @param message the event payload to dispatch.
|
|
4292
|
+
* @param tag for scoping events to a specific tag. Use `*` dispatch to every tag.
|
|
4293
|
+
*/
|
|
4294
|
+
this.dispatch = (message, tag = '*') => {
|
|
4283
4295
|
const eventKind = message.eventPayload.oneofKind;
|
|
4284
4296
|
if (!eventKind)
|
|
4285
4297
|
return;
|
|
4286
4298
|
const payload = message.eventPayload[eventKind];
|
|
4287
4299
|
this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
|
|
4288
|
-
const
|
|
4289
|
-
if (!
|
|
4300
|
+
const handlers = this.subscribers[eventKind];
|
|
4301
|
+
if (!handlers)
|
|
4290
4302
|
return;
|
|
4291
|
-
|
|
4303
|
+
this.emit(payload, handlers[tag]);
|
|
4304
|
+
if (tag !== '*')
|
|
4305
|
+
this.emit(payload, handlers['*']);
|
|
4306
|
+
};
|
|
4307
|
+
/**
|
|
4308
|
+
* Emit an event to a list of listeners.
|
|
4309
|
+
*
|
|
4310
|
+
* @param payload the event payload to emit.
|
|
4311
|
+
* @param listeners the list of listeners to emit the event to.
|
|
4312
|
+
*/
|
|
4313
|
+
this.emit = (payload, listeners = []) => {
|
|
4314
|
+
for (const listener of listeners) {
|
|
4292
4315
|
try {
|
|
4293
|
-
|
|
4316
|
+
listener(payload);
|
|
4294
4317
|
}
|
|
4295
4318
|
catch (e) {
|
|
4296
4319
|
this.logger.warn('Listener failed with error', e);
|
|
4297
4320
|
}
|
|
4298
4321
|
}
|
|
4299
4322
|
};
|
|
4300
|
-
|
|
4323
|
+
/**
|
|
4324
|
+
* Subscribe to an event.
|
|
4325
|
+
*
|
|
4326
|
+
* @param eventName the name of the event to subscribe to.
|
|
4327
|
+
* @param tag for scoping events to a specific tag. Use `*` dispatch to every tag.
|
|
4328
|
+
* @param fn the callback function to invoke when the event is emitted.
|
|
4329
|
+
* @returns a function that can be called to unsubscribe from the event.
|
|
4330
|
+
*/
|
|
4331
|
+
this.on = (eventName, tag, fn) => {
|
|
4301
4332
|
var _a;
|
|
4302
|
-
((_a = this.subscribers)[eventName] ?? (_a[eventName] =
|
|
4333
|
+
const bucket = ((_a = this.subscribers)[eventName] ?? (_a[eventName] = {}));
|
|
4334
|
+
(bucket[tag] ?? (bucket[tag] = [])).push(fn);
|
|
4303
4335
|
return () => {
|
|
4304
|
-
this.off(eventName, fn);
|
|
4336
|
+
this.off(eventName, tag, fn);
|
|
4305
4337
|
};
|
|
4306
4338
|
};
|
|
4307
|
-
|
|
4308
|
-
|
|
4339
|
+
/**
|
|
4340
|
+
* Unsubscribe from an event.
|
|
4341
|
+
*
|
|
4342
|
+
* @param eventName the name of the event to unsubscribe from.
|
|
4343
|
+
* @param tag for scoping events to a specific tag. Use `*` dispatch to every tag.
|
|
4344
|
+
* @param fn the callback function to remove from the event listeners.
|
|
4345
|
+
*/
|
|
4346
|
+
this.off = (eventName, tag, fn) => {
|
|
4347
|
+
const bucket = this.subscribers[eventName];
|
|
4348
|
+
const listeners = bucket?.[tag];
|
|
4349
|
+
if (!listeners)
|
|
4350
|
+
return;
|
|
4351
|
+
bucket[tag] = listeners.filter((f) => f !== fn);
|
|
4309
4352
|
};
|
|
4310
4353
|
}
|
|
4311
4354
|
}
|
|
@@ -6189,7 +6232,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6189
6232
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6190
6233
|
};
|
|
6191
6234
|
|
|
6192
|
-
const version = "1.42.
|
|
6235
|
+
const version = "1.42.3";
|
|
6193
6236
|
const [major, minor, patch] = version.split('.');
|
|
6194
6237
|
let sdkInfo = {
|
|
6195
6238
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -7256,7 +7299,7 @@ class BasePeerConnection {
|
|
|
7256
7299
|
* Consecutive events are queued and executed one after the other.
|
|
7257
7300
|
*/
|
|
7258
7301
|
this.on = (event, fn) => {
|
|
7259
|
-
this.subscriptions.push(this.dispatcher.on(event, (e) => {
|
|
7302
|
+
this.subscriptions.push(this.dispatcher.on(event, this.tag, (e) => {
|
|
7260
7303
|
const lockKey = `pc.${this.lock}.${event}`;
|
|
7261
7304
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
7262
7305
|
if (this.isDisposed)
|
|
@@ -7441,6 +7484,7 @@ class BasePeerConnection {
|
|
|
7441
7484
|
this.dispatcher = dispatcher;
|
|
7442
7485
|
this.iceRestartDelay = iceRestartDelay;
|
|
7443
7486
|
this.clientPublishOptions = clientPublishOptions;
|
|
7487
|
+
this.tag = tag;
|
|
7444
7488
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
7445
7489
|
this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
|
|
7446
7490
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
@@ -8443,6 +8487,21 @@ const getTimers = lazy(() => {
|
|
|
8443
8487
|
return new WorkerTimer({ useWorker: timerWorkerEnabled });
|
|
8444
8488
|
});
|
|
8445
8489
|
|
|
8490
|
+
class SfuJoinError extends Error {
|
|
8491
|
+
constructor(event) {
|
|
8492
|
+
super(event.error?.message || 'Join Error');
|
|
8493
|
+
this.errorEvent = event;
|
|
8494
|
+
this.unrecoverable =
|
|
8495
|
+
event.reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT;
|
|
8496
|
+
}
|
|
8497
|
+
static isJoinErrorCode(event) {
|
|
8498
|
+
const code = event.error?.code;
|
|
8499
|
+
return (code === ErrorCode.SFU_FULL ||
|
|
8500
|
+
code === ErrorCode.SFU_SHUTTING_DOWN ||
|
|
8501
|
+
code === ErrorCode.CALL_PARTICIPANT_LIMIT_REACHED);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
|
|
8446
8505
|
/**
|
|
8447
8506
|
* The client used for exchanging information with the SFU.
|
|
8448
8507
|
*/
|
|
@@ -8619,7 +8678,7 @@ class StreamSfuClient {
|
|
|
8619
8678
|
const { timeout = 7 * 1000 } = opts;
|
|
8620
8679
|
this.migrationTask?.reject(new Error('Cancelled previous migration'));
|
|
8621
8680
|
const task = (this.migrationTask = promiseWithResolvers());
|
|
8622
|
-
const unsubscribe = this.dispatcher.on('participantMigrationComplete', () => {
|
|
8681
|
+
const unsubscribe = this.dispatcher.on('participantMigrationComplete', this.tag, () => {
|
|
8623
8682
|
unsubscribe();
|
|
8624
8683
|
clearTimeout(this.migrateAwayTimeout);
|
|
8625
8684
|
task.resolve();
|
|
@@ -8645,27 +8704,29 @@ class StreamSfuClient {
|
|
|
8645
8704
|
// be replaced with a new one in case a second join request is made
|
|
8646
8705
|
const current = this.joinResponseTask;
|
|
8647
8706
|
let timeoutId = undefined;
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8707
|
+
let unsubscribeJoinResponse = undefined;
|
|
8708
|
+
let unsubscribeJoinErrorEvents = undefined;
|
|
8709
|
+
const cleanupJoinSubscriptions = () => {
|
|
8710
|
+
clearTimeout(timeoutId);
|
|
8711
|
+
timeoutId = undefined;
|
|
8712
|
+
unsubscribeJoinErrorEvents?.();
|
|
8713
|
+
unsubscribeJoinErrorEvents = undefined;
|
|
8714
|
+
unsubscribeJoinResponse?.();
|
|
8715
|
+
unsubscribeJoinResponse = undefined;
|
|
8716
|
+
};
|
|
8717
|
+
unsubscribeJoinErrorEvents = this.dispatcher.on('error', this.tag, (event) => {
|
|
8718
|
+
if (SfuJoinError.isJoinErrorCode(event)) {
|
|
8719
|
+
cleanupJoinSubscriptions();
|
|
8656
8720
|
current.reject(new SfuJoinError(event));
|
|
8657
8721
|
}
|
|
8658
8722
|
});
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
unsubscribe();
|
|
8662
|
-
unsubscribeJoinErrorEvents();
|
|
8723
|
+
unsubscribeJoinResponse = this.dispatcher.on('joinResponse', this.tag, (joinResponse) => {
|
|
8724
|
+
cleanupJoinSubscriptions();
|
|
8663
8725
|
this.keepAlive();
|
|
8664
8726
|
current.resolve(joinResponse);
|
|
8665
8727
|
});
|
|
8666
8728
|
timeoutId = setTimeout(() => {
|
|
8667
|
-
|
|
8668
|
-
unsubscribeJoinErrorEvents();
|
|
8729
|
+
cleanupJoinSubscriptions();
|
|
8669
8730
|
const message = `Waiting for "joinResponse" has timed out after ${this.joinResponseTimeout}ms`;
|
|
8670
8731
|
this.tracer?.trace('joinRequestTimeout', message);
|
|
8671
8732
|
current.reject(new Error(message));
|
|
@@ -8760,8 +8821,8 @@ class StreamSfuClient {
|
|
|
8760
8821
|
// In that case, those events (ICE candidates) need to be buffered
|
|
8761
8822
|
// and later added to the appropriate PeerConnection
|
|
8762
8823
|
// once the remoteDescription is known and set.
|
|
8763
|
-
this.unsubscribeIceTrickle = dispatcher.on('iceTrickle', (
|
|
8764
|
-
this.iceTrickleBuffer.push(
|
|
8824
|
+
this.unsubscribeIceTrickle = dispatcher.on('iceTrickle', tag, (t) => {
|
|
8825
|
+
this.iceTrickleBuffer.push(t);
|
|
8765
8826
|
});
|
|
8766
8827
|
// listen to network changes to handle offline state
|
|
8767
8828
|
// we shouldn't attempt to recover websocket connection when offline
|
|
@@ -8810,14 +8871,6 @@ StreamSfuClient.DISPOSE_OLD_SOCKET = 4100;
|
|
|
8810
8871
|
* The close code used when the client fails to join the call (on the SFU).
|
|
8811
8872
|
*/
|
|
8812
8873
|
StreamSfuClient.JOIN_FAILED = 4101;
|
|
8813
|
-
class SfuJoinError extends Error {
|
|
8814
|
-
constructor(event) {
|
|
8815
|
-
super(event.error?.message || 'Join Error');
|
|
8816
|
-
this.errorEvent = event;
|
|
8817
|
-
this.unrecoverable =
|
|
8818
|
-
event.reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT;
|
|
8819
|
-
}
|
|
8820
|
-
}
|
|
8821
8874
|
|
|
8822
8875
|
/**
|
|
8823
8876
|
* Event handler that watched the delivery of `call.accepted`.
|
|
@@ -8966,7 +9019,7 @@ const removeFromIfPresent = (arr, ...values) => {
|
|
|
8966
9019
|
};
|
|
8967
9020
|
|
|
8968
9021
|
const watchConnectionQualityChanged = (dispatcher, state) => {
|
|
8969
|
-
return dispatcher.on('connectionQualityChanged', (e) => {
|
|
9022
|
+
return dispatcher.on('connectionQualityChanged', '*', (e) => {
|
|
8970
9023
|
const { connectionQualityUpdates } = e;
|
|
8971
9024
|
if (!connectionQualityUpdates)
|
|
8972
9025
|
return;
|
|
@@ -8984,7 +9037,7 @@ const watchConnectionQualityChanged = (dispatcher, state) => {
|
|
|
8984
9037
|
* health check events that our SFU sends.
|
|
8985
9038
|
*/
|
|
8986
9039
|
const watchParticipantCountChanged = (dispatcher, state) => {
|
|
8987
|
-
return dispatcher.on('healthCheckResponse', (e) => {
|
|
9040
|
+
return dispatcher.on('healthCheckResponse', '*', (e) => {
|
|
8988
9041
|
const { participantCount } = e;
|
|
8989
9042
|
if (participantCount) {
|
|
8990
9043
|
state.setParticipantCount(participantCount.total);
|
|
@@ -8993,7 +9046,7 @@ const watchParticipantCountChanged = (dispatcher, state) => {
|
|
|
8993
9046
|
});
|
|
8994
9047
|
};
|
|
8995
9048
|
const watchLiveEnded = (dispatcher, call) => {
|
|
8996
|
-
return dispatcher.on('error', (e) => {
|
|
9049
|
+
return dispatcher.on('error', '*', (e) => {
|
|
8997
9050
|
if (e.error && e.error.code !== ErrorCode.LIVE_ENDED)
|
|
8998
9051
|
return;
|
|
8999
9052
|
call.state.setBackstage(true);
|
|
@@ -9008,7 +9061,7 @@ const watchLiveEnded = (dispatcher, call) => {
|
|
|
9008
9061
|
* Watches and logs the errors reported by the currently connected SFU.
|
|
9009
9062
|
*/
|
|
9010
9063
|
const watchSfuErrorReports = (dispatcher) => {
|
|
9011
|
-
return dispatcher.on('error', (e) => {
|
|
9064
|
+
return dispatcher.on('error', '*', (e) => {
|
|
9012
9065
|
if (!e.error)
|
|
9013
9066
|
return;
|
|
9014
9067
|
const logger = videoLoggerSystem.getLogger('SfuClient');
|
|
@@ -9207,7 +9260,7 @@ const reconcileOrphanedTracks = (state, participant) => {
|
|
|
9207
9260
|
* Watches for `dominantSpeakerChanged` events.
|
|
9208
9261
|
*/
|
|
9209
9262
|
const watchDominantSpeakerChanged = (dispatcher, state) => {
|
|
9210
|
-
return dispatcher.on('dominantSpeakerChanged', (e) => {
|
|
9263
|
+
return dispatcher.on('dominantSpeakerChanged', '*', (e) => {
|
|
9211
9264
|
const { sessionId } = e;
|
|
9212
9265
|
if (sessionId === state.dominantSpeaker?.sessionId)
|
|
9213
9266
|
return;
|
|
@@ -9234,7 +9287,7 @@ const watchDominantSpeakerChanged = (dispatcher, state) => {
|
|
|
9234
9287
|
* Watches for `audioLevelChanged` events.
|
|
9235
9288
|
*/
|
|
9236
9289
|
const watchAudioLevelChanged = (dispatcher, state) => {
|
|
9237
|
-
return dispatcher.on('audioLevelChanged', (e) => {
|
|
9290
|
+
return dispatcher.on('audioLevelChanged', '*', (e) => {
|
|
9238
9291
|
const { audioLevels } = e;
|
|
9239
9292
|
state.updateParticipants(audioLevels.reduce((patches, current) => {
|
|
9240
9293
|
patches[current.sessionId] = {
|
|
@@ -12394,6 +12447,7 @@ class Call {
|
|
|
12394
12447
|
this.hasJoinedOnce = false;
|
|
12395
12448
|
this.deviceSettingsAppliedOnce = false;
|
|
12396
12449
|
this.initialized = false;
|
|
12450
|
+
this.acceptRejectConcurrencyTag = Symbol('acceptRejectTag');
|
|
12397
12451
|
this.joinLeaveConcurrencyTag = Symbol('joinLeaveConcurrencyTag');
|
|
12398
12452
|
/**
|
|
12399
12453
|
* A list hooks/functions to invoke when the call is left.
|
|
@@ -12590,7 +12644,7 @@ class Call {
|
|
|
12590
12644
|
*/
|
|
12591
12645
|
this.on = (eventName, fn) => {
|
|
12592
12646
|
if (isSfuEvent(eventName)) {
|
|
12593
|
-
return this.dispatcher.on(eventName, fn);
|
|
12647
|
+
return this.dispatcher.on(eventName, '*', fn);
|
|
12594
12648
|
}
|
|
12595
12649
|
const offHandler = this.streamClient.on(eventName, (e) => {
|
|
12596
12650
|
const event = e;
|
|
@@ -12612,7 +12666,7 @@ class Call {
|
|
|
12612
12666
|
*/
|
|
12613
12667
|
this.off = (eventName, fn) => {
|
|
12614
12668
|
if (isSfuEvent(eventName)) {
|
|
12615
|
-
return this.dispatcher.off(eventName, fn);
|
|
12669
|
+
return this.dispatcher.off(eventName, '*', fn);
|
|
12616
12670
|
}
|
|
12617
12671
|
// unsubscribe from the stream client event by using the 'off' reference
|
|
12618
12672
|
const registeredOffHandler = this.streamClientEventHandlers.get(fn);
|
|
@@ -12808,8 +12862,10 @@ class Call {
|
|
|
12808
12862
|
* Unless you are implementing a custom "ringing" flow, you should not use this method.
|
|
12809
12863
|
*/
|
|
12810
12864
|
this.accept = async () => {
|
|
12811
|
-
this.
|
|
12812
|
-
|
|
12865
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12866
|
+
this.tracer.trace('call.accept', '');
|
|
12867
|
+
return this.streamClient.post(`${this.streamClientBasePath}/accept`);
|
|
12868
|
+
});
|
|
12813
12869
|
};
|
|
12814
12870
|
/**
|
|
12815
12871
|
* Marks the incoming call as rejected.
|
|
@@ -12821,8 +12877,10 @@ class Call {
|
|
|
12821
12877
|
* @param reason the reason for rejecting the call.
|
|
12822
12878
|
*/
|
|
12823
12879
|
this.reject = async (reason = 'decline') => {
|
|
12824
|
-
this.
|
|
12825
|
-
|
|
12880
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12881
|
+
this.tracer.trace('call.reject', reason);
|
|
12882
|
+
return this.streamClient.post(`${this.streamClientBasePath}/reject`, { reason });
|
|
12883
|
+
});
|
|
12826
12884
|
};
|
|
12827
12885
|
/**
|
|
12828
12886
|
* Will start to watch for call related WebSocket events and initiate a call session with the server.
|
|
@@ -12848,6 +12906,7 @@ class Call {
|
|
|
12848
12906
|
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
12849
12907
|
await this.doJoin(data);
|
|
12850
12908
|
delete joinData.migrating_from;
|
|
12909
|
+
delete joinData.migrating_from_list;
|
|
12851
12910
|
break;
|
|
12852
12911
|
}
|
|
12853
12912
|
catch (err) {
|
|
@@ -12859,11 +12918,15 @@ class Call {
|
|
|
12859
12918
|
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
12860
12919
|
throw err;
|
|
12861
12920
|
}
|
|
12921
|
+
// immediately switch to a different SFU in case of recoverable join error
|
|
12922
|
+
const switchSfu = err instanceof SfuJoinError &&
|
|
12923
|
+
SfuJoinError.isJoinErrorCode(err.errorEvent);
|
|
12862
12924
|
const sfuId = this.credentials?.server.edge_name || '';
|
|
12863
12925
|
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
12864
12926
|
sfuJoinFailures.set(sfuId, failures);
|
|
12865
|
-
if (failures >= 2) {
|
|
12927
|
+
if (switchSfu || failures >= 2) {
|
|
12866
12928
|
joinData.migrating_from = sfuId;
|
|
12929
|
+
joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
|
|
12867
12930
|
}
|
|
12868
12931
|
if (attempt === maxJoinRetries - 1) {
|
|
12869
12932
|
throw err;
|
|
@@ -13391,12 +13454,17 @@ class Call {
|
|
|
13391
13454
|
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
|
|
13392
13455
|
try {
|
|
13393
13456
|
const currentSfu = currentSfuClient.edgeName;
|
|
13394
|
-
await this.doJoin({
|
|
13457
|
+
await this.doJoin({
|
|
13458
|
+
...this.joinCallData,
|
|
13459
|
+
migrating_from: currentSfu,
|
|
13460
|
+
migrating_from_list: [currentSfu],
|
|
13461
|
+
});
|
|
13395
13462
|
}
|
|
13396
13463
|
finally {
|
|
13397
13464
|
// cleanup the migration_from field after the migration is complete or failed
|
|
13398
13465
|
// as we don't want to keep dirty data in the join call data
|
|
13399
13466
|
delete this.joinCallData?.migrating_from;
|
|
13467
|
+
delete this.joinCallData?.migrating_from_list;
|
|
13400
13468
|
}
|
|
13401
13469
|
await this.restorePublishedTracks();
|
|
13402
13470
|
this.restoreSubscribedTracks();
|
|
@@ -13431,6 +13499,11 @@ class Call {
|
|
|
13431
13499
|
// handles the "error" event, through which the SFU can request a reconnect
|
|
13432
13500
|
const unregisterOnError = this.on('error', (e) => {
|
|
13433
13501
|
const { reconnectStrategy: strategy, error } = e;
|
|
13502
|
+
// SFU_FULL is a join error, and when emitted, although it specifies a
|
|
13503
|
+
// `migrate` strategy, we should actually perform a REJOIN to a new SFU.
|
|
13504
|
+
// This is now handled separately in the `call.join()` method.
|
|
13505
|
+
if (SfuJoinError.isJoinErrorCode(e))
|
|
13506
|
+
return;
|
|
13434
13507
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
13435
13508
|
return;
|
|
13436
13509
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
@@ -15527,7 +15600,7 @@ class StreamClient {
|
|
|
15527
15600
|
this.getUserAgent = () => {
|
|
15528
15601
|
if (!this.cachedUserAgent) {
|
|
15529
15602
|
const { clientAppIdentifier = {} } = this.options;
|
|
15530
|
-
const { sdkName = 'js', sdkVersion = "1.42.
|
|
15603
|
+
const { sdkName = 'js', sdkVersion = "1.42.3", ...extras } = clientAppIdentifier;
|
|
15531
15604
|
this.cachedUserAgent = [
|
|
15532
15605
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15533
15606
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|