stream-chat 8.15.0 → 8.16.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/src/thread.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { StreamChat } from './client';
2
+ import {
3
+ DefaultGenerics,
4
+ ExtendableGenerics,
5
+ MessageResponse,
6
+ ThreadResponse,
7
+ ChannelResponse,
8
+ FormatMessageResponse,
9
+ ReactionResponse,
10
+ UserResponse,
11
+ } from './types';
12
+ import { addToMessageList, formatMessage } from './utils';
13
+
14
+ type ThreadReadStatus<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = Record<
15
+ string,
16
+ {
17
+ last_read: Date;
18
+ last_read_message_id: string;
19
+ unread_messages: number;
20
+ user: UserResponse<StreamChatGenerics>;
21
+ }
22
+ >;
23
+
24
+ export class Thread<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> {
25
+ id: string;
26
+ latestReplies: FormatMessageResponse<StreamChatGenerics>[] = [];
27
+ participants: ThreadResponse['thread_participants'] = [];
28
+ message: FormatMessageResponse<StreamChatGenerics>;
29
+ channel: ChannelResponse<StreamChatGenerics>;
30
+ _channel: ReturnType<StreamChat<StreamChatGenerics>['channel']>;
31
+ replyCount = 0;
32
+ _client: StreamChat<StreamChatGenerics>;
33
+ read: ThreadReadStatus<StreamChatGenerics> = {};
34
+
35
+ constructor(client: StreamChat<StreamChatGenerics>, t: ThreadResponse<StreamChatGenerics>) {
36
+ this.id = t.parent_message.id;
37
+ this.message = formatMessage(t.parent_message);
38
+ this.latestReplies = t.latest_replies.map(formatMessage);
39
+ this.participants = t.thread_participants;
40
+ this.replyCount = t.reply_count;
41
+ this.channel = t.channel;
42
+ this._channel = client.channel(t.channel.type, t.channel.id);
43
+ this._client = client;
44
+ for (const r of t.read) {
45
+ this.read[r.user.id] = {
46
+ ...r,
47
+ last_read: new Date(r.last_read),
48
+ };
49
+ }
50
+ }
51
+
52
+ getClient(): StreamChat<StreamChatGenerics> {
53
+ return this._client;
54
+ }
55
+
56
+ addReply(message: MessageResponse<StreamChatGenerics>) {
57
+ this.latestReplies = addToMessageList(this.latestReplies, formatMessage(message));
58
+ }
59
+
60
+ updateReply(message: MessageResponse<StreamChatGenerics>) {
61
+ this.latestReplies = this.latestReplies.map((m) => {
62
+ if (m.id === message.id) {
63
+ return formatMessage(message);
64
+ }
65
+ return m;
66
+ });
67
+ }
68
+
69
+ updateMessageOrReplyIfExists(message: MessageResponse<StreamChatGenerics>) {
70
+ if (!message.parent_id && message.id !== this.message.id) {
71
+ return;
72
+ }
73
+
74
+ if (message.parent_id && message.parent_id !== this.message.id) {
75
+ return;
76
+ }
77
+
78
+ if (message.parent_id && message.parent_id === this.message.id) {
79
+ this.updateReply(message);
80
+ }
81
+
82
+ if (!message.parent_id && message.id === this.message.id) {
83
+ this.message = formatMessage(message);
84
+ }
85
+ }
86
+
87
+ addReaction(
88
+ reaction: ReactionResponse<StreamChatGenerics>,
89
+ message?: MessageResponse<StreamChatGenerics>,
90
+ enforce_unique?: boolean,
91
+ ) {
92
+ if (!message) return;
93
+
94
+ this.latestReplies = this.latestReplies.map((m) => {
95
+ if (m.id === message.id) {
96
+ return formatMessage(
97
+ this._channel.state.addReaction(reaction, message, enforce_unique) as MessageResponse<StreamChatGenerics>,
98
+ );
99
+ }
100
+ return m;
101
+ });
102
+ }
103
+
104
+ removeReaction(reaction: ReactionResponse<StreamChatGenerics>, message?: MessageResponse<StreamChatGenerics>) {
105
+ if (!message) return;
106
+
107
+ this.latestReplies = this.latestReplies.map((m) => {
108
+ if (m.id === message.id) {
109
+ return formatMessage(
110
+ this._channel.state.removeReaction(reaction, message) as MessageResponse<StreamChatGenerics>,
111
+ );
112
+ }
113
+ return m;
114
+ });
115
+ }
116
+ }
package/src/types.ts CHANGED
@@ -478,6 +478,58 @@ export type GetMessageAPIResponse<
478
478
  StreamChatGenerics extends ExtendableGenerics = DefaultGenerics
