@wxn0brp/gloves-link-server 0.0.13 → 0.1.0-beta.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/dist/http.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Router } from "@wxn0brp/falcon-frame";
2
+ import { SocketStatus } from "./types.js";
3
+ import { GlovesLinkServer } from "./index.js";
4
+ /**
5
+ * Saves the status of a socket connection for temporary tracking
6
+ */
7
+ export declare function saveSocketStatus(wss: GlovesLinkServer, socketStatus: SocketStatus): void;
8
+ /**
9
+ * Creates a router for handling status requests
10
+ * @returns A router instance for status endpoints
11
+ */
12
+ export declare function statusRouter(): Router;
13
+ /**
14
+ * Creates a router for serving client files
15
+ * @param clientDir - Optional directory path for client files, defaults to node_modules/@wxn0brp/gloves-link-client/dist/
16
+ * @returns A router instance for client file serving
17
+ */
18
+ export declare function clientRouter(clientDir?: string): Router;
package/dist/http.js ADDED
@@ -0,0 +1,60 @@
1
+ import { Router } from "@wxn0brp/falcon-frame";
2
+ /**
3
+ * Saves the status of a socket connection for temporary tracking
4
+ */
5
+ export function saveSocketStatus(wss, socketStatus) {
6
+ const { namespace, socketSelfId, status, msg } = socketStatus;
7
+ if (!socketSelfId)
8
+ return;
9
+ const id = namespace + "-" + socketSelfId;
10
+ wss.initStatusTemp[id] = {
11
+ status,
12
+ msg
13
+ };
14
+ setTimeout(() => {
15
+ delete wss.initStatusTemp[id];
16
+ }, wss.opts.statusTimeout);
17
+ }
18
+ /**
19
+ * Creates a router for handling status requests
20
+ * @returns A router instance for status endpoints
21
+ */
22
+ export function statusRouter() {
23
+ const router = new Router();
24
+ router.get("/status", (req, res) => {
25
+ const id = req.query.id;
26
+ if (!id) {
27
+ res.status(400).json({ err: true, msg: "No id provided" });
28
+ return;
29
+ }
30
+ const path = req.query.path;
31
+ if (!path) {
32
+ res.status(400).json({ err: true, msg: "No path provided" });
33
+ return;
34
+ }
35
+ const statusKey = path + "-" + id;
36
+ const status = this.initStatusTemp[statusKey];
37
+ if (status === undefined) {
38
+ res.status(404).json({ err: true, msg: "Socket not found" });
39
+ return;
40
+ }
41
+ res.json({ err: false, status });
42
+ delete this.initStatusTemp[statusKey];
43
+ });
44
+ return router;
45
+ }
46
+ /**
47
+ * Creates a router for serving client files
48
+ * @param clientDir - Optional directory path for client files, defaults to node_modules/@wxn0brp/gloves-link-client/dist/
49
+ * @returns A router instance for client file serving
50
+ */
51
+ export function clientRouter(clientDir) {
52
+ const router = new Router();
53
+ clientDir = clientDir || "node_modules/@wxn0brp/gloves-link-client/dist/";
54
+ router.static("/", clientDir);
55
+ router.get("/*", (req, res) => {
56
+ res.redirect("/gloves-link/GlovesLinkClient.js");
57
+ res.end();
58
+ });
59
+ return router;
60
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import FalconFrame, { Router } from "@wxn0brp/falcon-frame";
1
+ import FalconFrame from "@wxn0brp/falcon-frame";
2
2
  import http from "http";
3
3
  import { WebSocketServer } from "ws";
4
4
  import { Namespace } from "./namespace.js";
5
- import { Room, Rooms } from "./room.js";
5
+ import { Room } from "./room.js";
6
6
  import { GLSocket } from "./socket.js";
7
7
  import { Server_Opts } from "./types.js";
8
8
  /**
@@ -15,23 +15,13 @@ export declare class GlovesLinkServer {
15
15
  status: number;
16
16
  msg?: string;
17
17
  }>;
18
- rooms: Rooms;
19
18
  namespaces: Map<string, Namespace>;
20
19
  /**
21
20
  * Creates a new GlovesLinkServer instance
22
21
  * @param opts - Server options including the HTTP server instance
23
22
  */
24
23
  constructor(opts: Partial<Server_Opts>);
25
- createServer(server: http.Server): void;
26
- /**
27
- * Saves the status of a socket connection for temporary tracking
28
- * @param socketSelfId - The ID of the socket
29
- * @param namespace - The namespace of the socket
30
- * @param status - The status code to save
31
- * @param msg - Optional message to save with the status
32
- * @private
33
- */
34
- private saveSocketStatus;
24
+ attachToHttpServer(server: http.Server): void;
35
25
  /**
36
26
  * Gets or creates a namespace by path
37
27
  * @param path - The path for the namespace
@@ -46,22 +36,18 @@ export declare class GlovesLinkServer {
46
36
  */
47
37
  broadcastRoom(roomName: string, event: string, ...args: any[]): void;
48
38
  /**
49
- * Gets or creates a room by name
39
+ * Gets or creates a room by name (from the root namespace)
50
40
  * @param name - The name of the room
51
41
  * @returns The room instance
52
42
  */
53
43
  room(name: string): Room;
54
44
  /**
55
- * Creates a router for handling status requests
56
- * @returns A router instance for status endpoints
57
- */
58
- statusRouter(): Router;
59
- /**
60
- * Creates a router for serving client files
61
- * @param clientDir - Optional directory path for client files, defaults to node_modules/@wxn0brp/gloves-link-client/dist/
62
- * @returns A router instance for client file serving
45
+ * Emits an event to the socket associated with the specified user ID
46
+ * @param userId - The user ID to target
47
+ * @param event - The event name to emit
48
+ * @param args - The arguments to pass with the event
63
49
  */
64
- clientRouter(clientDir?: string): Router;
50
+ emitToUserId(userId: string, event: string, ...args: any[]): void;
65
51
  /**
66
52
  * Integrates the GlovesLink server with a FalconFrame application
67
53
  * @param app - The FalconFrame application instance
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Router } from "@wxn0brp/falcon-frame";
2
2
  import { WebSocketServer } from "ws";
3
+ import { clientRouter, saveSocketStatus, statusRouter } from "./http.js";
3
4
  import { Namespace } from "./namespace.js";
4
- import { Room } from "./room.js";
5
+ import { getRoom } from "./room.js";
5
6
  import { GLSocket } from "./socket.js";
6
7
  /**
7
8
  * GlovesLinkServer class provides a WebSocket server with namespace and room functionality
@@ -10,7 +11,6 @@ export class GlovesLinkServer {
10
11
  wss;
11
12
  opts;
12
13
  initStatusTemp = {};
13
- rooms = new Map();
14
14
  namespaces = new Map();
15
15
  /**
16
16
  * Creates a new GlovesLinkServer instance
@@ -19,11 +19,12 @@ export class GlovesLinkServer {
19
19
  constructor(opts) {
20
20
  this.opts = {
21
21
  logs: false,
22
+ statusTimeout: 10_000,
22
23
  ...opts
23
24
  };
24
25
  this.wss = new WebSocketServer({ noServer: true });
25
26
  }
26
- createServer(server) {
27
+ attachToHttpServer(server) {
27
28
  server.on("upgrade", async (request, socket, head) => {
28
29
  const headers = request.headers;
29
30
  let socketSelfId;
@@ -31,10 +32,15 @@ export class GlovesLinkServer {
31
32
  const url = new URL(request.url, `http://${request.headers.host}`);
32
33
  const token = url.searchParams.get("token");
33
34
  socketSelfId = url.searchParams.get("id");
35
+ const type = url.searchParams.get("type");
34
36
  const { pathname } = url;
35
37
  const namespace = this.namespaces.get(pathname);
36
38
  if (!namespace) {
37
- this.saveSocketStatus(socketSelfId, pathname, 404);
39
+ saveSocketStatus(this, {
40
+ socketSelfId,
41
+ namespace: pathname,
42
+ status: 404
43
+ });
38
44
  socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
39
45
  socket.destroy();
40
46
  return;
@@ -47,7 +53,12 @@ export class GlovesLinkServer {
47
53
  };
48
54
  const authResult = await namespace.authFn(authData);
49
55
  if (!authResult || authResult.status !== 200) {
50
- this.saveSocketStatus(socketSelfId, pathname, authResult?.status || 401, authResult?.msg || "Unauthorized");
56
+ saveSocketStatus(this, {
57
+ socketSelfId,
58
+ namespace: pathname,
59
+ status: authResult?.status || 401,
60
+ msg: authResult?.msg || "Unauthorized"
61
+ });
51
62
  socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
52
63
  socket.destroy();
53
64
  return;
@@ -57,14 +68,24 @@ export class GlovesLinkServer {
57
68
  glSocket.logs = this.opts.logs;
58
69
  glSocket.authData = authData;
59
70
  glSocket.authResult = authResult;
71
+ glSocket.dataFormatType = type && ["json", "bin"].includes(type) ? type : "json";
60
72
  if (typeof authResult.user === "object" && authResult.user !== null)
61
73
  glSocket.user = authResult.user;
62
- glSocket.namespace = pathname;
63
- namespace.room.join(glSocket);
64
- namespace.onConnectHandler(glSocket, authData, authResult);
74
+ glSocket.namespacePath = pathname;
75
+ glSocket.namespace = namespace;
76
+ namespace._room.join(glSocket);
77
+ const userId = authResult?.user?._id;
78
+ if (userId)
79
+ getRoom(namespace.users, userId).join(glSocket);
80
+ namespace._onConnectHandler(glSocket, authData, authResult);
65
81
  ws.on("close", () => {
66
- glSocket.handlers?.disconnect?.();
67
- namespace.room.leave(glSocket);
82
+ glSocket.handlers.emit("disconnect");
83
+ namespace._room.leave(glSocket);
84
+ glSocket.leaveAllRooms();
85
+ if (userId) {
86
+ const room = getRoom(namespace.users, userId);
87
+ room.leave(glSocket);
88
+ }
68
89
  });
69
90
  });
70
91
  }
@@ -73,32 +94,16 @@ export class GlovesLinkServer {
73
94
  console.error("[GlovesLinkServer]", err);
74
95
  if (this.opts.logs)
75
96
  console.warn("[ws auth] Error during authentication:", err);
76
- this.saveSocketStatus(socketSelfId, "/", 500);
97
+ saveSocketStatus(this, {
98
+ socketSelfId,
99
+ namespace: "/",
100
+ status: 500
101
+ });
77
102
  socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
78
103
  socket.destroy();
79
104
  }
80
105
  });
81
106
  }
82
- /**
83
- * Saves the status of a socket connection for temporary tracking
84
- * @param socketSelfId - The ID of the socket
85
- * @param namespace - The namespace of the socket
86
- * @param status - The status code to save
87
- * @param msg - Optional message to save with the status
88
- * @private
89
- */
90
- saveSocketStatus(socketSelfId, namespace, status, msg) {
91
- if (!socketSelfId)
92
- return;
93
- const id = namespace + "-" + socketSelfId;
94
- this.initStatusTemp[id] = {
95
- status,
96
- msg
97
- };
98
- setTimeout(() => {
99
- delete this.initStatusTemp[id];
100
- }, 10_000);
101
- }
102
107
  /**
103
108
  * Gets or creates a namespace by path
104
109
  * @param path - The path for the namespace
@@ -125,54 +130,21 @@ export class GlovesLinkServer {
125
130
  room.emit(event, ...args);
126
131
  }
127
132
  /**
128
- * Gets or creates a room by name
133
+ * Gets or creates a room by name (from the root namespace)
129
134
  * @param name - The name of the room
130
135
  * @returns The room instance
131
136
  */
132
137
  room(name) {
133
- return this.rooms.get(name) || this.rooms.set(name, new Room()).get(name);
134
- }
135
- /**
136
- * Creates a router for handling status requests
137
- * @returns A router instance for status endpoints
138
- */
139
- statusRouter() {
140
- const router = new Router();
141
- router.get("/status", (req, res) => {
142
- const id = req.query.id;
143
- if (!id) {
144
- res.status(400).json({ err: true, msg: "No id provided" });
145
- return;
146
- }
147
- const path = req.query.path;
148
- if (!path) {
149
- res.status(400).json({ err: true, msg: "No path provided" });
150
- return;
151
- }
152
- const status = this.initStatusTemp[path + "-" + id];
153
- if (status === undefined) {
154
- res.status(404).json({ err: true, msg: "Socket not found" });
155
- return;
156
- }
157
- res.json({ status });
158
- delete this.initStatusTemp[id];
159
- });
160
- return router;
138
+ return this.of("/").room(name);
161
139
  }
162
140
  /**
163
- * Creates a router for serving client files
164
- * @param clientDir - Optional directory path for client files, defaults to node_modules/@wxn0brp/gloves-link-client/dist/
165
- * @returns A router instance for client file serving
141
+ * Emits an event to the socket associated with the specified user ID
142
+ * @param userId - The user ID to target
143
+ * @param event - The event name to emit
144
+ * @param args - The arguments to pass with the event
166
145
  */
167
- clientRouter(clientDir) {
168
- const router = new Router();
169
- clientDir = clientDir || "node_modules/@wxn0brp/gloves-link-client/dist/";
170
- router.static("/", clientDir);
171
- router.get("/*", (req, res) => {
172
- res.redirect("/gloves-link/GlovesLinkClient.js");
173
- res.end();
174
- });
175
- return router;
146
+ emitToUserId(userId, event, ...args) {
147
+ this.namespaces.forEach((ns) => ns.emitToUserId(userId, event, ...args));
176
148
  }
177
149
  /**
178
150
  * Integrates the GlovesLink server with a FalconFrame application
@@ -182,9 +154,9 @@ export class GlovesLinkServer {
182
154
  falconFrame(app, clientDir) {
183
155
  const router = new Router();
184
156
  app.use("/gloves-link", router);
185
- router.use(this.statusRouter());
157
+ router.use(statusRouter());
186
158
  if (clientDir !== false)
187
- router.use(this.clientRouter(clientDir));
159
+ router.use(clientRouter(clientDir));
188
160
  }
189
161
  }
190
162
  export { GLSocket, Namespace };
@@ -1,16 +1,18 @@
1
- import { AuthFn, OnConnect } from "./types.js";
2
- import { GLSocket } from "./socket.js";
3
- import { Room } from "./room.js";
4
1
  import { GlovesLinkServer } from "./index.js";
2
+ import { Room, Rooms } from "./room.js";
3
+ import { GLSocket } from "./socket.js";
4
+ import { AuthFn, OnConnect } from "./types.js";
5
5
  /**
6
6
  * Namespace class represents a logical grouping of sockets that can communicate with each other
7
7
  */
8
8
  export declare class Namespace {
9
9
  name: string;
10
10
  private server;
11
- private onConnectEvent;
11
+ _onConnectHandler: OnConnect;
12
12
  authFn: AuthFn;
13
- room: Room;
13
+ _room: Room;
14
+ rooms: Rooms;
15
+ users: Rooms;
14
16
  /**
15
17
  * Creates a new Namespace instance
16
18
  * @param name - The name of the namespace
@@ -29,17 +31,18 @@ export declare class Namespace {
29
31
  * @returns The current Namespace instance for chaining
30
32
  */
31
33
  auth(authFn: AuthFn): this;
32
- /**
33
- * Gets the connection event handler for this namespace
34
- * @returns The connection event handler function
35
- */
36
- get onConnectHandler(): OnConnect;
37
34
  /**
38
35
  * Emits an event to all sockets in the namespace's room
39
36
  * @param event - The event name to emit
40
37
  * @param args - The arguments to pass with the event
41
38
  */
42
39
  emit(event: string, ...args: any[]): void;
40
+ /**
41
+ * Gets or creates a room by name
42
+ * @param name - The name of the room to get or create
43
+ * @returns The Room instance
44
+ */
45
+ room(name: string): Room;
43
46
  /**
44
47
  * Emits an event to all sockets in the namespace's room except the specified socket
45
48
  * @param socket - The socket to exclude from the emission
@@ -47,4 +50,11 @@ export declare class Namespace {
47
50
  * @param args - The arguments to pass with the event
48
51
  */
49
52
  emitWithoutSelf(socket: GLSocket, event: string, ...args: any[]): void;
53
+ /**
54
+ * Emits an event to the socket associated with the specified user ID
55
+ * @param userId - The user ID to target
56
+ * @param event - The event name to emit
57
+ * @param args - The arguments to pass with the event
58
+ */
59
+ emitToUserId(userId: string, event: string, ...args: any[]): void;
50
60
  }
package/dist/namespace.js CHANGED
@@ -1,12 +1,15 @@
1
+ import { getRoom, Room } from "./room.js";
1
2
  /**
2
3
  * Namespace class represents a logical grouping of sockets that can communicate with each other
3
4
  */
4
5
  export class Namespace {
5
6
  name;
6
7
  server;
7
- onConnectEvent = () => { };
8
+ _onConnectHandler = () => { };
8
9
  authFn = async () => ({ status: 200 });
9
- room;
10
+ _room = new Room();
11
+ rooms = new Map();
12
+ users = new Map();
10
13
  /**
11
14
  * Creates a new Namespace instance
12
15
  * @param name - The name of the namespace
@@ -15,8 +18,6 @@ export class Namespace {
15
18
  constructor(name, server) {
16
19
  this.name = name;
17
20
  this.server = server;
18
- const roomName = `gls-namespace-${name}`;
19
- this.room = this.server.room(roomName);
20
21
  }
21
22
  /**
22
23
  * Sets the connection event handler for this namespace
@@ -24,7 +25,7 @@ export class Namespace {
24
25
  * @returns The current Namespace instance for chaining
25
26
  */
26
27
  onConnect(handler) {
27
- this.onConnectEvent = handler;
28
+ this._onConnectHandler = handler;
28
29
  return this;
29
30
  }
30
31
  /**
@@ -36,20 +37,21 @@ export class Namespace {
36
37
  this.authFn = authFn;
37
38
  return this;
38
39
  }
39
- /**
40
- * Gets the connection event handler for this namespace
41
- * @returns The connection event handler function
42
- */
43
- get onConnectHandler() {
44
- return this.onConnectEvent;
45
- }
46
40
  /**
47
41
  * Emits an event to all sockets in the namespace's room
48
42
  * @param event - The event name to emit
49
43
  * @param args - The arguments to pass with the event
50
44
  */
51
45
  emit(event, ...args) {
52
- this.room.emit(event, ...args);
46
+ this._room.emit(event, ...args);
47
+ }
48
+ /**
49
+ * Gets or creates a room by name
50
+ * @param name - The name of the room to get or create
51
+ * @returns The Room instance
52
+ */
53
+ room(name) {
54
+ return getRoom(this.rooms, name);
53
55
  }
54
56
  /**
55
57
  * Emits an event to all sockets in the namespace's room except the specified socket
@@ -58,6 +60,15 @@ export class Namespace {
58
60
  * @param args - The arguments to pass with the event
59
61
  */
60
62
  emitWithoutSelf(socket, event, ...args) {
61
- this.room.emitWithoutSelf(socket, event, ...args);
63
+ this._room.emitWithoutSelf(socket, event, ...args);
64
+ }
65
+ /**
66
+ * Emits an event to the socket associated with the specified user ID
67
+ * @param userId - The user ID to target
68
+ * @param event - The event name to emit
69
+ * @param args - The arguments to pass with the event
70
+ */
71
+ emitToUserId(userId, event, ...args) {
72
+ this.users.get(userId)?.emit(event, ...args);
62
73
  }
63
74
  }
package/dist/room.d.ts CHANGED
@@ -5,7 +5,7 @@ export type Rooms = Map<string, Room>;
5
5
  * Room class represents a collection of sockets that can communicate with each other
6
6
  */
7
7
  export declare class Room {
8
- clients: Set<GLSocket>;
8
+ _clients: Set<GLSocket>;
9
9
  eventEmitter: EventEmitter<any>;
10
10
  /**
11
11
  * Adds a socket to the room
@@ -66,19 +66,9 @@ export declare class Room {
66
66
  has(socket: GLSocket): boolean;
67
67
  }
68
68
  /**
69
- * Adds a socket to a room by name, creating the room if it doesn't exist
70
- * @param socket - The socket to add to the room
71
- * @param name - The name of the room to join
69
+ * Gets or creates a room by name
70
+ * @param rooms - The map of rooms to search in
71
+ * @param name - The name of the room to get or create
72
+ * @returns The Room instance
72
73
  */
73
- export declare function joinSocketToRoom(socket: GLSocket, name: string): void;
74
- /**
75
- * Removes a socket from a room by name
76
- * @param socket - The socket to remove from the room
77
- * @param roomName - The name of the room to leave
78
- */
79
- export declare function leaveSocketFromRoom(socket: GLSocket, roomName: string): void;
80
- /**
81
- * Removes a socket from all rooms it has joined
82
- * @param socket - The socket to remove from all rooms
83
- */
84
- export declare function leaveAllSocketFromRoom(socket: GLSocket): void;
74
+ export declare function getRoom(rooms: Rooms, name: string): Room;
package/dist/room.js CHANGED
@@ -3,14 +3,15 @@ import EventEmitter from "events";
3
3
  * Room class represents a collection of sockets that can communicate with each other
4
4
  */
5
5
  export class Room {
6
- clients = new Set();
6
+ _clients = new Set();
7
7
  eventEmitter = new EventEmitter();
8
8
  /**
9
9
  * Adds a socket to the room
10
10
  * @param socket - The socket to add to the room
11
11
  */
12
12
  join(socket) {
13
- this.clients.add(socket);
13
+ this._clients.add(socket);
14
+ socket.rooms.add(this);
14
15
  this.eventEmitter.emit("join", socket, this);
15
16
  }
16
17
  /**
@@ -18,15 +19,21 @@ export class Room {
18
19
  * @param socket - The socket to remove from the room
19
20
  */
20
21
  leave(socket) {
21
- this.clients.delete(socket);
22
+ this._clients.delete(socket);
23
+ socket.rooms.delete(this);
22
24
  this.eventEmitter.emit("leave", socket, this);
25
+ if (this._clients.size === 0)
26
+ this.eventEmitter.emit("empty", this);
23
27
  }
24
28
  /**
25
29
  * Removes all sockets from the room
26
30
  */
27
31
  leaveAll() {
28
- this.clients.clear();
32
+ for (const socket of this._clients)
33
+ socket.rooms.delete(this);
34
+ this._clients.clear();
29
35
  this.eventEmitter.emit("leaveAll", this);
36
+ this.eventEmitter.emit("empty", this);
30
37
  }
31
38
  /**
32
39
  * Registers a handler for when a socket joins the room
@@ -51,14 +58,14 @@ export class Room {
51
58
  * @returns The number of clients in the room
52
59
  */
53
60
  get size() {
54
- return this.clients.size;
61
+ return this._clients.size;
55
62
  }
56
63
  /**
57
64
  * Gets an array of all clients in the room
58
65
  * @returns An array containing all the sockets in the room
59
66
  */
60
67
  get sockets() {
61
- return Array.from(this.clients);
68
+ return Array.from(this._clients);
62
69
  }
63
70
  /**
64
71
  * Emits an event to all clients in the room
@@ -66,7 +73,7 @@ export class Room {
66
73
  * @param data - The data to send with the event
67
74
  */
68
75
  emit(evtName, ...data) {
69
- for (const socket of this.clients) {
76
+ for (const socket of this._clients) {
70
77
  socket.emit(evtName, ...data);
71
78
  }
72
79
  }
@@ -77,7 +84,7 @@ export class Room {
77
84
  * @param data - The data to send with the event
78
85
  */
79
86
  emitWithoutSelf(socket, evtName, ...data) {
80
- for (const client of this.clients) {
87
+ for (const client of this._clients) {
81
88
  if (client === socket)
82
89
  continue;
83
90
  client.emit(evtName, ...data);
@@ -89,40 +96,21 @@ export class Room {
89
96
  * @returns True if the socket is in the room, false otherwise
90
97
  */
91
98
  has(socket) {
92
- return this.clients.has(socket);
99
+ return this._clients.has(socket);
93
100
  }
94
101
  }
95
102
  /**
96
- * Adds a socket to a room by name, creating the room if it doesn't exist
97
- * @param socket - The socket to add to the room
98
- * @param name - The name of the room to join
103
+ * Gets or creates a room by name
104
+ * @param rooms - The map of rooms to search in
105
+ * @param name - The name of the room to get or create
106
+ * @returns The Room instance
99
107
  */
100
- export function joinSocketToRoom(socket, name) {
101
- const rooms = socket.server.rooms;
102
- const room = rooms.get(name) || rooms.set(name, new Room()).get(name);
103
- room.join(socket);
104
- }
105
- /**
106
- * Removes a socket from a room by name
107
- * @param socket - The socket to remove from the room
108
- * @param roomName - The name of the room to leave
109
- */
110
- export function leaveSocketFromRoom(socket, roomName) {
111
- const rooms = socket.server.rooms;
112
- const room = rooms.get(roomName);
113
- if (!room)
114
- return;
115
- room.leave(socket);
116
- if (room.size > 0)
117
- return;
118
- rooms.delete(roomName);
119
- }
120
- /**
121
- * Removes a socket from all rooms it has joined
122
- * @param socket - The socket to remove from all rooms
123
- */
124
- export function leaveAllSocketFromRoom(socket) {
125
- const rooms = socket.server.rooms;
126
- for (const room of rooms.values())
127
- room.leave(socket);
108
+ export function getRoom(rooms, name) {
109
+ const existedRoom = rooms.get(name);
110
+ if (existedRoom)
111
+ return existedRoom;
112
+ const createdRoom = new Room();
113
+ rooms.set(name, createdRoom);
114
+ createdRoom.eventEmitter.on("empty", () => rooms.delete(name));
115
+ return createdRoom;
128
116
  }
package/dist/socket.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { WebSocket } from "ws";
2
- import { GlovesLinkServer } from "./index.js";
3
- import { Room, Rooms } from "./room.js";
2
+ import { GlovesLinkServer, Namespace } from "./index.js";
3
+ import { Room } from "./room.js";
4
4
  import { AuthFnResult, Server_Auth_Opts } from "./types.js";
5
+ import EventEmitter from "events";
5
6
  /**
6
7
  * GLSocket class represents a WebSocket connection with additional functionality
7
8
  * @template T - The type of user data associated with the socket
@@ -13,16 +14,16 @@ export declare class GLSocket<T = {
13
14
  server: GlovesLinkServer;
14
15
  id: string;
15
16
  user: T;
16
- namespace: string;
17
+ namespacePath: string;
18
+ namespace: Namespace;
19
+ rooms: Set<Room>;
17
20
  ackIdCounter: number;
18
21
  ackCallbacks: Map<number, Function>;
19
22
  logs: boolean;
20
- handlers: {
21
- [key: string]: Function;
22
- };
23
- rooms: Set<string>;
23
+ handlers: EventEmitter<any>;
24
24
  authData: Server_Auth_Opts;
25
25
  authResult: AuthFnResult;
26
+ dataFormatType: "json" | "bin";
26
27
  /**
27
28
  * Creates a new GLSocket instance
28
29
  * @param ws - The underlying WebSocket connection
@@ -57,41 +58,25 @@ export declare class GLSocket<T = {
57
58
  /**
58
59
  * Closes the WebSocket connection
59
60
  */
60
- close(): void;
61
+ disconnect(): void;
61
62
  /**
62
63
  * Joins the socket to a room
63
64
  * @param roomName - The name of the room to join
64
65
  */
65
- joinRoom(roomName: string): void;
66
+ joinRoom(roomOrName: Room | string): void;
66
67
  /**
67
68
  * Removes the socket from a room
68
69
  * @param roomName - The name of the room to leave
69
70
  */
70
- leaveRoom(roomName: string): void;
71
+ leaveRoom(roomOrName: Room | string): void;
71
72
  /**
72
73
  * Removes the socket from all rooms it has joined
73
74
  */
74
75
  leaveAllRooms(): void;
75
76
  /**
76
- * Gets the namespace associated with this socket
77
- * @returns The namespace object or undefined if not found
78
- */
79
- getNamespace(): import("./namespace.js").Namespace;
80
- /**
81
- * Gets the room associated with this socket's namespace
82
- * @returns The room object or undefined if namespace is not found
83
- */
84
- namespaceRoom(): Room;
85
- /**
86
- * Gets a room from the server by name
87
- * @param roomName - The name of the room to retrieve
88
- * @returns The room object
89
- */
90
- serverRoom(roomName: string): Room;
91
- /**
92
- * Gets all rooms from the server
93
- * @returns A map of all rooms on the server
77
+ * Gets a room by name
78
+ * @param name - The name of the room to get
79
+ * @returns The room object or undefined if not found
94
80
  */
95
- serverRooms(): Rooms;
96
- disconnect(): void;
81
+ room(name: string): Room;
97
82
  }
package/dist/socket.js CHANGED
@@ -1,4 +1,5 @@
1
- import { joinSocketToRoom, leaveSocketFromRoom } from "./room.js";
1
+ import { parse, stringify } from "./transport.js";
2
+ import EventEmitter from "events";
2
3
  /**
3
4
  * GLSocket class represents a WebSocket connection with additional functionality
4
5
  * @template T - The type of user data associated with the socket
@@ -8,14 +9,16 @@ export class GLSocket {
8
9
  server;
9
10
  id;
10
11
  user;
12
+ namespacePath;
11
13
  namespace;
14
+ rooms = new Set();
12
15
  ackIdCounter = 1;
13
16
  ackCallbacks = new Map();
14
17
  logs = false;
15
- handlers;
16
- rooms = new Set();
18
+ handlers = new EventEmitter();
17
19
  authData;
18
20
  authResult;
21
+ dataFormatType = "json";
19
22
  /**
20
23
  * Creates a new GLSocket instance
21
24
  * @param ws - The underlying WebSocket connection
@@ -27,21 +30,17 @@ export class GLSocket {
27
30
  this.server = server;
28
31
  this.id = id || Date.now().toString(36) + Math.random().toString(36).substring(2, 10);
29
32
  this.user = { _id: this.id };
30
- this.handlers = {};
31
- this.ws.on("message", (raw) => this._handle(raw));
33
+ this.ws.on("message", (raw) => this._handle(raw.toString()));
32
34
  }
33
35
  /**
34
36
  * Internal method to handle incoming messages from the WebSocket
35
37
  * @param raw - The raw message string received from the WebSocket
36
38
  */
37
39
  _handle(raw) {
38
- let msg;
39
- try {
40
- msg = JSON.parse(raw);
41
- }
42
- catch {
40
+ const msg = parse(this.dataFormatType, raw);
41
+ if (!msg) {
43
42
  if (this.logs)
44
- console.warn("[ws] Invalid JSON:", raw);
43
+ console.warn(`[ws] Invalid format (${this.dataFormatType}):`, raw);
45
44
  return;
46
45
  }
47
46
  if ("ack" in msg) {
@@ -67,7 +66,7 @@ export class GLSocket {
67
66
  break;
68
67
  const ackId = data[ackIndex];
69
68
  data[ackIndex] = (...res) => {
70
- this.ws.send(JSON.stringify({ ack: ackId, data: res }));
69
+ this.ws.send(stringify(this.dataFormatType, { ack: ackId, data: res }));
71
70
  };
72
71
  }
73
72
  }
@@ -79,7 +78,7 @@ export class GLSocket {
79
78
  * @param handler - The function to be called when the event is received
80
79
  */
81
80
  on(evt, handler) {
82
- this.handlers[evt] = handler;
81
+ this.handlers.on(evt, handler);
83
82
  }
84
83
  /**
85
84
  * Sends an event to the connected WebSocket client
@@ -97,7 +96,7 @@ export class GLSocket {
97
96
  this.ackCallbacks.set(ackId, args[ackIndex]);
98
97
  args[ackIndex] = ackId;
99
98
  }
100
- this.ws.send(JSON.stringify({
99
+ this.ws.send(stringify(this.dataFormatType, {
101
100
  evt,
102
101
  data: args || undefined,
103
102
  ackI: ackI.length ? ackI : undefined
@@ -115,64 +114,38 @@ export class GLSocket {
115
114
  /**
116
115
  * Closes the WebSocket connection
117
116
  */
118
- close() {
117
+ disconnect() {
119
118
  this.ws.close();
120
119
  }
121
120
  /**
122
121
  * Joins the socket to a room
123
122
  * @param roomName - The name of the room to join
124
123
  */
125
- joinRoom(roomName) {
126
- joinSocketToRoom(this, roomName);
127
- this.rooms.add(roomName);
124
+ joinRoom(roomOrName) {
125
+ const room = typeof roomOrName === "string" ? this.room(roomOrName) : roomOrName;
126
+ room.join(this);
128
127
  }
129
128
  /**
130
129
  * Removes the socket from a room
131
130
  * @param roomName - The name of the room to leave
132
131
  */
133
- leaveRoom(roomName) {
134
- leaveSocketFromRoom(this, roomName);
135
- this.rooms.delete(roomName);
132
+ leaveRoom(roomOrName) {
133
+ const room = typeof roomOrName === "string" ? this.room(roomOrName) : roomOrName;
134
+ room.leave(this);
136
135
  }
137
136
  /**
138
137
  * Removes the socket from all rooms it has joined
139
138
  */
140
139
  leaveAllRooms() {
141
- for (const roomName of this.rooms) {
142
- leaveSocketFromRoom(this, roomName);
143
- }
144
- this.rooms.clear();
140
+ for (const room of this.rooms.values())
141
+ room.leave(this);
145
142
  }
146
143
  /**
147
- * Gets the namespace associated with this socket
148
- * @returns The namespace object or undefined if not found
144
+ * Gets a room by name
145
+ * @param name - The name of the room to get
146
+ * @returns The room object or undefined if not found
149
147
  */
150
- getNamespace() {
151
- return this.server.namespaces.get(this.namespace);
152
- }
153
- /**
154
- * Gets the room associated with this socket's namespace
155
- * @returns The room object or undefined if namespace is not found
156
- */
157
- namespaceRoom() {
158
- return this.getNamespace()?.room;
159
- }
160
- /**
161
- * Gets a room from the server by name
162
- * @param roomName - The name of the room to retrieve
163
- * @returns The room object
164
- */
165
- serverRoom(roomName) {
166
- return this.server.room(roomName);
167
- }
168
- /**
169
- * Gets all rooms from the server
170
- * @returns A map of all rooms on the server
171
- */
172
- serverRooms() {
173
- return this.server.rooms;
174
- }
175
- disconnect() {
176
- this.ws.close();
148
+ room(name) {
149
+ return this.namespace.room(name);
177
150
  }
178
151
  }
@@ -0,0 +1,3 @@
1
+ import { Server_AckEvent, Server_DataEvent } from "./types.js";
2
+ export declare function parse(type: string, raw: string): Server_DataEvent | Server_AckEvent;
3
+ export declare function stringify(type: "json" | "bin", data: any): string;
@@ -0,0 +1,47 @@
1
+ const DELIMITER = process.env.GLOVES_LINK_DELIMITER || "\b";
2
+ export function parse(type, raw) {
3
+ if (type === "json") {
4
+ try {
5
+ return JSON.parse(raw);
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ try {
12
+ const [evt, ack, ackIdsString, ...contentString] = raw.split(DELIMITER);
13
+ const contents = contentString.map(value => {
14
+ try {
15
+ return JSON.parse(value);
16
+ }
17
+ catch {
18
+ return value;
19
+ }
20
+ });
21
+ if (ack) {
22
+ return {
23
+ ack: Number(ack),
24
+ data: contents,
25
+ };
26
+ }
27
+ const ackIds = ackIdsString.split(",").filter(Boolean).map(Number);
28
+ return {
29
+ evt,
30
+ ackI: ackIds,
31
+ data: contents,
32
+ };
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ export function stringify(type, data) {
39
+ if (type === "json")
40
+ return JSON.stringify(data);
41
+ return [
42
+ data.evt ?? "",
43
+ (data.ack ?? ""),
44
+ (data.ackI || []).join(","),
45
+ ...data.data.map(JSON.stringify)
46
+ ].join(DELIMITER);
47
+ }
package/dist/types.d.ts CHANGED
@@ -3,6 +3,7 @@ import Stream from "stream";
3
3
  import { GLSocket } from "./socket.js";
4
4
  export interface Server_Opts {
5
5
  logs: boolean;
6
+ statusTimeout: number;
6
7
  }
7
8
  export interface Server_DataEvent {
8
9
  evt: string;
@@ -30,3 +31,9 @@ export interface AuthFnResult {
30
31
  }
31
32
  export type AuthFn = (data: Server_Auth_Opts) => Promise<AuthFnResult>;
32
33
  export type OnConnect = (socket: GLSocket, auth: Server_Auth_Opts, result: AuthFnResult) => void;
34
+ export interface SocketStatus {
35
+ socketSelfId: string;
36
+ namespace: string;
37
+ status: number;
38
+ msg?: string;
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/gloves-link-server",
3
- "version": "0.0.13",
3
+ "version": "0.1.0-beta.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",