polfan-server-js-client 0.1.995 → 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,23 +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
- /**
6
- * No messages exist in the history window.
7
- */
8
- EMPTY = 0,
9
5
  /**
10
6
  * The latest messages (those received live) are available in the history window, history has not been fetched.
11
7
  */
12
- LATEST_LIVE = 1,
8
+ LIVE = 0,
13
9
  /**
14
10
  * The latest messages has been fetched and are available in the history window.
15
11
  */
16
- LATEST_FETCHED = 2,
12
+ LATEST = 1,
17
13
  /**
18
14
  * The historical messages have been fetched and are available in the history window.
19
15
  * Latest messages are not available and will not be available.
20
16
  */
21
- PAST_FETCHED = 3
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
22
23
  }
23
24
  export declare abstract class TraversableRemoteCollection<T> extends ObservableIndexedObjectCollection<T> {
24
25
  /**
@@ -40,9 +41,8 @@ export declare abstract class TraversableRemoteCollection<T> extends ObservableI
40
41
  protected abstract fetchItemsAfter(): Promise<T[] | null>;
41
42
  protected abstract isLatestItemLoaded(): Promise<boolean>;
42
43
  protected refreshFetchedState(): Promise<void>;
43
- protected refreshLiveState(): void;
44
+ protected addItems(newItems: T[], to: 'head' | 'tail'): void;
44
45
  private throwIfFetchingInProgress;
45
- private addItems;
46
46
  /**
47
47
  * Return array with messages of count that matching limit.
48
48
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polfan-server-js-client",
3
- "version": "0.1.995",
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",
@@ -3,26 +3,27 @@ import {ChatStateTracker} from "./ChatStateTracker";
3
3
  import {ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
4
4
 
5
5
  export enum WindowState {
6
- /**
7
- * No messages exist in the history window.
8
- */
9
- EMPTY,
10
-
11
6
  /**
12
7
  * The latest messages (those received live) are available in the history window, history has not been fetched.
13
8
  */
14
- LATEST_LIVE,
9
+ LIVE,
15
10
 
16
11
  /**
17
12
  * The latest messages has been fetched and are available in the history window.
18
13
  */
19
- LATEST_FETCHED,
14
+ LATEST,
20
15
 
21
16
  /**
22
17
  * The historical messages have been fetched and are available in the history window.
23
18
  * Latest messages are not available and will not be available.
24
19
  */
25
- PAST_FETCHED,
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,
26
27
  }
27
28
 