479
479
  > = SendMessageAPIResponse<StreamChatGenerics>;
480
480
 
481
+ export type ThreadResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
482
+ channel: ChannelResponse<StreamChatGenerics>;
483
+ channel_cid: string;
484
+ created_at: string;
485
+ deleted_at: string;
486
+ latest_replies: MessageResponse<StreamChatGenerics>[];
487
+ parent_message: MessageResponse<StreamChatGenerics>;
488
+ parent_message_id: string;
489
+ read: {
490
+ last_read: string;
491
+ last_read_message_id: string;
492
+ unread_messages: number;
493
+ user: UserResponse<StreamChatGenerics>;
494
+ }[];
495
+ reply_count: number;
496
+ thread_participants: {
497
+ created_at: string;
498
+ user: UserResponse<StreamChatGenerics>;
499
+ }[];
500
+ title: string;
501
+ updated_at: string;
502
+ };
503
+
504
+ // TODO: Figure out a way to strongly type set and unset.
505
+ export type PartialThreadUpdate = {
506
+ set?: Partial<Record<string, unknown>>;
507
+ unset?: Partial<Record<string, unknown>>;
508
+ };
509
+
510
+ export type QueryThreadsOptions = {
511
+ limit?: number;
512
+ next?: string;
513
+ participant_limit?: number;
514
+ reply_limit?: number;
515
+ watch?: boolean;
516
+ };
517
+
518
+ export type QueryThreadsAPIResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = APIResponse & {
519
+ threads: ThreadResponse<StreamChatGenerics>[];
520
+ next?: string;
521
+ };
522
+
523
+ export type GetThreadOptions = {
524
+ participant_limit?: number;
525
+ reply_limit?: number;
526
+ watch?: boolean;
527
+ };
528
+
529
+ export type GetThreadAPIResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = APIResponse & {
530
+ thread: ThreadResponse<StreamChatGenerics>;
531
+ };
532
+
481
533
  export type GetMultipleMessagesAPIResponse<
482
534
  StreamChatGenerics extends ExtendableGenerics = DefaultGenerics
483
535
  > = APIResponse & {
@@ -510,7 +562,14 @@ export type GetUnreadCountAPIResponse = APIResponse & {
510
562
  last_read: string;
511
563
  unread_count: number;
512
564
  }[];
565
+ threads: {
566
+ last_read: string;
567
+ last_read_message_id: string;
568
+ parent_message_id: string;
569
+ unread_count: number;
570
+ }[];
513
571
  total_unread_count: number;
572
+ total_unread_threads_count: number;
514
573
  };
515
574
 
516
575
  export type GetUnreadCountBatchAPIResponse = APIResponse & {
@@ -893,6 +952,7 @@ export type MarkChannelsReadOptions<StreamChatGenerics extends ExtendableGeneric
893
952
  export type MarkReadOptions<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
894
953
  client_id?: string;
895
954
  connection_id?: string;
955
+ thread_id?: string;
896
956
  user?: UserResponse<StreamChatGenerics>;
897
957
  user_id?: string;
898
958
  };
@@ -1099,6 +1159,7 @@ export type Event<StreamChatGenerics extends ExtendableGenerics = DefaultGeneric
1099
1159
  reaction?: ReactionResponse<StreamChatGenerics>;
1100
1160
  received_at?: string | Date;
1101
1161
  team?: string;
1162
+ thread?: ThreadResponse<StreamChatGenerics>;
1102
1163
  // @deprecated number of all unread messages across all current user's unread channels, equals unread_count
1103
1164
  total_unread_count?: number;
1104
1165
  // number of all current user's channels with at least one unread message including the channel in this event
package/src/utils.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  import FormData from 'form-data';
2
- import { AscDesc, ExtendableGenerics, DefaultGenerics, OwnUserBase, OwnUserResponse, UserResponse } from './types';
2
+ import {
3
+ AscDesc,
4
+ ExtendableGenerics,
5
+ DefaultGenerics,
6
+ OwnUserBase,
7
+ OwnUserResponse,
8
+ UserResponse,
9
+ MessageResponse,
10
+ FormatMessageResponse,
11
+ } from './types';
3
12
  import { AxiosRequestConfig } from 'axios';
