polfan-server-js-client 0.1.994 → 0.1.996

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.
@@ -2,9 +2,24 @@ import { Message, MessageReference } from "../types/src";
2
2
  import { ChatStateTracker } from "./ChatStateTracker";
3
3
  import { ObservableIndexedObjectCollection } from "../IndexedObjectCollection";
4
4
  export declare enum WindowState {
5
- UNINITIALIZED = 0,
5
+ /**
6
+ * The latest messages (those received live) are available in the history window, history has not been fetched.
7
+ */
8
+ LIVE = 0,
9
+ /**
10
+ * The latest messages has been fetched and are available in the history window.
11
+ */
6
12
  LATEST = 1,
7
- PAST = 2
13
+ /**
14
+ * The historical messages have been fetched and are available in the history window.
15
+ * Latest messages are not available and will not be available.
16
+ */
17
+ PAST = 2,
18
+ /**
19
+ * The oldest messages have been fetched and are available in the history window.
20
+ * Next attempts to fetch previous messages will result with no-op.
21
+ */
22
+ OLDEST = 3
8
23
  }
9
24
  export declare abstract class TraversableRemoteCollection<T> extends ObservableIndexedObjectCollection<T> {
10
25
  /**
@@ -25,9 +40,9 @@ export declare abstract class TraversableRemoteCollection<T> extends ObservableI
25
40
  protected abstract fetchItemsBefore(): Promise<T[] | null>;
26
41
  protected abstract fetchItemsAfter(): Promise<T[] | null>;
27
42
  protected abstract isLatestItemLoaded(): Promise<boolean>;
28
- protected refreshMode(): Promise<void>;
43
+ protected refreshFetchedState(): Promise<void>;
44
+ protected addItems(newItems: T[], to: 'head' | 'tail'): void;
29
45
  private throwIfFetchingInProgress;
30
- private addItems;
31
46
  /**
32
47
  * Return array with messages of count that matching limit.
33
48
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polfan-server-js-client",
3
- "version": "0.1.994",
3
+ "version": "0.1.996",
4
4
  "description": "JavaScript client library for handling communication with Polfan chat server.",
5
5
  "author": "Jarosław Żak",
6
6
  "license": "MIT",
@@ -1,11 +1,29 @@
1
- import {Message, MessageReference, NewMessage, Room, Session, Topic} from "../types/src";
1
+ import {Message, MessageReference, NewMessage, Session, Topic} from "../types/src";
2
2
  import {ChatStateTracker} from "./ChatStateTracker";
3
3
  import {ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
4
4
 
5
5
  export enum WindowState {
6
- UNINITIALIZED,
6
+ /**
7
+ * The latest messages (those received live) are available in the history window, history has not been fetched.
8
+ */
9
+ LIVE,
10
+
11
+ /**
12
+ * The latest messages has been fetched and are available in the history window.
13
+ */
7
14
  LATEST,
15
+
16
+ /**
17
+ * The historical messages have been fetched and are available in the history window.
18
+ * Latest messages are not available and will not be available.
19
+ */
8
20
  PAST,
21
+
22
+ /**
23
+ * The oldest messages have been fetched and are available in the history window.
24
+ * Next attempts to fetch previous messages will result with no-op.
25
+ */
26
+ OLDEST,
9
27
  }
10
28
 
