stream-chat 4.3.0 → 4.4.2
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/dist/browser.es.js +830 -635
- package/dist/browser.es.js.map +1 -1
- package/dist/browser.full-bundle.min.js +1 -1
- package/dist/browser.full-bundle.min.js.map +1 -1
- package/dist/browser.js +832 -634
- package/dist/browser.js.map +1 -1
- package/dist/index.es.js +830 -635
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +832 -634
- package/dist/index.js.map +1 -1
- package/dist/types/client.d.ts +11 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/connection.d.ts +14 -2
- package/dist/types/connection.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/insights.d.ts +58 -0
- package/dist/types/insights.d.ts.map +1 -0
- package/dist/types/types.d.ts +5 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils.d.ts +1 -0
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +27 -0
- package/src/connection.ts +40 -7
- package/src/index.ts +1 -0
- package/src/insights.ts +72 -0
- package/src/types.ts +4 -1
- package/src/utils.ts +51 -6
package/src/client.ts
CHANGED
|
@@ -118,6 +118,7 @@ import {
|
|
|
118
118
|
DeleteChannelsResponse,
|
|
119
119
|
TaskResponse,
|
|
120
120
|
} from './types';
|
|
121
|
+
import { InsightTypes, InsightMetrics } from './insights';
|
|
121
122
|
|
|
122
123
|
function isString(x: unknown): x is string {
|
|
123
124
|
return typeof x === 'string' || x instanceof String;
|
|
@@ -197,6 +198,7 @@ export class StreamChat<
|
|
|
197
198
|
wsConnection: StableWSConnection<ChannelType, CommandType, UserType> | null;
|
|
198
199
|
wsPromise: ConnectAPIResponse<ChannelType, CommandType, UserType> | null;
|
|
199
200
|
consecutiveFailures: number;
|
|
201
|
+
insightMetrics: InsightMetrics;
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
204
|
* Initialize a client
|
|
@@ -291,6 +293,7 @@ export class StreamChat<
|
|
|
291
293
|
// generated from secret.
|
|
292
294
|
this.tokenManager = new TokenManager(this.secret);
|
|
293
295
|
this.consecutiveFailures = 0;
|
|
296
|
+
this.insightMetrics = new InsightMetrics();
|
|
294
297
|
|
|
295
298
|
/**
|
|
296
299
|
* logger function should accept 3 parameters:
|
|
@@ -1617,6 +1620,7 @@ export class StreamChat<
|
|
|
1617
1620
|
}
|
|
1618
1621
|
|
|
1619
1622
|
// The StableWSConnection handles all the reconnection logic.
|
|
1623
|
+
|
|
1620
1624
|
this.wsConnection = new StableWSConnection<ChannelType, CommandType, UserType>({
|
|
1621
1625
|
wsBaseURL: client.wsBaseURL,
|
|
1622
1626
|
clientID: client.clientID,
|
|
@@ -1631,6 +1635,8 @@ export class StreamChat<
|
|
|
1631
1635
|
eventCallback: this.dispatchEvent as (event: ConnectionChangeEvent) => void,
|
|
1632
1636
|
logger: this.logger,
|
|
1633
1637
|
device: this.options.device,
|
|
1638
|
+
postInsights: this.options.enableInsights ? this.postInsights : undefined,
|
|
1639
|
+
insightMetrics: this.insightMetrics,
|
|
1634
1640
|
});
|
|
1635
1641
|
|
|
1636
1642
|
let warmUpPromise;
|
|
@@ -3343,6 +3349,27 @@ export class StreamChat<
|
|
|
3343
3349
|
);
|
|
3344
3350
|
}
|
|
3345
3351
|
|
|
3352
|
+
postInsights = async (insightType: InsightTypes, insights: Record<string, unknown>) => {
|
|
3353
|
+
const maxAttempts = 3;
|
|
3354
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
3355
|
+
try {
|
|
3356
|
+
await this.axiosInstance.post(
|
|
3357
|
+
`https://insights.stream-io-api.com/insights/${insightType}`,
|
|
3358
|
+
insights,
|
|
3359
|
+
);
|
|
3360
|
+
} catch (e) {
|
|
3361
|
+
this.logger('warn', `failed to send insights event ${insightType}`, {
|
|
3362
|
+
tags: ['insights', 'connection'],
|
|
3363
|
+
error: e,
|
|
3364
|
+
insights,
|
|
3365
|
+
});
|
|
3366
|
+
await sleep((i + 1) * 3000);
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
3369
|
+
break;
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
|
|
3346
3373
|
/**
|
|
3347
3374
|
* deleteUsers - Batch Delete Users
|
|
3348
3375
|
*
|
package/src/connection.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import WebSocket from 'isomorphic-ws';
|
|
2
|
-
import { chatCodes, sleep, retryInterval } from './utils';
|
|
2
|
+
import { chatCodes, sleep, retryInterval, randomId } from './utils';
|
|
3
3
|
import { TokenManager } from './token_manager';
|
|
4
|
+
import {
|
|
5
|
+
buildWsFatalInsight,
|
|
6
|
+
buildWsSuccessAfterFailureInsight,
|
|
7
|
+
InsightMetrics,
|
|
8
|
+
InsightTypes,
|
|
9
|
+
} from './insights';
|
|
4
10
|
import {
|
|
5
11
|
BaseDeviceFields,
|
|
6
12
|
ConnectAPIResponse,
|
|
@@ -30,6 +36,7 @@ type Constructor<
|
|
|
30
36
|
authType: 'anonymous' | 'jwt';
|
|
31
37
|
clientID: string;
|
|
32
38
|
eventCallback: (event: ConnectionChangeEvent) => void;
|
|
39
|
+
insightMetrics: InsightMetrics;
|
|
33
40
|
logger: Logger | (() => void);
|
|
34
41
|
messageCallback: (messageEvent: WebSocket.MessageEvent) => void;
|
|
35
42
|
recoverCallback: (
|
|
@@ -41,6 +48,7 @@ type Constructor<
|
|
|
41
48
|
userID: string;
|
|
42
49
|
wsBaseURL: string;
|
|
43
50
|
device?: BaseDeviceFields;
|
|
51
|
+
postInsights?: (eventType: InsightTypes, event: Record<string, unknown>) => void;
|
|
44
52
|
};
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -78,7 +86,6 @@ export class StableWSConnection<
|
|
|
78
86
|
userID: Constructor<ChannelType, CommandType, UserType>['userID'];
|
|
79
87
|
wsBaseURL: Constructor<ChannelType, CommandType, UserType>['wsBaseURL'];
|
|
80
88
|
device: Constructor<ChannelType, CommandType, UserType>['device'];
|
|
81
|
-
|
|
82
89
|
connectionID?: string;
|
|
83
90
|
connectionOpen?: ConnectAPIResponse<ChannelType, CommandType, UserType>;
|
|
84
91
|
consecutiveFailures: number;
|
|
@@ -97,11 +104,14 @@ export class StableWSConnection<
|
|
|
97
104
|
StatusCode?: string | number;
|
|
98
105
|
},
|
|
99
106
|
) => void;
|
|
107
|
+
requestID: string | undefined;
|
|
108
|
+
connectionStartTimestamp: number | undefined;
|
|
100
109
|
resolvePromise?: (value: WebSocket.MessageEvent) => void;
|
|
101
110
|
totalFailures: number;
|
|
102
111
|
ws?: WebSocket;
|
|
103
112
|
wsID: number;
|
|
104
|
-
|
|
113
|
+
postInsights?: Constructor<ChannelType, CommandType, UserType>['postInsights'];
|
|
114
|
+
insightMetrics: InsightMetrics;
|
|
105
115
|
constructor({
|
|
106
116
|
apiKey,
|
|
107
117
|
authType,
|
|
@@ -116,6 +126,8 @@ export class StableWSConnection<
|
|
|
116
126
|
userID,
|
|
117
127
|
wsBaseURL,
|
|
118
128
|
device,
|
|
129
|
+
postInsights,
|
|
130
|
+
insightMetrics,
|
|
119
131
|
}: Constructor<ChannelType, CommandType, UserType>) {
|
|
120
132
|
this.wsBaseURL = wsBaseURL;
|
|
121
133
|
this.clientID = clientID;
|
|
@@ -149,6 +161,8 @@ export class StableWSConnection<
|
|
|
149
161
|
this.pingInterval = 25 * 1000;
|
|
150
162
|
this.connectionCheckTimeout = this.pingInterval + 10 * 1000;
|
|
151
163
|
this._listenForConnectionChanges();
|
|
164
|
+
this.postInsights = postInsights;
|
|
165
|
+
this.insightMetrics = insightMetrics;
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
/**
|
|
@@ -244,13 +258,19 @@ export class StableWSConnection<
|
|
|
244
258
|
]);
|
|
245
259
|
}
|
|
246
260
|
|
|
247
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Builds and returns the url for websocket.
|
|
263
|
+
* @param reqID Unique identifier generated on client side, to help tracking apis on backend.
|
|
264
|
+
* @returns url string
|
|
265
|
+
*/
|
|
266
|
+
_buildUrl = (reqID?: string) => {
|
|
248
267
|
const params = {
|
|
249
268
|
user_id: this.user.id,
|
|
250
269
|
user_details: this.user,
|
|
251
270
|
user_token: this.tokenManager.getToken(),
|
|
252
271
|
server_determines_connection_id: true,
|
|
253
272
|
device: this.device,
|
|
273
|
+
request_id: reqID,
|
|
254
274
|
};
|
|
255
275
|
const qs = encodeURIComponent(JSON.stringify(params));
|
|
256
276
|
const token = this.tokenManager.getToken();
|
|
@@ -352,11 +372,12 @@ export class StableWSConnection<
|
|
|
352
372
|
async _connect() {
|
|
353
373
|
if (this.isConnecting) return; // simply ignore _connect if it's currently trying to connect
|
|
354
374
|
this.isConnecting = true;
|
|
355
|
-
|
|
375
|
+
this.requestID = randomId();
|
|
376
|
+
this.insightMetrics.connectionStartTimestamp = new Date().getTime();
|
|
356
377
|
try {
|
|
357
378
|
await this.tokenManager.tokenReady();
|
|
358
379
|
this._setupConnectionPromise();
|
|
359
|
-
const wsURL = this._buildUrl();
|
|
380
|
+
const wsURL = this._buildUrl(this.requestID);
|
|
360
381
|
this.ws = new WebSocket(wsURL);
|
|
361
382
|
this.ws.onopen = this.onopen.bind(this, this.wsID);
|
|
362
383
|
this.ws.onclose = this.onclose.bind(this, this.wsID);
|
|
@@ -367,6 +388,13 @@ export class StableWSConnection<
|
|
|
367
388
|
|
|
368
389
|
if (response) {
|
|
369
390
|
this.connectionID = response.connection_id;
|
|
391
|
+
if (this.insightMetrics.wsConsecutiveFailures > 0) {
|
|
392
|
+
this.postInsights?.(
|
|
393
|
+
'ws_success_after_failure',
|
|
394
|
+
buildWsSuccessAfterFailureInsight(this),
|
|
395
|
+
);
|
|
396
|
+
this.insightMetrics.wsConsecutiveFailures = 0;
|
|
397
|
+
}
|
|
370
398
|
return response;
|
|
371
399
|
}
|
|
372
400
|
} catch (err) {
|
|
@@ -559,6 +587,12 @@ export class StableWSConnection<
|
|
|
559
587
|
};
|
|
560
588
|
|
|
561
589
|
onclose = (wsID: number, event: WebSocket.CloseEvent) => {
|
|
590
|
+
if (event.code !== chatCodes.WS_CLOSED_SUCCESS) {
|
|
591
|
+
this.insightMetrics.wsConsecutiveFailures++;
|
|
592
|
+
this.insightMetrics.wsTotalFailures++;
|
|
593
|
+
this.postInsights?.('ws_fatal', buildWsFatalInsight(this, event));
|
|
594
|
+
}
|
|
595
|
+
|
|
562
596
|
this.logger('info', 'connection:onclose() - onclose callback - ' + event.code, {
|
|
563
597
|
tags: ['connection'],
|
|
564
598
|
event,
|
|
@@ -791,7 +825,6 @@ export class StableWSConnection<
|
|
|
791
825
|
{
|
|
792
826
|
type: 'health.check',
|
|
793
827
|
client_id: this.clientID,
|
|
794
|
-
user_id: this.userID,
|
|
795
828
|
},
|
|
796
829
|
];
|
|
797
830
|
// try to send on the connection
|
package/src/index.ts
CHANGED
package/src/insights.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { StableWSConnection } from './connection';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
import { LiteralStringForUnion, UnknownType } from './types';
|
|
4
|
+
import { randomId } from './utils';
|
|
5
|
+
|
|
6
|
+
export type InsightTypes = 'ws_fatal' | 'ws_success_after_failure';
|
|
7
|
+
export class InsightMetrics {
|
|
8
|
+
connectionStartTimestamp: number | null;
|
|
9
|
+
wsConsecutiveFailures: number;
|
|
10
|
+
wsTotalFailures: number;
|
|
11
|
+
instanceClientId: string;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.connectionStartTimestamp = null;
|
|
15
|
+
this.wsTotalFailures = 0;
|
|
16
|
+
this.wsConsecutiveFailures = 0;
|
|
17
|
+
this.instanceClientId = randomId();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildWsFatalInsight<
|
|
22
|
+
ChannelType extends UnknownType = UnknownType,
|
|
23
|
+
CommandType extends string = LiteralStringForUnion,
|
|
24
|
+
UserType extends UnknownType = UnknownType
|
|
25
|
+
>(
|
|
26
|
+
connection: StableWSConnection<ChannelType, CommandType, UserType>,
|
|
27
|
+
event: WebSocket.CloseEvent,
|
|
28
|
+
) {
|
|
29
|
+
return {
|
|
30
|
+
err: {
|
|
31
|
+
wasClean: event.wasClean,
|
|
32
|
+
code: event.code,
|
|
33
|
+
reason: event.reason,
|
|
34
|
+
},
|
|
35
|
+
...buildWsBaseInsight(connection),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildWsBaseInsight<
|
|
40
|
+
ChannelType extends UnknownType = UnknownType,
|
|
41
|
+
CommandType extends string = LiteralStringForUnion,
|
|
42
|
+
UserType extends UnknownType = UnknownType
|
|
43
|
+
>(connection: StableWSConnection<ChannelType, CommandType, UserType>) {
|
|
44
|
+
return {
|
|
45
|
+
ready_state: connection.ws?.readyState,
|
|
46
|
+
url: connection._buildUrl(connection.requestID),
|
|
47
|
+
api_key: connection.apiKey,
|
|
48
|
+
start_ts: connection.insightMetrics.connectionStartTimestamp,
|
|
49
|
+
end_ts: new Date().getTime(),
|
|
50
|
+
auth_type: connection.authType,
|
|
51
|
+
token: connection.tokenManager.token,
|
|
52
|
+
user_id: connection.userID,
|
|
53
|
+
user_details: connection.user,
|
|
54
|
+
device: connection.device,
|
|
55
|
+
client_id: connection.connectionID,
|
|
56
|
+
ws_details: connection.ws,
|
|
57
|
+
ws_consecutive_failures: connection.insightMetrics.wsConsecutiveFailures,
|
|
58
|
+
ws_total_failures: connection.insightMetrics.wsTotalFailures,
|
|
59
|
+
request_id: connection.requestID,
|
|
60
|
+
online: typeof navigator !== 'undefined' ? navigator?.onLine : null,
|
|
61
|
+
user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null,
|
|
62
|
+
instance_client_id: connection.insightMetrics.instanceClientId,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function buildWsSuccessAfterFailureInsight<
|
|
67
|
+
ChannelType extends UnknownType = UnknownType,
|
|
68
|
+
CommandType extends string = LiteralStringForUnion,
|
|
69
|
+
UserType extends UnknownType = UnknownType
|
|
70
|
+
>(connection: StableWSConnection<ChannelType, CommandType, UserType>) {
|
|
71
|
+
return buildWsBaseInsight(connection);
|
|
72
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -69,6 +69,7 @@ export type AppSettingsAPIResponse<
|
|
|
69
69
|
mutes?: boolean;
|
|
70
70
|
name?: string;
|
|
71
71
|
push_notifications?: boolean;
|
|
72
|
+
quotes?: boolean;
|
|
72
73
|
reactions?: boolean;
|
|
73
74
|
read_events?: boolean;
|
|
74
75
|
replies?: boolean;
|
|
@@ -859,6 +860,7 @@ export type CreateChannelOptions<CommandType extends string = LiteralStringForUn
|
|
|
859
860
|
name?: string;
|
|
860
861
|
permissions?: PermissionObject[];
|
|
861
862
|
push_notifications?: boolean;
|
|
863
|
+
quotes?: boolean;
|
|
862
864
|
reactions?: boolean;
|
|
863
865
|
read_events?: boolean;
|
|
864
866
|
replies?: boolean;
|
|
@@ -995,6 +997,7 @@ export type StreamChatOptions = AxiosRequestConfig & {
|
|
|
995
997
|
baseURL?: string;
|
|
996
998
|
browser?: boolean;
|
|
997
999
|
device?: BaseDeviceFields;
|
|
1000
|
+
enableInsights?: boolean;
|
|
998
1001
|
logger?: Logger;
|
|
999
1002
|
/**
|
|
1000
1003
|
* When network is recovered, we re-query the active channels on client. But in single query, you can recover
|
|
@@ -1631,6 +1634,7 @@ export type ChannelConfigFields = {
|
|
|
1631
1634
|
mutes?: boolean;
|
|
1632
1635
|
name?: string;
|
|
1633
1636
|
push_notifications?: boolean;
|
|
1637
|
+
quotes?: boolean;
|
|
1634
1638
|
reactions?: boolean;
|
|
1635
1639
|
read_events?: boolean;
|
|
1636
1640
|
replies?: boolean;
|
|
@@ -1699,7 +1703,6 @@ export type PushProvider = 'apn' | 'firebase' | 'huawei';
|
|
|
1699
1703
|
export type CommandVariants<CommandType extends string = LiteralStringForUnion> =
|
|
1700
1704
|
| 'all'
|
|
1701
1705
|
| 'ban'
|
|
1702
|
-
| 'flag'
|
|
1703
1706
|
| 'fun_set'
|
|
1704
1707
|
| 'giphy'
|
|
1705
1708
|
| 'imgur'
|
package/src/utils.ts
CHANGED
|
@@ -144,12 +144,57 @@ export function retryInterval(numberOfFailures: number) {
|
|
|
144
144
|
return Math.floor(Math.random() * (max - min) + min);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/** adopted from https://github.com/ai/nanoid/blob/master/non-secure/index.js */
|
|
148
|
-
const alphabet = 'ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW';
|
|
149
147
|
export function randomId() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
return generateUUIDv4();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hex(bytes: Uint8Array): string {
|
|
152
|
+
let s = '';
|
|
153
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
154
|
+
s += bytes[i].toString(16).padStart(2, '0');
|
|
153
155
|
}
|
|
154
|
-
return
|
|
156
|
+
return s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// https://tools.ietf.org/html/rfc4122
|
|
160
|
+
export function generateUUIDv4() {
|
|
161
|
+
const bytes = getRandomBytes(16);
|
|
162
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
|
|
163
|
+
bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
hex(bytes.subarray(0, 4)) +
|
|
167
|
+
'-' +
|
|
168
|
+
hex(bytes.subarray(4, 6)) +
|
|
169
|
+
'-' +
|
|
170
|
+
hex(bytes.subarray(6, 8)) +
|
|
171
|
+
'-' +
|
|
172
|
+
hex(bytes.subarray(8, 10)) +
|
|
173
|
+
'-' +
|
|
174
|
+
hex(bytes.subarray(10, 16))
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getRandomValuesWithMathRandom(bytes: Uint8Array): void {
|
|
179
|
+
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
180
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
181
|
+
bytes[i] = Math.random() * max;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
declare const msCrypto: Crypto;
|
|
185
|
+
|
|
186
|
+
const getRandomValues = (() => {
|
|
187
|
+
if (typeof crypto !== 'undefined') {
|
|
188
|
+
return crypto.getRandomValues.bind(crypto);
|
|
189
|
+
} else if (typeof msCrypto !== 'undefined') {
|
|
190
|
+
return msCrypto.getRandomValues.bind(msCrypto);
|
|
191
|
+
} else {
|
|
192
|
+
return getRandomValuesWithMathRandom;
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
|
|
196
|
+
function getRandomBytes(length: number): Uint8Array {
|
|
197
|
+
const bytes = new Uint8Array(length);
|
|
198
|
+
getRandomValues(bytes);
|
|
199
|
+
return bytes;
|
|
155
200
|
}
|