stream-chat 9.2.0 → 9.3.0

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.
@@ -139,7 +139,7 @@ export declare class Channel {
139
139
  sendReaction(messageID: string, reaction: Reaction, options?: {
140
140
  enforce_unique?: boolean;
141
141
  skip_push?: boolean;
142
- }): Promise<ReactionAPIResponse | undefined>;
142
+ }): Promise<ReactionAPIResponse>;
143
143
  /**
144
144
  * sendReaction - Send a reaction about a message
145
145
  *
@@ -152,10 +152,8 @@ export declare class Channel {
152
152
  _sendReaction(messageID: string, reaction: Reaction, options?: {
153
153
  enforce_unique?: boolean;
154
154
  skip_push?: boolean;
155
- }): Promise<ReactionAPIResponse | undefined>;
156
- deleteReaction(messageID: string, reactionType: string, user_id?: string): Promise<(APIResponse & {
157
- message: MessageResponse;
158
- }) | undefined>;
155
+ }): Promise<ReactionAPIResponse>;
156
+ deleteReaction(messageID: string, reactionType: string, user_id?: string): Promise<ReactionAPIResponse>;
159
157
  /**
160
158
  * deleteReaction - Delete a reaction by user and type
161
159
  *
@@ -227,7 +227,24 @@ export declare class StreamChat {
227
227
  'data_template': 'data handlebars template',
228
228
  'apn_template': 'apn notification handlebars template under v2'
229
229
  },
230
- 'webhook_url': 'https://acme.com/my/awesome/webhook/'
230
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/',
231
+ 'event_hooks': [
232
+ {
233
+ 'hook_type': 'webhook',
234
+ 'enabled': true,
235
+ 'event_types': ['message.new'],
236
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/'
237
+ },
238
+ {
239
+ 'hook_type': 'sqs',
240
+ 'enabled': true,
241
+ 'event_types': ['message.new'],
242
+ 'sqs_url': 'https://sqs.us-east-1.amazonaws.com/1234567890/my-queue',
243
+ 'sqs_auth_type': 'key',
244
+ 'sqs_key': 'my-access-key',
245
+ 'sqs_secret': 'my-secret-key'
246
+ }
247
+ ]
231
248
  }
232
249
  */
233
250
  updateAppSettings(options: AppSettings): Promise<APIResponse>;
