stream-chat 9.12.0 → 9.14.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.
Files changed (43) hide show
  1. package/dist/cjs/index.browser.cjs +452 -184
  2. package/dist/cjs/index.browser.cjs.map +4 -4
  3. package/dist/cjs/index.node.cjs +1679 -233
  4. package/dist/cjs/index.node.cjs.map +4 -4
  5. package/dist/esm/index.js +452 -184
  6. package/dist/esm/index.js.map +4 -4
  7. package/dist/types/client.d.ts +8 -1
  8. package/dist/types/messageComposer/attachmentManager.d.ts +11 -3
  9. package/dist/types/messageComposer/configuration/types.d.ts +1 -1
  10. package/dist/types/messageComposer/middleware/attachmentManager/index.d.ts +3 -0
  11. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.d.ts +5 -0
  12. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts +2 -0
  13. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/index.d.ts +3 -0
  14. package/dist/types/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.d.ts +3 -0
  15. package/dist/types/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.d.ts +5 -0
  16. package/dist/types/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.d.ts +3 -0
  17. package/dist/types/messageComposer/middleware/attachmentManager/preUpload/index.d.ts +3 -0
  18. package/dist/types/messageComposer/middleware/attachmentManager/preUpload/serverUploadConfigCheck.d.ts +3 -0
  19. package/dist/types/messageComposer/middleware/attachmentManager/types.d.ts +20 -0
  20. package/dist/types/messageComposer/types.d.ts +1 -0
  21. package/dist/types/middleware.d.ts +3 -2
  22. package/dist/types/types.d.ts +14 -0
  23. package/package.json +2 -2
  24. package/src/channel_manager.ts +18 -2
  25. package/src/client.ts +13 -0
  26. package/src/messageComposer/attachmentManager.ts +116 -25
  27. package/src/messageComposer/configuration/types.ts +3 -2
  28. package/src/messageComposer/messageComposer.ts +1 -1
  29. package/src/messageComposer/middleware/attachmentManager/index.ts +3 -0
  30. package/src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts +20 -0
  31. package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +43 -0
  32. package/src/messageComposer/middleware/attachmentManager/postUpload/index.ts +3 -0
  33. package/src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts +39 -0
  34. package/src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts +20 -0
  35. package/src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts +38 -0
  36. package/src/messageComposer/middleware/attachmentManager/preUpload/index.ts +3 -0
  37. package/src/messageComposer/middleware/attachmentManager/preUpload/serverUploadConfigCheck.ts +40 -0
  38. package/src/messageComposer/middleware/attachmentManager/types.ts +32 -0
  39. package/src/messageComposer/types.ts +6 -0
  40. package/src/middleware.ts +22 -10
  41. package/src/offline-support/offline_sync_manager.ts +16 -0
  42. package/src/types.ts +17 -0
  43. package/src/utils.ts +5 -1
@@ -7,7 +7,7 @@ import { TokenManager } from './token_manager';
7
7
  import { WSConnectionFallback } from './connection_fallback';
8
8
  import { Campaign } from './campaign';
9
9
  import { Segment } from './segment';
