polfan-server-js-client 0.1.2 → 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.
Files changed (207) hide show
  1. package/.gitmodules +3 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  3. package/.idea/polfan-server-js-client.iml +3 -1
  4. package/.idea/vcs.xml +1 -1
  5. package/README.md +17 -17
  6. package/build/index.js +1968 -462
  7. package/build/index.js.map +1 -1
  8. package/build/types/AbstractChatClient.d.ts +26 -9
  9. package/build/types/IndexedObjectCollection.d.ts +3 -3
  10. package/build/types/Permissions.d.ts +65 -0
  11. package/build/types/WebSocketChatClient.d.ts +1 -1
  12. package/build/types/index.d.ts +4 -1
  13. package/build/types/state-tracker/AsyncUtils.d.ts +14 -0
  14. package/build/types/state-tracker/ChatStateTracker.d.ts +26 -0
  15. package/build/types/state-tracker/MessagesManager.d.ts +39 -0
  16. package/build/types/state-tracker/PermissionsManager.d.ts +41 -0
  17. package/build/types/state-tracker/RoomsManager.d.ts +49 -0
  18. package/build/types/state-tracker/SpacesManager.d.ts +52 -0
  19. package/build/types/state-tracker/functions.d.ts +2 -0
  20. package/build/types/types/src/index.d.ts +82 -0
  21. package/build/types/types/src/schemes/AckReport.d.ts +7 -0
  22. package/build/types/types/src/schemes/ChatLocation.d.ts +5 -0
  23. package/build/types/types/src/schemes/Envelope.d.ts +5 -0
  24. package/build/types/types/src/schemes/Message.d.ts +7 -0
  25. package/build/types/types/src/schemes/PermissionOverwritesValue.d.ts +4 -0
  26. package/build/types/types/src/schemes/Role.d.ts +6 -0
  27. package/build/types/types/src/schemes/Room.d.ts +10 -0
  28. package/build/types/types/src/schemes/RoomMember.d.ts +7 -0
  29. package/build/types/types/src/schemes/RoomSummary.d.ts +5 -0
  30. package/build/types/types/src/schemes/Space.d.ts +6 -0
  31. package/build/types/types/src/schemes/SpaceMember.d.ts +5 -0
  32. package/build/types/types/src/schemes/Topic.d.ts +5 -0
  33. package/build/types/types/src/schemes/User.d.ts +8 -0
  34. package/build/types/types/src/schemes/UserState.d.ts +6 -0
  35. package/build/types/types/src/schemes/commands/Ack.d.ts +5 -0
  36. package/build/types/types/src/schemes/commands/AssignRole.d.ts +6 -0
  37. package/build/types/types/src/schemes/commands/CreateMessage.d.ts +5 -0
  38. package/build/types/types/src/schemes/commands/CreateOwner.d.ts +5 -0
  39. package/build/types/types/src/schemes/commands/CreateRole.d.ts +5 -0
  40. package/build/types/types/src/schemes/commands/CreateRoom.d.ts +7 -0
  41. package/build/types/types/src/schemes/commands/CreateSpace.d.ts +3 -0
  42. package/build/types/types/src/schemes/commands/CreateTopic.d.ts +12 -0
  43. package/build/types/types/src/schemes/commands/DeassignRole.d.ts +6 -0
  44. package/build/types/types/src/schemes/commands/DeleteOwner.d.ts +5 -0
  45. package/build/types/types/src/schemes/commands/DeleteRole.d.ts +4 -0
  46. package/build/types/types/src/schemes/commands/DeleteRoom.d.ts +3 -0
  47. package/build/types/types/src/schemes/commands/DeleteSpace.d.ts +3 -0
  48. package/build/types/types/src/schemes/commands/DeleteTopic.d.ts +4 -0
  49. package/build/types/types/src/schemes/commands/GetAckReports.d.ts +4 -0
  50. package/build/types/types/src/schemes/commands/GetComputedPermissions.d.ts +4 -0
  51. package/build/types/types/src/schemes/commands/GetOwners.d.ts +4 -0
  52. package/build/types/types/src/schemes/commands/GetPermissionOverwriteTargets.d.ts +4 -0
  53. package/build/types/types/src/schemes/commands/GetPermissionOverwrites.d.ts +6 -0
  54. package/build/types/types/src/schemes/commands/GetRoomMembers.d.ts +3 -0
  55. package/build/types/types/src/schemes/commands/GetSession.d.ts +2 -0
  56. package/build/types/types/src/schemes/commands/GetSpaceMembers.d.ts +3 -0
  57. package/build/types/types/src/schemes/commands/GetSpaceRooms.d.ts +3 -0
  58. package/build/types/types/src/schemes/commands/JoinRoom.d.ts +3 -0
  59. package/build/types/types/src/schemes/commands/JoinSpace.d.ts +3 -0
  60. package/build/types/types/src/schemes/commands/LeaveRoom.d.ts +3 -0
  61. package/build/types/types/src/schemes/commands/LeaveSpace.d.ts +3 -0
  62. package/build/types/types/src/schemes/commands/SetPermissionOverwrites.d.ts +8 -0
  63. package/build/types/types/src/schemes/commands/UpdateRole.d.ts +7 -0
  64. package/build/types/types/src/schemes/commands/UpdateRoom.d.ts +5 -0
  65. package/build/types/types/src/schemes/commands/UpdateSpace.d.ts +4 -0
  66. package/build/types/types/src/schemes/events/AckReports.d.ts +4 -0
  67. package/build/types/types/src/schemes/events/Bye.d.ts +3 -0
  68. package/build/types/types/src/schemes/events/ComputedPermissions.d.ts +5 -0
  69. package/build/types/types/src/schemes/events/Error.d.ts +4 -0
  70. package/build/types/types/src/schemes/events/NewMessage.d.ts +6 -0
  71. package/build/types/types/src/schemes/events/NewRole.d.ts +5 -0
  72. package/build/types/types/src/schemes/events/NewRoom.d.ts +5 -0
  73. package/build/types/types/src/schemes/events/NewTopic.d.ts +5 -0
  74. package/build/types/types/src/schemes/events/Ok.d.ts +2 -0
  75. package/build/types/types/src/schemes/events/Owners.d.ts +8 -0
  76. package/build/types/types/src/schemes/events/PermissionOverwriteTargets.d.ts +10 -0
  77. package/build/types/types/src/schemes/events/PermissionOverwrites.d.ts +8 -0
  78. package/build/types/types/src/schemes/events/PermissionOverwritesUpdated.d.ts +8 -0
  79. package/build/types/types/src/schemes/events/RoleDeleted.d.ts +4 -0
  80. package/build/types/types/src/schemes/events/RoleUpdated.d.ts +5 -0
  81. package/build/types/types/src/schemes/events/RoomDeleted.d.ts +3 -0
  82. package/build/types/types/src/schemes/events/RoomJoined.d.ts +4 -0
  83. package/build/types/types/src/schemes/events/RoomLeft.d.ts +3 -0
  84. package/build/types/types/src/schemes/events/RoomMemberJoined.d.ts +5 -0
  85. package/build/types/types/src/schemes/events/RoomMemberLeft.d.ts +4 -0
  86. package/build/types/types/src/schemes/events/RoomMemberUpdated.d.ts +6 -0
  87. package/build/types/types/src/schemes/events/RoomMembers.d.ts +5 -0
  88. package/build/types/types/src/schemes/events/RoomUpdated.d.ts +4 -0
  89. package/build/types/types/src/schemes/events/Session.d.ts +7 -0
  90. package/build/types/types/src/schemes/events/SpaceDeleted.d.ts +3 -0
  91. package/build/types/types/src/schemes/events/SpaceJoined.d.ts +4 -0
  92. package/build/types/types/src/schemes/events/SpaceLeft.d.ts +3 -0
  93. package/build/types/types/src/schemes/events/SpaceMemberJoined.d.ts +5 -0
  94. package/build/types/types/src/schemes/events/SpaceMemberLeft.d.ts +4 -0
  95. package/build/types/types/src/schemes/events/SpaceMemberUpdated.d.ts +6 -0
  96. package/build/types/types/src/schemes/events/SpaceMembers.d.ts +5 -0
  97. package/build/types/types/src/schemes/events/SpaceRooms.d.ts +5 -0
  98. package/build/types/types/src/schemes/events/SpaceUpdated.d.ts +4 -0
  99. package/build/types/types/src/schemes/events/TopicDeleted.d.ts +4 -0
  100. package/build/types/types/src/schemes/events/UserUpdated.d.ts +4 -0
  101. package/jest.config.ts +199 -0
  102. package/package.json +4 -22
  103. package/src/AbstractChatClient.ts +50 -16
  104. package/src/AbstractRestClient.ts +65 -65
  105. package/src/IndexedObjectCollection.ts +3 -3
  106. package/src/Permissions.ts +46 -0
  107. package/src/WebApiChatClient.ts +1 -1
  108. package/src/WebSocketChatClient.ts +2 -2
  109. package/src/index.ts +7 -2
  110. package/src/state-tracker/AsyncUtils.ts +38 -0
  111. package/src/state-tracker/ChatStateTracker.ts +42 -0
  112. package/src/state-tracker/MessagesManager.ts +154 -0
  113. package/src/state-tracker/PermissionsManager.ts +306 -0
  114. package/src/state-tracker/RoomsManager.ts +272 -0
  115. package/src/state-tracker/SpacesManager.ts +267 -0
  116. package/src/state-tracker/functions.ts +25 -0
  117. package/src/types/README.md +2 -0
  118. package/src/types/package-lock.json +31 -0
  119. package/src/types/package.json +16 -0
  120. package/src/types/src/index.ts +168 -0
  121. package/src/types/src/schemes/AckReport.ts +7 -0
  122. package/src/types/src/schemes/ChatLocation.ts +5 -0
  123. package/src/types/src/schemes/Envelope.ts +5 -0
  124. package/src/types/src/schemes/Message.ts +8 -0
  125. package/src/types/src/schemes/PermissionOverwritesValue.ts +4 -0
  126. package/src/types/src/schemes/Role.ts +6 -0
  127. package/src/types/src/schemes/Room.ts +12 -0
  128. package/src/types/src/schemes/RoomMember.ts +8 -0
  129. package/src/types/src/schemes/RoomSummary.ts +5 -0
  130. package/src/types/src/schemes/Space.ts +7 -0
  131. package/src/types/src/schemes/SpaceMember.ts +6 -0
  132. package/src/types/src/schemes/Topic.ts +5 -0
  133. package/src/types/src/schemes/User.ts +9 -0
  134. package/src/types/src/schemes/UserState.ts +7 -0
  135. package/src/types/src/schemes/commands/Ack.ts +6 -0
  136. package/src/types/src/schemes/commands/AssignRole.ts +7 -0
  137. package/src/types/src/schemes/commands/CreateMessage.ts +6 -0
  138. package/src/types/src/schemes/commands/CreateOwner.ts +6 -0
  139. package/src/types/src/schemes/commands/CreateRole.ts +5 -0
  140. package/src/types/src/schemes/commands/CreateRoom.ts +8 -0
  141. package/src/types/src/schemes/commands/CreateSpace.ts +3 -0
  142. package/src/types/src/schemes/commands/CreateTopic.ts +14 -0
  143. package/src/types/src/schemes/commands/DeassignRole.ts +7 -0
  144. package/src/types/src/schemes/commands/DeleteOwner.ts +6 -0
  145. package/src/types/src/schemes/commands/DeleteRole.ts +4 -0
  146. package/src/types/src/schemes/commands/DeleteRoom.ts +3 -0
  147. package/src/types/src/schemes/commands/DeleteSpace.ts +3 -0
  148. package/src/types/src/schemes/commands/DeleteTopic.ts +5 -0
  149. package/src/types/src/schemes/commands/GetAckReports.ts +5 -0
  150. package/src/types/src/schemes/commands/GetComputedPermissions.ts +5 -0
  151. package/src/types/src/schemes/commands/GetOwners.ts +5 -0
  152. package/src/types/src/schemes/commands/GetPermissionOverwriteTargets.ts +5 -0
  153. package/src/types/src/schemes/commands/GetPermissionOverwrites.ts +7 -0
  154. package/src/types/src/schemes/commands/GetRoomMembers.ts +3 -0
  155. package/src/types/src/schemes/commands/GetSession.ts +2 -0
  156. package/src/types/src/schemes/commands/GetSpaceMembers.ts +3 -0
  157. package/src/types/src/schemes/commands/GetSpaceRooms.ts +3 -0
  158. package/src/types/src/schemes/commands/JoinRoom.ts +3 -0
  159. package/src/types/src/schemes/commands/JoinSpace.ts +3 -0
  160. package/src/types/src/schemes/commands/LeaveRoom.ts +3 -0
  161. package/src/types/src/schemes/commands/LeaveSpace.ts +3 -0
  162. package/src/types/src/schemes/commands/SetPermissionOverwrites.ts +9 -0
  163. package/src/types/src/schemes/commands/UpdateRole.ts +7 -0
  164. package/src/types/src/schemes/commands/UpdateRoom.ts +5 -0
  165. package/src/types/src/schemes/commands/UpdateSpace.ts +4 -0
  166. package/src/types/src/schemes/events/AckReports.ts +5 -0
  167. package/src/types/src/schemes/events/Bye.ts +3 -0
  168. package/src/types/src/schemes/events/ComputedPermissions.ts +6 -0
  169. package/src/types/src/schemes/events/Error.ts +4 -0
  170. package/src/types/src/schemes/events/NewMessage.ts +7 -0
  171. package/src/types/src/schemes/events/NewRole.ts +6 -0
  172. package/src/types/src/schemes/events/NewRoom.ts +6 -0
  173. package/src/types/src/schemes/events/NewTopic.ts +6 -0
  174. package/src/types/src/schemes/events/Ok.ts +3 -0
  175. package/src/types/src/schemes/events/Owners.ts +9 -0
  176. package/src/types/src/schemes/events/PermissionOverwriteTargets.ts +11 -0
  177. package/src/types/src/schemes/events/PermissionOverwrites.ts +9 -0
  178. package/src/types/src/schemes/events/PermissionOverwritesUpdated.ts +9 -0
  179. package/src/types/src/schemes/events/RoleDeleted.ts +4 -0
  180. package/src/types/src/schemes/events/RoleUpdated.ts +6 -0
  181. package/src/types/src/schemes/events/RoomDeleted.ts +3 -0
  182. package/src/types/src/schemes/events/RoomJoined.ts +5 -0
  183. package/src/types/src/schemes/events/RoomLeft.ts +3 -0
  184. package/src/types/src/schemes/events/RoomMemberJoined.ts +6 -0
  185. package/src/types/src/schemes/events/RoomMemberLeft.ts +4 -0
  186. package/src/types/src/schemes/events/RoomMemberUpdated.ts +7 -0
  187. package/src/types/src/schemes/events/RoomMembers.ts +6 -0
  188. package/src/types/src/schemes/events/RoomUpdated.ts +5 -0
  189. package/src/types/src/schemes/events/Session.ts +8 -0
  190. package/src/types/src/schemes/events/SpaceDeleted.ts +3 -0
  191. package/src/types/src/schemes/events/SpaceJoined.ts +5 -0
  192. package/src/types/src/schemes/events/SpaceLeft.ts +3 -0
  193. package/src/types/src/schemes/events/SpaceMemberJoined.ts +6 -0
  194. package/src/types/src/schemes/events/SpaceMemberLeft.ts +4 -0
  195. package/src/types/src/schemes/events/SpaceMemberUpdated.ts +7 -0
  196. package/src/types/src/schemes/events/SpaceMembers.ts +6 -0
  197. package/src/types/src/schemes/events/SpaceRooms.ts +6 -0
  198. package/src/types/src/schemes/events/SpaceUpdated.ts +5 -0
  199. package/src/types/src/schemes/events/TopicDeleted.ts +5 -0
  200. package/src/types/src/schemes/events/UserUpdated.ts +5 -0
  201. package/src/types/tsconfig.json +75 -0
  202. package/tests/async-utils.test.ts +30 -0
  203. package/tests/permissions.test.ts +14 -0
  204. package/tests/space-roles.test.ts +43 -0
  205. package/build/types/ChatStateTracker.d.ts +0 -54
  206. package/index.html +0 -8
  207. package/src/ChatStateTracker.ts +0 -336
