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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { StreamChat } from './client';
|
|
2
|
+
import type { UploadRequestOptions } from './messageComposer/configuration/types';
|
|
3
|
+
import { StateStore } from './store';
|
|
4
|
+
import type { AttachmentManager } from '.';
|
|
5
|
+
|
|
6
|
+
export type UploadRecord = {
|
|
7
|
+
id: string;
|
|
8
|
+
uploadProgress?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type UploadManagerState = {
|
|
12
|
+
uploads: Record<string, UploadRecord>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const initState = (): UploadManagerState => ({ uploads: {} });
|
|
16
|
+
|
|
17
|
+
const upsertById = (
|
|
18
|
+
uploads: Record<string, UploadRecord>,
|
|
19
|
+
record: UploadRecord,
|
|
20
|
+
): Record<string, UploadRecord> => ({
|
|
21
|
+
...uploads,
|
|
22
|
+
[record.id]: { ...uploads[record.id], ...record },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const updateById = (
|
|
26
|
+
uploads: Record<string, UploadRecord>,
|
|
27
|
+
record: UploadRecord,
|
|
28
|
+
): Record<string, UploadRecord> | null => {
|
|
29
|
+
if (!(record.id in uploads)) return null;
|
|
30
|
+
const current = uploads[record.id];
|
|
31
|
+
return { ...uploads, [record.id]: { ...current, ...record } };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type UploadPromise = ReturnType<typeof AttachmentManager.prototype.doUploadRequest>;
|
|
35
|
+
|
|
36
|
+
type InFlightUpload = { promise: UploadPromise; abortController: AbortController };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export class UploadManager {
|
|
42
|
+
readonly state: StateStore<UploadManagerState>;
|
|
43
|
+
|
|
44
|
+
private inFlightUploads = new Map<string, InFlightUpload>();
|
|
45
|
+
|
|
46
|
+
constructor(private readonly client: StreamChat) {
|
|
47
|
+
this.state = new StateStore<UploadManagerState>(initState());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private resolveAttachmentManager(channelCid: string) {
|
|
51
|
+
const colon = channelCid.indexOf(':');
|
|
52
|
+
if (colon <= 0 || colon === channelCid.length - 1) {
|
|
53
|
+
throw new Error(`Invalid channelCid: ${channelCid}`);
|
|
54
|
+
}
|
|
55
|
+
const channelType = channelCid.slice(0, colon);
|
|
56
|
+
const channelId = channelCid.slice(colon + 1);
|
|
57
|
+
return this.client.channel(channelType, channelId).messageComposer.attachmentManager;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get uploads() {
|
|
61
|
+
return this.state.getLatestValue().uploads;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getUpload = (id: string) => this.uploads[id];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clears all upload records.
|
|
68
|
+
* Invoked when the user disconnects so a later session does not inherit stale upload state.
|
|
69
|
+
* Aborts every in-flight upload request via its `UploadRequestOptions.abortSignal`.
|
|
70
|
+
*/
|
|
71
|
+
reset = () => {
|
|
72
|
+
for (const { abortController } of this.inFlightUploads.values()) {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
}
|
|
75
|
+
this.inFlightUploads.clear();
|
|
76
|
+
this.state.next(initState());
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Removes the upload record for `id` if present.
|
|
81
|
+
* If an upload is still in progress, aborts its `UploadRequestOptions.abortSignal`.
|
|
82
|
+
*/
|
|
83
|
+
deleteUploadRecord = (id: string) => {
|
|
84
|
+
const flight = this.inFlightUploads.get(id);
|
|
85
|
+
if (flight) {
|
|
86
|
+
this.inFlightUploads.delete(id);
|
|
87
|
+
flight.abortController.abort();
|
|
88
|
+
}
|
|
89
|
+
this.state.next((current) => {
|
|
90
|
+
if (!(id in current.uploads)) return current;
|
|
91
|
+
const uploads = { ...current.uploads };
|
|
92
|
+
delete uploads[id];
|
|
93
|
+
return { ...current, uploads };
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Starts an upload for `id`, or returns the existing in-flight promise if one is already running.
|
|
99
|
+
* Uses {@link StreamChat.channel}(`channelCid`) → `messageComposer.attachmentManager.doUploadRequest`.
|
|
100
|
+
* Resolves with that result; rejects if the upload rejects (the record is removed from state either way).
|
|
101
|
+
*/
|
|
102
|
+
upload = ({
|
|
103
|
+
id,
|
|
104
|
+
channelCid,
|
|
105
|
+
file,
|
|
106
|
+
}: {
|
|
107
|
+
id: string;
|
|
108
|
+
channelCid: string;
|
|
109
|
+
file: Parameters<typeof AttachmentManager.prototype.doUploadRequest>[0];
|
|
110
|
+
}): ReturnType<typeof AttachmentManager.prototype.doUploadRequest> => {
|
|
111
|
+
const existing = this.inFlightUploads.get(id);
|
|
112
|
+
if (existing) return existing.promise;
|
|
113
|
+
|
|
114
|
+
let resolvePromise!: (value: Awaited<UploadPromise>) => void;
|
|
115
|
+
let rejectPromise!: (reason?: unknown) => void;
|
|
116
|
+
const promise = new Promise<Awaited<UploadPromise>>((resolve, reject) => {
|
|
117
|
+
resolvePromise = resolve;
|
|
118
|
+
rejectPromise = reject;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const abortController = new AbortController();
|
|
122
|
+
this.inFlightUploads.set(id, { promise, abortController });
|
|
123
|
+
|
|
124
|
+
void (async () => {
|
|
125
|
+
const attachmentManager = this.resolveAttachmentManager(channelCid);
|
|
126
|
+
const trackProgress = attachmentManager.config.trackUploadProgress;
|
|
127
|
+
try {
|
|
128
|
+
this.upsertUpload({
|
|
129
|
+
id,
|
|
130
|
+
uploadProgress: trackProgress ? 0 : undefined,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const onProgress = trackProgress
|
|
134
|
+
? (progress?: number) => {
|
|
135
|
+
this.updateUpload({
|
|
136
|
+
id,
|
|
137
|
+
uploadProgress: progress,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
: undefined;
|
|
141
|
+
|
|
142
|
+
const uploadRequestOptions: UploadRequestOptions = {
|
|
143
|
+
abortSignal: abortController.signal,
|
|
144
|
+
...(onProgress ? { onProgress } : {}),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const response = await attachmentManager.doUploadRequest(
|
|
148
|
+
file,
|
|
149
|
+
uploadRequestOptions,
|
|
150
|
+
);
|
|
151
|
+
resolvePromise(response);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
rejectPromise(error);
|
|
154
|
+
} finally {
|
|
155
|
+
this.inFlightUploads.delete(id);
|
|
156
|
+
this.deleteUploadRecord(id);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
|
|
160
|
+
return promise;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
private upsertUpload = (record: UploadRecord) => {
|
|
164
|
+
this.state.partialNext({
|
|
165
|
+
uploads: upsertById(this.uploads, record),
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
private updateUpload = (record: UploadRecord) => {
|
|
170
|
+
this.state.next((current) => {
|
|
171
|
+
const nextUploads = updateById(current.uploads, record);
|
|
172
|
+
if (!nextUploads) return current;
|
|
173
|
+
return { ...current, uploads: nextUploads };
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -811,6 +811,7 @@ type MessagePaginationUpdatedParams = {
|
|
|
811
811
|
parentSet: MessageSet;
|
|
812
812
|
requestedPageSize: number;
|
|
813
813
|
returnedPage: MessageResponse[];
|
|
814
|
+
filteredReturnedPage: MessageResponse[];
|
|
814
815
|
logger?: Logger;
|
|
815
816
|
messagePaginationOptions?: MessagePaginationOptions;
|
|
816
817
|
};
|
|
@@ -849,6 +850,7 @@ const messagePaginationCreatedAtAround = ({
|
|
|
849
850
|
parentSet,
|
|
850
851
|
requestedPageSize,
|
|
851
852
|
returnedPage,
|
|
853
|
+
filteredReturnedPage,
|
|
852
854
|
messagePaginationOptions,
|
|
853
855
|
}: MessagePaginationUpdatedParams) => {
|
|
854
856
|
const newPagination = { ...parentSet.pagination };
|
|
@@ -892,9 +894,14 @@ const messagePaginationCreatedAtAround = ({
|
|
|
892
894
|
hasNext = hasPrev = false;
|
|
893
895
|
updateHasPrev = updateHasNext = true;
|
|
894
896
|
} else {
|
|
897
|
+
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
898
|
+
filteredReturnedPage[0],
|
|
899
|
+
filteredReturnedPage.slice(-1)[0],
|
|
900
|
+
];
|
|
895
901
|
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
896
|
-
|
|
897
|
-
|
|
902
|
+
firstFilteredPageMsg?.id && firstFilteredPageMsg.id === parentSet.messages[0]?.id,
|
|
903
|
+
lastFilteredPageMsg?.id &&
|
|
904
|
+
lastFilteredPageMsg.id === parentSet.messages.slice(-1)[0]?.id,
|
|
898
905
|
];
|
|
899
906
|
updateHasPrev = firstPageMsgIsFirstInSet;
|
|
900
907
|
updateHasNext = lastPageMsgIsLastInSet;
|
|
@@ -920,6 +927,7 @@ const messagePaginationIdAround = ({
|
|
|
920
927
|
parentSet,
|
|
921
928
|
requestedPageSize,
|
|
922
929
|
returnedPage,
|
|
930
|
+
filteredReturnedPage,
|
|
923
931
|
messagePaginationOptions,
|
|
924
932
|
}: MessagePaginationUpdatedParams) => {
|
|
925
933
|
const newPagination = { ...parentSet.pagination };
|
|
@@ -928,10 +936,13 @@ const messagePaginationIdAround = ({
|
|
|
928
936
|
let hasPrev;
|
|
929
937
|
let hasNext;
|
|
930
938
|
|
|
931
|
-
const [
|
|
939
|
+
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
940
|
+
filteredReturnedPage[0],
|
|
941
|
+
filteredReturnedPage.slice(-1)[0],
|
|
942
|
+
];
|
|
932
943
|
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
933
|
-
|
|
934
|
-
|
|
944
|
+
firstFilteredPageMsg?.id === parentSet.messages[0]?.id,
|
|
945
|
+
lastFilteredPageMsg?.id === parentSet.messages.slice(-1)[0]?.id,
|
|
935
946
|
];
|
|
936
947
|
let updateHasPrev = firstPageMsgIsFirstInSet;
|
|
937
948
|
let updateHasNext = lastPageMsgIsLastInSet;
|
|
@@ -974,6 +985,7 @@ const messagePaginationLinear = ({
|
|
|
974
985
|
parentSet,
|
|
975
986
|
requestedPageSize,
|
|
976
987
|
returnedPage,
|
|
988
|
+
filteredReturnedPage,
|
|
977
989
|
messagePaginationOptions,
|
|
978
990
|
}: MessagePaginationUpdatedParams) => {
|
|
979
991
|
const newPagination = { ...parentSet.pagination };
|
|
@@ -981,10 +993,14 @@ const messagePaginationLinear = ({
|
|
|
981
993
|
let hasPrev;
|
|
982
994
|
let hasNext;
|
|
983
995
|
|
|
984
|
-
const [
|
|
996
|
+
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
997
|
+
filteredReturnedPage[0],
|
|
998
|
+
filteredReturnedPage.slice(-1)[0],
|
|
999
|
+
];
|
|
985
1000
|
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
986
|
-
|
|
987
|
-
|
|
1001
|
+
firstFilteredPageMsg?.id && firstFilteredPageMsg.id === parentSet.messages[0]?.id,
|
|
1002
|
+
lastFilteredPageMsg?.id &&
|
|
1003
|
+
lastFilteredPageMsg.id === parentSet.messages.slice(-1)[0]?.id,
|
|
988
1004
|
];
|
|
989
1005
|
|
|
990
1006
|
const queriedNextMessages =
|
|
@@ -1028,9 +1044,9 @@ const messagePaginationLinear = ({
|
|
|
1028
1044
|
};
|
|
1029
1045
|
|
|
1030
1046
|
export const messageSetPagination = (params: MessagePaginationUpdatedParams) => {
|
|
1031
|
-
const messagesFilteredLocally = params.returnedPage.filter(({ shadowed }) => shadowed);
|
|
1032
1047
|
if (
|
|
1033
|
-
params.parentSet.messages.length +
|
|
1048
|
+
params.parentSet.messages.length +
|
|
1049
|
+
(params.returnedPage.length - params.filteredReturnedPage.length) <
|
|
1034
1050
|
params.returnedPage.length
|
|
1035
1051
|
) {
|
|
1036
1052
|
params.logger?.(
|