10
- import type { ActiveLiveLocationsAPIResponse, APIErrorResponse, APIResponse, AppSettings, AppSettingsAPIResponse, BannedUsersFilters, BannedUsersPaginationOptions, BannedUsersResponse, BannedUsersSort, BanUserOptions, BaseDeviceFields, BlockList, BlockListResponse, BlockUserAPIResponse, CampaignData, CampaignFilters, CampaignQueryOptions, CampaignResponse, CampaignSort, CastVoteAPIResponse, ChannelAPIResponse, ChannelData, ChannelFilters, ChannelMute, ChannelOptions, ChannelResponse, ChannelSort, ChannelStateOptions, CheckPushResponse, CheckSNSResponse, CheckSQSResponse, Configs, ConnectAPIResponse, CreateChannelOptions, CreateChannelResponse, CreateCommandOptions, CreateCommandResponse, CreateImportOptions, CreateImportResponse, CreateImportURLResponse, CreatePollAPIResponse, CreatePollData, CreatePollOptionAPIResponse, CreateReminderOptions, CustomPermissionOptions, DeactivateUsersOptions, DeleteCommandResponse, DeleteUserOptions, Device, DeviceIdentifier, DraftFilters, DraftSort, EndpointName, Event, EventHandler, ExportChannelOptions, ExportChannelRequest, ExportChannelResponse, ExportChannelStatusResponse, ExportUsersRequest, ExportUsersResponse, FlagMessageResponse, FlagReportsFilters, FlagReportsPaginationOptions, FlagReportsResponse, FlagsFilters, FlagsPaginationOptions, FlagsResponse, FlagUserResponse, GetBlockedUsersAPIResponse, GetCampaignOptions, GetChannelTypeResponse, GetCommandResponse, GetImportResponse, GetMessageOptions, GetPollAPIResponse, GetRateLimitsResponse, GetThreadAPIResponse, GetThreadOptions, GetUnreadCountAPIResponse, GetUnreadCountBatchAPIResponse, ListChannelResponse, ListCommandsResponse, ListImportsPaginationOptions, ListImportsResponse, LocalMessage, Logger, MarkChannelsReadOptions, MessageFilters, MessageFlagsFilters, MessageFlagsPaginationOptions, MessageFlagsResponse, MessageResponse, Mute, MuteUserOptions, MuteUserResponse, OGAttachment, OwnUserResponse, Pager, PartialMessageUpdate, PartialPollUpdate, PartialThreadUpdate, PartialUserUpdate, PermissionAPIResponse, PermissionsAPIResponse, PollAnswersAPIResponse, PollData, PollOptionData, PollSort, PollVote, PollVoteData, PollVotesAPIResponse, PushPreference, PushProvider, PushProviderConfig, PushProviderID, PushProviderListResponse, PushProviderUpsertResponse, QueryDraftsResponse, QueryMessageHistoryFilters, QueryMessageHistoryOptions, QueryMessageHistoryResponse, QueryMessageHistorySort, QueryPollsFilters, QueryPollsOptions, QueryPollsResponse, QueryReactionsAPIResponse, QueryReactionsOptions, QueryRemindersOptions, QueryRemindersResponse, QuerySegmentsOptions, QuerySegmentTargetsFilter, QueryThreadsOptions, QueryVotesFilters, QueryVotesOptions, ReactionFilters, ReactionResponse, ReactionSort, ReactivateUserOptions, ReactivateUsersOptions, ReminderAPIResponse, ReviewFlagReportOptions, ReviewFlagReportResponse, SdkIdentifier, SearchAPIResponse, SearchOptions, SegmentData, SegmentResponse, SegmentTargetsResponse, SegmentType, SendFileAPIResponse, SharedLocationResponse, SortParam, StreamChatOptions, SyncOptions, SyncResponse, TaskResponse, TaskStatus, TestPushDataInput, TestSNSDataInput, TestSQSDataInput, TokenOrProvider, TranslateResponse, UnBanUserOptions, UpdateChannelTypeRequest, UpdateChannelTypeResponse, UpdateCommandOptions, UpdateCommandResponse, UpdateLocationPayload, UpdateMessageAPIResponse, UpdateMessageOptions, UpdatePollAPIResponse, UpdateReminderOptions, UpdateSegmentData, UpsertPushPreferencesResponse, UserCustomEvent, UserFilters, UserOptions, UserResponse, UserSort, VoteSort } from './types';
10
+ import type { ActiveLiveLocationsAPIResponse, APIErrorResponse, APIResponse, AppSettings, AppSettingsAPIResponse, BannedUsersFilters, BannedUsersPaginationOptions, BannedUsersResponse, BannedUsersSort, BanUserOptions, BaseDeviceFields, BlockList, BlockListResponse, BlockUserAPIResponse, CampaignData, CampaignFilters, CampaignQueryOptions, CampaignResponse, CampaignSort, CastVoteAPIResponse, ChannelAPIResponse, ChannelData, ChannelFilters, ChannelMute, ChannelOptions, ChannelResponse, ChannelSort, ChannelStateOptions, CheckPushResponse, CheckSNSResponse, CheckSQSResponse, Configs, ConnectAPIResponse, CreateChannelOptions, CreateChannelResponse, CreateCommandOptions, CreateCommandResponse, CreateImportOptions, CreateImportResponse, CreateImportURLResponse, CreatePollAPIResponse, CreatePollData, CreatePollOptionAPIResponse, CreateReminderOptions, CustomPermissionOptions, DeactivateUsersOptions, DeleteCommandResponse, DeleteUserOptions, Device, DeviceIdentifier, DraftFilters, DraftSort, EndpointName, Event, EventHandler, ExportChannelOptions, ExportChannelRequest, ExportChannelResponse, ExportChannelStatusResponse, ExportUsersRequest, ExportUsersResponse, FlagMessageResponse, FlagReportsFilters, FlagReportsPaginationOptions, FlagReportsResponse, FlagsFilters, FlagsPaginationOptions, FlagsResponse, FlagUserResponse, GetBlockedUsersAPIResponse, GetCampaignOptions, GetChannelTypeResponse, GetCommandResponse, GetHookEventsResponse, GetImportResponse, GetMessageOptions, GetPollAPIResponse, GetRateLimitsResponse, GetThreadAPIResponse, GetThreadOptions, GetUnreadCountAPIResponse, GetUnreadCountBatchAPIResponse, ListChannelResponse, ListCommandsResponse, ListImportsPaginationOptions, ListImportsResponse, LocalMessage, Logger, MarkChannelsReadOptions, MessageFilters, MessageFlagsFilters, MessageFlagsPaginationOptions, MessageFlagsResponse, MessageResponse, Mute, MuteUserOptions, MuteUserResponse, OGAttachment, OwnUserResponse, Pager, PartialMessageUpdate, PartialPollUpdate, PartialThreadUpdate, PartialUserUpdate, PermissionAPIResponse, PermissionsAPIResponse, PollAnswersAPIResponse, PollData, PollOptionData, PollSort, PollVote, PollVoteData, PollVotesAPIResponse, Product, PushPreference, PushProvider, PushProviderConfig, PushProviderID, PushProviderListResponse, PushProviderUpsertResponse, QueryDraftsResponse, QueryMessageHistoryFilters, QueryMessageHistoryOptions, QueryMessageHistoryResponse, QueryMessageHistorySort, QueryPollsFilters, QueryPollsOptions, QueryPollsResponse, QueryReactionsAPIResponse, QueryReactionsOptions, QueryRemindersOptions, QueryRemindersResponse, QuerySegmentsOptions, QuerySegmentTargetsFilter, QueryThreadsOptions, QueryVotesFilters, QueryVotesOptions, ReactionFilters, ReactionResponse, ReactionSort, ReactivateUserOptions, ReactivateUsersOptions, ReminderAPIResponse, ReviewFlagReportOptions, ReviewFlagReportResponse, SdkIdentifier, SearchAPIResponse, SearchOptions, SegmentData, SegmentResponse, SegmentTargetsResponse, SegmentType, SendFileAPIResponse, SharedLocationResponse, SortParam, StreamChatOptions, SyncOptions, SyncResponse, TaskResponse, TaskStatus, TestPushDataInput, TestSNSDataInput, TestSQSDataInput, TokenOrProvider, TranslateResponse, UnBanUserOptions, UpdateChannelTypeRequest, UpdateChannelTypeResponse, UpdateCommandOptions, UpdateCommandResponse, UpdateLocationPayload, UpdateMessageAPIResponse, UpdateMessageOptions, UpdatePollAPIResponse, UpdateReminderOptions, UpdateSegmentData, UpsertPushPreferencesResponse, UserCustomEvent, UserFilters, UserOptions, UserResponse, UserSort, VoteSort } from './types';
11
11
  import { ErrorFromResponse } from './types';
