polfan-server-js-client 0.1.24 → 0.1.25

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.
@@ -0,0 +1,59 @@
1
+ import { ObservableIndexedObjectCollection } from "../IndexedObjectCollection";
2
+ import { Message, Room, RoomMember, SpaceMember, Topic } from "pserv-ts-types";
3
+ import { ChatStateTracker } from "./ChatStateTracker";
4
+ export declare class RoomsManager {
5
+ private tracker;
6
+ private readonly list;
7
+ private readonly topics;
8
+ private readonly topicsMessages;
9
+ private readonly members;
10
+ private readonly deferredSession;
11
+ constructor(tracker: ChatStateTracker);
12
+ /**
13
+ * Get collection of room members.
14
+ */
15
+ getMembers(roomId: string): Promise<ObservableIndexedObjectCollection<RoomMember> | null>;
16
+ /**
17
+ * Get a room member representing the current user.
18
+ */
19
+ getMe(roomId: string): Promise<RoomMember | null>;
20
+ /**
21
+ * Get collection of all the rooms you are in.
22
+ */
23
+ get(): Promise<ObservableIndexedObjectCollection<Room>>;
24
+ /**
25
+ * Get collection of room topics.
26
+ */
27
+ getTopics(roomId: string): Promise<ObservableIndexedObjectCollection<Topic> | null>;
28
+ /**
29
+ * Get collection of the messages written in topic.
30
+ */
31
+ getMessages(topicId: string): Promise<ObservableIndexedObjectCollection<Message> | null>;
32
+ /**
33
+ * For internal use. If you want to leave the room, execute a proper command on client object.
34
+ * @internal
35
+ */
36
+ _delete(...roomIds: string[]): void;
37
+ /**
38
+ * For internal use. If you want to leave the room, execute a proper command on client object.
39
+ * @internal
40
+ */
41
+ _deleteBySpaceId(spaceId: string): void;
42
+ /**
43
+ * For internal use.
44
+ * @internal
45
+ */
46
+ _handleSpaceMemberUpdate(spaceId: string, member: SpaceMember): void;
47
+ private handleRoomMemberUpdated;
48
+ private handleTopicDeleted;
49
+ private handleNewMessage;
50
+ private handleNewTopic;
51
+ private addJoinedRoomTopics;
52
+ private handleRoomJoined;
53
+ private addJoinedRooms;
54
+ private handleRoomLeft;
55
+ private handleRoomMemberJoined;
56
+ private handleRoomMemberLeft;
57
+ private handleRoomMembers;
58
+ private handleSession;
59
+ }
@@ -0,0 +1,47 @@
1
+ import { ChatStateTracker } from "./ChatStateTracker";
2
+ import { ObservableIndexedObjectCollection } from "../IndexedObjectCollection";
3
+ import { Role, RoomSummary, Space, SpaceMember } from "pserv-ts-types";
4
+ export declare class SpacesManager {
5
+ private tracker;
6
+ private readonly list;
7
+ private readonly roles;
8
+ private readonly rooms;
9
+ private readonly members;
10
+ private readonly deferredSession;
11
+ constructor(tracker: ChatStateTracker);
12
+ /**
13
+ * Get collection of all the spaces you are in.
14
+ */
15
+ get(): Promise<ObservableIndexedObjectCollection<Space>>;
16
+ /**
17
+ * Get collection of space roles.
18
+ */
19
+ getRoles(spaceId: string): Promise<ObservableIndexedObjectCollection<Role> | null>;
20
+ /**
21
+ * Get collection of the all available rooms inside given space.
22
+ */
23
+ getRooms(spaceId: string): Promise<ObservableIndexedObjectCollection<RoomSummary> | null>;
24
+ /**
25
+ * Get collection of space members.
26
+ */
27
+ getMembers(spaceId: string): Promise<ObservableIndexedObjectCollection<SpaceMember> | null>;
28
+ /**
29
+ * Get a space member representing the current user.
30
+ */
31
+ getMe(spaceId: string): Promise<SpaceMember | null>;
32
+ private handleNewRole;
33
+ private handleNewRoom;
34
+ private handleRoomDeleted;
35
+ private handleRoleDeleted;
36
+ private handleSpaceDeleted;
37
+ private handleSpaceJoined;
38
+ private addJoinedSpaces;
39
+ private handleSpaceLeft;
40
+ private handleSpaceMemberJoined;
41
+ private handleSpaceMemberLeft;
42
+ private handleSpaceMembers;
43
+ private handleSpaceRooms;
44
+ private handleSpaceMemberUpdated;
45
+ private handleRoleUpdated;
46
+ private handleSession;
47
+ }
package/index.html ADDED
@@ -0,0 +1,6 @@
1
+ <html>
2
+ <head>
3
+ <script src="build/index.js"></script>
4
+ </head>
5
+ <body></body>
6
+ </html>
package/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "polfan-server-js-client",
3
- "version": "0.1.24",
4
- "description": "JavaScript client library for handling communication with Polfan chat server.",
5
- "author": "Jarosław Żak",
6
- "license": "MIT",
7
- "main": "build/index.js",
8
- "types": "build/types/index.d.ts",
9
- "scripts": {
10
- "start": "webpack serve --config webpack.config.demo.js",
11
- "build": "webpack && tsc",
12
- "build:demo": "webpack --config webpack.config.demo.js",
13
- "test": "jest --silent",
14
- "coverage": "npm run test -- --coverage",
15
- "prepare": "npm run build",
16
- "trypublish": "npm publish || true"
17
- },
18
- "devDependencies": {
19
- "@babel/cli": "^7.20.7",
20
- "@babel/core": "^7.20.12",
21
- "@babel/plugin-proposal-class-properties": "^7.16.0",
22
- "@babel/plugin-transform-typescript": "^7.20.2",
23
- "@babel/polyfill": "^7.12.1",
24
- "@babel/preset-env": "^7.20.2",
25
- "@types/jest": "^29.2.5",
26
- "@typescript-eslint/eslint-plugin": "^4.33.0",
27
- "@typescript-eslint/parser": "^4.33.0",
28
- "babel-eslint": "^10.1.0",
29
- "babel-loader": "^9.1.2",
30
- "babel-preset-minify": "^0.5.2",
31
- "css-loader": "^6.7.3",
32
- "css-minimizer-webpack-plugin": "^4.2.2",
33
- "eslint": "^7.32.0",
34
- "file-loader": "^6.2.0",
35
- "html-webpack-plugin": "^5.5.0",
36
- "jest": "^29.3.1",
37
- "mini-css-extract-plugin": "^2.7.2",
38
- "style-loader": "^3.3.1",
39
- "terser-webpack-plugin": "^5.3.5",
40
- "typescript": "^4.9.4",
41
- "url-loader": "^4.1.1",
42
- "webpack": "^5.75.0",
43
- "webpack-cli": "^5.0.1",
44
- "webpack-dev-server": "4.11.1",
45
- "pserv-ts-types": "^0.0.15"
46
- },
47
- "jest": {
48
- "moduleNameMapper": {
49
- "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/scripts/testMock.js",
50
- "\\.(css|less)$": "<rootDir>/scripts/testMock.js"
51
- },
52
- "moduleFileExtensions": [
53
- "web.js",
54
- "js",
55
- "web.ts",
56
- "ts",
57
- "web.tsx",
58
- "tsx",
59
- "json",
60
- "web.jsx",
61
- "jsx",
62
- "node"
63
- ]
64
- }
65
- }
1
+ {
2
+ "name": "polfan-server-js-client",
3
+ "version": "0.1.25",
4
+ "description": "JavaScript client library for handling communication with Polfan chat server.",
5
+ "author": "Jarosław Żak",
6
+ "license": "MIT",
7
+ "main": "build/index.js",
8
+ "types": "build/types/index.d.ts",
9
+ "scripts": {
10
+ "start": "webpack serve --config webpack.config.demo.js",
11
+ "build": "webpack && tsc",
12
+ "build:demo": "webpack --config webpack.config.demo.js",
13
+ "test": "jest --silent",
14
+ "coverage": "npm run test -- --coverage",
15
+ "prepare": "npm run build",
16
+ "trypublish": "npm publish || true"
17
+ },
18
+ "devDependencies": {
19
+ "@babel/cli": "^7.20.7",
20
+ "@babel/core": "^7.20.12",
21
+ "@babel/plugin-proposal-class-properties": "^7.16.0",
22
+ "@babel/plugin-transform-typescript": "^7.20.2",
23
+ "@babel/polyfill": "^7.12.1",
24
+ "@babel/preset-env": "^7.20.2",
25
+ "@types/jest": "^29.2.5",
26
+ "@typescript-eslint/eslint-plugin": "^4.33.0",
27
+ "@typescript-eslint/parser": "^4.33.0",
28
+ "babel-eslint": "^10.1.0",
29
+ "babel-loader": "^9.1.2",
30
+ "babel-preset-minify": "^0.5.2",
31
+ "css-loader": "^6.7.3",
32
+ "css-minimizer-webpack-plugin": "^4.2.2",
33
+ "eslint": "^7.32.0",
34
+ "file-loader": "^6.2.0",
35
+ "html-webpack-plugin": "^5.5.0",
36
+ "jest": "^29.3.1",
37
+ "mini-css-extract-plugin": "^2.7.2",
38
+ "style-loader": "^3.3.1",
39
+ "terser-webpack-plugin": "^5.3.5",
40
+ "typescript": "^4.9.4",
41
+ "url-loader": "^4.1.1",
42
+ "webpack": "^5.75.0",
43
+ "webpack-cli": "^5.0.1",
44
+ "webpack-dev-server": "4.11.1",
45
+ "pserv-ts-types": "^0.0.21"
46
+ },
47
+ "jest": {
48
+ "moduleNameMapper": {
49
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/scripts/testMock.js",
50
+ "\\.(css|less)$": "<rootDir>/scripts/testMock.js"
51
+ },
52
+ "moduleFileExtensions": [
53
+ "web.js",
54
+ "js",
55
+ "web.ts",
56
+ "ts",
57
+ "web.tsx",
58
+ "tsx",
59
+ "json",
60
+ "web.jsx",
61
+ "jsx",
62
+ "node"
63
+ ]
64
+ }
65
+ }
@@ -2,14 +2,13 @@ import {
2
2
  Bye,
3
3
  GetSession,
4
4
  JoinSpace,
5
- Ok,
6
5
  Session,
7
6
  SpaceJoined,
8
7
  Error as ErrorType,
9
8
  SpaceLeft,
10
9
  SpaceMemberJoined,
11
10
  SpaceMemberLeft,
12
- SpaceMemberUpdate,
11
+ SpaceMemberUpdated,
13
12
  SpaceDeleted,
14
13
  SpaceMembers,
15
14
  SpaceRooms,
@@ -24,8 +23,7 @@ import {
24
23
  NewTopic,
25
24
  TopicDeleted,
26
25
  NewMessage,
27
- GetUserPermissions,
28
- SetUserPermissions,
26
+ GetPermissionOverwrites,
29
27
  GetComputedPermissions,
30
28
  LeaveSpace,
31
29
  CreateSpace,
@@ -36,8 +34,7 @@ import {
36
34
  DeleteRole,
37
35
  AssignRole,
38
36
  DeassignRole,
39
- SetRolePermissions,
40
- GetRolePermissions,
37
+ SetPermissionOverwrites,
41
38
  JoinRoom,
42
39
  LeaveRoom,
43
40
  CreateRoom,
@@ -45,7 +42,13 @@ import {
45
42
  GetRoomMembers,
46
43
  CreateTopic,
47
44
  DeleteTopic,
48
- CreateMessage, Envelope,
45
+ CreateMessage,
46
+ Envelope,
47
+ PermissionOverwrites,
48
+ PermissionOverwritesChanged,
49
+ RoomMemberUpdated,
50
+ UpdateRole,
51
+ RoleUpdated,
49
52
  } from "pserv-ts-types";
50
53
  import {EventTarget} from "./EventTarget";
51
54
 
@@ -110,26 +113,29 @@ export type CommandResult<ResultT> = {data?: ResultT, error?: Error};
110
113
  export type EventsMap = {
111
114
  // General Events
112
115
  Bye: Bye,
113
- Ok: Ok,
114
116
  Error: ErrorType,
115
117
  Session: Session,
116
118
  Permissions: Permissions,
119
+ PermissionOverwrites: PermissionOverwrites,
120
+ PermissionOverwritesChanged: PermissionOverwritesChanged,
117
121
  // Space events
118
122
  SpaceJoined: SpaceJoined,
119
123
  SpaceLeft: SpaceLeft,
120
124
  SpaceMemberJoined: SpaceMemberJoined,
121
125
  SpaceMemberLeft: SpaceMemberLeft,
122
- SpaceMemberUpdate: SpaceMemberUpdate,
126
+ SpaceMemberUpdated: SpaceMemberUpdated,
123
127
  SpaceDeleted: SpaceDeleted,
124
128
  SpaceMembers: SpaceMembers,
125
129
  SpaceRooms: SpaceRooms,
126
130
  NewRole: NewRole,
127
131
  RoleDeleted: RoomDeleted,
132
+ RoleUpdated: RoleUpdated,
128
133
  // Room events
129
134
  RoomJoined: RoomJoined,
130
135
  RoomLeft: RoomLeft,
131
136
  RoomMemberJoined: RoomMemberJoined,
132
137
  RoomMemberLeft: RoomMemberLeft,
138
+ RoomMemberUpdated: RoomMemberUpdated,
133
139
  RoomMembers: RoomMembers,
134
140
  NewRoom: NewRoom,
135
141
  RoomDeleted: RoomDeleted,
@@ -145,8 +151,8 @@ export type EventsMap = {
145
151
  export type CommandsMap = {
146
152
  // General commands
147
153
  GetSession: [GetSession, EventsMap['Session']],
148
- SetUserPermissions: [SetUserPermissions, EventsMap['Permissions']],
149
- GetUserPermissions: [GetUserPermissions, EventsMap['Permissions']],
154
+ SetPermissionOverwrites: [SetPermissionOverwrites, EventsMap['PermissionOverwritesChanged']],
155
+ GetPermissionOverwrites: [GetPermissionOverwrites, EventsMap['PermissionOverwrites']],
150
156
  GetComputedPermissions: [GetComputedPermissions, EventsMap['Permissions']],
151
157
  // Space commands
152
158
  JoinSpace: [JoinSpace, EventsMap['SpaceJoined']],
@@ -157,10 +163,9 @@ export type CommandsMap = {
157
163
  GetSpaceRooms: [GetSpaceRooms, EventsMap['SpaceRooms']],
158
164
  CreateRole: [CreateRole, EventsMap['NewRole']],
159
165
  DeleteRole: [DeleteRole, EventsMap['RoleDeleted']],
160
- AssignRole: [AssignRole, EventsMap['SpaceMemberUpdate']],
161
- DeassignRole: [DeassignRole, EventsMap['SpaceMemberUpdate']],
162
- SetRolePermissions: [SetRolePermissions, EventsMap['Permissions']],
163
- GetRolePermissions: [GetRolePermissions, EventsMap['Permissions']],
166
+ UpdateRole: [UpdateRole, EventsMap['RoleUpdated']],
167
+ AssignRole: [AssignRole, EventsMap['SpaceMemberUpdated'] | EventsMap['RoomMemberUpdated']],
168
+ DeassignRole: [DeassignRole, EventsMap['SpaceMemberUpdated'] | EventsMap['RoomMemberUpdated']],
164
169
  // Room commands
165
170
  JoinRoom: [JoinRoom, EventsMap['RoomJoined']],
166
171
  LeaveRoom: [LeaveRoom, EventsMap['RoomLeft']],
@@ -0,0 +1,15 @@
1
+ export enum Permission {
2
+ Root = 1 << 0,
3
+ CreateSpaces = 1 << 1,
4
+ ManageSpaces = 1 << 2,
5
+ ManageRoles = 1 << 3,
6
+ ChangeNick = 1 << 4,
7
+ ManageRooms = 1 << 5,
8
+ ManageTopics = 1 << 6,
9
+ ManageMembers = 1 << 7,
10
+ SendMessages = 1 << 8,
11
+ ViewMessages = 1 << 9,
12
+ ChangeMessages = 1 << 10,
13
+ ManageMessages = 1 << 11,
14
+ ManagePermissions = 1 << 12,
15
+ }
@@ -1,7 +1,7 @@
1
1
  import {ObservableInterface} from "./EventTarget";
2
2
  import {AbstractChatClient, CommandResult, CommandsMap} from "./AbstractChatClient";
3
3
  import {Envelope} from "pserv-ts-types";
4
- import {ChatStateTracker} from "./ChatStateTracker";
4
+ import {ChatStateTracker} from "./state-tracker/ChatStateTracker";
5
5
 
6
6
  export interface WebSocketClientOptions {
7
7
  url: string;
package/src/index.ts CHANGED
@@ -7,10 +7,12 @@ import {
7
7
  ObservableIndexedObjectCollection
8
8
  } from "./IndexedObjectCollection";
9
9
  import { AuthClient } from "./AuthClient";
10
+ import { Permission } from "./Permission";
10
11
 
11
12
  export {
12
13
  IndexedCollection, ObservableIndexedCollection,
13
14
  IndexedObjectCollection, ObservableIndexedObjectCollection,
15
+ Permission,
14
16
  WebSocketChatClient, WebApiChatClient,
15
17
  AuthClient
16
18
  };
@@ -0,0 +1,38 @@
1
+ import {WebSocketChatClient} from "../WebSocketChatClient";
2
+ import {Session, User} from "pserv-ts-types";
3
+ import {RoomsManager} from "./RoomsManager";
4
+ import {SpacesManager} from "./SpacesManager";
5
+ import {PermissionsManager} from "./PermissionsManager";
6
+ import {DeferredTask} from "./DeferredTask";
7
+
8
+ export class ChatStateTracker {
9
+ /**
10
+ * State of the rooms you are in.
11
+ */
12
+ public readonly rooms: RoomsManager = new RoomsManager(this);
13
+ /**
14
+ * State of the spaces you are in.
15
+ */
16
+ public readonly spaces = new SpacesManager(this);
17
+ /**
18
+ * State of your permissions.
19
+ */
20
+ public readonly permissions = new PermissionsManager(this);
21
+
22
+ private me: User = null;
23
+ private readonly deferredSession = new DeferredTask();
24
+
25
+ public constructor(public readonly client: WebSocketChatClient) {
26
+ this.client.on('Session', ev => this.handleSession(ev));
27
+ }
28
+
29
+ public async getMe(): Promise<User> {
30
+ await this.deferredSession.promise;
31
+ return this.me;
32
+ }
33
+
34
+ private handleSession(ev: Session): void {
35
+ this.me = ev.user;
36
+ this.deferredSession.resolve();
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ export class DeferredTask {
2
+ public readonly promise: Promise<void>;
3
+ public resolve: () => void;
4
+
5
+ public constructor() {
6
+ this.promise = new Promise<void>(resolve => this.resolve = resolve);
7
+ }
8
+ }
@@ -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 | null> {
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
+ }