stream-chat 8.54.1 → 8.56.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/browser.es.js +4048 -2245
- package/dist/browser.es.js.map +1 -1
- package/dist/browser.full-bundle.min.js +1 -1
- package/dist/browser.full-bundle.min.js.map +1 -1
- package/dist/browser.js +4057 -2244
- package/dist/browser.js.map +1 -1
- package/dist/index.es.js +4048 -2245
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4057 -2244
- package/dist/index.js.map +1 -1
- package/dist/types/channel.d.ts.map +1 -1
- package/dist/types/channel_manager.d.ts +111 -0
- package/dist/types/channel_manager.d.ts.map +1 -0
- package/dist/types/client.d.ts +65 -2
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/insights.d.ts +2 -2
- package/dist/types/search_controller.d.ts +174 -0
- package/dist/types/search_controller.d.ts.map +1 -0
- package/dist/types/store.d.ts +3 -1
- package/dist/types/store.d.ts.map +1 -1
- package/dist/types/types.d.ts +11 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils.d.ts +118 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/channel.ts +6 -7
- package/src/channel_manager.ts +581 -0
- package/src/client.ts +27 -3
- package/src/index.ts +2 -0
- package/src/search_controller.ts +489 -0
- package/src/store.ts +4 -3
- package/src/types.ts +12 -0
- package/src/utils.ts +356 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import type { StreamChat } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
DefaultGenerics,
|
|
4
|
+
ExtendableGenerics,
|
|
5
|
+
Event,
|
|
6
|
+
ChannelOptions,
|
|
7
|
+
ChannelStateOptions,
|
|
8
|
+
ChannelFilters,
|
|
9
|
+
ChannelSort,
|
|
10
|
+
} from './types';
|
|
11
|
+
import { StateStore, ValueOrPatch, isPatch } from './store';
|
|
12
|
+
import { Channel } from './channel';
|
|
13
|
+
import {
|
|
14
|
+
extractSortValue,
|
|
15
|
+
findLastPinnedChannelIndex,
|
|
16
|
+
getAndWatchChannel,
|
|
17
|
+
isChannelArchived,
|
|
18
|
+
isChannelPinned,
|
|
19
|
+
promoteChannel,
|
|
20
|
+
shouldConsiderArchivedChannels,
|
|
21
|
+
shouldConsiderPinnedChannels,
|
|
22
|
+
} from './utils';
|
|
23
|
+
|
|
24
|
+
export type ChannelManagerPagination<SCG extends ExtendableGenerics = DefaultGenerics> = {
|
|
25
|
+
filters: ChannelFilters<SCG>;
|
|
26
|
+
hasNext: boolean;
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
isLoadingNext: boolean;
|
|
29
|
+
options: ChannelOptions;
|
|
30
|
+
sort: ChannelSort<SCG>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ChannelManagerState<SCG extends ExtendableGenerics = DefaultGenerics> = {
|
|
34
|
+
channels: Channel<SCG>[];
|
|
35
|
+
/**
|
|
36
|
+
* This value will become true the first time queryChannels is successfully executed and
|
|
37
|
+
* will remain false otherwise. It's used as a control property regarding whether the list
|
|
38
|
+
* has been initialized yet (i.e a query has already been done at least once) or not. We do
|
|
39
|
+
* this to prevent state.channels from being forced to be nullable.
|
|
40
|
+
*/
|
|
41
|
+
initialized: boolean;
|
|
42
|
+
pagination: ChannelManagerPagination<SCG>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type ChannelSetterParameterType<SCG extends ExtendableGenerics = DefaultGenerics> = ValueOrPatch<
|
|
46
|
+
ChannelManagerState<SCG>['channels']
|
|
47
|
+
>;
|
|
48
|
+
export type ChannelSetterType<SCG extends ExtendableGenerics = DefaultGenerics> = (
|
|
49
|
+
arg: ChannelSetterParameterType<SCG>,
|
|
50
|
+
) => void;
|
|
51
|
+
|
|
52
|
+
export type GenericEventHandlerType<T extends unknown[]> = (
|
|
53
|
+
...args: T
|
|
54
|
+
) => void | (() => void) | ((...args: T) => Promise<void>) | Promise<void>;
|
|
55
|
+
export type EventHandlerType<SCG extends ExtendableGenerics = DefaultGenerics> = GenericEventHandlerType<[Event<SCG>]>;
|
|
56
|
+
export type EventHandlerOverrideType<SCG extends ExtendableGenerics = DefaultGenerics> = GenericEventHandlerType<
|
|
57
|
+
[ChannelSetterType<SCG>, Event<SCG>]
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
export type ChannelManagerEventTypes =
|
|
61
|
+
| 'notification.added_to_channel'
|
|
62
|
+
| 'notification.message_new'
|
|
63
|
+
| 'notification.removed_from_channel'
|
|
64
|
+
| 'message.new'
|
|
65
|
+
| 'member.updated'
|
|
66
|
+
| 'channel.deleted'
|
|
67
|
+
| 'channel.hidden'
|
|
68
|
+
| 'channel.truncated'
|
|
69
|
+
| 'channel.visible';
|
|
70
|
+
|
|
71
|
+
export type ChannelManagerEventHandlerNames =
|
|
72
|
+
| 'channelDeletedHandler'
|
|
73
|
+
| 'channelHiddenHandler'
|
|
74
|
+
| 'channelTruncatedHandler'
|
|
75
|
+
| 'channelVisibleHandler'
|
|
76
|
+
| 'newMessageHandler'
|
|
77
|
+
| 'memberUpdatedHandler'
|
|
78
|
+
| 'notificationAddedToChannelHandler'
|
|
79
|
+
| 'notificationNewMessageHandler'
|
|
80
|
+
| 'notificationRemovedFromChannelHandler';
|
|
81
|
+
|
|
82
|
+
export type ChannelManagerEventHandlerOverrides<SCG extends ExtendableGenerics = DefaultGenerics> = Partial<
|
|
83
|
+
Record<ChannelManagerEventHandlerNames, EventHandlerOverrideType<SCG>>
|
|
84
|
+
>;
|
|
85
|
+
|
|
86
|
+
export const channelManagerEventToHandlerMapping: {
|
|
87
|
+
[key in ChannelManagerEventTypes]: ChannelManagerEventHandlerNames;
|
|
88
|
+
} = {
|
|
89
|
+
'channel.deleted': 'channelDeletedHandler',
|
|
90
|
+
'channel.hidden': 'channelHiddenHandler',
|
|
91
|
+
'channel.truncated': 'channelTruncatedHandler',
|
|
92
|
+
'channel.visible': 'channelVisibleHandler',
|
|
93
|
+
'message.new': 'newMessageHandler',
|
|
94
|
+
'member.updated': 'memberUpdatedHandler',
|
|
95
|
+
'notification.added_to_channel': 'notificationAddedToChannelHandler',
|
|
96
|
+
'notification.message_new': 'notificationNewMessageHandler',
|
|
97
|
+
'notification.removed_from_channel': 'notificationRemovedFromChannelHandler',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type ChannelManagerOptions = {
|
|
101
|
+
/**
|
|
102
|
+
* Aborts a channels query that is already in progress and runs the new one.
|
|
103
|
+
*/
|
|
104
|
+
abortInFlightQuery?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Allows channel promotion to be applied where applicable for channels that are
|
|
107
|
+
* currently not part of the channel list within the state. A good example of
|
|
108
|
+
* this would be a channel that is being watched and it receives a new message,
|
|
109
|
+
* but is not part of the list initially.
|
|
110
|
+
*/
|
|
111
|
+
allowNotLoadedChannelPromotionForEvent?: {
|
|
112
|
+
'channel.visible': boolean;
|
|
113
|
+
'message.new': boolean;
|
|
114
|
+
'notification.added_to_channel': boolean;
|
|
115
|
+
'notification.message_new': boolean;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Allows us to lock the order of channels within the list. Any event that would
|
|
119
|
+
* change the order of channels within the list will do nothing.
|
|
120
|
+
*/
|
|
121
|
+
lockChannelOrder?: boolean;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const DEFAULT_CHANNEL_MANAGER_OPTIONS = {
|
|
125
|
+
abortInFlightQuery: false,
|
|
126
|
+
allowNotLoadedChannelPromotionForEvent: {
|
|
127
|
+
'channel.visible': true,
|
|
128
|
+
'message.new': true,
|
|
129
|
+
'notification.added_to_channel': true,
|
|
130
|
+
'notification.message_new': true,
|
|
131
|
+
},
|
|
132
|
+
lockChannelOrder: false,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS = {
|
|
136
|
+
limit: 10,
|
|
137
|
+
offset: 0,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A class that manages a list of channels and changes it based on configuration and WS events. The
|
|
142
|
+
* list of channels is reactive as well as the pagination and it can be subscribed to for state updates.
|
|
143
|
+
*
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
export class ChannelManager<SCG extends ExtendableGenerics = DefaultGenerics> {
|
|
147
|
+
public readonly state: StateStore<ChannelManagerState<SCG>>;
|
|
148
|
+
private client: StreamChat<SCG>;
|
|
149
|
+
private unsubscribeFunctions: Set<() => void> = new Set();
|
|
150
|
+
private eventHandlers: Map<string, EventHandlerType<SCG>> = new Map();
|
|
151
|
+
private eventHandlerOverrides: Map<string, EventHandlerOverrideType<SCG>> = new Map();
|
|
152
|
+
private options: ChannelManagerOptions = {};
|
|
153
|
+
private stateOptions: ChannelStateOptions = {};
|
|
154
|
+
|
|
155
|
+
constructor({
|
|
156
|
+
client,
|
|
157
|
+
eventHandlerOverrides = {},
|
|
158
|
+
options = {},
|
|
159
|
+
}: {
|
|
160
|
+
client: StreamChat<SCG>;
|
|
161
|
+
eventHandlerOverrides?: ChannelManagerEventHandlerOverrides<SCG>;
|
|
162
|
+
options?: ChannelManagerOptions;
|
|
163
|
+
}) {
|
|
164
|
+
this.client = client;
|
|
165
|
+
this.state = new StateStore<ChannelManagerState<SCG>>({
|
|
166
|
+
channels: [],
|
|
167
|
+
pagination: {
|
|
168
|
+
isLoading: false,
|
|
169
|
+
isLoadingNext: false,
|
|
170
|
+
hasNext: false,
|
|
171
|
+
filters: {},
|
|
172
|
+
sort: {},
|
|
173
|
+
options: DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS,
|
|
174
|
+
},
|
|
175
|
+
initialized: false,
|
|
176
|
+
});
|
|
177
|
+
this.setEventHandlerOverrides(eventHandlerOverrides);
|
|
178
|
+
this.setOptions(options);
|
|
179
|
+
this.eventHandlers = new Map(
|
|
180
|
+
Object.entries<EventHandlerType<SCG>>({
|
|
181
|
+
channelDeletedHandler: this.channelDeletedHandler,
|
|
182
|
+
channelHiddenHandler: this.channelHiddenHandler,
|
|
183
|
+
channelVisibleHandler: this.channelVisibleHandler,
|
|
184
|
+
memberUpdatedHandler: this.memberUpdatedHandler,
|
|
185
|
+
newMessageHandler: this.newMessageHandler,
|
|
186
|
+
notificationAddedToChannelHandler: this.notificationAddedToChannelHandler,
|
|
187
|
+
notificationNewMessageHandler: this.notificationNewMessageHandler,
|
|
188
|
+
notificationRemovedFromChannelHandler: this.notificationRemovedFromChannelHandler,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public setChannels = (valueOrFactory: ChannelSetterParameterType<SCG>) => {
|
|
194
|
+
this.state.next((current) => {
|
|
195
|
+
const { channels: currentChannels } = current;
|
|
196
|
+
const newChannels = isPatch(valueOrFactory) ? valueOrFactory(currentChannels) : valueOrFactory;
|
|
197
|
+
|
|
198
|
+
// If the references between the two values are the same, just return the
|
|
199
|
+
// current state; otherwise trigger a state change.
|
|
200
|
+
if (currentChannels === newChannels) {
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
return { ...current, channels: newChannels };
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
public setEventHandlerOverrides = (eventHandlerOverrides: ChannelManagerEventHandlerOverrides<SCG> = {}) => {
|
|
208
|
+
const truthyEventHandlerOverrides = Object.entries(eventHandlerOverrides).reduce<
|
|
209
|
+
Partial<ChannelManagerEventHandlerOverrides<SCG>>
|
|
210
|
+
>((acc, [key, value]) => {
|
|
211
|
+
if (value) {
|
|
212
|
+
acc[key as keyof ChannelManagerEventHandlerOverrides<SCG>] = value;
|
|
213
|
+
}
|
|
214
|
+
return acc;
|
|
215
|
+
}, {});
|
|
216
|
+
this.eventHandlerOverrides = new Map(Object.entries<EventHandlerOverrideType<SCG>>(truthyEventHandlerOverrides));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
public setOptions = (options: ChannelManagerOptions = {}) => {
|
|
220
|
+
this.options = { ...DEFAULT_CHANNEL_MANAGER_OPTIONS, ...options };
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
public queryChannels = async (
|
|
224
|
+
filters: ChannelFilters<SCG>,
|
|
225
|
+
sort: ChannelSort<SCG> = [],
|
|
226
|
+
options: ChannelOptions = {},
|
|
227
|
+
stateOptions: ChannelStateOptions = {},
|
|
228
|
+
) => {
|
|
229
|
+
const { offset, limit } = { ...DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS, ...options };
|
|
230
|
+
const {
|
|
231
|
+
pagination: { isLoading },
|
|
232
|
+
} = this.state.getLatestValue();
|
|
233
|
+
|
|
234
|
+
if (isLoading && !this.options.abortInFlightQuery) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
this.stateOptions = stateOptions;
|
|
240
|
+
this.state.next((currentState) => ({
|
|
241
|
+
...currentState,
|
|
242
|
+
pagination: {
|
|
243
|
+
...currentState.pagination,
|
|
244
|
+
isLoading: true,
|
|
245
|
+
isLoadingNext: false,
|
|
246
|
+
filters,
|
|
247
|
+
sort,
|
|
248
|
+
options,
|
|
249
|
+
},
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
const channels = await this.client.queryChannels(filters, sort, options, stateOptions);
|
|
253
|
+
const newOffset = offset + (channels?.length ?? 0);
|
|
254
|
+
const newOptions = { ...options, offset: newOffset };
|
|
255
|
+
const { pagination } = this.state.getLatestValue();
|
|
256
|
+
|
|
257
|
+
this.state.partialNext({
|
|
258
|
+
channels,
|
|
259
|
+
pagination: {
|
|
260
|
+
...pagination,
|
|
261
|
+
hasNext: (channels?.length ?? 0) >= limit,
|
|
262
|
+
isLoading: false,
|
|
263
|
+
options: newOptions,
|
|
264
|
+
},
|
|
265
|
+
initialized: true,
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
this.client.logger('error', (error as Error).message);
|
|
269
|
+
this.state.next((currentState) => ({
|
|
270
|
+
...currentState,
|
|
271
|
+
pagination: { ...currentState.pagination, isLoading: false },
|
|
272
|
+
}));
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
public loadNext = async () => {
|
|
278
|
+
const { pagination, channels, initialized } = this.state.getLatestValue();
|
|
279
|
+
const { filters, sort, options, isLoadingNext, hasNext } = pagination;
|
|
280
|
+
|
|
281
|
+
if (!initialized || isLoadingNext || !hasNext) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const { offset, limit } = { ...DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS, ...options };
|
|
287
|
+
this.state.partialNext({
|
|
288
|
+
pagination: { ...pagination, isLoading: false, isLoadingNext: true },
|
|
289
|
+
});
|
|
290
|
+
const nextChannels = await this.client.queryChannels(filters, sort, options, this.stateOptions);
|
|
291
|
+
const newOffset = offset + (nextChannels?.length ?? 0);
|
|
292
|
+
const newOptions = { ...options, offset: newOffset };
|
|
293
|
+
|
|
294
|
+
this.state.partialNext({
|
|
295
|
+
channels: [...(channels || []), ...nextChannels],
|
|
296
|
+
pagination: {
|
|
297
|
+
...pagination,
|
|
298
|
+
hasNext: (nextChannels?.length ?? 0) >= limit,
|
|
299
|
+
isLoading: false,
|
|
300
|
+
isLoadingNext: false,
|
|
301
|
+
options: newOptions,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
this.client.logger('error', (error as Error).message);
|
|
306
|
+
this.state.next((currentState) => ({
|
|
307
|
+
...currentState,
|
|
308
|
+
pagination: { ...currentState.pagination, isLoadingNext: false },
|
|
309
|
+
}));
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
private notificationAddedToChannelHandler = async (event: Event<SCG>) => {
|
|
315
|
+
const { id, type, members } = event?.channel ?? {};
|
|
316
|
+
if (!type || !this.options.allowNotLoadedChannelPromotionForEvent?.['notification.added_to_channel']) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const channel = await getAndWatchChannel({
|
|
320
|
+
client: this.client,
|
|
321
|
+
id,
|
|
322
|
+
members: members?.reduce<string[]>((acc, { user, user_id }) => {
|
|
323
|
+
const userId = user_id || user?.id;
|
|
324
|
+
if (userId) {
|
|
325
|
+
acc.push(userId);
|
|
326
|
+
}
|
|
327
|
+
return acc;
|
|
328
|
+
}, []),
|
|
329
|
+
type,
|
|
330
|
+
});
|
|
331
|
+
const { pagination, channels } = this.state.getLatestValue();
|
|
332
|
+
if (!channels) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const { sort } = pagination ?? {};
|
|
337
|
+
|
|
338
|
+
this.setChannels(
|
|
339
|
+
promoteChannel({
|
|
340
|
+
channels,
|
|
341
|
+
channelToMove: channel,
|
|
342
|
+
sort,
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
private channelDeletedHandler = (event: Event<SCG>) => {
|
|
348
|
+
const { channels } = this.state.getLatestValue();
|
|
349
|
+
if (!channels) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const newChannels = [...channels];
|
|
354
|
+
const channelIndex = newChannels.findIndex((channel) => channel.cid === (event.cid || event.channel?.cid));
|
|
355
|
+
|
|
356
|
+
if (channelIndex < 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
newChannels.splice(channelIndex, 1);
|
|
361
|
+
this.setChannels(newChannels);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
private channelHiddenHandler = this.channelDeletedHandler;
|
|
365
|
+
|
|
366
|
+
private newMessageHandler = (event: Event<SCG>) => {
|
|
367
|
+
const { pagination, channels } = this.state.getLatestValue();
|
|
368
|
+
if (!channels) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const { filters, sort } = pagination ?? {};
|
|
372
|
+
|
|
373
|
+
const channelType = event.channel_type;
|
|
374
|
+
const channelId = event.channel_id;
|
|
375
|
+
|
|
376
|
+
if (!channelType || !channelId) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const targetChannel = this.client.channel(channelType, channelId);
|
|
381
|
+
const targetChannelIndex = channels.indexOf(targetChannel);
|
|
382
|
+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
|
|
383
|
+
|
|
384
|
+
const isTargetChannelPinned = isChannelPinned(targetChannel);
|
|
385
|
+
const isTargetChannelArchived = isChannelArchived(targetChannel);
|
|
386
|
+
|
|
387
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
388
|
+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
// filter is defined, target channel is archived and filter option is set to false
|
|
392
|
+
(considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
|
|
393
|
+
// filter is defined, target channel isn't archived and filter option is set to true
|
|
394
|
+
(considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
|
|
395
|
+
// sort option is defined, target channel is pinned
|
|
396
|
+
(considerPinnedChannels && isTargetChannelPinned) ||
|
|
397
|
+
// list order is locked
|
|
398
|
+
this.options.lockChannelOrder ||
|
|
399
|
+
// target channel is not within the loaded list and loading from cache is disallowed
|
|
400
|
+
(!targetChannelExistsWithinList && !this.options.allowNotLoadedChannelPromotionForEvent?.['message.new'])
|
|
401
|
+
) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.setChannels(
|
|
406
|
+
promoteChannel({
|
|
407
|
+
channels,
|
|
408
|
+
channelToMove: targetChannel,
|
|
409
|
+
channelToMoveIndexWithinChannels: targetChannelIndex,
|
|
410
|
+
sort,
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
private notificationNewMessageHandler = async (event: Event<SCG>) => {
|
|
416
|
+
const { id, type } = event?.channel ?? {};
|
|
417
|
+
|
|
418
|
+
const { channels, pagination } = this.state.getLatestValue();
|
|
419
|
+
const { filters, sort } = pagination ?? {};
|
|
420
|
+
|
|
421
|
+
if (!channels || !id || !type) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const channel = await getAndWatchChannel({
|
|
426
|
+
client: this.client,
|
|
427
|
+
id,
|
|
428
|
+
type,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
432
|
+
const isTargetChannelArchived = isChannelArchived(channel);
|
|
433
|
+
|
|
434
|
+
if (
|
|
435
|
+
(considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
|
|
436
|
+
(considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
|
|
437
|
+
!this.options.allowNotLoadedChannelPromotionForEvent?.['notification.message_new']
|
|
438
|
+
) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.setChannels(
|
|
443
|
+
promoteChannel({
|
|
444
|
+
channels,
|
|
445
|
+
channelToMove: channel,
|
|
446
|
+
sort,
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
private channelVisibleHandler = async (event: Event<SCG>) => {
|
|
452
|
+
const { channels, pagination } = this.state.getLatestValue();
|
|
453
|
+
const { sort, filters } = pagination ?? {};
|
|
454
|
+
const { channel_type: channelType, channel_id: channelId } = event;
|
|
455
|
+
|
|
456
|
+
if (!channels || !channelType || !channelId) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const channel = await getAndWatchChannel({
|
|
461
|
+
client: this.client,
|
|
462
|
+
id: event.channel_id,
|
|
463
|
+
type: event.channel_type,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
467
|
+
const isTargetChannelArchived = isChannelArchived(channel);
|
|
468
|
+
|
|
469
|
+
if (
|
|
470
|
+
(considerArchivedChannels && isTargetChannelArchived && !filters.archived) ||
|
|
471
|
+
(considerArchivedChannels && !isTargetChannelArchived && filters.archived) ||
|
|
472
|
+
!this.options.allowNotLoadedChannelPromotionForEvent?.['channel.visible']
|
|
473
|
+
) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
this.setChannels(
|
|
478
|
+
promoteChannel({
|
|
479
|
+
channels,
|
|
480
|
+
channelToMove: channel,
|
|
481
|
+
sort,
|
|
482
|
+
}),
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
private notificationRemovedFromChannelHandler = this.channelDeletedHandler;
|
|
487
|
+
|
|
488
|
+
private memberUpdatedHandler = (event: Event<SCG>) => {
|
|
489
|
+
const { pagination, channels } = this.state.getLatestValue();
|
|
490
|
+
const { filters, sort } = pagination;
|
|
491
|
+
if (
|
|
492
|
+
!event.member?.user ||
|
|
493
|
+
event.member.user.id !== this.client.userID ||
|
|
494
|
+
!event.channel_type ||
|
|
495
|
+
!event.channel_id
|
|
496
|
+
) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const channelType = event.channel_type;
|
|
500
|
+
const channelId = event.channel_id;
|
|
501
|
+
|
|
502
|
+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
|
|
503
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
504
|
+
const pinnedAtSort = extractSortValue({ atIndex: 0, sort, targetKey: 'pinned_at' });
|
|
505
|
+
|
|
506
|
+
if (!channels || (!considerPinnedChannels && !considerArchivedChannels) || this.options.lockChannelOrder) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const targetChannel = this.client.channel(channelType, channelId);
|
|
511
|
+
// assumes that channel instances are not changing
|
|
512
|
+
const targetChannelIndex = channels.indexOf(targetChannel);
|
|
513
|
+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
|
|
514
|
+
|
|
515
|
+
const isTargetChannelPinned = isChannelPinned(targetChannel);
|
|
516
|
+
const isTargetChannelArchived = isChannelArchived(targetChannel);
|
|
517
|
+
|
|
518
|
+
const newChannels = [...channels];
|
|
519
|
+
|
|
520
|
+
if (targetChannelExistsWithinList) {
|
|
521
|
+
newChannels.splice(targetChannelIndex, 1);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// handle archiving (remove channel)
|
|
525
|
+
if (
|
|
526
|
+
// When archived filter true, and channel is unarchived
|
|
527
|
+
(considerArchivedChannels && !isTargetChannelArchived && filters?.archived) ||
|
|
528
|
+
// When archived filter false, and channel is archived
|
|
529
|
+
(considerArchivedChannels && isTargetChannelArchived && !filters?.archived)
|
|
530
|
+
) {
|
|
531
|
+
this.setChannels(newChannels);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// handle pinning
|
|
536
|
+
let lastPinnedChannelIndex: number | null = null;
|
|
537
|
+
|
|
538
|
+
if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !isTargetChannelPinned)) {
|
|
539
|
+
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
|
|
540
|
+
}
|
|
541
|
+
const newTargetChannelIndex = typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0;
|
|
542
|
+
|
|
543
|
+
// skip state update if the position of the channel does not change
|
|
544
|
+
if (channels[newTargetChannelIndex] === targetChannel) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
newChannels.splice(newTargetChannelIndex, 0, targetChannel);
|
|
549
|
+
this.setChannels(newChannels);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
private subscriptionOrOverride = (event: Event<SCG>) => {
|
|
553
|
+
const handlerName = channelManagerEventToHandlerMapping[event.type as ChannelManagerEventTypes];
|
|
554
|
+
const defaultEventHandler = this.eventHandlers.get(handlerName);
|
|
555
|
+
const eventHandlerOverride = this.eventHandlerOverrides.get(handlerName);
|
|
556
|
+
if (eventHandlerOverride && typeof eventHandlerOverride === 'function') {
|
|
557
|
+
eventHandlerOverride(this.setChannels, event);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (defaultEventHandler && typeof defaultEventHandler === 'function') {
|
|
562
|
+
defaultEventHandler(event);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
public registerSubscriptions = () => {
|
|
567
|
+
if (this.unsubscribeFunctions.size) {
|
|
568
|
+
// Already listening for events and changes
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
for (const eventType of Object.keys(channelManagerEventToHandlerMapping)) {
|
|
573
|
+
this.unsubscribeFunctions.add(this.client.on(eventType, this.subscriptionOrOverride).unsubscribe);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
public unregisterSubscriptions = () => {
|
|
578
|
+
this.unsubscribeFunctions.forEach((cleanupFunction) => cleanupFunction());
|
|
579
|
+
this.unsubscribeFunctions.clear();
|
|
580
|
+
};
|
|
581
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
addFileToFormData,
|
|
19
19
|
axiosParamsSerializer,
|
|
20
20
|
chatCodes,
|
|
21
|
+
generateChannelTempCid,
|
|
21
22
|
isFunction,
|
|
22
23
|
isOnline,
|
|
23
24
|
isOwnUserBaseProperty,
|
|
@@ -126,6 +127,7 @@ import {
|
|
|
126
127
|
Mute,
|
|
127
128
|
MuteUserOptions,
|
|
128
129
|
MuteUserResponse,
|
|
130
|
+
NewMemberPayload,
|
|
129
131
|
OGAttachment,
|
|
130
132
|
OwnUserResponse,
|
|
131
133
|
PartialMessageUpdate,
|
|
@@ -216,6 +218,7 @@ import { Moderation } from './moderation';
|
|
|
216
218
|
import { ThreadManager } from './thread_manager';
|
|
217
219
|
import { DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE } from './constants';
|
|
218
220
|
import { PollManager } from './poll_manager';
|
|
221
|
+
import { ChannelManager, ChannelManagerEventHandlerOverrides, ChannelManagerOptions } from './channel_manager';
|
|
219
222
|
|
|
220
223
|
function isString(x: unknown): x is string {
|
|
221
224
|
return typeof x === 'string' || x instanceof String;
|
|
@@ -599,6 +602,24 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
|
|
|
599
602
|
return Promise.resolve();
|
|
600
603
|
};
|
|
601
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Creates an instance of ChannelManager.
|
|
607
|
+
*
|
|
608
|
+
* @internal
|
|
609
|
+
*
|
|
610
|
+
* @param eventHandlerOverrides - the overrides for event handlers to be used
|
|
611
|
+
* @param options - the options used for the channel manager
|
|
612
|
+
*/
|
|
613
|
+
createChannelManager = ({
|
|
614
|
+
eventHandlerOverrides = {},
|
|
615
|
+
options = {},
|
|
616
|
+
}: {
|
|
617
|
+
eventHandlerOverrides?: ChannelManagerEventHandlerOverrides<StreamChatGenerics>;
|
|
618
|
+
options?: ChannelManagerOptions;
|
|
619
|
+
}) => {
|
|
620
|
+
return new ChannelManager({ client: this, eventHandlerOverrides, options });
|
|
621
|
+
};
|
|
622
|
+
|
|
602
623
|
/**
|
|
603
624
|
* Creates a new WebSocket connection with the current user. Returns empty promise, if there is an active connection
|
|
604
625
|
*/
|
|
@@ -1935,10 +1956,13 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
|
|
|
1935
1956
|
getChannelByMembers = (channelType: string, custom: ChannelData<StreamChatGenerics>) => {
|
|
1936
1957
|
// Check if the channel already exists.
|
|
1937
1958
|
// Only allow 1 channel object per cid
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1959
|
+
const memberIds = (custom.members ?? []).map((member: string | NewMemberPayload<StreamChatGenerics>) =>
|
|
1960
|
+
typeof member === 'string' ? member : member.user_id ?? '',
|
|
1961
|
+
);
|
|
1962
|
+
const membersStr = memberIds.sort().join(',');
|
|
1963
|
+
const tempCid = generateChannelTempCid(channelType, memberIds);
|
|
1940
1964
|
|
|
1941
|
-
if (!
|
|
1965
|
+
if (!tempCid) {
|
|
1942
1966
|
throw Error('Please specify atleast one member when creating unique conversation');
|
|
1943
1967
|
}
|
|
1944
1968
|
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './moderation';
|
|
|
11
11
|
export * from './permissions';
|
|
12
12
|
export * from './poll';
|
|
13
13
|
export * from './poll_manager';
|
|
14
|
+
export * from './search_controller';
|
|
14
15
|
export * from './segment';
|
|
15
16
|
export * from './signing';
|
|
16
17
|
export * from './store';
|
|
@@ -19,4 +20,5 @@ export type { ThreadState, ThreadReadState, ThreadRepliesPagination, ThreadUserR
|
|
|
19
20
|
export * from './thread_manager';
|
|
20
21
|
export * from './token_manager';
|
|
21
22
|
export * from './types';
|
|
23
|
+
export * from './channel_manager';
|
|
22
24
|
export { isOwnUser, chatCodes, logChatPromiseExecution, formatMessage } from './utils';
|