@cocreate/socket-server 1.30.0 → 1.31.0

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/src/index.js CHANGED
@@ -1,548 +1,640 @@
1
- const WebSocket = require('ws');
1
+ const WebSocket = require("ws");
2
2
  const { URL } = require("url");
3
3
  const EventEmitter = require("events").EventEmitter;
4
- const uid = require('@cocreate/uuid')
5
- const config = require('@cocreate/config')
4
+ const uid = require("@cocreate/uuid");
5
+ const config = require("@cocreate/config");
6
6
 
7
7
  class SocketServer extends EventEmitter {
8
- constructor(server) {
9
- super();
10
- this.serverId = uid.generate(12)
11
- this.organizations = new Map();
12
- this.clients = new Map();
13
- this.sockets = new Map();
14
- this.users = new Map();
15
-
16
- config({ organization_id: { prompt: 'Enter your organization_id: ' } })
17
-
18
- this.wss = new WebSocket.Server({ noServer: true });
19
-
20
- this.wss.on('headers', (headers, request) => {
21
- headers.push('Access-Control-Allow-Origin: *');
22
- });
23
-
24
- this.wss.on('error', (error) => {
25
- socket.destroy();
26
- });
27
-
28
- server.https.on('upgrade', (request, socket, head) => this.upgrade(request, socket, head, 'wss'))
29
- server.http.on('upgrade', (request, socket, head) => this.upgrade(request, socket, head, 'ws'))
30
- }
31
-
32
- upgrade(request, socket, head, protocol) {
33
- const self = this;
34
- let organization_id = request.url.split('/')
35
- organization_id = organization_id[organization_id.length - 1]
36
-
37
- this.wss.handleUpgrade(request, socket, head, async function (socket) {
38
- if (organization_id) {
39
- let organization = self.organizations.get(organization_id)
40
- if (organization && organization.status === false) {
41
- let errors = {}
42
- errors.serverOrganization = organization.serverOrganization
43
- errors.serverStorage = organization.serverStorage
44
- errors.organizationBalance = organization.organizationBalance
45
- errors.error = organization.error
46
- return socket.send(JSON.stringify({ method: 'Access Denied', error: errors }))
47
- }
48
-
49
- let options = decodeURIComponent(request.headers['sec-websocket-protocol'])
50
- options = JSON.parse(options)
51
-
52
- socket.organization_id = organization_id
53
- socket.id = options.socketId;
54
- socket.clientId = options.clientId;
55
- socket.pathname = request.url
56
- socket.origin = request.headers.origin
57
- socket.host = request.headers.host
58
-
59
- if (!socket.host && socket.origin && socket.origin !== 'null') {
60
- if (socket.origin.includes('://'))
61
- socket.host = new URL(socket.origin).host
62
- else
63
- socket.host = socket.origin;
64
- }
65
-
66
- socket.socketUrl = protocol + '://' + socket.host + socket.pathname
67
-
68
-
69
- // if (!await server.acme.checkCertificate(socket.host, organization_id))
70
- // return socket.send(JSON.stringify({ method: 'Access Denied', error: 'Host not whitelisted' }))
71
-
72
- if (!organization || organization && organization.status !== false) {
73
- let data = {
74
- socket,
75
- method: 'object.read',
76
- host: socket.host,
77
- array: 'message_log',
78
- $filter: {
79
- sort: [
80
- { key: '_id', direction: 'desc' }
81
- ]
82
- },
83
- sync: true,
84
- organization_id
85
- }
86
-
87
- if (options.lastSynced)
88
- data.$filter.query = {
89
- _id: { $gt: options.lastSynced }
90
- }
91
- else
92
- data.$filter.limit = 1
93
-
94
- self.emit('object.read', data);
95
-
96
- if (self.authenticate) {
97
- const { user_id, expires } = await self.authenticate.decodeToken(options.token, organization_id, options.clientId)
98
- const userStatus = { socket, method: 'userStatus', host: socket.host, user_id: options.user_id, clientId: options.clientId, userStatus: 'off', organization_id }
99
- if (user_id) {
100
- options.user_id = user_id
101
- socket.user_id = user_id;
102
- socket.expires = expires;
103
- userStatus.userStatus = 'on'
104
- self.emit("notification.user", socket)
105
- }
106
-
107
- self.emit('userStatus', userStatus);
108
-
109
- self.onWebSocket(socket);
110
-
111
- } else
112
- self.onWebSocket(socket);
113
- }
114
-
115
- } else {
116
- socket.send(JSON.stringify({ method: 'Access Denied', error: 'An organization_id is required' }))
117
- }
118
- })
119
- }
120
-
121
- onWebSocket(socket) {
122
- const self = this;
123
- this.add(socket);
124
-
125
- socket.on('message', async (message) => {
126
- self.onMessage(socket, message);
127
- })
128
-
129
- socket.on('close', () => {
130
- self.delete(socket)
131
- })
132
-
133
- socket.on("error", () => {
134
- self.delete(socket)
135
- });
136
-
137
- socket.send(JSON.stringify({ method: 'connect', connectedKey: socket.pathname }))
138
-
139
- }
140
-
141
- add(socket) {
142
- let organization_id = socket.organization_id
143
-
144
- let organization = this.organizations.get(organization_id)
145
- if (!organization) {
146
- organization = {
147
- status: true,
148
- clients: {}
149
- }
150
-
151
- this.organizations.set(organization_id, organization)
152
-
153
- this.emit('object.update', {
154
- method: 'object.update',
155
- host: socket.host,
156
- array: 'organizations',
157
- object: {
158
- _id: organization_id, ['$addToSet.activeHost']: socket.socketUrl // needs socketId
159
- },
160
- organization_id
161
- });
162
-
163
- this.emit('mesh.create', {
164
- url: socket.socketUrl,
165
- organization_id
166
- });
167
-
168
- } else
169
- clearTimeout(organization.debounce);
170
-
171
-
172
- if (!this.clients.has(socket.clientId)) {
173
- this.clients.set(socket.clientId, {});
174
-
175
- if (!organization.clients)
176
- organization.clients = { [socket.clientId]: {} }
177
- else
178
- organization.clients[socket.clientId] = {}
179
- }
180
-
181
-
182
- this.sockets.set(socket.id, socket);
183
- this.clients.get(socket.clientId)[socket.id] = socket
184
- if (!organization.clients[socket.clientId])
185
- organization.clients[socket.clientId] = { [socket.id]: socket }
186
- else
187
- organization.clients[socket.clientId][socket.id] = socket
188
-
189
- if (socket.user_id) {
190
- this.emit('userStatus', { socket, host: socket.host, user_id: socket.user_id, clientId: socket.clientId, userStatus: 'on', organization_id });
191
- let user = this.users.get(socket.user_id)
192
-
193
- if (!user) {
194
- this.users.set(socket.user_id, { [socket.id]: socket })
195
- } else {
196
- clearTimeout(user)
197
- user[socket.id] = socket
198
- }
199
- }
200
-
201
- }
202
-
203
- get(data) {
204
- let sockets = [], clients
205
- let organization = this.organizations.get(data.organization_id)
206
- if (organization)
207
- clients = organization.clients
208
- else
209
- return []
210
-
211
- if (data.broadcast !== false) {
212
- for (let client of Object.keys(clients)) {
213
- if (data.broadcastSender === false && client === data.clientId) continue
214
-
215
- if (data.broadcastClient) {
216
- if (client === data.clientId && clients[client][data.socket.id])
217
- sockets.push(clients[client][data.socket.id])
218
- else
219
- sockets.push(Object.values(clients[client])[0])
220
- } else
221
- sockets.push(...Array.from(Object.values(clients[client])))
222
- }
223
- } else if (data.broadcastSender !== false) {
224
- if (clients[data.clientId]) {
225
- if (clients[data.clientId][data.socket.id])
226
- sockets.push(clients[data.clientId][data.socket.id])
227
- else
228
- sockets.push(Object.values(clients[data.clientId])[0])
229
- } else
230
- sockets.push(data.socket)
231
- }
232
- return sockets
233
- }
234
-
235
- delete(socket) {
236
- let organization_id = socket.organization_id
237
- if (this.organizations.has(organization_id)) {
238
- let clients = this.organizations.get(organization_id)
239
- if (clients)
240
- clients.clients;
241
- // Check if the client exists
242
- if (clients && clients[socket.clientId]) {
243
- const client = clients[socket.clientId]
244
- delete client[socket.id]
245
-
246
- if (!Object.keys(client).length)
247
- delete clients[socket.clientId];
248
-
249
- // Check if the socket exists in the client's sockets
250
- // const index = client.findIndex(item => item.id === socket.id);
251
- // if (index !== -1) {
252
- // client.splice(index, 1);
253
- // }
254
-
255
- // if (!client.length) {
256
- // delete clients[socket.clientId];
257
- // }
258
-
259
- if (!Object.keys(clients).length) {
260
- this.organizations.delete(socket.organization_id);
261
- this.emit('object.update', {
262
- method: 'object.update',
263
- host: socket.host,
264
- array: 'organizations',
265
- object: {
266
- _id: organization_id, ['$pull.activeHost']: socket.socketUrl
267
- },
268
- organization_id
269
- });
270
-
271
- this.emit('mesh.update', {
272
- url: socket.socketUrl,
273
- organization_id
274
- });
275
- }
276
-
277
- }
278
- }
279
-
280
- if (this.clients.has(socket.clientId)) {
281
- const client = this.clients.get(socket.clientId)
282
- delete client[socket.id]
283
-
284
- if (!Object.keys(client).length)
285
- this.clients.delete(socket.clientId);
286
- }
287
-
288
- if (this.clients.size === 0) {
289
- let organization = this.organizations.get(socket.organization_id)
290
- let debounceTimer
291
- if (organization)
292
- debounceTimer = organization.debounce
293
-
294
- clearTimeout(debounceTimer);
295
- debounceTimer = setTimeout(() => {
296
- this.organizations.delete(socket.organization_id);
297
- }, 10000);
298
-
299
- if (!organization)
300
- this.organizations.set(socket.organization_id, { debounce: debounceTimer })
301
- else
302
- organization.debounce = debounceTimer
303
-
304
- this.sockets.delete(socket.id);
305
-
306
- if (socket.user_id) {
307
- let sockets = this.users.get(socket.user_id)
308
- if (sockets) {
309
- delete sockets[socket.id]
310
- if (!Object.keys(sockets).length) {
311
- let userDebounceTimer = sockets
312
-
313
- clearTimeout(userDebounceTimer);
314
- userDebounceTimer = setTimeout(() => {
315
- this.users.delete(socket.user_id);
316
- this.emit('userStatus', { socket, user_id: socket.user_id, host: socket.host, clientId: socket.clientId, userStatus: 'off', organization_id });
317
- }, 10000);
318
-
319
- this.users.set(socket.user_id, userDebounceTimer)
320
-
321
- }
322
- }
323
- }
324
- }
325
- }
326
-
327
- async onMessage(socket, message) {
328
- try {
329
- this.emit("setBandwidth", {
330
- type: 'in',
331
- data: message,
332
- organization_id: socket.organization_id
333
- });
334
-
335
-
336
- let data = JSON.parse(message)
337
- if (data.method)
338
- this.Message(socket, data)
339
- else {
340
- data.error = 'method is required'
341
- return socket.send(JSON.stringify(data))
342
- }
343
- } catch (e) {
344
- console.log(e);
345
- }
346
- }
347
-
348
- async Message(socket, data) {
349
- try {
350
- const organization_id = socket.organization_id
351
-
352
- const organization = this.organizations.get(organization_id)
353
- if (organization && organization.organizationBalance == false) {
354
- data.organizationBalance = false
355
- data.error = organization.error
356
- return socket.send(JSON.stringify(data))
357
- }
358
-
359
- if (data.method === 'region.added' || data.method === 'region.removed')
360
- console.log('data.method: ', data.method)
361
-
362
- if (socket.user_id && socket.expires && new Date(new Date().toISOString()).getTime() >= socket.expires) {
363
- // data.error = 'Token expired'
364
- // socket.send(JSON.stringify(data))
365
- await this.send({
366
- socket, method: 'updateUserStatus', user_id: socket.user_id, host: socket.host, clientId: data.clientId, userStatus: 'off', socketId: data.socketId, organization_id
367
- })
368
- socket.user_id = socket.expires = null
369
- return
370
- }
371
-
372
- if (this.authorize) {
373
- if (!this.sockets.has(socket.id)) {
374
- if (organization && organization.organizationBalance == false) {
375
- data.organizationBalance = false
376
- data.error = organization.error
377
- return socket.send(JSON.stringify(data))
378
- }
379
- }
380
-
381
- data.socket = socket
382
- data.host = socket.host
383
-
384
- const authorized = await this.authorize.check(data, socket.user_id)
385
- let errors = {}
386
- if (authorized === false) {
387
- delete data.socket
388
- data.error = 'authorization failed'
389
- return socket.send(JSON.stringify(data))
390
- } else if (authorized.serverOrganization === false) {
391
- organization.status = errors.status = false;
392
- organization.serverOrganization = false;
393
- organization.error = authorized.error
394
- } else if (authorized.serverStorage === false) {
395
- data.database = process.env.organization_id
396
- data.organization_id = process.env.organization_id
397
-
398
- const authorized2 = await this.authorize.check(data, req, socket.user_id)
399
- if (!authorized2 || authorized2.error) {
400
- organization.status = errors.status = false;
401
- organization.error = errors.error = authorized.error
402
- if (authorized2.serverOrganization === false) {
403
- organization.serverOrganization = false;
404
- }
405
- if (authorized2.serverStorage === false) {
406
- organization.serverStorage = false;
407
- }
408
- }
409
- } else if (authorized.error) {
410
- organization.status = false;
411
- organization.error = authorized.error
412
- }
413
-
414
- if (organization && organization.status === false) {
415
- let errors = {}
416
- data.serverOrganization = organization.serverOrganization
417
- data.serverStorage = organization.serverStorage
418
- data.organizationBalance = organization.organizationBalance
419
- data.error = organization.error
420
- delete data.socket
421
- return socket.send(JSON.stringify(data))
422
- }
423
-
424
- // dburl is true and db does not have 'keys' array
425
- // action: syncCollection data{array: 'keys', object[]}
426
- // actions: add keys as once keys are added admin user can do anything
427
-
428
-
429
- // }
430
-
431
- if (authorized.authorized)
432
- data = authorized.authorized
433
-
434
- // TODO: handle non module cases where send is required from messages
435
- let moduleName = data.method.split('.')[0]
436
- if (['storage', 'database', 'array', 'index', 'object'].includes(moduleName))
437
- this.emit(data.method, data);
438
- else if (data.method === 'crdt' || data.method === 'cursor')
439
- this.send(data);
440
- else
441
- this.emit(moduleName, data);
442
- }
443
- } catch (e) {
444
- console.log(e);
445
- }
446
- }
447
-
448
- async send(data) {
449
- // const socket = this.sockets.get(data.socketId)
450
- delete data.wsManager
451
- const socket = data.socket
452
-
453
- if (data.sync) {
454
- if (!data.object || !data.object.length)
455
- return
456
-
457
- for (let i = 0; i < data.object.length; i++) {
458
- if (data.object[i].data) {
459
- data.object[i].data._id = data.object[i]._id
460
- data.object[i].data.sync = true
461
-
462
- const authorized = await this.authorize.check(data.object[i].data, socket.user_id)
463
- if (authorized && authorized.authorized)
464
- socket.send(JSON.stringify(authorized.authorized));
465
- else
466
- socket.send(JSON.stringify(data.object[i].data));
467
- } else
468
- console.log('server sync missing data')
469
- }
470
- } else {
471
- const sent = []
472
-
473
- //TODO: get socket using clientId if socket has been deleted from data
474
- const authorized = await this.authorize.check(data, socket.user_id)
475
- if (authorized && authorized.authorized)
476
- data = authorized.authorized
477
-
478
- if (data.log !== false && data.log !== 'false' && !data.method.endsWith('.read') && data.method !== 'updateUserStatus' && data.method !== 'userStatus' && data.method !== 'signIn' && data.method !== 'signUp') {
479
- // TODO: store logged messages more efficently by combing objects wherever possible
480
- let moduleName = data.method.split('.')
481
- if (['storage', 'database', 'array', 'index', 'object'].includes(moduleName[0]) && ['delete'].includes(moduleName[1])) {
482
- let object = { url: socket.socketUrl, data: { ...data } }
483
- delete object.data.socket
484
-
485
- // object.data.socket = { id: object.data.socket.id }
486
- this.emit('object.create', {
487
- method: 'object.create',
488
- host: data.host,
489
- array: 'message_log',
490
- object,
491
- organization_id: data.organization_id
492
- });
493
- }
494
- }
495
-
496
- let sockets = this.get(data);
497
-
498
- // delete data.socket
499
-
500
- for (let i = 0; i < sockets.length; i++) {
501
- if (!sockets[i])
502
- continue
503
-
504
- const authorized = await this.authorize.check(data, sockets[i].user_id)
505
- let Data
506
- if (authorized && authorized.authorized) {
507
- Data = { ...authorized.authorized }
508
- } else {
509
- Data = { ...data }
510
- }
511
-
512
- // TODO: the following code can cause issues in client and improved approach is to check if user has permission and send or dont send
513
- if (Data.$filter && Data.$filter.query && Data.$filter.query._id && Data.$filter.query._id.$eq === '$user_id')
514
- delete Data.$filter.query._id
515
-
516
- delete Data.socket
517
- sockets[i].send(JSON.stringify(Data));
518
-
519
- sent.push(socket.clientId)
520
- this.emit("setBandwidth", {
521
- type: 'out',
522
- data: Data,
523
- organization_id: socket.organization_id
524
- })
525
- }
526
-
527
- // TODO: sent is an array of clientId's so that notification can send to subscribed clients that are not currently connected
528
- this.emit("notification", { sent })
529
- }
530
- }
531
-
532
- getConfigFromUrl(pathname) {
533
- const path = pathname.split("/");
534
- if (path[2]) {
535
- path[2] = decodeURIComponent(path[2]);
536
- path[2] = JSON.parse(path[2]) || {};
537
- }
538
-
539
- const config = {
540
- organization_id: path[1],
541
- ...path[2]
542
- }
543
- return config
544
- }
545
-
8
+ constructor(server) {
9
+ super();
10
+ this.serverId = uid.generate(12);
11
+ this.organizations = new Map();
12
+ this.clients = new Map();
13
+ this.sockets = new Map();
14
+ this.users = new Map();
15
+
16
+ config({ organization_id: { prompt: "Enter your organization_id: " } });
17
+
18
+ this.wss = new WebSocket.Server({ noServer: true });
19
+
20
+ this.wss.on("headers", (headers, request) => {
21
+ headers.push("Access-Control-Allow-Origin: *");
22
+ });
23
+
24
+ this.wss.on("error", (error) => {
25
+ socket.destroy();
26
+ });
27
+
28
+ server.https.on("upgrade", (request, socket, head) =>
29
+ this.upgrade(request, socket, head, "wss")
30
+ );
31
+ server.http.on("upgrade", (request, socket, head) =>
32
+ this.upgrade(request, socket, head, "ws")
33
+ );
34
+ }
35
+
36
+ upgrade(request, socket, head, protocol) {
37
+ const self = this;
38
+ let organization_id = request.url.split("/");
39
+ organization_id = organization_id[organization_id.length - 1];
40
+
41
+ this.wss.handleUpgrade(request, socket, head, async function (socket) {
42
+ if (organization_id) {
43
+ let organization = self.organizations.get(organization_id);
44
+ if (organization && organization.status === false) {
45
+ let errors = {};
46
+ errors.serverOrganization = organization.serverOrganization;
47
+ errors.serverStorage = organization.serverStorage;
48
+ errors.organizationBalance =
49
+ organization.organizationBalance;
50
+ errors.error = organization.error;
51
+ return socket.send(
52
+ JSON.stringify({
53
+ method: "Access Denied",
54
+ error: errors
55
+ })
56
+ );
57
+ }
58
+
59
+ let options = decodeURIComponent(
60
+ request.headers["sec-websocket-protocol"]
61
+ );
62
+ options = JSON.parse(options);
63
+
64
+ socket.organization_id = organization_id;
65
+ socket.id = options.socketId;
66
+ socket.clientId = options.clientId;
67
+ socket.pathname = request.url;
68
+ socket.origin = request.headers.origin;
69
+ socket.host = request.headers.host;
70
+
71
+ if (!socket.host && socket.origin && socket.origin !== "null") {
72
+ if (socket.origin.includes("://"))
73
+ socket.host = new URL(socket.origin).host;
74
+ else socket.host = socket.origin;
75
+ }
76
+
77
+ socket.socketUrl =
78
+ protocol + "://" + socket.host + socket.pathname;
79
+
80
+ // if (!await server.acme.checkCertificate(socket.host, organization_id))
81
+ // return socket.send(JSON.stringify({ method: 'Access Denied', error: 'Host not whitelisted' }))
82
+
83
+ if (
84
+ !organization ||
85
+ (organization && organization.status !== false)
86
+ ) {
87
+ let data = {
88
+ socket,
89
+ method: "object.read",
90
+ host: socket.host,
91
+ array: "message_log",
92
+ $filter: {
93
+ sort: [{ key: "_id", direction: "desc" }]
94
+ },
95
+ sync: true,
96
+ organization_id
97
+ };
98
+
99
+ if (options.lastSynced)
100
+ data.$filter.query = {
101
+ _id: { $gt: options.lastSynced }
102
+ };
103
+ else data.$filter.limit = 1;
104
+
105
+ self.emit("object.read", data);
106
+
107
+ if (self.authenticate) {
108
+ const { user_id, expires } =
109
+ await self.authenticate.decodeToken(
110
+ options.token,
111
+ organization_id,
112
+ options.clientId
113
+ );
114
+ const userStatus = {
115
+ socket,
116
+ method: "userStatus",
117
+ host: socket.host,
118
+ user_id: options.user_id,
119
+ clientId: options.clientId,
120
+ userStatus: "off",
121
+ organization_id
122
+ };
123
+ if (user_id) {
124
+ options.user_id = user_id;
125
+ socket.user_id = user_id;
126
+ socket.expires = expires;
127
+ userStatus.userStatus = "on";
128
+ self.emit("notification.user", socket);
129
+ }
130
+
131
+ self.emit("userStatus", userStatus);
132
+
133
+ self.onWebSocket(socket);
134
+ } else self.onWebSocket(socket);
135
+ }
136
+ } else {
137
+ socket.send(
138
+ JSON.stringify({
139
+ method: "Access Denied",
140
+ error: "An organization_id is required"
141
+ })
142
+ );
143
+ }
144
+ });
145
+ }
146
+
147
+ onWebSocket(socket) {
148
+ const self = this;
149
+ this.add(socket);
150
+
151
+ socket.on("message", async (message) => {
152
+ self.onMessage(socket, message);
153
+ });
154
+
155
+ socket.on("close", () => {
156
+ self.delete(socket);
157
+ });
158
+
159
+ socket.on("error", () => {
160
+ self.delete(socket);
161
+ });
162
+
163
+ socket.send(
164
+ JSON.stringify({ method: "connect", connectedKey: socket.pathname })
165
+ );
166
+ }
167
+
168
+ add(socket) {
169
+ let organization_id = socket.organization_id;
170
+
171
+ let organization = this.organizations.get(organization_id);
172
+ if (!organization) {
173
+ organization = {
174
+ status: true,
175
+ clients: {}
176
+ };
177
+
178
+ this.organizations.set(organization_id, organization);
179
+
180
+ this.emit("object.update", {
181
+ method: "object.update",
182
+ host: socket.host,
183
+ array: "organizations",
184
+ object: {
185
+ _id: organization_id,
186
+ ["$addToSet.activeHost"]: socket.socketUrl // needs socketId
187
+ },
188
+ organization_id
189
+ });
190
+
191
+ this.emit("mesh.create", {
192
+ url: socket.socketUrl,
193
+ organization_id
194
+ });
195
+ } else clearTimeout(organization.debounce);
196
+
197
+ if (!this.clients.has(socket.clientId)) {
198
+ this.clients.set(socket.clientId, {});
199
+
200
+ if (!organization.clients)
201
+ organization.clients = { [socket.clientId]: {} };
202
+ else organization.clients[socket.clientId] = {};
203
+ }
204
+
205
+ this.sockets.set(socket.id, socket);
206
+ this.clients.get(socket.clientId)[socket.id] = socket;
207
+ if (!organization.clients[socket.clientId])
208
+ organization.clients[socket.clientId] = { [socket.id]: socket };
209
+ else organization.clients[socket.clientId][socket.id] = socket;
210
+
211
+ if (socket.user_id) {
212
+ this.emit("userStatus", {
213
+ socket,
214
+ host: socket.host,
215
+ user_id: socket.user_id,
216
+ clientId: socket.clientId,
217
+ userStatus: "on",
218
+ organization_id
219
+ });
220
+ let user = this.users.get(socket.user_id);
221
+
222
+ if (!user) {
223
+ this.users.set(socket.user_id, { [socket.id]: socket });
224
+ } else {
225
+ clearTimeout(user);
226
+ user[socket.id] = socket;
227
+ }
228
+ }
229
+ }
230
+
231
+ get(data) {
232
+ let sockets = [],
233
+ clients;
234
+ let organization = this.organizations.get(data.organization_id);
235
+ if (organization) clients = organization.clients;
236
+ else return [];
237
+
238
+ if (data.broadcast !== false) {
239
+ for (let client of Object.keys(clients)) {
240
+ if (data.broadcastSender === false && client === data.clientId)
241
+ continue;
242
+
243
+ if (data.broadcastClient) {
244
+ if (
245
+ client === data.clientId &&
246
+ clients[client][data.socket.id]
247
+ )
248
+ sockets.push(clients[client][data.socket.id]);
249
+ else sockets.push(Object.values(clients[client])[0]);
250
+ } else
251
+ sockets.push(...Array.from(Object.values(clients[client])));
252
+ }
253
+ } else if (data.broadcastSender !== false) {
254
+ if (clients[data.clientId]) {
255
+ if (clients[data.clientId][data.socket.id])
256
+ sockets.push(clients[data.clientId][data.socket.id]);
257
+ else sockets.push(Object.values(clients[data.clientId])[0]);
258
+ } else sockets.push(data.socket);
259
+ }
260
+ return sockets;
261
+ }
262
+
263
+ delete(socket) {
264
+ let organization_id = socket.organization_id;
265
+ if (this.organizations.has(organization_id)) {
266
+ let clients = this.organizations.get(organization_id);
267
+ if (clients) clients.clients;
268
+ // Check if the client exists
269
+ if (clients && clients[socket.clientId]) {
270
+ const client = clients[socket.clientId];
271
+ delete client[socket.id];
272
+
273
+ if (!Object.keys(client).length)
274
+ delete clients[socket.clientId];
275
+
276
+ // Check if the socket exists in the client's sockets
277
+ // const index = client.findIndex(item => item.id === socket.id);
278
+ // if (index !== -1) {
279
+ // client.splice(index, 1);
280
+ // }
281
+
282
+ // if (!client.length) {
283
+ // delete clients[socket.clientId];
284
+ // }
285
+
286
+ if (!Object.keys(clients).length) {
287
+ this.organizations.delete(socket.organization_id);
288
+ this.emit("object.update", {
289
+ method: "object.update",
290
+ host: socket.host,
291
+ array: "organizations",
292
+ object: {
293
+ _id: organization_id,
294
+ ["$pull.activeHost"]: socket.socketUrl
295
+ },
296
+ organization_id
297
+ });
298
+
299
+ this.emit("mesh.update", {
300
+ url: socket.socketUrl,
301
+ organization_id
302
+ });
303
+ }
304
+ }
305
+ }
306
+
307
+ if (this.clients.has(socket.clientId)) {
308
+ const client = this.clients.get(socket.clientId);
309
+ delete client[socket.id];
310
+
311
+ if (!Object.keys(client).length)
312
+ this.clients.delete(socket.clientId);
313
+ }
314
+
315
+ if (this.clients.size === 0) {
316
+ let organization = this.organizations.get(socket.organization_id);
317
+ let debounceTimer;
318
+ if (organization) debounceTimer = organization.debounce;
319
+
320
+ clearTimeout(debounceTimer);
321
+ debounceTimer = setTimeout(() => {
322
+ this.organizations.delete(socket.organization_id);
323
+ }, 10000);
324
+
325
+ if (!organization)
326
+ this.organizations.set(socket.organization_id, {
327
+ debounce: debounceTimer
328
+ });
329
+ else organization.debounce = debounceTimer;
330
+
331
+ this.sockets.delete(socket.id);
332
+
333
+ if (socket.user_id) {
334
+ let sockets = this.users.get(socket.user_id);
335
+ if (sockets) {
336
+ delete sockets[socket.id];
337
+ if (!Object.keys(sockets).length) {
338
+ let userDebounceTimer = sockets;
339
+
340
+ clearTimeout(userDebounceTimer);
341
+ userDebounceTimer = setTimeout(() => {
342
+ this.users.delete(socket.user_id);
343
+ this.emit("userStatus", {
344
+ socket,
345
+ user_id: socket.user_id,
346
+ host: socket.host,
347
+ clientId: socket.clientId,
348
+ userStatus: "off",
349
+ organization_id
350
+ });
351
+ }, 10000);
352
+
353
+ this.users.set(socket.user_id, userDebounceTimer);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ async onMessage(socket, message) {
361
+ try {
362
+ this.emit("setBandwidth", {
363
+ type: "in",
364
+ data: message,
365
+ organization_id: socket.organization_id
366
+ });
367
+
368
+ let data = JSON.parse(message);
369
+ if (data.method) this.Message(socket, data);
370
+ else {
371
+ data.error = "method is required";
372
+ return socket.send(JSON.stringify(data));
373
+ }
374
+ } catch (e) {
375
+ console.log(e);
376
+ }
377
+ }
378
+
379
+ async Message(socket, data) {
380
+ try {
381
+ const organization_id = socket.organization_id;
382
+
383
+ const organization = this.organizations.get(organization_id);
384
+ if (organization && organization.organizationBalance == false) {
385
+ data.organizationBalance = false;
386
+ data.error = organization.error;
387
+ return socket.send(JSON.stringify(data));
388
+ }
389
+
390
+ if (
391
+ data.method === "region.added" ||
392
+ data.method === "region.removed"
393
+ )
394
+ console.log("data.method: ", data.method);
395
+
396
+ if (
397
+ socket.user_id &&
398
+ socket.expires &&
399
+ new Date(new Date().toISOString()).getTime() >= socket.expires
400
+ ) {
401
+ // data.error = 'Token expired'
402
+ // socket.send(JSON.stringify(data))
403
+ await this.send({
404
+ socket,
405
+ method: "updateUserStatus",
406
+ user_id: socket.user_id,
407
+ host: socket.host,
408
+ clientId: data.clientId,
409
+ userStatus: "off",
410
+ socketId: data.socketId,
411
+ organization_id
412
+ });
413
+ socket.user_id = socket.expires = null;
414
+ return;
415
+ }
416
+
417
+ if (this.authorize) {
418
+ if (!this.sockets.has(socket.id)) {
419
+ if (
420
+ organization &&
421
+ organization.organizationBalance == false
422
+ ) {
423
+ data.organizationBalance = false;
424
+ data.error = organization.error;
425
+ return socket.send(JSON.stringify(data));
426
+ }
427
+ }
428
+
429
+ data.socket = socket;
430
+ data.host = socket.host;
431
+
432
+ const authorized = await this.authorize.check(
433
+ data,
434
+ socket.user_id
435
+ );
436
+ let errors = {};
437
+ if (authorized === false) {
438
+ delete data.socket;
439
+ data.error = "authorization failed";
440
+ return socket.send(JSON.stringify(data));
441
+ } else if (authorized.serverOrganization === false) {
442
+ organization.status = errors.status = false;
443
+ organization.serverOrganization = false;
444
+ organization.error = authorized.error;
445
+ } else if (authorized.serverStorage === false) {
446
+ data.database = process.env.organization_id;
447
+ data.organization_id = process.env.organization_id;
448
+
449
+ const authorized2 = await this.authorize.check(
450
+ data,
451
+ req,
452
+ socket.user_id
453
+ );
454
+ if (!authorized2 || authorized2.error) {
455
+ organization.status = errors.status = false;
456
+ organization.error = errors.error = authorized.error;
457
+ if (authorized2.serverOrganization === false) {
458
+ organization.serverOrganization = false;
459
+ }
460
+ if (authorized2.serverStorage === false) {
461
+ organization.serverStorage = false;
462
+ }
463
+ }
464
+ } else if (authorized.error) {
465
+ organization.status = false;
466
+ organization.error = authorized.error;
467
+ }
468
+
469
+ if (organization && organization.status === false) {
470
+ let errors = {};
471
+ data.serverOrganization = organization.serverOrganization;
472
+ data.serverStorage = organization.serverStorage;
473
+ data.organizationBalance = organization.organizationBalance;
474
+ data.error = organization.error;
475
+ delete data.socket;
476
+ return socket.send(JSON.stringify(data));
477
+ }
478
+
479
+ // dburl is true and db does not have 'keys' array
480
+ // action: syncCollection data{array: 'keys', object[]}
481
+ // actions: add keys as once keys are added admin user can do anything
482
+
483
+ // }
484
+
485
+ if (authorized.authorized) data = authorized.authorized;
486
+
487
+ if (data.endpoint) {
488
+ this.emit("endpoint", data);
489
+ } else {
490
+ // TODO: handle non module cases where send is required from messages
491
+ let moduleName = data.method.split(".")[0];
492
+ if (
493
+ [
494
+ "storage",
495
+ "database",
496
+ "array",
497
+ "index",
498
+ "object"
499
+ ].includes(moduleName)
500
+ ) {
501
+ this.emit(data.method, data);
502
+ } else if (
503
+ data.method === "crdt" ||
504
+ data.method === "cursor"
505
+ ) {
506
+ this.send(data);
507
+ } else {
508
+ this.emit(moduleName, data);
509
+ }
510
+ }
511
+ }
512
+ } catch (e) {
513
+ console.log(e);
514
+ }
515
+ }
516
+
517
+ async send(data) {
518
+ // const socket = this.sockets.get(data.socketId)
519
+ delete data.wsManager;
520
+ const socket = data.socket;
521
+
522
+ if (data.sync) {
523
+ if (!data.object || !data.object.length) return;
524
+
525
+ for (let i = 0; i < data.object.length; i++) {
526
+ if (data.object[i].data) {
527
+ data.object[i].data._id = data.object[i]._id;
528
+ data.object[i].data.sync = true;
529
+
530
+ const authorized = await this.authorize.check(
531
+ data.object[i].data,
532
+ socket.user_id
533
+ );
534
+ if (authorized && authorized.authorized)
535
+ socket.send(JSON.stringify(authorized.authorized));
536
+ else socket.send(JSON.stringify(data.object[i].data));
537
+ } else console.log("server sync missing data");
538
+ }
539
+ } else {
540
+ const sent = [];
541
+
542
+ //TODO: get socket using clientId if socket has been deleted from data
543
+ const authorized = await this.authorize.check(data, socket.user_id);
544
+ if (authorized && authorized.authorized)
545
+ data = authorized.authorized;
546
+
547
+ if (
548
+ data.log !== false &&
549
+ data.log !== "false" &&
550
+ !data.method.endsWith(".read") &&
551
+ data.method !== "updateUserStatus" &&
552
+ data.method !== "userStatus" &&
553
+ data.method !== "signIn" &&
554
+ data.method !== "signUp"
555
+ ) {
556
+ // TODO: store logged messages more efficently by combing objects wherever possible
557
+ let moduleName = data.method.split(".");
558
+ if (
559
+ [
560
+ "storage",
561
+ "database",
562
+ "array",
563
+ "index",
564
+ "object"
565
+ ].includes(moduleName[0]) &&
566
+ ["delete"].includes(moduleName[1])
567
+ ) {
568
+ let object = { url: socket.socketUrl, data: { ...data } };
569
+ delete object.data.socket;
570
+
571
+ // object.data.socket = { id: object.data.socket.id }
572
+ this.emit("object.create", {
573
+ method: "object.create",
574
+ host: data.host,
575
+ array: "message_log",
576
+ object,
577
+ organization_id: data.organization_id
578
+ });
579
+ }
580
+ }
581
+
582
+ let sockets = this.get(data);
583
+
584
+ // delete data.socket
585
+
586
+ for (let i = 0; i < sockets.length; i++) {
587
+ if (!sockets[i]) continue;
588
+
589
+ const authorized = await this.authorize.check(
590
+ data,
591
+ sockets[i].user_id
592
+ );
593
+ let Data;
594
+ if (authorized && authorized.authorized) {
595
+ Data = { ...authorized.authorized };
596
+ } else {
597
+ Data = { ...data };
598
+ }
599
+
600
+ // TODO: the following code can cause issues in client and improved approach is to check if user has permission and send or dont send
601
+ if (
602
+ Data.$filter &&
603
+ Data.$filter.query &&
604
+ Data.$filter.query._id &&
605
+ Data.$filter.query._id.$eq === "$user_id"
606
+ )
607
+ delete Data.$filter.query._id;
608
+
609
+ delete Data.socket;
610
+ sockets[i].send(JSON.stringify(Data));
611
+
612
+ sent.push(socket.clientId);
613
+ this.emit("setBandwidth", {
614
+ type: "out",
615
+ data: Data,
616
+ organization_id: socket.organization_id
617
+ });
618
+ }
619
+
620
+ // TODO: sent is an array of clientId's so that notification can send to subscribed clients that are not currently connected
621
+ this.emit("notification", { sent });
622
+ }
623
+ }
624
+
625
+ getConfigFromUrl(pathname) {
626
+ const path = pathname.split("/");
627
+ if (path[2]) {
628
+ path[2] = decodeURIComponent(path[2]);
629
+ path[2] = JSON.parse(path[2]) || {};
630
+ }
631
+
632
+ const config = {
633
+ organization_id: path[1],
634
+ ...path[2]
635
+ };
636
+ return config;
637
+ }
546
638
  }
547
639
 
548
- module.exports = SocketServer
640
+ module.exports = SocketServer;