polfan-server-js-client 0.2.3 → 0.2.6
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/.idea/copilot.data.migration.agent.xml +6 -0
- package/.idea/copilot.data.migration.ask.xml +6 -0
- package/.idea/copilot.data.migration.ask2agent.xml +6 -0
- package/.idea/copilot.data.migration.edit.xml +6 -0
- package/.idea/workspace.xml +372 -153
- package/babel.config.js +4 -5
- package/build/index.cjs.js +4538 -1816
- package/build/index.cjs.js.map +1 -1
- package/build/index.umd.js +1 -1
- package/build/index.umd.js.map +1 -1
- package/build/types/AbstractChatClient.d.ts +12 -2
- package/build/types/FilesClient.d.ts +7 -6
- package/build/types/IndexedObjectCollection.d.ts +4 -3
- package/build/types/Permissions.d.ts +4 -0
- package/build/types/WebSocketChatClient.d.ts +2 -0
- package/build/types/state-tracker/ChatStateTracker.d.ts +5 -0
- package/build/types/state-tracker/RelationshipsManager.d.ts +15 -0
- package/build/types/state-tracker/RoomMessagesHistory.d.ts +2 -0
- package/build/types/state-tracker/SpacesManager.d.ts +1 -0
- package/build/types/state-tracker/TopicHistoryWindow.d.ts +23 -5
- package/build/types/state-tracker/UsersManager.d.ts +3 -1
- package/build/types/types/src/index.d.ts +12 -3
- package/build/types/types/src/schemes/Emoticon.d.ts +1 -0
- package/build/types/types/src/schemes/Message.d.ts +1 -1
- package/build/types/types/src/schemes/Room.d.ts +2 -0
- package/build/types/types/src/schemes/RoomHistory.d.ts +5 -0
- package/build/types/types/src/schemes/RoomSummary.d.ts +1 -0
- package/build/types/types/src/schemes/SpaceSummary.d.ts +1 -0
- package/build/types/types/src/schemes/User.d.ts +2 -2
- package/build/types/types/src/schemes/UserRelationship.d.ts +6 -0
- package/build/types/types/src/schemes/commands/CreateMessage.d.ts +2 -0
- package/build/types/types/src/schemes/commands/CreateRelationship.d.ts +5 -0
- package/build/types/types/src/schemes/commands/CreateTopic.d.ts +5 -2
- package/build/types/types/src/schemes/commands/DeleteRelationship.d.ts +5 -0
- package/build/types/types/src/schemes/commands/GetRelationships.d.ts +2 -0
- package/build/types/types/src/schemes/commands/UpdateRoom.d.ts +2 -0
- package/build/types/types/src/schemes/commands/UpdateRoomMember.d.ts +7 -0
- package/build/types/types/src/schemes/commands/UpdateSpaceMember.d.ts +5 -0
- package/build/types/types/src/schemes/events/NewRelationship.d.ts +4 -0
- package/build/types/types/src/schemes/events/RelationshipDeleted.d.ts +4 -0
- package/build/types/types/src/schemes/events/Relationships.d.ts +4 -0
- package/build/types/types/src/schemes/events/RoomSummaryUpdated.d.ts +7 -0
- package/build/types/types/src/schemes/events/Session.d.ts +1 -0
- package/package.json +15 -28
- package/src/AbstractChatClient.ts +28 -4
- package/src/FilesClient.ts +26 -13
- package/src/IndexedObjectCollection.ts +26 -10
- package/src/Permissions.ts +1 -0
- package/src/WebSocketChatClient.ts +19 -11
- package/src/state-tracker/ChatStateTracker.ts +22 -6
- package/src/state-tracker/EmoticonsManager.ts +6 -4
- package/src/state-tracker/MessagesManager.ts +3 -3
- package/src/state-tracker/RelationshipsManager.ts +68 -0
- package/src/state-tracker/RoomMessagesHistory.ts +20 -3
- package/src/state-tracker/RoomsManager.ts +30 -7
- package/src/state-tracker/SpacesManager.ts +28 -1
- package/src/state-tracker/TopicHistoryWindow.ts +94 -23
- package/src/state-tracker/UsersManager.ts +16 -6
- package/src/types/src/index.ts +26 -5
- package/src/types/src/schemes/Emoticon.ts +1 -0
- package/src/types/src/schemes/Message.ts +1 -1
- package/src/types/src/schemes/Room.ts +2 -0
- package/src/types/src/schemes/RoomHistory.ts +6 -0
- package/src/types/src/schemes/RoomSummary.ts +1 -0
- package/src/types/src/schemes/SpaceSummary.ts +1 -0
- package/src/types/src/schemes/User.ts +2 -2
- package/src/types/src/schemes/UserRelationship.ts +8 -0
- package/src/types/src/schemes/commands/CreateMessage.ts +2 -0
- package/src/types/src/schemes/commands/CreateRelationship.ts +6 -0
- package/src/types/src/schemes/commands/CreateTopic.ts +6 -2
- package/src/types/src/schemes/commands/DeleteRelationship.ts +6 -0
- package/src/types/src/schemes/commands/GetRelationships.ts +3 -0
- package/src/types/src/schemes/commands/UpdateRoom.ts +2 -0
- package/src/types/src/schemes/commands/UpdateRoomMember.ts +7 -0
- package/src/types/src/schemes/commands/UpdateSpaceMember.ts +5 -0
- package/src/types/src/schemes/events/NewRelationship.ts +5 -0
- package/src/types/src/schemes/events/RelationshipDeleted.ts +5 -0
- package/src/types/src/schemes/events/Relationships.ts +5 -0
- package/src/types/src/schemes/events/RoomSummaryUpdated.ts +8 -0
- package/src/types/src/schemes/events/Session.ts +1 -0
- package/tests/history-window.test.ts +6 -1
- package/webpack.config.browser.js +2 -24
- package/webpack.config.node.js +2 -14
- package/.eslintignore +0 -0
- package/.eslintrc.json +0 -0
- package/src/types/src/schemes/commands/SetCustomNick.ts +0 -5
|
@@ -82,14 +82,30 @@ import {
|
|
|
82
82
|
GetEmoticons,
|
|
83
83
|
Emoticons,
|
|
84
84
|
EmoticonDeleted,
|
|
85
|
-
NewEmoticon,
|
|
85
|
+
NewEmoticon,
|
|
86
|
+
Bans,
|
|
87
|
+
GetBans,
|
|
88
|
+
Ban,
|
|
89
|
+
Unban,
|
|
90
|
+
Kick,
|
|
91
|
+
ClientData,
|
|
92
|
+
GetClientData,
|
|
93
|
+
SetClientData,
|
|
86
94
|
GetRoomSummary,
|
|
87
95
|
GetSpaceSummary,
|
|
88
96
|
RoomSummaryEvent,
|
|
89
97
|
SpaceSummaryEvent,
|
|
90
|
-
|
|
98
|
+
UpdateSpaceMember,
|
|
99
|
+
Relationships,
|
|
100
|
+
RelationshipDeleted,
|
|
101
|
+
NewRelationship,
|
|
102
|
+
DeleteRelationship,
|
|
103
|
+
CreateRelationship,
|
|
104
|
+
RoomSummaryUpdated,
|
|
91
105
|
} from "./types/src/index";
|
|
92
106
|
import {EventTarget} from "./EventTarget";
|
|
107
|
+
import {GetRelationships} from "./types/src/schemes/commands/GetRelationships";
|
|
108
|
+
import {UpdateRoomMember} from "./types/src/schemes/commands/UpdateRoomMember";
|
|
93
109
|
|
|
94
110
|
type ArrayOfPromiseResolvers = [(value: any) => void, (reason?: any) => void];
|
|
95
111
|
|
|
@@ -139,7 +155,7 @@ export abstract class AbstractChatClient extends EventTarget {
|
|
|
139
155
|
if (!this.awaitingResponse.has(envelope.ref)) {
|
|
140
156
|
return;
|
|
141
157
|
}
|
|
142
|
-
this.awaitingResponse.get(envelope.ref)[
|
|
158
|
+
this.awaitingResponse.get(envelope.ref)[1](error);
|
|
143
159
|
this.awaitingResponse.delete(envelope.ref);
|
|
144
160
|
}
|
|
145
161
|
}
|
|
@@ -165,6 +181,9 @@ export type EventsMap = {
|
|
|
165
181
|
Emoticons: Emoticons,
|
|
166
182
|
Bans: Bans,
|
|
167
183
|
ClientData: ClientData,
|
|
184
|
+
NewRelationship: NewRelationship,
|
|
185
|
+
RelationshipDeleted: RelationshipDeleted,
|
|
186
|
+
Relationships: Relationships,
|
|
168
187
|
// Space events
|
|
169
188
|
DiscoverableSpaces: DiscoverableSpaces,
|
|
170
189
|
SpaceJoined: SpaceJoined,
|
|
@@ -191,6 +210,7 @@ export type EventsMap = {
|
|
|
191
210
|
RoomDeleted: RoomDeleted,
|
|
192
211
|
RoomUpdated: RoomUpdated,
|
|
193
212
|
RoomSummaryEvent: RoomSummaryEvent,
|
|
213
|
+
RoomSummaryUpdated: RoomSummaryUpdated,
|
|
194
214
|
// Topic events
|
|
195
215
|
NewTopic: NewTopic,
|
|
196
216
|
TopicDeleted: TopicDeleted,
|
|
@@ -227,6 +247,9 @@ export type CommandsMap = {
|
|
|
227
247
|
Kick: [Kick, EventsMap['Ok']],
|
|
228
248
|
GetClientData: [GetClientData, EventsMap['ClientData']],
|
|
229
249
|
SetClientData: [SetClientData, EventsMap['Ok']],
|
|
250
|
+
DeleteRelationship: [DeleteRelationship, EventsMap['RelationshipDeleted']],
|
|
251
|
+
CreateRelationship: [CreateRelationship, EventsMap['NewRelationship']],
|
|
252
|
+
GetRelationships: [GetRelationships, EventsMap['Relationships']],
|
|
230
253
|
// Space commands
|
|
231
254
|
GetDiscoverableSpaces: [GetDiscoverableSpaces, EventsMap['DiscoverableSpaces']],
|
|
232
255
|
JoinSpace: [JoinSpace, EventsMap['SpaceJoined']],
|
|
@@ -242,7 +265,7 @@ export type CommandsMap = {
|
|
|
242
265
|
AssignRole: [AssignRole, EventsMap['SpaceMemberUpdated'] | EventsMap['RoomMemberUpdated']],
|
|
243
266
|
DeassignRole: [DeassignRole, EventsMap['SpaceMemberUpdated'] | EventsMap['RoomMemberUpdated']],
|
|
244
267
|
GetSpaceSummary: [GetSpaceSummary, EventsMap['SpaceSummaryEvent']],
|
|
245
|
-
|
|
268
|
+
UpdateSpaceMember: [UpdateSpaceMember, EventsMap['SpaceMemberUpdated']],
|
|
246
269
|
// Room commands
|
|
247
270
|
JoinRoom: [JoinRoom, EventsMap['RoomJoined']],
|
|
248
271
|
LeaveRoom: [LeaveRoom, EventsMap['RoomLeft']],
|
|
@@ -251,6 +274,7 @@ export type CommandsMap = {
|
|
|
251
274
|
UpdateRoom: [UpdateRoom, EventsMap['RoomUpdated']],
|
|
252
275
|
GetRoomMembers: [GetRoomMembers, EventsMap['RoomMembers']],
|
|
253
276
|
GetRoomSummary: [GetRoomSummary, EventsMap['RoomSummaryEvent']],
|
|
277
|
+
UpdateRoomMember: [UpdateRoomMember, EventsMap['RoomMemberUpdated']],
|
|
254
278
|
// Topic commands
|
|
255
279
|
CreateTopic: [CreateTopic, EventsMap['NewTopic']],
|
|
256
280
|
DeleteTopic: [DeleteTopic, EventsMap['TopicDeleted']],
|
package/src/FilesClient.ts
CHANGED
|
@@ -3,28 +3,41 @@ import {AbstractRestClient, RestClientResponse} from "./AbstractRestClient";
|
|
|
3
3
|
export interface File {
|
|
4
4
|
id: string;
|
|
5
5
|
url: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
mime_type: string;
|
|
6
|
+
name: string;
|
|
7
|
+
mime: string;
|
|
9
8
|
size: number;
|
|
10
|
-
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export class FilesClient extends AbstractRestClient {
|
|
14
|
-
protected defaultUrl: string = 'https://
|
|
14
|
+
protected defaultUrl: string = 'https://files.devana.pl';
|
|
15
15
|
|
|
16
|
-
public async uploadFile(file:
|
|
17
|
-
const
|
|
18
|
-
|
|
16
|
+
public async uploadFile(file: globalThis.File | Blob): Promise<RestClientResponse<File>> {
|
|
17
|
+
const name = encodeURIComponent((file as globalThis.File).name ?? '');
|
|
18
|
+
let headers = {
|
|
19
|
+
...this.getAuthHeaders(),
|
|
20
|
+
Accept: 'application/json',
|
|
21
|
+
'Content-Disposition': `attachment; filename="${name}"`,
|
|
22
|
+
'Content-Length': file.size
|
|
23
|
+
};
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const response = await fetch(this.getUrl('files'), {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: file,
|
|
28
|
+
headers
|
|
29
|
+
});
|
|
23
30
|
|
|
24
31
|
return this.convertFetchResponse<File>(response);
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
public async
|
|
28
|
-
return this.send('GET', '/' + id);
|
|
34
|
+
public async getFileMeta(id: string): Promise<RestClientResponse<File>> {
|
|
35
|
+
return this.send('GET', 'files/' + id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async getFileMetaBulk(ids: string[]): Promise<RestClientResponse<File[]>> {
|
|
39
|
+
const searchParams = new URLSearchParams();
|
|
40
|
+
ids.forEach(id => searchParams.append('id[]', id));
|
|
41
|
+
return this.send('GET', 'files?' + searchParams);
|
|
29
42
|
}
|
|
30
43
|
}
|
|
@@ -2,16 +2,11 @@ import {EventTarget, ObservableInterface} from "./EventTarget";
|
|
|
2
2
|
|
|
3
3
|
export class IndexedCollection<KeyT, ValueT> {
|
|
4
4
|
protected _items: Map<KeyT, ValueT> = new Map();
|
|
5
|
-
protected _mutationCounter: number = 0;
|
|
6
5
|
|
|
7
6
|
public constructor(items: [key: KeyT, value: ValueT][] = []) {
|
|
8
7
|
this.set(...items);
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
public get mutationCounter(): number {
|
|
12
|
-
return this._mutationCounter;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
10
|
public get items(): Map<KeyT, ValueT> {
|
|
16
11
|
return this._items;
|
|
17
12
|
}
|
|
@@ -21,7 +16,6 @@ export class IndexedCollection<KeyT, ValueT> {
|
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
public set(...items: [KeyT, ValueT][]): void {
|
|
24
|
-
this._mutationCounter++;
|
|
25
19
|
for (const item of items) {
|
|
26
20
|
this._items.set(item[0], item[1]);
|
|
27
21
|
}
|
|
@@ -58,6 +52,12 @@ export class IndexedCollection<KeyT, ValueT> {
|
|
|
58
52
|
}
|
|
59
53
|
return result;
|
|
60
54
|
}
|
|
55
|
+
|
|
56
|
+
public createMirror(): IndexedCollection<KeyT, ValueT> {
|
|
57
|
+
const copy = new IndexedCollection<KeyT, ValueT>();
|
|
58
|
+
copy._items = this._items;
|
|
59
|
+
return copy;
|
|
60
|
+
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export class IndexedObjectCollection<T> {
|
|
@@ -79,10 +79,6 @@ export class IndexedObjectCollection<T> {
|
|
|
79
79
|
return this._items.length;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
public get mutationCounter(): number {
|
|
83
|
-
return this._items.mutationCounter;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
public set(...items: T[]): void {
|
|
87
83
|
this._items.set(...(items.map(item => [this.getId(item), item] as [string, T])));
|
|
88
84
|
}
|
|
@@ -120,6 +116,12 @@ export class IndexedObjectCollection<T> {
|
|
|
120
116
|
return result;
|
|
121
117
|
}
|
|
122
118
|
|
|
119
|
+
public createMirror(): IndexedObjectCollection<T> {
|
|
120
|
+
const copy = new IndexedObjectCollection<T>(this.id);
|
|
121
|
+
copy._items = this._items;
|
|
122
|
+
return copy;
|
|
123
|
+
}
|
|
124
|
+
|
|
123
125
|
protected getId(item: T): any {
|
|
124
126
|
return typeof this.id === 'function' ? this.id(item) : item[this.id];
|
|
125
127
|
}
|
|
@@ -161,6 +163,13 @@ export class ObservableIndexedCollection<KeyT, ValueT> extends IndexedCollection
|
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
public createMirror(): ObservableIndexedCollection<KeyT, ValueT> {
|
|
167
|
+
const copy = new ObservableIndexedCollection<KeyT, ValueT>();
|
|
168
|
+
copy.eventTarget = this.eventTarget;
|
|
169
|
+
copy._items = this._items;
|
|
170
|
+
return copy;
|
|
171
|
+
}
|
|
172
|
+
|
|
164
173
|
public on(eventName: 'change', handler: (ev?: ObservableCollectionEvent<KeyT>) => void): this {
|
|
165
174
|
this.eventTarget.on(eventName, handler);
|
|
166
175
|
return this;
|
|
@@ -211,6 +220,13 @@ export class ObservableIndexedObjectCollection<T> extends IndexedObjectCollectio
|
|
|
211
220
|
}
|
|
212
221
|
}
|
|
213
222
|
|
|
223
|
+
public createMirror(): IndexedObjectCollection<T> {
|
|
224
|
+
const copy = new ObservableIndexedObjectCollection<T>(this.id);
|
|
225
|
+
copy.eventTarget = this.eventTarget;
|
|
226
|
+
copy._items = this._items;
|
|
227
|
+
return copy;
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
public on(eventName: 'change', handler: (ev?: ObservableCollectionEvent<string>) => void): this {
|
|
215
231
|
this.eventTarget.on(eventName, handler);
|
|
216
232
|
return this;
|
package/src/Permissions.ts
CHANGED
|
@@ -30,6 +30,7 @@ export class Permissions {
|
|
|
30
30
|
ManageBan: {value: 1 << 15, maxLayer: Layer.Room},
|
|
31
31
|
Kick: {value: 1 << 16, maxLayer: Layer.Room},
|
|
32
32
|
ChangeOwnNick: {value: 1 << 17, maxLayer: Layer.Space},
|
|
33
|
+
ChangeOwnColor: {value: 1 << 18, maxLayer: Layer.Room},
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
public static getNames(): (keyof typeof this.list)[] {
|
|
@@ -60,28 +60,28 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
|
|
|
60
60
|
|
|
61
61
|
public async send<CommandType extends keyof CommandsMap>(commandType: CommandType, commandData: CommandsMap[CommandType][0]):
|
|
62
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
63
|
const envelope = this.createEnvelope<CommandsMap[CommandType][0]>(commandType, commandData);
|
|
68
64
|
const promise = this.createPromiseFromCommandEnvelope<CommandType>(envelope);
|
|
69
65
|
|
|
70
|
-
if (this.
|
|
66
|
+
if (this.isPendingReadyWsState()) {
|
|
71
67
|
this.sendQueue.push(envelope);
|
|
72
68
|
return promise;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
if (this.ws.readyState !== this.ws.OPEN) {
|
|
76
|
-
throw new Error(`Invalid websocket state=${this.ws.readyState}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
71
|
this.sendEnvelope(envelope);
|
|
80
72
|
return promise;
|
|
81
73
|
}
|
|
82
74
|
|
|
83
75
|
private sendEnvelope(envelope: Envelope): void {
|
|
84
|
-
this.
|
|
76
|
+
if (this.isReadyToSendWsState()) {
|
|
77
|
+
this.ws.send(JSON.stringify(envelope));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.handleEnvelopeSendError(
|
|
82
|
+
envelope,
|
|
83
|
+
new Error(`Cannot send; invalid websocket state=${this.ws?.readyState ?? '[no connection]'}`)
|
|
84
|
+
);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
private onMessage(event: MessageEvent): void {
|
|
@@ -108,7 +108,7 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
|
|
|
108
108
|
clearTimeout(this.connectingTimeoutId);
|
|
109
109
|
const reconnect = event.code !== 1000; // Connection was closed because of error
|
|
110
110
|
if (reconnect) {
|
|
111
|
-
this.connect();
|
|
111
|
+
void this.connect();
|
|
112
112
|
}
|
|
113
113
|
this.emit(this.Event.disconnect, reconnect);
|
|
114
114
|
}
|
|
@@ -129,4 +129,12 @@ export class WebSocketChatClient extends AbstractChatClient implements Observabl
|
|
|
129
129
|
this.disconnect();
|
|
130
130
|
this.emit(this.Event.error, new Error('Connection timeout'));
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
private isPendingReadyWsState(): boolean {
|
|
134
|
+
return this.ws && this.ws.readyState === this.ws.CONNECTING || !this.authenticated;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private isReadyToSendWsState(): boolean {
|
|
138
|
+
return this.ws && this.ws.readyState === this.ws.OPEN && this.authenticated;
|
|
139
|
+
}
|
|
132
140
|
}
|
|
@@ -6,38 +6,54 @@ import {PermissionsManager} from "./PermissionsManager";
|
|
|
6
6
|
import {DeferredTask} from "./AsyncUtils";
|
|
7
7
|
import {EmoticonsManager} from "./EmoticonsManager";
|
|
8
8
|
import {UsersManager} from "./UsersManager";
|
|
9
|
+
import {RelationshipsManager} from "./RelationshipsManager";
|
|
9
10
|
|
|
10
11
|
export class ChatStateTracker {
|
|
12
|
+
public readonly client: WebSocketChatClient;
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* State of your permissions.
|
|
13
16
|
*/
|
|
14
|
-
public readonly permissions
|
|
17
|
+
public readonly permissions: PermissionsManager;
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* State of the rooms you are in.
|
|
18
21
|
*/
|
|
19
|
-
public readonly rooms: RoomsManager
|
|
22
|
+
public readonly rooms: RoomsManager;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* State of the spaces you are in.
|
|
23
26
|
*/
|
|
24
|
-
public readonly spaces
|
|
27
|
+
public readonly spaces: SpacesManager;
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* State of the emoticons (global and space-related).
|
|
28
31
|
*/
|
|
29
|
-
public readonly emoticons
|
|
32
|
+
public readonly emoticons: EmoticonsManager;
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Users related state.
|
|
33
36
|
*/
|
|
34
|
-
public readonly users
|
|
37
|
+
public readonly users: UsersManager;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* State of relationships with other users.
|
|
41
|
+
*/
|
|
42
|
+
public readonly relationships: RelationshipsManager;
|
|
35
43
|
|
|
36
44
|
private _me: User = null;
|
|
37
45
|
private readonly deferredSession = new DeferredTask();
|
|
38
46
|
|
|
39
|
-
public constructor(
|
|
47
|
+
public constructor(client: WebSocketChatClient) {
|
|
48
|
+
this.client = client;
|
|
40
49
|
this.client.on('Session', ev => this.handleSession(ev));
|
|
50
|
+
|
|
51
|
+
this.permissions = new PermissionsManager(this);
|
|
52
|
+
this.rooms = new RoomsManager(this);
|
|
53
|
+
this.spaces = new SpacesManager(this);
|
|
54
|
+
this.emoticons = new EmoticonsManager(this);
|
|
55
|
+
this.users = new UsersManager(this);
|
|
56
|
+
this.relationships = new RelationshipsManager(this);
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
public get me(): User | null {
|
|
@@ -21,18 +21,20 @@ export class EmoticonsManager {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
public async get(spaceId?: string): Promise<ObservableIndexedObjectCollection<Emoticon>> {
|
|
24
|
-
|
|
24
|
+
const key = spaceId ?? GLOBAL_KEY;
|
|
25
|
+
|
|
26
|
+
if (this.emoticonsPromises.notExist(key)) {
|
|
25
27
|
this.emoticonsPromises.registerByFunction(async () => {
|
|
26
28
|
const result = await this.tracker.client.send('GetEmoticons', {spaceId});
|
|
27
29
|
if (result.error) {
|
|
28
30
|
throw result.error;
|
|
29
31
|
}
|
|
30
32
|
this.handleEmoticons(result.data);
|
|
31
|
-
},
|
|
33
|
+
}, key);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
await this.emoticonsPromises.get(
|
|
35
|
-
return this.list.get(
|
|
36
|
+
await this.emoticonsPromises.get(key);
|
|
37
|
+
return this.list.get(key);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
private handleEmoticons(event: Emoticons): void {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RoomDeleted,
|
|
9
9
|
RoomLeft,
|
|
10
10
|
TopicDeleted,
|
|
11
|
-
FollowedTopicUpdated, RoomJoined, NewTopic, Session, Room,
|
|
11
|
+
FollowedTopicUpdated, RoomJoined, NewTopic, Session, Room, MessageType,
|
|
12
12
|
} from "../types/src";
|
|
13
13
|
import {
|
|
14
14
|
IndexedCollection,
|
|
@@ -210,8 +210,8 @@ export class MessagesManager {
|
|
|
210
210
|
const roomFollowedTopics = this.followedTopics.get(ev.message.location.roomId);
|
|
211
211
|
const followedTopic = roomFollowedTopics?.get(ev.message.location.topicId);
|
|
212
212
|
|
|
213
|
-
if (!
|
|
214
|
-
// Skip if we don't follow this room or targeted topic
|
|
213
|
+
if (!roomFollowedTopics || !followedTopic || ev.message.type === 'Ephemeral') {
|
|
214
|
+
// Skip if we don't follow this room or targeted topic or the message is ephemeral
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
217
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
2
|
+
import {
|
|
3
|
+
NewRelationship,
|
|
4
|
+
RelationshipDeleted,
|
|
5
|
+
Relationships,
|
|
6
|
+
UserRelationship,
|
|
7
|
+
UserRelationshipType
|
|
8
|
+
} from "../types/src";
|
|
9
|
+
import {PromiseRegistry} from "./AsyncUtils";
|
|
10
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
11
|
+
|
|
12
|
+
const getId = (refUserId: string, type: UserRelationshipType): string => `${refUserId}-${type}`;
|
|
13
|
+
const getIdFromRelationship = (relationship: UserRelationship): string => getId(relationship.refUser.id, relationship.type);
|
|
14
|
+
|
|
15
|
+
export class RelationshipsManager {
|
|
16
|
+
private relationships: ObservableIndexedObjectCollection<UserRelationship> = new ObservableIndexedObjectCollection<UserRelationship>(getIdFromRelationship);
|
|
17
|
+
private promises = new PromiseRegistry();
|
|
18
|
+
|
|
19
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
20
|
+
this.tracker.client.on('Relationships', ev => this.handleRelationships(ev));
|
|
21
|
+
this.tracker.client.on('NewRelationship', ev => this.handleNewRelationship(ev));
|
|
22
|
+
this.tracker.client.on('RelationshipDeleted', ev => this.handleRelationshipDeleted(ev));
|
|
23
|
+
this.tracker.client.on('Session', () => this.handleSession());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async get(): Promise<ObservableIndexedObjectCollection<UserRelationship>> {
|
|
27
|
+
if (this.promises.notExist('all')) {
|
|
28
|
+
this.promises.registerByFunction(async () => {
|
|
29
|
+
const result = await this.tracker.client.send('GetRelationships', {});
|
|
30
|
+
if (result.error) {
|
|
31
|
+
throw result.error;
|
|
32
|
+
}
|
|
33
|
+
}, 'all');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await this.promises.get('all');
|
|
37
|
+
return this.relationships;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async exists(refUserId: string, type: UserRelationshipType): Promise<boolean> {
|
|
41
|
+
await this.get();
|
|
42
|
+
return this.relationships.has(getId(refUserId, type));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private handleRelationships(ev: Relationships): void {
|
|
46
|
+
this.relationships.deleteAll();
|
|
47
|
+
ev.relationships.forEach(relationship => {
|
|
48
|
+
this.relationships.set(relationship);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private handleNewRelationship(ev: NewRelationship): void {
|
|
53
|
+
if (this.promises.has('all')) {
|
|
54
|
+
this.relationships.set(ev.relationship);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private handleRelationshipDeleted(ev: RelationshipDeleted): void {
|
|
59
|
+
if (this.promises.has('all')) {
|
|
60
|
+
this.relationships.delete(getIdFromRelationship(ev.relationship));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private handleSession(): void {
|
|
65
|
+
this.promises.forgetAll();
|
|
66
|
+
this.relationships.deleteAll();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -5,6 +5,7 @@ import {TopicHistoryWindow} from "./TopicHistoryWindow";
|
|
|
5
5
|
|
|
6
6
|
export class RoomMessagesHistory {
|
|
7
7
|
private historyWindows = new IndexedCollection<string, TopicHistoryWindow>();
|
|
8
|
+
private traverseLock: boolean = false;
|
|
8
9
|
|
|
9
10
|
public constructor(
|
|
10
11
|
private room: Room,
|
|
@@ -14,6 +15,8 @@ export class RoomMessagesHistory {
|
|
|
14
15
|
this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
|
|
15
16
|
this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
|
|
16
17
|
|
|
18
|
+
this.updateTraverseLock(this.room);
|
|
19
|
+
|
|
17
20
|
if (this.room.defaultTopic) {
|
|
18
21
|
this.createHistoryWindowForTopic(this.room.defaultTopic);
|
|
19
22
|
}
|
|
@@ -25,7 +28,7 @@ export class RoomMessagesHistory {
|
|
|
25
28
|
public async getMessagesWindow(topicId: string): Promise<TopicHistoryWindow | undefined> {
|
|
26
29
|
let historyWindow = this.historyWindows.get(topicId);
|
|
27
30
|
|
|
28
|
-
if (!
|
|
31
|
+
if (!historyWindow) {
|
|
29
32
|
const topic = (await this.tracker.rooms.getTopics(this.room.id, [topicId])).get(topicId);
|
|
30
33
|
|
|
31
34
|
if (topic) {
|
|
@@ -36,13 +39,19 @@ export class RoomMessagesHistory {
|
|
|
36
39
|
return this.historyWindows.get(topicId);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
private handleRoomUpdated(ev: RoomUpdated): void {
|
|
42
|
+
private async handleRoomUpdated(ev: RoomUpdated): Promise<void> {
|
|
40
43
|
if (this.room.id === ev.room.id) {
|
|
41
44
|
this.room = ev.room;
|
|
42
45
|
|
|
46
|
+
this.updateTraverseLock(ev.room);
|
|
47
|
+
|
|
43
48
|
if (ev.room.defaultTopic) {
|
|
44
49
|
this.createHistoryWindowForTopic(ev.room.defaultTopic);
|
|
45
50
|
}
|
|
51
|
+
|
|
52
|
+
for (const [, window] of Array.from(this.historyWindows.items)) {
|
|
53
|
+
await window.setTraverseLock(this.traverseLock);
|
|
54
|
+
}
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
|
|
@@ -63,7 +72,11 @@ export class RoomMessagesHistory {
|
|
|
63
72
|
return;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
const historyWindow = new TopicHistoryWindow(this.room.id, topic.id, this.tracker);
|
|
76
|
+
|
|
77
|
+
void historyWindow.setTraverseLock(this.traverseLock);
|
|
78
|
+
|
|
79
|
+
this.historyWindows.set([topic.id, historyWindow]);
|
|
67
80
|
|
|
68
81
|
// If new topic refers to some message from this room, update other structures
|
|
69
82
|
if (topic.refMessage) {
|
|
@@ -71,4 +84,8 @@ export class RoomMessagesHistory {
|
|
|
71
84
|
refHistoryWindow?._updateMessageReference(topic);
|
|
72
85
|
}
|
|
73
86
|
}
|
|
87
|
+
|
|
88
|
+
private updateTraverseLock(room: Room): void {
|
|
89
|
+
this.traverseLock = room.history.mode === 'Ephemeral';
|
|
90
|
+
}
|
|
74
91
|
}
|
|
@@ -174,6 +174,7 @@ export class RoomsManager {
|
|
|
174
174
|
const newMember = ev.member;
|
|
175
175
|
const user = member.spaceMember?.user ?? member.user;
|
|
176
176
|
|
|
177
|
+
// Preserving user object, because it's not included in event
|
|
177
178
|
if (newMember.spaceMember) {
|
|
178
179
|
newMember.spaceMember.user = user;
|
|
179
180
|
} else {
|
|
@@ -214,7 +215,9 @@ export class RoomsManager {
|
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
private handleRoomUpdated(ev: RoomUpdated): void {
|
|
217
|
-
this.list.
|
|
218
|
+
if (this.list.has(ev.room.id)) {
|
|
219
|
+
this.list.set(ev.room);
|
|
220
|
+
}
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
private handleRoomDeleted(ev: RoomDeleted): void {
|
|
@@ -295,6 +298,7 @@ export class RoomsManager {
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
private handleUserUpdated(ev: UserUpdated): void {
|
|
301
|
+
// Update room members users
|
|
298
302
|
this.members.items.forEach((members) => {
|
|
299
303
|
const member = members.get(ev.user.id);
|
|
300
304
|
|
|
@@ -313,18 +317,37 @@ export class RoomsManager {
|
|
|
313
317
|
|
|
314
318
|
members.set(newMember);
|
|
315
319
|
});
|
|
320
|
+
|
|
321
|
+
// Update recipients users
|
|
322
|
+
const newRooms: Room[] = [];
|
|
323
|
+
this.list.items.forEach(room => {
|
|
324
|
+
if (room.recipients?.some(user => user.id === ev.user.id)) {
|
|
325
|
+
room.recipients = room.recipients.map(user => user.id === ev.user.id ? ev.user : user);
|
|
326
|
+
newRooms.push({...room});
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
this.list.set(...newRooms);
|
|
316
330
|
}
|
|
317
331
|
|
|
318
332
|
private handleNewMessage(ev: NewMessage): void {
|
|
319
333
|
const topics = this.topics.get(ev.message.location.roomId);
|
|
320
334
|
const topic = topics?.get(ev.message.location.topicId);
|
|
321
335
|
|
|
322
|
-
if (topic) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
336
|
+
if (!topic) {
|
|
337
|
+
return; // No topic found, nothing to update
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const newTopic = {
|
|
341
|
+
...topic,
|
|
342
|
+
messageCount: topic.messageCount + 1,
|
|
343
|
+
lastMessage: ev.message,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
topics.set(newTopic);
|
|
347
|
+
const room = this.list.get(ev.message.location.roomId);
|
|
348
|
+
|
|
349
|
+
if (room.defaultTopic?.id === ev.message.location.topicId) {
|
|
350
|
+
this.list.set({ ...room, defaultTopic: newTopic });
|
|
328
351
|
}
|
|
329
352
|
}
|
|
330
353
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
RoleUpdated,
|
|
9
9
|
RoomDeleted,
|
|
10
10
|
RoomSummary,
|
|
11
|
+
RoomSummaryUpdated,
|
|
11
12
|
RoomUpdated,
|
|
12
13
|
Session,
|
|
13
14
|
Space,
|
|
@@ -48,6 +49,7 @@ export class SpacesManager {
|
|
|
48
49
|
this.tracker.client.on('SpaceMemberLeft', ev => this.handleSpaceMemberLeft(ev));
|
|
49
50
|
this.tracker.client.on('SpaceMembers', ev => this.handleSpaceMembers(ev));
|
|
50
51
|
this.tracker.client.on('SpaceRooms', ev => this.handleSpaceRooms(ev));
|
|
52
|
+
this.tracker.client.on('RoomSummaryUpdated', ev => this.handleRoomSummaryUpdated(ev));
|
|
51
53
|
this.tracker.client.on('SpaceMemberUpdated', ev => this.handleSpaceMemberUpdated(ev));
|
|
52
54
|
this.tracker.client.on('UserUpdated', ev => this.handleUserUpdated(ev));
|
|
53
55
|
this.tracker.client.on('NewRole', ev => this.handleNewRole(ev));
|
|
@@ -229,12 +231,37 @@ export class SpacesManager {
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
private handleSpaceRooms(ev: SpaceRooms): void {
|
|
232
|
-
if (!
|
|
234
|
+
if (!this.rooms.has(ev.id)) {
|
|
233
235
|
this.rooms.set([ev.id, new ObservableIndexedObjectCollection('id', ev.summaries)]);
|
|
234
236
|
ev.summaries.forEach(summary => this.roomIdToSpaceId.set([summary.id, ev.id]));
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
private async handleRoomSummaryUpdated(ev: RoomSummaryUpdated): Promise<void> {
|
|
241
|
+
const spaceId = this.roomIdToSpaceId.get(ev.summary.id);
|
|
242
|
+
const summariesPromise = this.roomsPromises.get(spaceId);
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Update summary only if the list was already loaded.
|
|
246
|
+
* RoomSummaryUpdated event has a partial summary, so we need to update the existing summary by merging it.
|
|
247
|
+
*/
|
|
248
|
+
if (spaceId && summariesPromise) {
|
|
249
|
+
await summariesPromise;
|
|
250
|
+
|
|
251
|
+
const summaries = this.rooms.get(spaceId);
|
|
252
|
+
const oldSummary = summaries.get(ev.summary.id);
|
|
253
|
+
let newSummary: RoomSummary;
|
|
254
|
+
|
|
255
|
+
if (oldSummary) {
|
|
256
|
+
newSummary = {...oldSummary, ...ev.summary};
|
|
257
|
+
} else {
|
|
258
|
+
newSummary = ev.summary;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
summaries.set(newSummary);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
238
265
|
private handleSpaceMemberUpdated(ev: SpaceMemberUpdated): void {
|
|
239
266
|
if (this.members.has(ev.spaceId)) {
|
|
240
267
|
const members = this.members.get(ev.spaceId);
|