@stream-io/video-client 1.11.5 → 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 +14 -0
- package/dist/index.browser.es.js +77 -569
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +62 -554
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +77 -569
- 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/dist/src/rtc/codecs.d.ts +2 -1
- 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/src/rtc/Publisher.ts +8 -3
- package/src/rtc/codecs.ts +7 -3
- 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
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { AxiosRequestConfig, CancelTokenSource } from 'axios';
|
|
2
|
-
import { StreamClient } from './client';
|
|
3
|
-
import { LogLevel, UR } from './types';
|
|
4
|
-
import { ConnectedEvent } from '../../gen/coordinator';
|
|
5
|
-
export declare enum ConnectionState {
|
|
6
|
-
Closed = "CLOSED",
|
|
7
|
-
Connected = "CONNECTED",
|
|
8
|
-
Connecting = "CONNECTING",
|
|
9
|
-
Disconnected = "DISCONNECTED",
|
|
10
|
-
Init = "INIT"
|
|
11
|
-
}
|
|
12
|
-
export declare class WSConnectionFallback {
|
|
13
|
-
client: StreamClient;
|
|
14
|
-
state: ConnectionState;
|
|
15
|
-
consecutiveFailures: number;
|
|
16
|
-
connectionID?: string;
|
|
17
|
-
cancelToken?: CancelTokenSource;
|
|
18
|
-
constructor(client: StreamClient);
|
|
19
|
-
_log(msg: string, extra?: UR, level?: LogLevel): void;
|
|
20
|
-
_setState(state: ConnectionState): void;
|
|
21
|
-
/** @private */
|
|
22
|
-
_onlineStatusChanged: (event: {
|
|
23
|
-
type: string;
|
|
24
|
-
}) => void;
|
|
25
|
-
/** @private */
|
|
26
|
-
_req: <T = UR>(params: UR, config: AxiosRequestConfig, retry: boolean) => Promise<T>;
|
|
27
|
-
/** @private */
|
|
28
|
-
_poll: () => Promise<void>;
|
|
29
|
-
/**
|
|
30
|
-
* connect try to open a longpoll request
|
|
31
|
-
* @param reconnect should be false for first call and true for subsequent calls to keep the connection alive and call recoverState
|
|
32
|
-
*/
|
|
33
|
-
connect: (reconnect?: boolean) => Promise<ConnectedEvent | undefined>;
|
|
34
|
-
/**
|
|
35
|
-
* isHealthy checks if there is a connectionID and connection is in Connected state
|
|
36
|
-
*/
|
|
37
|
-
isHealthy: () => boolean;
|
|
38
|
-
disconnect: (timeout?: number) => Promise<void>;
|
|
39
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { AxiosResponse } from 'axios';
|
|
2
|
-
import { APIErrorResponse } from './types';
|
|
3
|
-
export declare const APIErrorCodes: Record<string, {
|
|
4
|
-
name: string;
|
|
5
|
-
retryable: boolean;
|
|
6
|
-
}>;
|
|
7
|
-
type APIError = Error & {
|
|
8
|
-
code: number;
|
|
9
|
-
isWSFailure?: boolean;
|
|
10
|
-
};
|
|
11
|
-
export declare function isAPIError(error: Error): error is APIError;
|
|
12
|
-
export declare function isErrorRetryable(error: APIError): boolean;
|
|
13
|
-
export declare function isConnectionIDError(error: APIError): boolean;
|
|
14
|
-
export declare function isWSFailure(err: APIError): boolean;
|
|
15
|
-
export declare function isErrorResponse(res: AxiosResponse<unknown>): res is AxiosResponse<APIErrorResponse>;
|
|
16
|
-
export {};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { StableWSConnection } from './connection';
|
|
2
|
-
export type InsightTypes = 'ws_fatal' | 'ws_success_after_failure' | 'http_hi_failed';
|
|
3
|
-
export declare class InsightMetrics {
|
|
4
|
-
connectionStartTimestamp: number | null;
|
|
5
|
-
wsConsecutiveFailures: number;
|
|
6
|
-
wsTotalFailures: number;
|
|
7
|
-
instanceClientId: string;
|
|
8
|
-
constructor();
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* postInsights is not supposed to be used by end users directly within chat application, and thus is kept isolated
|
|
12
|
-
* from all the client/connection code/logic.
|
|
13
|
-
*
|
|
14
|
-
* @param insightType
|
|
15
|
-
* @param insights
|
|
16
|
-
*/
|
|
17
|
-
export declare const postInsights: (insightType: InsightTypes, insights: Record<string, unknown>) => Promise<void>;
|
|
18
|
-
export declare function buildWsFatalInsight(connection: StableWSConnection, event: Record<string, unknown>): {
|
|
19
|
-
ready_state: 0 | 1 | 2 | 3 | undefined;
|
|
20
|
-
url: string;
|
|
21
|
-
api_key: string;
|
|
22
|
-
start_ts: number | null;
|
|
23
|
-
end_ts: number;
|
|
24
|
-
auth_type: string;
|
|
25
|
-
token: string | undefined;
|
|
26
|
-
user_id: string | undefined;
|
|
27
|
-
user_details: import("./types").UserWithId | undefined;
|
|
28
|
-
device: string;
|
|
29
|
-
client_id: string | undefined;
|
|
30
|
-
ws_details: import("ws") | undefined;
|
|
31
|
-
ws_consecutive_failures: number;
|
|
32
|
-
ws_total_failures: number;
|
|
33
|
-
request_id: string | undefined;
|
|
34
|
-
online: boolean | null;
|
|
35
|
-
user_agent: string | null;
|
|
36
|
-
instance_client_id: string;
|
|
37
|
-
};
|
|
38
|
-
export declare function buildWsSuccessAfterFailureInsight(connection: StableWSConnection): {
|
|
39
|
-
ready_state: 0 | 1 | 2 | 3 | undefined;
|
|
40
|
-
url: string;
|
|
41
|
-
api_key: string;
|
|
42
|
-
start_ts: number | null;
|
|
43
|
-
end_ts: number;
|
|
44
|
-
auth_type: string;
|
|
45
|
-
token: string | undefined;
|
|
46
|
-
user_id: string | undefined;
|
|
47
|
-
user_details: import("./types").UserWithId | undefined;
|
|
48
|
-
device: string;
|
|
49
|
-
client_id: string | undefined;
|
|
50
|
-
ws_details: import("ws") | undefined;
|
|
51
|
-
ws_consecutive_failures: number;
|
|
52
|
-
ws_total_failures: number;
|
|
53
|
-
request_id: string | undefined;
|
|
54
|
-
online: boolean | null;
|
|
55
|
-
user_agent: string | null;
|
|
56
|
-
instance_client_id: string;
|
|
57
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { fromByteArray } from 'base64-js';
|
|
2
|
-
|
|
3
|
-
function isString<T>(arrayOrString: string | T[]): arrayOrString is string {
|
|
4
|
-
return typeof (arrayOrString as string) === 'string';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type MapGenericCallback<T, U> = (value: T, index: number, array: T[]) => U;
|
|
8
|
-
type MapStringCallback<U> = (value: string, index: number, string: string) => U;
|
|
9
|
-
|
|
10
|
-
function isMapStringCallback<T, U>(
|
|
11
|
-
arrayOrString: string | T[],
|
|
12
|
-
callback: MapGenericCallback<T, U> | MapStringCallback<U>,
|
|
13
|
-
): callback is MapStringCallback<U> {
|
|
14
|
-
return !!callback && isString(arrayOrString);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// source - https://github.com/beatgammit/base64-js/blob/master/test/convert.js#L72
|
|
18
|
-
function map<T, U>(array: T[], callback: MapGenericCallback<T, U>): U[];
|
|
19
|
-
function map<U>(string: string, callback: MapStringCallback<U>): U[];
|
|
20
|
-
function map<T, U>(
|
|
21
|
-
arrayOrString: string | T[],
|
|
22
|
-
callback: MapGenericCallback<T, U> | MapStringCallback<U>,
|
|
23
|
-
): U[] {
|
|
24
|
-
const res = [];
|
|
25
|
-
|
|
26
|
-
if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) {
|
|
27
|
-
for (let k = 0, len = arrayOrString.length; k < len; k++) {
|
|
28
|
-
if (arrayOrString.charAt(k)) {
|
|
29
|
-
const kValue = arrayOrString.charAt(k);
|
|
30
|
-
const mappedValue = callback(kValue, k, arrayOrString);
|
|
31
|
-
res[k] = mappedValue;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} else if (
|
|
35
|
-
!isString(arrayOrString) &&
|
|
36
|
-
!isMapStringCallback(arrayOrString, callback)
|
|
37
|
-
) {
|
|
38
|
-
for (let k = 0, len = arrayOrString.length; k < len; k++) {
|
|
39
|
-
if (k in arrayOrString) {
|
|
40
|
-
const kValue = arrayOrString[k];
|
|
41
|
-
const mappedValue = callback(kValue, k, arrayOrString);
|
|
42
|
-
res[k] = mappedValue;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return res;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const encodeBase64 = (data: string): string =>
|
|
51
|
-
fromByteArray(new Uint8Array(map(data, (char) => char.charCodeAt(0))));
|
|
52
|
-
|
|
53
|
-
// base-64 decoder throws exception if encoded string is not padded by '=' to make string length
|
|
54
|
-
// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility
|
|
55
|
-
// https://github.com/beatgammit/base64-js/blob/master/index.js#L26
|
|
56
|
-
export const decodeBase64 = (s: string): string => {
|
|
57
|
-
const e = {} as { [key: string]: number },
|
|
58
|
-
w = String.fromCharCode,
|
|
59
|
-
L = s.length;
|
|
60
|
-
let i,
|
|
61
|
-
b = 0,
|
|
62
|
-
c,
|
|
63
|
-
x,
|
|
64
|
-
l = 0,
|
|
65
|
-
a,
|
|
66
|
-
r = '';
|
|
67
|
-
const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
68
|
-
for (i = 0; i < 64; i++) {
|
|
69
|
-
e[A.charAt(i)] = i;
|
|
70
|
-
}
|
|
71
|
-
for (x = 0; x < L; x++) {
|
|
72
|
-
c = e[s.charAt(x)];
|
|
73
|
-
b = (b << 6) + c;
|
|
74
|
-
l += 6;
|
|
75
|
-
while (l >= 8) {
|
|
76
|
-
((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return r;
|
|
80
|
-
};
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosRequestConfig, CancelTokenSource } from 'axios';
|
|
2
|
-
import { StreamClient } from './client';
|
|
3
|
-
import {
|
|
4
|
-
addConnectionEventListeners,
|
|
5
|
-
removeConnectionEventListeners,
|
|
6
|
-
retryInterval,
|
|
7
|
-
sleep,
|
|
8
|
-
} from './utils';
|
|
9
|
-
import { isAPIError, isConnectionIDError, isErrorRetryable } from './errors';
|
|
10
|
-
import { LogLevel, StreamVideoEvent, UR } from './types';
|
|
11
|
-
import { ConnectedEvent } from '../../gen/coordinator';
|
|
12
|
-
|
|
13
|
-
export enum ConnectionState {
|
|
14
|
-
Closed = 'CLOSED',
|
|
15
|
-
Connected = 'CONNECTED',
|
|
16
|
-
Connecting = 'CONNECTING',
|
|
17
|
-
Disconnected = 'DISCONNECTED',
|
|
18
|
-
Init = 'INIT',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class WSConnectionFallback {
|
|
22
|
-
client: StreamClient;
|
|
23
|
-
state: ConnectionState;
|
|
24
|
-
consecutiveFailures: number;
|
|
25
|
-
connectionID?: string;
|
|
26
|
-
cancelToken?: CancelTokenSource;
|
|
27
|
-
|
|
28
|
-
constructor(client: StreamClient) {
|
|
29
|
-
this.client = client;
|
|
30
|
-
this.state = ConnectionState.Init;
|
|
31
|
-
this.consecutiveFailures = 0;
|
|
32
|
-
|
|
33
|
-
addConnectionEventListeners(this._onlineStatusChanged);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
_log(msg: string, extra: UR = {}, level: LogLevel = 'info') {
|
|
37
|
-
this.client.logger(level, 'WSConnectionFallback:' + msg, {
|
|
38
|
-
...extra,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
_setState(state: ConnectionState) {
|
|
43
|
-
this._log(`_setState() - ${state}`);
|
|
44
|
-
|
|
45
|
-
// transition from connecting => connected
|
|
46
|
-
if (
|
|
47
|
-
this.state === ConnectionState.Connecting &&
|
|
48
|
-
state === ConnectionState.Connected
|
|
49
|
-
) {
|
|
50
|
-
this.client.dispatchEvent({ type: 'connection.changed', online: true });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
state === ConnectionState.Closed ||
|
|
55
|
-
state === ConnectionState.Disconnected
|
|
56
|
-
) {
|
|
57
|
-
this.client.dispatchEvent({ type: 'connection.changed', online: false });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this.state = state;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** @private */
|
|
64
|
-
_onlineStatusChanged = (event: { type: string }) => {
|
|
65
|
-
this._log(`_onlineStatusChanged() - ${event.type}`);
|
|
66
|
-
|
|
67
|
-
if (event.type === 'offline') {
|
|
68
|
-
this._setState(ConnectionState.Closed);
|
|
69
|
-
this.cancelToken?.cancel('disconnect() is called');
|
|
70
|
-
this.cancelToken = undefined;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (event.type === 'online' && this.state === ConnectionState.Closed) {
|
|
75
|
-
this.connect(true);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/** @private */
|
|
80
|
-
_req = async <T = UR>(
|
|
81
|
-
params: UR,
|
|
82
|
-
config: AxiosRequestConfig,
|
|
83
|
-
retry: boolean,
|
|
84
|
-
): Promise<T> => {
|
|
85
|
-
if (!this.cancelToken && !params.close) {
|
|
86
|
-
this.cancelToken = axios.CancelToken.source();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const res = await this.client.doAxiosRequest<T>(
|
|
91
|
-
'get',
|
|
92
|
-
(this.client.baseURL as string).replace(':3030', ':8900') + '/longpoll', // replace port if present for testing with local API
|
|
93
|
-
undefined,
|
|
94
|
-
{
|
|
95
|
-
config: { ...config, cancelToken: this.cancelToken?.token },
|
|
96
|
-
params,
|
|
97
|
-
publicEndpoint: true,
|
|
98
|
-
},
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
this.consecutiveFailures = 0; // always reset in case of no error
|
|
102
|
-
return res;
|
|
103
|
-
} catch (err) {
|
|
104
|
-
this.consecutiveFailures += 1;
|
|
105
|
-
|
|
106
|
-
// @ts-ignore
|
|
107
|
-
if (retry && isErrorRetryable(err)) {
|
|
108
|
-
this._log(`_req() - Retryable error, retrying request`);
|
|
109
|
-
await sleep(retryInterval(this.consecutiveFailures));
|
|
110
|
-
return this._req<T>(params, config, retry);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/** @private */
|
|
118
|
-
_poll = async () => {
|
|
119
|
-
while (this.state === ConnectionState.Connected) {
|
|
120
|
-
try {
|
|
121
|
-
const data = await this._req<{
|
|
122
|
-
events: StreamVideoEvent[];
|
|
123
|
-
}>(
|
|
124
|
-
{},
|
|
125
|
-
{
|
|
126
|
-
timeout: 30000,
|
|
127
|
-
},
|
|
128
|
-
true,
|
|
129
|
-
); // 30s => API responds in 20s if there is no event
|
|
130
|
-
|
|
131
|
-
if (data.events?.length) {
|
|
132
|
-
for (let i = 0; i < data.events.length; i++) {
|
|
133
|
-
this.client.dispatchEvent(data.events[i]);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} catch (err) {
|
|
137
|
-
if (axios.isCancel(err)) {
|
|
138
|
-
this._log(`_poll() - axios canceled request`);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** client.doAxiosRequest will take care of TOKEN_EXPIRED error */
|
|
143
|
-
|
|
144
|
-
// @ts-ignore
|
|
145
|
-
if (isConnectionIDError(err)) {
|
|
146
|
-
this._log(`_poll() - ConnectionID error, connecting without ID...`);
|
|
147
|
-
this._setState(ConnectionState.Disconnected);
|
|
148
|
-
this.connect(true);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// @ts-ignore
|
|
153
|
-
if (isAPIError(err) && !isErrorRetryable(err)) {
|
|
154
|
-
this._setState(ConnectionState.Closed);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await sleep(retryInterval(this.consecutiveFailures));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* connect try to open a longpoll request
|
|
165
|
-
* @param reconnect should be false for first call and true for subsequent calls to keep the connection alive and call recoverState
|
|
166
|
-
*/
|
|
167
|
-
connect = async (reconnect = false) => {
|
|
168
|
-
if (this.state === ConnectionState.Connecting) {
|
|
169
|
-
this._log(
|
|
170
|
-
'connect() - connecting already in progress',
|
|
171
|
-
{ reconnect },
|
|
172
|
-
'warn',
|
|
173
|
-
);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (this.state === ConnectionState.Connected) {
|
|
177
|
-
this._log(
|
|
178
|
-
'connect() - already connected and polling',
|
|
179
|
-
{ reconnect },
|
|
180
|
-
'warn',
|
|
181
|
-
);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this._setState(ConnectionState.Connecting);
|
|
186
|
-
this.connectionID = undefined; // connect should be sent with empty connection_id so API creates one
|
|
187
|
-
try {
|
|
188
|
-
const { event } = await this._req<{
|
|
189
|
-
event: ConnectedEvent;
|
|
190
|
-
}>(
|
|
191
|
-
{ json: this.client._buildWSPayload() },
|
|
192
|
-
{
|
|
193
|
-
timeout: 8000, // 8s
|
|
194
|
-
},
|
|
195
|
-
reconnect,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
this._setState(ConnectionState.Connected);
|
|
199
|
-
this.connectionID = event.connection_id;
|
|
200
|
-
this.client.resolveConnectionId?.();
|
|
201
|
-
// @ts-expect-error
|
|
202
|
-
this.client.dispatchEvent(event);
|
|
203
|
-
this._poll();
|
|
204
|
-
return event;
|
|
205
|
-
} catch (err) {
|
|
206
|
-
this._setState(ConnectionState.Closed);
|
|
207
|
-
this.client.rejectConnectionId?.();
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* isHealthy checks if there is a connectionID and connection is in Connected state
|
|
214
|
-
*/
|
|
215
|
-
isHealthy = () => {
|
|
216
|
-
return !!this.connectionID && this.state === ConnectionState.Connected;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
disconnect = async (timeout = 2000) => {
|
|
220
|
-
removeConnectionEventListeners(this._onlineStatusChanged);
|
|
221
|
-
|
|
222
|
-
this._setState(ConnectionState.Disconnected);
|
|
223
|
-
this.cancelToken?.cancel('disconnect() is called');
|
|
224
|
-
this.cancelToken = undefined;
|
|
225
|
-
|
|
226
|
-
const connection_id = this.connectionID;
|
|
227
|
-
this.connectionID = undefined;
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
await this._req(
|
|
231
|
-
{ close: true, connection_id },
|
|
232
|
-
{
|
|
233
|
-
timeout,
|
|
234
|
-
},
|
|
235
|
-
false,
|
|
236
|
-
);
|
|
237
|
-
this._log(`disconnect() - Closed connectionID`);
|
|
238
|
-
} catch (err) {
|
|
239
|
-
this._log(`disconnect() - Failed`, { err }, 'error');
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { AxiosResponse } from 'axios';
|
|
2
|
-
import { APIErrorResponse } from './types';
|
|
3
|
-
|
|
4
|
-
export const APIErrorCodes: Record<
|
|
5
|
-
string,
|
|
6
|
-
{ name: string; retryable: boolean }
|
|
7
|
-
> = {
|
|
8
|
-
'-1': { name: 'InternalSystemError', retryable: true },
|
|
9
|
-
'2': { name: 'AccessKeyError', retryable: false },
|
|
10
|
-
'3': { name: 'AuthenticationFailedError', retryable: true },
|
|
11
|
-
'4': { name: 'InputError', retryable: false },
|
|
12
|
-
'6': { name: 'DuplicateUsernameError', retryable: false },
|
|
13
|
-
'9': { name: 'RateLimitError', retryable: true },
|
|
14
|
-
'16': { name: 'DoesNotExistError', retryable: false },
|
|
15
|
-
'17': { name: 'NotAllowedError', retryable: false },
|
|
16
|
-
'18': { name: 'EventNotSupportedError', retryable: false },
|
|
17
|
-
'19': { name: 'ChannelFeatureNotSupportedError', retryable: false },
|
|
18
|
-
'20': { name: 'MessageTooLongError', retryable: false },
|
|
19
|
-
'21': { name: 'MultipleNestingLevelError', retryable: false },
|
|
20
|
-
'22': { name: 'PayloadTooBigError', retryable: false },
|
|
21
|
-
'23': { name: 'RequestTimeoutError', retryable: true },
|
|
22
|
-
'24': { name: 'MaxHeaderSizeExceededError', retryable: false },
|
|
23
|
-
'40': { name: 'AuthErrorTokenExpired', retryable: false },
|
|
24
|
-
'41': { name: 'AuthErrorTokenNotValidYet', retryable: false },
|
|
25
|
-
'42': { name: 'AuthErrorTokenUsedBeforeIssuedAt', retryable: false },
|
|
26
|
-
'43': { name: 'AuthErrorTokenSignatureInvalid', retryable: false },
|
|
27
|
-
'44': { name: 'CustomCommandEndpointMissingError', retryable: false },
|
|
28
|
-
'45': { name: 'CustomCommandEndpointCallError', retryable: true },
|
|
29
|
-
'46': { name: 'ConnectionIDNotFoundError', retryable: false },
|
|
30
|
-
'60': { name: 'CoolDownError', retryable: true },
|
|
31
|
-
'69': { name: 'ErrWrongRegion', retryable: false },
|
|
32
|
-
'70': { name: 'ErrQueryChannelPermissions', retryable: false },
|
|
33
|
-
'71': { name: 'ErrTooManyConnections', retryable: true },
|
|
34
|
-
'99': { name: 'AppSuspendedError', retryable: false },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// todo: this is not a correct error declaration. /recordings endpoint returns error objects as follows:
|
|
38
|
-
// {
|
|
39
|
-
// "code": 16,
|
|
40
|
-
// "message": "ListRecordings failed with error: \"Can't find call with id default:bbbb\"",
|
|
41
|
-
// "StatusCode": 404,
|
|
42
|
-
// "duration": "0.00ms",
|
|
43
|
-
// "more_info": "https://getstream.io/chat/docs/api_errors_response",
|
|
44
|
-
// "details": []
|
|
45
|
-
// }
|
|
46
|
-
|
|
47
|
-
type APIError = Error & { code: number; isWSFailure?: boolean };
|
|
48
|
-
|
|
49
|
-
export function isAPIError(error: Error): error is APIError {
|
|
50
|
-
return (error as APIError).code !== undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function isErrorRetryable(error: APIError) {
|
|
54
|
-
if (!error.code) return false;
|
|
55
|
-
const err = APIErrorCodes[`${error.code}`];
|
|
56
|
-
if (!err) return false;
|
|
57
|
-
return err.retryable;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function isConnectionIDError(error: APIError) {
|
|
61
|
-
return error.code === 46; // ConnectionIDNotFoundError
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isWSFailure(err: APIError): boolean {
|
|
65
|
-
if (typeof err.isWSFailure === 'boolean') {
|
|
66
|
-
return err.isWSFailure;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
return JSON.parse(err.message).isWSFailure;
|
|
71
|
-
} catch (_) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function isErrorResponse(
|
|
77
|
-
res: AxiosResponse<unknown>,
|
|
78
|
-
): res is AxiosResponse<APIErrorResponse> {
|
|
79
|
-
return !res.status || res.status < 200 || 300 <= res.status;
|
|
80
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { StableWSConnection } from './connection';
|
|
3
|
-
import { randomId, sleep } from './utils';
|
|
4
|
-
|
|
5
|
-
export type InsightTypes =
|
|
6
|
-
| 'ws_fatal'
|
|
7
|
-
| 'ws_success_after_failure'
|
|
8
|
-
| 'http_hi_failed';
|
|
9
|
-
export class InsightMetrics {
|
|
10
|
-
connectionStartTimestamp: number | null;
|
|
11
|
-
wsConsecutiveFailures: number;
|
|
12
|
-
wsTotalFailures: number;
|
|
13
|
-
instanceClientId: string;
|
|
14
|
-
|
|
15
|
-
constructor() {
|
|
16
|
-
this.connectionStartTimestamp = null;
|
|
17
|
-
this.wsTotalFailures = 0;
|
|
18
|
-
this.wsConsecutiveFailures = 0;
|
|
19
|
-
this.instanceClientId = randomId();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* postInsights is not supposed to be used by end users directly within chat application, and thus is kept isolated
|
|
25
|
-
* from all the client/connection code/logic.
|
|
26
|
-
*
|
|
27
|
-
* @param insightType
|
|
28
|
-
* @param insights
|
|
29
|
-
*/
|
|
30
|
-
export const postInsights = async (
|
|
31
|
-
insightType: InsightTypes,
|
|
32
|
-
insights: Record<string, unknown>,
|
|
33
|
-
) => {
|
|
34
|
-
const maxAttempts = 3;
|
|
35
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
36
|
-
try {
|
|
37
|
-
await axios.post(
|
|
38
|
-
`https://chat-insights.getstream.io/insights/${insightType}`,
|
|
39
|
-
insights,
|
|
40
|
-
);
|
|
41
|
-
} catch (e) {
|
|
42
|
-
await sleep((i + 1) * 3000);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export function buildWsFatalInsight(
|
|
50
|
-
connection: StableWSConnection,
|
|
51
|
-
event: Record<string, unknown>,
|
|
52
|
-
) {
|
|
53
|
-
return {
|
|
54
|
-
...event,
|
|
55
|
-
...buildWsBaseInsight(connection),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function buildWsBaseInsight(connection: StableWSConnection) {
|
|
60
|
-
const { client } = connection;
|
|
61
|
-
return {
|
|
62
|
-
ready_state: connection.ws?.readyState,
|
|
63
|
-
url: connection._buildUrl(),
|
|
64
|
-
api_key: client.key,
|
|
65
|
-
start_ts: client.insightMetrics.connectionStartTimestamp,
|
|
66
|
-
end_ts: new Date().getTime(),
|
|
67
|
-
auth_type: client.getAuthType(),
|
|
68
|
-
token: client.tokenManager.token,
|
|
69
|
-
user_id: client.userID,
|
|
70
|
-
user_details: client._user,
|
|
71
|
-
// device: client.options.device,
|
|
72
|
-
device: 'browser',
|
|
73
|
-
client_id: connection.connectionID,
|
|
74
|
-
ws_details: connection.ws,
|
|
75
|
-
ws_consecutive_failures: client.insightMetrics.wsConsecutiveFailures,
|
|
76
|
-
ws_total_failures: client.insightMetrics.wsTotalFailures,
|
|
77
|
-
request_id: connection.requestID,
|
|
78
|
-
online: typeof navigator !== 'undefined' ? navigator?.onLine : null,
|
|
79
|
-
user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null,
|
|
80
|
-
instance_client_id: client.insightMetrics.instanceClientId,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function buildWsSuccessAfterFailureInsight(
|
|
85
|
-
connection: StableWSConnection,
|
|
86
|
-
) {
|
|
87
|
-
return buildWsBaseInsight(connection);
|
|
88
|
-
}
|