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