polfan-server-js-client 0.0.96 → 0.1.0
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 +1 -1
- package/build/index.js.map +1 -1
- package/build/types/AbstractClient.d.ts +78 -0
- package/build/types/EventTarget.d.ts +16 -0
- package/build/types/IndexedObjectCollection.d.ts +53 -0
- package/build/types/WebApiClient.d.ts +30 -0
- package/build/types/WebSocketClient.d.ts +36 -0
- package/build/types/WebSocketStateTracker.d.ts +54 -0
- package/build/types/index.d.ts +4 -68
- package/package.json +65 -64
- package/src/AbstractClient.ts +174 -0
- package/src/EventTarget.ts +39 -0
- package/src/IndexedObjectCollection.ts +209 -0
- package/src/{connections/WebApiConnection.ts → WebApiClient.ts} +94 -81
- package/src/WebSocketClient.ts +125 -0
- package/src/WebSocketStateTracker.ts +326 -0
- package/src/index.ts +13 -123
- package/build/types/Client.d.ts +0 -107
- package/build/types/ObservableInterface.d.ts +0 -16
- package/build/types/connections/ConnectionAssets.d.ts +0 -17
- package/build/types/connections/RestApiConnection.d.ts +0 -14
- package/build/types/connections/WebApiConnection.d.ts +0 -21
- package/build/types/connections/WebSocketConnection.d.ts +0 -22
- package/build/types/dtos/Dto.d.ts +0 -16
- package/build/types/dtos/Message.d.ts +0 -9
- package/build/types/dtos/Permission.d.ts +0 -7
- package/build/types/dtos/Role.d.ts +0 -7
- package/build/types/dtos/Room.d.ts +0 -10
- package/build/types/dtos/RoomMember.d.ts +0 -6
- package/build/types/dtos/RoomSummary.d.ts +0 -7
- package/build/types/dtos/Space.d.ts +0 -8
- package/build/types/dtos/SpaceMember.d.ts +0 -7
- package/build/types/dtos/Topic.d.ts +0 -7
- package/build/types/dtos/User.d.ts +0 -9
- package/build/types/dtos/UserState.d.ts +0 -8
- package/build/types/dtos/protocol/Envelope.d.ts +0 -7
- package/build/types/dtos/protocol/commands/AssignRole.d.ts +0 -7
- package/build/types/dtos/protocol/commands/CreateMessage.d.ts +0 -7
- package/build/types/dtos/protocol/commands/CreateRole.d.ts +0 -7
- package/build/types/dtos/protocol/commands/CreateRoom.d.ts +0 -7
- package/build/types/dtos/protocol/commands/CreateSpace.d.ts +0 -5
- package/build/types/dtos/protocol/commands/CreateTopic.d.ts +0 -7
- package/build/types/dtos/protocol/commands/DeassignRole.d.ts +0 -7
- package/build/types/dtos/protocol/commands/DeleteRole.d.ts +0 -6
- package/build/types/dtos/protocol/commands/DeleteRoom.d.ts +0 -5
- package/build/types/dtos/protocol/commands/DeleteSpace.d.ts +0 -5
- package/build/types/dtos/protocol/commands/DeleteTopic.d.ts +0 -5
- package/build/types/dtos/protocol/commands/GetComputedPermissions.d.ts +0 -8
- package/build/types/dtos/protocol/commands/GetRolePermissions.d.ts +0 -8
- package/build/types/dtos/protocol/commands/GetRoomMembers.d.ts +0 -5
- package/build/types/dtos/protocol/commands/GetSession.d.ts +0 -4
- package/build/types/dtos/protocol/commands/GetSpaceMembers.d.ts +0 -5
- package/build/types/dtos/protocol/commands/GetSpaceRooms.d.ts +0 -5
- package/build/types/dtos/protocol/commands/GetUserPermissions.d.ts +0 -8
- package/build/types/dtos/protocol/commands/JoinRoom.d.ts +0 -5
- package/build/types/dtos/protocol/commands/JoinSpace.d.ts +0 -5
- package/build/types/dtos/protocol/commands/LeaveRoom.d.ts +0 -5
- package/build/types/dtos/protocol/commands/LeaveSpace.d.ts +0 -5
- package/build/types/dtos/protocol/commands/SetRolePermissions.d.ts +0 -9
- package/build/types/dtos/protocol/commands/SetUserPermissions.d.ts +0 -9
- package/build/types/dtos/protocol/events/Bye.d.ts +0 -5
- package/build/types/dtos/protocol/events/Error.d.ts +0 -6
- package/build/types/dtos/protocol/events/NewMessage.d.ts +0 -6
- package/build/types/dtos/protocol/events/NewRole.d.ts +0 -7
- package/build/types/dtos/protocol/events/NewRoom.d.ts +0 -7
- package/build/types/dtos/protocol/events/NewTopic.d.ts +0 -7
- package/build/types/dtos/protocol/events/Ok.d.ts +0 -4
- package/build/types/dtos/protocol/events/Permissions.d.ts +0 -6
- package/build/types/dtos/protocol/events/RoleDeleted.d.ts +0 -6
- package/build/types/dtos/protocol/events/RoomDeleted.d.ts +0 -5
- package/build/types/dtos/protocol/events/RoomJoined.d.ts +0 -6
- package/build/types/dtos/protocol/events/RoomLeft.d.ts +0 -5
- package/build/types/dtos/protocol/events/RoomMemberJoined.d.ts +0 -6
- package/build/types/dtos/protocol/events/RoomMemberLeft.d.ts +0 -5
- package/build/types/dtos/protocol/events/RoomMembers.d.ts +0 -6
- package/build/types/dtos/protocol/events/Session.d.ts +0 -9
- package/build/types/dtos/protocol/events/SpaceDeleted.d.ts +0 -5
- package/build/types/dtos/protocol/events/SpaceJoined.d.ts +0 -6
- package/build/types/dtos/protocol/events/SpaceLeft.d.ts +0 -5
- package/build/types/dtos/protocol/events/SpaceMemberJoined.d.ts +0 -6
- package/build/types/dtos/protocol/events/SpaceMemberLeft.d.ts +0 -5
- package/build/types/dtos/protocol/events/SpaceMemberUpdate.d.ts +0 -6
- package/build/types/dtos/protocol/events/SpaceMembers.d.ts +0 -6
- package/build/types/dtos/protocol/events/SpaceRooms.d.ts +0 -6
- package/build/types/dtos/protocol/events/TopicDeleted.d.ts +0 -5
- package/build/types/protocol.d.ts +0 -102
- package/src/Client.ts +0 -267
- package/src/ObservableInterface.ts +0 -39
- package/src/connections/ConnectionAssets.ts +0 -20
- package/src/connections/RestApiConnection.ts +0 -47
- package/src/connections/WebSocketConnection.ts +0 -103
- package/src/dtos/Dto.ts +0 -45
- package/src/dtos/Message.ts +0 -16
- package/src/dtos/Permission.ts +0 -12
- package/src/dtos/Role.ts +0 -12
- package/src/dtos/Room.ts +0 -17
- package/src/dtos/RoomMember.ts +0 -13
- package/src/dtos/RoomSummary.ts +0 -12
- package/src/dtos/Space.ts +0 -15
- package/src/dtos/SpaceMember.ts +0 -14
- package/src/dtos/Topic.ts +0 -12
- package/src/dtos/User.ts +0 -15
- package/src/dtos/UserState.ts +0 -16
- package/src/dtos/protocol/Envelope.ts +0 -12
- package/src/dtos/protocol/commands/AssignRole.ts +0 -12
- package/src/dtos/protocol/commands/CreateMessage.ts +0 -12
- package/src/dtos/protocol/commands/CreateRole.ts +0 -12
- package/src/dtos/protocol/commands/CreateRoom.ts +0 -12
- package/src/dtos/protocol/commands/CreateSpace.ts +0 -10
- package/src/dtos/protocol/commands/CreateTopic.ts +0 -12
- package/src/dtos/protocol/commands/DeassignRole.ts +0 -12
- package/src/dtos/protocol/commands/DeleteRole.ts +0 -11
- package/src/dtos/protocol/commands/DeleteRoom.ts +0 -10
- package/src/dtos/protocol/commands/DeleteSpace.ts +0 -10
- package/src/dtos/protocol/commands/DeleteTopic.ts +0 -10
- package/src/dtos/protocol/commands/GetComputedPermissions.ts +0 -13
- package/src/dtos/protocol/commands/GetRolePermissions.ts +0 -13
- package/src/dtos/protocol/commands/GetRoomMembers.ts +0 -10
- package/src/dtos/protocol/commands/GetSession.ts +0 -8
- package/src/dtos/protocol/commands/GetSpaceMembers.ts +0 -10
- package/src/dtos/protocol/commands/GetSpaceRooms.ts +0 -10
- package/src/dtos/protocol/commands/GetUserPermissions.ts +0 -13
- package/src/dtos/protocol/commands/JoinRoom.ts +0 -10
- package/src/dtos/protocol/commands/JoinSpace.ts +0 -10
- package/src/dtos/protocol/commands/LeaveRoom.ts +0 -10
- package/src/dtos/protocol/commands/LeaveSpace.ts +0 -10
- package/src/dtos/protocol/commands/SetRolePermissions.ts +0 -16
- package/src/dtos/protocol/commands/SetUserPermissions.ts +0 -16
- package/src/dtos/protocol/events/Bye.ts +0 -10
- package/src/dtos/protocol/events/Error.ts +0 -11
- package/src/dtos/protocol/events/NewMessage.ts +0 -13
- package/src/dtos/protocol/events/NewRole.ts +0 -14
- package/src/dtos/protocol/events/NewRoom.ts +0 -14
- package/src/dtos/protocol/events/NewTopic.ts +0 -14
- package/src/dtos/protocol/events/Ok.ts +0 -8
- package/src/dtos/protocol/events/Permissions.ts +0 -13
- package/src/dtos/protocol/events/RoleDeleted.ts +0 -11
- package/src/dtos/protocol/events/RoomDeleted.ts +0 -10
- package/src/dtos/protocol/events/RoomJoined.ts +0 -13
- package/src/dtos/protocol/events/RoomLeft.ts +0 -10
- package/src/dtos/protocol/events/RoomMemberJoined.ts +0 -13
- package/src/dtos/protocol/events/RoomMemberLeft.ts +0 -10
- package/src/dtos/protocol/events/RoomMembers.ts +0 -13
- package/src/dtos/protocol/events/Session.ts +0 -17
- package/src/dtos/protocol/events/SpaceDeleted.ts +0 -10
- package/src/dtos/protocol/events/SpaceJoined.ts +0 -13
- package/src/dtos/protocol/events/SpaceLeft.ts +0 -10
- package/src/dtos/protocol/events/SpaceMemberJoined.ts +0 -13
- package/src/dtos/protocol/events/SpaceMemberLeft.ts +0 -10
- package/src/dtos/protocol/events/SpaceMemberUpdate.ts +0 -13
- package/src/dtos/protocol/events/SpaceMembers.ts +0 -13
- package/src/dtos/protocol/events/SpaceRooms.ts +0 -13
- package/src/dtos/protocol/events/TopicDeleted.ts +0 -10
- package/src/protocol.ts +0 -113
|
@@ -1,82 +1,95 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
import {AbstractClient, CommandResult, CommandsMap} from "./AbstractClient";
|
|
2
|
+
import {ObservableInterface} from "./EventTarget";
|
|
3
|
+
import {Envelope} from "pserv-ts-types";
|
|
4
|
+
|
|
5
|
+
export interface WebApiClientOptions {
|
|
6
|
+
url: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
temporaryNick?: string;
|
|
9
|
+
attemptsToSend?: number;
|
|
10
|
+
attemptDelayMs?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
enum WebApiClientEvent {
|
|
14
|
+
message = 'message',
|
|
15
|
+
error = 'error',
|
|
16
|
+
destroy = 'destroy',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class WebApiClient extends AbstractClient implements ObservableInterface {
|
|
20
|
+
public readonly Event = WebApiClientEvent;
|
|
21
|
+
|
|
22
|
+
protected sendStack: {data: any, attempts: number, lastTimeoutId: any}[];
|
|
23
|
+
|
|
24
|
+
public constructor(private readonly options: WebApiClientOptions) {
|
|
25
|
+
super();
|
|
26
|
+
if (!this.options.token && !this.options.temporaryNick) {
|
|
27
|
+
throw new Error('Token or temporary nick is required');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async send<CommandType extends keyof CommandsMap>(commandType: CommandType, commandData: CommandsMap[CommandType][0]):
|
|
32
|
+
Promise<CommandResult<CommandsMap[CommandType][1]>> {
|
|
33
|
+
const envelope = this.createEnvelope(commandType, commandData);
|
|
34
|
+
this.sendStack.push({data: envelope, attempts: 0, lastTimeoutId: null});
|
|
35
|
+
this.makeApiCall(this.sendStack.length - 1);
|
|
36
|
+
return this.createPromiseFromCommandEnvelope(envelope);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public destroy(): void {
|
|
40
|
+
// Cancel all awaiting requests
|
|
41
|
+
this.sendStack.forEach(item => {
|
|
42
|
+
if (item.lastTimeoutId) {
|
|
43
|
+
clearTimeout(item.lastTimeoutId);
|
|
44
|
+
}
|
|
45
|
+
this.awaitingResponse.delete(item.data.ref);
|
|
46
|
+
});
|
|
47
|
+
this.sendStack = [];
|
|
48
|
+
this.emit(this.Event.destroy, false);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected async onMessage(reqId: number, response: Response): Promise<void> {
|
|
52
|
+
this.sendStack.splice(reqId, 1);
|
|
53
|
+
const envelope: Envelope = await response.json();
|
|
54
|
+
this.handleIncomingEnvelope(envelope);
|
|
55
|
+
this.emit(envelope.type, envelope);
|
|
56
|
+
this.emit(this.Event.message, envelope);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected onError(reqId: number, body: string): void {
|
|
60
|
+
if (this.sendStack[reqId].attempts >= (this.options.attemptsToSend ?? 10)) {
|
|
61
|
+
this.sendStack.splice(reqId, 1);
|
|
62
|
+
this.handleEnvelopeSendError(this.sendStack[reqId].data, new Error(
|
|
63
|
+
`Cannot send ${body}; aborting after reaching the maximum connection errors`
|
|
64
|
+
));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.sendStack[reqId].lastTimeoutId = setTimeout(
|
|
68
|
+
() => this.makeApiCall(reqId),
|
|
69
|
+
this.options.attemptDelayMs ?? 3000
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected makeApiCall(reqId: number): void {
|
|
74
|
+
this.sendStack[reqId].attempts++;
|
|
75
|
+
const bodyJson = JSON.stringify(this.sendStack[reqId].data);
|
|
76
|
+
const headers: any = {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
Accept: 'application/json'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (this.options.token) {
|
|
82
|
+
headers.Authorization = `Bearer ${this.options.token}`;
|
|
83
|
+
} else if (this.options.temporaryNick) {
|
|
84
|
+
headers.Authorization = `Temp ${this.options.temporaryNick}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fetch(this.options.url, {
|
|
88
|
+
headers,
|
|
89
|
+
body: bodyJson,
|
|
90
|
+
method: 'POST',
|
|
91
|
+
})
|
|
92
|
+
.then(response => this.onMessage(reqId, response))
|
|
93
|
+
.catch(() => this.onError(reqId, bodyJson));
|
|
94
|
+
}
|
|
82
95
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {ObservableInterface} from "./EventTarget";
|
|
2
|
+
import {AbstractClient, CommandResult, CommandsMap} from "./AbstractClient";
|
|
3
|
+
import {Envelope} from "pserv-ts-types";
|
|
4
|
+
import {WebSocketStateTracker} from "./WebSocketStateTracker";
|
|
5
|
+
|
|
6
|
+
export interface WebSocketClientOptions {
|
|
7
|
+
url: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
temporaryNick?: string;
|
|
10
|
+
connectingTimeoutMs?: number;
|
|
11
|
+
awaitQueueSendDelayMs?: number;
|
|
12
|
+
stateTracking?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
enum WebSocketClientEvent {
|
|
16
|
+
connect = 'connect',
|
|
17
|
+
disconnect = 'disconnect',
|
|
18
|
+
message = 'message',
|
|
19
|
+
error = 'error',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class WebSocketClient extends AbstractClient implements ObservableInterface {
|
|
23
|
+
public readonly Event = WebSocketClientEvent;
|
|
24
|
+
public readonly state?: WebSocketStateTracker;
|
|
25
|
+
|
|
26
|
+
protected ws: WebSocket|null = null;
|
|
27
|
+
protected sendQueue: [commandType: keyof CommandsMap, commandData: any][] = [];
|
|
28
|
+
protected connectingTimeoutId: any;
|
|
29
|
+
protected authenticated: boolean;
|
|
30
|
+
protected authenticatedResolvers: [() => void, (error: Error) => void];
|
|
31
|
+
|
|
32
|
+
public constructor(private readonly options: WebSocketClientOptions) {
|
|
33
|
+
super();
|
|
34
|
+
if (!this.options.token && !this.options.temporaryNick) {
|
|
35
|
+
throw new Error('Token or temporary nick is required');
|
|
36
|
+
}
|
|
37
|
+
if (this.options.stateTracking ?? true) {
|
|
38
|
+
this.state = new WebSocketStateTracker(this);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async connect(): Promise<void> {
|
|
43
|
+
const authString = this.options.token ? `token=${this.options.token}` : `nick=${this.options.temporaryNick}`;
|
|
44
|
+
this.ws = new WebSocket(`${this.options.url}?${authString}`);
|
|
45
|
+
this.ws.onclose = ev => this.onClose(ev);
|
|
46
|
+
this.ws.onmessage = ev => this.onMessage(ev);
|
|
47
|
+
this.connectingTimeoutId = setTimeout(
|
|
48
|
+
() => this.triggerConnectionTimeout(),
|
|
49
|
+
this.options.connectingTimeoutMs ?? 10000
|
|
50
|
+
);
|
|
51
|
+
this.authenticated = false;
|
|
52
|
+
return new Promise((...args) => this.authenticatedResolvers = args);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public disconnect(): void {
|
|
56
|
+
this.sendQueue = [];
|
|
57
|
+
this.ws?.close();
|
|
58
|
+
this.ws = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async send<CommandType extends keyof CommandsMap>(commandType: CommandType, commandData: CommandsMap[CommandType][0]):
|
|
62
|
+
Promise<CommandResult<CommandsMap[CommandType][1]>> {
|
|
63
|
+
if (!this.ws || [this.ws.CLOSED, this.ws.CLOSING].includes(this.ws.readyState)) {
|
|
64
|
+
throw new Error('Cannot send; close or closing connection state');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.ws.readyState === this.ws.CONNECTING || !this.authenticated) {
|
|
68
|
+
this.sendQueue.push([commandType, commandData] as any);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this.ws.readyState !== this.ws.OPEN) {
|
|
73
|
+
throw new Error(`Invalid websocket state=${this.ws.readyState}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const envelope = this.createEnvelope<CommandsMap[CommandType][0]>(commandType, commandData);
|
|
77
|
+
this.ws.send(JSON.stringify(envelope));
|
|
78
|
+
return this.createPromiseFromCommandEnvelope<CommandType>(envelope);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private onMessage(event: MessageEvent): void {
|
|
82
|
+
const envelope: Envelope = JSON.parse(event.data);
|
|
83
|
+
// Login successfully
|
|
84
|
+
if (!this.authenticated) {
|
|
85
|
+
const isAuthenticated = envelope.type !== 'Error';
|
|
86
|
+
this.authenticated = isAuthenticated;
|
|
87
|
+
if (isAuthenticated) {
|
|
88
|
+
this.authenticatedResolvers[0]();
|
|
89
|
+
this.emit(this.Event.connect);
|
|
90
|
+
this.sendFromQueue();
|
|
91
|
+
} else {
|
|
92
|
+
this.authenticatedResolvers[1](envelope.data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.handleIncomingEnvelope(envelope);
|
|
96
|
+
this.emit(envelope.type, envelope);
|
|
97
|
+
this.emit(this.Event.message, envelope);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private onClose(event: CloseEvent): void {
|
|
101
|
+
clearTimeout(this.connectingTimeoutId);
|
|
102
|
+
const reconnect = event.code !== 1000; // Connection was closed because of error
|
|
103
|
+
if (reconnect) {
|
|
104
|
+
this.connect();
|
|
105
|
+
}
|
|
106
|
+
this.emit(this.Event.disconnect, reconnect);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private sendFromQueue(): void {
|
|
110
|
+
// Send awaiting data to server
|
|
111
|
+
let lastDelay = 0;
|
|
112
|
+
for (const dataIndex in this.sendQueue) {
|
|
113
|
+
const data = this.sendQueue[dataIndex];
|
|
114
|
+
setTimeout(() => this.send(...data), lastDelay);
|
|
115
|
+
lastDelay += this.options.awaitQueueSendDelayMs ?? 500;
|
|
116
|
+
}
|
|
117
|
+
this.sendQueue = [];
|
|
118
|
+
clearTimeout(this.connectingTimeoutId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private triggerConnectionTimeout(): void {
|
|
122
|
+
this.disconnect();
|
|
123
|
+
this.emit(this.Event.error, new Error('Connection timeout'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {WebSocketClient} from "./WebSocketClient";
|
|
2
|
+
import {IndexedCollection, ObservableIndexedObjectCollection} from "./IndexedObjectCollection";
|
|
3
|
+
import {
|
|
4
|
+
Message,
|
|
5
|
+
NewMessage,
|
|
6
|
+
NewRole,
|
|
7
|
+
NewRoom,
|
|
8
|
+
NewTopic, Role,
|
|
9
|
+
RoleDeleted,
|
|
10
|
+
Room,
|
|
11
|
+
RoomDeleted,
|
|
12
|
+
RoomJoined,
|
|
13
|
+
RoomLeft, RoomMember,
|
|
14
|
+
RoomMemberJoined,
|
|
15
|
+
RoomMemberLeft,
|
|
16
|
+
RoomMembers, RoomSummary,
|
|
17
|
+
Session,
|
|
18
|
+
Space,
|
|
19
|
+
SpaceDeleted,
|
|
20
|
+
SpaceJoined,
|
|
21
|
+
SpaceLeft, SpaceMember,
|
|
22
|
+
SpaceMemberJoined,
|
|
23
|
+
SpaceMemberLeft,
|
|
24
|
+
SpaceMembers,
|
|
25
|
+
SpaceMemberUpdate,
|
|
26
|
+
SpaceRooms, Topic,
|
|
27
|
+
TopicDeleted, User
|
|
28
|
+
} from "pserv-ts-types";
|
|
29
|
+
|
|
30
|
+
type Deferred = {resolver: () => void, promise: Promise<void>};
|
|
31
|
+
|
|
32
|
+
export class WebSocketStateTracker {
|
|
33
|
+
private readonly joinedSpaces = new ObservableIndexedObjectCollection<Space>('id');
|
|
34
|
+
private readonly joinedRooms = new ObservableIndexedObjectCollection<Room>('id');
|
|
35
|
+
private readonly spacesRoles = new IndexedCollection<string, ObservableIndexedObjectCollection<Role>>();
|
|
36
|
+
private readonly roomsTopics = new IndexedCollection<string, ObservableIndexedObjectCollection<Topic>>();
|
|
37
|
+
// Temporary not lazy loaded; server must implement GetTopicMessages command.
|
|
38
|
+
private readonly topicsMessages = new IndexedCollection<string, ObservableIndexedObjectCollection<Message>>();
|
|
39
|
+
|
|
40
|
+
// lazy loaded collections
|
|
41
|
+
private readonly spacesRooms = new IndexedCollection<string, ObservableIndexedObjectCollection<RoomSummary>>();
|
|
42
|
+
private readonly spacesMembers = new IndexedCollection<string, ObservableIndexedObjectCollection<SpaceMember>>();
|
|
43
|
+
private readonly roomsMembers = new IndexedCollection<string, ObservableIndexedObjectCollection<RoomMember>>();
|
|
44
|
+
private readonly deferredGetters = new IndexedCollection<string, Deferred>();
|
|
45
|
+
|
|
46
|
+
private reconnecting: boolean = false;
|
|
47
|
+
private me: User = null;
|
|
48
|
+
|
|
49
|
+
public constructor(private readonly client: WebSocketClient) {
|
|
50
|
+
this.bind();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async getMe(): Promise<User> {
|
|
54
|
+
await this.deferredGetterReadiness('session');
|
|
55
|
+
return this.me;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async getJoinedSpaces(): Promise<ObservableIndexedObjectCollection<Space>> {
|
|
59
|
+
await this.deferredGetterReadiness('session');
|
|
60
|
+
return this.joinedSpaces;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async getJoinedRooms(): Promise<ObservableIndexedObjectCollection<Room>> {
|
|
64
|
+
await this.deferredGetterReadiness('session');
|
|
65
|
+
return this.joinedRooms;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async getSpaceRoles(spaceId: string): Promise<ObservableIndexedObjectCollection<Role> | null> {
|
|
69
|
+
await this.deferredGetterReadiness('session');
|
|
70
|
+
return this.spacesRoles.get(spaceId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public async getRoomTopics(roomId: string): Promise<ObservableIndexedObjectCollection<Topic> | null> {
|
|
74
|
+
await this.deferredGetterReadiness('session');
|
|
75
|
+
return this.roomsTopics.get(roomId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async getTopicMessages(topicId: string): Promise<ObservableIndexedObjectCollection<Message> | null> {
|
|
79
|
+
return this.topicsMessages.get(topicId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public async getSpaceRooms(spaceId: string): Promise<ObservableIndexedObjectCollection<RoomSummary> | null> {
|
|
83
|
+
const deferredGetterName = `spaces-rooms-${spaceId}`;
|
|
84
|
+
if (!this.spacesRooms.has(spaceId) && !this.deferredGetters.has(deferredGetterName)) {
|
|
85
|
+
this.createDeferredGetter(deferredGetterName);
|
|
86
|
+
this.client.send('GetSpaceRooms', {id: spaceId});
|
|
87
|
+
}
|
|
88
|
+
await this.deferredGetterReadiness(deferredGetterName);
|
|
89
|
+
return this.spacesRooms.get(spaceId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async getSpaceMembers(spaceId: string): Promise<ObservableIndexedObjectCollection<SpaceMember> | null> {
|
|
93
|
+
const deferredGetterName = `spaces-members-${spaceId}`;
|
|
94
|
+
if (!this.spacesMembers.has(spaceId) && !this.deferredGetters.has(deferredGetterName)) {
|
|
95
|
+
this.createDeferredGetter(deferredGetterName);
|
|
96
|
+
this.client.send('GetSpaceMembers', {id: spaceId});
|
|
97
|
+
}
|
|
98
|
+
await this.deferredGetterReadiness(deferredGetterName);
|
|
99
|
+
return this.spacesMembers.get(spaceId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async getRoomMembers(roomId: string): Promise<ObservableIndexedObjectCollection<RoomMember> | null> {
|
|
103
|
+
const deferredGetterName = `rooms-members-${roomId}`;
|
|
104
|
+
if (!this.roomsMembers.has(roomId) && !this.deferredGetters.has(deferredGetterName)) {
|
|
105
|
+
this.createDeferredGetter(deferredGetterName);
|
|
106
|
+
this.client.send('GetRoomMembers', {id: roomId});
|
|
107
|
+
}
|
|
108
|
+
await this.deferredGetterReadiness(deferredGetterName);
|
|
109
|
+
return this.roomsMembers.get(roomId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async deferredGetterReadiness(name: string): Promise<void> {
|
|
113
|
+
if (this.deferredGetters.has(name)) {
|
|
114
|
+
await this.deferredGetters.get(name)?.promise;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private bind(): void {
|
|
119
|
+
this.client.on(this.client.Event.disconnect, ev => this.handleDisconnect(ev as any as boolean));
|
|
120
|
+
this.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
121
|
+
this.client.on('NewRole', ev => this.handleNewRole(ev));
|
|
122
|
+
this.client.on('NewRoom', ev => this.handleNewRoom(ev));
|
|
123
|
+
this.client.on('NewTopic', ev => this.handleNewTopic(ev));
|
|
124
|
+
this.client.on('RoleDeleted', ev => this.handleRoleDeleted(ev));
|
|
125
|
+
this.client.on('RoomDeleted', ev => this.handleRoomDeleted(ev));
|
|
126
|
+
this.client.on('RoomJoined', ev => this.handleRoomJoined(ev));
|
|
127
|
+
this.client.on('RoomLeft', ev => this.handleRoomLeft(ev));
|
|
128
|
+
this.client.on('RoomMemberJoined', ev => this.handleRoomMemberJoined(ev));
|
|
129
|
+
this.client.on('RoomMemberLeft', ev => this.handleRoomMemberLeft(ev));
|
|
130
|
+
this.client.on('RoomMembers', ev => this.handleRoomMembers(ev));
|
|
131
|
+
this.client.on('Session', ev => this.handleSession(ev));
|
|
132
|
+
this.client.on('SpaceDeleted', ev => this.handleSpaceDeleted(ev));
|
|
133
|
+
this.client.on('SpaceJoined', ev => this.handleSpaceJoined(ev));
|
|
134
|
+
this.client.on('SpaceLeft', ev => this.handleSpaceLeft(ev));
|
|
135
|
+
this.client.on('SpaceMemberJoined', ev => this.handleSpaceMemberJoined(ev));
|
|
136
|
+
this.client.on('SpaceMemberLeft', ev => this.handleSpaceMemberLeft(ev));
|
|
137
|
+
this.client.on('SpaceMembers', ev => this.handleSpaceMembers(ev));
|
|
138
|
+
this.client.on('SpaceRooms', ev => this.handleSpaceRooms(ev));
|
|
139
|
+
this.client.on('SpaceMemberUpdate', ev => this.handleSpaceMemberUpdate(ev));
|
|
140
|
+
this.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private createDeferredGetter(name: string): void {
|
|
144
|
+
if (this.deferredGetters.has(name)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const deferred: Deferred = {promise: undefined, resolver: undefined};
|
|
148
|
+
deferred.promise = new Promise(resolve => deferred.resolver = resolve);
|
|
149
|
+
this.deferredGetters.set([name, deferred]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private handleDisconnect(reconnect: boolean): void {
|
|
153
|
+
if (reconnect) {
|
|
154
|
+
this.reconnecting = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private handleNewMessage(ev: NewMessage): void {
|
|
160
|
+
this.topicsMessages.get(ev.topicId).set(ev.message);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private handleNewRole(ev: NewRole): void {
|
|
164
|
+
const collection = this.spacesRoles.get(ev.spaceId);
|
|
165
|
+
collection.set(ev.role);
|
|
166
|
+
this.joinedSpaces.get(ev.spaceId).roles = collection.items;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private handleNewRoom(ev: NewRoom): void {
|
|
170
|
+
this.spacesRooms.get(ev.spaceId)?.set(ev.summary);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private handleNewTopic(ev: NewTopic): void {
|
|
174
|
+
const collection = this.roomsTopics.get(ev.roomId);
|
|
175
|
+
collection.set(ev.topic);
|
|
176
|
+
this.joinedRooms.get(ev.roomId).topics = collection.items;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private handleRoleDeleted(ev: RoleDeleted): void {
|
|
180
|
+
const collection = this.spacesRoles.get(ev.spaceId);
|
|
181
|
+
collection.delete(ev.id);
|
|
182
|
+
this.joinedSpaces.get(ev.spaceId).roles = collection.items;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private handleRoomDeleted(ev: RoomDeleted): void {
|
|
186
|
+
if (ev.spaceId) {
|
|
187
|
+
this.spacesRooms.get(ev.spaceId).delete(ev.id);
|
|
188
|
+
}
|
|
189
|
+
this.joinedRooms.delete(ev.id);
|
|
190
|
+
this.roomsMembers.delete(ev.id);
|
|
191
|
+
this.roomsTopics.delete(ev.id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private handleRoomJoined(ev: RoomJoined): void {
|
|
195
|
+
this.addJoinedRooms(ev.room);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private addJoinedRooms(...rooms: Room[]): void {
|
|
199
|
+
this.roomsTopics.set(...rooms.map<[string, ObservableIndexedObjectCollection<Topic>]>(room => [
|
|
200
|
+
room.id,
|
|
201
|
+
new ObservableIndexedObjectCollection<Topic>('id', room.topics)
|
|
202
|
+
]));
|
|
203
|
+
this.joinedRooms.set(...rooms);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private handleRoomLeft(ev: RoomLeft): void {
|
|
207
|
+
this.joinedRooms.delete(ev.id);
|
|
208
|
+
this.roomsMembers.delete(ev.id);
|
|
209
|
+
this.roomsTopics.delete(ev.id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private handleRoomMemberJoined(ev: RoomMemberJoined): void {
|
|
213
|
+
if (this.roomsMembers.has(ev.roomId)) {
|
|
214
|
+
this.roomsMembers.get(ev.roomId).set(ev.member);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private handleRoomMemberLeft(ev: RoomMemberLeft): void {
|
|
219
|
+
if (this.roomsMembers.has(ev.roomId)) {
|
|
220
|
+
this.roomsMembers.get(ev.roomId).delete(ev.userId);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private handleRoomMembers(ev: RoomMembers): void {
|
|
225
|
+
if (!this.roomsMembers.has(ev.id)) {
|
|
226
|
+
this.roomsMembers.set([
|
|
227
|
+
ev.id,
|
|
228
|
+
new ObservableIndexedObjectCollection(
|
|
229
|
+
member => member?.user.id ?? member.spaceMember.user.id,
|
|
230
|
+
ev.members,
|
|
231
|
+
)
|
|
232
|
+
]);
|
|
233
|
+
this.deferredGetters.get(`rooms-members-${ev.id}`)?.resolver();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private handleSession(ev: Session): void {
|
|
238
|
+
this.me = ev.user;
|
|
239
|
+
|
|
240
|
+
if (this.me && !this.reconnecting) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.reconnecting = false;
|
|
245
|
+
|
|
246
|
+
this.joinedRooms.deleteAll();
|
|
247
|
+
this.roomsTopics.deleteAll();
|
|
248
|
+
this.roomsMembers.deleteAll();
|
|
249
|
+
this.joinedSpaces.deleteAll();
|
|
250
|
+
this.spacesRoles.deleteAll();
|
|
251
|
+
this.spacesRooms.deleteAll();
|
|
252
|
+
this.spacesMembers.deleteAll();
|
|
253
|
+
|
|
254
|
+
this.addJoinedRooms(...ev.state.rooms);
|
|
255
|
+
this.addJoinedSpaces(...ev.state.spaces);
|
|
256
|
+
|
|
257
|
+
this.deferredGetters.get('session')?.resolver();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private handleSpaceDeleted(ev: SpaceDeleted): void {
|
|
261
|
+
this.spacesRoles.delete(ev.id);
|
|
262
|
+
this.spacesMembers.delete(ev.id);
|
|
263
|
+
this.spacesRooms.delete(ev.id);
|
|
264
|
+
this.joinedSpaces.delete(ev.id);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private handleSpaceJoined(ev: SpaceJoined): void {
|
|
268
|
+
this.addJoinedSpaces(ev.space);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private addJoinedSpaces(...spaces: Space[]): void {
|
|
272
|
+
this.spacesRoles.set(...(spaces.map(space => [
|
|
273
|
+
space.id,
|
|
274
|
+
new ObservableIndexedObjectCollection<Role>('id', space.roles)
|
|
275
|
+
]) as [string, ObservableIndexedObjectCollection<Role>][]));
|
|
276
|
+
this.joinedSpaces.set(...spaces);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private handleSpaceLeft(ev: SpaceLeft): void {
|
|
280
|
+
this.spacesRoles.delete(ev.id);
|
|
281
|
+
this.spacesMembers.delete(ev.id);
|
|
282
|
+
this.spacesRooms.delete(ev.id);
|
|
283
|
+
this.joinedSpaces.delete(ev.id);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private handleSpaceMemberJoined(ev: SpaceMemberJoined): void {
|
|
287
|
+
if (this.spacesMembers.has(ev.spaceId)) {
|
|
288
|
+
this.spacesMembers.get(ev.spaceId).set(ev.member);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private handleSpaceMemberLeft(ev: SpaceMemberLeft): void {
|
|
293
|
+
if (this.spacesMembers.has(ev.spaceId)) {
|
|
294
|
+
this.spacesMembers.get(ev.spaceId).delete(ev.userId);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private handleSpaceMembers(ev: SpaceMembers): void {
|
|
299
|
+
if (!this.spacesMembers.has(ev.id)) {
|
|
300
|
+
this.spacesMembers.set([
|
|
301
|
+
ev.id,
|
|
302
|
+
new ObservableIndexedObjectCollection(member => member?.user.id, ev.members)
|
|
303
|
+
]);
|
|
304
|
+
this.deferredGetters.get(`spaces-members-${ev.id}`)?.resolver();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private handleSpaceRooms(ev: SpaceRooms): void {
|
|
309
|
+
if (!this.spacesRooms.has(ev.id)) {
|
|
310
|
+
this.spacesRooms.set([ev.id, new ObservableIndexedObjectCollection('id', ev.summaries)]);
|
|
311
|
+
this.deferredGetters.get(`spaces-rooms-${ev.id}`)?.resolver();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private handleSpaceMemberUpdate(ev: SpaceMemberUpdate): void {
|
|
316
|
+
if (this.spacesMembers.has(ev.spaceId)) {
|
|
317
|
+
this.spacesMembers.get(ev.spaceId).set(ev.member);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private handleTopicDeleted(ev: TopicDeleted): void {
|
|
322
|
+
const collection = this.roomsTopics.get(ev.roomId);
|
|
323
|
+
collection.delete(ev.id);
|
|
324
|
+
this.joinedRooms.get(ev.roomId).topics = collection.items;
|
|
325
|
+
}
|
|
326
|
+
}
|