stream-chat 9.51.0 → 9.52.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/cjs/index.browser.js +418 -215
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +427 -216
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +416 -213
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +5 -3
- package/dist/types/client.d.ts +10 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/messageComposer/attachmentIdentity.d.ts +4 -0
- package/dist/types/messageComposer/attachmentManager.d.ts +8 -0
- package/dist/types/messageComposer/messageComposer.d.ts +13 -0
- package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts +20 -1
- package/dist/types/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.d.ts +6 -1
- package/dist/types/messageComposer/middleware/messageComposer/attachments.d.ts +24 -0
- package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +22 -1
- package/dist/types/messageComposer/types.d.ts +5 -0
- package/dist/types/middleware.d.ts +8 -0
- package/dist/types/uploadManager.d.ts +30 -0
- package/dist/types/utils.d.ts +29 -6
- package/package.json +1 -1
- package/src/channel.ts +42 -26
- package/src/client.ts +27 -14
- package/src/index.ts +12 -0
- package/src/messageComposer/attachmentIdentity.ts +27 -0
- package/src/messageComposer/attachmentManager.ts +61 -12
- package/src/messageComposer/messageComposer.ts +43 -8
- package/src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts +1 -1
- package/src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts +58 -33
- package/src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts +4 -0
- package/src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts +9 -0
- package/src/messageComposer/middleware/messageComposer/attachments.ts +109 -0
- package/src/messageComposer/middleware/messageComposer/types.ts +22 -1
- package/src/messageComposer/types.ts +5 -0
- package/src/messageDelivery/MessageDeliveryReporter.ts +5 -4
- package/src/middleware.ts +11 -0
- package/src/offline-support/offline_support_api.ts +4 -0
- package/src/uploadManager.ts +45 -0
- package/src/utils.ts +102 -7
package/dist/types/channel.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare class Channel {
|
|
|
80
80
|
*
|
|
81
81
|
* @return {Promise<SendMessageAPIResponse>} The Server Response
|
|
82
82
|
*/
|
|
83
|
-
_sendMessage(
|
|
83
|
+
_sendMessage(rawMessage: Message, options?: SendMessageOptions): Promise<SendMessageAPIResponse>;
|
|
84
84
|
sendMessage(message: Message, options?: SendMessageOptions): Promise<SendMessageAPIResponse>;
|
|
85
85
|
/**
|
|
86
86
|
* Upload a file to this channel’s file endpoint (multipart). Forwards to the client’s `sendFile` implementation.
|
|
@@ -491,14 +491,16 @@ export declare class Channel {
|
|
|
491
491
|
*/
|
|
492
492
|
markRead(data?: MarkReadOptions): Promise<EventAPIResponse | null>;
|
|
493
493
|
/**
|
|
494
|
-
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
|
|
494
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
|
|
495
|
+
* enabled and the user holds the `read-events` capability on this channel
|
|
495
496
|
*
|
|
496
497
|
* @param {MarkReadOptions} data
|
|
497
498
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
498
499
|
*/
|
|
499
500
|
markAsReadRequest(data?: MarkReadOptions): Promise<EventAPIResponse | null>;
|
|
500
501
|
/**
|
|
501
|
-
* markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
|
|
502
|
+
* markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
|
|
503
|
+
* enabled and the user holds the `read-events` capability on this channel
|
|
502
504
|
*
|
|
503
505
|
* @param {MarkUnreadOptions} data
|
|
504
506
|
* @return {APIResponse} An API response
|
package/dist/types/client.d.ts
CHANGED
|
@@ -990,6 +990,11 @@ export declare class StreamChat {
|
|
|
990
990
|
}): Promise<FlagUserResponse>;
|
|
991
991
|
/**
|
|
992
992
|
* unflagMessage - unflag a message
|
|
993
|
+
*
|
|
994
|
+
* @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
|
|
995
|
+
* the backend - calling this resolves successfully but does not remove the flag.
|
|
996
|
+
* Use `client.moderation.submitAction()` against the review queue item instead.
|
|
997
|
+
*
|
|
993
998
|
* @param {string} targetMessageID
|
|
994
999
|
* @param {string} [options.user_id] currentUserID, only used with serverside auth
|
|
995
1000
|
* @returns {Promise<APIResponse>}
|
|
@@ -999,6 +1004,11 @@ export declare class StreamChat {
|
|
|
999
1004
|
}): Promise<FlagMessageResponse>;
|
|
1000
1005
|
/**
|
|
1001
1006
|
* unflagUser - unflag a user
|
|
1007
|
+
*
|
|
1008
|
+
* @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
|
|
1009
|
+
* the backend - calling this resolves successfully but does not remove the flag.
|
|
1010
|
+
* Use `client.moderation.submitAction()` against the review queue item instead.
|
|
1011
|
+
*
|
|
1002
1012
|
* @param {string} targetID
|
|
1003
1013
|
* @param {string} [options.user_id] currentUserID, only used with serverside auth
|
|
1004
1014
|
* @returns {Promise<APIResponse>}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -35,3 +35,10 @@ export * from './LiveLocationManager';
|
|
|
35
35
|
export type { CustomAttachmentData, CustomChannelData, CustomCommandData, CustomEventData, CustomEventTypes, CustomMemberData, CustomMessageComposerData, CustomMessageData, CustomPollOptionData, CustomPollData, CustomReactionData, CustomUserData, CustomThreadData, } from './custom_types';
|
|
36
36
|
export { isOwnUser, chatCodes, logChatPromiseExecution, localMessageToNewMessagePayload, formatMessage, promoteChannel, } from './utils';
|
|
37
37
|
export { FixedSizeQueueCache } from './utils/FixedSizeQueueCache';
|
|
38
|
+
/**
|
|
39
|
+
* Tag-keyed async runners. Exported so the UI SDKs (and integrators) serialize their own
|
|
40
|
+
* actions with the same primitive the SDK uses internally instead of hand-rolling promise
|
|
41
|
+
* chains. Tags share one process-wide map, so namespace yours (`my-app/thing/${id}`) to avoid
|
|
42
|
+
* colliding with another caller's.
|
|
43
|
+
*/
|
|
44
|
+
export { hasPending, settled, withCancellation, withoutConcurrency, } from './utils/concurrency';
|
|
@@ -3,6 +3,10 @@ import type { AudioAttachment, FileAttachment, GiphyAttachment, ImageAttachment,
|
|
|
3
3
|
export declare const isScrapedContent: (attachment: Attachment) => boolean;
|
|
4
4
|
export declare const isLocalAttachment: (attachment: unknown) => attachment is LocalAttachment;
|
|
5
5
|
export declare const isLocalUploadAttachment: (attachment: unknown) => attachment is LocalUploadAttachment;
|
|
6
|
+
/** Whether an upload for this attachment is still expected to resolve. */
|
|
7
|
+
export declare const isPendingUpload: (attachment: unknown) => attachment is LocalUploadAttachment;
|
|
8
|
+
/** Whether this attachment's upload already resolved to a URL. */
|
|
9
|
+
export declare const isFinishedUpload: (attachment: unknown) => attachment is LocalUploadAttachment;
|
|
6
10
|
export declare const isFileAttachment: (attachment: Attachment | LocalAttachment, supportedVideoFormat?: string[]) => attachment is FileAttachment;
|
|
7
11
|
export declare const isLocalFileAttachment: (attachment: Attachment | LocalAttachment) => attachment is LocalFileAttachment;
|
|
8
12
|
export declare const isImageAttachment: (attachment: Attachment) => attachment is ImageAttachment;
|
|
@@ -57,6 +57,14 @@ export declare class AttachmentManager {
|
|
|
57
57
|
private prepareAttachmentUpdate;
|
|
58
58
|
updateAttachment: (attachmentToUpdate: LocalAttachment) => void;
|
|
59
59
|
upsertAttachments: (attachmentsToUpsert: LocalAttachment[]) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Releases a local preview URL the SDK minted, if it minted one.
|
|
62
|
+
*
|
|
63
|
+
* Only `blob:` URLs are ours to revoke: `toLocalUploadAttachment` puts a
|
|
64
|
+
* `URL.createObjectURL` result in `previewUri` for a `File`, but for a React Native
|
|
65
|
+
* `FileReference` it stores the caller's own native file URI, which must be left alone.
|
|
66
|
+
*/
|
|
67
|
+
private revokePreviewUri;
|
|
60
68
|
removeAttachments: (localAttachmentIds: string[]) => void;
|
|
61
69
|
getUploadConfigCheck: (fileLike: FileReference | FileLike) => Promise<UploadPermissionCheckResult>;
|
|
62
70
|
static toLocalUploadAttachment: (fileLike: FileReference | FileLike) => LocalUploadAttachment;
|
|
@@ -107,6 +107,19 @@ export declare class MessageComposer extends WithSubscriptions {
|
|
|
107
107
|
isCommandDisabled: (command: CommandResponse) => boolean;
|
|
108
108
|
validateCommandSendability: (command: CommandResponse, text?: string) => CommandSendability;
|
|
109
109
|
get isCommandSendable(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Whether the installed composition middleware composes messages whose uploads are still in
|
|
112
|
+
* flight — see `MessageCompositionMiddleware.allowsPendingUploads`.
|
|
113
|
+
*
|
|
114
|
+
* Derived from the chain rather than from config, so there is exactly one switch: the UI SDK
|
|
115
|
+
* that implements the rest of the flow installs the middleware, and everything that has to
|
|
116
|
+
* follow from that (starting with {@link hasSendableData}) follows on its own.
|
|
117
|
+
*
|
|
118
|
+
* **Temporary API.** In v10 sending with pending uploads becomes part of the composer
|
|
119
|
+
* configuration (`MessageComposerConfig`, set through `updateConfig`), and this getter,
|
|
120
|
+
* together with the middleware declaration it reads, is replaced by that config field.
|
|
121
|
+
*/
|
|
122
|
+
get allowsPendingUploads(): boolean;
|
|
110
123
|
get hasSendableData(): boolean;
|
|
111
124
|
get compositionIsEmpty(): boolean;
|
|
112
125
|
get contentIsEmpty(): boolean;
|
package/dist/types/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import type { AttachmentPostUploadMiddleware } from '../types';
|
|
2
|
-
|
|
2
|
+
import type { MessageComposer } from '../../../messageComposer';
|
|
3
|
+
/**
|
|
4
|
+
* Writes the uploaded URL onto the attachment and releases its local preview.
|
|
5
|
+
*
|
|
6
|
+
* The preview is a blob URL the composer created for the file. Releasing it once the CDN URL is
|
|
7
|
+
* in is what keeps the browser from holding the file in memory.
|
|
8
|
+
*
|
|
9
|
+
* Pass `composer` and the preview is released only while the attachment is still one of the
|
|
10
|
+
* composer's own — still listed in `attachmentManager.attachments`, i.e. still shown above the
|
|
11
|
+
* input, waiting to be sent.
|
|
12
|
+
*
|
|
13
|
+
* This runs when an upload finishes, which can be after the composer was cleared: `uploadFile`
|
|
14
|
+
* executes this chain before the (by then no-op) `updateAttachment`. If the attachment is gone
|
|
15
|
+
* from the composer, a message is rendering from the preview instead, and releasing it would
|
|
16
|
+
* blank that message out. A message can be sent with uploads still running — see
|
|
17
|
+
* `createSendWithPendingUploadsAttachmentsMiddleware`.
|
|
18
|
+
*
|
|
19
|
+
* Omit `composer` and the preview is always released.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createPostUploadAttachmentEnrichmentMiddleware: (composer?: MessageComposer) => AttachmentPostUploadMiddleware;
|
package/dist/types/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { MiddlewareExecutor } from '../../../middleware';
|
|
2
|
-
import type { MessageComposerMiddlewareExecutorOptions, MessageComposerMiddlewareState, MessageDraftComposerMiddlewareExecutorOptions, MessageDraftComposerMiddlewareValueState } from './types';
|
|
2
|
+
import type { MessageComposerMiddlewareExecutorOptions, MessageComposerMiddlewareState, MessageCompositionMiddleware, MessageDraftComposerMiddlewareExecutorOptions, MessageDraftComposerMiddlewareValueState } from './types';
|
|
3
3
|
export declare class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<MessageComposerMiddlewareState, 'compose'> {
|
|
4
4
|
constructor({ composer }: MessageComposerMiddlewareExecutorOptions);
|
|
5
|
+
/**
|
|
6
|
+
* Narrows the base accessor to the composition middleware type, so callers can read the
|
|
7
|
+
* declarations it carries (`allowsPendingUploads`) without casting.
|
|
8
|
+
*/
|
|
9
|
+
get installedMiddleware(): ReadonlyArray<MessageCompositionMiddleware>;
|
|
5
10
|
}
|
|
6
11
|
export declare class MessageDraftComposerMiddlewareExecutor extends MiddlewareExecutor<MessageDraftComposerMiddlewareValueState, 'compose'> {
|
|
7
12
|
constructor({ composer }: MessageDraftComposerMiddlewareExecutorOptions);
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import type { MessageComposer } from '../../messageComposer';
|
|
2
2
|
import type { MessageCompositionMiddleware, MessageDraftCompositionMiddleware } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Drop-in replacement for {@link createAttachmentsCompositionMiddleware} that lets a message be
|
|
5
|
+
* composed while its attachments are still uploading.
|
|
6
|
+
*
|
|
7
|
+
* Reuses the same middleware id, so installing it with
|
|
8
|
+
* `compositionMiddlewareExecutor.replace([...])` keeps its position in the chain. The default
|
|
9
|
+
* refuses instead: it warns "Wait until all attachments have uploaded" and discards the
|
|
10
|
+
* composition.
|
|
11
|
+
*
|
|
12
|
+
* **Installing this is only half of the flow.** The composition it produces is not ready for the
|
|
13
|
+
* wire — `message.attachments` omits everything that has no URL yet. Whoever performs the send
|
|
14
|
+
* has to await those uploads (`UploadManager.upload` is idempotent by `localMetadata.id`, so
|
|
15
|
+
* calling it again returns the in-flight promise), write the resolved URLs in, and send after
|
|
16
|
+
* that.
|
|
17
|
+
*
|
|
18
|
+
* Sendability follows on its own: the `allowsPendingUploads` declaration below is what
|
|
19
|
+
* {@link MessageComposer.hasSendableData} reads to stop treating an upload in flight as a
|
|
20
|
+
* blocker, so installing this middleware is the only switch there is.
|
|
21
|
+
*
|
|
22
|
+
* This is why there is no config option turning it on: the switch belongs to the UI SDK that
|
|
23
|
+
* implements the other half. stream-chat-react exposes it as a `Chat` prop;
|
|
24
|
+
* stream-chat-react-native as `allowSendBeforeAttachmentsUpload` on the message input.
|
|
25
|
+
*/
|
|
26
|
+
export declare const createSendWithPendingUploadsAttachmentsMiddleware: (composer: MessageComposer) => MessageCompositionMiddleware;
|
|
3
27
|
export declare const createAttachmentsCompositionMiddleware: (composer: MessageComposer) => MessageCompositionMiddleware;
|
|
4
28
|
export declare const createDraftAttachmentsCompositionMiddleware: (composer: MessageComposer) => MessageDraftCompositionMiddleware;
|
|
@@ -16,5 +16,26 @@ export type MessageDraftComposerMiddlewareValueState = {
|
|
|
16
16
|
export type MessageDraftComposerMiddlewareExecutorOptions = {
|
|
17
17
|
composer: MessageComposer;
|
|
18
18
|
};
|
|
19
|
-
export type MessageCompositionMiddleware = Middleware<MessageComposerMiddlewareState, 'compose'
|
|
19
|
+
export type MessageCompositionMiddleware = Middleware<MessageComposerMiddlewareState, 'compose'> & {
|
|
20
|
+
/**
|
|
21
|
+
* Declares that this middleware composes messages whose attachment uploads are still in
|
|
22
|
+
* flight, instead of refusing to compose until they finish.
|
|
23
|
+
*
|
|
24
|
+
* {@link MessageComposer.hasSendableData} reads this: an upload in flight must not block the
|
|
25
|
+
* send while such a middleware is installed, or the send could never be triggered. Any
|
|
26
|
+
* middleware implementing that contract may set it - the composer keys off the declaration,
|
|
27
|
+
* not off a middleware id, so a custom implementation is treated the same as the one shipped
|
|
28
|
+
* here.
|
|
29
|
+
*
|
|
30
|
+
* Composing this way is only half of the flow: whoever performs the send has to await those
|
|
31
|
+
* uploads and write the resolved URLs into the payload. See
|
|
32
|
+
* {@link createSendWithPendingUploadsAttachmentsMiddleware}.
|
|
33
|
+
*
|
|
34
|
+
* **Temporary API.** In v10 sending with pending uploads becomes part of the composer
|
|
35
|
+
* configuration (`MessageComposerConfig`, set through `updateConfig`), and this declaration,
|
|
36
|
+
* together with {@link MessageComposer.allowsPendingUploads}, is replaced by that config
|
|
37
|
+
* field.
|
|
38
|
+
*/
|
|
39
|
+
allowsPendingUploads?: boolean;
|
|
40
|
+
};
|
|
20
41
|
export type MessageDraftCompositionMiddleware = Middleware<MessageDraftComposerMiddlewareValueState, 'compose'>;
|
|
@@ -68,6 +68,11 @@ export type LocalAttachmentUploadMetadata = {
|
|
|
68
68
|
*/
|
|
69
69
|
previewUri?: string;
|
|
70
70
|
uploadState: AttachmentLoadingState;
|
|
71
|
+
/**
|
|
72
|
+
* Mirrors {@link UploadRecord.uploadConfirmationPending}: every byte has been sent, but the server has
|
|
73
|
+
* not confirmed yet - not returned response to the upload request. Render an indeterminate indicator rather than a full bar.
|
|
74
|
+
*/
|
|
75
|
+
uploadConfirmationPending?: boolean;
|
|
71
76
|
uploadPermissionCheck?: UploadPermissionCheckResult;
|
|
72
77
|
/** 0–100 while uploading when progress tracking is enabled; undefined otherwise or when indeterminate */
|
|
73
78
|
uploadProgress?: number;
|
|
@@ -34,6 +34,14 @@ export declare class MiddlewareExecutor<TValue, THandlers extends string> {
|
|
|
34
34
|
readonly id: string;
|
|
35
35
|
private middleware;
|
|
36
36
|
constructor();
|
|
37
|
+
/**
|
|
38
|
+
* The middleware currently installed, in execution order.
|
|
39
|
+
*
|
|
40
|
+
* Read-only: use `use` / `insert` / `replace` / `setOrder` / `remove` to change the chain. It
|
|
41
|
+
* exists so components can ask what the chain is capable of - a middleware may declare
|
|
42
|
+
* behaviour its host has to account for, and the host has no other way to find out.
|
|
43
|
+
*/
|
|
44
|
+
get installedMiddleware(): ReadonlyArray<Middleware<TValue, THandlers>>;
|
|
37
45
|
use(middleware: Middleware<TValue, THandlers> | Middleware<TValue, THandlers>[]): this;
|
|
38
46
|
replace(middleware: Middleware<TValue, THandlers>[]): this;
|
|
39
47
|
insert({ middleware, position, unique, }: {
|
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import type { StreamChat } from './client';
|
|
2
2
|
import { StateStore } from './store';
|
|
3
3
|
import type { AttachmentManager } from '.';
|
|
4
|
+
/**
|
|
5
|
+
* Whether an upload ended because it was aborted rather than because it failed.
|
|
6
|
+
*
|
|
7
|
+
* {@link UploadManager.deleteUploadRecord} and {@link UploadManager.cancelAllUploads} abort the
|
|
8
|
+
* request through its `AbortController`, which is what happens when an attachment is removed
|
|
9
|
+
* from the composer mid-upload or the client disconnects. Axios reports that as a
|
|
10
|
+
* `CanceledError` (`axios.isCancel`); a custom `doUploadRequest` on another transport
|
|
11
|
+
* conventionally throws a `DOMException` named `AbortError`.
|
|
12
|
+
*
|
|
13
|
+
* Callers use this to keep a deliberate cancellation from being reported as an error.
|
|
14
|
+
*/
|
|
15
|
+
export declare const isUploadCancellation: (error: unknown) => boolean;
|
|
4
16
|
export type UploadRecord = {
|
|
5
17
|
id: string;
|
|
18
|
+
/**
|
|
19
|
+
* `true` once every byte has been handed to the transport but the server has not responded
|
|
20
|
+
* yet.
|
|
21
|
+
*
|
|
22
|
+
* Upload progress measures bytes *written to the connection*, not bytes the server
|
|
23
|
+
* acknowledged, so it reaches 100% the moment the request body is flushed — then the
|
|
24
|
+
* connection sits idle while the CDN ingests the file and the response travels back. On a
|
|
25
|
+
* large file over a slow link that window is long, and a UI that renders 100% as "done"
|
|
26
|
+
* claims the upload is confirmed while it is not.
|
|
27
|
+
*
|
|
28
|
+
* Nothing measurable happens during that window, so it is the point at which a determinate
|
|
29
|
+
* progress bar should hand over to an indeterminate one. Confirmation is the record being
|
|
30
|
+
* removed, not progress reaching 100.
|
|
31
|
+
*
|
|
32
|
+
* Requires `AttachmentManagerConfig.trackUploadProgress`; without progress reporting there is
|
|
33
|
+
* no way to tell when the flush happened, and this stays `false`.
|
|
34
|
+
*/
|
|
35
|
+
uploadConfirmationPending?: boolean;
|
|
6
36
|
uploadProgress?: number;
|
|
7
37
|
};
|
|
8
38
|
export type UploadManagerState = {
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import FormData from 'form-data';
|
|
2
|
-
import type { AscDesc, ChannelFilters, ChannelQueryOptions, ChannelSort, ChannelSortBase, LocalMessage, LocalMessageBase, Logger, Message, MessagePaginationOptions, MessageResponse, MessageResponseBase, MessageSet, OwnUserResponse, PromoteChannelParams, ReactionGroupResponse, UpdatedMessage, UserResponse } from './types';
|
|
2
|
+
import type { AscDesc, Attachment, ChannelFilters, ChannelQueryOptions, ChannelSort, ChannelSortBase, LocalMessage, LocalMessageBase, Logger, Message, MessagePaginationOptions, MessageResponse, MessageResponseBase, MessageSet, OwnUserResponse, PromoteChannelParams, ReactionGroupResponse, UpdatedMessage, UserResponse } from './types';
|
|
3
3
|
import type { StreamChat } from './client';
|
|
4
4
|
import type { Channel } from './channel';
|
|
5
5
|
import type { AxiosRequestConfig } from 'axios';
|
|
@@ -12,6 +12,15 @@ import type { AxiosRequestConfig } from 'axios';
|
|
|
12
12
|
*
|
|
13
13
|
*/
|
|
14
14
|
export declare function logChatPromiseExecution<T>(promise: Promise<T>, name: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* The default no-op client logger.
|
|
17
|
+
*/
|
|
18
|
+
export declare const noopLogger: Logger;
|
|
19
|
+
/**
|
|
20
|
+
* Invokes a single event listener in isolation, so that a faulty listener cannot
|
|
21
|
+
* abort the dispatch loop or the bookkeeping that follows it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const invokeEventListener: <T>(listener: (event: T) => unknown, event: T, logger: Logger) => void;
|
|
15
24
|
export declare const sleep: (m: number) => Promise<void>;
|
|
16
25
|
export declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
|
|
17
26
|
export declare const chatCodes: {
|
|
@@ -28,11 +37,6 @@ export declare const channelHasReadEvents: (channel?: Channel) => boolean;
|
|
|
28
37
|
* channelTracksReadLocally - Whether a channel maintains a client local unread count.
|
|
29
38
|
*/
|
|
30
39
|
export declare const channelTracksReadLocally: (channel?: Channel) => boolean;
|
|
31
|
-
/**
|
|
32
|
-
* userHasReadReceipts - Whether the current user allows read receipts, per their privacy settings.
|
|
33
|
-
* Read receipts are treated as enabled unless the user has explicitly disabled them.
|
|
34
|
-
*/
|
|
35
|
-
export declare const userHasReadReceipts: (client: StreamChat) => boolean;
|
|
36
40
|
export declare function addFileToFormData(uri: string | NodeJS.ReadableStream | Buffer | File, name?: string, contentType?: string): FormData;
|
|
37
41
|
export declare function normalizeQuerySort<T extends Record<string, AscDesc | undefined>>(sort: T | T[]): {
|
|
38
42
|
direction: AscDesc;
|
|
@@ -74,6 +78,25 @@ export declare function formatMessage(message: MessageResponse | MessageResponse
|
|
|
74
78
|
* @param {MessageResponse} message `MessageResponse` object
|
|
75
79
|
*/
|
|
76
80
|
export declare function unformatMessage(message: LocalMessage): MessageResponse;
|
|
81
|
+
/**
|
|
82
|
+
* Strips composer-internal state from a message's attachments, and drops any whose upload never
|
|
83
|
+
* resolved.
|
|
84
|
+
*
|
|
85
|
+
* `localMetadata` is how `AttachmentManager` tracks an upload (its id, the `File`, the local
|
|
86
|
+
* preview); it must never reach the API. An attachment that still carries it and has no remote
|
|
87
|
+
* URL was never settled — which is what happens when
|
|
88
|
+
* `createSendWithPendingUploadsAttachmentsMiddleware` is installed by a UI SDK that does not
|
|
89
|
+
* implement the rest of the flow (awaiting those uploads before sending). Sending it would store
|
|
90
|
+
* an attachment pointing at nothing, so it is dropped and logged instead.
|
|
91
|
+
*
|
|
92
|
+
* Returns the same message object when there was nothing to change.
|
|
93
|
+
*/
|
|
94
|
+
export declare const sanitizeOutgoingAttachments: <T extends {
|
|
95
|
+
attachments?: Attachment[];
|
|
96
|
+
}>({ client, message, }: {
|
|
97
|
+
client: StreamChat;
|
|
98
|
+
message: T;
|
|
99
|
+
}) => T;
|
|
77
100
|
export declare const localMessageToNewMessagePayload: (localMessage: LocalMessage) => Message;
|
|
78
101
|
export declare const toUpdatedMessagePayload: (message: LocalMessage | Partial<MessageResponse>) => UpdatedMessage;
|
|
79
102
|
export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -6,9 +6,11 @@ import { MessageReceiptsTracker } from './messageDelivery';
|
|
|
6
6
|
import {
|
|
7
7
|
channelHasReadEvents,
|
|
8
8
|
generateChannelTempCid,
|
|
9
|
+
invokeEventListener,
|
|
9
10
|
logChatPromiseExecution,
|
|
10
11
|
messageSetPagination,
|
|
11
12
|
normalizeQuerySort,
|
|
13
|
+
sanitizeOutgoingAttachments,
|
|
12
14
|
} from './utils';
|
|
13
15
|
import type { StreamChat } from './client';
|
|
14
16
|
import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from './constants';
|
|
@@ -212,14 +214,17 @@ export class Channel {
|
|
|
212
214
|
*
|
|
213
215
|
* @return {Promise<SendMessageAPIResponse>} The Server Response
|
|
214
216
|
*/
|
|
215
|
-
async _sendMessage(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
217
|
+
async _sendMessage(rawMessage: Message, options?: SendMessageOptions) {
|
|
218
|
+
const client = this.getClient();
|
|
219
|
+
const message = sanitizeOutgoingAttachments({
|
|
220
|
+
client,
|
|
221
|
+
message: rawMessage,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return await client.post<SendMessageAPIResponse>(this._channelURL() + '/message', {
|
|
225
|
+
message,
|
|
226
|
+
...options,
|
|
227
|
+
});
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
async sendMessage(message: Message, options?: SendMessageOptions) {
|
|
@@ -1217,7 +1222,8 @@ export class Channel {
|
|
|
1217
1222
|
}
|
|
1218
1223
|
|
|
1219
1224
|
/**
|
|
1220
|
-
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
|
|
1225
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is
|
|
1226
|
+
* enabled and the user holds the `read-events` capability on this channel
|
|
1221
1227
|
*
|
|
1222
1228
|
* @param {MarkReadOptions} data
|
|
1223
1229
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
@@ -1225,7 +1231,13 @@ export class Channel {
|
|
|
1225
1231
|
async markAsReadRequest(data: MarkReadOptions = {}) {
|
|
1226
1232
|
this._checkInitialized();
|
|
1227
1233
|
|
|
1228
|
-
|
|
1234
|
+
// Server-side auth acts on behalf of a user with no own capabilities, so only the client-side
|
|
1235
|
+
// flow is gated. Channels the user has no `read-events` capability on do not track read state
|
|
1236
|
+
// on the backend at all - see `markReadLocally` for the client-local unread count.
|
|
1237
|
+
if (
|
|
1238
|
+
!this.getClient()._isUsingServerAuth() &&
|
|
1239
|
+
(!this.getConfig()?.read_events || !channelHasReadEvents(this))
|
|
1240
|
+
) {
|
|
1229
1241
|
return null;
|
|
1230
1242
|
}
|
|
1231
1243
|
|
|
@@ -1235,7 +1247,8 @@ export class Channel {
|
|
|
1235
1247
|
}
|
|
1236
1248
|
|
|
1237
1249
|
/**
|
|
1238
|
-
* markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
|
|
1250
|
+
* markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is
|
|
1251
|
+
* enabled and the user holds the `read-events` capability on this channel
|
|
1239
1252
|
*
|
|
1240
1253
|
* @param {MarkUnreadOptions} data
|
|
1241
1254
|
* @return {APIResponse} An API response
|
|
@@ -1243,7 +1256,11 @@ export class Channel {
|
|
|
1243
1256
|
async markUnread(data: MarkUnreadOptions) {
|
|
1244
1257
|
this._checkInitialized();
|
|
1245
1258
|
|
|
1246
|
-
|
|
1259
|
+
// Gated the same way as `markAsReadRequest` - see the comment there.
|
|
1260
|
+
if (
|
|
1261
|
+
!this.getClient()._isUsingServerAuth() &&
|
|
1262
|
+
(!this.getConfig()?.read_events || !channelHasReadEvents(this))
|
|
1263
|
+
) {
|
|
1247
1264
|
return Promise.resolve(null);
|
|
1248
1265
|
}
|
|
1249
1266
|
|
|
@@ -2007,6 +2024,10 @@ export class Channel {
|
|
|
2007
2024
|
// delivery-report network sync below is skipped for it.
|
|
2008
2025
|
case 'message.read_locally':
|
|
2009
2026
|
case 'message.read':
|
|
2027
|
+
// Thread read events are handled indiscriminately within the reactive `thread` object, we can
|
|
2028
|
+
// skip them here in order to ensure the channel does not get read incidentally when it should
|
|
2029
|
+
// not.
|
|
2030
|
+
if (event.thread) break;
|
|
2010
2031
|
if (event.user?.id && event.created_at) {
|
|
2011
2032
|
const previousReadState = channelState.read[event.user.id];
|
|
2012
2033
|
channelState.read[event.user.id] = {
|
|
@@ -2346,22 +2367,17 @@ export class Channel {
|
|
|
2346
2367
|
}
|
|
2347
2368
|
|
|
2348
2369
|
_callChannelListeners = (event: Event) => {
|
|
2349
|
-
//
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
if (channel.listeners[event.type]) {
|
|
2357
|
-
listeners.push(...channel.listeners[event.type]);
|
|
2358
|
-
}
|
|
2370
|
+
// snapshot before dispatching: `on` pushes into these arrays in place, so a
|
|
2371
|
+
// listener that subscribes while handling an event must not be invoked for it
|
|
2372
|
+
const listeners = [
|
|
2373
|
+
...(this.listeners.all ?? []),
|
|
2374
|
+
...(this.listeners[event.type] ?? []),
|
|
2375
|
+
];
|
|
2359
2376
|
|
|
2360
|
-
|
|
2377
|
+
const { logger } = this._client;
|
|
2361
2378
|
for (const listener of listeners) {
|
|
2362
|
-
if (typeof listener
|
|
2363
|
-
|
|
2364
|
-
}
|
|
2379
|
+
if (typeof listener === 'string') continue;
|
|
2380
|
+
invokeEventListener(listener, event, logger);
|
|
2365
2381
|
}
|
|
2366
2382
|
};
|
|
2367
2383
|
|
package/src/client.ts
CHANGED
|
@@ -29,13 +29,16 @@ import {
|
|
|
29
29
|
axiosParamsSerializer,
|
|
30
30
|
chatCodes,
|
|
31
31
|
generateChannelTempCid,
|
|
32
|
+
invokeEventListener,
|
|
32
33
|
isFunction,
|
|
33
34
|
isOnline,
|
|
34
35
|
isOwnUserBaseProperty,
|
|
35
36
|
messageSetPagination,
|
|
37
|
+
noopLogger,
|
|
36
38
|
normalizeQuerySort,
|
|
37
39
|
randomId,
|
|
38
40
|
retryInterval,
|
|
41
|
+
sanitizeOutgoingAttachments,
|
|
39
42
|
sleep,
|
|
40
43
|
toUpdatedMessagePayload,
|
|
41
44
|
} from './utils';
|
|
@@ -574,7 +577,7 @@ export class StreamChat {
|
|
|
574
577
|
* channel: object
|
|
575
578
|
* }
|
|
576
579
|
*/
|
|
577
|
-
this.logger = isFunction(inputOptions.logger) ? inputOptions.logger :
|
|
580
|
+
this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : noopLogger;
|
|
578
581
|
this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;
|
|
579
582
|
this.threads = new ThreadManager({ client: this });
|
|
580
583
|
this.polls = new PollManager({ client: this });
|
|
@@ -1699,20 +1702,15 @@ export class StreamChat {
|
|
|
1699
1702
|
}
|
|
1700
1703
|
|
|
1701
1704
|
_callClientListeners = (event: Event) => {
|
|
1702
|
-
//
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
if (client.listeners[event.type]) {
|
|
1710
|
-
listeners.push(...client.listeners[event.type]);
|
|
1711
|
-
}
|
|
1705
|
+
// snapshot before dispatching: `on` pushes into these arrays in place, so a
|
|
1706
|
+
// listener that subscribes while handling an event must not be invoked for it
|
|
1707
|
+
const listeners = [
|
|
1708
|
+
...(this.listeners.all ?? []),
|
|
1709
|
+
...(this.listeners[event.type] ?? []),
|
|
1710
|
+
];
|
|
1712
1711
|
|
|
1713
|
-
// call the event and send it to the listeners
|
|
1714
1712
|
for (const listener of listeners) {
|
|
1715
|
-
listener
|
|
1713
|
+
invokeEventListener(listener, event, this.logger);
|
|
1716
1714
|
}
|
|
1717
1715
|
};
|
|
1718
1716
|
|
|
@@ -3051,6 +3049,11 @@ export class StreamChat {
|
|
|
3051
3049
|
|
|
3052
3050
|
/**
|
|
3053
3051
|
* unflagMessage - unflag a message
|
|
3052
|
+
*
|
|
3053
|
+
* @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
|
|
3054
|
+
* the backend - calling this resolves successfully but does not remove the flag.
|
|
3055
|
+
* Use `client.moderation.submitAction()` against the review queue item instead.
|
|
3056
|
+
*
|
|
3054
3057
|
* @param {string} targetMessageID
|
|
3055
3058
|
* @param {string} [options.user_id] currentUserID, only used with serverside auth
|
|
3056
3059
|
* @returns {Promise<APIResponse>}
|
|
@@ -3064,6 +3067,11 @@ export class StreamChat {
|
|
|
3064
3067
|
|
|
3065
3068
|
/**
|
|
3066
3069
|
* unflagUser - unflag a user
|
|
3070
|
+
*
|
|
3071
|
+
* @deprecated The `/moderation/unflag` endpoint is deprecated and is a no-op on
|
|
3072
|
+
* the backend - calling this resolves successfully but does not remove the flag.
|
|
3073
|
+
* Use `client.moderation.submitAction()` against the review queue item instead.
|
|
3074
|
+
*
|
|
3067
3075
|
* @param {string} targetID
|
|
3068
3076
|
* @param {string} [options.user_id] currentUserID, only used with serverside auth
|
|
3069
3077
|
* @returns {Promise<APIResponse>}
|
|
@@ -3420,7 +3428,12 @@ export class StreamChat {
|
|
|
3420
3428
|
}
|
|
3421
3429
|
|
|
3422
3430
|
// should not include user object
|
|
3423
|
-
|
|
3431
|
+
// Sanitized at the point of sending, which is the only place every path converges: the
|
|
3432
|
+
// offline replay of a queued `update-message` task calls this method directly.
|
|
3433
|
+
const payload = sanitizeOutgoingAttachments({
|
|
3434
|
+
client: this,
|
|
3435
|
+
message: toUpdatedMessagePayload(message),
|
|
3436
|
+
});
|
|
3424
3437
|
|
|
3425
3438
|
// add user_id (if exists)
|
|
3426
3439
|
if (typeof partialUserOrUserId === 'string') {
|
package/src/index.ts
CHANGED
|
@@ -62,3 +62,15 @@ export {
|
|
|
62
62
|
promoteChannel,
|
|
63
63
|
} from './utils';
|
|
64
64
|
export { FixedSizeQueueCache } from './utils/FixedSizeQueueCache';
|
|
65
|
+
/**
|
|
66
|
+
* Tag-keyed async runners. Exported so the UI SDKs (and integrators) serialize their own
|
|
67
|
+
* actions with the same primitive the SDK uses internally instead of hand-rolling promise
|
|
68
|
+
* chains. Tags share one process-wide map, so namespace yours (`my-app/thing/${id}`) to avoid
|
|
69
|
+
* colliding with another caller's.
|
|
70
|
+
*/
|
|
71
|
+
export {
|
|
72
|
+
hasPending,
|
|
73
|
+
settled,
|
|
74
|
+
withCancellation,
|
|
75
|
+
withoutConcurrency,
|
|
76
|
+
} from './utils/concurrency';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Attachment, SharedLocationResponse } from '../types';
|
|
2
2
|
import type {
|
|
3
|
+
AttachmentLoadingState,
|
|
3
4
|
AudioAttachment,
|
|
4
5
|
FileAttachment,
|
|
5
6
|
GiphyAttachment,
|
|
@@ -27,6 +28,32 @@ export const isLocalUploadAttachment = (
|
|
|
27
28
|
): attachment is LocalUploadAttachment =>
|
|
28
29
|
!!(attachment as LocalAttachment)?.localMetadata?.uploadState;
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Upload states meaning "the bytes are not on the CDN yet, but they are on their way".
|
|
33
|
+
* `blocked` and `failed` are excluded - those never resolve on their own.
|
|
34
|
+
*
|
|
35
|
+
* Typed against {@link AttachmentLoadingState} rather than `string`, so renaming a state fails
|
|
36
|
+
* the build instead of silently making this list match nothing - it decides what gets sent.
|
|
37
|
+
*/
|
|
38
|
+
const PENDING_UPLOAD_STATES: ReadonlyArray<AttachmentLoadingState> = [
|
|
39
|
+
'pending',
|
|
40
|
+
'uploading',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/** Whether an upload for this attachment is still expected to resolve. */
|
|
44
|
+
export const isPendingUpload = (
|
|
45
|
+
attachment: unknown,
|
|
46
|
+
): attachment is LocalUploadAttachment =>
|
|
47
|
+
isLocalUploadAttachment(attachment) &&
|
|
48
|
+
PENDING_UPLOAD_STATES.includes(attachment.localMetadata.uploadState);
|
|
49
|
+
|
|
50
|
+
/** Whether this attachment's upload already resolved to a URL. */
|
|
51
|
+
export const isFinishedUpload = (
|
|
52
|
+
attachment: unknown,
|
|
53
|
+
): attachment is LocalUploadAttachment =>
|
|
54
|
+
isLocalUploadAttachment(attachment) &&
|
|
55
|
+
attachment.localMetadata.uploadState === 'finished';
|
|
56
|
+
|
|
30
57
|
export const isFileAttachment = (
|
|
31
58
|
attachment: Attachment | LocalAttachment,
|
|
32
59
|
supportedVideoFormat: string[] = [],
|