@nka212bg/backend-utils 0.1.40 → 0.1.41

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 (2) hide show
  1. package/package.json +1 -1
  2. package/socket.js +48 -14
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nka212bg/backend-utils",
3
3
  "author": "nka212bg",
4
- "version": "0.1.40",
4
+ "version": "0.1.41",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "@maxmind/geoip2-node": "^5.0.0",
package/socket.js CHANGED
@@ -1,13 +1,51 @@
1
1
  const WebSocket = require("ws");
2
2
 
3
- module.exports = (server) => {
4
- const socket = new WebSocket.Server({ server, noServer: !server });
3
+ const parseParams = (url) => {
4
+ try {
5
+ return Object.fromEntries(new URLSearchParams(decodeURIComponent(url || "").replace(/^(\/\?|\?|\/)/g, "")));
6
+ } catch (error) {
7
+ return {};
8
+ }
9
+ };
10
+
11
+ // options.authenticate (params, req) => boolean, rejects the connection with 401 during the handshake when falsy
12
+ module.exports = (server, { authenticate } = {}) => {
13
+ const socket = new WebSocket.Server({
14
+ server,
15
+ noServer: !server,
16
+ verifyClient: authenticate && (({ req }) => !!authenticate(parseParams(req.url), req)),
17
+ });
5
18
  socket.on("connection", (ws, req) => {
6
- try {
7
- ws.params = Object.fromEntries(new URLSearchParams(decodeURIComponent(req.url || "").replace(/^(\/\?|\?|\/)/g, "")));
8
- } catch (error) {}
19
+ ws.params = parseParams(req.url);
20
+ const userId = ws.params.userId;
21
+
22
+ // first tab of this user -> online
23
+ if (userId && !isConnected(userId, ws)) notifyWatchers(userId, 1);
24
+
25
+ // { watchStatuses: [userIds] } -> replies { statuses: { userId: 0 | 1 } }, then pushes { status: { userId, online } } on changes
26
+ ws.on("message", (raw) => {
27
+ try {
28
+ const { watchStatuses } = JSON.parse(raw);
29
+ if (!watchStatuses) return;
30
+ ws.watching = new Set(watchStatuses.map(String));
31
+ ws.send(JSON.stringify({ statuses: socket.getStatuses(watchStatuses) }));
32
+ } catch (error) {}
33
+ });
34
+
35
+ // last tab closed -> offline
36
+ ws.on("close", () => userId && !isConnected(userId, ws) && notifyWatchers(userId, 0));
9
37
  });
10
38
 
39
+ const isConnected = (userId, except) => {
40
+ for (const c of socket.clients) if (c !== except && c.params?.userId === userId) return true;
41
+ return false;
42
+ };
43
+
44
+ const notifyWatchers = (userId, online) => {
45
+ const data = JSON.stringify({ statuses: { [userId]: online } });
46
+ for (const c of socket.clients) if (c.watching?.has(userId)) c.send(data);
47
+ };
48
+
11
49
  socket.broadcast = (data, { skipId, skipToken } = {}) => {
12
50
  if (!data) return 0;
13
51
  if (typeof data === "object") data = JSON.stringify(data);
@@ -50,23 +88,19 @@ module.exports = (server) => {
50
88
 
51
89
  socket.getStatuses = (users) => {
52
90
  users = new Set(String(users).split(/[,"' ]/g).filter(Boolean));
53
-
54
91
  const usersObj = {};
55
- for (const u of users) usersObj[u] = 0;
56
92
 
57
- let remaining = users.size;
58
- for (const client of socket.clients || []) {
59
- if (!remaining) break;
60
- if (!users.has(client.params.userId)) continue;
93
+ for (const u of users) usersObj[u] = 0;
61
94
 
62
- usersObj[client.params.userId] = 1;
63
- remaining--;
95
+ for (const client of socket.clients || []) {
96
+ if (!users.size) break;
97
+ const id = client.params?.userId;
98
+ if (users.delete(id)) usersObj[id] = 1; // delete() is true only the first time -> extra tabs are ignored
64
99
  }
65
100
 
66
101
  return usersObj;
67
102
  };
68
103
 
69
-
70
104
  return socket;
71
105
  };
72
106