polfan-server-js-client 0.2.89 → 0.2.91

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.
@@ -1,5 +1,5 @@
1
1
  import { ChatStateTracker } from "./ChatStateTracker";
2
- import { FollowedTopic } from "../types/src";
2
+ import { FollowedTopic, Message, ChatLocation } from "../types/src";
3
3
  import { ObservableIndexedObjectCollection } from "../IndexedObjectCollection";
4
4
  import { RoomMessagesHistory } from "./RoomMessagesHistory";
5
5
  export declare class MessagesManager {
@@ -38,6 +38,11 @@ export declare class MessagesManager {
38
38
  * @internal
39
39
  */
40
40
  _deleteByTopicIds(roomId: string, ...topicIds: string[]): void;
41
+ /**
42
+ * For internal use.
43
+ * @internal
44
+ */
45
+ _resolveLastMessage(location: ChatLocation): Promise<Message | null>;
41
46
  private createHistoryForNewRoom;
42
47
  private handleNewMessage;
43
48
  private handleFollowedTopicUpdated;
@@ -9,8 +9,10 @@ export declare class RoomMessagesHistory {
9
9
  constructor(room: Room, tracker: ChatStateTracker);
10
10
  /**
11
11
  * Returns a history window object for the given topic ID, allowing you to view message history.
12
+ * @param topicId
13
+ * @param peek If true, do not create a cache for this topic and do not allow it to collect new messages.
12
14
  */
13
- getMessagesWindow(topicId: string): Promise<TopicHistoryWindow | undefined>;
15
+ getMessagesWindow(topicId: string, peek?: boolean): Promise<TopicHistoryWindow | undefined>;
14
16
  private handleRoomUpdated;
15
17
  private handleNewTopic;
16
18
  private handleTopicDeleted;
@@ -73,6 +73,7 @@ export declare abstract class TraversableRemoteCollection<ItemT, EventMapT exten
73
73
  protected abstract isLatestItemLoaded(): Promise<boolean>;
74
74
  protected refreshFetchedState(): Promise<void>;
75
75
  protected addItems(newItems: ItemT[], to: 'head' | 'tail'): void;
76
+ protected emitChangeWithDiff(itemChanged: boolean, originalState: WindowState): void;
76
77
  /**
77
78
  * Return array with messages trimmed using High/Low Watermark strategy.
78
79
  */
@@ -104,12 +105,12 @@ export declare class TopicHistoryWindow extends TraversableRemoteCollection<Mess
104
105
  * @internal
105
106
  */
106
107
  _updateMessageReference(refTopic: Topic): void;
107
- private handleNewMessage;
108
- private handleMessagesRedacted;
109
108
  protected fetchItemsAfter(): Promise<Message[] | null>;
110
109
  protected fetchItemsBefore(): Promise<Message[] | null>;
111
110
  protected fetchLatestItems(): Promise<Message[]>;
112
111
  private getTopic;
113
112
  private getLatestMessageId;
114
113
  protected isLatestItemLoaded(): Promise<boolean>;
114
+ private handleNewMessage;
115
+ private handleMessagesRedacted;
115
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polfan-server-js-client",
3
- "version": "0.2.89",
3
+ "version": "0.2.91",
4
4
  "description": "JavaScript client library for handling communication with Polfan chat server.",
5
5
  "author": "Jarosław Żak",
6
6
  "license": "MIT",
@@ -7,7 +7,12 @@ import {
7
7
  RoomDeleted,
8
8
  RoomLeft,
9
9
  TopicDeleted,
10
- FollowedTopicUpdated, RoomJoined, NewTopic, Session, Room, MessageType,
10
+ FollowedTopicUpdated,
11
+ RoomJoined,
12
+ NewTopic,
13
+ Session,
14
+ Room,
15
+ Message, ChatLocation,
11
16
  } from "../types/src";
12
17
  import {
13
18
  IndexedCollection,
@@ -129,7 +134,7 @@ export class MessagesManager {
129
134
  return collection.items.reduce(
130
135
  (previousValue: number, currentValue) => previousValue + (currentValue.missed ?? 0),
131
136
  0,
132
- );
137
+ ) as number;
133
138
  }
134
139
 
135
140
  return undefined;
@@ -143,6 +148,30 @@ export class MessagesManager {
143
148
  this.followedTopics.get(roomId)?.delete(...topicIds);
144
149
  }
145
150
 
151
+ /**
152
+ * For internal use.
153
+ * @internal
154
+ */
155
+ public async _resolveLastMessage(location: ChatLocation): Promise<Message|null> {
156
+ // Try to get last message from history window (if cached)
157
+ let message: Message = await this.getRoomHistory(location.roomId)
158
+ .then(roomHistory => roomHistory?.getMessagesWindow(location.topicId, true))
159
+ .then(
160
+ historyWindow =>
161
+ historyWindow?.hasLatest && historyWindow.getAt(historyWindow.length - 1)
162
+ );
163
+
164
+ if (!message) {
165
+ const result = await this.tracker.client.send('GetMessages', {
166
+ location,
167
+ limit: 1,
168
+ });
169
+ message = result.data?.messages[0];
170
+ }
171
+
172
+ return message || null;
173
+ }
174
+
146
175
  private createHistoryForNewRoom(room: Room): void {
147
176
  this.roomHistories.set([room.id, new RoomMessagesHistory(room, this.tracker)]);
148
177
  }
@@ -24,9 +24,11 @@ export class RoomMessagesHistory {
24
24
 
25
25
  /**
26
26
  * Returns a history window object for the given topic ID, allowing you to view message history.
27
+ * @param topicId
28
+ * @param peek If true, do not create a cache for this topic and do not allow it to collect new messages.
27
29
  */
28
- public async getMessagesWindow(topicId: string): Promise<TopicHistoryWindow | undefined> {
29
- if (!this.historyWindows.has(topicId)) {
30
+ public async getMessagesWindow(topicId: string, peek: boolean = false): Promise<TopicHistoryWindow | undefined> {
31
+ if (!this.historyWindows.has(topicId) && !peek) {
30
32
  const topic = (await this.tracker.rooms.getTopics(this.room.id, [topicId])).get(topicId);
31
33
 
32
34
  if (topic) {
@@ -1,5 +1,6 @@
1
1
  import {IndexedCollection, ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
2
2
  import {
3
+ Message,
3
4
  MessagesRedacted,
4
5
  NewMessage,
5
6
  NewTopic,
@@ -368,16 +369,14 @@ export class RoomsManager {
368
369
  }
369
370
  }
370
371
 
371
- private handleMessagesRedacted(ev: MessagesRedacted): void {
372
+ private async handleMessagesRedacted(ev: MessagesRedacted): Promise<void> {
372
373
  // Remove redacted messages from topic and update metadata
373
374
  const topics = this.topics.get(ev.location.roomId);
374
375
  const topic = topics?.get(ev.location.topicId);
375
376
  if (topic) {
376
- topics.set({
377
- ...topic,
378
- messageCount: Math.max(topic.messageCount - ev.ids.length, 0),
379
- lastMessage: ev.ids.includes(topic.lastMessage?.id) ? null : topic.lastMessage,
380
- } as Topic);
377
+ const messageCount = Math.max(topic.messageCount - ev.ids.length, 0);
378
+ const lastMessage: Message = messageCount > 0 ? await this.messages._resolveLastMessage(ev.location) : null;
379
+ topics.set({ ...topic, messageCount, lastMessage } as Topic);
381
380
  }
382
381
  }
383
382
  }
@@ -116,9 +116,9 @@ export abstract class TraversableRemoteCollection<
116
116
  return;
117
117
  }
118
118
 
119
- this.internalState.ongoing = WindowState.LATEST;
120
-
121
119
  let result;
120
+ const originalState = this.state;
121
+ this.internalState.ongoing = WindowState.LATEST;
122
122
 
123
123
  try {
124
124
  result = await this.fetchLatestItems();
@@ -130,6 +130,7 @@ export abstract class TraversableRemoteCollection<
130
130
  this._items.deleteAll(); // Directly call deleteAll to prevent event emit.
131
131
  this.addItems(result, 'tail');
132
132
  this.internalState.current = WindowState.LATEST;
133
+ this.emitChangeWithDiff(true, originalState);
133
134
  }
134
135
 
135
136
  public async fetchPrevious(): Promise<void> {
@@ -137,9 +138,9 @@ export abstract class TraversableRemoteCollection<
137
138
  return;
138
139
  }
139
140
 
140
- this.internalState.ongoing = WindowState.PAST;
141
-
142
141
  let result;
142
+ const originalState = this.state;
143
+ this.internalState.ongoing = WindowState.PAST;
143
144
 
144
145
  try {
145
146
  result = await this.fetchItemsBefore();
@@ -163,11 +164,13 @@ export abstract class TraversableRemoteCollection<
163
164
  this.internalState.current = WindowState.OLDEST;
164
165
  }
165
166
 
167
+ this.emitChangeWithDiff(false, originalState);
166
168
  return;
167
169
  }
168
170
 
169
171
  this.addItems(result, 'head');
170
172
  await this.refreshFetchedState();
173
+ this.emitChangeWithDiff(true, originalState);
171
174
  }
172
175
 
173
176
  public async fetchNext(): Promise<void> {
@@ -175,9 +178,9 @@ export abstract class TraversableRemoteCollection<
175
178
  return;
176
179
  }
177
180
 
178
- this.internalState.ongoing = WindowState.PAST;
179
-
180
181
  let result;
182
+ const originalState = this.state;
183
+ this.internalState.ongoing = WindowState.PAST;
181
184
 
182
185
  try {
183
186
  result = await this.fetchItemsAfter();
@@ -194,6 +197,7 @@ export abstract class TraversableRemoteCollection<
194
197
  if (result.length) {
195
198
  this.addItems(result, 'tail');
196
199
  await this.refreshFetchedState();
200
+ this.emitChangeWithDiff(true, originalState);
197
201
  return;
198
202
  }
199
203
  }
@@ -221,8 +225,15 @@ export abstract class TraversableRemoteCollection<
221
225
  result = this.trimItemsArrayToLimit([...this.items, ...newItems], 'head');
222
226
  }
223
227
 
224
- this._items.deleteAll(); // Directly call deleteAll to prevent event emit.
225
- this.set(...result);
228
+ // Directly calls to prevent event emit.
229
+ this._items.deleteAll();
230
+ this._items.set(...(result.map(item => [this.getId(item), item] as [string, ItemT])));
231
+ }
232
+
233
+ protected emitChangeWithDiff(itemChanged: boolean, originalState: WindowState): void {
234
+ if (itemChanged || originalState !== this.state) {
235
+ this.eventTarget.emit('change', { setItems: Array.from(this._items.items.keys()) })
236
+ }
226
237
  }
227
238
 
228
239
  /**
@@ -336,36 +347,6 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
336
347
  }
337
348
  }
338
349
 
339
- private async handleNewMessage(ev: NewMessage): Promise<void> {
340
- if (
341
- [WindowState.LATEST, WindowState.LIVE].includes(this.state)
342
- && ev.message.location.roomId === this.roomId
343
- && ev.message.location.topicId === this.topicId
344
- ) {
345
- this.addItems([ev.message], 'tail');
346
- }
347
- }
348
-
349
- private async handleMessagesRedacted(ev: MessagesRedacted): Promise<void> {
350
- if (ev.location.topicId !== this.topicId || ev.location.roomId !== this.roomId) {
351
- return;
352
- }
353
-
354
- const refTopicIds = this.items
355
- .filter(msg => msg.topicRef && ev.ids.includes(msg.id))
356
- .map(msg => msg.topicRef as string);
357
-
358
- this.delete(...ev.ids);
359
-
360
- if (this.length === 0) {
361
- await this.resetToLatest();
362
- }
363
-
364
- if (refTopicIds.length > 0) {
365
- this.eventTarget.emit('reftopicsdeleted', refTopicIds);
366
- }
367
- }
368
-
369
350
  protected async fetchItemsAfter(): Promise<Message[] | null> {
370
351
  const afterId = this.getAt(this.length - 1)?.id;
371
352
 
@@ -433,4 +414,36 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
433
414
  const lastMessageId = await this.getLatestMessageId();
434
415
  return lastMessageId ? this.has(lastMessageId) : true;
435
416
  }
417
+
418
+ private async handleNewMessage(ev: NewMessage): Promise<void> {
419
+ if (
420
+ [WindowState.LATEST, WindowState.LIVE].includes(this.state)
421
+ && ev.message.location.roomId === this.roomId
422
+ && ev.message.location.topicId === this.topicId
423
+ ) {
424
+ const originalState = this.state;
425
+ this.addItems([ev.message], 'tail');
426
+ this.emitChangeWithDiff(true, originalState);
427
+ }
428
+ }
429
+
430
+ private async handleMessagesRedacted(ev: MessagesRedacted): Promise<void> {
431
+ if (ev.location.topicId !== this.topicId || ev.location.roomId !== this.roomId) {
432
+ return;
433
+ }
434
+
435
+ const refTopicIds = this.items
436
+ .filter(msg => msg.topicRef && ev.ids.includes(msg.id))
437
+ .map(msg => msg.topicRef as string);
438
+
439
+ this.delete(...ev.ids);
440
+
441
+ if (this.length === 0) {
442
+ await this.resetToLatest();
443
+ }
444
+
445
+ if (refTopicIds.length > 0) {
446
+ this.eventTarget.emit('reftopicsdeleted', refTopicIds);
447
+ }
448
+ }
436
449
  }