12
12
  import { InsightMetrics } from './insights';
13
13
  import { Thread } from './thread';
@@ -619,6 +619,13 @@ export declare class StreamChat {
619
619
  serverSide?: boolean;
620
620
  web?: boolean;
621
621
  }): Promise<GetRateLimitsResponse>;
622
+ /**
623
+ * getHookEvents - Get available events for hooks (webhook, SQS, and SNS)
624
+ *
625
+ * @param {Product[]} [products] Optional array of products to filter events by (e.g., [Product.Chat, Product.Video])
626
+ * @returns {Promise<GetHookEventsResponse>} Response containing available hook events
627
+ */
628
+ getHookEvents(products?: Product[]): Promise<GetHookEventsResponse>;
622
629
  _addChannelConfig({ cid, config }: ChannelResponse): void;
623
630
  /**
624
631
  * channel - Returns a new channel with the given type, id and custom data
@@ -1,4 +1,5 @@
1
- import type { AttachmentManagerConfig, MinimumUploadRequestResult, UploadRequestFn } from './configuration';
1
+ import type { AttachmentManagerConfig, UploadRequestFn } from './configuration';
2
+ import { AttachmentPostUploadMiddlewareExecutor, AttachmentPreUploadMiddlewareExecutor } from './middleware/attachmentManager';
2
3
  import { StateStore } from '../store';
3
4
  import type { AttachmentLoadingState, FileLike, FileReference, LocalAttachment, LocalUploadAttachment, UploadPermissionCheckResult } from './types';
4
5
  import type { DraftMessage, LocalMessage } from '../types';
@@ -14,6 +15,8 @@ export type AttachmentManagerOptions = {
14
15
  export declare class AttachmentManager {
15
16
  readonly state: StateStore<AttachmentManagerState>;
16
17
  readonly composer: MessageComposer;
18
+ readonly preUploadMiddlewareExecutor: AttachmentPreUploadMiddlewareExecutor;
19
+ readonly postUploadMiddlewareExecutor: AttachmentPostUploadMiddlewareExecutor;
17
20
  private attachmentsByIdGetterCache;
18
21
  constructor({ composer, message }: AttachmentManagerOptions);
19
22
  get attachmentsById(): Record<string, LocalAttachment>;
@@ -47,6 +50,7 @@ export declare class AttachmentManager {
47
50
  upsertAttachments: (attachmentsToUpsert: LocalAttachment[]) => void;
48
51
  removeAttachments: (localAttachmentIds: string[]) => void;
49
52
  getUploadConfigCheck: (fileLike: FileReference | FileLike) => Promise<UploadPermissionCheckResult>;
53
+ static toLocalUploadAttachment: (fileLike: FileReference | FileLike) => LocalUploadAttachment;
50
54
  fileToLocalUploadAttachment: (fileLike: FileReference | FileLike) => Promise<LocalUploadAttachment>;
51
55
  private ensureLocalUploadAttachment;
52
56
  /**
@@ -60,7 +64,11 @@ export declare class AttachmentManager {
60
64
  /**
61
65
  * todo: docs how to customize the image and file upload by overriding do
62
66
  */
63
- doUploadRequest: (fileLike: FileReference | FileLike) => Promise<MinimumUploadRequestResult>;
67
+ doUploadRequest: (fileLike: FileReference | FileLike) => Promise<{
68
+ file: string;
69
+ thumb_url?: string;
70
+ }>;
64
71
  uploadAttachment: (attachment: LocalUploadAttachment) => Promise<LocalUploadAttachment | undefined>;
