stream-chat 4.2.0 → 4.4.1
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 +1007 -618
- 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 +1009 -617
- package/dist/browser.js.map +1 -1
- package/dist/index.es.js +1007 -618
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1009 -617
- package/dist/index.js.map +1 -1
- package/dist/types/channel.d.ts +7 -5
- package/dist/types/channel.d.ts.map +1 -1
- package/dist/types/client.d.ts +76 -11
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/connection.d.ts +14 -3
- 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 +55 -0
- package/dist/types/insights.d.ts.map +1 -0
- package/dist/types/types.d.ts +78 -10
- 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/channel.ts +8 -6
- package/src/client.ts +135 -11
- package/src/connection.ts +46 -18
- package/src/index.ts +1 -0
- package/src/insights.ts +68 -0
- package/src/types.ts +100 -10
- package/src/utils.ts +51 -6
package/src/client.ts
CHANGED
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
ListCommandsResponse,
|
|
73
73
|
LiteralStringForUnion,
|
|
74
74
|
Logger,
|
|
75
|
-
|
|
75
|
+
MarkChannelsReadOptions,
|
|
76
76
|
Message,
|
|
77
77
|
MessageFilters,
|
|
78
78
|
MessageResponse,
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
PartialUserUpdate,
|
|
85
85
|
PermissionAPIResponse,
|
|
86
86
|
PermissionsAPIResponse,
|
|
87
|
+
PushProvider,
|
|
87
88
|
ReactionResponse,
|
|
88
89
|
SearchOptions,
|
|
89
90
|
SearchPayload,
|
|
@@ -111,7 +112,13 @@ import {
|
|
|
111
112
|
Segment,
|
|
112
113
|
Campaign,
|
|
113
114
|
CampaignData,
|
|
115
|
+
OGAttachment,
|
|
116
|
+
TaskStatus,
|
|
117
|
+
DeleteUserOptions,
|
|
118
|
+
DeleteChannelsResponse,
|
|
119
|
+
TaskResponse,
|
|
114
120
|
} from './types';
|
|
121
|
+
import { InsightTypes, InsightMetrics } from './insights';
|
|
115
122
|
|
|
116
123
|
function isString(x: unknown): x is string {
|
|
117
124
|
return typeof x === 'string' || x instanceof String;
|
|
@@ -191,6 +198,7 @@ export class StreamChat<
|
|
|
191
198
|
wsConnection: StableWSConnection<ChannelType, CommandType, UserType> | null;
|
|
192
199
|
wsPromise: ConnectAPIResponse<ChannelType, CommandType, UserType> | null;
|
|
193
200
|
consecutiveFailures: number;
|
|
201
|
+
insightMetrics: InsightMetrics;
|
|
194
202
|
|
|
195
203
|
/**
|
|
196
204
|
* Initialize a client
|
|
@@ -285,6 +293,7 @@ export class StreamChat<
|
|
|
285
293
|
// generated from secret.
|
|
286
294
|
this.tokenManager = new TokenManager(this.secret);
|
|
287
295
|
this.consecutiveFailures = 0;
|
|
296
|
+
this.insightMetrics = new InsightMetrics();
|
|
288
297
|
|
|
289
298
|
/**
|
|
290
299
|
* logger function should accept 3 parameters:
|
|
@@ -729,6 +738,7 @@ export class StreamChat<
|
|
|
729
738
|
apnTemplate: '{}', //if app doesn't have apn configured it will error
|
|
730
739
|
firebaseTemplate: '{}', //if app doesn't have firebase configured it will error
|
|
731
740
|
firebaseDataTemplate: '{}', //if app doesn't have firebase configured it will error
|
|
741
|
+
huaweiDataTemplate: '{}' //if app doesn't have huawei configured it will error
|
|
732
742
|
skipDevices: true, // skip config/device checks and sending to real devices
|
|
733
743
|
}
|
|
734
744
|
*/
|
|
@@ -741,6 +751,9 @@ export class StreamChat<
|
|
|
741
751
|
...(data.firebaseDataTemplate
|
|
742
752
|
? { firebase_data_template: data.firebaseDataTemplate }
|
|
743
753
|
: {}),
|
|
754
|
+
...(data.huaweiDataTemplate
|
|
755
|
+
? { huawei_data_template: data.huaweiDataTemplate }
|
|
756
|
+
: {}),
|
|
744
757
|
...(data.skipDevices ? { skip_devices: true } : {}),
|
|
745
758
|
});
|
|
746
759
|
}
|
|
@@ -748,13 +761,11 @@ export class StreamChat<
|
|
|
748
761
|
/**
|
|
749
762
|
* testSQSSettings - Tests that the given or configured SQS configuration is valid
|
|
750
763
|
*
|
|
751
|
-
* @param {
|
|
752
|
-
* @param {TestPushDataInput} [data] Overrides for push templates/message used
|
|
764
|
+
* @param {TestSQSDataInput} [data] Overrides SQS settings for testing if needed
|
|
753
765
|
* IE: {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
firebaseDataTemplate: '{}', //if app doesn't have firebase configured it will error
|
|
766
|
+
sqs_key: 'auth_key',
|
|
767
|
+
sqs_secret: 'auth_secret',
|
|
768
|
+
sqs_url: 'url_to_queue',
|
|
758
769
|
}
|
|
759
770
|
*/
|
|
760
771
|
async testSQSSettings(data: TestSQSDataInput = {}) {
|
|
@@ -1609,6 +1620,7 @@ export class StreamChat<
|
|
|
1609
1620
|
}
|
|
1610
1621
|
|
|
1611
1622
|
// The StableWSConnection handles all the reconnection logic.
|
|
1623
|
+
|
|
1612
1624
|
this.wsConnection = new StableWSConnection<ChannelType, CommandType, UserType>({
|
|
1613
1625
|
wsBaseURL: client.wsBaseURL,
|
|
1614
1626
|
clientID: client.clientID,
|
|
@@ -1623,6 +1635,8 @@ export class StreamChat<
|
|
|
1623
1635
|
eventCallback: this.dispatchEvent as (event: ConnectionChangeEvent) => void,
|
|
1624
1636
|
logger: this.logger,
|
|
1625
1637
|
device: this.options.device,
|
|
1638
|
+
postInsights: this.options.enableInsights ? this.postInsights : undefined,
|
|
1639
|
+
insightMetrics: this.insightMetrics,
|
|
1626
1640
|
});
|
|
1627
1641
|
|
|
1628
1642
|
let warmUpPromise;
|
|
@@ -1890,7 +1904,7 @@ export class StreamChat<
|
|
|
1890
1904
|
*
|
|
1891
1905
|
* @param {BaseDeviceFields} device the device object
|
|
1892
1906
|
* @param {string} device.id device id
|
|
1893
|
-
* @param {string} device.push_provider the push provider
|
|
1907
|
+
* @param {string} device.push_provider the push provider
|
|
1894
1908
|
*
|
|
1895
1909
|
*/
|
|
1896
1910
|
setLocalDevice(device: BaseDeviceFields) {
|
|
@@ -1905,11 +1919,11 @@ export class StreamChat<
|
|
|
1905
1919
|
* addDevice - Adds a push device for a user.
|
|
1906
1920
|
*
|
|
1907
1921
|
* @param {string} id the device id
|
|
1908
|
-
* @param {
|
|
1922
|
+
* @param {PushProvider} push_provider the push provider
|
|
1909
1923
|
* @param {string} [userID] the user id (defaults to current user)
|
|
1910
1924
|
*
|
|
1911
1925
|
*/
|
|
1912
|
-
async addDevice(id: string, push_provider:
|
|
1926
|
+
async addDevice(id: string, push_provider: PushProvider, userID?: string) {
|
|
1913
1927
|
return await this.post<APIResponse>(this.baseURL + '/devices', {
|
|
1914
1928
|
id,
|
|
1915
1929
|
push_provider,
|
|
@@ -2505,12 +2519,24 @@ export class StreamChat<
|
|
|
2505
2519
|
}
|
|
2506
2520
|
|
|
2507
2521
|
/**
|
|
2522
|
+
* @deprecated use markChannelsRead instead
|
|
2523
|
+
*
|
|
2508
2524
|
* markAllRead - marks all channels for this user as read
|
|
2509
2525
|
* @param {MarkAllReadOptions<UserType>} [data]
|
|
2510
2526
|
*
|
|
2511
2527
|
* @return {Promise<APIResponse>}
|
|
2512
2528
|
*/
|
|
2513
|
-
|
|
2529
|
+
markAllRead = this.markChannelsRead;
|
|
2530
|
+
|
|
2531
|
+
/**
|
|
2532
|
+
* markChannelsRead - marks channels read -
|
|
2533
|
+
* it accepts a map of cid:messageid pairs, if messageid is empty, the whole channel will be marked as read
|
|
2534
|
+
*
|
|
2535
|
+
* @param {MarkChannelsReadOptions <UserType>} [data]
|
|
2536
|
+
*
|
|
2537
|
+
* @return {Promise<APIResponse>}
|
|
2538
|
+
*/
|
|
2539
|
+
async markChannelsRead(data: MarkChannelsReadOptions<UserType> = {}) {
|
|
2514
2540
|
await this.post<APIResponse>(this.baseURL + '/channels/read', {
|
|
2515
2541
|
...data,
|
|
2516
2542
|
});
|
|
@@ -2692,6 +2718,7 @@ export class StreamChat<
|
|
|
2692
2718
|
*
|
|
2693
2719
|
* @param {Omit<MessageResponse<AttachmentType, ChannelType, CommandType, MessageType, ReactionType, UserType>, 'mentioned_users'> & { mentioned_users?: string[] }} message object, id needs to be specified
|
|
2694
2720
|
* @param {string | { id: string }} [userId]
|
|
2721
|
+
* @param {boolean} [options.skip_enrich_url] Do not try to enrich the URLs within message
|
|
2695
2722
|
*
|
|
2696
2723
|
* @return {APIResponse & { message: MessageResponse<AttachmentType, ChannelType, CommandType, MessageType, ReactionType, UserType> }} Response that includes the message
|
|
2697
2724
|
*/
|
|
@@ -2705,6 +2732,7 @@ export class StreamChat<
|
|
|
2705
2732
|
UserType
|
|
2706
2733
|
>,
|
|
2707
2734
|
userId?: string | { id: string },
|
|
2735
|
+
options?: { skip_enrich_url?: boolean },
|
|
2708
2736
|
) {
|
|
2709
2737
|
if (!message.id) {
|
|
2710
2738
|
throw Error('Please specify the message id when calling updateMessage');
|
|
@@ -2777,6 +2805,7 @@ export class StreamChat<
|
|
|
2777
2805
|
>
|
|
2778
2806
|
>(this.baseURL + `/messages/${message.id}`, {
|
|
2779
2807
|
message: clonedMessage,
|
|
2808
|
+
...options,
|
|
2780
2809
|
});
|
|
2781
2810
|
}
|
|
2782
2811
|
|
|
@@ -2789,12 +2818,15 @@ export class StreamChat<
|
|
|
2789
2818
|
* example: {id: "user1", set:{text: "hi"}, unset:["color"]}
|
|
2790
2819
|
* @param {string | { id: string }} [userId]
|
|
2791
2820
|
*
|
|
2821
|
+
* @param {boolean} [options.skip_enrich_url] Do not try to enrich the URLs within message
|
|
2822
|
+
*
|
|
2792
2823
|
* @return {APIResponse & { message: MessageResponse<AttachmentType, ChannelType, CommandType, MessageType, ReactionType, UserType> }} Response that includes the updated message
|
|
2793
2824
|
*/
|
|
2794
2825
|
async partialUpdateMessage(
|
|
2795
2826
|
id: string,
|
|
2796
2827
|
partialMessageObject: PartialMessageUpdate<MessageType>,
|
|
2797
2828
|
userId?: string | { id: string },
|
|
2829
|
+
options?: { skip_enrich_url?: boolean },
|
|
2798
2830
|
) {
|
|
2799
2831
|
if (!id) {
|
|
2800
2832
|
throw Error('Please specify the message id when calling partialUpdateMessage');
|
|
@@ -2814,6 +2846,7 @@ export class StreamChat<
|
|
|
2814
2846
|
>
|
|
2815
2847
|
>(this.baseURL + `/messages/${id}`, {
|
|
2816
2848
|
...partialMessageObject,
|
|
2849
|
+
...options,
|
|
2817
2850
|
user,
|
|
2818
2851
|
});
|
|
2819
2852
|
}
|
|
@@ -3279,4 +3312,95 @@ export class StreamChat<
|
|
|
3279
3312
|
);
|
|
3280
3313
|
return campaign;
|
|
3281
3314
|
}
|
|
3315
|
+
|
|
3316
|
+
/**
|
|
3317
|
+
* enrichURL - Get OpenGraph data of the given link
|
|
3318
|
+
*
|
|
3319
|
+
* @param {string} url link
|
|
3320
|
+
* @return {OGAttachment} OG Attachment
|
|
3321
|
+
*/
|
|
3322
|
+
async enrichURL(url: string) {
|
|
3323
|
+
return this.get<APIResponse & OGAttachment>(this.baseURL + `/og`, { url });
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
/**
|
|
3327
|
+
* getTask - Gets status of a long running task
|
|
3328
|
+
*
|
|
3329
|
+
* @param {string} id Task ID
|
|
3330
|
+
*
|
|
3331
|
+
* @return {TaskStatus} The task status
|
|
3332
|
+
*/
|
|
3333
|
+
async getTask(id: string) {
|
|
3334
|
+
return this.get<APIResponse & TaskStatus>(`${this.baseURL}/tasks/${id}`);
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
/**
|
|
3338
|
+
* deleteChannels - Deletes a list of channel
|
|
3339
|
+
*
|
|
3340
|
+
* @param {string[]} cids Channel CIDs
|
|
3341
|
+
* @param {boolean} [options.hard_delete] Defines if the channel is hard deleted or not
|
|
3342
|
+
*
|
|
3343
|
+
* @return {DeleteChannelsResponse} Result of the soft deletion, if server-side, it holds the task ID as well
|
|
3344
|
+
*/
|
|
3345
|
+
async deleteChannels(cids: string[], options: { hard_delete?: boolean } = {}) {
|
|
3346
|
+
return await this.post<APIResponse & DeleteChannelsResponse>(
|
|
3347
|
+
this.baseURL + `/channels/delete`,
|
|
3348
|
+
{ cids, ...options },
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
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
|
+
|
|
3373
|
+
/**
|
|
3374
|
+
* deleteUsers - Batch Delete Users
|
|
3375
|
+
*
|
|
3376
|
+
* @param {string[]} user_ids which users to delete
|
|
3377
|
+
* @param {DeleteUserOptions} options Configuration how to delete users
|
|
3378
|
+
*
|
|
3379
|
+
* @return {APIResponse} A task ID
|
|
3380
|
+
*/
|
|
3381
|
+
async deleteUsers(user_ids: string[], options: DeleteUserOptions) {
|
|
3382
|
+
if (options?.user !== 'soft' && options?.user !== 'hard') {
|
|
3383
|
+
throw new Error('Invalid delete user options. user must be one of [soft hard]');
|
|
3384
|
+
}
|
|
3385
|
+
if (
|
|
3386
|
+
options.messages !== undefined &&
|
|
3387
|
+
options.messages !== 'soft' &&
|
|
3388
|
+
options.messages !== 'hard'
|
|
3389
|
+
) {
|
|
3390
|
+
throw new Error('Invalid delete user options. messages must be one of [soft hard]');
|
|
3391
|
+
}
|
|
3392
|
+
if (
|
|
3393
|
+
options.conversations !== undefined &&
|
|
3394
|
+
options.conversations !== 'soft' &&
|
|
3395
|
+
options.conversations !== 'hard'
|
|
3396
|
+
) {
|
|
3397
|
+
throw new Error(
|
|
3398
|
+
'Invalid delete user options. conversations must be one of [soft hard]',
|
|
3399
|
+
);
|
|
3400
|
+
}
|
|
3401
|
+
return await this.post<APIResponse & TaskResponse>(this.baseURL + `/users/delete`, {
|
|
3402
|
+
user_ids,
|
|
3403
|
+
...options,
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3282
3406
|
}
|
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,
|
|
@@ -701,25 +735,20 @@ export class StableWSConnection<
|
|
|
701
735
|
|
|
702
736
|
/**
|
|
703
737
|
* _listenForConnectionChanges - Adds an event listener for the browser going online or offline
|
|
704
|
-
*
|
|
705
738
|
*/
|
|
706
739
|
_listenForConnectionChanges = () => {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
window.addEventListener != null
|
|
711
|
-
) {
|
|
740
|
+
// (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
|
|
741
|
+
// and thus (window === undefined) will result in ReferenceError.
|
|
742
|
+
if (typeof window !== 'undefined' && window?.addEventListener) {
|
|
712
743
|
window.addEventListener('offline', this.onlineStatusChanged);
|
|
713
744
|
window.addEventListener('online', this.onlineStatusChanged);
|
|
714
745
|
}
|
|
715
746
|
};
|
|
716
747
|
|
|
717
748
|
_removeConnectionListeners = () => {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
window.addEventListener != null
|
|
722
|
-
) {
|
|
749
|
+
// (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
|
|
750
|
+
// and thus (window === undefined) will result in ReferenceError.
|
|
751
|
+
if (typeof window !== 'undefined' && window?.removeEventListener) {
|
|
723
752
|
window.removeEventListener('offline', this.onlineStatusChanged);
|
|
724
753
|
window.removeEventListener('online', this.onlineStatusChanged);
|
|
725
754
|
}
|
|
@@ -796,7 +825,6 @@ export class StableWSConnection<
|
|
|
796
825
|
{
|
|
797
826
|
type: 'health.check',
|
|
798
827
|
client_id: this.clientID,
|
|
799
|
-
user_id: this.userID,
|
|
800
828
|
},
|
|
801
829
|
];
|
|
802
830
|
// try to send on the connection
|
package/src/index.ts
CHANGED
package/src/insights.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StableWSConnection } from './connection';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
import { LiteralStringForUnion, UnknownType } from './types';
|
|
4
|
+
|
|
5
|
+
export type InsightTypes = 'ws_fatal' | 'ws_success_after_failure';
|
|
6
|
+
export class InsightMetrics {
|
|
7
|
+
connectionStartTimestamp: number | null;
|
|
8
|
+
wsConsecutiveFailures: number;
|
|
9
|
+
wsTotalFailures: number;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.connectionStartTimestamp = null;
|
|
13
|
+
this.wsTotalFailures = 0;
|
|
14
|
+
this.wsConsecutiveFailures = 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildWsFatalInsight<
|
|
19
|
+
ChannelType extends UnknownType = UnknownType,
|
|
20
|
+
CommandType extends string = LiteralStringForUnion,
|
|
21
|
+
UserType extends UnknownType = UnknownType
|
|
22
|
+
>(
|
|
23
|
+
connection: StableWSConnection<ChannelType, CommandType, UserType>,
|
|
24
|
+
event: WebSocket.CloseEvent,
|
|
25
|
+
) {
|
|
26
|
+
return {
|
|
27
|
+
err: {
|
|
28
|
+
wasClean: event.wasClean,
|
|
29
|
+
code: event.code,
|
|
30
|
+
reason: event.reason,
|
|
31
|
+
},
|
|
32
|
+
...buildWsBaseInsight(connection),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildWsBaseInsight<
|
|
37
|
+
ChannelType extends UnknownType = UnknownType,
|
|
38
|
+
CommandType extends string = LiteralStringForUnion,
|
|
39
|
+
UserType extends UnknownType = UnknownType
|
|
40
|
+
>(connection: StableWSConnection<ChannelType, CommandType, UserType>) {
|
|
41
|
+
return {
|
|
42
|
+
ready_state: connection.ws?.readyState,
|
|
43
|
+
url: connection._buildUrl(connection.requestID),
|
|
44
|
+
api_key: connection.apiKey,
|
|
45
|
+
start_ts: connection.insightMetrics.connectionStartTimestamp,
|
|
46
|
+
end_ts: new Date().getTime(),
|
|
47
|
+
auth_type: connection.authType,
|
|
48
|
+
token: connection.tokenManager.token,
|
|
49
|
+
user_id: connection.userID,
|
|
50
|
+
user_details: connection.user,
|
|
51
|
+
device: connection.device,
|
|
52
|
+
client_id: connection.connectionID,
|
|
53
|
+
ws_details: connection.ws,
|
|
54
|
+
ws_consecutive_failures: connection.insightMetrics.wsConsecutiveFailures,
|
|
55
|
+
ws_total_failures: connection.insightMetrics.wsTotalFailures,
|
|
56
|
+
request_id: connection.requestID,
|
|
57
|
+
online: typeof navigator !== 'undefined' ? navigator?.onLine : null,
|
|
58
|
+
user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function buildWsSuccessAfterFailureInsight<
|
|
63
|
+
ChannelType extends UnknownType = UnknownType,
|
|
64
|
+
CommandType extends string = LiteralStringForUnion,
|
|
65
|
+
UserType extends UnknownType = UnknownType
|
|
66
|
+
>(connection: StableWSConnection<ChannelType, CommandType, UserType>) {
|
|
67
|
+
return buildWsBaseInsight(connection);
|
|
68
|
+
}
|