@@ -1103,9 +1120,9 @@ export declare class StreamChat {
1103
1120
  partialUpdateMessage(id: string, partialMessageObject: PartialMessageUpdate, partialUserOrUserId?: string | {
1104
1121
  id: string;
1105
1122
  }, options?: UpdateMessageOptions): Promise<UpdateMessageAPIResponse>;
1106
- deleteMessage(messageID: string, hardDelete?: boolean): Promise<(APIResponse & {
1123
+ deleteMessage(messageID: string, hardDelete?: boolean): Promise<APIResponse & {
1107
1124
  message: MessageResponse;
1108
- }) | undefined>;
1125
+ }>;
1109
1126
  _deleteMessage(messageID: string, hardDelete?: boolean): Promise<APIResponse & {
1110
1127
  message: MessageResponse;
1111
1128
  }>;
@@ -522,11 +522,9 @@ export declare abstract class AbstractOfflineDB implements OfflineDBApi {
522
522
  * It will return the response from the execution if it succeeded.
523
523
  * @param task - the pending task we want to execute
524
524
  */
525
- queueTask: ({ task }: {
525
+ queueTask: <T>({ task }: {
526
526
  task: PendingTask;
527
- }) => Promise<(import("..").APIResponse & {
528
- message: import("..").MessageResponse;
529
- }) | undefined>;
527
+ }) => Promise<T>;
530
528
  /**
531
529
  * A utility method that determines if a failed task should be added to the
532
530
  * queue based on its error.
@@ -1,4 +1,4 @@
1
- import type { AppSettingsAPIResponse, ChannelAPIResponse, ChannelFilters, ChannelMemberResponse, ChannelResponse, ChannelSort, LocalMessage, Message, MessageResponse, PollResponse, ReactionFilters, ReactionResponse, ReactionSort, ReadResponse } from '../types';
1
+ import type { AppSettingsAPIResponse, ChannelAPIResponse, ChannelFilters, ChannelMemberResponse, ChannelResponse, ChannelSort, LocalMessage, MessageResponse, PollResponse, ReactionFilters, ReactionResponse, ReactionSort, ReadResponse } from '../types';
2
2
  import type { Channel } from '../channel';
3
3
  import type { StreamChat } from '../client';
4
4
  export type PrepareBatchDBQueries = [string] | [string, Array<unknown> | Array<Array<unknown>>];
@@ -322,6 +322,16 @@ export type PendingTask = {
322
322
  payload: Parameters<Channel['sendMessage']>;
323
323
  type: PendingTaskTypes['sendMessage'];
324
324
  });
325
- export type PendingTaskExtraData = {
326
- message?: Message;
327
- };
325
+ export type OfflineErrorType = 'connection:lost';
326
+ export declare class OfflineError extends Error {
327
+ type: OfflineErrorType;
328
+ name: string;
329
+ constructor(message: string, { type, }: {
330
+ type: OfflineError['type'];
331
+ });
332
+ toJSON(): {
333
+ message: string;
334
+ stack: string | undefined;
335
+ name: string;
336
+ };
337
+ }
@@ -90,6 +90,7 @@ export type AppSettingsAPIResponse = APIResponse & {
90
90
  disable_auth_checks?: boolean;
91
91
  disable_permissions_checks?: boolean;
92
92
  enforce_unique_usernames?: 'no' | 'app' | 'team';
93
+ event_hooks?: Array<EventHook>;
93
94
  file_upload_config?: FileUploadConfig;
94
95
  geofences?: Array<{
95
96
  country_codes: Array<string>;
@@ -1698,6 +1699,7 @@ export type AppSettings = {
1698
1699
  disable_auth_checks?: boolean;
1699
1700
  disable_permissions_checks?: boolean;
1700
1701
  enforce_unique_usernames?: 'no' | 'app' | 'team';
1702
+ event_hooks?: Array<EventHook> | null;
1701
1703
  file_upload_config?: FileUploadConfig;
1702
1704
  firebase_config?: {
1703
1705
  apn_template?: string;
@@ -2905,4 +2907,26 @@ export type ThreadFilters = QueryFilters<{
2905
2907
  } & {
2906
2908
  last_message_at?: RequireOnlyOne<Pick<QueryFilter<ThreadResponse['last_message_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>> | PrimitiveFilter<ThreadResponse['last_message_at']>;
2907
2909
  }>;
2910
+ export type HookType = 'webhook' | 'sqs' | 'sns';
2911
+ export type EventHook = {
2912
+ id?: string;
2913
+ hook_type?: HookType;
2914
+ enabled?: boolean;
2915
+ event_types?: Array<string>;
2916
+ webhook_url?: string;
2917
+ sqs_queue_url?: string;
2918
+ sqs_region?: string;
2919
+ sqs_auth_type?: string;
2920
+ sqs_key?: string;
2921
+ sqs_secret?: string;
2922
+ sqs_role_arn?: string;
2923
+ sns_topic_arn?: string;
2924
+ sns_region?: string;
2925
+ sns_auth_type?: string;
2926
+ sns_key?: string;
2927
+ sns_secret?: string;
2928
+ sns_role_arn?: string;
2929
+ created_at?: string;
2930
+ updated_at?: string;
2931
+ };
2908
2932
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
package/src/channel.ts CHANGED
@@ -207,7 +207,7 @@ export class Channel {
207
207
  if (offlineDb) {
208
208
  const messageId = message.id;
209
209
  if (messageId) {
210
- return (await offlineDb.queueTask({
210
+ return await offlineDb.queueTask<SendMessageAPIResponse>({
211
211
  task: {
212
212
  channelId: this.id as string,
213
213
  channelType: this.type,
@@ -215,7 +215,7 @@ export class Channel {
215
215
  payload: [message, options],
216
216
  type: 'send-message',
217
217
  },
218
- })) as SendMessageAPIResponse;
218
+ });
219
219
  }
220
220
  }
221
221
  } catch (error) {
@@ -410,7 +410,7 @@ export class Channel {
410
410
  messageID: string,
411
411
  reaction: Reaction,
412
412
  options?: { enforce_unique?: boolean; skip_push?: boolean },
413
- ): Promise<ReactionAPIResponse | undefined> {
413
+ ) {
414
414
  if (!messageID) {
415
415
  throw Error(`Message id is missing`);
416
416
  }
@@ -454,7 +454,7 @@ export class Channel {
454
454
  messageID: string,
455
455
  reaction: Reaction,
456
456
  options?: { enforce_unique?: boolean; skip_push?: boolean },
457
- ): Promise<ReactionAPIResponse | undefined> {
457
+ ) {
458
458
  if (!messageID) {
459
459
  throw Error(`Message id is missing`);
460
460
  }
@@ -498,7 +498,7 @@ export class Channel {
498
498
  });
499
499
  }
500
500
 
501
- return await offlineDb.queueTask({
501
+ return await offlineDb.queueTask<ReactionAPIResponse>({
502
502
  task: {
503
503
  channelId: this.id as string,
504
504
  channelType: this.type,
package/src/client.ts CHANGED
@@ -777,7 +777,24 @@ export class StreamChat {
777
777
  'data_template': 'data handlebars template',
778
778
  'apn_template': 'apn notification handlebars template under v2'
779
779
  },
780
- 'webhook_url': 'https://acme.com/my/awesome/webhook/'
780
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/',
781
+ 'event_hooks': [
782
+ {
783
+ 'hook_type': 'webhook',
784
+ 'enabled': true,
785
+ 'event_types': ['message.new'],
786
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/'
787
+ },
788
+ {
789
+ 'hook_type': 'sqs',
790
+ 'enabled': true,
791
+ 'event_types': ['message.new'],
792
+ 'sqs_url': 'https://sqs.us-east-1.amazonaws.com/1234567890/my-queue',
793
+ 'sqs_auth_type': 'key',
794
+ 'sqs_key': 'my-access-key',
795
+ 'sqs_secret': 'my-secret-key'
796
+ }
797
+ ]
781
798
  }
782
799
  */
783
800
  async updateAppSettings(options: AppSettings) {
@@ -3019,13 +3036,15 @@ export class StreamChat {
3019
3036
  } else {
3020
3037
  await this.offlineDb.softDeleteMessage({ id: messageID });
3021
3038
  }
3022
- return await this.offlineDb.queueTask({
3023
- task: {
3024
- messageId: messageID,
3025
- payload: [messageID, hardDelete],
3026
- type: 'delete-message',
3039
+ return await this.offlineDb.queueTask<APIResponse & { message: MessageResponse }>(
3040
+ {
3041
+ task: {
3042
+ messageId: messageID,
3043
+ payload: [messageID, hardDelete],
3044
+ type: 'delete-message',
3045
+ },
3027
3046
  },
3028
- });
3047
+ );
3029
3048
  }
3030
3049
  } catch (error) {
3031
3050
  this.logger('error', `offlineDb:deleteMessage`, {
@@ -6,6 +6,7 @@ import type {
6
6
  PendingTask,
7
7
  PrepareBatchDBQueries,
8
8
  } from './types';
9
+ import { OfflineError } from './types';
9
10
  import type { StreamChat } from '../client';
10
11
  import type { AxiosError } from 'axios';
11
12
  import { OfflineDBSyncManager } from './offline_sync_manager';
@@ -986,22 +987,24 @@ export abstract class AbstractOfflineDB implements OfflineDBApi {
986
987
  * It will return the response from the execution if it succeeded.
987
988
  * @param task - the pending task we want to execute
988
989
  */
989
- public queueTask = async ({ task }: { task: PendingTask }) => {
990
- let response;
991
- try {
990
+ public queueTask = async <T>({ task }: { task: PendingTask }): Promise<T> => {
991
+ const attemptTaskExecution = async () => {
992
992
  if (!this.client.wsConnection?.isHealthy) {
993
- await this.addPendingTask(task);
994
- return;
993
+ throw new OfflineError(
994
+ 'Cannot execute task because the connection has been lost.',
995
+ { type: 'connection:lost' },
996
+ );
995
997
  }
996
- response = await this.executeTask({ task });
998
+ return (await this.executeTask({ task })) as T;
999
+ };
1000
+ try {
1001
+ return await attemptTaskExecution();
997
1002
  } catch (e) {
998
1003
  if (!this.shouldSkipQueueingTask(e as AxiosError<APIErrorResponse>)) {
999
1004
  await this.addPendingTask(task);
1000
- throw e;
1001
1005
  }
1006
+ throw e;
1002
1007
  }
1003
-
1004
- return response;
1005
1008
  };
1006
1009
 
1007
1010
  /**
@@ -6,7 +6,6 @@ import type {
6
6
  ChannelResponse,
7
7
  ChannelSort,
8
8
  LocalMessage,
9
- Message,
10
9
  MessageResponse,
11
10
  PollResponse,
12
11
  ReactionFilters,
@@ -398,6 +397,31 @@ export type PendingTask = {
398
397
  }
399
398
  );
400
399
 
401
- export type PendingTaskExtraData = {
402
- message?: Message;
403
- };
400
+ export type OfflineErrorType = 'connection:lost';
401
+
402
+ export class OfflineError extends Error {
403
+ public type: OfflineErrorType;
404
+ public name = 'OfflineError';
405
+
406
+ constructor(
407
+ message: string,
408
+ {
409
+ type,
410
+ }: {
411
+ type: OfflineError['type'];
412
+ },
413
+ ) {
414
+ super(message);
415
+ this.type = type;
416
+ }
417
+
418
+ // Vitest helper (serialized errors are too large to read)
419
+ // https://github.com/vitest-dev/vitest/blob/v3.1.3/packages/utils/src/error.ts#L60-L62
420
+ toJSON() {
421
+ return {
422
+ message: `${this.type} - ${this.message}`,
423
+ stack: this.stack,
424
+ name: this.name,
425
+ };
426
+ }
427
+ }
package/src/types.ts CHANGED
@@ -129,6 +129,7 @@ export type AppSettingsAPIResponse = APIResponse & {
129
129
  disable_auth_checks?: boolean;
130
130
  disable_permissions_checks?: boolean;
131
131
  enforce_unique_usernames?: 'no' | 'app' | 'team';
132
+ event_hooks?: Array<EventHook>;
132
133
  file_upload_config?: FileUploadConfig;
133
134
  geofences?: Array<{
134
135
  country_codes: Array<string>;
@@ -2213,6 +2214,7 @@ export type AppSettings = {
2213
2214
  disable_auth_checks?: boolean;
2214
2215
  disable_permissions_checks?: boolean;
2215
2216
  enforce_unique_usernames?: 'no' | 'app' | 'team';
2217
+ event_hooks?: Array<EventHook> | null;
2216
2218
  // all possible file mime types are https://www.iana.org/assignments/media-types/media-types.xhtml
2217
2219
  file_upload_config?: FileUploadConfig;
2218
2220
  firebase_config?: {
@@ -4006,3 +4008,27 @@ export type ThreadFilters = QueryFilters<
4006
4008
  | PrimitiveFilter<ThreadResponse['last_message_at']>;
4007
4009
  }
4008
4010
  >;
4011
+
4012
+ export type HookType = 'webhook' | 'sqs' | 'sns';
4013
+
4014
+ export type EventHook = {
4015
+ id?: string;
4016
+ hook_type?: HookType;
4017
+ enabled?: boolean;
4018
+ event_types?: Array<string>;
4019
+ webhook_url?: string;
4020
+ sqs_queue_url?: string;
4021
+ sqs_region?: string;
4022
+ sqs_auth_type?: string;
4023
+ sqs_key?: string;
4024
+ sqs_secret?: string;
4025
+ sqs_role_arn?: string;
4026
+ sns_topic_arn?: string;
4027
+ sns_region?: string;
4028
+ sns_auth_type?: string;
4029
+ sns_key?: string;
4030
+ sns_secret?: string;
4031
+ sns_role_arn?: string;
4032
+ created_at?: string;
4033
+ updated_at?: string;
4034
+ };
@@ -438,7 +438,13 @@ export function createMergeCore<T extends object>(options: { trackDiff?: boolean
438
438
  return false;
439
439
  }
440
440
 
441
- function createNewTarget(targetValue: unknown, srcValue: unknown): object {
441
+ function createNewTarget(targetValue: unknown, srcValue: object): object {
442
+ if (targetValue === null || typeof targetValue === 'undefined') {
443
+ return srcValue;
444
+ }
445
+ if (!Array.isArray(targetValue) && typeof targetValue !== 'object') {
446
+ return srcValue;
447
+ }
442
448
  if (targetValue && typeof targetValue === 'object') {
443
449
  // Check if it's a class instance (not a plain object)
444
450
  const isTargetClassInstance = isClassInstance(targetValue);