stream-chat 9.41.0 → 9.42.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.
- package/dist/cjs/index.browser.js +226 -79
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +227 -79
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +226 -79
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +1 -0
- package/dist/types/channel_state.d.ts +2 -0
- package/dist/types/client.d.ts +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messageComposer/LocationComposer.d.ts +1 -1
- package/dist/types/messageComposer/attachmentManager.d.ts +1 -0
- package/dist/types/messageComposer/configuration/types.d.ts +6 -1
- package/dist/types/messageComposer/fileUtils.d.ts +1 -1
- package/dist/types/messageComposer/messageComposer.d.ts +4 -4
- package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +2 -2
- package/dist/types/messageComposer/pollComposer.d.ts +2 -2
- package/dist/types/messageComposer/textComposer.d.ts +2 -2
- package/dist/types/uploadManager.d.ts +45 -0
- package/dist/types/utils.d.ts +4 -3
- package/package.json +2 -2
- package/src/channel.ts +9 -2
- package/src/channel_state.ts +5 -1
- package/src/client.ts +17 -2
- package/src/index.ts +1 -0
- package/src/messageComposer/attachmentManager.ts +65 -69
- package/src/messageComposer/configuration/types.ts +6 -1
- package/src/uploadManager.ts +176 -0
- package/src/utils.ts +26 -10
package/dist/types/channel.d.ts
CHANGED
|
@@ -748,6 +748,7 @@ export declare class Channel {
|
|
|
748
748
|
_checkInitialized(): void;
|
|
749
749
|
_initializeState(state: ChannelAPIResponse, messageSetToAddToIfDoesNotExist?: MessageSetType): {
|
|
750
750
|
messageSet: import("./types").MessageSet;
|
|
751
|
+
filteredMessageIds: string[];
|
|
751
752
|
};
|
|
752
753
|
_extendEventWithOwnReactions(event: Event): void;
|
|
753
754
|
_hydrateMembers({ members, overrideCurrentState, }: {
|
|
@@ -67,6 +67,7 @@ export declare class ChannelState {
|
|
|
67
67
|
*/
|
|
68
68
|
addMessageSorted(newMessage: MessageResponse | LocalMessage, timestampChanged?: boolean, addIfDoesNotExist?: boolean, messageSetToAddToIfDoesNotExist?: MessageSetType): {
|
|
69
69
|
messageSet: MessageSet;
|
|
70
|
+
filteredMessageIds: string[];
|
|
70
71
|
};
|
|
71
72
|
/**
|
|
72
73
|
* Takes the message object, parses the dates, sets `__html`
|
|
@@ -87,6 +88,7 @@ export declare class ChannelState {
|
|
|
87
88
|
*/
|
|
88
89
|
addMessagesSorted(newMessages: (MessageResponse | LocalMessage)[], timestampChanged?: boolean, initializing?: boolean, addIfDoesNotExist?: boolean, messageSetToAddToIfDoesNotExist?: MessageSetType): {
|
|
89
90
|
messageSet: MessageSet;
|
|
91
|
+
filteredMessageIds: string[];
|
|
90
92
|
};
|
|
91
93
|
/**
|
|
92
94
|
* addPinnedMessages - adds messages in pinnedMessages property
|
package/dist/types/client.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type WebSocket from 'isomorphic-ws';
|
|
|
3
3
|
import { Channel } from './channel';
|
|
4
4
|
import { ClientState } from './client_state';
|
|
5
5
|
import { StableWSConnection } from './connection';
|
|
6
|
+
import { UploadManager } from './uploadManager';
|
|
6
7
|
import { TokenManager } from './token_manager';
|
|
7
8
|
import { WSConnectionFallback } from './connection_fallback';
|
|
8
9
|
import { Campaign } from './campaign';
|
|
@@ -44,6 +45,10 @@ export type MessageComposerSetupState = {
|
|
|
44
45
|
export declare class StreamChat {
|
|
45
46
|
private static _instance?;
|
|
46
47
|
messageDeliveryReporter: MessageDeliveryReporter;
|
|
48
|
+
/**
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
uploadManager: UploadManager;
|
|
47
52
|
_user?: OwnUserResponse | UserResponse;
|
|
48
53
|
appSettingsPromise?: Promise<AppSettingsAPIResponse>;
|
|
49
54
|
activeChannels: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export type { ThreadState, ThreadReadState, ThreadRepliesPagination, ThreadUserR
|
|
|
28
28
|
export * from './thread_manager';
|
|
29
29
|
export * from './token_manager';
|
|
30
30
|
export * from './types';
|
|
31
|
+
export * from './uploadManager';
|
|
31
32
|
export * from './channel_manager';
|
|
32
33
|
export * from './offline-support';
|
|
33
34
|
export * from './LiveLocationManager';
|
|
@@ -21,7 +21,7 @@ export declare class LocationComposer {
|
|
|
21
21
|
readonly composer: MessageComposer;
|
|
22
22
|
private _deviceId;
|
|
23
23
|
constructor({ composer, message }: LocationComposerOptions);
|
|
24
|
-
get config(): import("
|
|
24
|
+
get config(): import("./configuration").LocationComposerConfig;
|
|
25
25
|
get deviceId(): string;
|
|
26
26
|
get location(): StaticLocationPayload | LiveLocationPreview | null;
|
|
27
27
|
get validLocation(): StaticLocationPayload | LiveLocationPayload | null;
|
|
@@ -73,4 +73,5 @@ export declare class AttachmentManager {
|
|
|
73
73
|
uploadAttachment: (attachment: LocalUploadAttachment) => Promise<LocalUploadAttachment | undefined>;
|
|
74
74
|
uploadFile: (file: FileReference | FileLike) => Promise<LocalUploadAttachment>;
|
|
75
75
|
uploadFiles: (files: FileReference[] | FileList | FileLike[]) => Promise<LocalUploadAttachment[] | undefined>;
|
|
76
|
+
private upload;
|
|
76
77
|
}
|
|
@@ -5,9 +5,14 @@ export type MinimumUploadRequestResult = {
|
|
|
5
5
|
file: string;
|
|
6
6
|
thumb_url?: string;
|
|
7
7
|
} & Partial<Record<string, unknown>>;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Optional second argument to `UploadRequestFn`.
|
|
10
|
+
* - Call `onProgress` to report 0–100 or `undefined` when indeterminate.
|
|
11
|
+
* - Forward `abortSignal` to your upload implementation to cancel the upload if the user deletes the attachment while it's uploading.
|
|
12
|
+
*/
|
|
9
13
|
export type UploadRequestOptions = {
|
|
10
14
|
onProgress?: (percent: number | undefined) => void;
|
|
15
|
+
abortSignal?: AbortSignal;
|
|
11
16
|
};
|
|
12
17
|
export type UploadRequestFn = (fileLike: FileReference | FileLike, options?: UploadRequestOptions) => Promise<MinimumUploadRequestResult>;
|
|
13
18
|
export type DraftsConfiguration = {
|
|
@@ -13,5 +13,5 @@ export declare const getExtensionFromMimeType: (mimeType: string) => string | un
|
|
|
13
13
|
export declare const readFileAsArrayBuffer: (file: File) => Promise<ArrayBuffer>;
|
|
14
14
|
export declare const generateFileName: (mimeType: string) => string;
|
|
15
15
|
export declare const isImageFile: (fileLike: FileReference | FileLike) => boolean;
|
|
16
|
-
export declare const getAttachmentTypeFromMimeType: (mimeType: string) => "file" | "
|
|
16
|
+
export declare const getAttachmentTypeFromMimeType: (mimeType: string) => "file" | "image" | "audio" | "video";
|
|
17
17
|
export declare const ensureIsLocalAttachment: (attachment: Attachment | LocalAttachment) => LocalAttachment | null;
|
|
@@ -61,15 +61,15 @@ export declare class MessageComposer extends WithSubscriptions {
|
|
|
61
61
|
locationComposer: LocationComposer;
|
|
62
62
|
customDataManager: CustomDataManager;
|
|
63
63
|
constructor({ composition, config, compositionContext, client, }: MessageComposerOptions);
|
|
64
|
-
static evaluateContextType(compositionContext: CompositionContext): "
|
|
64
|
+
static evaluateContextType(compositionContext: CompositionContext): "message" | "channel" | "thread" | "legacy_thread";
|
|
65
65
|
static constructTag(compositionContext: CompositionContext): `${ReturnType<typeof MessageComposer.evaluateContextType>}_${string}`;
|
|
66
66
|
static generateId: typeof generateUUIDv4;
|
|
67
67
|
get config(): MessageComposerConfig;
|
|
68
68
|
get editedMessage(): LocalMessage | undefined;
|
|
69
69
|
set editedMessage(editedMessage: LocalMessage | undefined);
|
|
70
70
|
setEditedMessage: (editedMessage: LocalMessage | null | undefined) => void;
|
|
71
|
-
get contextType(): "
|
|
72
|
-
get tag(): `
|
|
71
|
+
get contextType(): "message" | "channel" | "thread" | "legacy_thread";
|
|
72
|
+
get tag(): `message_${string}` | `channel_${string}` | `thread_${string}` | `legacy_thread_${string}`;
|
|
73
73
|
get threadId(): string | null;
|
|
74
74
|
get client(): StreamChat;
|
|
75
75
|
get id(): string;
|
|
@@ -111,7 +111,7 @@ export declare class MessageComposer extends WithSubscriptions {
|
|
|
111
111
|
clear: () => void;
|
|
112
112
|
restore: () => void;
|
|
113
113
|
compose: () => Promise<MessageComposerMiddlewareValue["state"] | undefined>;
|
|
114
|
-
composeDraft: () => Promise<import("
|
|
114
|
+
composeDraft: () => Promise<import("./middleware").MessageDraftComposerMiddlewareValueState | undefined>;
|
|
115
115
|
createDraft: () => Promise<void>;
|
|
116
116
|
deleteDraft: () => Promise<void>;
|
|
117
117
|
getDraft: () => Promise<void>;
|
|
@@ -21,12 +21,12 @@ export declare class CommandSearchSource extends BaseSearchSourceSync<CommandSug
|
|
|
21
21
|
};
|
|
22
22
|
query(searchQuery: string): {
|
|
23
23
|
items: {
|
|
24
|
-
id: "all" | "giphy" | "
|
|
24
|
+
id: "all" | "giphy" | "ban" | "fun_set" | "moderation_set" | "mute" | "unban" | "unmute";
|
|
25
25
|
created_at?: string | undefined;
|
|
26
26
|
updated_at?: string | undefined;
|
|
27
27
|
args?: string;
|
|
28
28
|
description?: string;
|
|
29
|
-
name: "all" | "giphy" | "
|
|
29
|
+
name: "all" | "giphy" | "ban" | "fun_set" | "moderation_set" | "mute" | "unban" | "unmute";
|
|
30
30
|
set?: import("../../..").CommandVariants;
|
|
31
31
|
}[];
|
|
32
32
|
next: null;
|
|
@@ -20,7 +20,7 @@ export declare class PollComposer {
|
|
|
20
20
|
get id(): string;
|
|
21
21
|
get max_votes_allowed(): string;
|
|
22
22
|
get name(): string;
|
|
23
|
-
get options(): import("
|
|
23
|
+
get options(): import("./middleware").PollComposerOption[];
|
|
24
24
|
get user_id(): string | undefined;
|
|
25
25
|
get voting_visibility(): VotingVisibility | undefined;
|
|
26
26
|
get canCreatePoll(): boolean;
|
|
@@ -32,5 +32,5 @@ export declare class PollComposer {
|
|
|
32
32
|
*/
|
|
33
33
|
updateFields: (data: UpdateFieldsData, injectedFieldErrors?: PollComposerFieldErrors) => Promise<void>;
|
|
34
34
|
handleFieldBlur: (field: keyof PollComposerState["data"]) => Promise<void>;
|
|
35
|
-
compose: () => Promise<import("
|
|
35
|
+
compose: () => Promise<import("./middleware").PollComposerCompositionMiddlewareValueState | undefined>;
|
|
36
36
|
}
|
|
@@ -17,7 +17,7 @@ export declare class TextComposer {
|
|
|
17
17
|
middlewareExecutor: TextComposerMiddlewareExecutor;
|
|
18
18
|
constructor({ composer, message }: TextComposerOptions);
|
|
19
19
|
get channel(): import("..").Channel;
|
|
20
|
-
get config(): import("
|
|
20
|
+
get config(): import("./configuration").TextComposerConfig;
|
|
21
21
|
get enabled(): boolean;
|
|
22
22
|
set enabled(enabled: boolean);
|
|
23
23
|
get defaultValue(): string | undefined;
|
|
@@ -31,7 +31,7 @@ export declare class TextComposer {
|
|
|
31
31
|
get command(): CommandResponse | null | undefined;
|
|
32
32
|
get mentionedUsers(): UserResponse[];
|
|
33
33
|
get selection(): TextSelection;
|
|
34
|
-
get suggestions(): Suggestions<import("
|
|
34
|
+
get suggestions(): Suggestions<import("./middleware").Suggestion> | undefined;
|
|
35
35
|
get text(): string;
|
|
36
36
|
get textIsEmpty(): boolean;
|
|
37
37
|
initState: ({ message }?: {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StreamChat } from './client';
|
|
2
|
+
import { StateStore } from './store';
|
|
3
|
+
import type { AttachmentManager } from '.';
|
|
4
|
+
export type UploadRecord = {
|
|
5
|
+
id: string;
|
|
6
|
+
uploadProgress?: number;
|
|
7
|
+
};
|
|
8
|
+
export type UploadManagerState = {
|
|
9
|
+
uploads: Record<string, UploadRecord>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare class UploadManager {
|
|
15
|
+
private readonly client;
|
|
16
|
+
readonly state: StateStore<UploadManagerState>;
|
|
17
|
+
private inFlightUploads;
|
|
18
|
+
constructor(client: StreamChat);
|
|
19
|
+
private resolveAttachmentManager;
|
|
20
|
+
get uploads(): Record<string, UploadRecord>;
|
|
21
|
+
getUpload: (id: string) => UploadRecord;
|
|
22
|
+
/**
|
|
23
|
+
* Clears all upload records.
|
|
24
|
+
* Invoked when the user disconnects so a later session does not inherit stale upload state.
|
|
25
|
+
* Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
|
|
26
|
+
*/
|
|
27
|
+
reset: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Removes the upload record for `id` if present.
|
|
30
|
+
* If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
|
|
31
|
+
*/
|
|
32
|
+
deleteUploadRecord: (id: string) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
|
|
35
|
+
* Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
|
|
36
|
+
* Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
|
|
37
|
+
*/
|
|
38
|
+
upload: ({ id, channelCid, file, }: {
|
|
39
|
+
id: string;
|
|
40
|
+
channelCid: string;
|
|
41
|
+
file: Parameters<typeof AttachmentManager.prototype.doUploadRequest>[0];
|
|
42
|
+
}) => ReturnType<typeof AttachmentManager.prototype.doUploadRequest>;
|
|
43
|
+
private upsertUpload;
|
|
44
|
+
private updateUpload;
|
|
45
|
+
}
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -89,8 +89,6 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
|
|
|
89
89
|
deleted_at: Date | null;
|
|
90
90
|
text?: string | undefined;
|
|
91
91
|
user_id?: string | undefined;
|
|
92
|
-
user?: (UserResponse | null) | undefined;
|
|
93
|
-
channel?: import("./types").ChannelResponse | undefined;
|
|
94
92
|
command?: string | undefined;
|
|
95
93
|
mentioned_users?: UserResponse[] | undefined;
|
|
96
94
|
latest_reactions?: import("./types").ReactionResponse[] | undefined;
|
|
@@ -103,9 +101,10 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
|
|
|
103
101
|
language: import("./types").TranslationLanguages;
|
|
104
102
|
}) | undefined;
|
|
105
103
|
html?: string | undefined;
|
|
106
|
-
|
|
104
|
+
user?: (UserResponse | null) | undefined;
|
|
107
105
|
id: string;
|
|
108
106
|
mml?: string | undefined;
|
|
107
|
+
parent_id?: string | undefined;
|
|
109
108
|
pin_expires?: string | null | undefined;
|
|
110
109
|
pinned?: boolean | undefined;
|
|
111
110
|
poll_id?: string | undefined;
|
|
@@ -115,6 +114,7 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
|
|
|
115
114
|
silent?: boolean | undefined;
|
|
116
115
|
args?: string | undefined;
|
|
117
116
|
before_message_send_failed?: boolean | undefined;
|
|
117
|
+
channel?: import("./types").ChannelResponse | undefined;
|
|
118
118
|
cid?: string | undefined;
|
|
119
119
|
command_info?: {
|
|
120
120
|
name?: string;
|
|
@@ -223,6 +223,7 @@ type MessagePaginationUpdatedParams = {
|
|
|
223
223
|
parentSet: MessageSet;
|
|
224
224
|
requestedPageSize: number;
|
|
225
225
|
returnedPage: MessageResponse[];
|
|
226
|
+
filteredReturnedPage: MessageResponse[];
|
|
226
227
|
logger?: Logger;
|
|
227
228
|
messagePaginationOptions?: MessagePaginationOptions;
|
|
228
229
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.42.0",
|
|
4
4
|
"description": "JS SDK for the Stream Chat API",
|
|
5
5
|
"homepage": "https://getstream.io/chat/",
|
|
6
6
|
"author": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@types/jsonwebtoken": "^9.0.8",
|
|
53
53
|
"@types/ws": "^8.5.14",
|
|
54
|
-
"axios": "^1.
|
|
54
|
+
"axios": "^1.15.1",
|
|
55
55
|
"base64-js": "^1.5.1",
|
|
56
56
|
"form-data": "^4.0.4",
|
|
57
57
|
"isomorphic-ws": "^5.0.0",
|
package/src/channel.ts
CHANGED
|
@@ -1581,7 +1581,10 @@ export class Channel {
|
|
|
1581
1581
|
}
|
|
1582
1582
|
|
|
1583
1583
|
// add any messages to our channel state
|
|
1584
|
-
const { messageSet } = this._initializeState(
|
|
1584
|
+
const { messageSet, filteredMessageIds } = this._initializeState(
|
|
1585
|
+
state,
|
|
1586
|
+
messageSetToAddToIfDoesNotExist,
|
|
1587
|
+
);
|
|
1585
1588
|
messageSet.pagination = {
|
|
1586
1589
|
...messageSet.pagination,
|
|
1587
1590
|
...messageSetPagination({
|
|
@@ -1590,6 +1593,9 @@ export class Channel {
|
|
|
1590
1593
|
requestedPageSize:
|
|
1591
1594
|
options?.messages?.limit ?? DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE,
|
|
1592
1595
|
returnedPage: state.messages,
|
|
1596
|
+
filteredReturnedPage: state.messages.filter(
|
|
1597
|
+
(m) => !filteredMessageIds.includes(m.id),
|
|
1598
|
+
),
|
|
1593
1599
|
logger: this.getClient().logger,
|
|
1594
1600
|
}),
|
|
1595
1601
|
};
|
|
@@ -2368,7 +2374,7 @@ export class Channel {
|
|
|
2368
2374
|
if (!this.state.messages) {
|
|
2369
2375
|
this.state.initMessages();
|
|
2370
2376
|
}
|
|
2371
|
-
const { messageSet } = this.state.addMessagesSorted(
|
|
2377
|
+
const { messageSet, filteredMessageIds } = this.state.addMessagesSorted(
|
|
2372
2378
|
messages,
|
|
2373
2379
|
false,
|
|
2374
2380
|
true,
|
|
@@ -2434,6 +2440,7 @@ export class Channel {
|
|
|
2434
2440
|
|
|
2435
2441
|
return {
|
|
2436
2442
|
messageSet,
|
|
2443
|
+
filteredMessageIds,
|
|
2437
2444
|
};
|
|
2438
2445
|
}
|
|
2439
2446
|
|
package/src/channel_state.ts
CHANGED
|
@@ -217,9 +217,12 @@ export class ChannelState {
|
|
|
217
217
|
messageSetToAddToIfDoesNotExist,
|
|
218
218
|
);
|
|
219
219
|
|
|
220
|
+
const filteredMessageIds: string[] = [];
|
|
221
|
+
|
|
220
222
|
for (let i = 0; i < messagesToAdd.length; i += 1) {
|
|
221
223
|
const isFromShadowBannedUser = messagesToAdd[i].shadowed;
|
|
222
|
-
if (isFromShadowBannedUser) {
|
|
224
|
+
if (isFromShadowBannedUser && addIfDoesNotExist) {
|
|
225
|
+
filteredMessageIds.push(messagesToAdd[i].id);
|
|
223
226
|
continue;
|
|
224
227
|
}
|
|
225
228
|
// If message is already formatted we can skip the tasks below
|
|
@@ -306,6 +309,7 @@ export class ChannelState {
|
|
|
306
309
|
|
|
307
310
|
return {
|
|
308
311
|
messageSet: this.messageSets[targetMessageSetIndex],
|
|
312
|
+
filteredMessageIds,
|
|
309
313
|
};
|
|
310
314
|
}
|
|
311
315
|
|
package/src/client.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type WebSocket from 'isomorphic-ws';
|
|
|
9
9
|
import { Channel } from './channel';
|
|
10
10
|
import { ClientState } from './client_state';
|
|
11
11
|
import { StableWSConnection } from './connection';
|
|
12
|
+
import { UploadManager } from './uploadManager';
|
|
12
13
|
import { CheckSignature, DevToken, JWTUserToken } from './signing';
|
|
13
14
|
import { TokenManager } from './token_manager';
|
|
14
15
|
import { WSConnectionFallback } from './connection_fallback';
|
|
@@ -296,6 +297,10 @@ export type MessageComposerSetupState = {
|
|
|
296
297
|
export class StreamChat {
|
|
297
298
|
private static _instance?: unknown | StreamChat; // type is undefined|StreamChat, unknown is due to TS limitations with statics
|
|
298
299
|
messageDeliveryReporter: MessageDeliveryReporter;
|
|
300
|
+
/**
|
|
301
|
+
* @internal
|
|
302
|
+
*/
|
|
303
|
+
uploadManager: UploadManager;
|
|
299
304
|
_user?: OwnUserResponse | UserResponse;
|
|
300
305
|
appSettingsPromise?: Promise<AppSettingsAPIResponse>;
|
|
301
306
|
activeChannels: {
|
|
@@ -401,6 +406,7 @@ export class StreamChat {
|
|
|
401
406
|
this.moderation = new Moderation(this);
|
|
402
407
|
|
|
403
408
|
this.notifications = options?.notifications ?? new NotificationManager();
|
|
409
|
+
this.uploadManager = new UploadManager(this);
|
|
404
410
|
|
|
405
411
|
// set the secret
|
|
406
412
|
if (secretOrOptions && isString(secretOrOptions)) {
|
|
@@ -1020,6 +1026,7 @@ export class StreamChat {
|
|
|
1020
1026
|
this.state = new ClientState({ client: this });
|
|
1021
1027
|
// reset thread manager
|
|
1022
1028
|
this.threads.resetState();
|
|
1029
|
+
this.uploadManager.reset();
|
|
1023
1030
|
|
|
1024
1031
|
// Since we wipe all user data already, we should reset token manager as well
|
|
1025
1032
|
closePromise
|
|
@@ -2040,12 +2047,17 @@ export class StreamChat {
|
|
|
2040
2047
|
c.push_preferences = channelState.push_preferences;
|
|
2041
2048
|
|
|
2042
2049
|
let updatedMessagesSet;
|
|
2050
|
+
let filteredMessageIds: string[] = [];
|
|
2043
2051
|
if (skipInitialization === undefined) {
|
|
2044
|
-
const { messageSet } =
|
|
2052
|
+
const { messageSet, filteredMessageIds: _filteredMessageIds } =
|
|
2053
|
+
c._initializeState(channelState, 'latest');
|
|
2054
|
+
filteredMessageIds = _filteredMessageIds;
|
|
2045
2055
|
updatedMessagesSet = messageSet;
|
|
2046
2056
|
} else if (!skipInitialization.includes(channelState.channel.id)) {
|
|
2047
2057
|
c.state.clearMessages();
|
|
2048
|
-
const { messageSet } =
|
|
2058
|
+
const { messageSet, filteredMessageIds: _filteredMessageIds } =
|
|
2059
|
+
c._initializeState(channelState, 'latest');
|
|
2060
|
+
filteredMessageIds = _filteredMessageIds;
|
|
2049
2061
|
updatedMessagesSet = messageSet;
|
|
2050
2062
|
}
|
|
2051
2063
|
|
|
@@ -2058,6 +2070,9 @@ export class StreamChat {
|
|
|
2058
2070
|
queryChannelsOptions?.message_limit ||
|
|
2059
2071
|
DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE,
|
|
2060
2072
|
returnedPage: channelState.messages,
|
|
2073
|
+
filteredReturnedPage: channelState.messages.filter(
|
|
2074
|
+
(m) => !filteredMessageIds.includes(m.id),
|
|
2075
|
+
),
|
|
2061
2076
|
logger: this.logger,
|
|
2062
2077
|
}),
|
|
2063
2078
|
};
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type {
|
|
|
33
33
|
export * from './thread_manager';
|
|
34
34
|
export * from './token_manager';
|
|
35
35
|
export * from './types';
|
|
36
|
+
export * from './uploadManager';
|
|
36
37
|
export * from './channel_manager';
|
|
37
38
|
export * from './offline-support';
|
|
38
39
|
export * from './LiveLocationManager';
|
|
@@ -237,12 +237,12 @@ export class AttachmentManager {
|
|
|
237
237
|
return attachments;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
return
|
|
240
|
+
return stateAttachments;
|
|
241
241
|
};
|
|
242
242
|
|
|
243
243
|
updateAttachment = (attachmentToUpdate: LocalAttachment) => {
|
|
244
244
|
const updatedAttachments = this.prepareAttachmentUpdate(attachmentToUpdate);
|
|
245
|
-
if (updatedAttachments) {
|
|
245
|
+
if (updatedAttachments && updatedAttachments !== this.attachments) {
|
|
246
246
|
this.state.partialNext({ attachments: updatedAttachments });
|
|
247
247
|
}
|
|
248
248
|
};
|
|
@@ -253,16 +253,17 @@ export class AttachmentManager {
|
|
|
253
253
|
let hasUpdates = false;
|
|
254
254
|
attachmentsToUpsert.forEach((attachment) => {
|
|
255
255
|
const updatedAttachments = this.prepareAttachmentUpdate(attachment);
|
|
256
|
-
if (updatedAttachments) {
|
|
257
|
-
attachments = updatedAttachments;
|
|
258
|
-
hasUpdates = true;
|
|
259
|
-
} else {
|
|
256
|
+
if (updatedAttachments === null) {
|
|
260
257
|
const localAttachment = ensureIsLocalAttachment(attachment);
|
|
261
258
|
if (localAttachment) {
|
|
262
259
|
attachments.push(localAttachment);
|
|
263
260
|
hasUpdates = true;
|
|
264
261
|
}
|
|
262
|
+
} else if (updatedAttachments !== this.attachments) {
|
|
263
|
+
attachments = updatedAttachments;
|
|
264
|
+
hasUpdates = true;
|
|
265
265
|
}
|
|
266
|
+
// else: id exists and merge was a no-op (`prepareAttachmentUpdate` returns current state)
|
|
266
267
|
});
|
|
267
268
|
if (hasUpdates) {
|
|
268
269
|
this.state.partialNext({ attachments });
|
|
@@ -270,11 +271,17 @@ export class AttachmentManager {
|
|
|
270
271
|
};
|
|
271
272
|
|
|
272
273
|
removeAttachments = (localAttachmentIds: string[]) => {
|
|
274
|
+
if (!localAttachmentIds.length) return;
|
|
275
|
+
|
|
273
276
|
this.state.partialNext({
|
|
274
277
|
attachments: this.attachments.filter(
|
|
275
278
|
(attachment) => !localAttachmentIds.includes(attachment.localMetadata?.id),
|
|
276
279
|
),
|
|
277
280
|
});
|
|
281
|
+
|
|
282
|
+
for (const id of localAttachmentIds) {
|
|
283
|
+
this.client.uploadManager.deleteUploadRecord(id);
|
|
284
|
+
}
|
|
278
285
|
};
|
|
279
286
|
|
|
280
287
|
getUploadConfigCheck = async (
|
|
@@ -464,13 +471,21 @@ export class AttachmentManager {
|
|
|
464
471
|
}
|
|
465
472
|
: undefined;
|
|
466
473
|
|
|
474
|
+
const axiosUploadConfig =
|
|
475
|
+
progressHandler || options?.abortSignal
|
|
476
|
+
? {
|
|
477
|
+
...(progressHandler ? { onUploadProgress: progressHandler } : {}),
|
|
478
|
+
...(options?.abortSignal ? { signal: options.abortSignal } : {}),
|
|
479
|
+
}
|
|
480
|
+
: undefined;
|
|
481
|
+
|
|
467
482
|
if (isFileReference(fileLike)) {
|
|
468
483
|
return this.channel[isImageFile(fileLike) ? 'sendImage' : 'sendFile'](
|
|
469
484
|
fileLike.uri,
|
|
470
485
|
fileLike.name,
|
|
471
486
|
fileLike.type,
|
|
472
487
|
undefined,
|
|
473
|
-
|
|
488
|
+
axiosUploadConfig,
|
|
474
489
|
);
|
|
475
490
|
}
|
|
476
491
|
|
|
@@ -485,13 +500,7 @@ export class AttachmentManager {
|
|
|
485
500
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
486
501
|
const { duration, ...result } = await this.channel[
|
|
487
502
|
isImageFile(fileLike) ? 'sendImage' : 'sendFile'
|
|
488
|
-
](
|
|
489
|
-
file,
|
|
490
|
-
undefined,
|
|
491
|
-
undefined,
|
|
492
|
-
undefined,
|
|
493
|
-
progressHandler ? { onUploadProgress: progressHandler } : undefined,
|
|
494
|
-
);
|
|
503
|
+
](file, undefined, undefined, undefined, axiosUploadConfig);
|
|
495
504
|
return result;
|
|
496
505
|
};
|
|
497
506
|
|
|
@@ -537,37 +546,9 @@ export class AttachmentManager {
|
|
|
537
546
|
return localAttachment;
|
|
538
547
|
}
|
|
539
548
|
|
|
540
|
-
const shouldTrackProgress = this.config.trackUploadProgress;
|
|
541
|
-
const uploadingAttachment: LocalUploadAttachment = {
|
|
542
|
-
...attachment,
|
|
543
|
-
localMetadata: {
|
|
544
|
-
...attachment.localMetadata,
|
|
545
|
-
uploadState: 'uploading',
|
|
546
|
-
...(shouldTrackProgress && { uploadProgress: 0 }),
|
|
547
|
-
},
|
|
548
|
-
};
|
|
549
|
-
this.upsertAttachments([uploadingAttachment]);
|
|
550
|
-
|
|
551
|
-
const uploadOptions = shouldTrackProgress
|
|
552
|
-
? {
|
|
553
|
-
onProgress: (percent: number | undefined) => {
|
|
554
|
-
this.updateAttachment({
|
|
555
|
-
...uploadingAttachment,
|
|
556
|
-
localMetadata: {
|
|
557
|
-
...uploadingAttachment.localMetadata,
|
|
558
|
-
uploadProgress: percent,
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
},
|
|
562
|
-
}
|
|
563
|
-
: undefined;
|
|
564
|
-
|
|
565
549
|
let response: MinimumUploadRequestResult;
|
|
566
550
|
try {
|
|
567
|
-
response = await this.
|
|
568
|
-
localAttachment.localMetadata.file,
|
|
569
|
-
uploadOptions,
|
|
570
|
-
);
|
|
551
|
+
response = await this.upload(attachment);
|
|
571
552
|
} catch (error) {
|
|
572
553
|
const reason = error instanceof Error ? error.message : 'unknown error';
|
|
573
554
|
const failedAttachment: LocalUploadAttachment = {
|
|
@@ -655,35 +636,10 @@ export class AttachmentManager {
|
|
|
655
636
|
return preUpload.state.attachment;
|
|
656
637
|
}
|
|
657
638
|
|
|
658
|
-
const shouldTrackProgress = this.config.trackUploadProgress;
|
|
659
|
-
attachment = {
|
|
660
|
-
...attachment,
|
|
661
|
-
localMetadata: {
|
|
662
|
-
...attachment.localMetadata,
|
|
663
|
-
uploadState: 'uploading',
|
|
664
|
-
...(shouldTrackProgress && { uploadProgress: 0 }),
|
|
665
|
-
},
|
|
666
|
-
};
|
|
667
|
-
this.upsertAttachments([attachment]);
|
|
668
|
-
|
|
669
|
-
const uploadOptions = shouldTrackProgress
|
|
670
|
-
? {
|
|
671
|
-
onProgress: (percent: number | undefined) => {
|
|
672
|
-
this.updateAttachment({
|
|
673
|
-
...attachment,
|
|
674
|
-
localMetadata: {
|
|
675
|
-
...attachment.localMetadata,
|
|
676
|
-
uploadProgress: percent,
|
|
677
|
-
},
|
|
678
|
-
});
|
|
679
|
-
},
|
|
680
|
-
}
|
|
681
|
-
: undefined;
|
|
682
|
-
|
|
683
639
|
let response: MinimumUploadRequestResult | undefined;
|
|
684
640
|
let error: Error | undefined;
|
|
685
641
|
try {
|
|
686
|
-
response = await this.
|
|
642
|
+
response = await this.upload(attachment);
|
|
687
643
|
} catch (err) {
|
|
688
644
|
error = err instanceof Error ? err : undefined;
|
|
689
645
|
}
|
|
@@ -725,4 +681,44 @@ export class AttachmentManager {
|
|
|
725
681
|
iterableFiles.slice(0, this.availableUploadSlots).map(this.uploadFile),
|
|
726
682
|
);
|
|
727
683
|
};
|
|
684
|
+
|
|
685
|
+
private upload(attachment: LocalUploadAttachment) {
|
|
686
|
+
const localId = attachment.localMetadata.id;
|
|
687
|
+
|
|
688
|
+
this.upsertAttachments([
|
|
689
|
+
{
|
|
690
|
+
...attachment,
|
|
691
|
+
localMetadata: {
|
|
692
|
+
...attachment.localMetadata,
|
|
693
|
+
uploadState: 'uploading',
|
|
694
|
+
uploadProgress: this.config.trackUploadProgress ? 0 : undefined,
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
]);
|
|
698
|
+
|
|
699
|
+
const unsubscribe = this.client.uploadManager.state.subscribeWithSelector(
|
|
700
|
+
(s) => ({ upload: s.uploads[localId] }),
|
|
701
|
+
({ upload: nextUpload }) => {
|
|
702
|
+
if (!nextUpload) return;
|
|
703
|
+
this.updateAttachment({
|
|
704
|
+
...attachment,
|
|
705
|
+
localMetadata: {
|
|
706
|
+
...attachment.localMetadata,
|
|
707
|
+
uploadState: 'uploading',
|
|
708
|
+
uploadProgress: nextUpload.uploadProgress,
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
return this.client.uploadManager
|
|
715
|
+
.upload({
|
|
716
|
+
id: localId,
|
|
717
|
+
channelCid: this.channel.cid,
|
|
718
|
+
file: attachment.localMetadata.file,
|
|
719
|
+
})
|
|
720
|
+
.finally(() => {
|
|
721
|
+
unsubscribe();
|
|
722
|
+
});
|
|
723
|
+
}
|
|
728
724
|
}
|
|
@@ -6,9 +6,14 @@ export type MinimumUploadRequestResult = { file: string; thumb_url?: string } &
|
|
|
6
6
|
Record<string, unknown>
|
|
7
7
|
>;
|
|
8
8
|
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Optional second argument to `UploadRequestFn`.
|
|
11
|
+
* - Call `onProgress` to report 0–100 or `undefined` when indeterminate.
|
|
12
|
+
* - Forward `abortSignal` to your upload implementation to cancel the upload if the user deletes the attachment while it's uploading.
|
|
13
|
+
*/
|
|
10
14
|
export type UploadRequestOptions = {
|
|
11
15
|
onProgress?: (percent: number | undefined) => void;
|
|
16
|
+
abortSignal?: AbortSignal;
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
export type UploadRequestFn = (
|