polfan-server-js-client 0.1.24 → 0.1.26
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 +1020 -424
- package/build/index.js.map +1 -1
- package/build/types/AbstractChatClient.d.ts +11 -9
- package/build/types/IndexedObjectCollection.d.ts +3 -3
- package/build/types/Permission.d.ts +15 -0
- package/build/types/WebSocketChatClient.d.ts +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/state-tracker/ChatStateTracker.d.ts +25 -0
- package/build/types/state-tracker/DeferredTask.d.ts +5 -0
- package/build/types/state-tracker/PermissionsManager.d.ts +22 -0
- package/build/types/state-tracker/RoomsManager.d.ts +59 -0
- package/build/types/state-tracker/SpacesManager.d.ts +47 -0
- package/index.html +6 -0
- package/package.json +65 -65
- package/src/AbstractChatClient.ts +20 -15
- package/src/IndexedObjectCollection.ts +3 -3
- package/src/Permission.ts +15 -0
- package/src/WebSocketChatClient.ts +1 -1
- package/src/index.ts +2 -0
- package/src/state-tracker/ChatStateTracker.ts +38 -0
- package/src/state-tracker/DeferredTask.ts +8 -0
- package/src/state-tracker/PermissionsManager.ts +205 -0
- package/src/state-tracker/RoomsManager.ts +222 -0
- package/src/state-tracker/SpacesManager.ts +201 -0
- package/build/types/ChatStateTracker.d.ts +0 -56
- package/src/ChatStateTracker.ts +0 -349
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
+
import {
|
|
3
|
+
PermissionOverwrites,
|
|
4
|
+
PermissionOverwritesChanged,
|
|
5
|
+
PermissionOverwritesValue,
|
|
6
|
+
Role
|
|
7
|
+
} from "pserv-ts-types";
|
|
8
|
+
import {EventHandler, EventTarget} from "../EventTarget";
|
|
9
|
+
import {IndexedCollection} from "../IndexedObjectCollection";
|
|
10
|
+
import {Permission} from "../Permission";
|
|
11
|
+
|
|
12
|
+
const getOvId = (
|
|
13
|
+
layer: PermissionOverwrites['layer'],
|
|
14
|
+
layerId: PermissionOverwrites['layerId'],
|
|
15
|
+
target: PermissionOverwrites['target'],
|
|
16
|
+
targetId: PermissionOverwrites['targetId'],
|
|
17
|
+
) => layer + (layerId ?? '') + target + targetId;
|
|
18
|
+
|
|
19
|
+
const getOvIdByObject = (overwrites: PermissionOverwrites | PermissionOverwritesChanged): string => getOvId(
|
|
20
|
+
overwrites.layer, overwrites.layerId, overwrites.target, overwrites.targetId
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export class PermissionsManager extends EventTarget {
|
|
24
|
+
private readonly overwrites = new IndexedCollection<string, PermissionOverwrites>();
|
|
25
|
+
|
|
26
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
27
|
+
super();
|
|
28
|
+
this.tracker.client.on('PermissionOverwrites', ev => this.handlePermissionOverwrites(ev));
|
|
29
|
+
this.tracker.client.on('PermissionOverwritesChanged', ev => this.handlePermissionOverwrites(ev));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public async getOverwrites(
|
|
33
|
+
layer: PermissionOverwrites['layer'],
|
|
34
|
+
layerId: PermissionOverwrites['layerId'],
|
|
35
|
+
target: PermissionOverwrites['target'],
|
|
36
|
+
targetId: PermissionOverwrites['targetId'],
|
|
37
|
+
): Promise<PermissionOverwrites | undefined> {
|
|
38
|
+
const id = getOvId(layer, layerId, target, targetId);
|
|
39
|
+
|
|
40
|
+
if (this.overwrites.has(id)) {
|
|
41
|
+
return this.overwrites.get(id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = await this.tracker.client.send(
|
|
45
|
+
'GetPermissionOverwrites',
|
|
46
|
+
{layer, layerId, target, targetId},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return result.error ? null : result.data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public on(eventName: 'change', handler: EventHandler<any>): this {
|
|
53
|
+
return super.on(eventName, handler);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public async check(
|
|
57
|
+
permissionNames: (keyof typeof Permission)[],
|
|
58
|
+
spaceId?: string,
|
|
59
|
+
roomId?: string,
|
|
60
|
+
topicId?: string,
|
|
61
|
+
): Promise<{ok: boolean, missing: string[]}> {
|
|
62
|
+
const ownedPermissions = await this.calculatePermissions(spaceId, roomId, topicId);
|
|
63
|
+
const missing: string[] = [];
|
|
64
|
+
|
|
65
|
+
permissionNames.forEach(name => {
|
|
66
|
+
if (~ ownedPermissions & Permission[name]) {
|
|
67
|
+
missing.push(name);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {ok: missing.length === 0, missing};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async calculatePermissions(spaceId?: string, roomId?: string, topicId?: string): Promise<number> {
|
|
75
|
+
if (topicId && ! roomId || roomId && ! spaceId) {
|
|
76
|
+
throw new Error('Corrupted arguments hierarchy');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const userId = (await this.tracker.getMe()).id;
|
|
80
|
+
|
|
81
|
+
const userRoles: string[] = [];
|
|
82
|
+
|
|
83
|
+
const promises: Promise<PermissionOverwritesValue>[] = [
|
|
84
|
+
// Global user overwrites
|
|
85
|
+
this.getOverwrites('Global', null, 'User', userId).then(v => v.overwrites),
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
if (spaceId) {
|
|
89
|
+
userRoles.push(...(await this.tracker.spaces.getMe(spaceId)).roles);
|
|
90
|
+
promises.push(this.collectRoleOverwrites(spaceId, 'Space', spaceId, userRoles));
|
|
91
|
+
promises.push(this.getOverwrites('Space', spaceId, 'User', userId).then(v => v.overwrites));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (roomId) {
|
|
95
|
+
const roomMember = await this.tracker.rooms.getMe(roomId);
|
|
96
|
+
|
|
97
|
+
if (roomMember.roles !== null) { // Room overwrites from roles (only for space rooms)
|
|
98
|
+
userRoles.push(...roomMember.roles);
|
|
99
|
+
promises.push(this.collectRoleOverwrites(spaceId, 'Room', roomId, userRoles));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
promises.push(this.getOverwrites('Room', roomId, 'User', userId).then(v => v.overwrites));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (topicId) {
|
|
106
|
+
if (userRoles.length) {
|
|
107
|
+
promises.push(this.collectRoleOverwrites(spaceId, 'Topic', topicId, userRoles));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
promises.push(this.getOverwrites('Topic', topicId, 'User', userId).then(v => v.overwrites));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.resolveOverwritesHierarchy(await Promise.all(promises));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private handlePermissionOverwrites(ev: PermissionOverwritesChanged | PermissionOverwrites) {
|
|
117
|
+
this.overwrites.set([getOvIdByObject(ev), ev]);
|
|
118
|
+
this.emit('change');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async collectRoleOverwrites(
|
|
122
|
+
spaceId: string,
|
|
123
|
+
layer: PermissionOverwrites['layer'],
|
|
124
|
+
layerId: PermissionOverwrites['layerId'],
|
|
125
|
+
userRoles: string[],
|
|
126
|
+
): Promise<PermissionOverwritesValue> {
|
|
127
|
+
const roleOverwrites = await Promise.all(userRoles.map(
|
|
128
|
+
roleId => this.getOverwrites(layer, layerId, 'Role', roleId)
|
|
129
|
+
));
|
|
130
|
+
|
|
131
|
+
return this.resolveOverwritesFromRolesByOrder(spaceId, roleOverwrites);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async resolveOverwritesFromRolesByOrder(
|
|
135
|
+
spaceId: string,
|
|
136
|
+
overwrites: PermissionOverwrites[],
|
|
137
|
+
): Promise<PermissionOverwritesValue> {
|
|
138
|
+
let allows = 0, denies = 0;
|
|
139
|
+
const roles = await this.tracker.spaces.getRoles(spaceId);
|
|
140
|
+
const sortedOverwrites = overwrites.sort(
|
|
141
|
+
(a, b) =>
|
|
142
|
+
roles.get(a.targetId).priority - roles.get(b.targetId).priority
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Max length of bit word
|
|
146
|
+
const permissionsLength = overwrites.reduce(
|
|
147
|
+
(previousValue: number, currentValue: PermissionOverwrites) =>
|
|
148
|
+
Math.max(
|
|
149
|
+
previousValue,
|
|
150
|
+
currentValue.overwrites.allow.toString(2).length,
|
|
151
|
+
currentValue.overwrites.deny.toString(2).length
|
|
152
|
+
),
|
|
153
|
+
0,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
sortedOverwrites.forEach(overwriteEvent => {
|
|
157
|
+
const overwrites = overwriteEvent.overwrites;
|
|
158
|
+
const revDecDenies = overwrites.deny.toString(2).split('').reverse().join('');
|
|
159
|
+
const revDecAllows = overwrites.allow.toString(2).split('').reverse().join('');
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < permissionsLength; i++) {
|
|
162
|
+
const deny = parseInt(revDecDenies[i] ?? '0');
|
|
163
|
+
const allow = parseInt(revDecAllows[i] ?? '0');
|
|
164
|
+
|
|
165
|
+
if (deny) {
|
|
166
|
+
denies |= 1 << i;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (allow) {
|
|
170
|
+
allows |= 1 << i;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {allow: allows, deny: denies};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private resolveOverwritesHierarchy(permissionOverwritesValues: PermissionOverwritesValue[]): number {
|
|
179
|
+
let result = 0;
|
|
180
|
+
|
|
181
|
+
for (const value of permissionOverwritesValues) {
|
|
182
|
+
if (value.allow & Permission.Root) {
|
|
183
|
+
return this.getRootAccessValue();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
result = (result & ~value.deny) | value.allow;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private getRootAccessValue(): number {
|
|
193
|
+
let result = 0;
|
|
194
|
+
|
|
195
|
+
for (const name of this.getPermissionNames()) {
|
|
196
|
+
result |= Permission[name];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private getPermissionNames(): string[] {
|
|
203
|
+
return Object.keys(Permission).filter(key => Number.isNaN(parseInt(key)));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {IndexedCollection, ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
2
|
+
import {
|
|
3
|
+
Message, NewMessage, NewTopic,
|
|
4
|
+
Room,
|
|
5
|
+
RoomJoined, RoomLeft,
|
|
6
|
+
RoomMember, RoomMemberJoined, RoomMemberLeft, RoomMembers,
|
|
7
|
+
RoomMemberUpdated, Session,
|
|
8
|
+
SpaceMember,
|
|
9
|
+
Topic,
|
|
10
|
+
TopicDeleted
|
|
11
|
+
} from "pserv-ts-types";
|
|
12
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
13
|
+
import {DeferredTask} from "./DeferredTask";
|
|
14
|
+
|
|
15
|
+
export class RoomsManager {
|
|
16
|
+
private readonly list = new ObservableIndexedObjectCollection<Room>('id');
|
|
17
|
+
private readonly topics = new IndexedCollection<string, ObservableIndexedObjectCollection<Topic>>();
|
|
18
|
+
// Temporary not lazy loaded; server must implement GetTopicMessages command.
|
|
19
|
+
private readonly topicsMessages = new IndexedCollection<string, ObservableIndexedObjectCollection<Message>>();
|
|
20
|
+
private readonly members = new IndexedCollection<string, ObservableIndexedObjectCollection<RoomMember>>();
|
|
21
|
+
private readonly deferredSession = new DeferredTask();
|
|
22
|
+
|
|
23
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
24
|
+
this.tracker.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
25
|
+
this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
|
|
26
|
+
this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
|
|
27
|
+
this.tracker.client.on('RoomJoined', ev => this.handleRoomJoined(ev));
|
|
28
|
+
this.tracker.client.on('RoomLeft', ev => this.handleRoomLeft(ev));
|
|
29
|
+
this.tracker.client.on('RoomMemberJoined', ev => this.handleRoomMemberJoined(ev));
|
|
30
|
+
this.tracker.client.on('RoomMemberLeft', ev => this.handleRoomMemberLeft(ev));
|
|
31
|
+
this.tracker.client.on('RoomMembers', ev => this.handleRoomMembers(ev));
|
|
32
|
+
this.tracker.client.on('RoomMemberUpdated', ev => this.handleRoomMemberUpdated(ev));
|
|
33
|
+
this.tracker.client.on('Session', ev => this.handleSession(ev));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get collection of room members.
|
|
38
|
+
*/
|
|
39
|
+
public async getMembers(roomId: string): Promise<ObservableIndexedObjectCollection<RoomMember> | undefined> {
|
|
40
|
+
if (! this.members.has(roomId)) {
|
|
41
|
+
const result = await this.tracker.client.send('GetRoomMembers', {id: roomId});
|
|
42
|
+
|
|
43
|
+
if (result.error) {
|
|
44
|
+
throw result.error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.handleRoomMembers(result.data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.members.get(roomId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get a room member representing the current user.
|
|
55
|
+
*/
|
|
56
|
+
public async getMe(roomId: string): Promise<RoomMember | undefined> {
|
|
57
|
+
const userId = (await this.tracker.getMe()).id;
|
|
58
|
+
const members = await this.getMembers(roomId);
|
|
59
|
+
|
|
60
|
+
if (! members) {
|
|
61
|
+
// User is not in passed room.
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return members.items.find(member => (member.user?.id ?? member.spaceMember.user.id) === userId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get collection of all the rooms you are in.
|
|
70
|
+
*/
|
|
71
|
+
public async get(): Promise<ObservableIndexedObjectCollection<Room>> {
|
|
72
|
+
await this.deferredSession.promise;
|
|
73
|
+
return this.list;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get collection of room topics.
|
|
78
|
+
*/
|
|
79
|
+
public async getTopics(roomId: string): Promise<ObservableIndexedObjectCollection<Topic> | undefined> {
|
|
80
|
+
await this.deferredSession.promise;
|
|
81
|
+
return this.topics.get(roomId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get collection of the messages written in topic.
|
|
86
|
+
*/
|
|
87
|
+
public async getMessages(topicId: string): Promise<ObservableIndexedObjectCollection<Message> | undefined> {
|
|
88
|
+
return this.topicsMessages.get(topicId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* For internal use. If you want to leave the room, execute a proper command on client object.
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
public _delete(...roomIds: string[]): void {
|
|
96
|
+
this.list.delete(...roomIds);
|
|
97
|
+
this.members.delete(...roomIds);
|
|
98
|
+
|
|
99
|
+
const topicIds: string[] = [];
|
|
100
|
+
for (const roomId of roomIds) {
|
|
101
|
+
topicIds.push(...(this.topics.get(roomId)?.map(topic => topic.id) ?? []));
|
|
102
|
+
}
|
|
103
|
+
this.topicsMessages.delete(...topicIds);
|
|
104
|
+
|
|
105
|
+
this.topics.delete(...roomIds);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* For internal use. If you want to leave the room, execute a proper command on client object.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
public _deleteBySpaceId(spaceId: string): void {
|
|
113
|
+
this._delete(
|
|
114
|
+
...this.list.findBy('spaceId', spaceId).map(room => room.id)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* For internal use.
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
public _handleSpaceMemberUpdate(spaceId: string, member: SpaceMember): void {
|
|
123
|
+
// Update members of rooms related to this space
|
|
124
|
+
for (const room of this.list.findBy('spaceId', spaceId).items) {
|
|
125
|
+
const roomMembers = this.members.get(room.id);
|
|
126
|
+
|
|
127
|
+
if (! roomMembers) {
|
|
128
|
+
// Skip update if member list for this room is not loaded
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const roomMember = roomMembers.get(member.user.id);
|
|
133
|
+
roomMember.spaceMember = member;
|
|
134
|
+
roomMembers.set(roomMember);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private handleRoomMemberUpdated(ev: RoomMemberUpdated): void {
|
|
139
|
+
if (! this.members.has(ev.roomId)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.members.get(ev.roomId).set(ev.member);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private handleTopicDeleted(ev: TopicDeleted): void {
|
|
147
|
+
const collection = this.topics.get(ev.roomId);
|
|
148
|
+
collection.delete(ev.id);
|
|
149
|
+
this.list.get(ev.roomId).topics = collection.items;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private handleNewMessage(ev: NewMessage): void {
|
|
153
|
+
this.topicsMessages.get(ev.topicId).set(ev.message);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private handleNewTopic(ev: NewTopic): void {
|
|
157
|
+
this.addJoinedRoomTopics(ev.roomId, ev.topic);
|
|
158
|
+
this.list.get(ev.roomId).topics.push(ev.topic);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private addJoinedRoomTopics(roomId: string, ...topics: Topic[]): void {
|
|
162
|
+
if (this.topics.has(roomId)) {
|
|
163
|
+
this.topics.get(roomId).set(...topics);
|
|
164
|
+
} else {
|
|
165
|
+
this.topics.set([roomId, new ObservableIndexedObjectCollection<Topic>('id', topics)]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.topicsMessages.set(...topics.map<[string, ObservableIndexedObjectCollection<Message>]>(topic => [
|
|
169
|
+
topic.id,
|
|
170
|
+
new ObservableIndexedObjectCollection<Message>('id')
|
|
171
|
+
]));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private handleRoomJoined(ev: RoomJoined): void {
|
|
175
|
+
this.addJoinedRooms(ev.room);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private addJoinedRooms(...rooms: Room[]): void {
|
|
179
|
+
for (const room of rooms) {
|
|
180
|
+
this.addJoinedRoomTopics(room.id, ...room.topics);
|
|
181
|
+
}
|
|
182
|
+
this.list.set(...rooms);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private handleRoomLeft(ev: RoomLeft): void {
|
|
186
|
+
this._delete(ev.id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private handleRoomMemberJoined(ev: RoomMemberJoined): void {
|
|
190
|
+
if (this.members.has(ev.roomId)) {
|
|
191
|
+
this.members.get(ev.roomId).set(ev.member);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private handleRoomMemberLeft(ev: RoomMemberLeft): void {
|
|
196
|
+
if (this.members.has(ev.roomId)) {
|
|
197
|
+
this.members.get(ev.roomId).delete(ev.userId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private handleRoomMembers(ev: RoomMembers): void {
|
|
202
|
+
if (! this.members.has(ev.id)) {
|
|
203
|
+
this.members.set([
|
|
204
|
+
ev.id,
|
|
205
|
+
new ObservableIndexedObjectCollection(
|
|
206
|
+
member => member.user?.id ?? member.spaceMember.user.id,
|
|
207
|
+
ev.members,
|
|
208
|
+
)
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private handleSession(ev: Session) {
|
|
214
|
+
this.list.deleteAll();
|
|
215
|
+
this.topics.deleteAll();
|
|
216
|
+
this.members.deleteAll();
|
|
217
|
+
|
|
218
|
+
this.addJoinedRooms(...ev.state.rooms);
|
|
219
|
+
|
|
220
|
+
this.deferredSession.resolve();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
+
import {IndexedCollection, ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
3
|
+
import {
|
|
4
|
+
NewRole,
|
|
5
|
+
NewRoom,
|
|
6
|
+
Role,
|
|
7
|
+
RoleDeleted, RoleUpdated, RoomDeleted,
|
|
8
|
+
RoomSummary, Session,
|
|
9
|
+
Space,
|
|
10
|
+
SpaceDeleted, SpaceJoined, SpaceLeft,
|
|
11
|
+
SpaceMember, SpaceMemberJoined, SpaceMemberLeft, SpaceMembers, SpaceMemberUpdated, SpaceRooms
|
|
12
|
+
} from "pserv-ts-types";
|
|
13
|
+
import {DeferredTask} from "./DeferredTask";
|
|
14
|
+
|
|
15
|
+
export class SpacesManager {
|
|
16
|
+
private readonly list = new ObservableIndexedObjectCollection<Space>('id');
|
|
17
|
+
private readonly roles = new IndexedCollection<string, ObservableIndexedObjectCollection<Role>>();
|
|
18
|
+
private readonly rooms = new IndexedCollection<string, ObservableIndexedObjectCollection<RoomSummary>>();
|
|
19
|
+
private readonly members = new IndexedCollection<string, ObservableIndexedObjectCollection<SpaceMember>>();
|
|
20
|
+
private readonly deferredSession = new DeferredTask();
|
|
21
|
+
|
|
22
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
23
|
+
this.tracker.client.on('NewRoom', ev => this.handleNewRoom(ev));
|
|
24
|
+
this.tracker.client.on('RoomDeleted', ev => this.handleRoomDeleted(ev));
|
|
25
|
+
this.tracker.client.on('SpaceDeleted', ev => this.handleSpaceDeleted(ev));
|
|
26
|
+
this.tracker.client.on('SpaceJoined', ev => this.handleSpaceJoined(ev));
|
|
27
|
+
this.tracker.client.on('SpaceLeft', ev => this.handleSpaceLeft(ev));
|
|
28
|
+
this.tracker.client.on('SpaceMemberJoined', ev => this.handleSpaceMemberJoined(ev));
|
|
29
|
+
this.tracker.client.on('SpaceMemberLeft', ev => this.handleSpaceMemberLeft(ev));
|
|
30
|
+
this.tracker.client.on('SpaceMembers', ev => this.handleSpaceMembers(ev));
|
|
31
|
+
this.tracker.client.on('SpaceRooms', ev => this.handleSpaceRooms(ev));
|
|
32
|
+
this.tracker.client.on('SpaceMemberUpdated', ev => this.handleSpaceMemberUpdated(ev));
|
|
33
|
+
this.tracker.client.on('NewRole', ev => this.handleNewRole(ev));
|
|
34
|
+
this.tracker.client.on('RoleDeleted', ev => this.handleRoleDeleted(ev));
|
|
35
|
+
this.tracker.client.on('RoleUpdated', ev => this.handleRoleUpdated(ev));
|
|
36
|
+
this.tracker.client.on('Session', ev => this.handleSession(ev));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get collection of all the spaces you are in.
|
|
41
|
+
*/
|
|
42
|
+
public async get(): Promise<ObservableIndexedObjectCollection<Space>> {
|
|
43
|
+
await this.deferredSession.promise;
|
|
44
|
+
return this.list;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get collection of space roles.
|
|
49
|
+
*/
|
|
50
|
+
public async getRoles(spaceId: string): Promise<ObservableIndexedObjectCollection<Role> | undefined> {
|
|
51
|
+
await this.deferredSession.promise;
|
|
52
|
+
return this.roles.get(spaceId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get collection of the all available rooms inside given space.
|
|
57
|
+
*/
|
|
58
|
+
public async getRooms(spaceId: string): Promise<ObservableIndexedObjectCollection<RoomSummary> | undefined> {
|
|
59
|
+
if (! this.rooms.has(spaceId)) {
|
|
60
|
+
const result = await this.tracker.client.send('GetSpaceRooms', {id: spaceId});
|
|
61
|
+
|
|
62
|
+
if (result.error) {
|
|
63
|
+
throw result.error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.handleSpaceRooms(result.data);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.rooms.get(spaceId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get collection of space members.
|
|
74
|
+
*/
|
|
75
|
+
public async getMembers(spaceId: string): Promise<ObservableIndexedObjectCollection<SpaceMember> | undefined> {
|
|
76
|
+
if (! this.members.has(spaceId)) {
|
|
77
|
+
const result = await this.tracker.client.send('GetSpaceMembers', {id: spaceId});
|
|
78
|
+
|
|
79
|
+
if (result.error) {
|
|
80
|
+
throw result.error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.handleSpaceMembers(result.data);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.members.get(spaceId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get a space member representing the current user.
|
|
91
|
+
*/
|
|
92
|
+
public async getMe(spaceId: string): Promise<SpaceMember | undefined> {
|
|
93
|
+
const userId = (await this.tracker.getMe()).id;
|
|
94
|
+
const members = await this.getMembers(spaceId);
|
|
95
|
+
|
|
96
|
+
if (! members) {
|
|
97
|
+
// User is not in passed space.
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return members.items.find(member => member.user.id === userId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private handleNewRole(ev: NewRole): void {
|
|
105
|
+
const collection = this.roles.get(ev.spaceId);
|
|
106
|
+
collection.set(ev.role);
|
|
107
|
+
this.list.get(ev.spaceId).roles = collection.items;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private handleNewRoom(ev: NewRoom): void {
|
|
111
|
+
this.rooms.get(ev.spaceId)?.set(ev.summary);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private handleRoomDeleted(ev: RoomDeleted): void {
|
|
115
|
+
if (ev.spaceId) {
|
|
116
|
+
this.rooms.get(ev.spaceId).delete(ev.id);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.tracker.rooms._delete(ev.id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private handleRoleDeleted(ev: RoleDeleted): void {
|
|
123
|
+
const collection = this.roles.get(ev.spaceId);
|
|
124
|
+
collection.delete(ev.id);
|
|
125
|
+
this.list.get(ev.spaceId).roles = collection.items;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private handleSpaceDeleted(ev: SpaceDeleted): void {
|
|
129
|
+
this.tracker.rooms._deleteBySpaceId(ev.id);
|
|
130
|
+
this.roles.delete(ev.id);
|
|
131
|
+
this.members.delete(ev.id);
|
|
132
|
+
this.rooms.delete(ev.id);
|
|
133
|
+
this.list.delete(ev.id);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private handleSpaceJoined(ev: SpaceJoined): void {
|
|
137
|
+
this.addJoinedSpaces(ev.space);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private addJoinedSpaces(...spaces: Space[]): void {
|
|
141
|
+
this.roles.set(...(spaces.map(space => [
|
|
142
|
+
space.id,
|
|
143
|
+
new ObservableIndexedObjectCollection<Role>('id', space.roles)
|
|
144
|
+
]) as [string, ObservableIndexedObjectCollection<Role>][]));
|
|
145
|
+
this.list.set(...spaces);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private handleSpaceLeft(ev: SpaceLeft): void {
|
|
149
|
+
this.handleSpaceDeleted(ev);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private handleSpaceMemberJoined(ev: SpaceMemberJoined): void {
|
|
153
|
+
if (this.members.has(ev.spaceId)) {
|
|
154
|
+
this.members.get(ev.spaceId).set(ev.member);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private handleSpaceMemberLeft(ev: SpaceMemberLeft): void {
|
|
159
|
+
if (this.members.has(ev.spaceId)) {
|
|
160
|
+
this.members.get(ev.spaceId).delete(ev.userId);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private handleSpaceMembers(ev: SpaceMembers): void {
|
|
165
|
+
if (! this.members.has(ev.id)) {
|
|
166
|
+
this.members.set([
|
|
167
|
+
ev.id,
|
|
168
|
+
new ObservableIndexedObjectCollection(member => member?.user.id, ev.members)
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private handleSpaceRooms(ev: SpaceRooms): void {
|
|
174
|
+
if (! this.rooms.has(ev.id)) {
|
|
175
|
+
this.rooms.set([ev.id, new ObservableIndexedObjectCollection('id', ev.summaries)]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private handleSpaceMemberUpdated(ev: SpaceMemberUpdated): void {
|
|
180
|
+
if (this.members.has(ev.spaceId)) {
|
|
181
|
+
this.members.get(ev.spaceId).set(ev.member);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.tracker.rooms._handleSpaceMemberUpdate(ev.spaceId, ev.member);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private handleRoleUpdated(ev: RoleUpdated): void {
|
|
188
|
+
this.roles.get(ev.spaceId).set(ev.role);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private handleSession(ev: Session) {
|
|
192
|
+
this.list.deleteAll();
|
|
193
|
+
this.roles.deleteAll();
|
|
194
|
+
this.rooms.deleteAll();
|
|
195
|
+
this.members.deleteAll();
|
|
196
|
+
|
|
197
|
+
this.addJoinedSpaces(...ev.state.spaces);
|
|
198
|
+
|
|
199
|
+
this.deferredSession.resolve();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { WebSocketChatClient } from "./WebSocketChatClient";
|
|
2
|
-
import { ObservableIndexedObjectCollection } from "./IndexedObjectCollection";
|
|
3
|
-
import { Message, Role, Room, RoomMember, RoomSummary, Space, SpaceMember, Topic, User } from "pserv-ts-types";
|
|
4
|
-
export declare class ChatStateTracker {
|
|
5
|
-
private readonly client;
|
|
6
|
-
private readonly joinedSpaces;
|
|
7
|
-
private readonly joinedRooms;
|
|
8
|
-
private readonly spacesRoles;
|
|
9
|
-
private readonly roomsTopics;
|
|
10
|
-
private readonly topicsMessages;
|
|
11
|
-
private readonly spacesRooms;
|
|
12
|
-
private readonly spacesMembers;
|
|
13
|
-
private readonly roomsMembers;
|
|
14
|
-
private readonly deferredGetters;
|
|
15
|
-
private reconnecting;
|
|
16
|
-
private me;
|
|
17
|
-
constructor(client: WebSocketChatClient);
|
|
18
|
-
getMe(): Promise<User>;
|
|
19
|
-
getJoinedSpaces(): Promise<ObservableIndexedObjectCollection<Space>>;
|
|
20
|
-
getJoinedRooms(): Promise<ObservableIndexedObjectCollection<Room>>;
|
|
21
|
-
getSpaceRoles(spaceId: string): Promise<ObservableIndexedObjectCollection<Role> | null>;
|
|
22
|
-
getRoomTopics(roomId: string): Promise<ObservableIndexedObjectCollection<Topic> | null>;
|
|
23
|
-
getTopicMessages(topicId: string): Promise<ObservableIndexedObjectCollection<Message> | null>;
|
|
24
|
-
getSpaceRooms(spaceId: string): Promise<ObservableIndexedObjectCollection<RoomSummary> | null>;
|
|
25
|
-
getSpaceMembers(spaceId: string): Promise<ObservableIndexedObjectCollection<SpaceMember> | null>;
|
|
26
|
-
getRoomMembers(roomId: string): Promise<ObservableIndexedObjectCollection<RoomMember> | null>;
|
|
27
|
-
private deferredGetterReadiness;
|
|
28
|
-
private bind;
|
|
29
|
-
private createDeferredGetter;
|
|
30
|
-
private handleDisconnect;
|
|
31
|
-
private handleNewMessage;
|
|
32
|
-
private handleNewRole;
|
|
33
|
-
private handleNewRoom;
|
|
34
|
-
private handleNewTopic;
|
|
35
|
-
private addJoinedRoomTopics;
|
|
36
|
-
private handleRoleDeleted;
|
|
37
|
-
private handleRoomDeleted;
|
|
38
|
-
private handleRoomJoined;
|
|
39
|
-
private addJoinedRooms;
|
|
40
|
-
private handleRoomLeft;
|
|
41
|
-
private deleteJoinedRooms;
|
|
42
|
-
private handleRoomMemberJoined;
|
|
43
|
-
private handleRoomMemberLeft;
|
|
44
|
-
private handleRoomMembers;
|
|
45
|
-
private handleSession;
|
|
46
|
-
private handleSpaceDeleted;
|
|
47
|
-
private handleSpaceJoined;
|
|
48
|
-
private addJoinedSpaces;
|
|
49
|
-
private handleSpaceLeft;
|
|
50
|
-
private handleSpaceMemberJoined;
|
|
51
|
-
private handleSpaceMemberLeft;
|
|
52
|
-
private handleSpaceMembers;
|
|
53
|
-
private handleSpaceRooms;
|
|
54
|
-
private handleSpaceMemberUpdate;
|
|
55
|
-
private handleTopicDeleted;
|
|
56
|
-
}
|