stream-chat 6.2.0 → 6.5.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/channel.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  Message,
29
29
  MessageFilters,
30
30
  MessageResponse,
31
+ MessageSetType,
31
32
  MuteChannelAPIResponse,
32
33
  PartialUpdateChannel,
33
34
  PartialUpdateChannelAPIResponse,
@@ -668,12 +669,12 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
668
669
  lastMessage() {
669
670
  // get last 5 messages, sort, return the latest
670
671
  // get a slice of the last 5
671
- let min = this.state.messages.length - 5;
672
+ let min = this.state.latestMessages.length - 5;
672
673
  if (min < 0) {
673
674
  min = 0;
674
675
  }
675
- const max = this.state.messages.length + 1;
676
- const messageSlice = this.state.messages.slice(min, max);
676
+ const max = this.state.latestMessages.length + 1;
677
+ const messageSlice = this.state.latestMessages.slice(min, max);
677
678
 
678
679
  // sort by pk desc
679
680
  messageSlice.sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
@@ -736,7 +737,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
736
737
  }
737
738
 
738
739
  const combined = { ...defaultOptions, ...options };
739
- const state = await this.query(combined);
740
+ const state = await this.query(combined, 'latest');
740
741
  this.initialized = true;
741
742
  this.data = state.channel;
742
743
 
@@ -767,7 +768,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
767
768
  * getReplies - List the message replies for a parent message
768
769
  *
769
770
  * @param {string} parent_id The message parent id, ie the top of the thread
770
- * @param {PaginationOptions & { user?: UserResponse<StreamChatGenerics>; user_id?: string }} options Pagination params, ie {limit:10, id_lte: 10}
771
+ * @param {MessagePaginationOptions & { user?: UserResponse<StreamChatGenerics>; user_id?: string }} options Pagination params, ie {limit:10, id_lte: 10}
771
772
  *
772
773
  * @return {Promise<GetRepliesAPIResponse<StreamChatGenerics>>} A response with a list of messages
773
774
  */
@@ -883,8 +884,8 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
883
884
  if (!lastRead) return this.state.unreadCount;
884
885
 
885
886
  let count = 0;