4
13
 
5
14
  /**
@@ -263,3 +272,94 @@ export const axiosParamsSerializer: AxiosRequestConfig['paramsSerializer'] = (pa
263
272
 
264
273
  return newParams.join('&');
265
274
  };
275
+
276
+ /**
277
+ * formatMessage - Takes the message object. Parses the dates, sets __html
278
+ * and sets the status to received if missing. Returns a message object
279
+ *
280
+ * @param {MessageResponse<StreamChatGenerics>} message a message object
281
+ *
282
+ */
283
+ export function formatMessage<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics>(
284
+ message: MessageResponse<StreamChatGenerics>,
285
+ ): FormatMessageResponse<StreamChatGenerics> {
286
+ return {
287
+ ...message,
288
+ /**
289
+ * @deprecated please use `html`
290
+ */
291
+ __html: message.html,
292
+ // parse the date..
293
+ pinned_at: message.pinned_at ? new Date(message.pinned_at) : null,
294
+ created_at: message.created_at ? new Date(message.created_at) : new Date(),
295
+ updated_at: message.updated_at ? new Date(message.updated_at) : new Date(),
296
+ status: message.status || 'received',
297
+ };
298
+ }
299
+
300
+ export function addToMessageList<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics>(
301
+ messages: Array<FormatMessageResponse<StreamChatGenerics>>,
302
+ message: FormatMessageResponse<StreamChatGenerics>,
303
+ timestampChanged = false,
304
+ sortBy: 'pinned_at' | 'created_at' = 'created_at',
305
+ addIfDoesNotExist = true,
306
+ ) {
307
+ const addMessageToList = addIfDoesNotExist || timestampChanged;
308
+ let messageArr = messages;
309
+
310
+ // if created_at has changed, message should be filtered and re-inserted in correct order
311
+ // slow op but usually this only happens for a message inserted to state before actual response with correct timestamp
312
+ if (timestampChanged) {
313
+ messageArr = messageArr.filter((msg) => !(msg.id && message.id === msg.id));
314
+ }
315
+
316
+ // Get array length after filtering
317
+ const messageArrayLength = messageArr.length;
318
+
319
+ // for empty list just concat and return unless it's an update or deletion
320
+ if (messageArrayLength === 0 && addMessageToList) {
321
+ return messageArr.concat(message);
322
+ } else if (messageArrayLength === 0) {
323
+ return [...messageArr];
324
+ }
325
+
326
+ const messageTime = (message[sortBy] as Date).getTime();
327
+ const messageIsNewest = (messageArr[messageArrayLength - 1][sortBy] as Date).getTime() < messageTime;
328
+
329
+ // if message is newer than last item in the list concat and return unless it's an update or deletion
330
+ if (messageIsNewest && addMessageToList) {
331
+ return messageArr.concat(message);
332
+ } else if (messageIsNewest) {
333
+ return [...messageArr];
334
+ }
335
+
336
+ // find the closest index to push the new message
337
+ let left = 0;
338
+ let middle = 0;
339
+ let right = messageArrayLength - 1;
340
+ while (left <= right) {
341
+ middle = Math.floor((right + left) / 2);
342
+ if ((messageArr[middle][sortBy] as Date).getTime() <= messageTime) left = middle + 1;
343
+ else right = middle - 1;
344
+ }
345
+
346
+ // message already exists and not filtered due to timestampChanged, update and return
347
+ if (!timestampChanged && message.id) {
348
+ if (messageArr[left] && message.id === messageArr[left].id) {
349
+ messageArr[left] = message;
350
+ return [...messageArr];
351
+ }
352
+
353
+ if (messageArr[left - 1] && message.id === messageArr[left - 1].id) {
354
+ messageArr[left - 1] = message;
355
+ return [...messageArr];
356
+ }
357
+ }
358
+
359
+ // Do not add updated or deleted messages to the list if they do not already exist
360
+ // or have a timestamp change.
361
+ if (addMessageToList) {
362
+ messageArr.splice(left, 0, message);
363
+ }
364
+ return [...messageArr];
365
+ }