@@ -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
+ }
@@ -0,0 +1,272 @@
1
+ import {IndexedCollection, ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
2
+ import {
3
+ NewTopic,
4
+ Room, RoomDeleted,
5
+ RoomJoined, RoomLeft,
6
+ RoomMember, RoomMemberJoined, RoomMemberLeft, RoomMembers,
7
+ RoomMemberUpdated, RoomUpdated, Session, SpaceDeleted, SpaceLeft, SpaceMemberLeft,
8
+ SpaceMemberUpdated,
9
+ Topic,
10
+ TopicDeleted,
11
+ UserUpdated,
12
+ } from "../types/src";
13
+ import {ChatStateTracker} from "./ChatStateTracker";
14
+ import {DeferredTask, PromiseRegistry} from "./AsyncUtils";
15
+ import {MessagesManager} from "./MessagesManager";
16
+
17
+ export class RoomsManager {
18
+ public readonly messages: MessagesManager;
19
+
20
+ private readonly list = new ObservableIndexedObjectCollection<Room>('id');
21
+ private readonly topics = new IndexedCollection<string, ObservableIndexedObjectCollection<Topic>>();
22
+ private readonly members = new IndexedCollection<string, ObservableIndexedObjectCollection<RoomMember>>();
23
+ private readonly deferredSession = new DeferredTask();
24
+ private readonly membersPromises = new PromiseRegistry();
25
+
26
+ public constructor(private tracker: ChatStateTracker) {
27
+ this.messages = new MessagesManager(tracker);
28
+
29
+ this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
30
+ this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
31
+ this.tracker.client.on('RoomJoined', ev => this.handleRoomJoined(ev));
32
+ this.tracker.client.on('RoomLeft', ev => this.handleRoomLeft(ev));
33
+ this.tracker.client.on('RoomUpdated', ev => this.handleRoomUpdated(ev));
34
+ this.tracker.client.on('RoomDeleted', ev => this.handleRoomDeleted(ev));
35
+ this.tracker.client.on('RoomMemberJoined', ev => this.handleRoomMemberJoined(ev));
36
+ this.tracker.client.on('RoomMemberLeft', ev => this.handleRoomMemberLeft(ev));
37
+ this.tracker.client.on('RoomMembers', ev => this.handleRoomMembers(ev));
38
+ this.tracker.client.on('RoomMemberUpdated', ev => this.handleRoomMemberUpdated(ev));
39
+ this.tracker.client.on('SpaceMemberLeft', ev => this.handleSpaceMemberLeft(ev));
40
+ this.tracker.client.on('SpaceMemberUpdated', ev => this.handleSpaceMemberUpdated(ev));
41
+ this.tracker.client.on('SpaceDeleted', ev => this.handleSpaceDeleted(ev));
42
+ this.tracker.client.on('SpaceLeft', ev => this.handleSpaceDeleted(ev));
43
+ this.tracker.client.on('UserUpdated', ev => this.handleUserUpdated(ev));
44
+ this.tracker.client.on('Session', ev => this.handleSession(ev));
45
+ }
46
+
47
+ /**
48
+ * Get collection of room members.
49
+ */
50
+ public async getMembers(roomId: string): Promise<ObservableIndexedObjectCollection<RoomMember> | undefined> {
51
+ if (this.membersPromises.notExist(roomId)) {
52
+ this.membersPromises.registerByFunction(async () => {
53
+ const result = await this.tracker.client.send('GetRoomMembers', {id: roomId});
54
+ if (result.error) {
55
+ throw result.error;
56
+ }
57
+ this.handleRoomMembers(result.data);
58
+ }, roomId);
59
+ }
60
+
61
+ await this.membersPromises.get(roomId);
62
+ return this.members.get(roomId);
63
+ }
64
+
65
+ /**
66
+ * Get a room member representing the current user.
67
+ */
68
+ public async getMe(roomId: string): Promise<RoomMember | undefined> {
69
+ const userId = (await this.tracker.getMe()).id;
70
+
71
+ if (! this.list.has(roomId)) {
72
+ // User is not in passed room.
73
+ return undefined;
74
+ }
75
+
76
+ const members = await this.getMembers(roomId);
77
+ return members.items.find(member => (member.user?.id ?? member.spaceMember.user.id) === userId);
78
+ }
79
+
80
+ /**
81
+ * Get collection of all the rooms you are in.
82
+ */
83
+ public async get(): Promise<ObservableIndexedObjectCollection<Room>> {
84
+ await this.deferredSession.promise;
85
+ return this.list;
86
+ }
87
+
88
+ /**
89
+ * Get collection of room topics.
90
+ */
91
+ public async getTopics(roomId: string): Promise<ObservableIndexedObjectCollection<Topic> | undefined> {
92
+ await this.deferredSession.promise;
93
+ return this.topics.get(roomId);
94
+ }
95
+
96
+ private deleteRoom(...roomIds: string[]): void {
97
+ this.list.delete(...roomIds);
98
+ this.members.delete(...roomIds);
99
+ this.membersPromises.forget(...roomIds);
100
+
101
+ for (const roomId of roomIds) {
102
+ const topicIds: string[] = this.topics.get(roomId)?.map(topic => topic.id) ?? [];
103
+ this.messages._deleteByTopicIds(roomId, ...topicIds);
104
+ }
105
+
106
+ this.topics.delete(...roomIds);
107
+ }
108
+
109
+ private deleteRoomsBySpaceId(spaceId: string): void {
110
+ this.deleteRoom(
111
+ ...this.list.findBy('spaceId', spaceId).map(room => room.id)
112
+ );
113
+ }
114
+
115
+ private handleSpaceMemberUpdated(ev: SpaceMemberUpdated): void {
116
+ // Update members of rooms related to this space
117
+ for (const room of this.list.findBy('spaceId', ev.spaceId).items) {
118
+ const roomMembers = this.members.get(room.id);
119
+
120
+ if (! roomMembers || ! roomMembers.has(ev.userId)) {
121
+ // Skip update if member list for this room is not loaded
122
+ // or user is not in room
123
+ continue;
124
+ }
125
+
126
+ const roomMember = roomMembers.get(ev.userId);
127
+ const user = roomMember.spaceMember.user;
128
+
129
+ // Update space member but first fill user object (it's null in event object)
130
+ roomMember.spaceMember = {...ev.member, user};
131
+ roomMembers.set(roomMember);
132
+ }
133
+ }
134
+
135
+ private handleSpaceMemberLeft(ev: SpaceMemberLeft): void {
136
+ this.list
137
+ .findBy('spaceId', ev.spaceId).items
138
+ .forEach(room => this.members.get(room.id)?.delete(ev.userId));
139
+ }
140
+
141
+ private handleRoomMemberUpdated(ev: RoomMemberUpdated): void {
142
+ if (! this.members.has(ev.roomId)) {
143
+ // We do not track member list for this room.
144
+ return;
145
+ }
146
+
147
+ const members = this.members.get(ev.roomId);
148
+ const member = members.get(ev.userId);
149
+ const newMember = ev.member;
150
+ const user = member.spaceMember?.user ?? member.user;
151
+
152
+ if (newMember.spaceMember) {
153
+ newMember.spaceMember.user = user;
154
+ } else {
155
+ newMember.user = user;
156
+ }
157
+
158
+ members.set(newMember);
159
+ }
160
+
161
+ private handleSpaceDeleted(ev: SpaceDeleted | SpaceLeft): void {
162
+ this.deleteRoomsBySpaceId(ev.id);
163
+ }
164
+
165
+ private handleTopicDeleted(ev: TopicDeleted): void {
166
+ const collection = this.topics.get(ev.location.roomId);
167
+ collection.delete(ev.location.topicId);
168
+
169
+ const room = this.list.get(ev.location.roomId);
170
+ if (room.defaultTopic?.id === ev.location.topicId) {
171
+ this.list.set({...room, defaultTopic: null});
172
+ }
173
+ }
174
+
175
+ private handleNewTopic(ev: NewTopic): void {
176
+ this.addJoinedRoomTopics(ev.roomId, ev.topic);
177
+ }
178
+
179
+ private addJoinedRoomTopics(roomId: string, ...topics: Topic[]): void {
180
+ if (this.topics.has(roomId)) {
181
+ this.topics.get(roomId).set(...topics);
182
+ } else {
183
+ this.topics.set([roomId, new ObservableIndexedObjectCollection<Topic>('id', topics)]);
184
+ }
185
+
186
+ this.messages._handleNewTopics(roomId, ...topics);
187
+ }
188
+
189
+ private handleRoomJoined(ev: RoomJoined): void {
190
+ this.addJoinedRooms(ev.room);
191
+ }
192
+
193
+ private handleRoomUpdated(ev: RoomUpdated): void {
194
+ this.list.set({
195
+ ...this.list.get(ev.room.id),
196
+ name: ev.room.name,
197
+ description: ev.room.description,
198
+ } as Room);
199
+ }
200
+
201
+ private handleRoomDeleted(ev: RoomDeleted): void {
202
+ this.deleteRoom(ev.id);
203
+ }
204
+
205
+ private addJoinedRooms(...rooms: Room[]): void {
206
+ for (const room of rooms) {
207
+ if (room.defaultTopic) {
208
+ this.addJoinedRoomTopics(room.id, room.defaultTopic);
209
+ }
210
+ }
211
+ this.list.set(...rooms);
212
+ }
213
+
214
+ private handleRoomLeft(ev: RoomLeft): void {
215
+ this.deleteRoom(ev.id);
216
+ }
217
+
218
+ private handleRoomMemberJoined(ev: RoomMemberJoined): void {
219
+ if (this.members.has(ev.roomId)) {
220
+ this.members.get(ev.roomId).set(ev.member);
221
+ }
222
+ }
223
+
224
+ private handleRoomMemberLeft(ev: RoomMemberLeft): void {
225
+ if (this.members.has(ev.roomId)) {
226
+ this.members.get(ev.roomId).delete(ev.userId);
227
+ }
228
+ }
229
+
230
+ private handleRoomMembers(ev: RoomMembers): void {
231
+ if (! this.members.has(ev.id)) {
232
+ this.members.set([
233
+ ev.id,
234
+ new ObservableIndexedObjectCollection(
235
+ member => member.user?.id ?? member.spaceMember.user.id,
236
+ ev.members,
237
+ )
238
+ ]);
239
+ }
240
+ }
241
+
242
+ private handleSession(ev: Session): void {
243
+ this.list.deleteAll();
244
+ this.topics.deleteAll();
245
+ this.members.deleteAll();
246
+
247
+ this.addJoinedRooms(...ev.state.rooms);
248
+
249
+ this.deferredSession.resolve();
250
+ }
251
+
252
+ private handleUserUpdated(ev: UserUpdated): void {
253
+ this.members.items.forEach((members) => {
254
+ const member = members.get(ev.user.id);
255
+
256
+ if (! member) {
257
+ // Skip room; updated user is not here
258
+ return;
259
+ }
260
+
261
+ const newMember: RoomMember = {...member};
262
+
263
+ if (member.user) {
264
+ newMember.user = ev.user;
265
+ } else {
266
+ newMember.spaceMember.user = ev.user;
267
+ }
268
+
269
+ members.set(newMember);
270
+ });
271
+ }
272
+ }