polfan-server-js-client 0.1.997 → 0.1.999

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,6 +1,7 @@
1
1
  import { ObservableInterface } from "./EventTarget";
2
2
  import { AbstractChatClient, CommandResult, CommandsMap } from "./AbstractChatClient";
3
3
  import { ChatStateTracker } from "./state-tracker/ChatStateTracker";
4
+ import { Envelope } from "./types/src";
4
5
  export interface WebSocketClientOptions {
5
6
  url: string;
6
7
  token?: string;
@@ -20,7 +21,7 @@ export declare class WebSocketChatClient extends AbstractChatClient implements O
20
21
  readonly Event: typeof WebSocketChatClientEvent;
21
22
  readonly state?: ChatStateTracker;
22
23
  protected ws: WebSocket | null;
23
- protected sendQueue: [commandType: keyof CommandsMap, commandData: any][];
24
+ protected sendQueue: Envelope[];
24
25
  protected connectingTimeoutId: any;
25
26
  protected authenticated: boolean;
26
27
  protected authenticatedResolvers: [() => void, (error: Error) => void];
@@ -28,6 +29,7 @@ export declare class WebSocketChatClient extends AbstractChatClient implements O
28
29
  connect(): Promise<void>;
29
30
  disconnect(): void;
30
31
  send<CommandType extends keyof CommandsMap>(commandType: CommandType, commandData: CommandsMap[CommandType][0]): Promise<CommandResult<CommandsMap[CommandType][1]>>;
32
+ private sendEnvelope;
31
33
  private onMessage;
32
34
  private onClose;
33
35
  private sendFromQueue;
@@ -33,6 +33,9 @@ export declare abstract class TraversableRemoteCollection<T> extends ObservableI
33
33
  limit: number | null;
34
34
  private currentState;
35
35
  private fetchingState;
36
+ oldestId: string;
37
+ get hasLatest(): boolean;
38
+ get hasOldest(): boolean;
36
39
  resetToLatest(): Promise<void>;
37
40
  fetchPrevious(): Promise<void>;
38
41
  fetchNext(): Promise<void>;
@@ -42,7 +45,6 @@ export declare abstract class TraversableRemoteCollection<T> extends ObservableI
42
45
  protected abstract isLatestItemLoaded(): Promise<boolean>;
43
46
  protected refreshFetchedState(): Promise<void>;
44
47
  protected addItems(newItems: T[], to: 'head' | 'tail'): void;
45
- private throwIfFetchingInProgress;
46
48
  /**
47
49
  * Return array with messages of count that matching limit.
48
50
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polfan-server-js-client",
3
- "version": "0.1.997",
3
+ "version": "0.1.999",
4
4
  "description": "JavaScript client library for handling communication with Polfan chat server.",
5
5
  "author": "Jarosław Żak",
6
6
  "license": "MIT",
@@ -24,7 +24,7 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
24
24
  public readonly state?: ChatStateTracker;
25
25
 
26
26
  protected ws: WebSocket|null = null;
27
- protected sendQueue: [commandType: keyof CommandsMap, commandData: any][] = [];
27
+ protected sendQueue: Envelope[] = [];
28
28
  protected connectingTimeoutId: any;
29
29
  protected authenticated: boolean;
30
30
  protected authenticatedResolvers: [() => void, (error: Error) => void];
@@ -64,18 +64,24 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
64
64
  throw new Error('Cannot send; close or closing connection state');
65
65
  }
66
66
 
67
+ const envelope = this.createEnvelope<CommandsMap[CommandType][0]>(commandType, commandData);
68
+ const promise = this.createPromiseFromCommandEnvelope<CommandType>(envelope);
69
+
67
70
  if (this.ws.readyState === this.ws.CONNECTING || !this.authenticated) {
68
- this.sendQueue.push([commandType, commandData] as any);
69
- return;
71
+ this.sendQueue.push(envelope);
72
+ return promise;
70
73
  }
71
74
 
72
75
  if (this.ws.readyState !== this.ws.OPEN) {
73
76
  throw new Error(`Invalid websocket state=${this.ws.readyState}`);
74
77
  }
75
78
 
76
- const envelope = this.createEnvelope<CommandsMap[CommandType][0]>(commandType, commandData);
79
+ this.sendEnvelope(envelope);
80
+ return promise;
81
+ }
82
+
83
+ private sendEnvelope(envelope: Envelope): void {
77
84
  this.ws.send(JSON.stringify(envelope));
78
- return this.createPromiseFromCommandEnvelope<CommandType>(envelope);
79
85
  }
80
86
 
81
87
  private onMessage(event: MessageEvent): void {
@@ -111,8 +117,8 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
111
117
  // Send awaiting data to server
112
118
  let lastDelay = 0;
113
119
  for (const dataIndex in this.sendQueue) {
114
- const data = this.sendQueue[dataIndex];
115
- setTimeout(() => this.send(...data), lastDelay);
120
+ const envelope = this.sendQueue[dataIndex];
121
+ setTimeout(() => this.sendEnvelope(envelope), lastDelay);
116
122
  lastDelay += this.options.awaitQueueSendDelayMs ?? 500;
117
123
  }
118
124
  this.sendQueue = [];
@@ -42,11 +42,18 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
42
42
 
43
43
  private currentState: WindowState = WindowState.LIVE;
44
44
  private fetchingState: WindowState = undefined;
45
+ public oldestId: string = null;
45
46
 
46
- public async resetToLatest(): Promise<void> {
47
- this.throwIfFetchingInProgress();
47
+ public get hasLatest(): boolean {
48
+ return [WindowState.LATEST, WindowState.LIVE].includes(this.state);
49
+ }
50
+
51
+ public get hasOldest(): boolean {
52
+ return this.state === WindowState.OLDEST || this.oldestId !== null && this.has(this.oldestId);
53
+ }
48
54
 
49
- if (this.currentState === WindowState.LATEST) {
55
+ public async resetToLatest(): Promise<void> {
56
+ if (this.fetchingState || this.currentState === WindowState.LATEST) {
50
57
  return;
51
58
  }
52
59
 
@@ -66,12 +73,10 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
66
73
  }
67
74
 
68
75
  public async fetchPrevious(): Promise<void> {
69
- if (this.state === WindowState.OLDEST) {
76
+ if (this.fetchingState || this.hasOldest) {
70
77
  return;
71
78
  }
72
79
 
73
- this.throwIfFetchingInProgress();
74
-
75
80
  this.fetchingState = WindowState.PAST;
76
81
 
77
82
  let result;
@@ -87,7 +92,17 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
87
92
  }
88
93
 
89
94
  if (! result.length) {
90
- this.currentState = WindowState.OLDEST;
95
+ const firstItem = this.getAt(0);
96
+ console.log(firstItem)
97
+ this.oldestId = firstItem ? this.getId(firstItem) : null;
98
+
99
+ await this.refreshFetchedState();
100
+
101
+ // LATEST state has priority over OLDEST
102
+ if (this.currentState === WindowState.PAST) {
103
+ this.currentState = WindowState.OLDEST;
104
+ }
105
+
91
106
  return;
92
107
  }
93
108
 
@@ -96,7 +111,9 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
96
111
  }
97
112
 
98
113
  public async fetchNext(): Promise<void> {
99
- this.throwIfFetchingInProgress();
114
+ if (this.fetchingState || this.hasLatest) {
115
+ return;
116
+ }
100
117
 
101
118
  this.fetchingState = WindowState.PAST;
102
119
 
@@ -116,6 +133,7 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
116
133
  if (result.length) {
117
134
  this.addItems(result, 'tail');
118
135
  await this.refreshFetchedState();
136
+ return;
119
137
  }
120
138
  }
121
139
 
@@ -146,12 +164,6 @@ export abstract class TraversableRemoteCollection<T> extends ObservableIndexedOb
146
164
  this.set(...result);
147
165
  }
148
166
 
149
- private throwIfFetchingInProgress(): void {
150
- if (this.fetchingState) {
151
- throw new Error(`Items fetching in progress: ${WindowState[this.fetchingState]}`);
152
- }
153
- }
154
-
155
167
  /**
156
168
  * Return array with messages of count that matching limit.
157
169
  */
@@ -122,10 +122,16 @@ test('history window - traverse forward', async () => {
122
122
  const window = new TestableHistoryWindow();
123
123
  window.limit = 5;
124
124
 
125
- await window.fetchNext(); // [7,8,9]
125
+ await window.fetchNext(); // []
126
+
127
+ expect(window.state).toEqual(WindowState.LIVE);
128
+ expect(window.items).toHaveLength(0);
129
+
130
+ await window.resetToLatest(); // [7,8,9]
126
131
 
127
132
  expect(window.state).toEqual(WindowState.LATEST);
128
133
  expect(window.items).toHaveLength(3);
134
+
129
135
  [7,8,9].forEach(id => expect(window.items.map(item => item.id)).toContain(id));
130
136
 
131
137
  await window.fetchPrevious(); // [4,5,6,7,8]
@@ -188,4 +194,25 @@ test('history window - trim messages window to limit', async () => {
188
194
  window.simulateNewMessageReceived();
189
195
 
190
196
  expect(window.items).toHaveLength(5);
197
+ });
198
+
199
+ test('history window - states priority', async () => {
200
+ const window = new TestableHistoryWindow();
201
+ window.limit = 10;
202
+
203
+ await window.resetToLatest(); // [7,8,9]
204
+
205
+ expect(window.state).toEqual(WindowState.LATEST);
206
+
207
+ await window.fetchPrevious(); // [4,5,6,7,8,9]
208
+ await window.fetchPrevious(); // [1,2,3,4,5,6,7,8,9]
209
+ await window.fetchPrevious(); // [0,1,2,3,4,5,6,7,8,9]
210
+
211
+ expect(window.state).toEqual(WindowState.LATEST);
212
+ expect(window.hasOldest).toBeFalsy();
213
+ expect(window.hasLatest).toBeTruthy();
214
+
215
+ await window.fetchPrevious();
216
+
217
+ expect(window.hasOldest).toBeTruthy();
191
218
  });