886
- for (let i = 0; i < this.state.messages.length; i += 1) {
887
- const message = this.state.messages[i];
887
+ for (let i = 0; i < this.state.latestMessages.length; i += 1) {
888
+ const message = this.state.latestMessages[i];
888
889
  if (message.created_at > lastRead && this._countMessageAsUnread(message)) {
889
890
  count++;
890
891
  }
@@ -893,7 +894,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
893
894
  }
894
895
 
895
896
  /**
896
- * countUnread - Count the number of unread messages mentioning the current user
897
+ * countUnreadMentions - Count the number of unread messages mentioning the current user
897
898
  *
898
899
  * @return {number} Unread mentions count
899
900
  */
@@ -902,8 +903,8 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
902
903
  const userID = this.getClient().userID;
903
904
 
904
905
  let count = 0;
905
- for (let i = 0; i < this.state.messages.length; i += 1) {
906
- const message = this.state.messages[i];
906
+ for (let i = 0; i < this.state.latestMessages.length; i += 1) {
907
+ const message = this.state.latestMessages[i];
907
908
  if (
908
909
  this._countMessageAsUnread(message) &&
909
910
  (!lastRead || message.created_at > lastRead) &&
@@ -926,17 +927,21 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
926
927
  state: false,
927
928
  presence: false,
928
929
  };
929
- return await this.query(options);
930
+ return await this.query(options, 'latest');
930
931
  };
931
932
 
932
933
  /**
933
934
  * query - Query the API, get messages, members or other channel fields
934
935
  *
935
936
  * @param {ChannelQueryOptions<StreamChatGenerics>} options The query options
937
+ * @param {MessageSetType} messageSetToAddToIfDoesNotExist It's possible to load disjunct sets of a channel's messages into state, use `current` to load the initial channel state or if you want to extend the currently displayed messages, use `latest` if you want to load/extend the latest messages, `new` is used for loading a specific message and it's surroundings
936
938
  *
937
939
  * @return {Promise<ChannelAPIResponse<StreamChatGenerics>>} Returns a query response
938
940
  */
939
- async query(options: ChannelQueryOptions<StreamChatGenerics>) {
941
+ async query(
942
+ options: ChannelQueryOptions<StreamChatGenerics>,
943
+ messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
944
+ ) {
940
945
  // Make sure we wait for the connect promise if there is a pending one
941
946
  await this.getClient().wsPromise;
942
947
 
@@ -977,7 +982,7 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
977
982
  this.getClient()._addChannelConfig(state);
978
983
 
979
984
  // add any messages to our channel state
980
- this._initializeState(state);
985
+ this._initializeState(state, messageSetToAddToIfDoesNotExist);
981
986
 
982
987
  return state;
983
988
  }
@@ -1340,7 +1345,10 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
1340
1345
  }
1341
1346
 
1342
1347
  // eslint-disable-next-line sonarjs/cognitive-complexity
1343
- _initializeState(state: ChannelAPIResponse<StreamChatGenerics>) {
1348
+ _initializeState(
1349
+ state: ChannelAPIResponse<StreamChatGenerics>,
1350
+ messageSetToAddToIfDoesNotExist: MessageSetType = 'latest',
1351
+ ) {
1344
1352
  const { state: clientState, user, userID } = this.getClient();
1345
1353
 
1346
1354
  // add the Users
@@ -1356,9 +1364,9 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
1356
1364
 
1357
1365
  const messages = state.messages || [];
1358
1366
  if (!this.state.messages) {
1359
- this.state.messages = [];
1367
+ this.state.initMessages();
1360
1368
  }
1361
- this.state.addMessagesSorted(messages, false, true);
1369
+ this.state.addMessagesSorted(messages, false, true, true, messageSetToAddToIfDoesNotExist);
1362
1370
  if (!this.state.pinnedMessages) {
1363
1371
  this.state.pinnedMessages = [];
1364
1372
  }
@@ -6,6 +6,7 @@ import {
6
6
  Event,
7
7
  ExtendableGenerics,
8
8
  DefaultGenerics,
9
+ MessageSetType,
9
10
  MessageResponse,
10
11
  ReactionResponse,
11
12
  UserResponse,
@@ -24,7 +25,6 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
24
25
  watcher_count: number;
25
26
  typing: Record<string, Event<StreamChatGenerics>>;
26
27
  read: ChannelReadStatus<StreamChatGenerics>;
27
- messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>;
28
28
  pinnedMessages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>;
29
29
  threads: Record<string, Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>>;
30
30
  mutedUsers: Array<UserResponse<StreamChatGenerics>>;
@@ -40,12 +40,23 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
40
40
  * be pushed on to message list.
41
41
  */
42
42
  isUpToDate: boolean;
43
+ /**
44
+ * Disjoint lists of messages
45
+ * Users can jump in the message list (with searching) and this can result in disjoint lists of messages
46
+ * The state manages these lists and merges them when lists overlap
47
+ * The messages array contains the currently active set
48
+ */
49
+ messageSets: {
50
+ isCurrent: boolean;
51
+ isLatest: boolean;
52
+ messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>;
53
+ }[] = [];
43
54
  constructor(channel: Channel<StreamChatGenerics>) {
44
55
  this._channel = channel;
45
56
  this.watcher_count = 0;
46
57
  this.typing = {};
47
58
  this.read = {};
48
- this.messages = [];
59
+ this.initMessages();
49
60
  this.pinnedMessages = [];
50
61
  this.threads = {};
51
62
  // a list of users to hide messages from
@@ -64,20 +75,49 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
64
75
  this.last_message_at = channel?.state?.last_message_at != null ? new Date(channel.state.last_message_at) : null;
65
76
  }
66
77
 
78
+ get messages() {
79
+ return this.messageSets.find((s) => s.isCurrent)?.messages || [];
80
+ }
81
+
82
+ set messages(messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>) {
83
+ const index = this.messageSets.findIndex((s) => s.isCurrent);
84
+ this.messageSets[index].messages = messages;
85
+ }
86
+
87
+ /**
88
+ * The list of latest messages
89
+ * The messages array not always contains the latest messages (for example if a user searched for an earlier message, that is in a different message set)
90
+ */
91
+ get latestMessages() {
92
+ return this.messageSets.find((s) => s.isLatest)?.messages || [];
93
+ }
94
+
95
+ set latestMessages(messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>) {
96
+ const index = this.messageSets.findIndex((s) => s.isLatest);
97
+ this.messageSets[index].messages = messages;
98
+ }
99
+
67
100
  /**
68
101
  * addMessageSorted - Add a message to the state
69
102
  *
70
103
  * @param {MessageResponse<StreamChatGenerics>} newMessage A new message
71
104
  * @param {boolean} timestampChanged Whether updating a message with changed created_at value.
72
105
  * @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
73
- *
106
+ * @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if message is not in the list (only used if addIfDoesNotExist is true)
74
107
  */
75
108
  addMessageSorted(
76
109
  newMessage: MessageResponse<StreamChatGenerics>,
77
110
  timestampChanged = false,
78
111
  addIfDoesNotExist = true,
112
+ messageSetToAddToIfDoesNotExist: MessageSetType = 'latest',
79
113
  ) {
80
- return this.addMessagesSorted([newMessage], timestampChanged, false, addIfDoesNotExist);
114
+ return this.addMessagesSorted(
115
+ [newMessage],
116
+ timestampChanged,
117
+ false,
118
+ addIfDoesNotExist,
119
+ messageSetToAddToIfDoesNotExist,
120
+ );
81
121
  }
82
122
 
83
123
  /**
@@ -109,6 +149,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
109
149
  * @param {boolean} timestampChanged Whether updating messages with changed created_at value.
110
150
  * @param {boolean} initializing Whether channel is being initialized.
111
151
  * @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
152
+ * @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if messages are not in the list (only used if addIfDoesNotExist is true)
112
153
  *
113
154
  */
114
155
  addMessagesSorted(
@@ -116,46 +157,62 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
116
157
  timestampChanged = false,
117
158
  initializing = false,
118
159
  addIfDoesNotExist = true,
160
+ messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
119
161
  ) {
120
- for (let i = 0; i < newMessages.length; i += 1) {
121
- const isFromShadowBannedUser = newMessages[i].shadowed;
162
+ const { messagesToAdd, targetMessageSetIndex } = this.findTargetMessageSet(
163
+ newMessages,
164
+ addIfDoesNotExist,
165
+ messageSetToAddToIfDoesNotExist,
166
+ );
167
+
168
+ for (let i = 0; i < messagesToAdd.length; i += 1) {
169
+ const isFromShadowBannedUser = messagesToAdd[i].shadowed;
122
170
  if (isFromShadowBannedUser) {
123
171
  continue;
124
172
  }
125
- const message = this.formatMessage(newMessages[i]);
126
-
127
- if (message.user && this._channel?.cid) {
128
- /**
129
- * Store the reference to user for this channel, so that when we have to
130
- * handle updates to user, we can use the reference map, to determine which
131
- * channels need to be updated with updated user object.
132
- */
133
- this._channel.getClient().state.updateUserReference(message.user, this._channel.cid);
134
- }
173
+ // If message is already formatted we can skip the tasks below
174
+ // This will be true for messages that are already present at the state -> this happens when we perform merging of message sets
175
+ // This will be also true for message previews used by some SDKs
176
+ const isMessageFormatted = messagesToAdd[i].created_at instanceof Date;
177
+ let message: ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>;
178
+ if (isMessageFormatted) {
179
+ message = messagesToAdd[i] as ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>;
180
+ } else {
181
+ message = this.formatMessage(messagesToAdd[i] as MessageResponse<StreamChatGenerics>);
182
+
183
+ if (message.user && this._channel?.cid) {
184
+ /**
185
+ * Store the reference to user for this channel, so that when we have to
186
+ * handle updates to user, we can use the reference map, to determine which
187
+ * channels need to be updated with updated user object.
188
+ */
189
+ this._channel.getClient().state.updateUserReference(message.user, this._channel.cid);
190
+ }
135
191
 
136
- if (initializing && message.id && this.threads[message.id]) {
137
- // If we are initializing the state of channel (e.g., in case of connection recovery),
138
- // then in that case we remove thread related to this message from threads object.
139
- // This way we can ensure that we don't have any stale data in thread object
140
- // and consumer can refetch the replies.
141
- delete this.threads[message.id];
142
- }
192
+ if (initializing && message.id && this.threads[message.id]) {
193
+ // If we are initializing the state of channel (e.g., in case of connection recovery),
194
+ // then in that case we remove thread related to this message from threads object.
195
+ // This way we can ensure that we don't have any stale data in thread object
196
+ // and consumer can refetch the replies.
197
+ delete this.threads[message.id];
198
+ }
143
199
 
144
- if (!this.last_message_at) {
145
- this.last_message_at = new Date(message.created_at.getTime());
146
- }
200
+ if (!this.last_message_at) {
201
+ this.last_message_at = new Date(message.created_at.getTime());
202
+ }
147
203
 
148
- if (message.created_at.getTime() > this.last_message_at.getTime()) {
149
- this.last_message_at = new Date(message.created_at.getTime());
204
+ if (message.created_at.getTime() > this.last_message_at.getTime()) {
205
+ this.last_message_at = new Date(message.created_at.getTime());
206
+ }
150
207
  }
151
208
 
152
209
  // update or append the messages...
153
210
  const parentID = message.parent_id;
154
211
 
155
- // add to the main message list
156
- if (!parentID || message.show_in_channel) {
157
- this.messages = this._addToMessageList(
158
- this.messages,
212
+ // add to the given message set
213
+ if ((!parentID || message.show_in_channel) && targetMessageSetIndex !== -1) {
214
+ this.messageSets[targetMessageSetIndex].messages = this._addToMessageList(
215
+ this.messageSets[targetMessageSetIndex].messages,
159
216
  message,
160
217
  timestampChanged,
161
218
  'created_at',
@@ -286,12 +343,14 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
286
343
  updated_at: m.updated_at?.toString(),
287
344
  } as unknown) as MessageResponse<StreamChatGenerics>);
288
345
 
289
- const updatedMessages = this.messages
290
- .filter((msg) => msg.quoted_message_id === message.id)
291
- .map(parseMessage)
292
- .map((msg) => ({ ...msg, quoted_message: { ...message, attachments: [] } }));
346
+ this.messageSets.forEach((set) => {
347
+ const updatedMessages = set.messages
348
+ .filter((msg) => msg.quoted_message_id === message.id)
349
+ .map(parseMessage)
350
+ .map((msg) => ({ ...msg, quoted_message: { ...message, attachments: [] } }));
293
351
 
294
- this.addMessagesSorted(updatedMessages, true);
352
+ this.addMessagesSorted(updatedMessages, true);
353
+ });
295
354
  }
296
355
 
297
356
  /**
@@ -322,9 +381,14 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
322
381
  }
323
382
 
324
383
  if ((!show_in_channel && !parent_id) || show_in_channel) {
325
- const msgIndex = this.messages.findIndex((msg) => msg.id === message.id);
326
- if (msgIndex !== -1) {
327
- this.messages[msgIndex] = updateFunc(this.messages[msgIndex]);
384
+ const messageSetIndex = this.findMessageSetIndex(message);
385
+ if (messageSetIndex !== -1) {
386
+ const msgIndex = this.messageSets[messageSetIndex].messages.findIndex((msg) => msg.id === message.id);
387
+ if (msgIndex !== -1) {
388
+ this.messageSets[messageSetIndex].messages[msgIndex] = updateFunc(
389
+ this.messageSets[messageSetIndex].messages[msgIndex],
390
+ );
391
+ }
328
392
  }
329
393
  }
330
394
 
@@ -442,9 +506,15 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
442
506
  this.threads[messageToRemove.parent_id] = threadMessages;
443
507
  isRemoved = removed;
444
508
  } else {
445
- const { removed, result: messages } = this.removeMessageFromArray(this.messages, messageToRemove);
446
- this.messages = messages;
447
- isRemoved = removed;
509
+ const messageSetIndex = this.findMessageSetIndex(messageToRemove);
510
+ if (messageSetIndex !== -1) {
511
+ const { removed, result: messages } = this.removeMessageFromArray(
512
+ this.messageSets[messageSetIndex].messages,
513
+ messageToRemove,
514
+ );
515
+ this.messageSets[messageSetIndex].messages = messages;
516
+ isRemoved = removed;
517
+ }
448
518
  }
449
519
 
450
520
  return isRemoved;
@@ -477,7 +547,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
477
547
  }
478
548
  };
479
549
 
480
- _updateUserMessages(this.messages, user);
550
+ this.messageSets.forEach((set) => _updateUserMessages(set.messages, user));
481
551
 
482
552
  for (const parentId in this.threads) {
483
553
  _updateUserMessages(this.threads[parentId], user);
@@ -535,7 +605,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
535
605
  }
536
606
  };
537
607
 
538
- _deleteUserMessages(this.messages, user, hardDelete);
608
+ this.messageSets.forEach((set) => _deleteUserMessages(set.messages, user, hardDelete));
539
609
 
540
610
  for (const parentId in this.threads) {
541
611
  _deleteUserMessages(this.threads[parentId], user, hardDelete);
@@ -549,9 +619,9 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
549
619
  *
550
620
  */
551
621
  filterErrorMessages() {
552
- const filteredMessages = this.messages.filter((message) => message.type !== 'error');
622
+ const filteredMessages = this.latestMessages.filter((message) => message.type !== 'error');
553
623
 
554
- this.messages = filteredMessages;
624
+ this.latestMessages = filteredMessages;
555
625
  }
556
626
 
557
627
  /**
@@ -577,7 +647,129 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
577
647
  }
578
648
 
579
649
  clearMessages() {
580
- this.messages = [];
650
+ this.initMessages();
581
651
  this.pinnedMessages = [];
582
652
  }
653
+
654
+ initMessages() {
655
+ this.messageSets = [{ messages: [], isLatest: true, isCurrent: true }];
656
+ }
657
+
658
+ /**
659
+ * loadMessageIntoState - Loads a given message (and messages around it) into the state
660
+ *
661
+ * @param {string} messageId The id of the message, or 'latest' to indicate switching to the latest messages
662
+ * @param {string} parentMessageId The id of the parent message, if we want load a thread reply
663
+ */
664
+ async loadMessageIntoState(messageId: string | 'latest', parentMessageId?: string) {
665
+ let messageSetIndex: number;
666
+ let switchedToMessageSet = false;
667
+ let loadedMessageThread = false;
668
+ const messageIdToFind = parentMessageId || messageId;
669
+ if (messageId === 'latest') {
670
+ if (this.messages === this.latestMessages) {
671
+ return;
672
+ }
673
+ messageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
674
+ } else {
675
+ messageSetIndex = this.findMessageSetIndex({ id: messageIdToFind });
676
+ }
677
+ if (messageSetIndex !== -1) {
678
+ this.switchToMessageSet(messageSetIndex);
679
+ switchedToMessageSet = true;
680
+ }
681
+ loadedMessageThread = !parentMessageId || !!this.threads[parentMessageId]?.find((m) => m.id === messageId);
682
+ if (switchedToMessageSet && loadedMessageThread) {
683
+ return;
684
+ }
685
+ if (!switchedToMessageSet) {
686
+ await this._channel.query({ messages: { id_around: messageIdToFind, limit: 25 } }, 'new');
687
+ }
688
+ if (!loadedMessageThread && parentMessageId) {
689
+ await this._channel.getReplies(parentMessageId, { id_around: messageId, limit: 25 });
690
+ }
691
+ messageSetIndex = this.findMessageSetIndex({ id: messageIdToFind });
692
+ if (messageSetIndex !== -1) {
693
+ this.switchToMessageSet(messageSetIndex);
694
+ }
695
+ }
696
+
697
+ private switchToMessageSet(index: number) {
698
+ const currentMessages = this.messageSets.find((s) => s.isCurrent);
699
+ if (!currentMessages) {
700
+ return;
701
+ }
702
+ currentMessages.isCurrent = false;
703
+ this.messageSets[index].isCurrent = true;
704
+ }
705
+
706
+ private areMessageSetsOverlap(messages1: Array<{ id: string }>, messages2: Array<{ id: string }>) {
707
+ return messages1.some((m1) => messages2.find((m2) => m1.id === m2.id));
708
+ }
709
+
710
+ private findMessageSetIndex(message: { id?: string }) {
711
+ return this.messageSets.findIndex((set) => !!set.messages.find((m) => m.id === message.id));
712
+ }
713
+
714
+ private findTargetMessageSet(
715
+ newMessages: MessageResponse<StreamChatGenerics>[],
716
+ addIfDoesNotExist = true,
717
+ messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
718
+ ) {
719
+ let messagesToAdd: (
720
+ | MessageResponse<StreamChatGenerics>
721
+ | ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>
722
+ )[] = newMessages;
723
+ let targetMessageSetIndex!: number;
724
+ if (addIfDoesNotExist) {
725
+ const overlappingMessageSetIndices = this.messageSets
726
+ .map((_, i) => i)
727
+ .filter((i) => this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages));
728
+ switch (messageSetToAddToIfDoesNotExist) {
729
+ case 'new':
730
+ if (overlappingMessageSetIndices.length > 0) {
731
+ targetMessageSetIndex = overlappingMessageSetIndices[0];
732
+ // No new message set is created if newMessages only contains thread replies
733
+ } else if (newMessages.some((m) => !m.parent_id)) {
734
+ this.messageSets.push({ messages: [], isCurrent: false, isLatest: false });
735
+ targetMessageSetIndex = this.messageSets.length - 1;
736
+ }
737
+ break;
738
+ case 'current':
739
+ targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
740
+ break;
741
+ case 'latest':
742
+ targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
743
+ break;
744
+ default:
745
+ targetMessageSetIndex = -1;
746
+ }
747
+ // when merging the target set will be the first one from the overlapping message sets
748
+ const mergeTargetMessageSetIndex = overlappingMessageSetIndices.splice(0, 1)[0];
749
+ const mergeSourceMessageSetIndices = [...overlappingMessageSetIndices];
750
+ if (mergeTargetMessageSetIndex !== undefined && mergeTargetMessageSetIndex !== targetMessageSetIndex) {
751
+ mergeSourceMessageSetIndices.push(targetMessageSetIndex);
752
+ }
753
+ // merge message sets
754
+ if (mergeSourceMessageSetIndices.length > 0) {
755
+ const target = this.messageSets[mergeTargetMessageSetIndex];
756
+ const sources = this.messageSets.filter((_, i) => mergeSourceMessageSetIndices.indexOf(i) !== -1);
757
+ sources.forEach((messageSet) => {
758
+ target.isLatest = target.isLatest || messageSet.isLatest;
759
+ target.isCurrent = target.isCurrent || messageSet.isCurrent;
760
+ messagesToAdd = [...messagesToAdd, ...messageSet.messages];
761
+ });
762
+ sources.forEach((s) => this.messageSets.splice(this.messageSets.indexOf(s), 1));
763
+ const overlappingMessageSetIndex = this.messageSets.findIndex((s) =>
764
+ this.areMessageSetsOverlap(s.messages, newMessages),
765
+ );
766
+ targetMessageSetIndex = overlappingMessageSetIndex;
767
+ }
768
+ } else {
769
+ // assumes that all new messages belong to the same set
770
+ targetMessageSetIndex = this.findMessageSetIndex(newMessages[0]);
771
+ }
772
+
773
+ return { targetMessageSetIndex, messagesToAdd };
774
+ }
583
775
  }