polfan-server-js-client 0.1.1 → 0.1.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/.gitmodules +3 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/polfan-server-js-client.iml +3 -1
- package/.idea/vcs.xml +1 -1
- package/README.md +17 -17
- package/build/index.js +3282 -1
- package/build/index.js.map +1 -1
- package/build/types/AbstractChatClient.d.ts +26 -9
- package/build/types/AbstractRestClient.d.ts +1 -1
- package/build/types/EventTarget.d.ts +2 -0
- package/build/types/IndexedObjectCollection.d.ts +6 -3
- package/build/types/Permissions.d.ts +65 -0
- package/build/types/WebSocketChatClient.d.ts +1 -1
- package/build/types/index.d.ts +4 -1
- package/build/types/state-tracker/AsyncUtils.d.ts +14 -0
- package/build/types/state-tracker/ChatStateTracker.d.ts +26 -0
- package/build/types/state-tracker/MessagesManager.d.ts +39 -0
- package/build/types/state-tracker/PermissionsManager.d.ts +41 -0
- package/build/types/state-tracker/RoomsManager.d.ts +49 -0
- package/build/types/state-tracker/SpacesManager.d.ts +52 -0
- package/build/types/state-tracker/functions.d.ts +2 -0
- package/build/types/types/src/index.d.ts +82 -0
- package/build/types/types/src/schemes/AckReport.d.ts +7 -0
- package/build/types/types/src/schemes/ChatLocation.d.ts +5 -0
- package/build/types/types/src/schemes/Envelope.d.ts +5 -0
- package/build/types/types/src/schemes/Message.d.ts +7 -0
- package/build/types/types/src/schemes/PermissionOverwritesValue.d.ts +4 -0
- package/build/types/types/src/schemes/Role.d.ts +6 -0
- package/build/types/types/src/schemes/Room.d.ts +10 -0
- package/build/types/types/src/schemes/RoomMember.d.ts +7 -0
- package/build/types/types/src/schemes/RoomSummary.d.ts +5 -0
- package/build/types/types/src/schemes/Space.d.ts +6 -0
- package/build/types/types/src/schemes/SpaceMember.d.ts +5 -0
- package/build/types/types/src/schemes/Topic.d.ts +5 -0
- package/build/types/types/src/schemes/User.d.ts +8 -0
- package/build/types/types/src/schemes/UserState.d.ts +6 -0
- package/build/types/types/src/schemes/commands/Ack.d.ts +5 -0
- package/build/types/types/src/schemes/commands/AssignRole.d.ts +6 -0
- package/build/types/types/src/schemes/commands/CreateMessage.d.ts +5 -0
- package/build/types/types/src/schemes/commands/CreateOwner.d.ts +5 -0
- package/build/types/types/src/schemes/commands/CreateRole.d.ts +5 -0
- package/build/types/types/src/schemes/commands/CreateRoom.d.ts +7 -0
- package/build/types/types/src/schemes/commands/CreateSpace.d.ts +3 -0
- package/build/types/types/src/schemes/commands/CreateTopic.d.ts +12 -0
- package/build/types/types/src/schemes/commands/DeassignRole.d.ts +6 -0
- package/build/types/types/src/schemes/commands/DeleteOwner.d.ts +5 -0
- package/build/types/types/src/schemes/commands/DeleteRole.d.ts +4 -0
- package/build/types/types/src/schemes/commands/DeleteRoom.d.ts +3 -0
- package/build/types/types/src/schemes/commands/DeleteSpace.d.ts +3 -0
- package/build/types/types/src/schemes/commands/DeleteTopic.d.ts +4 -0
- package/build/types/types/src/schemes/commands/GetAckReports.d.ts +4 -0
- package/build/types/types/src/schemes/commands/GetComputedPermissions.d.ts +4 -0
- package/build/types/types/src/schemes/commands/GetOwners.d.ts +4 -0
- package/build/types/types/src/schemes/commands/GetPermissionOverwriteTargets.d.ts +4 -0
- package/build/types/types/src/schemes/commands/GetPermissionOverwrites.d.ts +6 -0
- package/build/types/types/src/schemes/commands/GetRoomMembers.d.ts +3 -0
- package/build/types/types/src/schemes/commands/GetSession.d.ts +2 -0
- package/build/types/types/src/schemes/commands/GetSpaceMembers.d.ts +3 -0
- package/build/types/types/src/schemes/commands/GetSpaceRooms.d.ts +3 -0
- package/build/types/types/src/schemes/commands/JoinRoom.d.ts +3 -0
- package/build/types/types/src/schemes/commands/JoinSpace.d.ts +3 -0
- package/build/types/types/src/schemes/commands/LeaveRoom.d.ts +3 -0
- package/build/types/types/src/schemes/commands/LeaveSpace.d.ts +3 -0
- package/build/types/types/src/schemes/commands/SetPermissionOverwrites.d.ts +8 -0
- package/build/types/types/src/schemes/commands/UpdateRole.d.ts +7 -0
- package/build/types/types/src/schemes/commands/UpdateRoom.d.ts +5 -0
- package/build/types/types/src/schemes/commands/UpdateSpace.d.ts +4 -0
- package/build/types/types/src/schemes/events/AckReports.d.ts +4 -0
- package/build/types/types/src/schemes/events/Bye.d.ts +3 -0
- package/build/types/types/src/schemes/events/ComputedPermissions.d.ts +5 -0
- package/build/types/types/src/schemes/events/Error.d.ts +4 -0
- package/build/types/types/src/schemes/events/NewMessage.d.ts +6 -0
- package/build/types/types/src/schemes/events/NewRole.d.ts +5 -0
- package/build/types/types/src/schemes/events/NewRoom.d.ts +5 -0
- package/build/types/types/src/schemes/events/NewTopic.d.ts +5 -0
- package/build/types/types/src/schemes/events/Ok.d.ts +2 -0
- package/build/types/types/src/schemes/events/Owners.d.ts +8 -0
- package/build/types/types/src/schemes/events/PermissionOverwriteTargets.d.ts +10 -0
- package/build/types/types/src/schemes/events/PermissionOverwrites.d.ts +8 -0
- package/build/types/types/src/schemes/events/PermissionOverwritesUpdated.d.ts +8 -0
- package/build/types/types/src/schemes/events/RoleDeleted.d.ts +4 -0
- package/build/types/types/src/schemes/events/RoleUpdated.d.ts +5 -0
- package/build/types/types/src/schemes/events/RoomDeleted.d.ts +3 -0
- package/build/types/types/src/schemes/events/RoomJoined.d.ts +4 -0
- package/build/types/types/src/schemes/events/RoomLeft.d.ts +3 -0
- package/build/types/types/src/schemes/events/RoomMemberJoined.d.ts +5 -0
- package/build/types/types/src/schemes/events/RoomMemberLeft.d.ts +4 -0
- package/build/types/types/src/schemes/events/RoomMemberUpdated.d.ts +6 -0
- package/build/types/types/src/schemes/events/RoomMembers.d.ts +5 -0
- package/build/types/types/src/schemes/events/RoomUpdated.d.ts +4 -0
- package/build/types/types/src/schemes/events/Session.d.ts +7 -0
- package/build/types/types/src/schemes/events/SpaceDeleted.d.ts +3 -0
- package/build/types/types/src/schemes/events/SpaceJoined.d.ts +4 -0
- package/build/types/types/src/schemes/events/SpaceLeft.d.ts +3 -0
- package/build/types/types/src/schemes/events/SpaceMemberJoined.d.ts +5 -0
- package/build/types/types/src/schemes/events/SpaceMemberLeft.d.ts +4 -0
- package/build/types/types/src/schemes/events/SpaceMemberUpdated.d.ts +6 -0
- package/build/types/types/src/schemes/events/SpaceMembers.d.ts +5 -0
- package/build/types/types/src/schemes/events/SpaceRooms.d.ts +5 -0
- package/build/types/types/src/schemes/events/SpaceUpdated.d.ts +4 -0
- package/build/types/types/src/schemes/events/TopicDeleted.d.ts +4 -0
- package/build/types/types/src/schemes/events/UserUpdated.d.ts +4 -0
- package/jest.config.ts +199 -0
- package/package.json +4 -22
- package/src/AbstractChatClient.ts +50 -16
- package/src/AbstractRestClient.ts +65 -65
- package/src/AuthClient.ts +1 -1
- package/src/EventTarget.ts +9 -0
- package/src/IndexedObjectCollection.ts +21 -4
- package/src/Permissions.ts +46 -0
- package/src/WebApiChatClient.ts +2 -2
- package/src/WebSocketChatClient.ts +6 -5
- package/src/index.ts +7 -2
- package/src/state-tracker/AsyncUtils.ts +38 -0
- package/src/state-tracker/ChatStateTracker.ts +42 -0
- package/src/state-tracker/MessagesManager.ts +154 -0
- package/src/state-tracker/PermissionsManager.ts +306 -0
- package/src/state-tracker/RoomsManager.ts +272 -0
- package/src/state-tracker/SpacesManager.ts +267 -0
- package/src/state-tracker/functions.ts +25 -0
- package/src/types/README.md +2 -0
- package/src/types/package-lock.json +31 -0
- package/src/types/package.json +16 -0
- package/src/types/src/index.ts +168 -0
- package/src/types/src/schemes/AckReport.ts +7 -0
- package/src/types/src/schemes/ChatLocation.ts +5 -0
- package/src/types/src/schemes/Envelope.ts +5 -0
- package/src/types/src/schemes/Message.ts +8 -0
- package/src/types/src/schemes/PermissionOverwritesValue.ts +4 -0
- package/src/types/src/schemes/Role.ts +6 -0
- package/src/types/src/schemes/Room.ts +12 -0
- package/src/types/src/schemes/RoomMember.ts +8 -0
- package/src/types/src/schemes/RoomSummary.ts +5 -0
- package/src/types/src/schemes/Space.ts +7 -0
- package/src/types/src/schemes/SpaceMember.ts +6 -0
- package/src/types/src/schemes/Topic.ts +5 -0
- package/src/types/src/schemes/User.ts +9 -0
- package/src/types/src/schemes/UserState.ts +7 -0
- package/src/types/src/schemes/commands/Ack.ts +6 -0
- package/src/types/src/schemes/commands/AssignRole.ts +7 -0
- package/src/types/src/schemes/commands/CreateMessage.ts +6 -0
- package/src/types/src/schemes/commands/CreateOwner.ts +6 -0
- package/src/types/src/schemes/commands/CreateRole.ts +5 -0
- package/src/types/src/schemes/commands/CreateRoom.ts +8 -0
- package/src/types/src/schemes/commands/CreateSpace.ts +3 -0
- package/src/types/src/schemes/commands/CreateTopic.ts +14 -0
- package/src/types/src/schemes/commands/DeassignRole.ts +7 -0
- package/src/types/src/schemes/commands/DeleteOwner.ts +6 -0
- package/src/types/src/schemes/commands/DeleteRole.ts +4 -0
- package/src/types/src/schemes/commands/DeleteRoom.ts +3 -0
- package/src/types/src/schemes/commands/DeleteSpace.ts +3 -0
- package/src/types/src/schemes/commands/DeleteTopic.ts +5 -0
- package/src/types/src/schemes/commands/GetAckReports.ts +5 -0
- package/src/types/src/schemes/commands/GetComputedPermissions.ts +5 -0
- package/src/types/src/schemes/commands/GetOwners.ts +5 -0
- package/src/types/src/schemes/commands/GetPermissionOverwriteTargets.ts +5 -0
- package/src/types/src/schemes/commands/GetPermissionOverwrites.ts +7 -0
- package/src/types/src/schemes/commands/GetRoomMembers.ts +3 -0
- package/src/types/src/schemes/commands/GetSession.ts +2 -0
- package/src/types/src/schemes/commands/GetSpaceMembers.ts +3 -0
- package/src/types/src/schemes/commands/GetSpaceRooms.ts +3 -0
- package/src/types/src/schemes/commands/JoinRoom.ts +3 -0
- package/src/types/src/schemes/commands/JoinSpace.ts +3 -0
- package/src/types/src/schemes/commands/LeaveRoom.ts +3 -0
- package/src/types/src/schemes/commands/LeaveSpace.ts +3 -0
- package/src/types/src/schemes/commands/SetPermissionOverwrites.ts +9 -0
- package/src/types/src/schemes/commands/UpdateRole.ts +7 -0
- package/src/types/src/schemes/commands/UpdateRoom.ts +5 -0
- package/src/types/src/schemes/commands/UpdateSpace.ts +4 -0
- package/src/types/src/schemes/events/AckReports.ts +5 -0
- package/src/types/src/schemes/events/Bye.ts +3 -0
- package/src/types/src/schemes/events/ComputedPermissions.ts +6 -0
- package/src/types/src/schemes/events/Error.ts +4 -0
- package/src/types/src/schemes/events/NewMessage.ts +7 -0
- package/src/types/src/schemes/events/NewRole.ts +6 -0
- package/src/types/src/schemes/events/NewRoom.ts +6 -0
- package/src/types/src/schemes/events/NewTopic.ts +6 -0
- package/src/types/src/schemes/events/Ok.ts +3 -0
- package/src/types/src/schemes/events/Owners.ts +9 -0
- package/src/types/src/schemes/events/PermissionOverwriteTargets.ts +11 -0
- package/src/types/src/schemes/events/PermissionOverwrites.ts +9 -0
- package/src/types/src/schemes/events/PermissionOverwritesUpdated.ts +9 -0
- package/src/types/src/schemes/events/RoleDeleted.ts +4 -0
- package/src/types/src/schemes/events/RoleUpdated.ts +6 -0
- package/src/types/src/schemes/events/RoomDeleted.ts +3 -0
- package/src/types/src/schemes/events/RoomJoined.ts +5 -0
- package/src/types/src/schemes/events/RoomLeft.ts +3 -0
- package/src/types/src/schemes/events/RoomMemberJoined.ts +6 -0
- package/src/types/src/schemes/events/RoomMemberLeft.ts +4 -0
- package/src/types/src/schemes/events/RoomMemberUpdated.ts +7 -0
- package/src/types/src/schemes/events/RoomMembers.ts +6 -0
- package/src/types/src/schemes/events/RoomUpdated.ts +5 -0
- package/src/types/src/schemes/events/Session.ts +8 -0
- package/src/types/src/schemes/events/SpaceDeleted.ts +3 -0
- package/src/types/src/schemes/events/SpaceJoined.ts +5 -0
- package/src/types/src/schemes/events/SpaceLeft.ts +3 -0
- package/src/types/src/schemes/events/SpaceMemberJoined.ts +6 -0
- package/src/types/src/schemes/events/SpaceMemberLeft.ts +4 -0
- package/src/types/src/schemes/events/SpaceMemberUpdated.ts +7 -0
- package/src/types/src/schemes/events/SpaceMembers.ts +6 -0
- package/src/types/src/schemes/events/SpaceRooms.ts +6 -0
- package/src/types/src/schemes/events/SpaceUpdated.ts +5 -0
- package/src/types/src/schemes/events/TopicDeleted.ts +5 -0
- package/src/types/src/schemes/events/UserUpdated.ts +5 -0
- package/src/types/tsconfig.json +75 -0
- package/tests/async-utils.test.ts +30 -0
- package/tests/permissions.test.ts +14 -0
- package/tests/space-roles.test.ts +43 -0
- package/webpack.config.js +1 -1
- package/build/types/ChatStateTracker.d.ts +0 -54
- package/src/ChatStateTracker.ts +0 -326
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
+
import {AckReport, AckReports, ChatLocation, Message, NewMessage, Topic} from "../types/src";
|
|
3
|
+
import {
|
|
4
|
+
IndexedCollection,
|
|
5
|
+
ObservableIndexedObjectCollection
|
|
6
|
+
} from "../IndexedObjectCollection";
|
|
7
|
+
|
|
8
|
+
export const getCombinedId = (location: ChatLocation) => (location.roomId ?? '') + (location.topicId ?? '');
|
|
9
|
+
|
|
10
|
+
export class MessagesManager {
|
|
11
|
+
// Temporary not lazy loaded; server must implement GetTopicMessages command.
|
|
12
|
+
private readonly list = new IndexedCollection<string, ObservableIndexedObjectCollection<Message>>();
|
|
13
|
+
private readonly acks: IndexedCollection<string, ObservableIndexedObjectCollection<AckReport>> = new IndexedCollection();
|
|
14
|
+
|
|
15
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
16
|
+
this.tracker.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
17
|
+
this.tracker.client.on('AckReports', ev => this.handleAckReports(ev));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get collection of the messages written in topic.
|
|
22
|
+
*/
|
|
23
|
+
public async get(location: ChatLocation): Promise<ObservableIndexedObjectCollection<Message> | undefined> {
|
|
24
|
+
return this.list.get(getCombinedId(location));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cache ack reports for all joined rooms in a space and fetch them in bulk if necessary.
|
|
29
|
+
* Then you can get the reports using getRoomAckReports().
|
|
30
|
+
* @see getRoomAckReports
|
|
31
|
+
*/
|
|
32
|
+
public async cacheSpaceAckReports(spaceId: string): Promise<void> {
|
|
33
|
+
if (! (await this.tracker.spaces.get()).has(spaceId)) {
|
|
34
|
+
throw new Error(`You are not in space ${spaceId}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const roomIds = (await this.tracker.rooms.get()).findBy('spaceId', spaceId).map(room => room.id);
|
|
38
|
+
const missingRoomIds = roomIds.filter(roomId => ! this.acks.has(roomId));
|
|
39
|
+
|
|
40
|
+
if (missingRoomIds.length) {
|
|
41
|
+
// If we don't have ack reports for all rooms in space, fetch them
|
|
42
|
+
const result = await this.tracker.client.send('GetAckReports', {location: {spaceId}});
|
|
43
|
+
|
|
44
|
+
if (result.error) {
|
|
45
|
+
throw result.error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
missingRoomIds.forEach(roomId => {
|
|
49
|
+
const reports = result.data.reports.filter(report => report.roomId === roomId);
|
|
50
|
+
this.acks.set([roomId, new ObservableIndexedObjectCollection('topicId', reports)]);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get ack reports for the given room. Undefined if you are not in the room.
|
|
57
|
+
* @param roomId
|
|
58
|
+
*/
|
|
59
|
+
public async getRoomAckReports(roomId: string): Promise<ObservableIndexedObjectCollection<AckReport> | undefined> {
|
|
60
|
+
const room = (await this.tracker.rooms.get()).get(roomId);
|
|
61
|
+
|
|
62
|
+
if (! room) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (! this.acks.has(roomId)) {
|
|
67
|
+
const result = await this.tracker.client.send('GetAckReports', {location: {roomId}});
|
|
68
|
+
|
|
69
|
+
if (result.error) {
|
|
70
|
+
throw result.error;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.acks.set([roomId, new ObservableIndexedObjectCollection('topicId', result.data.reports)]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this.acks.get(roomId);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* For internal use. If you want to delete the message, execute a proper command on client object.
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
public _deleteByTopicIds(roomId: string, ...topicIds: string[]): void {
|
|
84
|
+
this.list.delete(...topicIds.map(topicId => getCombinedId({roomId, topicId})));
|
|
85
|
+
this.acks.get(roomId)?.delete(...topicIds);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* For internal use. If you want to add new topic, execute a proper command on client object.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
public _handleNewTopics(roomId: string, ...topics: Topic[]): void {
|
|
93
|
+
this.list.set(...topics.map<[string, ObservableIndexedObjectCollection<Message>]>(topic => [
|
|
94
|
+
getCombinedId({roomId, topicId: topic.id}),
|
|
95
|
+
new ObservableIndexedObjectCollection<Message>('id'),
|
|
96
|
+
]));
|
|
97
|
+
this.createAckReportsForNewTopics(roomId, topics);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private handleNewMessage(ev: NewMessage): void {
|
|
101
|
+
this.list.get(getCombinedId(ev.location)).set(ev.message);
|
|
102
|
+
this.updateLocallyAckReportOnNewMessage(ev);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private handleAckReports(ev: AckReports): void {
|
|
106
|
+
ev.reports.forEach(report => {
|
|
107
|
+
const ackReports = this.acks.get(report.roomId);
|
|
108
|
+
if (ackReports) {
|
|
109
|
+
ackReports.set(report);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private createAckReportsForNewTopics(roomId: string, topics: Topic[]): void {
|
|
115
|
+
const ackReports = this.acks.get(roomId);
|
|
116
|
+
|
|
117
|
+
if (! ackReports) {
|
|
118
|
+
// If we don't follow ack reports for this room, skip
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const newReports: AckReport[] = topics.map(topic => ({
|
|
123
|
+
roomId, topicId: topic.id, lastAckMessageId: null, missed: 0, missedMoreThan: null
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
ackReports.set(...newReports);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private updateLocallyAckReportOnNewMessage(ev: NewMessage): void {
|
|
130
|
+
const ackReports = this.acks.get(ev.location.roomId);
|
|
131
|
+
|
|
132
|
+
if (! ackReports) {
|
|
133
|
+
// If we don't follow ack reports for this room, skip
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const isMe = ev.message.author.id === this.tracker.me?.id;
|
|
138
|
+
const currentAckReport = ackReports.get(ev.location.topicId);
|
|
139
|
+
let update: Partial<AckReport>;
|
|
140
|
+
|
|
141
|
+
if (isMe) {
|
|
142
|
+
// Reset missed messages count if new message is authored by me
|
|
143
|
+
update = {missed: 0, missedMoreThan: null, lastAckMessageId: ev.message.id};
|
|
144
|
+
} else {
|
|
145
|
+
// ...add 1 otherwise
|
|
146
|
+
update = {
|
|
147
|
+
missed: currentAckReport.missed === null ? null : currentAckReport.missed + 1,
|
|
148
|
+
missedMoreThan: currentAckReport.missedMoreThan === null ? null : currentAckReport.missedMoreThan,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
ackReports.set({...currentAckReport, ...update});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
+
import {
|
|
3
|
+
ChatLocation,
|
|
4
|
+
PermissionOverwrites,
|
|
5
|
+
PermissionOverwritesUpdated,
|
|
6
|
+
PermissionOverwritesValue,
|
|
7
|
+
RoleDeleted,
|
|
8
|
+
RoomDeleted,
|
|
9
|
+
RoomLeft, RoomMember, RoomMemberUpdated,
|
|
10
|
+
SpaceDeleted,
|
|
11
|
+
SpaceLeft, SpaceMember,
|
|
12
|
+
SpaceMemberUpdated,
|
|
13
|
+
TopicDeleted,
|
|
14
|
+
} from "../types/src";
|
|
15
|
+
import {EventHandler, EventTarget} from "../EventTarget";
|
|
16
|
+
import {IndexedCollection} from "../IndexedObjectCollection";
|
|
17
|
+
import {Permissions} from "../Permissions";
|
|
18
|
+
import {PromiseRegistry} from "./AsyncUtils";
|
|
19
|
+
|
|
20
|
+
const getOvId = (
|
|
21
|
+
location: ChatLocation,
|
|
22
|
+
target?: PermissionOverwrites['target'],
|
|
23
|
+
targetId?: PermissionOverwrites['targetId'],
|
|
24
|
+
) => (location.spaceId ?? '') + (location.roomId ?? '') + (location.topicId ?? '') + (target ?? '') + (targetId ?? '');
|
|
25
|
+
|
|
26
|
+
const getOvIdByObject = (overwrites: PermissionOverwrites | PermissionOverwritesUpdated): string => getOvId(
|
|
27
|
+
overwrites.location, overwrites.target, overwrites.targetId
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
interface CheckPermissionsResult {
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Use `hasAll` instead.
|
|
33
|
+
*/
|
|
34
|
+
ok: boolean;
|
|
35
|
+
hasAll: boolean;
|
|
36
|
+
hasAny: boolean;
|
|
37
|
+
missing: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class PermissionsManager extends EventTarget {
|
|
41
|
+
private readonly overwrites = new IndexedCollection<string, PermissionOverwrites>();
|
|
42
|
+
private readonly overwritesPromises = new PromiseRegistry();
|
|
43
|
+
|
|
44
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
45
|
+
super();
|
|
46
|
+
this.tracker.client.on('PermissionOverwrites', ev => this.handlePermissionOverwrites(ev));
|
|
47
|
+
this.tracker.client.on('PermissionOverwritesUpdated', ev => this.handlePermissionOverwrites(ev));
|
|
48
|
+
this.tracker.client.on('SpaceDeleted', ev => this.handleSpaceDeleted(ev));
|
|
49
|
+
this.tracker.client.on('SpaceLeft', ev => this.handleSpaceDeleted(ev));
|
|
50
|
+
this.tracker.client.on('RoomDeleted', ev => this.handleRoomDeleted(ev));
|
|
51
|
+
this.tracker.client.on('RoomLeft', ev => this.handleRoomDeleted(ev));
|
|
52
|
+
this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
|
|
53
|
+
this.tracker.client.on('RoleDeleted', ev => this.handleRoleDeleted(ev));
|
|
54
|
+
this.tracker.client.on('SpaceMemberUpdated', ev => this.handleSpaceMemberUpdated(ev));
|
|
55
|
+
this.tracker.client.on('RoomMemberUpdated', ev => this.handleRoomMemberUpdated(ev));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async getOverwrites(
|
|
59
|
+
location: ChatLocation,
|
|
60
|
+
target: PermissionOverwrites['target'],
|
|
61
|
+
targetId: PermissionOverwrites['targetId'],
|
|
62
|
+
): Promise<PermissionOverwrites | undefined> {
|
|
63
|
+
this.validateLocation(location);
|
|
64
|
+
|
|
65
|
+
const id = getOvId(location, target, targetId);
|
|
66
|
+
|
|
67
|
+
if (this.overwritesPromises.notExist(id)) {
|
|
68
|
+
this.overwritesPromises.registerByFunction(async () => {
|
|
69
|
+
const result = await this.tracker.client.send(
|
|
70
|
+
'GetPermissionOverwrites',
|
|
71
|
+
{location, target, targetId},
|
|
72
|
+
);
|
|
73
|
+
if (result.error) {
|
|
74
|
+
throw result.error;
|
|
75
|
+
}
|
|
76
|
+
this.handlePermissionOverwrites(result.data);
|
|
77
|
+
}, id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await this.overwritesPromises.get(id);
|
|
81
|
+
return this.overwrites.get(id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public on(eventName: 'change', handler: EventHandler<any>): this {
|
|
85
|
+
return super.on(eventName, handler);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public async check(
|
|
89
|
+
permissionNames: (keyof typeof Permissions.list)[],
|
|
90
|
+
location: ChatLocation,
|
|
91
|
+
): Promise<CheckPermissionsResult> {
|
|
92
|
+
if (! permissionNames.length) {
|
|
93
|
+
throw new Error('Permission names array cannot be empty');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ownedPermissions = await this.calculatePermissions(location);
|
|
97
|
+
const missing: string[] = [];
|
|
98
|
+
|
|
99
|
+
permissionNames.forEach(name => {
|
|
100
|
+
if (~ ownedPermissions & Permissions.getByName(name).value) {
|
|
101
|
+
missing.push(name as string);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
ok: missing.length === 0,
|
|
107
|
+
hasAll: missing.length === 0,
|
|
108
|
+
hasAny: missing.length < permissionNames.length,
|
|
109
|
+
missing,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async calculatePermissions(location: ChatLocation): Promise<number> {
|
|
114
|
+
this.validateLocation(location);
|
|
115
|
+
|
|
116
|
+
const userId = (await this.tracker.getMe()).id;
|
|
117
|
+
const [spaceMember, roomMember] = await this.fetchMembersOrFail(location);
|
|
118
|
+
const userRoles: string[] = [...(spaceMember?.roles ?? []), ...(roomMember?.roles ?? [])];
|
|
119
|
+
const promises: Promise<PermissionOverwritesValue>[] = [
|
|
120
|
+
// Global user overwrites
|
|
121
|
+
this.getOverwrites({}, 'User', userId).then(v => v.overwrites),
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
if (location.spaceId && (await this.tracker.spaces.get())?.has(location.spaceId)) {
|
|
125
|
+
const filterLocation: ChatLocation = {spaceId: location.spaceId};
|
|
126
|
+
promises.push(this.collectRoleOverwrites(filterLocation, userRoles));
|
|
127
|
+
promises.push(this.getOverwrites(filterLocation, 'User', userId).then(v => v.overwrites));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (location.roomId && (await this.tracker.rooms.get())?.has(location.roomId)) {
|
|
131
|
+
const filterLocation: ChatLocation = {spaceId: location.spaceId, roomId: location.roomId};
|
|
132
|
+
if (userRoles.length) {
|
|
133
|
+
promises.push(this.collectRoleOverwrites(filterLocation, userRoles));
|
|
134
|
+
}
|
|
135
|
+
promises.push(this.getOverwrites(filterLocation, 'User', userId).then(v => v.overwrites));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (location.topicId && (await this.tracker.rooms.getTopics(location.roomId))?.has(location.topicId)) {
|
|
139
|
+
if (userRoles.length) {
|
|
140
|
+
promises.push(this.collectRoleOverwrites(location, userRoles));
|
|
141
|
+
}
|
|
142
|
+
promises.push(this.getOverwrites(location, 'User', userId).then(v => v.overwrites));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this.resolveOverwritesHierarchy(await Promise.all(promises));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private handlePermissionOverwrites(ev: PermissionOverwritesUpdated | PermissionOverwrites): void {
|
|
149
|
+
this.overwrites.set([getOvIdByObject(ev), ev]);
|
|
150
|
+
this.emit('change');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private handleSpaceDeleted(ev: SpaceDeleted | SpaceLeft): void {
|
|
154
|
+
const ids = this.deleteOverwritesByIdPrefix(getOvId({spaceId: ev.id}));
|
|
155
|
+
this.overwritesPromises.forget(...ids);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async handleRoomDeleted(ev: RoomDeleted | RoomLeft): Promise<void> {
|
|
159
|
+
const room = (await this.tracker.rooms.get()).get(ev.id);
|
|
160
|
+
if (room) {
|
|
161
|
+
const ids = this.deleteOverwritesByIdPrefix(getOvId({spaceId: room.spaceId, roomId: room.id}));
|
|
162
|
+
this.overwritesPromises.forget(...ids);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private handleTopicDeleted(ev: TopicDeleted): void {
|
|
167
|
+
const ids = this.deleteOverwritesByIdPrefix(getOvId(ev.location));
|
|
168
|
+
this.overwritesPromises.forget(...ids);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private handleRoleDeleted(ev: RoleDeleted): void {
|
|
172
|
+
const ids = this.deleteOverwritesByIdPrefix(getOvId({spaceId: ev.spaceId}, 'Role', ev.id));
|
|
173
|
+
this.overwritesPromises.forget(...ids);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private handleSpaceMemberUpdated(ev: SpaceMemberUpdated): void {
|
|
177
|
+
if (ev.userId === this.tracker.me?.id) {
|
|
178
|
+
// User roles in space could potentially have changed
|
|
179
|
+
this.emit('change');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private handleRoomMemberUpdated(ev: RoomMemberUpdated): void {
|
|
184
|
+
if (ev.userId === this.tracker.me?.id) {
|
|
185
|
+
// User roles in room could potentially have changed
|
|
186
|
+
this.emit('change');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @return Matched and deleted ids
|
|
192
|
+
*/
|
|
193
|
+
private deleteOverwritesByIdPrefix(prefix: string): string[] {
|
|
194
|
+
const ids: string[] = [];
|
|
195
|
+
this.overwrites.items.forEach((overwrites) => {
|
|
196
|
+
const id = getOvIdByObject(overwrites);
|
|
197
|
+
if (id.startsWith(prefix)) {
|
|
198
|
+
ids.push(id);
|
|
199
|
+
this.overwrites.delete(id);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return ids;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async collectRoleOverwrites(
|
|
206
|
+
location: ChatLocation,
|
|
207
|
+
userRoles: string[],
|
|
208
|
+
): Promise<PermissionOverwritesValue> {
|
|
209
|
+
const roleOverwrites = await Promise.all(userRoles.map(
|
|
210
|
+
roleId => this.getOverwrites(location, 'Role', roleId)
|
|
211
|
+
));
|
|
212
|
+
|
|
213
|
+
return this.resolveOverwritesFromRolesByOrder(location.spaceId, roleOverwrites);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async resolveOverwritesFromRolesByOrder(
|
|
217
|
+
spaceId: string,
|
|
218
|
+
overwrites: PermissionOverwrites[],
|
|
219
|
+
): Promise<PermissionOverwritesValue> {
|
|
220
|
+
let allows = 0, denies = 0;
|
|
221
|
+
const roles = await this.tracker.spaces.getRoles(spaceId);
|
|
222
|
+
const sortedOverwrites = overwrites.sort(
|
|
223
|
+
(a, b) =>
|
|
224
|
+
roles.get(a.targetId).priority - roles.get(b.targetId).priority
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Max length of bit word
|
|
228
|
+
const permissionsLength = overwrites.reduce(
|
|
229
|
+
(previousValue: number, currentValue: PermissionOverwrites) =>
|
|
230
|
+
Math.max(
|
|
231
|
+
previousValue,
|
|
232
|
+
currentValue.overwrites.allow?.toString(2).length ?? 0,
|
|
233
|
+
currentValue.overwrites.deny?.toString(2).length ?? 0,
|
|
234
|
+
),
|
|
235
|
+
0,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
sortedOverwrites.forEach(overwriteEvent => {
|
|
239
|
+
const overwrites = overwriteEvent.overwrites;
|
|
240
|
+
const revDecDenies = overwrites.deny?.toString(2).split('').reverse().join('') ?? '';
|
|
241
|
+
const revDecAllows = overwrites.allow?.toString(2).split('').reverse().join('') ?? '';
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < permissionsLength; i++) {
|
|
244
|
+
const deny = parseInt(revDecDenies[i] ?? '0');
|
|
245
|
+
const allow = parseInt(revDecAllows[i] ?? '0');
|
|
246
|
+
|
|
247
|
+
if (deny) {
|
|
248
|
+
denies |= 1 << i;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (allow) {
|
|
252
|
+
allows |= 1 << i;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return {allow: allows, deny: denies};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private resolveOverwritesHierarchy(permissionOverwritesValues: PermissionOverwritesValue[]): number {
|
|
261
|
+
let result = 0;
|
|
262
|
+
|
|
263
|
+
for (const value of permissionOverwritesValues) {
|
|
264
|
+
if (value.allow & Permissions.getByName('Root').value) {
|
|
265
|
+
return this.getRootAccessValue();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
result = (result & ~value.deny) | value.allow;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private getRootAccessValue(): number {
|
|
275
|
+
let result = 0;
|
|
276
|
+
|
|
277
|
+
for (const name of Permissions.getNames()) {
|
|
278
|
+
result |= Permissions.getByName(name).value;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async fetchMembersOrFail(location: ChatLocation): Promise<[SpaceMember | null, RoomMember | null]> {
|
|
285
|
+
const results = await Promise.all([
|
|
286
|
+
location.spaceId ? this.tracker.spaces.getMe(location.spaceId) : null,
|
|
287
|
+
location.roomId ? this.tracker.rooms.getMe(location.roomId) : null,
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
const spaceFail = location.spaceId && ! results[0];
|
|
291
|
+
const roomFail = location.roomId && ! results[1];
|
|
292
|
+
|
|
293
|
+
if (spaceFail || roomFail) {
|
|
294
|
+
const layer = spaceFail ? `space (${location.spaceId})` : `room (${location.roomId})`;
|
|
295
|
+
throw new Error(`Attempting to calculate permissions for a ${layer} that the user does not belong to`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private validateLocation(location: ChatLocation): void {
|
|
302
|
+
if (location.topicId && ! location.roomId) {
|
|
303
|
+
throw new Error('Corrupted arguments hierarchy');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|