insite-users-server 1.0.334

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,212 @@
1
+ import {
2
+ AbilitiesPublication,
3
+ RolesPublication,
4
+ UserPublication,
5
+ UsersPublication,
6
+ UsersExtendedPublication,
7
+ OrgsPublication,
8
+ OrgsExtendedPublication
9
+ } from "./publications";
10
+
11
+
12
+ export class UsersServer {
13
+ constructor(wss, options = {}) {
14
+ wss.usersServer = this;
15
+
16
+ const {
17
+ users,
18
+ publication: publicationOptions,
19
+ extendedPublication: extendedPublicationOptions,
20
+ userPublication: userPublicationOptions,
21
+ roles = {},
22
+ orgs = {},
23
+ getSessionProps
24
+ } = options;
25
+
26
+ const {
27
+ publication: rolesPublicationOptions
28
+ } = roles;
29
+
30
+ const {
31
+ publication: orgsPublicationOptions,
32
+ extendedPublication: orgsExtendedPublicationOptions
33
+ } = orgs;
34
+
35
+ this.users = users;
36
+ this.getSessionProps = getSessionProps;
37
+
38
+ for (const user of users.values())
39
+ UsersServer.handleUserCreate(user);
40
+
41
+ users.abilities.publication = new AbilitiesPublication(users.abilities);
42
+
43
+ users.roles.publication = new RolesPublication(users.roles, rolesPublicationOptions);
44
+ users.on("roles-update", UsersServer.handleRolesUpdate);
45
+ users.on("roles-role-update", UsersServer.handleRolesRoleUpdate);
46
+
47
+ users.publication = new UsersPublication(users, publicationOptions);
48
+ users.extendedPublication = new UsersExtendedPublication(users, extendedPublicationOptions);
49
+ users.userPublication = new UserPublication(users, userPublicationOptions);
50
+ users.on("user-create", UsersServer.handleUserCreate);
51
+ users.on("user-is-online", UsersServer.handleUserIsOnline);
52
+
53
+ users.on("session-delete", this.#handleSessionDelete);
54
+
55
+ users.orgs.publication = new OrgsPublication(users.orgs, orgsPublicationOptions);
56
+ users.orgs.extendedPublication = new OrgsExtendedPublication(users.orgs, orgsExtendedPublicationOptions);
57
+ users.on("orgs-update", UsersServer.handleOrgsUpdate);
58
+ users.on("orgs-org-update", UsersServer.handleOrgsOrgUpdate);
59
+
60
+ wss.options.WebSocket.prototype.login = UsersServer.login;
61
+ wss.options.WebSocket.prototype.logout = UsersServer.logout;
62
+ wss.options.WebSocket.prototype.setSession = UsersServer.setSession;
63
+
64
+ wss.on("client-connect", UsersServer.handleClientConnect);
65
+
66
+ if (wss.requestListener) {
67
+ wss.on("client-request:login", UsersServer.handleClientRequestLogin);
68
+ wss.on("client-request:logout", UsersServer.handleClientRequestLogout);
69
+ }
70
+
71
+ wss.on("client-closed", UsersServer.handleClientClosed);
72
+
73
+ }
74
+
75
+ wsBySessions = new Map();
76
+
77
+ getDefaultSessionProps({ userAgent, remoteAddress }) {
78
+ return {
79
+ userAgent,
80
+ remoteAddress
81
+ };
82
+ }
83
+
84
+
85
+ static handleUserCreate(user) {
86
+ user.webSockets = new Set();
87
+
88
+ }
89
+
90
+ static handleUserIsOnline({ _id, isOnline }) {
91
+ const updates = { _id, isOnline };
92
+
93
+ for (const usersSubscription of this.publication.subscriptions) {
94
+ const [ ws ] = usersSubscription.args;
95
+ if (ws.user && ws.user._id != _id)
96
+ usersSubscription.handler([ [ "u"/* update */, updates, true ] ]);
97
+ }
98
+
99
+ }
100
+
101
+ #handleSessionDelete = session => this.wsBySessions.get(session)?.setSession(null);
102
+
103
+ static handleRolesUpdate() {
104
+ this.roles.publication.flushInitial();
105
+
106
+ }
107
+
108
+ static handleRolesRoleUpdate(_, next) {
109
+ if (next)
110
+ this.roles.publication.skip(next);
111
+
112
+ }
113
+
114
+ static handleOrgsUpdate() {
115
+
116
+ this.orgs.publication.flushInitial();
117
+ this.orgs.extendedPublication.flushInitial();
118
+
119
+ }
120
+
121
+ static handleOrgsOrgUpdate(_, next) {
122
+ if (next) {
123
+ this.orgs.publication.skip(next);
124
+ this.orgs.extendedPublication.skip(next);
125
+ }
126
+
127
+ }
128
+
129
+
130
+ static handleClientConnect(ws) {
131
+ ws.session = null;
132
+
133
+ }
134
+
135
+ static handleClientRequestLogin(ws, email, password) {
136
+ return ws.login(email, password);
137
+ }
138
+
139
+ static handleClientRequestLogout(ws) {
140
+ return ws.logout();
141
+ }
142
+
143
+ static handleClientClosed(ws) {
144
+ ws.session?.offline();
145
+ ws.setSession(null);
146
+
147
+ }
148
+
149
+
150
+ static async login(email, password) {
151
+
152
+ const { usersServer } = this.wss;
153
+
154
+ const session = await usersServer.users.login(email, password, {
155
+ ...usersServer.getDefaultSessionProps(this),
156
+ ...usersServer.getSessionProps?.(this),
157
+ isOnline: true
158
+ });
159
+
160
+ if (session)
161
+ this.setSession(session);
162
+
163
+ }
164
+
165
+ static logout() {
166
+ if (this.session) {
167
+ this.wss.usersServer.users.logout(this.session);
168
+ this.setSession(null);
169
+ }
170
+
171
+ }
172
+
173
+
174
+ static setSession(session, shouldProlong) {
175
+
176
+ const { usersServer } = this.wss;
177
+
178
+ if (typeof session == "string")
179
+ session = usersServer.users.sessions.get(session) ?? null;
180
+
181
+ if (this.session != session) {
182
+ if (this.session)
183
+ usersServer.wsBySessions.delete(this.session);
184
+
185
+ if (session) {
186
+ this.session = session;
187
+ this.user = session.user;
188
+ this.lastUserId = this.user._id;
189
+
190
+ this.user.webSockets.add(this);
191
+ usersServer.wsBySessions.set(session, this);
192
+
193
+ if (shouldProlong)
194
+ session.prolong({
195
+ ...usersServer.getDefaultSessionProps(this),
196
+ ...usersServer.getSessionProps?.(this),
197
+ isOnline: true
198
+ });
199
+
200
+ } else {
201
+ this.user.webSockets.delete(this);
202
+
203
+ this.session = null;
204
+ delete this.user;
205
+ }
206
+
207
+ this.wss.emit("client-session", this, shouldProlong);
208
+ }
209
+
210
+ }
211
+
212
+ }
@@ -0,0 +1 @@
1
+ export * from "./UsersServer";
@@ -0,0 +1,258 @@
1
+ import { Publication, CollectionMapPublication } from "insite-subscriptions-server";
2
+
3
+
4
+ export class AbilitiesPublication extends Publication {
5
+ constructor(abilities) {
6
+ super("abilities", {
7
+
8
+ fetch({ user }) {
9
+ if (user?.abilities.login?.sections?.includes("users"))
10
+ return { abilities: abilities.getSchemeFor(user) };
11
+ }
12
+
13
+ });
14
+ }
15
+ }
16
+
17
+
18
+ export class RolesPublication extends CollectionMapPublication {
19
+ constructor(roles, options = {}) {
20
+
21
+ const {
22
+ projection = { title: 1, description: 1 },
23
+ sort = { _o: 1 },
24
+ transform
25
+ } = options;
26
+
27
+ Object.assign(projection, { involves: 1, abilities: 1 });
28
+
29
+ super("roles", roles.collection, ws => ws.user?.abilities.login?.sections?.includes("users") && {
30
+ query: { _id: { $in: ws.user.slaveRoleIds } },
31
+ projection,
32
+ sort
33
+ }, roleDoc => {
34
+ const { involves, abilities, inheritedAbilities, displayTitle, _o } = roles.get(roleDoc._id);
35
+
36
+ if (roleDoc.involves) {
37
+ roleDoc.ownInvolves = roleDoc.involves;
38
+ roleDoc.involves = involves.ids();
39
+ roleDoc.abilities = abilities;
40
+ roleDoc.inheritedAbilities = inheritedAbilities;
41
+ }
42
+
43
+ if (projection.title)
44
+ roleDoc.displayTitle = displayTitle;
45
+
46
+ roleDoc._o = _o;
47
+
48
+ transform?.(roleDoc);
49
+
50
+ });
51
+
52
+ }
53
+
54
+ }
55
+
56
+
57
+ export class UserPublication extends Publication {
58
+ constructor(users, options = {}) {
59
+
60
+ const {
61
+ fieldsToUpdate = [],
62
+ projection,
63
+ transform
64
+ } = options;
65
+
66
+ for (const key of [
67
+ "email",
68
+ "name",
69
+ "name.first",
70
+ "name.middle",
71
+ "name.last",
72
+ "org",
73
+ "job",
74
+ "avatar"
75
+ ])
76
+ if (!projection || projection[key] !== 0)
77
+ fieldsToUpdate.push(key);
78
+
79
+ super("user", {
80
+
81
+ onSubscribe(subscription) {
82
+ const [ { user } ] = subscription.args;
83
+
84
+ if (user) {
85
+ const { _id } = user;
86
+
87
+ subscription.changeListener = next => {
88
+ if (next.documentKey._id == _id)
89
+ switch (next.operationType) {
90
+ case "update":
91
+ if (!Object.keys(next.updateDescription.updatedFields).includesAny(fieldsToUpdate))
92
+ break;
93
+
94
+ case "replace":
95
+ case "delete":
96
+ subscription.changed(next);
97
+ }
98
+
99
+ };
100
+
101
+ users.collection.changeListeners.add(subscription.changeListener);
102
+ }
103
+
104
+ },
105
+
106
+ fetch({ user, session }, isSessionIdRequired) {
107
+ if (user) {
108
+ const userDoc = Object.pick(user, "_id", "email", "name", "initials", "displayLabel", "job", "avatarUrl", "abilities", "slaveIds");
109
+
110
+ userDoc.orgId = user.org._id;
111
+ userDoc.sessionId = isSessionIdRequired ? session._id : undefined;
112
+ userDoc.isOnline = true;
113
+
114
+ if (projection)
115
+ for (const key in projection)
116
+ if (projection[key])
117
+ userDoc[key] = user[key];
118
+ else
119
+ delete userDoc[key];
120
+
121
+ transform?.(userDoc);
122
+
123
+ return userDoc;
124
+ }
125
+ },
126
+
127
+ onUnsubscribe(subscription) {
128
+ if (subscription.changeListener)
129
+ users.collection.changeListeners.delete(subscription.changeListener);
130
+
131
+ }
132
+
133
+ });
134
+
135
+ }
136
+ }
137
+
138
+ export class UsersPublication extends CollectionMapPublication {
139
+ constructor(users, options = {}) {
140
+
141
+ const {
142
+ projection = { email: 1, name: 1, org: 1, job: 1, avatar: 1 },
143
+ sort = { "name.last": 1 },
144
+ transform
145
+ } = options;
146
+
147
+ super("users", users.collection, ws => ws.user?.abilities.login && {
148
+ query: {},
149
+ projection,
150
+ sort
151
+ }, userDoc => {
152
+ ({
153
+ initials: userDoc.initials,
154
+ displayLabel: userDoc.displayLabel,
155
+ avatarUrl: userDoc.avatarUrl,
156
+ isOnline: userDoc.isOnline
157
+ } = users.get(userDoc._id));
158
+
159
+ userDoc.orgId = userDoc.org;
160
+
161
+ delete userDoc.org;
162
+ delete userDoc.avatar;
163
+
164
+ transform?.(userDoc);
165
+
166
+ });
167
+
168
+ }
169
+ }
170
+
171
+ export class UsersExtendedPublication extends CollectionMapPublication {
172
+ constructor(users, options = {}) {
173
+
174
+ const {
175
+ projection = { _id: 1 },
176
+ sort,
177
+ triggers = [],
178
+ transform
179
+ } = options;
180
+
181
+ if (!triggers.includes("roles"))
182
+ triggers.push("roles");
183
+
184
+ super("users.extended", users.collection, ws => ws.user?.abilities.login?.sections?.includes("users") && {
185
+ query: { _id: { $in: ws.user.slaveIds } },
186
+ projection,
187
+ sort,
188
+ triggers
189
+ }, (userDoc, [ ws ]) => {
190
+ userDoc.roleIds = users.get(userDoc._id).ownRoleIds.intersection(ws.user.slaveRoleIds);
191
+
192
+ transform?.(userDoc);
193
+
194
+ });
195
+
196
+ }
197
+ }
198
+
199
+
200
+ export class OrgsPublication extends CollectionMapPublication {
201
+ constructor(orgs, options = {}) {
202
+
203
+ const {
204
+ projection = { title: 1 },
205
+ sort = { title: 1 },
206
+ transform
207
+ } = options;
208
+
209
+ super("orgs", orgs.collection, ws => ws.user?.abilities.login && {
210
+ query: {},
211
+ projection,
212
+ sort
213
+ }, orgDoc => {
214
+ ({
215
+ initials: orgDoc.initials,
216
+ displayLabel: orgDoc.displayLabel
217
+ } = orgs.get(orgDoc._id));
218
+
219
+ transform?.(orgDoc);
220
+
221
+ });
222
+
223
+ }
224
+
225
+ }
226
+
227
+ export class OrgsExtendedPublication extends CollectionMapPublication {
228
+ constructor(orgs, options = {}) {
229
+
230
+ const {
231
+ projection = { note: 1 },
232
+ sort = { _o: 1 },
233
+ triggers = [],
234
+ transform
235
+ } = options;
236
+
237
+ if (!triggers.includes("owners"))
238
+ triggers.push("owners");
239
+
240
+ super("orgs.extended", orgs.collection, ws => ws.user?.abilities.login?.sections?.includes("users") && {
241
+ query: { _id: { $in: ws.user.slaveIds } },
242
+ projection,
243
+ sort,
244
+ triggers
245
+ }, (orgDoc, [ ws ]) => {
246
+ const { ownerIds, slaveOrgs, _o } = orgs.get(orgDoc._id);
247
+
248
+ orgDoc.owners = ownerIds.intersection(ws.user.slaveIds);
249
+ orgDoc.slaveOrgs = slaveOrgs.intersection(ws.user.slaveOrgs).ids();
250
+ orgDoc._o = _o;
251
+
252
+ transform?.(orgDoc);
253
+
254
+ });
255
+
256
+ }
257
+
258
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "insite-users-server",
3
+ "version": "1.0.334",
4
+ "description": "inSite users server side",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "module": "./lib/index.js",
8
+ "exports": {
9
+ ".": "./lib/index.js",
10
+ "./ws": "./lib/ws/index.js"
11
+ },
12
+ "packageManager": "yarn@1.22.22",
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/nesvet/insite-users-server.git"
19
+ },
20
+ "keywords": [
21
+ "insite",
22
+ "users",
23
+ "server"
24
+ ],
25
+ "author": "Eugene Nesvetaev <dev@nesvet.dev> (https://nesvet.dev/)",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@nesvet/n": "^1",
29
+ "@nesvet/deprecated-extensions": "^1",
30
+ "insite-db": "^1",
31
+ "insite-subscriptions-server": "^1",
32
+ "argon2": "^0",
33
+ "eventemitter3": "^5"
34
+ },
35
+ "devDependencies": {
36
+ "@nesvet/eslint-config": "^1"
37
+ }
38
+ }