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.
- package/build/index.js +91 -73
- package/build/index.js.map +1 -1
- package/build/types/WebSocketChatClient.d.ts +3 -1
- package/build/types/state-tracker/TopicHistoryWindow.d.ts +3 -1
- package/package.json +1 -1
- package/src/WebSocketChatClient.ts +13 -7
- package/src/state-tracker/TopicHistoryWindow.ts +26 -14
- package/tests/history-window.test.ts +28 -1
|
@@ -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: [
|
|
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
|
@@ -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: [
|
|
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(
|
|
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
|
-
|
|
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
|
|
115
|
-
setTimeout(() => this.
|
|
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
|
|
47
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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(); // [
|
|
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
|
});
|