@stream-io/video-client 1.11.6 → 1.11.7
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 +7 -0
- package/dist/index.browser.es.js +68 -562
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +53 -547
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +68 -562
- package/dist/index.es.js.map +1 -1
- package/dist/src/coordinator/connection/client.d.ts +0 -18
- package/dist/src/coordinator/connection/connection.d.ts +4 -12
- package/dist/src/coordinator/connection/signing.d.ts +1 -7
- package/dist/src/coordinator/connection/token_manager.d.ts +0 -2
- package/dist/src/coordinator/connection/types.d.ts +5 -6
- package/dist/src/coordinator/connection/utils.d.ts +6 -8
- package/package.json +6 -10
- package/src/__tests__/Call.test.ts +3 -2
- package/src/coordinator/connection/client.ts +12 -149
- package/src/coordinator/connection/connection.ts +40 -109
- package/src/coordinator/connection/signing.ts +31 -17
- package/src/coordinator/connection/token_manager.ts +3 -9
- package/src/coordinator/connection/types.ts +5 -9
- package/src/coordinator/connection/utils.ts +18 -50
- package/src/devices/__tests__/InputMediaDeviceManagerState.test.ts +13 -8
- package/src/devices/__tests__/mocks.ts +0 -4
- package/dist/src/coordinator/connection/base64.d.ts +0 -2
- package/dist/src/coordinator/connection/connection_fallback.d.ts +0 -39
- package/dist/src/coordinator/connection/errors.d.ts +0 -16
- package/dist/src/coordinator/connection/insights.d.ts +0 -57
- package/src/coordinator/connection/base64.ts +0 -80
- package/src/coordinator/connection/connection_fallback.ts +0 -242
- package/src/coordinator/connection/errors.ts +0 -80
- package/src/coordinator/connection/insights.ts +0 -88
package/dist/index.es.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import 'webrtc-adapter';
|
|
2
2
|
import { MessageType, isJsonObject, typeofJsonValue, PbLong } from '@protobuf-ts/runtime';
|
|
3
3
|
import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc';
|
|
4
|
-
import axios
|
|
4
|
+
import axios from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
7
|
import { UAParser } from 'ua-parser-js';
|
|
8
|
-
import { ReplaySubject, combineLatest, BehaviorSubject, map
|
|
8
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, map, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, from, fromEvent, debounceTime, merge, pairwise, of } from 'rxjs';
|
|
9
9
|
import * as SDP from 'sdp-transform';
|
|
10
10
|
import https from 'https';
|
|
11
|
-
import WebSocket$1 from 'isomorphic-ws';
|
|
12
|
-
import { fromByteArray } from 'base64-js';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* @export
|
|
@@ -3175,42 +3173,6 @@ function getRandomBytes(length) {
|
|
|
3175
3173
|
getRandomValues(bytes);
|
|
3176
3174
|
return bytes;
|
|
3177
3175
|
}
|
|
3178
|
-
function convertErrorToJson(err) {
|
|
3179
|
-
const jsonObj = {};
|
|
3180
|
-
if (!err)
|
|
3181
|
-
return jsonObj;
|
|
3182
|
-
try {
|
|
3183
|
-
Object.getOwnPropertyNames(err).forEach((key) => {
|
|
3184
|
-
jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
catch (_) {
|
|
3188
|
-
return {
|
|
3189
|
-
error: 'failed to serialize the error',
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
|
-
return jsonObj;
|
|
3193
|
-
}
|
|
3194
|
-
/**
|
|
3195
|
-
* isOnline safely return the navigator.online value for browser env
|
|
3196
|
-
* if navigator is not in global object, it always return true
|
|
3197
|
-
*/
|
|
3198
|
-
function isOnline(logger) {
|
|
3199
|
-
const nav = typeof navigator !== 'undefined'
|
|
3200
|
-
? navigator
|
|
3201
|
-
: typeof window !== 'undefined' && window.navigator
|
|
3202
|
-
? window.navigator
|
|
3203
|
-
: undefined;
|
|
3204
|
-
if (!nav) {
|
|
3205
|
-
logger('warn', 'isOnline failed to access window.navigator and assume browser is online');
|
|
3206
|
-
return true;
|
|
3207
|
-
}
|
|
3208
|
-
// RN navigator has undefined for onLine
|
|
3209
|
-
if (typeof nav.onLine !== 'boolean') {
|
|
3210
|
-
return true;
|
|
3211
|
-
}
|
|
3212
|
-
return nav.onLine;
|
|
3213
|
-
}
|
|
3214
3176
|
/**
|
|
3215
3177
|
* listenForConnectionChanges - Adds an event listener fired on browser going online or offline
|
|
3216
3178
|
*/
|
|
@@ -3226,6 +3188,13 @@ function removeConnectionEventListeners(cb) {
|
|
|
3226
3188
|
window.removeEventListener('online', cb);
|
|
3227
3189
|
}
|
|
3228
3190
|
}
|
|
3191
|
+
function isErrorResponse(res) {
|
|
3192
|
+
return !res.status || res.status < 200 || 300 <= res.status;
|
|
3193
|
+
}
|
|
3194
|
+
// Type guards to check WebSocket error type
|
|
3195
|
+
function isCloseEvent(res) {
|
|
3196
|
+
return res.code !== undefined;
|
|
3197
|
+
}
|
|
3229
3198
|
|
|
3230
3199
|
/**
|
|
3231
3200
|
* Checks whether we are using React Native
|
|
@@ -3329,7 +3298,7 @@ const retryable = async (rpc, signal) => {
|
|
|
3329
3298
|
return result;
|
|
3330
3299
|
};
|
|
3331
3300
|
|
|
3332
|
-
const version = "1.11.
|
|
3301
|
+
const version = "1.11.7";
|
|
3333
3302
|
const [major, minor, patch] = version.split('.');
|
|
3334
3303
|
let sdkInfo = {
|
|
3335
3304
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5089,12 +5058,12 @@ class CallState {
|
|
|
5089
5058
|
this.participants$ = this.participantsSubject.asObservable().pipe(
|
|
5090
5059
|
// maintain stable-sort by mutating the participants stored
|
|
5091
5060
|
// in the original subject
|
|
5092
|
-
map
|
|
5093
|
-
this.localParticipant$ = this.participants$.pipe(map
|
|
5094
|
-
this.remoteParticipants$ = this.participants$.pipe(map
|
|
5095
|
-
this.pinnedParticipants$ = this.participants$.pipe(map
|
|
5096
|
-
this.dominantSpeaker$ = this.participants$.pipe(map
|
|
5097
|
-
this.hasOngoingScreenShare$ = this.participants$.pipe(map
|
|
5061
|
+
map((ps) => ps.sort(this.sortParticipantsBy)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5062
|
+
this.localParticipant$ = this.participants$.pipe(map((participants) => participants.find((p) => p.isLocalParticipant)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5063
|
+
this.remoteParticipants$ = this.participants$.pipe(map((participants) => participants.filter((p) => !p.isLocalParticipant)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5064
|
+
this.pinnedParticipants$ = this.participants$.pipe(map((participants) => participants.filter((p) => !!p.pin)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5065
|
+
this.dominantSpeaker$ = this.participants$.pipe(map((participants) => participants.find((p) => p.isDominantSpeaker)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5066
|
+
this.hasOngoingScreenShare$ = this.participants$.pipe(map((participants) => participants.some((p) => hasScreenShare(p))), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5098
5067
|
// dates
|
|
5099
5068
|
this.createdAt$ = this.createdAtSubject.asObservable();
|
|
5100
5069
|
this.endedAt$ = this.endedAtSubject.asObservable();
|
|
@@ -7629,7 +7598,7 @@ class DynascaleManager {
|
|
|
7629
7598
|
this.pendingSubscriptionsUpdate = null;
|
|
7630
7599
|
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
7631
7600
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
7632
|
-
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(map
|
|
7601
|
+
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(map((overrides) => {
|
|
7633
7602
|
const { [globalOverrideKey]: globalSettings, ...participants } = overrides;
|
|
7634
7603
|
return {
|
|
7635
7604
|
enabled: globalSettings?.enabled !== false,
|
|
@@ -7765,7 +7734,7 @@ class DynascaleManager {
|
|
|
7765
7734
|
});
|
|
7766
7735
|
this.applyTrackSubscriptions(debounceType);
|
|
7767
7736
|
};
|
|
7768
|
-
const participant$ = this.callState.participants$.pipe(map
|
|
7737
|
+
const participant$ = this.callState.participants$.pipe(map((participants) => participants.find((participant) => participant.sessionId === sessionId)), takeWhile((participant) => !!participant), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
7769
7738
|
/**
|
|
7770
7739
|
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
7771
7740
|
* visibility change, this subscription is not in use an stays here only for the
|
|
@@ -7777,7 +7746,7 @@ class DynascaleManager {
|
|
|
7777
7746
|
const viewportVisibilityStateSubscription = boundParticipant.isLocalParticipant
|
|
7778
7747
|
? null
|
|
7779
7748
|
: participant$
|
|
7780
|
-
.pipe(map
|
|
7749
|
+
.pipe(map((p) => p.viewportVisibilityState?.[trackType]), distinctUntilChanged())
|
|
7781
7750
|
.subscribe((nextViewportVisibilityState) => {
|
|
7782
7751
|
// skip initial trigger
|
|
7783
7752
|
if (!viewportVisibilityState) {
|
|
@@ -7821,7 +7790,7 @@ class DynascaleManager {
|
|
|
7821
7790
|
const publishedTracksSubscription = boundParticipant.isLocalParticipant
|
|
7822
7791
|
? null
|
|
7823
7792
|
: participant$
|
|
7824
|
-
.pipe(distinctUntilKeyChanged('publishedTracks'), map
|
|
7793
|
+
.pipe(distinctUntilKeyChanged('publishedTracks'), map((p) => trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p)), distinctUntilChanged())
|
|
7825
7794
|
.subscribe((isPublishing) => {
|
|
7826
7795
|
if (isPublishing) {
|
|
7827
7796
|
// the participant just started to publish a track
|
|
@@ -7883,7 +7852,7 @@ class DynascaleManager {
|
|
|
7883
7852
|
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
7884
7853
|
if (!participant || participant.isLocalParticipant)
|
|
7885
7854
|
return;
|
|
7886
|
-
const participant$ = this.callState.participants$.pipe(map
|
|
7855
|
+
const participant$ = this.callState.participants$.pipe(map((participants) => participants.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
7887
7856
|
const updateMediaStreamSubscription = participant$
|
|
7888
7857
|
.pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
|
|
7889
7858
|
? 'screenShareAudioStream'
|
|
@@ -8207,7 +8176,7 @@ class BrowserPermission {
|
|
|
8207
8176
|
// permissionState stays in 'prompt' state forever.
|
|
8208
8177
|
// Typically, this happens when a user grants one-time permission.
|
|
8209
8178
|
// Instead of checking if a permission is granted, we check if it isn't denied
|
|
8210
|
-
map
|
|
8179
|
+
map((state) => state !== 'denied'));
|
|
8211
8180
|
}
|
|
8212
8181
|
setState(state) {
|
|
8213
8182
|
if (this.state !== state) {
|
|
@@ -8308,7 +8277,7 @@ const getDeviceChangeObserver = lazy(() => {
|
|
|
8308
8277
|
// an observable that will never fire
|
|
8309
8278
|
if (!navigator.mediaDevices.addEventListener)
|
|
8310
8279
|
return from([]);
|
|
8311
|
-
return fromEvent(navigator.mediaDevices, 'devicechange').pipe(map
|
|
8280
|
+
return fromEvent(navigator.mediaDevices, 'devicechange').pipe(map(() => undefined), debounceTime(500));
|
|
8312
8281
|
});
|
|
8313
8282
|
/**
|
|
8314
8283
|
* Prompts the user for a permission to use audio devices (if not already granted
|
|
@@ -11501,68 +11470,6 @@ class Call {
|
|
|
11501
11470
|
}
|
|
11502
11471
|
}
|
|
11503
11472
|
|
|
11504
|
-
class InsightMetrics {
|
|
11505
|
-
constructor() {
|
|
11506
|
-
this.connectionStartTimestamp = null;
|
|
11507
|
-
this.wsTotalFailures = 0;
|
|
11508
|
-
this.wsConsecutiveFailures = 0;
|
|
11509
|
-
this.instanceClientId = randomId();
|
|
11510
|
-
}
|
|
11511
|
-
}
|
|
11512
|
-
/**
|
|
11513
|
-
* postInsights is not supposed to be used by end users directly within chat application, and thus is kept isolated
|
|
11514
|
-
* from all the client/connection code/logic.
|
|
11515
|
-
*
|
|
11516
|
-
* @param insightType
|
|
11517
|
-
* @param insights
|
|
11518
|
-
*/
|
|
11519
|
-
const postInsights = async (insightType, insights) => {
|
|
11520
|
-
const maxAttempts = 3;
|
|
11521
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
11522
|
-
try {
|
|
11523
|
-
await axios.post(`https://chat-insights.getstream.io/insights/${insightType}`, insights);
|
|
11524
|
-
}
|
|
11525
|
-
catch (e) {
|
|
11526
|
-
await sleep((i + 1) * 3000);
|
|
11527
|
-
continue;
|
|
11528
|
-
}
|
|
11529
|
-
break;
|
|
11530
|
-
}
|
|
11531
|
-
};
|
|
11532
|
-
function buildWsFatalInsight(connection, event) {
|
|
11533
|
-
return {
|
|
11534
|
-
...event,
|
|
11535
|
-
...buildWsBaseInsight(connection),
|
|
11536
|
-
};
|
|
11537
|
-
}
|
|
11538
|
-
function buildWsBaseInsight(connection) {
|
|
11539
|
-
const { client } = connection;
|
|
11540
|
-
return {
|
|
11541
|
-
ready_state: connection.ws?.readyState,
|
|
11542
|
-
url: connection._buildUrl(),
|
|
11543
|
-
api_key: client.key,
|
|
11544
|
-
start_ts: client.insightMetrics.connectionStartTimestamp,
|
|
11545
|
-
end_ts: new Date().getTime(),
|
|
11546
|
-
auth_type: client.getAuthType(),
|
|
11547
|
-
token: client.tokenManager.token,
|
|
11548
|
-
user_id: client.userID,
|
|
11549
|
-
user_details: client._user,
|
|
11550
|
-
// device: client.options.device,
|
|
11551
|
-
device: 'browser',
|
|
11552
|
-
client_id: connection.connectionID,
|
|
11553
|
-
ws_details: connection.ws,
|
|
11554
|
-
ws_consecutive_failures: client.insightMetrics.wsConsecutiveFailures,
|
|
11555
|
-
ws_total_failures: client.insightMetrics.wsTotalFailures,
|
|
11556
|
-
request_id: connection.requestID,
|
|
11557
|
-
online: typeof navigator !== 'undefined' ? navigator?.onLine : null,
|
|
11558
|
-
user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null,
|
|
11559
|
-
instance_client_id: client.insightMetrics.instanceClientId,
|
|
11560
|
-
};
|
|
11561
|
-
}
|
|
11562
|
-
function buildWsSuccessAfterFailureInsight(connection) {
|
|
11563
|
-
return buildWsBaseInsight(connection);
|
|
11564
|
-
}
|
|
11565
|
-
|
|
11566
11473
|
/**
|
|
11567
11474
|
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
11568
11475
|
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
@@ -11590,9 +11497,6 @@ function makeSafePromise(promise) {
|
|
|
11590
11497
|
return unwrapPromise;
|
|
11591
11498
|
}
|
|
11592
11499
|
|
|
11593
|
-
// Type guards to check WebSocket error type
|
|
11594
|
-
const isCloseEvent = (res) => res.code !== undefined;
|
|
11595
|
-
const isErrorEvent = (res) => res.error !== undefined;
|
|
11596
11500
|
/**
|
|
11597
11501
|
* StableWSConnection - A WS connection that reconnects upon failure.
|
|
11598
11502
|
* - the browser will sometimes report that you're online or offline
|
|
@@ -11627,12 +11531,9 @@ class StableWSConnection {
|
|
|
11627
11531
|
*/
|
|
11628
11532
|
this._buildUrl = () => {
|
|
11629
11533
|
const params = new URLSearchParams();
|
|
11630
|
-
// const qs = encodeURIComponent(this.client._buildWSPayload(this.requestID));
|
|
11631
|
-
// params.set('json', qs);
|
|
11632
11534
|
params.set('api_key', this.client.key);
|
|
11633
11535
|
params.set('stream-auth-type', this.client.getAuthType());
|
|
11634
11536
|
params.set('X-Stream-Client', this.client.getUserAgent());
|
|
11635
|
-
// params.append('authorization', this.client._getToken()!);
|
|
11636
11537
|
return `${this.client.wsBaseURL}/connect?${params.toString()}`;
|
|
11637
11538
|
};
|
|
11638
11539
|
/**
|
|
@@ -11681,7 +11582,6 @@ class StableWSConnection {
|
|
|
11681
11582
|
custom: user.custom,
|
|
11682
11583
|
},
|
|
11683
11584
|
};
|
|
11684
|
-
this.authenticationSent = true;
|
|
11685
11585
|
this.ws?.send(JSON.stringify(authMessage));
|
|
11686
11586
|
this._log('onopen() - onopen callback', { wsID });
|
|
11687
11587
|
};
|
|
@@ -11698,7 +11598,6 @@ class StableWSConnection {
|
|
|
11698
11598
|
if (!this.isResolved && data && data.type === 'connection.error') {
|
|
11699
11599
|
this.isResolved = true;
|
|
11700
11600
|
if (data.error) {
|
|
11701
|
-
// @ts-expect-error - the types of _errorFromWSEvent are incorrect
|
|
11702
11601
|
this.rejectPromise?.(this._errorFromWSEvent(data, false));
|
|
11703
11602
|
return;
|
|
11704
11603
|
}
|
|
@@ -11740,9 +11639,13 @@ class StableWSConnection {
|
|
|
11740
11639
|
// this is a permanent error raised by stream..
|
|
11741
11640
|
// usually caused by invalid auth details
|
|
11742
11641
|
const error = new Error(`WS connection reject with error ${event.reason}`);
|
|
11642
|
+
// @ts-expect-error
|
|
11743
11643
|
error.reason = event.reason;
|
|
11644
|
+
// @ts-expect-error
|
|
11744
11645
|
error.code = event.code;
|
|
11646
|
+
// @ts-expect-error
|
|
11745
11647
|
error.wasClean = event.wasClean;
|
|
11648
|
+
// @ts-expect-error
|
|
11746
11649
|
error.target = event.target;
|
|
11747
11650
|
this.rejectPromise?.(error);
|
|
11748
11651
|
this._log(`onclose() - WS connection reject with error ${event.reason}`, {
|
|
@@ -11769,7 +11672,7 @@ class StableWSConnection {
|
|
|
11769
11672
|
this.totalFailures += 1;
|
|
11770
11673
|
this._setHealth(false);
|
|
11771
11674
|
this.isConnecting = false;
|
|
11772
|
-
this.rejectPromise?.(
|
|
11675
|
+
this.rejectPromise?.(new Error(`WebSocket error: ${event}`));
|
|
11773
11676
|
this._log(`onerror() - WS connection resulted into error`, { event });
|
|
11774
11677
|
this._reconnect();
|
|
11775
11678
|
};
|
|
@@ -11779,7 +11682,6 @@ class StableWSConnection {
|
|
|
11779
11682
|
*
|
|
11780
11683
|
* @param {boolean} healthy boolean indicating if the connection is healthy or not
|
|
11781
11684
|
* @param {boolean} dispatchImmediately boolean indicating to dispatch event immediately even if the connection is unhealthy
|
|
11782
|
-
*
|
|
11783
11685
|
*/
|
|
11784
11686
|
this._setHealth = (healthy, dispatchImmediately = false) => {
|
|
11785
11687
|
if (healthy === this.isHealthy)
|
|
@@ -11804,7 +11706,6 @@ class StableWSConnection {
|
|
|
11804
11706
|
};
|
|
11805
11707
|
/**
|
|
11806
11708
|
* _errorFromWSEvent - Creates an error object for the WS event
|
|
11807
|
-
*
|
|
11808
11709
|
*/
|
|
11809
11710
|
this._errorFromWSEvent = (event, isWSFailure = true) => {
|
|
11810
11711
|
let code;
|
|
@@ -11812,17 +11713,18 @@ class StableWSConnection {
|
|
|
11812
11713
|
let message;
|
|
11813
11714
|
if (isCloseEvent(event)) {
|
|
11814
11715
|
code = event.code;
|
|
11815
|
-
statusCode = 'unknown';
|
|
11816
11716
|
message = event.reason;
|
|
11717
|
+
statusCode = 0;
|
|
11817
11718
|
}
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
message =
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11719
|
+
else {
|
|
11720
|
+
const { error } = event;
|
|
11721
|
+
code = error.code;
|
|
11722
|
+
message = error.message;
|
|
11723
|
+
statusCode = error.StatusCode;
|
|
11724
|
+
}
|
|
11725
|
+
const msg = `WS failed with code: ${code} and reason: ${message}`;
|
|
11726
|
+
this._log(msg, { event }, 'warn');
|
|
11727
|
+
const error = new Error(msg);
|
|
11826
11728
|
error.code = code;
|
|
11827
11729
|
/**
|
|
11828
11730
|
* StatusCode does not exist on any event types but has been left
|
|
@@ -11847,10 +11749,8 @@ class StableWSConnection {
|
|
|
11847
11749
|
* Schedules a next health check ping for websocket.
|
|
11848
11750
|
*/
|
|
11849
11751
|
this.scheduleNextPing = () => {
|
|
11850
|
-
if (this.healthCheckTimeoutRef) {
|
|
11851
|
-
clearTimeout(this.healthCheckTimeoutRef);
|
|
11852
|
-
}
|
|
11853
11752
|
// 30 seconds is the recommended interval (messenger uses this)
|
|
11753
|
+
clearTimeout(this.healthCheckTimeoutRef);
|
|
11854
11754
|
this.healthCheckTimeoutRef = setTimeout(() => {
|
|
11855
11755
|
// send the healthcheck..., server replies with a health check event
|
|
11856
11756
|
const data = [{ type: 'health.check', client_id: this.client.clientID }];
|
|
@@ -11869,9 +11769,7 @@ class StableWSConnection {
|
|
|
11869
11769
|
* to be reconnected.
|
|
11870
11770
|
*/
|
|
11871
11771
|
this.scheduleConnectionCheck = () => {
|
|
11872
|
-
|
|
11873
|
-
clearTimeout(this.connectionCheckTimeoutRef);
|
|
11874
|
-
}
|
|
11772
|
+
clearTimeout(this.connectionCheckTimeoutRef);
|
|
11875
11773
|
this.connectionCheckTimeoutRef = setTimeout(() => {
|
|
11876
11774
|
const now = new Date();
|
|
11877
11775
|
if (this.lastEvent &&
|
|
@@ -11889,8 +11787,6 @@ class StableWSConnection {
|
|
|
11889
11787
|
this.totalFailures = 0;
|
|
11890
11788
|
/** We only make 1 attempt to reconnect at the same time.. */
|
|
11891
11789
|
this.isConnecting = false;
|
|
11892
|
-
/** True after the auth payload is sent to the server */
|
|
11893
|
-
this.authenticationSent = false;
|
|
11894
11790
|
/** To avoid reconnect if client is disconnected */
|
|
11895
11791
|
this.isDisconnected = false;
|
|
11896
11792
|
/** Boolean that indicates if the connection promise is resolved */
|
|
@@ -11998,18 +11894,10 @@ class StableWSConnection {
|
|
|
11998
11894
|
this.isConnecting = false;
|
|
11999
11895
|
this.isDisconnected = true;
|
|
12000
11896
|
// start by removing all the listeners
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
}
|
|
12004
|
-
if (this.connectionCheckTimeoutRef) {
|
|
12005
|
-
clearInterval(this.connectionCheckTimeoutRef);
|
|
12006
|
-
}
|
|
11897
|
+
clearInterval(this.healthCheckTimeoutRef);
|
|
11898
|
+
clearInterval(this.connectionCheckTimeoutRef);
|
|
12007
11899
|
removeConnectionEventListeners(this.onlineStatusChanged);
|
|
12008
11900
|
this.isHealthy = false;
|
|
12009
|
-
// remove ws handlers...
|
|
12010
|
-
if (this.ws && this.ws.removeAllListeners) {
|
|
12011
|
-
this.ws.removeAllListeners();
|
|
12012
|
-
}
|
|
12013
11901
|
let isClosedPromise;
|
|
12014
11902
|
// and finally close...
|
|
12015
11903
|
// Assigning to local here because we will remove it from this before the
|
|
@@ -12042,12 +11930,10 @@ class StableWSConnection {
|
|
|
12042
11930
|
* @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
|
|
12043
11931
|
*/
|
|
12044
11932
|
async _connect() {
|
|
12045
|
-
if (this.isConnecting
|
|
12046
|
-
(this.isDisconnected && this.client.options.enableWSFallback))
|
|
11933
|
+
if (this.isConnecting)
|
|
12047
11934
|
return; // simply ignore _connect if it's currently trying to connect
|
|
12048
11935
|
this.isConnecting = true;
|
|
12049
11936
|
this.requestID = randomId();
|
|
12050
|
-
this.client.insightMetrics.connectionStartTimestamp = new Date().getTime();
|
|
12051
11937
|
let isTokenReady = false;
|
|
12052
11938
|
try {
|
|
12053
11939
|
this._log(`_connect() - waiting for token`);
|
|
@@ -12071,7 +11957,8 @@ class StableWSConnection {
|
|
|
12071
11957
|
wsURL,
|
|
12072
11958
|
requestID: this.requestID,
|
|
12073
11959
|
});
|
|
12074
|
-
this.
|
|
11960
|
+
const WS = this.client.options.WebSocketImpl ?? WebSocket;
|
|
11961
|
+
this.ws = new WS(wsURL);
|
|
12075
11962
|
this.ws.onopen = this.onopen.bind(this, this.wsID);
|
|
12076
11963
|
this.ws.onclose = this.onclose.bind(this, this.wsID);
|
|
12077
11964
|
this.ws.onerror = this.onerror.bind(this, this.wsID);
|
|
@@ -12081,11 +11968,6 @@ class StableWSConnection {
|
|
|
12081
11968
|
if (response) {
|
|
12082
11969
|
this.connectionID = response.connection_id;
|
|
12083
11970
|
this.client.resolveConnectionId?.(this.connectionID);
|
|
12084
|
-
if (this.client.insightMetrics.wsConsecutiveFailures > 0 &&
|
|
12085
|
-
this.client.options.enableInsights) {
|
|
12086
|
-
postInsights('ws_success_after_failure', buildWsSuccessAfterFailureInsight(this));
|
|
12087
|
-
this.client.insightMetrics.wsConsecutiveFailures = 0;
|
|
12088
|
-
}
|
|
12089
11971
|
return response;
|
|
12090
11972
|
}
|
|
12091
11973
|
}
|
|
@@ -12094,12 +11976,6 @@ class StableWSConnection {
|
|
|
12094
11976
|
this.isConnecting = false;
|
|
12095
11977
|
// @ts-ignore
|
|
12096
11978
|
this._log(`_connect() - Error - `, err);
|
|
12097
|
-
if (this.client.options.enableInsights) {
|
|
12098
|
-
this.client.insightMetrics.wsConsecutiveFailures++;
|
|
12099
|
-
this.client.insightMetrics.wsTotalFailures++;
|
|
12100
|
-
const insights = buildWsFatalInsight(this, convertErrorToJson(err));
|
|
12101
|
-
postInsights?.('ws_fatal', insights);
|
|
12102
|
-
}
|
|
12103
11979
|
this.client.rejectConnectionId?.(err);
|
|
12104
11980
|
throw err;
|
|
12105
11981
|
}
|
|
@@ -12133,7 +12009,7 @@ class StableWSConnection {
|
|
|
12133
12009
|
this._log('_reconnect() - Abort (2) since already connecting or healthy');
|
|
12134
12010
|
return;
|
|
12135
12011
|
}
|
|
12136
|
-
if (this.isDisconnected
|
|
12012
|
+
if (this.isDisconnected) {
|
|
12137
12013
|
this._log('_reconnect() - Abort (3) since disconnect() is called');
|
|
12138
12014
|
return;
|
|
12139
12015
|
}
|
|
@@ -12175,7 +12051,6 @@ class StableWSConnection {
|
|
|
12175
12051
|
// ws connection from now on.
|
|
12176
12052
|
this.wsID += 1;
|
|
12177
12053
|
try {
|
|
12178
|
-
this?.ws?.removeAllListeners();
|
|
12179
12054
|
this?.ws?.close();
|
|
12180
12055
|
}
|
|
12181
12056
|
catch (e) {
|
|
@@ -12187,36 +12062,16 @@ class StableWSConnection {
|
|
|
12187
12062
|
}
|
|
12188
12063
|
}
|
|
12189
12064
|
|
|
12190
|
-
function
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
return !!callback && isString(arrayOrString);
|
|
12195
|
-
}
|
|
12196
|
-
function map(arrayOrString, callback) {
|
|
12197
|
-
const res = [];
|
|
12198
|
-
if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) {
|
|
12199
|
-
for (let k = 0, len = arrayOrString.length; k < len; k++) {
|
|
12200
|
-
if (arrayOrString.charAt(k)) {
|
|
12201
|
-
const kValue = arrayOrString.charAt(k);
|
|
12202
|
-
const mappedValue = callback(kValue, k, arrayOrString);
|
|
12203
|
-
res[k] = mappedValue;
|
|
12204
|
-
}
|
|
12205
|
-
}
|
|
12206
|
-
}
|
|
12207
|
-
else if (!isString(arrayOrString) &&
|
|
12208
|
-
!isMapStringCallback(arrayOrString, callback)) {
|
|
12209
|
-
for (let k = 0, len = arrayOrString.length; k < len; k++) {
|
|
12210
|
-
if (k in arrayOrString) {
|
|
12211
|
-
const kValue = arrayOrString[k];
|
|
12212
|
-
const mappedValue = callback(kValue, k, arrayOrString);
|
|
12213
|
-
res[k] = mappedValue;
|
|
12214
|
-
}
|
|
12215
|
-
}
|
|
12065
|
+
function getUserFromToken(token) {
|
|
12066
|
+
const fragments = token.split('.');
|
|
12067
|
+
if (fragments.length !== 3) {
|
|
12068
|
+
return '';
|
|
12216
12069
|
}
|
|
12217
|
-
|
|
12070
|
+
const b64Payload = fragments[1];
|
|
12071
|
+
const payload = decodeBase64(b64Payload);
|
|
12072
|
+
const data = JSON.parse(payload);
|
|
12073
|
+
return data.user_id;
|
|
12218
12074
|
}
|
|
12219
|
-
const encodeBase64 = (data) => fromByteArray(new Uint8Array(map(data, (char) => char.charCodeAt(0))));
|
|
12220
12075
|
// base-64 decoder throws exception if encoded string is not padded by '=' to make string length
|
|
12221
12076
|
// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility
|
|
12222
12077
|
// https://github.com/beatgammit/base64-js/blob/master/index.js#L26
|
|
@@ -12238,29 +12093,6 @@ const decodeBase64 = (s) => {
|
|
|
12238
12093
|
return r;
|
|
12239
12094
|
};
|
|
12240
12095
|
|
|
12241
|
-
/**
|
|
12242
|
-
*
|
|
12243
|
-
* @param {string} userId the id of the user
|
|
12244
|
-
* @return {string}
|
|
12245
|
-
*/
|
|
12246
|
-
function DevToken(userId) {
|
|
12247
|
-
return [
|
|
12248
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', //{"alg": "HS256", "typ": "JWT"}
|
|
12249
|
-
encodeBase64(JSON.stringify({ user_id: userId })),
|
|
12250
|
-
'devtoken', // hardcoded signature
|
|
12251
|
-
].join('.');
|
|
12252
|
-
}
|
|
12253
|
-
function UserFromToken(token) {
|
|
12254
|
-
const fragments = token.split('.');
|
|
12255
|
-
if (fragments.length !== 3) {
|
|
12256
|
-
return '';
|
|
12257
|
-
}
|
|
12258
|
-
const b64Payload = fragments[1];
|
|
12259
|
-
const payload = decodeBase64(b64Payload);
|
|
12260
|
-
const data = JSON.parse(payload);
|
|
12261
|
-
return data.user_id;
|
|
12262
|
-
}
|
|
12263
|
-
|
|
12264
12096
|
/**
|
|
12265
12097
|
* TokenManager
|
|
12266
12098
|
*
|
|
@@ -12269,8 +12101,6 @@ function UserFromToken(token) {
|
|
|
12269
12101
|
class TokenManager {
|
|
12270
12102
|
/**
|
|
12271
12103
|
* Constructor
|
|
12272
|
-
*
|
|
12273
|
-
* @param {Secret} secret
|
|
12274
12104
|
*/
|
|
12275
12105
|
constructor(secret) {
|
|
12276
12106
|
/**
|
|
@@ -12323,7 +12153,7 @@ class TokenManager {
|
|
|
12323
12153
|
// Allow empty token for anonymous users
|
|
12324
12154
|
if (isAnonymous && tokenOrProvider === '')
|
|
12325
12155
|
return;
|
|
12326
|
-
const tokenUserId =
|
|
12156
|
+
const tokenUserId = getUserFromToken(tokenOrProvider);
|
|
12327
12157
|
if (tokenOrProvider != null &&
|
|
12328
12158
|
(tokenUserId == null ||
|
|
12329
12159
|
tokenUserId === '' ||
|
|
@@ -12338,7 +12168,6 @@ class TokenManager {
|
|
|
12338
12168
|
// Fetches a token from tokenProvider function and sets in tokenManager.
|
|
12339
12169
|
// In case of static token, it will simply resolve to static token.
|
|
12340
12170
|
this.loadToken = () => {
|
|
12341
|
-
// eslint-disable-next-line no-async-promise-executor
|
|
12342
12171
|
this.loadTokenPromise = new Promise(async (resolve, reject) => {
|
|
12343
12172
|
if (this.type === 'static') {
|
|
12344
12173
|
return resolve(this.token);
|
|
@@ -12367,236 +12196,11 @@ class TokenManager {
|
|
|
12367
12196
|
};
|
|
12368
12197
|
this.isStatic = () => this.type === 'static';
|
|
12369
12198
|
this.loadTokenPromise = null;
|
|
12370
|
-
|
|
12371
|
-
this.secret = secret;
|
|
12372
|
-
}
|
|
12199
|
+
this.secret = secret;
|
|
12373
12200
|
this.type = 'static';
|
|
12374
12201
|
}
|
|
12375
12202
|
}
|
|
12376
12203
|
|
|
12377
|
-
const APIErrorCodes = {
|
|
12378
|
-
'-1': { name: 'InternalSystemError', retryable: true },
|
|
12379
|
-
'2': { name: 'AccessKeyError', retryable: false },
|
|
12380
|
-
'3': { name: 'AuthenticationFailedError', retryable: true },
|
|
12381
|
-
'4': { name: 'InputError', retryable: false },
|
|
12382
|
-
'6': { name: 'DuplicateUsernameError', retryable: false },
|
|
12383
|
-
'9': { name: 'RateLimitError', retryable: true },
|
|
12384
|
-
'16': { name: 'DoesNotExistError', retryable: false },
|
|
12385
|
-
'17': { name: 'NotAllowedError', retryable: false },
|
|
12386
|
-
'18': { name: 'EventNotSupportedError', retryable: false },
|
|
12387
|
-
'19': { name: 'ChannelFeatureNotSupportedError', retryable: false },
|
|
12388
|
-
'20': { name: 'MessageTooLongError', retryable: false },
|
|
12389
|
-
'21': { name: 'MultipleNestingLevelError', retryable: false },
|
|
12390
|
-
'22': { name: 'PayloadTooBigError', retryable: false },
|
|
12391
|
-
'23': { name: 'RequestTimeoutError', retryable: true },
|
|
12392
|
-
'24': { name: 'MaxHeaderSizeExceededError', retryable: false },
|
|
12393
|
-
'40': { name: 'AuthErrorTokenExpired', retryable: false },
|
|
12394
|
-
'41': { name: 'AuthErrorTokenNotValidYet', retryable: false },
|
|
12395
|
-
'42': { name: 'AuthErrorTokenUsedBeforeIssuedAt', retryable: false },
|
|
12396
|
-
'43': { name: 'AuthErrorTokenSignatureInvalid', retryable: false },
|
|
12397
|
-
'44': { name: 'CustomCommandEndpointMissingError', retryable: false },
|
|
12398
|
-
'45': { name: 'CustomCommandEndpointCallError', retryable: true },
|
|
12399
|
-
'46': { name: 'ConnectionIDNotFoundError', retryable: false },
|
|
12400
|
-
'60': { name: 'CoolDownError', retryable: true },
|
|
12401
|
-
'69': { name: 'ErrWrongRegion', retryable: false },
|
|
12402
|
-
'70': { name: 'ErrQueryChannelPermissions', retryable: false },
|
|
12403
|
-
'71': { name: 'ErrTooManyConnections', retryable: true },
|
|
12404
|
-
'99': { name: 'AppSuspendedError', retryable: false },
|
|
12405
|
-
};
|
|
12406
|
-
function isAPIError(error) {
|
|
12407
|
-
return error.code !== undefined;
|
|
12408
|
-
}
|
|
12409
|
-
function isErrorRetryable(error) {
|
|
12410
|
-
if (!error.code)
|
|
12411
|
-
return false;
|
|
12412
|
-
const err = APIErrorCodes[`${error.code}`];
|
|
12413
|
-
if (!err)
|
|
12414
|
-
return false;
|
|
12415
|
-
return err.retryable;
|
|
12416
|
-
}
|
|
12417
|
-
function isConnectionIDError(error) {
|
|
12418
|
-
return error.code === 46; // ConnectionIDNotFoundError
|
|
12419
|
-
}
|
|
12420
|
-
function isWSFailure(err) {
|
|
12421
|
-
if (typeof err.isWSFailure === 'boolean') {
|
|
12422
|
-
return err.isWSFailure;
|
|
12423
|
-
}
|
|
12424
|
-
try {
|
|
12425
|
-
return JSON.parse(err.message).isWSFailure;
|
|
12426
|
-
}
|
|
12427
|
-
catch (_) {
|
|
12428
|
-
return false;
|
|
12429
|
-
}
|
|
12430
|
-
}
|
|
12431
|
-
function isErrorResponse(res) {
|
|
12432
|
-
return !res.status || res.status < 200 || 300 <= res.status;
|
|
12433
|
-
}
|
|
12434
|
-
|
|
12435
|
-
var ConnectionState;
|
|
12436
|
-
(function (ConnectionState) {
|
|
12437
|
-
ConnectionState["Closed"] = "CLOSED";
|
|
12438
|
-
ConnectionState["Connected"] = "CONNECTED";
|
|
12439
|
-
ConnectionState["Connecting"] = "CONNECTING";
|
|
12440
|
-
ConnectionState["Disconnected"] = "DISCONNECTED";
|
|
12441
|
-
ConnectionState["Init"] = "INIT";
|
|
12442
|
-
})(ConnectionState || (ConnectionState = {}));
|
|
12443
|
-
class WSConnectionFallback {
|
|
12444
|
-
constructor(client) {
|
|
12445
|
-
/** @private */
|
|
12446
|
-
this._onlineStatusChanged = (event) => {
|
|
12447
|
-
this._log(`_onlineStatusChanged() - ${event.type}`);
|
|
12448
|
-
if (event.type === 'offline') {
|
|
12449
|
-
this._setState(ConnectionState.Closed);
|
|
12450
|
-
this.cancelToken?.cancel('disconnect() is called');
|
|
12451
|
-
this.cancelToken = undefined;
|
|
12452
|
-
return;
|
|
12453
|
-
}
|
|
12454
|
-
if (event.type === 'online' && this.state === ConnectionState.Closed) {
|
|
12455
|
-
this.connect(true);
|
|
12456
|
-
}
|
|
12457
|
-
};
|
|
12458
|
-
/** @private */
|
|
12459
|
-
this._req = async (params, config, retry) => {
|
|
12460
|
-
if (!this.cancelToken && !params.close) {
|
|
12461
|
-
this.cancelToken = axios.CancelToken.source();
|
|
12462
|
-
}
|
|
12463
|
-
try {
|
|
12464
|
-
const res = await this.client.doAxiosRequest('get', this.client.baseURL.replace(':3030', ':8900') + '/longpoll', // replace port if present for testing with local API
|
|
12465
|
-
undefined, {
|
|
12466
|
-
config: { ...config, cancelToken: this.cancelToken?.token },
|
|
12467
|
-
params,
|
|
12468
|
-
publicEndpoint: true,
|
|
12469
|
-
});
|
|
12470
|
-
this.consecutiveFailures = 0; // always reset in case of no error
|
|
12471
|
-
return res;
|
|
12472
|
-
}
|
|
12473
|
-
catch (err) {
|
|
12474
|
-
this.consecutiveFailures += 1;
|
|
12475
|
-
// @ts-ignore
|
|
12476
|
-
if (retry && isErrorRetryable(err)) {
|
|
12477
|
-
this._log(`_req() - Retryable error, retrying request`);
|
|
12478
|
-
await sleep(retryInterval(this.consecutiveFailures));
|
|
12479
|
-
return this._req(params, config, retry);
|
|
12480
|
-
}
|
|
12481
|
-
throw err;
|
|
12482
|
-
}
|
|
12483
|
-
};
|
|
12484
|
-
/** @private */
|
|
12485
|
-
this._poll = async () => {
|
|
12486
|
-
while (this.state === ConnectionState.Connected) {
|
|
12487
|
-
try {
|
|
12488
|
-
const data = await this._req({}, {
|
|
12489
|
-
timeout: 30000,
|
|
12490
|
-
}, true); // 30s => API responds in 20s if there is no event
|
|
12491
|
-
if (data.events?.length) {
|
|
12492
|
-
for (let i = 0; i < data.events.length; i++) {
|
|
12493
|
-
this.client.dispatchEvent(data.events[i]);
|
|
12494
|
-
}
|
|
12495
|
-
}
|
|
12496
|
-
}
|
|
12497
|
-
catch (err) {
|
|
12498
|
-
if (axios.isCancel(err)) {
|
|
12499
|
-
this._log(`_poll() - axios canceled request`);
|
|
12500
|
-
return;
|
|
12501
|
-
}
|
|
12502
|
-
/** client.doAxiosRequest will take care of TOKEN_EXPIRED error */
|
|
12503
|
-
// @ts-ignore
|
|
12504
|
-
if (isConnectionIDError(err)) {
|
|
12505
|
-
this._log(`_poll() - ConnectionID error, connecting without ID...`);
|
|
12506
|
-
this._setState(ConnectionState.Disconnected);
|
|
12507
|
-
this.connect(true);
|
|
12508
|
-
return;
|
|
12509
|
-
}
|
|
12510
|
-
// @ts-ignore
|
|
12511
|
-
if (isAPIError(err) && !isErrorRetryable(err)) {
|
|
12512
|
-
this._setState(ConnectionState.Closed);
|
|
12513
|
-
return;
|
|
12514
|
-
}
|
|
12515
|
-
await sleep(retryInterval(this.consecutiveFailures));
|
|
12516
|
-
}
|
|
12517
|
-
}
|
|
12518
|
-
};
|
|
12519
|
-
/**
|
|
12520
|
-
* connect try to open a longpoll request
|
|
12521
|
-
* @param reconnect should be false for first call and true for subsequent calls to keep the connection alive and call recoverState
|
|
12522
|
-
*/
|
|
12523
|
-
this.connect = async (reconnect = false) => {
|
|
12524
|
-
if (this.state === ConnectionState.Connecting) {
|
|
12525
|
-
this._log('connect() - connecting already in progress', { reconnect }, 'warn');
|
|
12526
|
-
return;
|
|
12527
|
-
}
|
|
12528
|
-
if (this.state === ConnectionState.Connected) {
|
|
12529
|
-
this._log('connect() - already connected and polling', { reconnect }, 'warn');
|
|
12530
|
-
return;
|
|
12531
|
-
}
|
|
12532
|
-
this._setState(ConnectionState.Connecting);
|
|
12533
|
-
this.connectionID = undefined; // connect should be sent with empty connection_id so API creates one
|
|
12534
|
-
try {
|
|
12535
|
-
const { event } = await this._req({ json: this.client._buildWSPayload() }, {
|
|
12536
|
-
timeout: 8000, // 8s
|
|
12537
|
-
}, reconnect);
|
|
12538
|
-
this._setState(ConnectionState.Connected);
|
|
12539
|
-
this.connectionID = event.connection_id;
|
|
12540
|
-
this.client.resolveConnectionId?.();
|
|
12541
|
-
// @ts-expect-error
|
|
12542
|
-
this.client.dispatchEvent(event);
|
|
12543
|
-
this._poll();
|
|
12544
|
-
return event;
|
|
12545
|
-
}
|
|
12546
|
-
catch (err) {
|
|
12547
|
-
this._setState(ConnectionState.Closed);
|
|
12548
|
-
this.client.rejectConnectionId?.();
|
|
12549
|
-
throw err;
|
|
12550
|
-
}
|
|
12551
|
-
};
|
|
12552
|
-
/**
|
|
12553
|
-
* isHealthy checks if there is a connectionID and connection is in Connected state
|
|
12554
|
-
*/
|
|
12555
|
-
this.isHealthy = () => {
|
|
12556
|
-
return !!this.connectionID && this.state === ConnectionState.Connected;
|
|
12557
|
-
};
|
|
12558
|
-
this.disconnect = async (timeout = 2000) => {
|
|
12559
|
-
removeConnectionEventListeners(this._onlineStatusChanged);
|
|
12560
|
-
this._setState(ConnectionState.Disconnected);
|
|
12561
|
-
this.cancelToken?.cancel('disconnect() is called');
|
|
12562
|
-
this.cancelToken = undefined;
|
|
12563
|
-
const connection_id = this.connectionID;
|
|
12564
|
-
this.connectionID = undefined;
|
|
12565
|
-
try {
|
|
12566
|
-
await this._req({ close: true, connection_id }, {
|
|
12567
|
-
timeout,
|
|
12568
|
-
}, false);
|
|
12569
|
-
this._log(`disconnect() - Closed connectionID`);
|
|
12570
|
-
}
|
|
12571
|
-
catch (err) {
|
|
12572
|
-
this._log(`disconnect() - Failed`, { err }, 'error');
|
|
12573
|
-
}
|
|
12574
|
-
};
|
|
12575
|
-
this.client = client;
|
|
12576
|
-
this.state = ConnectionState.Init;
|
|
12577
|
-
this.consecutiveFailures = 0;
|
|
12578
|
-
addConnectionEventListeners(this._onlineStatusChanged);
|
|
12579
|
-
}
|
|
12580
|
-
_log(msg, extra = {}, level = 'info') {
|
|
12581
|
-
this.client.logger(level, 'WSConnectionFallback:' + msg, {
|
|
12582
|
-
...extra,
|
|
12583
|
-
});
|
|
12584
|
-
}
|
|
12585
|
-
_setState(state) {
|
|
12586
|
-
this._log(`_setState() - ${state}`);
|
|
12587
|
-
// transition from connecting => connected
|
|
12588
|
-
if (this.state === ConnectionState.Connecting &&
|
|
12589
|
-
state === ConnectionState.Connected) {
|
|
12590
|
-
this.client.dispatchEvent({ type: 'connection.changed', online: true });
|
|
12591
|
-
}
|
|
12592
|
-
if (state === ConnectionState.Closed ||
|
|
12593
|
-
state === ConnectionState.Disconnected) {
|
|
12594
|
-
this.client.dispatchEvent({ type: 'connection.changed', online: false });
|
|
12595
|
-
}
|
|
12596
|
-
this.state = state;
|
|
12597
|
-
}
|
|
12598
|
-
}
|
|
12599
|
-
|
|
12600
12204
|
const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, timeout = 2000, maxAttempts = 3) => {
|
|
12601
12205
|
const logger = getLogger(['location-hint']);
|
|
12602
12206
|
let attempt = 0;
|
|
@@ -12639,9 +12243,6 @@ class StreamClient {
|
|
|
12639
12243
|
*/
|
|
12640
12244
|
constructor(key, options) {
|
|
12641
12245
|
this.listeners = {};
|
|
12642
|
-
this.devToken = (userID) => {
|
|
12643
|
-
return DevToken(userID);
|
|
12644
|
-
};
|
|
12645
12246
|
this.getAuthType = () => {
|
|
12646
12247
|
return this.anonymous ? 'anonymous' : 'jwt';
|
|
12647
12248
|
};
|
|
@@ -12659,7 +12260,7 @@ class StreamClient {
|
|
|
12659
12260
|
}
|
|
12660
12261
|
return hint;
|
|
12661
12262
|
};
|
|
12662
|
-
this._getConnectionID = () => this.wsConnection?.connectionID
|
|
12263
|
+
this._getConnectionID = () => this.wsConnection?.connectionID;
|
|
12663
12264
|
this._hasConnectionID = () => Boolean(this._getConnectionID());
|
|
12664
12265
|
/**
|
|
12665
12266
|
* connectUser - Set the current user and open a WebSocket connection
|
|
@@ -12735,11 +12336,7 @@ class StreamClient {
|
|
|
12735
12336
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
12736
12337
|
*/
|
|
12737
12338
|
this.closeConnection = async (timeout) => {
|
|
12738
|
-
await
|
|
12739
|
-
this.wsConnection?.disconnect(timeout),
|
|
12740
|
-
this.wsFallback?.disconnect(timeout),
|
|
12741
|
-
]);
|
|
12742
|
-
return Promise.resolve();
|
|
12339
|
+
await this.wsConnection?.disconnect(timeout);
|
|
12743
12340
|
};
|
|
12744
12341
|
/**
|
|
12745
12342
|
* Creates a new WebSocket connection with the current user. Returns empty promise, if there is an active connection
|
|
@@ -12753,12 +12350,11 @@ class StreamClient {
|
|
|
12753
12350
|
this.logger('info', 'client:openConnection() - connection already in progress');
|
|
12754
12351
|
return await wsPromise;
|
|
12755
12352
|
}
|
|
12756
|
-
if (
|
|
12757
|
-
this._hasConnectionID()) {
|
|
12353
|
+
if (this.wsConnection?.isHealthy && this._hasConnectionID()) {
|
|
12758
12354
|
this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
|
|
12759
12355
|
return;
|
|
12760
12356
|
}
|
|
12761
|
-
this._setupConnectionIdPromise();
|
|
12357
|
+
await this._setupConnectionIdPromise();
|
|
12762
12358
|
this.clientID = `${this.userID}--${randomId()}`;
|
|
12763
12359
|
const newWsPromise = this.connect();
|
|
12764
12360
|
this.wsPromiseSafe = makeSafePromise(newWsPromise);
|
|
@@ -12785,11 +12381,7 @@ class StreamClient {
|
|
|
12785
12381
|
this.resolveConnectionId = undefined;
|
|
12786
12382
|
};
|
|
12787
12383
|
this.connectGuestUser = async (user) => {
|
|
12788
|
-
this.guestUserCreatePromise = this.doAxiosRequest('post', '/guest', {
|
|
12789
|
-
user: {
|
|
12790
|
-
...user,
|
|
12791
|
-
},
|
|
12792
|
-
}, { publicEndpoint: true });
|
|
12384
|
+
this.guestUserCreatePromise = this.doAxiosRequest('post', '/guest', { user }, { publicEndpoint: true });
|
|
12793
12385
|
const response = await this.guestUserCreatePromise;
|
|
12794
12386
|
this.guestUserCreatePromise.finally(() => (this.guestUserCreatePromise = undefined));
|
|
12795
12387
|
return this.connectUser(response.user, response.access_token);
|
|
@@ -12799,7 +12391,7 @@ class StreamClient {
|
|
|
12799
12391
|
*/
|
|
12800
12392
|
this.connectAnonymousUser = async (user, tokenOrProvider) => {
|
|
12801
12393
|
addConnectionEventListeners(this.updateNetworkConnectionStatus);
|
|
12802
|
-
this._setupConnectionIdPromise();
|
|
12394
|
+
await this._setupConnectionIdPromise();
|
|
12803
12395
|
this.anonymous = true;
|
|
12804
12396
|
await this._setToken(user, tokenOrProvider, this.anonymous);
|
|
12805
12397
|
this._setUser(user);
|
|
@@ -12993,78 +12585,17 @@ class StreamClient {
|
|
|
12993
12585
|
if (!this.userID || !this._user) {
|
|
12994
12586
|
throw Error('Call connectUser or connectAnonymousUser before starting the connection');
|
|
12995
12587
|
}
|
|
12996
|
-
if (!this.wsBaseURL)
|
|
12588
|
+
if (!this.wsBaseURL)
|
|
12997
12589
|
throw Error('Websocket base url not set');
|
|
12998
|
-
|
|
12999
|
-
if (!this.clientID) {
|
|
12590
|
+
if (!this.clientID)
|
|
13000
12591
|
throw Error('clientID is not set');
|
|
13001
|
-
}
|
|
13002
|
-
if (!this.wsConnection &&
|
|
13003
|
-
(this.options.warmUp || this.options.enableInsights)) {
|
|
13004
|
-
this._sayHi();
|
|
13005
|
-
}
|
|
13006
12592
|
// The StableWSConnection handles all the reconnection logic.
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
this.wsConnection = this.options
|
|
13011
|
-
.wsConnection;
|
|
13012
|
-
}
|
|
13013
|
-
else {
|
|
13014
|
-
this.wsConnection = new StableWSConnection(this);
|
|
13015
|
-
}
|
|
13016
|
-
try {
|
|
13017
|
-
// if fallback is used before, continue using it instead of waiting for WS to fail
|
|
13018
|
-
if (this.wsFallback) {
|
|
13019
|
-
return await this.wsFallback.connect();
|
|
13020
|
-
}
|
|
13021
|
-
this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
|
|
13022
|
-
// if WSFallback is enabled, ws connect should timeout faster so fallback can try
|
|
13023
|
-
return await this.wsConnection.connect(this.options.enableWSFallback
|
|
13024
|
-
? this.defaultWSTimeoutWithFallback
|
|
13025
|
-
: this.defaultWSTimeout);
|
|
13026
|
-
}
|
|
13027
|
-
catch (err) {
|
|
13028
|
-
// run fallback only if it's WS/Network error and not a normal API error
|
|
13029
|
-
// make sure browser is online before even trying the longpoll
|
|
13030
|
-
if (this.options.enableWSFallback &&
|
|
13031
|
-
// @ts-ignore
|
|
13032
|
-
isWSFailure(err) &&
|
|
13033
|
-
isOnline(this.logger)) {
|
|
13034
|
-
this.logger('warn', 'client:connect() - WS failed, fallback to longpoll');
|
|
13035
|
-
this.dispatchEvent({ type: 'transport.changed', mode: 'longpoll' });
|
|
13036
|
-
this.wsConnection._destroyCurrentWSConnection();
|
|
13037
|
-
this.wsConnection.disconnect().then(); // close WS so no retry
|
|
13038
|
-
this.wsFallback = new WSConnectionFallback(this);
|
|
13039
|
-
return await this.wsFallback.connect();
|
|
13040
|
-
}
|
|
13041
|
-
throw err;
|
|
13042
|
-
}
|
|
13043
|
-
};
|
|
13044
|
-
/**
|
|
13045
|
-
* Check the connectivity with server for warmup purpose.
|
|
13046
|
-
*
|
|
13047
|
-
* @private
|
|
13048
|
-
*/
|
|
13049
|
-
this._sayHi = () => {
|
|
13050
|
-
const client_request_id = randomId();
|
|
13051
|
-
const opts = {
|
|
13052
|
-
headers: AxiosHeaders.from({
|
|
13053
|
-
'x-client-request-id': client_request_id,
|
|
13054
|
-
}),
|
|
13055
|
-
};
|
|
13056
|
-
this.doAxiosRequest('get', this.baseURL + '/hi', null, opts).catch((e) => {
|
|
13057
|
-
if (this.options.enableInsights) {
|
|
13058
|
-
postInsights('http_hi_failed', {
|
|
13059
|
-
api_key: this.key,
|
|
13060
|
-
err: e,
|
|
13061
|
-
client_request_id,
|
|
13062
|
-
});
|
|
13063
|
-
}
|
|
13064
|
-
});
|
|
12593
|
+
this.wsConnection = new StableWSConnection(this);
|
|
12594
|
+
this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
|
|
12595
|
+
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
13065
12596
|
};
|
|
13066
12597
|
this.getUserAgent = () => {
|
|
13067
|
-
const version = "1.11.
|
|
12598
|
+
const version = "1.11.7";
|
|
13068
12599
|
return (this.userAgent ||
|
|
13069
12600
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
13070
12601
|
};
|
|
@@ -13112,18 +12643,6 @@ class StreamClient {
|
|
|
13112
12643
|
return null;
|
|
13113
12644
|
return this.tokenManager.getToken();
|
|
13114
12645
|
};
|
|
13115
|
-
/**
|
|
13116
|
-
* encode ws url payload
|
|
13117
|
-
* @private
|
|
13118
|
-
* @returns json string
|
|
13119
|
-
*/
|
|
13120
|
-
this._buildWSPayload = (client_request_id) => {
|
|
13121
|
-
return JSON.stringify({
|
|
13122
|
-
user_id: this.userID,
|
|
13123
|
-
user_details: this._user,
|
|
13124
|
-
client_request_id,
|
|
13125
|
-
});
|
|
13126
|
-
};
|
|
13127
12646
|
this.updateNetworkConnectionStatus = (event) => {
|
|
13128
12647
|
if (event.type === 'offline') {
|
|
13129
12648
|
this.logger('debug', 'device went offline');
|
|
@@ -13152,7 +12671,6 @@ class StreamClient {
|
|
|
13152
12671
|
this.options = {
|
|
13153
12672
|
timeout: 5000,
|
|
13154
12673
|
withCredentials: false, // making sure cookies are not sent
|
|
13155
|
-
warmUp: false,
|
|
13156
12674
|
...inputOptions,
|
|
13157
12675
|
};
|
|
13158
12676
|
if (this.node && !this.options.httpsAgent) {
|
|
@@ -13162,16 +12680,6 @@ class StreamClient {
|
|
|
13162
12680
|
});
|
|
13163
12681
|
}
|
|
13164
12682
|
this.setBaseURL(this.options.baseURL || 'https://video.stream-io-api.com/video');
|
|
13165
|
-
if (typeof process !== 'undefined' &&
|
|
13166
|
-
'env' in process &&
|
|
13167
|
-
process.env.STREAM_LOCAL_TEST_RUN) {
|
|
13168
|
-
this.setBaseURL('http://localhost:3030/video');
|
|
13169
|
-
}
|
|
13170
|
-
if (typeof process !== 'undefined' &&
|
|
13171
|
-
'env' in process &&
|
|
13172
|
-
process.env.STREAM_LOCAL_TEST_HOST) {
|
|
13173
|
-
this.setBaseURL(`http://${process.env.STREAM_LOCAL_TEST_HOST}/video`);
|
|
13174
|
-
}
|
|
13175
12683
|
this.axiosInstance = axios.create({
|
|
13176
12684
|
...this.options,
|
|
13177
12685
|
baseURL: this.baseURL,
|
|
@@ -13188,8 +12696,6 @@ class StreamClient {
|
|
|
13188
12696
|
// generated from secret.
|
|
13189
12697
|
this.tokenManager = new TokenManager(this.secret);
|
|
13190
12698
|
this.consecutiveFailures = 0;
|
|
13191
|
-
this.insightMetrics = new InsightMetrics();
|
|
13192
|
-
this.defaultWSTimeoutWithFallback = 6000;
|
|
13193
12699
|
this.defaultWSTimeout = 15000;
|
|
13194
12700
|
this.logger = isFunction(inputOptions.logger)
|
|
13195
12701
|
? inputOptions.logger
|