28
29
  export abstract class TraversableRemoteCollection<T> extends ObservableIndexedObjectCollection<T> {
@@ -39,17 +40,17 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
39
40
  */
40
41
  public limit: number | null = 50;
41
42
 
42
- private currentState: WindowState = WindowState.EMPTY;
43
+ private currentState: WindowState = WindowState.LIVE;
43
44
  private fetchingState: WindowState = undefined;
44
45
 
45
46
  public async resetToLatest(): Promise<void> {
46
47
  this.throwIfFetchingInProgress();
47
48
 
48
- if (this.currentState === WindowState.LATEST_FETCHED) {
49
+ if (this.currentState === WindowState.LATEST) {
49
50
  return;
50
51
  }
51
52
 
52
- this.fetchingState = WindowState.LATEST_FETCHED;
53
+ this.fetchingState = WindowState.LATEST;
53
54
 
54
55
  let result;
55
56
 
@@ -61,13 +62,17 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
61
62
 
62
63
  this.deleteAll();
63
64
  this.addItems(result, 'tail');
64
- this.currentState = WindowState.LATEST_FETCHED;
65
+ this.currentState = WindowState.LATEST;
65
66
  }
66
67
 
67
68
  public async fetchPrevious(): Promise<void> {
69
+ if (this.state === WindowState.OLDEST) {
70
+ return;
71
+ }
72
+
68
73
  this.throwIfFetchingInProgress();
69
74
 
70
- this.fetchingState = WindowState.PAST_FETCHED;
75
+ this.fetchingState = WindowState.PAST;
71
76
 
72
77
  let result;
73
78
 
@@ -78,18 +83,23 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
78
83
  }
79
84
 
80
85
  if (! result) {
81
- return this.resetToLatest();
86
+ return this.resetToLatest();
82
87
  }
83
88
 
84
- if (result.length) {
85
- this.addItems(result, 'head');
86
- await this.refreshFetchedState();
89
+ if (! result.length) {
90
+ this.currentState = WindowState.OLDEST;
91
+ return;
87
92
  }
93
+
94
+ this.addItems(result, 'head');
95
+ await this.refreshFetchedState();
88
96
  }
89
97
 
90
98
  public async fetchNext(): Promise<void> {
91
99
  this.throwIfFetchingInProgress();
92
100
 
101
+ this.fetchingState = WindowState.PAST;
102
+
93
103
  let result;
94
104
 
95
105
  try {
@@ -118,22 +128,10 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
118
128
  protected abstract isLatestItemLoaded(): Promise<boolean>;
119
129
 
120
130
  protected async refreshFetchedState(): Promise<void> {
121
- this.currentState = (await this.isLatestItemLoaded()) ? WindowState.LATEST_FETCHED : WindowState.PAST_FETCHED;
131
+ this.currentState = (await this.isLatestItemLoaded()) ? WindowState.LATEST : WindowState.PAST;
122
132
  }
123
133
 
124
- protected refreshLiveState(): void {
125
- if (this.currentState === WindowState.EMPTY && this.length) {
126
- this.currentState = WindowState.LATEST_LIVE;
127
- }
128
- }
129
-
130
- private throwIfFetchingInProgress(): void {
131
- if (this.fetchingState) {
132
- throw new Error(`Items fetching in progress: ${WindowState[this.fetchingState]}`);
133
- }
134
- }
135
-
136
- private addItems(newItems: T[], to: 'head' | 'tail'): void {
134
+ protected addItems(newItems: T[], to: 'head' | 'tail'): void {
137
135
  let result;
138
136
 
139
137
  if (to === 'head') {
@@ -148,6 +146,12 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
148
146
  this.set(...result);
149
147
  }
150
148
 
149
+ private throwIfFetchingInProgress(): void {
150
+ if (this.fetchingState) {
151
+ throw new Error(`Items fetching in progress: ${WindowState[this.fetchingState]}`);
152
+ }
153
+ }
154
+
151
155
  /**
152
156
  * Return array with messages of count that matching limit.
153
157
  */
@@ -197,12 +201,11 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<Message> {
197
201
 
198
202
  private async handleNewMessage(ev: NewMessage): Promise<void> {
199
203
  if (
200
- [WindowState.LATEST_FETCHED, WindowState.LATEST_LIVE, WindowState.EMPTY].includes(this.state)
204
+ [WindowState.LATEST, WindowState.LIVE].includes(this.state)
201
205
  && ev.location.roomId === this.roomId
202
206
  && ev.location.topicId === this.topicId
203
207
  ) {
204
- this.set(ev.message);
205
- this.refreshLiveState();
208
+ this.addItems([ev.message], 'tail');
206
209
  }
207
210
  }
208
211
 
@@ -23,8 +23,17 @@ class TestableHistoryWindow extends TraversableRemoteCollection<SimpleMessage> {
23
23
  }
24
24
 
25
25
  public simulateNewMessageReceived(): void {
26
- this.set(messages[messages.length - 1]);
27
- this.refreshLiveState();
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
+ }
28
37
  }
29
38
 
30
39
  protected async fetchItemsAfter(): Promise<SimpleMessage[]> {
@@ -63,20 +72,25 @@ test('history window - states change', async () => {
63
72
  const window = new TestableHistoryWindow();
64
73
  window.limit = 5;
65
74
 
66
- expect(window.state).toEqual(WindowState.EMPTY);
75
+ expect(window.state).toEqual(WindowState.LIVE);
67
76
 
68
77
  window.simulateNewMessageReceived(); // [9]
69
78
 
70
- expect(window.state).toEqual(WindowState.LATEST_LIVE);
79
+ expect(window.state).toEqual(WindowState.LIVE);
71
80
 
72
81
  await window.fetchPrevious(); // [6,7,8,9]
73
82
  await window.fetchPrevious(); // [3,4,5,6,7]
74
83
 
75
- expect(window.state).toEqual(WindowState.PAST_FETCHED);
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]
76
88
 
77
- await window.resetToLatest(); // [5,6,7,8,9]
89
+ expect(window.state).toEqual(WindowState.OLDEST);
78
90
 
79
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
91
+ await window.resetToLatest(); // [7,8,9]
92
+
93
+ expect(window.state).toEqual(WindowState.LATEST);
80
94
  });
81
95
 
82
96
  test('history window - traverse back', async () => {
@@ -85,13 +99,13 @@ test('history window - traverse back', async () => {
85
99
 
86
100
  await window.fetchPrevious(); // 7,8,9
87
101
 
88
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
102
+ expect(window.state).toEqual(WindowState.LATEST);
89
103
  expect(window.items).toHaveLength(3);
90
104
  [7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
91
105
 
92
106
  await window.fetchPrevious(); // 4,5,6,7,8
93
107
 
94
- expect(window.state).toEqual(WindowState.PAST_FETCHED);
108
+ expect(window.state).toEqual(WindowState.PAST);
95
109
  expect(window.items).toHaveLength(5);
96
110
  [4,5,6,7,8].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
97
111
 
@@ -99,7 +113,7 @@ test('history window - traverse back', async () => {
99
113
  await window.fetchPrevious(); // 0,1,2,3,4
100
114
  await window.fetchPrevious(); // 0,1,2,3,4
101
115
 
102
- expect(window.state).toEqual(WindowState.PAST_FETCHED);
116
+ expect(window.state).toEqual(WindowState.OLDEST);
103
117
  expect(window.items).toHaveLength(5);
104
118
  [0,1,2,3,4].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
105
119
  });
@@ -108,9 +122,9 @@ test('history window - traverse forward', async () => {
108
122
  const window = new TestableHistoryWindow();
109
123
  window.limit = 5;
110
124
 
111
- await window.fetchNext();
125
+ await window.fetchNext(); // [7,8,9]
112
126
 
113
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
127
+ expect(window.state).toEqual(WindowState.LATEST);
114
128
  expect(window.items).toHaveLength(3);
115
129
  [7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
116
130
 
@@ -118,7 +132,7 @@ test('history window - traverse forward', async () => {
118
132
  await window.fetchPrevious(); // [1,2,3,4,5]
119
133
  await window.fetchNext(); // [4,5,6,7,8]
120
134
 
121
- expect(window.state).toEqual(WindowState.PAST_FETCHED);
135
+ expect(window.state).toEqual(WindowState.PAST);
122
136
  expect(window.items).toHaveLength(5);
123
137
  [4,5,6,7,8].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
124
138
 
@@ -126,7 +140,7 @@ test('history window - traverse forward', async () => {
126
140
  await window.fetchNext();
127
141
  await window.fetchNext(); // move to latest
128
142
 
129
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
143
+ expect(window.state).toEqual(WindowState.LATEST);
130
144
  expect(window.items).toHaveLength(5);
131
145
  [5,6,7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
132
146
  });
@@ -135,19 +149,43 @@ test('history window - reset to latest', async () => {
135
149
  const window = new TestableHistoryWindow();
136
150
  window.limit = 5;
137
151
 
138
- await window.fetchPrevious();
139
-
140
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
141
-
142
- await window.fetchPrevious();
143
- await window.fetchPrevious();
144
- 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]
145
155
 
146
- expect(window.state).toEqual(WindowState.PAST_FETCHED);
156
+ expect(window.state).toEqual(WindowState.PAST);
147
157
 
148
158
  await window.resetToLatest();
149
159
 
150
- expect(window.state).toEqual(WindowState.LATEST_FETCHED);
160
+ expect(window.state).toEqual(WindowState.LATEST);
151
161
  expect(window.items).toHaveLength(3);
152
162
  [7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
153
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
+ });