stream-chat 9.25.0 → 9.26.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.
@@ -3,16 +3,20 @@ import { Channel } from '../channel';
3
3
  import type { ThreadUserReadState } from '../thread';
4
4
  import { Thread } from '../thread';
5
5
  import type {
6
+ ErrorFromResponse,
6
7
  EventAPIResponse,
7
8
  LocalMessage,
8
9
  MarkDeliveredOptions,
9
10
  MarkReadOptions,
10
11
  } from '../types';
12
+ import { type APIErrorResponse } from '../types';
11
13
  import { throttle } from '../utils';
14
+ import { isAPIError, isErrorRetryable } from '../errors';
12
15
 
13
16
  const MAX_DELIVERED_MESSAGE_COUNT_IN_PAYLOAD = 100 as const;
14
17
  const MARK_AS_DELIVERED_BUFFER_TIMEOUT = 1000 as const;
15
18
  const MARK_AS_READ_THROTTLE_TIMEOUT = 1000 as const;
19
+ const RETRY_COUNT_LIMIT_FOR_TIMEOUT_INCREASE = 3 as const;
16
20
 
17
21
  const isChannel = (item: Channel | Thread): item is Channel => item instanceof Channel;
18
22
  const isThread = (item: Channel | Thread): item is Thread => item instanceof Thread;