11
29
  export abstract class TraversableRemoteCollection<T> extends ObservableIndexedObjectCollection<T> {
@@ -22,7 +40,7 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
22
40
  */
23
41
  public limit: number | null = 50;
24
42
 
25
- private currentState: WindowState = WindowState.UNINITIALIZED;
43
+ private currentState: WindowState = WindowState.LIVE;
26
44
  private fetchingState: WindowState = undefined;
27
45
 
28
46
  public async resetToLatest(): Promise<void> {
@@ -48,6 +66,10 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
48
66
  }
49
67
 
50
68
  public async fetchPrevious(): Promise<void> {
69
+ if (this.state === WindowState.OLDEST) {
70
+ return;
71
+ }
72
+
51
73
  this.throwIfFetchingInProgress();
52
74
 
53
75
  this.fetchingState = WindowState.PAST;
@@ -61,18 +83,23 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
61
83
  }
62
84
 
63
85
  if (! result) {
64
- return this.resetToLatest();
86
+ return this.resetToLatest();
65
87
  }
66
88
 
67
- if (result.length) {
68
- this.addItems(result, 'head');
69
- this.currentState = (await this.isLatestItemLoaded()) ? WindowState.LATEST : WindowState.PAST;
89
+ if (! result.length) {
90
+ this.currentState = WindowState.OLDEST;
91
+ return;
70
92
  }
93
+
94
+ this.addItems(result, 'head');
95
+ await this.refreshFetchedState();
71
96
  }
72
97
 
73
98
  public async fetchNext(): Promise<void> {
74
99
  this.throwIfFetchingInProgress();
75
100
 
101
+ this.fetchingState = WindowState.PAST;
102
+
76
103
  let result;
77
104
 
78
105
  try {
@@ -88,7 +115,7 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
88
115
 
89
116
  if (result.length) {
90
117
  this.addItems(result, 'tail');
91
- await this.refreshMode();
118
+ await this.refreshFetchedState();
92
119
  }
93
120
  }
94
121
 
@@ -100,17 +127,11 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
100
127
 
101
128
  protected abstract isLatestItemLoaded(): Promise<boolean>;
102
129
 
103
- protected async refreshMode(): Promise<void> {
130
+ protected async refreshFetchedState(): Promise<void> {
104
131
  this.currentState = (await this.isLatestItemLoaded()) ? WindowState.LATEST : WindowState.PAST;
105
132
  }
106
133
 
107
- private throwIfFetchingInProgress(): void {
108
- if (this.fetchingState) {
109
- throw new Error(`Items fetching in progress: ${WindowState[this.fetchingState]}`);
110
- }
111
- }
112
-
113
- private addItems(newItems: T[], to: 'head' | 'tail'): void {
134
+ protected addItems(newItems: T[], to: 'head' | 'tail'): void {
114
135
  let result;
115
136
 
116
137
  if (to === 'head') {
@@ -125,6 +146,12 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
125
146
  this.set(...result);
126
147
  }
127
148
 
149
+ private throwIfFetchingInProgress(): void {
150
+ if (this.fetchingState) {
151
+ throw new Error(`Items fetching in progress: ${WindowState[this.fetchingState]}`);
152
+ }
153
+ }
154
+
128
155
  /**
129
156
  * Return array with messages of count that matching limit.
130
157
  */
@@ -174,15 +201,11 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<Message> {
174
201
 
175
202
  private async handleNewMessage(ev: NewMessage): Promise<void> {
176
203
  if (
177
- [WindowState.LATEST, WindowState.UNINITIALIZED].includes(this.state)
204
+ [WindowState.LATEST, WindowState.LIVE].includes(this.state)
178
205
  && ev.location.roomId === this.roomId
179
206
  && ev.location.topicId === this.topicId
180
207
  ) {
181
- this.set(ev.message);
182
-
183
- if (this.state === WindowState.UNINITIALIZED) {
184
- await this.refreshMode();
185
- }
208
+ this.addItems([ev.message], 'tail');
186
209
  }
187
210
  }
188
211
 
@@ -252,6 +275,6 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<Message> {
252
275
 
253
276
  protected async isLatestItemLoaded(): Promise<boolean> {
254
277
  const lastMessageId = await this.getLatestMessageId();
255
- return lastMessageId ? this.has(lastMessageId) : false;
278
+ return lastMessageId ? this.has(lastMessageId) : true;
256
279
  }
257
280
  }
@@ -22,6 +22,20 @@ class TestableHistoryWindow extends TraversableRemoteCollection<SimpleMessage> {
22
22
  super('id');
23
23
  }
24
24
 
25
+ public simulateNewMessageReceived(): void {
26
+ if ([WindowState.LATEST, WindowState.LIVE].includes(this.state)) {
27
+ const lastId = this.getAt(this.length - 1)?.id;
28
+
29
+ const messageToAdd = lastId
30
+ ? this.getAt(lastId + 1)
31
+ : messages[messages.length - 1];
32
+
33
+ if (messageToAdd) {
34
+ this.addItems([messageToAdd], 'tail');
35
+ }
36
+ }
37
+ }
38
+
25
39
  protected async fetchItemsAfter(): Promise<SimpleMessage[]> {
26
40
  const after = this.getAt(this.length - 1)?.id;
27
41
  if (after === undefined) {
@@ -52,7 +66,31 @@ test('history window - fresh instance', async () => {
52
66
  const window = new TestableHistoryWindow();
53
67
  expect(window.items).toHaveLength(0);
54
68
  expect(window.limit).toEqual(50);
55
- expect(window.state).toEqual(WindowState.UNINITIALIZED);
69
+ });
70
+
71
+ test('history window - states change', async () => {
72
+ const window = new TestableHistoryWindow();
73
+ window.limit = 5;
74
+
75
+ expect(window.state).toEqual(WindowState.LIVE);
76
+
77
+ window.simulateNewMessageReceived(); // [9]
78
+
79
+ expect(window.state).toEqual(WindowState.LIVE);
80
+
81
+ await window.fetchPrevious(); // [6,7,8,9]
82
+ await window.fetchPrevious(); // [3,4,5,6,7]
83
+
84
+ expect(window.state).toEqual(WindowState.PAST);
85
+
86
+ await window.fetchPrevious(); // [0,1,2,3,4]
87
+ await window.fetchPrevious(); // [0,1,2,3,4]
88
+
89
+ expect(window.state).toEqual(WindowState.OLDEST);
90
+
91
+ await window.resetToLatest(); // [7,8,9]
92
+
93
+ expect(window.state).toEqual(WindowState.LATEST);
56
94
  });
57
95
 
58
96
  test('history window - traverse back', async () => {
@@ -75,7 +113,7 @@ test('history window - traverse back', async () => {
75
113
  await window.fetchPrevious(); // 0,1,2,3,4
76
114
  await window.fetchPrevious(); // 0,1,2,3,4
77
115
 
78
- expect(window.state).toEqual(WindowState.PAST);
116
+ expect(window.state).toEqual(WindowState.OLDEST);
79
117
  expect(window.items).toHaveLength(5);
80
118
  [0,1,2,3,4].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
81
119
  });
@@ -84,7 +122,7 @@ test('history window - traverse forward', async () => {
84
122
  const window = new TestableHistoryWindow();
85
123
  window.limit = 5;
86
124
 
87
- await window.fetchNext();
125
+ await window.fetchNext(); // [7,8,9]
88
126
 
89
127
  expect(window.state).toEqual(WindowState.LATEST);
90
128
  expect(window.items).toHaveLength(3);
@@ -111,13 +149,9 @@ test('history window - reset to latest', async () => {
111
149
  const window = new TestableHistoryWindow();
112
150
  window.limit = 5;
113
151
 
114
- await window.fetchPrevious();
115
-
116
- expect(window.state).toEqual(WindowState.LATEST);
117
-
118
- await window.fetchPrevious();
119
- await window.fetchPrevious();
120
- await window.fetchPrevious(); // Move to start
152
+ await window.fetchPrevious(); // [7,8,9]
153
+ await window.fetchPrevious(); // [4,5,6,7,8]
154
+ await window.fetchPrevious(); // [1,2,3,4,5]
121
155
 
122
156
  expect(window.state).toEqual(WindowState.PAST);
123
157
 
@@ -127,3 +161,31 @@ test('history window - reset to latest', async () => {
127
161
  expect(window.items).toHaveLength(3);
128
162
  [7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
129
163
  });
164
+
165
+ test('history window - trim messages window to limit', async () => {
166
+ const window = new TestableHistoryWindow();
167
+ window.limit = 5;
168
+
169
+ expect(window.items).toHaveLength(0);
170
+
171
+ await window.fetchPrevious(); // [7,8,9]
172
+
173
+ expect(window.items).toHaveLength(3);
174
+
175
+ await window.fetchPrevious(); // [4,5,6,7,8]
176
+
177
+ expect(window.items).toHaveLength(5);
178
+
179
+ await window.fetchNext(); // [5,6,7,8,9]
180
+
181
+ expect(window.items).toHaveLength(5);
182
+
183
+ await window.fetchPrevious(); // [2,3,4,5,6]
184
+
185
+ expect(window.items).toHaveLength(5);
186
+
187
+ window.simulateNewMessageReceived();
188
+ window.simulateNewMessageReceived();
189
+
190
+ expect(window.items).toHaveLength(5);
191
+ });