65
- uploadFiles: (files: FileReference[] | FileList | FileLike[]) => Promise<(LocalUploadAttachment | undefined)[] | undefined>;
72
+ uploadFile: (file: FileReference | FileLike) => Promise<LocalUploadAttachment>;
73
+ uploadFiles: (files: FileReference[] | FileList | FileLike[]) => Promise<LocalUploadAttachment[] | undefined>;
66
74
  }
@@ -4,7 +4,7 @@ import type { FileLike, FileReference } from '../types';
4
4
  export type MinimumUploadRequestResult = {
5
5
  file: string;
6
6
  thumb_url?: string;
7
- };
7
+ } & Partial<Record<string, unknown>>;
8
8
  export type UploadRequestFn = (fileLike: FileReference | FileLike) => Promise<MinimumUploadRequestResult>;
9
9
  export type DraftsConfiguration = {
10
10
  enabled: boolean;
@@ -0,0 +1,3 @@
1
+ export * from './postUpload';
2
+ export * from './preUpload';
3
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import { MiddlewareExecutor } from '../../../../middleware';
2
+ import type { AttachmentPostUploadMiddlewareExecutorOptions, AttachmentPostUploadMiddlewareState } from '../types';
3
+ export declare class AttachmentPostUploadMiddlewareExecutor extends MiddlewareExecutor<AttachmentPostUploadMiddlewareState, 'postProcess'> {
4
+ constructor({ composer }: AttachmentPostUploadMiddlewareExecutorOptions);
5
+ }
@@ -0,0 +1,2 @@
1
+ import type { AttachmentPostUploadMiddleware } from '../types';
2
+ export declare const createPostUploadAttachmentEnrichmentMiddleware: () => AttachmentPostUploadMiddleware;
@@ -0,0 +1,3 @@
1
+ export * from './attachmentEnrichment';
2
+ export * from './AttachmentPostUploadMiddlewareExecutor';
3
+ export * from './uploadErrorHandler';
@@ -0,0 +1,3 @@
1
+ import type { MessageComposer } from '../../../messageComposer';
2
+ import type { AttachmentPostUploadMiddleware } from '../types';
3
+ export declare const createUploadErrorHandlerMiddleware: (composer: MessageComposer) => AttachmentPostUploadMiddleware;
@@ -0,0 +1,5 @@
1
+ import { MiddlewareExecutor } from '../../../../middleware';
2
+ import type { AttachmentPreUploadMiddlewareExecutorOptions, AttachmentPreUploadMiddlewareState } from '../types';
3
+ export declare class AttachmentPreUploadMiddlewareExecutor extends MiddlewareExecutor<AttachmentPreUploadMiddlewareState, 'prepare'> {
4
+ constructor({ composer }: AttachmentPreUploadMiddlewareExecutorOptions);
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { MessageComposer } from '../../../messageComposer';
2
+ import type { AttachmentPreUploadMiddleware } from '../types';
3
+ export declare const createBlockedAttachmentUploadNotificationMiddleware: (composer: MessageComposer) => AttachmentPreUploadMiddleware;
@@ -0,0 +1,3 @@
1
+ export * from './AttachmentPreUploadMiddlewareExecutor';
2
+ export * from './blockedUploadNotification';
3
+ export * from './serverUploadConfigCheck';
@@ -0,0 +1,3 @@
1
+ import type { MessageComposer } from '../../../messageComposer';
2
+ import type { AttachmentPreUploadMiddleware } from '../types';
3
+ export declare const createUploadConfigCheckMiddleware: (composer: MessageComposer) => AttachmentPreUploadMiddleware;
@@ -0,0 +1,20 @@
1
+ import type { LocalUploadAttachment } from '../../types';
2
+ import type { Middleware } from '../../../middleware';
3
+ import type { MessageComposer } from '../../messageComposer';
4
+ import type { MinimumUploadRequestResult } from '../../configuration';
5
+ export type AttachmentPreUploadMiddlewareState = {
6
+ attachment: LocalUploadAttachment;
7
+ };
8
+ export type AttachmentPostUploadMiddlewareState = {
9
+ attachment: LocalUploadAttachment;
10
+ error?: Error;
11
+ response?: MinimumUploadRequestResult;
12
+ };
13
+ export type AttachmentPreUploadMiddleware = Middleware<AttachmentPreUploadMiddlewareState, 'prepare'>;
14
+ export type AttachmentPostUploadMiddleware = Middleware<AttachmentPostUploadMiddlewareState, 'postProcess'>;
15
+ export type AttachmentPostUploadMiddlewareExecutorOptions = {
16
+ composer: MessageComposer;
17
+ };
18
+ export type AttachmentPreUploadMiddlewareExecutorOptions = {
19
+ composer: MessageComposer;
20
+ };
@@ -61,6 +61,7 @@ export type LocalAttachmentUploadMetadata = {
61
61
  export type LocalImageAttachmentUploadMetadata = LocalAttachmentUploadMetadata & {
62
62
  previewUri?: string;
63
63
  };
64
+ export type LocalNotImageAttachment = LocalFileAttachment | LocalAudioAttachment | LocalVideoAttachment | LocalVoiceRecordingAttachment;
64
65
  export type AttachmentLoadingState = 'uploading' | 'finished' | 'failed' | 'blocked' | 'pending';
65
66
  export type UploadPermissionCheckResult = {
66
67
  uploadBlocked: boolean;
@@ -13,6 +13,7 @@ export type MiddlewareExecutionResult<TValue> = {
13
13
  export type ExecuteParams<TValue> = {
14
14
  eventName: string;
15
15
  initialValue: TValue;
16
+ mode?: 'concurrent' | 'cancelable';
16
17
  };
17
18
  export type MiddlewareHandlerParams<TValue> = {
18
19
  state: TValue;
@@ -41,6 +42,6 @@ export declare class MiddlewareExecutor<TValue, THandlers extends string> {
41
42
  unique?: boolean;
42
43
  }): this;
43
44
  setOrder(order: string[]): void;
44
- protected executeMiddlewareChain({ eventName, initialValue, }: ExecuteParams<TValue>): Promise<MiddlewareExecutionResult<TValue>>;
45
- execute({ eventName, initialValue: initialState, }: ExecuteParams<TValue>): Promise<MiddlewareExecutionResult<TValue>>;
45
+ protected executeMiddlewareChain({ eventName, initialValue, mode, }: ExecuteParams<TValue>): Promise<MiddlewareExecutionResult<TValue>>;
46
+ execute({ eventName, initialValue: initialState, mode, }: ExecuteParams<TValue>): Promise<MiddlewareExecutionResult<TValue>>;
46
47
  }
@@ -478,6 +478,20 @@ export type GetRateLimitsResponse = APIResponse & {
478
478
  server_side?: RateLimitsMap;
479
479
  web?: RateLimitsMap;
480
480
  };
481
+ export declare enum Product {
482
+ Chat = "chat",
483
+ Video = "video",
484
+ Moderation = "moderation",
485
+ Feeds = "feeds"
486
+ }
487
+ export type HookEvent = {
488
+ name: string;
489
+ description: string;
490
+ products: Product[];
491
+ };
492
+ export type GetHookEventsResponse = APIResponse & {
493
+ events: HookEvent[];
494
+ };
481
495
  export type GetReactionsAPIResponse = APIResponse & {
482
496
  reactions: ReactionResponse[];
483
497
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.12.0",
3
+ "version": "9.14.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
@@ -53,7 +53,7 @@
53
53
  "@types/ws": "^8.5.14",
54
54
  "axios": "^1.6.0",
55
55
  "base64-js": "^1.5.1",
56
- "form-data": "^4.0.0",
56
+ "form-data": "^4.0.4",
57
57
  "isomorphic-ws": "^5.0.0",
58
58
  "jsonwebtoken": "^9.0.2",
59
59
  "linkifyjs": "^4.2.0",
@@ -317,7 +317,19 @@ export class ChannelManager extends WithSubscriptions {
317
317
  `Maximum number of retries reached in queryChannels. Last error message is: ${err}`,
318
318
  );
319
319
 
320
- this.state.partialNext({ error: wrappedError });
320
+ const state = this.state.getLatestValue();
321
+ // If the offline support is enabled, and there are channels in the DB, we should not error out.
322
+ const isOfflineSupportEnabledWithChannels =
323
+ this.client.offlineDb && state.channels.length > 0;
324
+
325
+ this.state.partialNext({
326
+ error: isOfflineSupportEnabledWithChannels ? undefined : wrappedError,
327
+ pagination: {
328
+ ...state.pagination,
329
+ isLoading: false,
330
+ isLoadingNext: false,
331
+ },
332
+ });
321
333
  return;
322
334
  }
323
335
 
@@ -444,7 +456,11 @@ export class ChannelManager extends WithSubscriptions {
444
456
  this.client.logger('error', (error as Error).message);
445
457
  this.state.next((currentState) => ({
446
458
  ...currentState,
447
- pagination: { ...currentState.pagination, isLoadingNext: false },
459
+ pagination: {
460
+ ...currentState.pagination,
461
+ isLoadingNext: false,
462
+ isLoading: false,
463
+ },
448
464
  }));
449
465
  throw error;
450
466
  }
package/src/client.ts CHANGED
@@ -106,6 +106,7 @@ import type {
106
106
  GetCampaignOptions,
107
107
  GetChannelTypeResponse,
108
108
  GetCommandResponse,
109
+ GetHookEventsResponse,
109
110
  GetImportResponse,
110
111
  GetMessageAPIResponse,
111
112
  GetMessageOptions,
@@ -148,6 +149,7 @@ import type {
148
149
  PollVote,
149
150
  PollVoteData,
150
151
  PollVotesAPIResponse,
152
+ Product,
151
153
  PushPreference,
152
154
  PushProvider,
153
155
  PushProviderConfig,
@@ -2164,6 +2166,17 @@ export class StreamChat {
2164
2166
  });
2165
2167
  }
2166
2168
 
2169
+ /**
2170
+ * getHookEvents - Get available events for hooks (webhook, SQS, and SNS)
2171
+ *
2172
+ * @param {Product[]} [products] Optional array of products to filter events by (e.g., [Product.Chat, Product.Video])
2173
+ * @returns {Promise<GetHookEventsResponse>} Response containing available hook events
2174
+ */
2175
+ async getHookEvents(products?: Product[]) {
2176
+ const params = products && products.length > 0 ? { product: products.join(',') } : {};
2177
+ return await this.get<GetHookEventsResponse>(this.baseURL + '/hook/events', params);
2178
+ }
2179
+
2167
2180
  _addChannelConfig({ cid, config }: ChannelResponse) {
2168
2181
  if (this._cacheEnabled()) {
2169
2182
  this.configs[cid] = config;
@@ -14,6 +14,10 @@ import {
14
14
  isFileReference,
15
15
  isImageFile,
16
16
  } from './fileUtils';
17
+ import {
18
+ AttachmentPostUploadMiddlewareExecutor,
19
+ AttachmentPreUploadMiddlewareExecutor,
20
+ } from './middleware/attachmentManager';
17
21
  import { StateStore } from '../store';
18
22
  import { generateUUIDv4 } from '../utils';
19
23
  import { DEFAULT_UPLOAD_SIZE_LIMIT_BYTES } from '../constants';
@@ -22,23 +26,14 @@ import type {
22
26
  FileLike,
23
27
  FileReference,
24
28
  LocalAttachment,
25
- LocalAudioAttachment,
26
- LocalFileAttachment,
29
+ LocalNotImageAttachment,
27
30
  LocalUploadAttachment,
28
- LocalVideoAttachment,
29
- LocalVoiceRecordingAttachment,
30
31
  UploadPermissionCheckResult,
31
32
  } from './types';
32
33
  import type { ChannelResponse, DraftMessage, LocalMessage } from '../types';
33
34
  import type { MessageComposer } from './messageComposer';
34
35
  import { mergeWithDiff } from '../utils/mergeWith';
35
36
 
36
- type LocalNotImageAttachment =
37
- | LocalFileAttachment
38
- | LocalAudioAttachment
39
- | LocalVideoAttachment
40
- | LocalVoiceRecordingAttachment;
41
-
42
37
  export type FileUploadFilter = (file: Partial<LocalUploadAttachment>) => boolean;
43
38
 
44
39
  export type AttachmentManagerState = {
@@ -71,6 +66,8 @@ const initState = ({
71
66
  export class AttachmentManager {
72
67
  readonly state: StateStore<AttachmentManagerState>;
73
68
  readonly composer: MessageComposer;
69
+ readonly preUploadMiddlewareExecutor: AttachmentPreUploadMiddlewareExecutor;
70
+ readonly postUploadMiddlewareExecutor: AttachmentPostUploadMiddlewareExecutor;
74
71
  private attachmentsByIdGetterCache: {
75
72
  attachmentsById: Record<string, LocalAttachment>;
76
73
  attachments: LocalAttachment[];
@@ -80,6 +77,13 @@ export class AttachmentManager {
80
77
  this.composer = composer;
81
78
  this.state = new StateStore<AttachmentManagerState>(initState({ message }));
82
79
  this.attachmentsByIdGetterCache = { attachmentsById: {}, attachments: [] };
80
+
81
+ this.preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor({
82
+ composer,
83
+ });
84
+ this.postUploadMiddlewareExecutor = new AttachmentPostUploadMiddlewareExecutor({
85
+ composer,
86
+ });
83
87
  }
84
88
 
85
89
  get attachmentsById() {
@@ -122,10 +126,16 @@ export class AttachmentManager {
122
126
  this.composer.updateConfig({ attachments: { acceptedFiles } });
123
127
  }
124
128
 
129
+ /*
130
+ @deprecated attachments can be filtered using injecting pre-upload middleware
131
+ */
125
132
  get fileUploadFilter() {
126
133
  return this.config.fileUploadFilter;
127
134
  }
128
135
 
136
+ /*
137
+ @deprecated attachments can be filtered using injecting pre-upload middleware
138
+ */
129
139
  set fileUploadFilter(fileUploadFilter: AttachmentManagerConfig['fileUploadFilter']) {
130
140
  this.composer.updateConfig({ attachments: { fileUploadFilter } });
131
141
  }
@@ -333,9 +343,9 @@ export class AttachmentManager {
333
343
  return { uploadBlocked: false };
334
344
  };
335
345
 
336
- fileToLocalUploadAttachment = async (
346
+ static toLocalUploadAttachment = (
337
347
  fileLike: FileReference | FileLike,
338
- ): Promise<LocalUploadAttachment> => {
348
+ ): LocalUploadAttachment => {
339
349
  const file =
340
350
  isFileReference(fileLike) || isFile(fileLike)
341
351
  ? fileLike
@@ -345,16 +355,13 @@ export class AttachmentManager {
345
355
  mimeType: fileLike.type,
346
356
  });
347
357
 
348
- const uploadPermissionCheck = await this.getUploadConfigCheck(file);
349
-
350
358
  const localAttachment: LocalUploadAttachment = {
351
359
  file_size: file.size,
352
360
  mime_type: file.type,
353
361
  localMetadata: {
354
362
  file,
355
363
  id: generateUUIDv4(),
356
- uploadPermissionCheck,
357
- uploadState: uploadPermissionCheck.uploadBlocked ? 'blocked' : 'pending',
364
+ uploadState: 'pending',
358
365
  },
359
366
  type: getAttachmentTypeFromMimeType(file.type),
360
367
  };
@@ -383,10 +390,26 @@ export class AttachmentManager {
383
390
  return localAttachment;
384
391
  };
385
392
 
393
+ // @deprecated use AttachmentManager.toLocalUploadAttachment(file)
394
+ fileToLocalUploadAttachment = async (
395
+ fileLike: FileReference | FileLike,
396
+ ): Promise<LocalUploadAttachment> => {
397
+ const localAttachment = AttachmentManager.toLocalUploadAttachment(fileLike);
398
+ const uploadPermissionCheck = await this.getUploadConfigCheck(
399
+ localAttachment.localMetadata.file,
400
+ );
401
+ localAttachment.localMetadata.uploadPermissionCheck = uploadPermissionCheck;
402
+ localAttachment.localMetadata.uploadState = uploadPermissionCheck.uploadBlocked
403
+ ? 'blocked'
404
+ : 'pending';
405
+
406
+ return localAttachment;
407
+ };
408
+
386
409
  private ensureLocalUploadAttachment = async (
387
410
  attachment: Partial<LocalUploadAttachment>,
388
411
  ) => {
389
- if (!attachment.localMetadata?.file || !attachment.localMetadata.id) {
412
+ if (!attachment.localMetadata?.file) {
390
413
  this.client.notifications.addError({
391
414
  message: 'File is required for upload attachment',
392
415
  origin: { emitter: 'AttachmentManager', context: { attachment } },
@@ -395,6 +418,15 @@ export class AttachmentManager {
395
418
  return;
396
419
  }
397
420
 
421
+ if (!attachment.localMetadata.id) {
422
+ this.client.notifications.addError({
423
+ message: 'Local upload attachment missing local id',
424
+ origin: { emitter: 'AttachmentManager', context: { attachment } },
425
+ options: { type: 'validation:attachment:id:missing' },
426
+ });
427
+ return;
428
+ }
429
+
398
430
  if (!this.fileUploadFilter(attachment)) return;
399
431
 
400
432
  const newAttachment = await this.fileToLocalUploadAttachment(
@@ -446,6 +478,7 @@ export class AttachmentManager {
446
478
  return this.doDefaultUploadRequest(fileLike);
447
479
  };
448
480
 
481
+ // @deprecated use attachmentManager.uploadFile(file)
449
482
  uploadAttachment = async (attachment: LocalUploadAttachment) => {
450
483
  if (!this.isUploadEnabled) return;
451
484
 
@@ -546,20 +579,78 @@ export class AttachmentManager {
546
579
  return uploadedAttachment;
547
580
  };
548
581
 
582
+ uploadFile = async (file: FileReference | FileLike) => {
583
+ const preUpload = await this.preUploadMiddlewareExecutor.execute({
584
+ eventName: 'prepare',
585
+ initialValue: {
586
+ attachment: AttachmentManager.toLocalUploadAttachment(file),
587
+ },
588
+ mode: 'concurrent',
589
+ });
590
+
591
+ let attachment: LocalUploadAttachment = preUpload.state.attachment;
592
+
593
+ if (preUpload.status === 'discard') return attachment;
594
+ // todo: remove with the next major release as filtering can be done in middleware
595
+ // should we return the attachment object?
596
+ if (!this.fileUploadFilter(attachment)) return attachment;
597
+
598
+ if (attachment.localMetadata.uploadState === 'blocked') {
599
+ this.upsertAttachments([attachment]);
600
+ return preUpload.state.attachment;
601
+ }
602
+
603
+ attachment = {
604
+ ...attachment,
605
+ localMetadata: {
606
+ ...attachment.localMetadata,
607
+ uploadState: 'uploading',
608
+ },
609
+ };
610
+ this.upsertAttachments([attachment]);
611
+
612
+ let response: MinimumUploadRequestResult | undefined;
613
+ let error: Error | undefined;
614
+ try {
615
+ response = await this.doUploadRequest(file);
616
+ } catch (err) {
617
+ error = err instanceof Error ? err : undefined;
618
+ }
619
+
620
+ const postUpload = await this.postUploadMiddlewareExecutor.execute({
621
+ eventName: 'postProcess',
622
+ initialValue: {
623
+ attachment: {
624
+ ...attachment,
625
+ localMetadata: {
626
+ ...attachment.localMetadata,
627
+ uploadState: error ? 'failed' : 'finished',
628
+ },
629
+ },
630
+ error,
631
+ response,
632
+ },
633
+ mode: 'concurrent',
634
+ });
635
+ attachment = postUpload.state.attachment;
636
+
637
+ if (postUpload.status === 'discard') {
638
+ this.removeAttachments([attachment.localMetadata.id]);
639
+ return attachment;
640
+ }
641
+
642
+ this.updateAttachment(attachment);
643
+ return attachment;
644
+ };
645
+
549
646
  uploadFiles = async (files: FileReference[] | FileList | FileLike[]) => {
550
647
  if (!this.isUploadEnabled) return;
551
648
  const iterableFiles: FileReference[] | FileLike[] = isFileList(files)
552
649
  ? Array.from(files)
553
650
  : files;
554
- const attachments = await Promise.all(
555
- iterableFiles.map(this.fileToLocalUploadAttachment),
556
- );
557
651
 
558
- return Promise.all(
559
- attachments
560
- .filter(this.fileUploadFilter)
561
- .slice(0, this.availableUploadSlots)
562
- .map(this.uploadAttachment),
652
+ return await Promise.all(
653
+ iterableFiles.slice(0, this.availableUploadSlots).map(this.uploadFile),
563
654
  );
564
655
  };
565
656
  }
@@ -2,7 +2,9 @@ import type { LinkPreview } from '../linkPreviewsManager';
2
2
  import type { FileUploadFilter } from '../attachmentManager';
3
3
  import type { FileLike, FileReference } from '../types';
4
4
 
5
- export type MinimumUploadRequestResult = { file: string; thumb_url?: string };
5
+ export type MinimumUploadRequestResult = { file: string; thumb_url?: string } & Partial<
6
+ Record<string, unknown>
7
+ >;
6
8
 
7
9
  export type UploadRequestFn = (
8
10
  fileLike: FileReference | FileLike,
@@ -39,7 +41,6 @@ export type AttachmentManagerConfig = {
39
41
  * describing which file types are allowed to be selected when uploading files.
40
42
  */
41
43
  acceptedFiles: string[];
42
- // todo: refactor this. We want a pipeline where it would be possible to customize the preparation, upload, and post-upload steps.
43
44
  /** Function that allows to customize the upload request. */
44
45
  doUploadRequest?: UploadRequestFn;
45
46
  };
@@ -700,7 +700,7 @@ export class MessageComposer extends WithSubscriptions {
700
700
  id: this.id,
701
701
  mentioned_users: [],
702
702
  parent_id: this.threadId ?? undefined,
703
- pinned_at: null,
703
+ pinned_at: this.editedMessage?.pinned_at || null,
704
704
  reaction_groups: null,
705
705
  status: this.editedMessage ? this.editedMessage.status : 'sending',
706
706
  text,
@@ -0,0 +1,3 @@
1
+ export * from './postUpload';
2
+ export * from './preUpload';
3
+ export * from './types';
@@ -0,0 +1,20 @@
1
+ import { MiddlewareExecutor } from '../../../../middleware';
2
+ import type {
3
+ AttachmentPostUploadMiddlewareExecutorOptions,
4
+ AttachmentPostUploadMiddlewareState,
5
+ } from '../types';
6
+ import { createPostUploadAttachmentEnrichmentMiddleware } from './attachmentEnrichment';
7
+ import { createUploadErrorHandlerMiddleware } from './uploadErrorHandler';
8
+
9
+ export class AttachmentPostUploadMiddlewareExecutor extends MiddlewareExecutor<
10
+ AttachmentPostUploadMiddlewareState,
11
+ 'postProcess'
12
+ > {
13
+ constructor({ composer }: AttachmentPostUploadMiddlewareExecutorOptions) {
14
+ super();
15
+ this.use([
16
+ createUploadErrorHandlerMiddleware(composer),
17
+ createPostUploadAttachmentEnrichmentMiddleware(),
18
+ ]);
19
+ }
20
+ }
@@ -0,0 +1,43 @@
1
+ import type { MiddlewareHandlerParams } from '../../../../middleware';
2
+ import type {
3
+ AttachmentPostUploadMiddleware,
4
+ AttachmentPostUploadMiddlewareState,
5
+ } from '../types';
6
+ import { isLocalImageAttachment } from '../../../attachmentIdentity';
7
+ import type { LocalNotImageAttachment } from '../../../types';
8
+
9
+ export const createPostUploadAttachmentEnrichmentMiddleware =
10
+ (): AttachmentPostUploadMiddleware => ({
11
+ id: 'stream-io/attachment-manager-middleware/post-upload-enrichment',
12
+ handlers: {
13
+ postProcess: ({
14
+ state,
15
+ discard,
16
+ forward,
17
+ next,
18
+ }: MiddlewareHandlerParams<AttachmentPostUploadMiddlewareState>) => {
19
+ const { attachment, error, response } = state;
20
+ if (error) return forward();
21
+ if (!attachment || !response) return discard();
22
+
23
+ const enrichedAttachment = { ...attachment };
24
+ if (isLocalImageAttachment(attachment)) {
25
+ if (attachment.localMetadata.previewUri) {
26
+ URL.revokeObjectURL(attachment.localMetadata.previewUri);
27
+ delete enrichedAttachment.localMetadata.previewUri;
28
+ }
29
+ enrichedAttachment.image_url = response.file;
30
+ } else {
31
+ (enrichedAttachment as LocalNotImageAttachment).asset_url = response.file;
32
+ }
33
+ if (response.thumb_url) {
34
+ (enrichedAttachment as LocalNotImageAttachment).thumb_url = response.thumb_url;
35
+ }
36
+
37
+ return next({
38
+ ...state,
39
+ attachment: enrichedAttachment,
40
+ });
41
+ },
42
+ },
43
+ });
@@ -0,0 +1,3 @@
1
+ export * from './attachmentEnrichment';
2
+ export * from './AttachmentPostUploadMiddlewareExecutor';
3
+ export * from './uploadErrorHandler';