@@ -40,6 +44,10 @@ export class MessageDeliveryReporter {
40
44
  protected markDeliveredRequestPromise: Promise<EventAPIResponse | void> | null = null;
41
45
  protected markDeliveredTimeout: ReturnType<typeof setTimeout> | null = null;
42
46
 
47
+ protected requestTimeoutMs: number = MARK_AS_DELIVERED_BUFFER_TIMEOUT;
48
+ // increased up to RETRY_COUNT_LIMIT_FOR_TIMEOUT_INCREASE
49
+ protected requestRetryCount: number = 0;
50
+
43
51
  constructor({ client }: MessageDeliveryReporterOptions) {
44
52
  this.client = client;
45
53
  }
@@ -47,18 +55,35 @@ export class MessageDeliveryReporter {
47
55
  private get markDeliveredRequestInFlight() {
48
56
  return this.markDeliveredRequestPromise !== null;
49
57
  }
58
+
50
59
  private get hasTimer() {
51
60
  return this.markDeliveredTimeout !== null;
52
61
  }
62
+
53
63
  private get hasDeliveryCandidates() {
54
64
  return this.deliveryReportCandidates.size > 0;
55
65
  }
56
66
 
67
+ private get canExecuteRequest() {
68
+ return !this.markDeliveredRequestInFlight && this.hasDeliveryCandidates;
69
+ }
70
+
57
71
  private static hasPermissionToReportDeliveryFor(collection: Channel | Thread) {
58
72
  if (isChannel(collection)) return !!collection.getConfig()?.delivery_events;
59
73
  if (isThread(collection)) return !!collection.channel.getConfig()?.delivery_events;
60
74
  }
61
75
 
76
+ private increaseBackOff() {
77
+ if (this.requestRetryCount >= RETRY_COUNT_LIMIT_FOR_TIMEOUT_INCREASE) return;
78
+ this.requestRetryCount = this.requestRetryCount + 1;
79
+ this.requestTimeoutMs = this.requestTimeoutMs * 2;
80
+ }
81
+
82
+ private resetBackOff() {
83
+ this.requestTimeoutMs = MARK_AS_DELIVERED_BUFFER_TIMEOUT;
84
+ this.requestRetryCount = 0;
85
+ }
86
+
62
87
  /**
63
88
  * Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
64
89
  */
@@ -186,7 +211,7 @@ export class MessageDeliveryReporter {
186
211
  * @param options
187
212
  */
188
213
  public announceDelivery = (options?: AnnounceDeliveryOptions) => {
189
- if (this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates) return;
214
+ if (!this.canExecuteRequest) return;
190
215
 
191
216
  const { latest_delivered_messages, sendBuffer } =
192
217
  this.confirmationsFromDeliveryReportCandidates();
@@ -194,7 +219,9 @@ export class MessageDeliveryReporter {
194
219
 
195
220
  const payload = { ...options, latest_delivered_messages };
196
221
 
197
- const postFlightReconcile = () => {
222
+ const postFlightReconcile = ({
223
+ preventSchedulingRetry,
224
+ }: { preventSchedulingRetry?: boolean } = {}) => {
198
225
  this.markDeliveredRequestPromise = null;
199
226
 
200
227
  // promote anything that arrived during request
@@ -203,32 +230,47 @@ export class MessageDeliveryReporter {
203
230
  }
204
231
  this.nextDeliveryReportCandidates = new Map();
205
232
 
233
+ if (preventSchedulingRetry) return;
206
234
  // checks internally whether there are candidates to announce
207
235
  this.announceDeliveryBuffered(options);
208
236
  };
209
237
 
210
- const handleError = () => {
211
- // repopulate relevant candidates for the next report
212
- for (const [k, v] of Object.entries(sendBuffer)) {
213
- if (!this.deliveryReportCandidates.has(k)) {
214
- this.deliveryReportCandidates.set(k, v);
215
- }
216
- }
238
+ const handleSuccess = () => {
239
+ this.resetBackOff();
217
240
  postFlightReconcile();
218
241
  };
219
242
 
243
+ const handleError = (error: ErrorFromResponse<APIErrorResponse> | Error) => {
244
+ // re-populate relevant candidates for the next report
245
+ // but make sure to keep the items that failed to be reported the first next time
246
+ const newDeliveryReportCandidates = new Map(sendBuffer);
247
+ for (const [k, v] of this.deliveryReportCandidates.entries()) {
248
+ newDeliveryReportCandidates.set(k, v);
249
+ }
250
+ this.deliveryReportCandidates = newDeliveryReportCandidates;
251
+
252
+ if (
253
+ (isAPIError(error) && isErrorRetryable(error)) ||
254
+ (error as ErrorFromResponse<APIErrorResponse>).status >= 500
255
+ ) {
256
+ this.increaseBackOff();
257
+ postFlightReconcile();
258
+ } else {
259
+ postFlightReconcile({ preventSchedulingRetry: true });
260
+ }
261
+ };
262
+
220
263
  this.markDeliveredRequestPromise = this.client
221
264
  .markChannelsDelivered(payload)
222
- .then(postFlightReconcile, handleError);
265
+ .then(handleSuccess, handleError);
223
266
  };
224
267
 
225
268
  public announceDeliveryBuffered = (options?: AnnounceDeliveryOptions) => {
226
- if (this.hasTimer || this.markDeliveredRequestInFlight || !this.hasDeliveryCandidates)
227
- return;
269
+ if (this.hasTimer || !this.canExecuteRequest) return;
228
270
  this.markDeliveredTimeout = setTimeout(() => {
229
271
  this.markDeliveredTimeout = null;
230
272
  this.announceDelivery(options);
231
- }, MARK_AS_DELIVERED_BUFFER_TIMEOUT);
273
+ }, this.requestTimeoutMs);
232
274
  };
233
275
 
234
276
  /**
package/src/types.ts CHANGED
@@ -71,6 +71,7 @@ export type Unpacked<T> = T extends (infer U)[]
71
71
 
72
72
  export type APIResponse = {
73
73
  duration: string;
74
+ blocklist?: BlockListResponse;
74
75
  };
75
76
 
76
77
  export type TranslateResponse = {
@@ -80,6 +81,8 @@ export type TranslateResponse = {
80
81
 
81
82
  export type AppSettingsAPIResponse = APIResponse & {
82
83
  app?: {
84
+ id?: string | number;
85
+ allow_multi_user_devices?: boolean;
83
86
  // TODO
84
87
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
88
  call_types: any;
@@ -108,6 +111,9 @@ export type AppSettingsAPIResponse = APIResponse & {
108
111
  read_events?: boolean;
109
112
  replies?: boolean;
110
113
  search?: boolean;
114
+ shared_locations?: boolean;
115
+ skip_last_msg_update_for_system_msgs?: boolean;
116
+ count_messages?: boolean;
111
117
  typing_events?: boolean;
112
118
  updated_at?: string;
113
119
  uploads?: boolean;
@@ -140,12 +146,28 @@ export type AppSettingsAPIResponse = APIResponse & {
140
146
  type: string;
141
147
  }>;
142
148
  grants?: Record<string, string[]>;
149
+ guest_user_creation_disabled?: boolean;
143
150
  image_moderation_enabled?: boolean;
151
+ image_moderation_labels?: string[];
144
152
  image_upload_config?: FileUploadConfig;
153
+ allowed_flag_reasons?: string[];
154
+ max_aggregated_activities_length?: number;
155
+ moderation_bulk_submit_action_enabled?: boolean;
156
+ moderation_dashboard_preferences?: Record<string, unknown> | null;
157
+ moderation_enabled?: boolean;
158
+ moderation_llm_configurability_enabled?: boolean;
159
+ moderation_multitenant_blocklist_enabled?: boolean;
160
+ moderation_webhook_url?: string;
145
161
  multi_tenant_enabled?: boolean;
146
162
  name?: string;
147
163
  organization?: string;
148
164
  permission_version?: string;
165
+ /**
166
+ * The placement of the app in the form of `${region}.${shard}`.
167
+ * Examples: "us-east.c1", "dublin.c3", "singapore.c2"
168
+ * Note: The backend may add/remove regions or shards occasionally.
169
+ */
170
+ placement?: string;
149
171
  policies?: Record<string, Policy[]>;
150
172
  poll_enabled?: boolean;
151
173
  push_notifications?: {
@@ -167,6 +189,8 @@ export type AppSettingsAPIResponse = APIResponse & {
167
189
  sqs_url?: string;
168
190
  suspended?: boolean;
169
191
  suspended_explanation?: string;
192
+ use_hook_v2?: boolean;
193
+ user_response_time_enabled?: boolean;
170
194
  user_search_disallowed_roles?: string[] | null;
171
195
  video_provider?: string;
172
196
  webhook_events?: Array<string>;
@@ -282,6 +306,7 @@ export type ChannelResponse = CustomChannelData & {
282
306
  created_by?: UserResponse | null;
283
307
  created_by_id?: string;
284
308
  deleted_at?: string;
309
+ filter_tags?: string[];
285
310
  hidden?: boolean;
286
311
  invites?: string[];
287
312
  joined?: boolean;
@@ -331,6 +356,7 @@ export type ChannelAPIResponse = {
331
356
 
332
357
  export type ChannelUpdateOptions = {
333
358
  hide_history?: boolean;
359
+ hide_history_before?: string | Date;
334
360
  skip_push?: boolean;
335
361
  };
336
362
 
@@ -786,6 +812,7 @@ export type OwnUserBase = {
786
812
  privacy_settings?: PrivacySettings;
787
813
  push_preferences?: PushPreference;
788
814
  roles?: string[];
815
+ total_unread_count_by_team?: Record<string, number> | null;
789
816
  };
790
817
 
791
818
  export type OwnUserResponse = UserResponse & OwnUserBase;
@@ -882,10 +909,12 @@ export type UpdateMessageAPIResponse = APIResponse & {
882
909
 
883
910
  export type UsersAPIResponse = APIResponse & {
884
911
  users: Array<UserResponse>;
912
+ membership_deletion_task_id?: string;
885
913
  };
886
914
 
887
915
  export type UpdateUsersAPIResponse = APIResponse & {
888
916
  users: { [key: string]: UserResponse };
917
+ membership_deletion_task_id?: string;
889
918
  };
890
919
 
891
920
  export type UserResponse = CustomUserData & {
@@ -908,7 +937,7 @@ export type UserResponse = CustomUserData & {
908
937
  role?: string;
909
938
  shadow_banned?: boolean;
910
939
  teams?: string[];
911
- teams_role?: TeamsRole;
940
+ teams_role?: TeamsRole | null;
912
941
  updated_at?: string;
913
942
  username?: string;
914
943
  avg_response_time?: number;
@@ -1032,11 +1061,13 @@ export type CreateChannelOptions = {
1032
1061
  reminders?: boolean;
1033
1062
  replies?: boolean;
1034
1063
  search?: boolean;
1064
+ shared_locations?: boolean;
1035
1065
  skip_last_msg_update_for_system_msgs?: boolean;
1036
1066
  typing_events?: boolean;
1037
1067
  uploads?: boolean;
1038
1068
  url_enrichment?: boolean;
1039
1069
  user_message_reminders?: boolean;
1070
+ count_messages?: boolean;
1040
1071
  };
1041
1072
 
1042
1073
  export type CreateCommandOptions = {
@@ -1064,9 +1095,8 @@ export type DeactivateUsersOptions = {
1064
1095
  export type NewMemberPayload = CustomMemberData &
1065
1096
  Pick<ChannelMemberResponse, 'user_id' | 'channel_role'>;
1066
1097
 
1067
- export type Thresholds = Record<
1068
- 'explicit' | 'spam' | 'toxic',
1069
- Partial<{ block: number; flag: number }>
1098
+ export type Thresholds = Partial<
1099
+ Record<'explicit' | 'spam' | 'toxic', Partial<{ block: number; flag: number }>>
1070
1100
  >;
1071
1101
 
1072
1102
  export type BlockListOptions = {
@@ -1170,6 +1200,7 @@ export type UpdateChannelTypeResponse = {
1170
1200
  reminders: boolean;
1171
1201
  replies: boolean;
1172
1202
  search: boolean;
1203
+ shared_locations: boolean;
1173
1204
  skip_last_msg_update_for_system_msgs: boolean;
1174
1205
  typing_events: boolean;
1175
1206
  updated_at: string;
@@ -1180,9 +1211,11 @@ export type UpdateChannelTypeResponse = {
1180
1211
  blocklist?: string;
1181
1212
  blocklist_behavior?: BlocklistBehavior;
1182
1213
  blocklists?: BlockListOptions[];
1214
+ message_retention?: string;
1183
1215
  partition_size?: number;
1184
1216
  partition_ttl?: string;
1185
1217
  count_messages?: boolean;
1218
+ user_message_reminders?: boolean;
1186
1219
  };
1187
1220
 
1188
1221
  export type GetChannelTypeResponse = {
@@ -1208,6 +1241,7 @@ export type GetChannelTypeResponse = {
1208
1241
  reminders: boolean;
1209
1242
  replies: boolean;
1210
1243
  search: boolean;
1244
+ shared_locations: boolean;
1211
1245
  skip_last_msg_update_for_system_msgs: boolean;
1212
1246
  typing_events: boolean;
1213
1247
  updated_at: string;
@@ -1218,9 +1252,11 @@ export type GetChannelTypeResponse = {
1218
1252
  blocklist?: string;
1219
1253
  blocklist_behavior?: BlocklistBehavior;
1220
1254
  blocklists?: BlockListOptions[];
1255
+ message_retention?: string;
1221
1256
  partition_size?: number;
1222
1257
  partition_ttl?: string;
1223
1258
  count_messages?: boolean;
1259
+ user_message_reminders?: boolean;
1224
1260
  };
1225
1261
 
1226
1262
  export type UpdateChannelOptions = Partial<{
@@ -1260,6 +1296,7 @@ export type MarkUnreadOptions = {
1260
1296
  connection_id?: string;
1261
1297
  message_id?: string;
1262
1298
  thread_id?: string;
1299
+ message_timestamp?: string | Date;
1263
1300
  user?: UserResponse;
1264
1301
  user_id?: string;
1265
1302
  };
@@ -1751,6 +1788,8 @@ export type ReactionFilters = QueryFilters<
1751
1788
 
1752
1789
  export type ChannelFilters = QueryFilters<
1753
1790
  ContainsOperator<Omit<CustomChannelData, 'name'>> & {
1791
+ app_banned?: 'only' | 'excluded';
1792
+ has_unread?: boolean;
1754
1793
  archived?: boolean;
1755
1794
  'member.user.name'?:
1756
1795
  | RequireOnlyOne<{
@@ -2044,7 +2083,7 @@ export type UserFilters = QueryFilters<
2044
2083
  }
2045
2084
  >;
2046
2085
 
2047
- export type InviteStatus = 'pending' | 'accepted' | 'rejected';
2086
+ export type InviteStatus = 'pending' | 'accepted' | 'rejected' | 'member';
2048
2087
 
2049
2088
  // https://getstream.io/chat/docs/react/channel_member/#update-channel-members
2050
2089
  export type MemberFilters = QueryFilters<
@@ -2368,6 +2407,8 @@ export type BlockList = {
2368
2407
  team?: string;
2369
2408
  type?: string;
2370
2409
  validate?: boolean;
2410
+ is_leet_check_enabled?: boolean;
2411
+ is_plural_check_enabled?: boolean;
2371
2412
  };
2372
2413
 
2373
2414
  export type ChannelConfig = ChannelConfigFields &
@@ -2403,6 +2444,7 @@ export type ChannelConfigFields = {
2403
2444
  replies?: boolean;
2404
2445
  search?: boolean;
2405
2446
  shared_locations?: boolean;
2447
+ skip_last_msg_update_for_system_msgs?: boolean;
2406
2448
  count_messages?: boolean;
2407
2449
  typing_events?: boolean;
2408
2450
  uploads?: boolean;
@@ -2423,6 +2465,7 @@ export type ChannelData = CustomChannelData &
2423
2465
  members: string[] | Array<NewMemberPayload>;
2424
2466
  blocklist_behavior: AutomodBehavior;
2425
2467
  automod: Automod;
2468
+ filter_tags: string[];
2426
2469
  }>;
2427
2470
 
2428
2471
  export type ChannelMute = {
@@ -3153,6 +3196,11 @@ export type CampaignData = {
3153
3196
  custom?: {};
3154
3197
  id?: string;
3155
3198
  members?: string[];
3199
+ members_template?: Array<{
3200
+ user_id: string;
3201
+ channel_role?: string;
3202
+ custom?: Record<string, unknown>;
3203
+ }>;
3156
3204
  team?: string;
3157
3205
  };
3158
3206
  create_channels?: boolean;
@@ -4435,6 +4483,7 @@ export type EventHook = {
4435
4483
  sns_key?: string;
4436
4484
  sns_secret?: string;
4437
4485
  sns_role_arn?: string;
4486
+ should_send_custom_events?: boolean;
4438
4487
 
4439
4488
  // pending message config
4440
4489
  timeout_ms?: number;
package/src/utils.ts CHANGED
@@ -104,6 +104,7 @@ export function isOwnUserBaseProperty(property: string) {
104
104
  privacy_settings: true,
105
105
  roles: true,
106
106
  push_preferences: true,
107
+ total_unread_count_by_team: true,
107
108
  };
108
109
 
109
110
  return ownUserBaseProperties[property as keyof OwnUserBase];