@wxn0brp/gloves-link-server 0.0.3
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/LICENSE +21 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +113 -0
- package/dist/room.d.ts +20 -0
- package/dist/room.js +66 -0
- package/dist/socket.d.ts +23 -0
- package/dist/socket.js +91 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wxn0brP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
import { Server_Opts } from "./types.js";
|
|
3
|
+
import { GLSocket } from "./socket.js";
|
|
4
|
+
import FalconFrame from "@wxn0brp/falcon-frame";
|
|
5
|
+
import { Room, Rooms } from "./room.js";
|
|
6
|
+
export declare class GlovesLinkServer {
|
|
7
|
+
wss: WebSocketServer;
|
|
8
|
+
private onConnectEvent;
|
|
9
|
+
logs: boolean;
|
|
10
|
+
opts: Server_Opts;
|
|
11
|
+
initStatusTemp: {
|
|
12
|
+
[key: string]: number;
|
|
13
|
+
};
|
|
14
|
+
rooms: Rooms;
|
|
15
|
+
globalRoom: Room;
|
|
16
|
+
constructor(opts: Partial<Server_Opts>);
|
|
17
|
+
private saveSocketStatus;
|
|
18
|
+
onConnect(handler: (ws: GLSocket) => void): void;
|
|
19
|
+
broadcast(event: string, ...args: any[]): void;
|
|
20
|
+
broadcastRoom(roomName: string, event: string, ...args: any[]): void;
|
|
21
|
+
broadcastWithoutSelf(socket: GLSocket, event: string, ...args: any[]): void;
|
|
22
|
+
room(name: string): Room;
|
|
23
|
+
falconFrame(app: FalconFrame, clientDir?: string): void;
|
|
24
|
+
}
|
|
25
|
+
export { GLSocket };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
import { GLSocket } from "./socket.js";
|
|
3
|
+
import { Router } from "@wxn0brp/falcon-frame";
|
|
4
|
+
import { Room } from "./room.js";
|
|
5
|
+
export class GlovesLinkServer {
|
|
6
|
+
wss;
|
|
7
|
+
onConnectEvent;
|
|
8
|
+
logs = false;
|
|
9
|
+
opts;
|
|
10
|
+
initStatusTemp = {};
|
|
11
|
+
rooms = new Map();
|
|
12
|
+
globalRoom = new Room();
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this.opts = {
|
|
15
|
+
server: null,
|
|
16
|
+
logs: false,
|
|
17
|
+
authFn: () => true,
|
|
18
|
+
...opts
|
|
19
|
+
};
|
|
20
|
+
if (!this.opts?.server) {
|
|
21
|
+
throw new Error("Server is not provided");
|
|
22
|
+
}
|
|
23
|
+
const { server } = opts;
|
|
24
|
+
this.wss = new WebSocketServer({ noServer: true });
|
|
25
|
+
server.on("upgrade", async (request, socket, head) => {
|
|
26
|
+
const headers = request.headers;
|
|
27
|
+
let socketSelfId;
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
30
|
+
const token = url.searchParams.get("token");
|
|
31
|
+
socketSelfId = url.searchParams.get("id");
|
|
32
|
+
const isAuthenticated = await this.opts.authFn({ headers, url, token });
|
|
33
|
+
if (!isAuthenticated) {
|
|
34
|
+
this.saveSocketStatus(socketSelfId, 401);
|
|
35
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
36
|
+
socket.destroy();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
40
|
+
const glSocket = new GLSocket(ws, this);
|
|
41
|
+
glSocket.logs = this.logs;
|
|
42
|
+
this.globalRoom.join(glSocket);
|
|
43
|
+
this.onConnectEvent(glSocket);
|
|
44
|
+
ws.on("close", () => {
|
|
45
|
+
glSocket.handlers?.disconnect?.();
|
|
46
|
+
glSocket.leaveAllRooms();
|
|
47
|
+
this.globalRoom.leave(glSocket);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (process.env.NODE_ENV === "development")
|
|
53
|
+
console.error("[GlovesLinkServer]", err);
|
|
54
|
+
if (this.logs)
|
|
55
|
+
console.warn("[auth] Error during authentication:", err);
|
|
56
|
+
this.saveSocketStatus(socketSelfId, 500);
|
|
57
|
+
socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
|
58
|
+
socket.destroy();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
saveSocketStatus(socketSelfId, status) {
|
|
63
|
+
if (!socketSelfId)
|
|
64
|
+
return;
|
|
65
|
+
this.initStatusTemp[socketSelfId] = status;
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
delete this.initStatusTemp[socketSelfId];
|
|
68
|
+
}, 10_000);
|
|
69
|
+
}
|
|
70
|
+
onConnect(handler) {
|
|
71
|
+
this.onConnectEvent = handler;
|
|
72
|
+
}
|
|
73
|
+
broadcast(event, ...args) {
|
|
74
|
+
this.globalRoom.emit(event, ...args);
|
|
75
|
+
}
|
|
76
|
+
broadcastRoom(roomName, event, ...args) {
|
|
77
|
+
const room = this.room(roomName);
|
|
78
|
+
if (!room)
|
|
79
|
+
return;
|
|
80
|
+
room.emit(event, ...args);
|
|
81
|
+
}
|
|
82
|
+
broadcastWithoutSelf(socket, event, ...args) {
|
|
83
|
+
this.globalRoom.emitWithoutSelf(socket, event, ...args);
|
|
84
|
+
}
|
|
85
|
+
room(name) {
|
|
86
|
+
return this.rooms.get(name) || this.rooms.set(name, new Room()).get(name);
|
|
87
|
+
}
|
|
88
|
+
falconFrame(app, clientDir) {
|
|
89
|
+
clientDir = clientDir || "node_modules/@wxn0brp/gloves-link-client/dist/";
|
|
90
|
+
const router = new Router();
|
|
91
|
+
app.use("/gloves-link", router);
|
|
92
|
+
router.static("/", clientDir);
|
|
93
|
+
router.get("/status", (req, res) => {
|
|
94
|
+
const id = req.query.id;
|
|
95
|
+
if (!id) {
|
|
96
|
+
res.status(400).json({ err: true, msg: "No id provided" });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const status = this.initStatusTemp[id];
|
|
100
|
+
if (status === undefined) {
|
|
101
|
+
res.status(404).json({ err: true, msg: "Socket not found" });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
res.json({ status });
|
|
105
|
+
delete this.initStatusTemp[id];
|
|
106
|
+
});
|
|
107
|
+
router.get("/*", (req, res) => {
|
|
108
|
+
res.redirect("/gloves-link/GlovesLinkClient.js");
|
|
109
|
+
res.end();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export { GLSocket };
|
package/dist/room.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
import { GLSocket } from "./socket.js";
|
|
3
|
+
export type Rooms = Map<string, Room>;
|
|
4
|
+
export declare class Room {
|
|
5
|
+
clients: Set<GLSocket>;
|
|
6
|
+
eventEmitter: EventEmitter<[never]>;
|
|
7
|
+
join(socket: GLSocket): void;
|
|
8
|
+
leave(socket: GLSocket): void;
|
|
9
|
+
leaveAll(): void;
|
|
10
|
+
onJoin(handler: (socket: GLSocket, room: Room) => void): this;
|
|
11
|
+
onLeave(handler: (socket: GLSocket, room: Room) => void): this;
|
|
12
|
+
get size(): number;
|
|
13
|
+
get sockets(): GLSocket[];
|
|
14
|
+
emit(evtName: string, ...data: any): void;
|
|
15
|
+
emitWithoutSelf(socket: GLSocket, evtName: string, ...data: any): void;
|
|
16
|
+
has(socket: GLSocket): boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function joinSocketToRoom(socket: GLSocket, name: string): void;
|
|
19
|
+
export declare function leaveSocketFromRoom(socket: GLSocket, roomName: string): void;
|
|
20
|
+
export declare function leaveAllSocketFromRoom(socket: GLSocket): void;
|
package/dist/room.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
export class Room {
|
|
3
|
+
clients = new Set();
|
|
4
|
+
eventEmitter = new EventEmitter();
|
|
5
|
+
join(socket) {
|
|
6
|
+
this.clients.add(socket);
|
|
7
|
+
this.eventEmitter.emit("join", socket, this);
|
|
8
|
+
}
|
|
9
|
+
leave(socket) {
|
|
10
|
+
this.clients.delete(socket);
|
|
11
|
+
this.eventEmitter.emit("leave", socket, this);
|
|
12
|
+
}
|
|
13
|
+
leaveAll() {
|
|
14
|
+
this.clients.clear();
|
|
15
|
+
this.eventEmitter.emit("leaveAll", this);
|
|
16
|
+
}
|
|
17
|
+
onJoin(handler) {
|
|
18
|
+
this.eventEmitter.on("join", handler);
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
onLeave(handler) {
|
|
22
|
+
this.eventEmitter.on("leave", handler);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
get size() {
|
|
26
|
+
return this.clients.size;
|
|
27
|
+
}
|
|
28
|
+
get sockets() {
|
|
29
|
+
return Array.from(this.clients);
|
|
30
|
+
}
|
|
31
|
+
emit(evtName, ...data) {
|
|
32
|
+
for (const socket of this.clients) {
|
|
33
|
+
socket.emit(evtName, ...data);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
emitWithoutSelf(socket, evtName, ...data) {
|
|
37
|
+
for (const client of this.clients) {
|
|
38
|
+
if (client === socket)
|
|
39
|
+
continue;
|
|
40
|
+
client.emit(evtName, ...data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
has(socket) {
|
|
44
|
+
return this.clients.has(socket);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function joinSocketToRoom(socket, name) {
|
|
48
|
+
const rooms = socket.server.rooms;
|
|
49
|
+
const room = rooms.get(name) || rooms.set(name, new Room()).get(name);
|
|
50
|
+
room.join(socket);
|
|
51
|
+
}
|
|
52
|
+
export function leaveSocketFromRoom(socket, roomName) {
|
|
53
|
+
const rooms = socket.server.rooms;
|
|
54
|
+
const room = rooms.get(roomName);
|
|
55
|
+
if (!room)
|
|
56
|
+
return;
|
|
57
|
+
room.leave(socket);
|
|
58
|
+
if (room.size > 0)
|
|
59
|
+
return;
|
|
60
|
+
rooms.delete(roomName);
|
|
61
|
+
}
|
|
62
|
+
export function leaveAllSocketFromRoom(socket) {
|
|
63
|
+
const rooms = socket.server.rooms;
|
|
64
|
+
for (const room of rooms.values())
|
|
65
|
+
room.leave(socket);
|
|
66
|
+
}
|
package/dist/socket.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
import { GlovesLinkServer } from "./index.js";
|
|
3
|
+
export declare class GLSocket {
|
|
4
|
+
ws: WebSocket;
|
|
5
|
+
server: GlovesLinkServer;
|
|
6
|
+
id: string;
|
|
7
|
+
ackIdCounter: number;
|
|
8
|
+
ackCallbacks: Map<number, Function>;
|
|
9
|
+
logs: boolean;
|
|
10
|
+
handlers: {
|
|
11
|
+
[key: string]: Function;
|
|
12
|
+
};
|
|
13
|
+
rooms: Set<string>;
|
|
14
|
+
constructor(ws: WebSocket, server: GlovesLinkServer, id?: string);
|
|
15
|
+
_handle(raw: string): void;
|
|
16
|
+
on(evt: string, handler: (...args: any[]) => void | any): void;
|
|
17
|
+
emit(evt: string, ...args: any[]): void;
|
|
18
|
+
send(evt: string, ...args: any[]): void;
|
|
19
|
+
close(): void;
|
|
20
|
+
joinRoom(roomName: string): void;
|
|
21
|
+
leaveRoom(roomName: string): void;
|
|
22
|
+
leaveAllRooms(): void;
|
|
23
|
+
}
|
package/dist/socket.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { joinSocketToRoom, leaveAllSocketFromRoom, leaveSocketFromRoom } from "./room.js";
|
|
2
|
+
export class GLSocket {
|
|
3
|
+
ws;
|
|
4
|
+
server;
|
|
5
|
+
id;
|
|
6
|
+
ackIdCounter = 1;
|
|
7
|
+
ackCallbacks = new Map();
|
|
8
|
+
logs = false;
|
|
9
|
+
handlers;
|
|
10
|
+
rooms = new Set();
|
|
11
|
+
constructor(ws, server, id) {
|
|
12
|
+
this.ws = ws;
|
|
13
|
+
this.server = server;
|
|
14
|
+
this.id = id || Date.now().toString(36) + Math.random().toString(36).substring(2, 10);
|
|
15
|
+
this.handlers = {};
|
|
16
|
+
this.ws.on("message", (raw) => this._handle(raw));
|
|
17
|
+
}
|
|
18
|
+
_handle(raw) {
|
|
19
|
+
let msg;
|
|
20
|
+
try {
|
|
21
|
+
msg = JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
if (this.logs)
|
|
25
|
+
console.warn("[ws] Invalid JSON:", raw);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if ("ack" in msg) {
|
|
29
|
+
const ackId = msg.ack;
|
|
30
|
+
const ackCallback = this.ackCallbacks.get(ackId);
|
|
31
|
+
if (ackCallback) {
|
|
32
|
+
this.ackCallbacks.delete(ackId);
|
|
33
|
+
ackCallback(...msg.data);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const { evt, data, ackI } = msg;
|
|
38
|
+
if (!evt || (data && !Array.isArray(data)))
|
|
39
|
+
return;
|
|
40
|
+
if (Array.isArray(ackI)) {
|
|
41
|
+
for (let i = 0; i < ackI.length; i++) {
|
|
42
|
+
const ackIndex = ackI[i];
|
|
43
|
+
if (!data[ackIndex])
|
|
44
|
+
break;
|
|
45
|
+
const ackId = data[ackIndex];
|
|
46
|
+
data[ackIndex] = (...res) => {
|
|
47
|
+
this.ws.send(JSON.stringify({ ack: ackId, data: res }));
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
this.handlers[evt]?.(...data);
|
|
52
|
+
}
|
|
53
|
+
on(evt, handler) {
|
|
54
|
+
this.handlers[evt] = handler;
|
|
55
|
+
}
|
|
56
|
+
emit(evt, ...args) {
|
|
57
|
+
const ackI = args.map((data, i) => {
|
|
58
|
+
if (typeof data === "function")
|
|
59
|
+
return i;
|
|
60
|
+
}).filter(i => i !== undefined);
|
|
61
|
+
for (let i = 0; i < ackI.length; i++) {
|
|
62
|
+
const ackIndex = ackI[i];
|
|
63
|
+
const ackId = this.ackIdCounter++;
|
|
64
|
+
this.ackCallbacks.set(ackId, args[ackIndex]);
|
|
65
|
+
args[ackIndex] = ackId;
|
|
66
|
+
}
|
|
67
|
+
this.ws.send(JSON.stringify({
|
|
68
|
+
evt,
|
|
69
|
+
data: args || undefined,
|
|
70
|
+
ackI: ackI.length ? ackI : undefined
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
send(evt, ...args) {
|
|
74
|
+
return this.emit(evt, ...args);
|
|
75
|
+
}
|
|
76
|
+
close() {
|
|
77
|
+
this.ws.close();
|
|
78
|
+
}
|
|
79
|
+
joinRoom(roomName) {
|
|
80
|
+
joinSocketToRoom(this, roomName);
|
|
81
|
+
this.rooms.add(roomName);
|
|
82
|
+
}
|
|
83
|
+
leaveRoom(roomName) {
|
|
84
|
+
leaveSocketFromRoom(this, roomName);
|
|
85
|
+
this.rooms.delete(roomName);
|
|
86
|
+
}
|
|
87
|
+
leaveAllRooms() {
|
|
88
|
+
leaveAllSocketFromRoom(this);
|
|
89
|
+
this.rooms.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
export interface Server_Opts {
|
|
3
|
+
server: http.Server;
|
|
4
|
+
logs: boolean;
|
|
5
|
+
authFn: AuthFn;
|
|
6
|
+
}
|
|
7
|
+
export interface Server_DataEvent {
|
|
8
|
+
evt: string;
|
|
9
|
+
data: any[];
|
|
10
|
+
ackI?: number[];
|
|
11
|
+
}
|
|
12
|
+
export interface Server_AckEvent {
|
|
13
|
+
ack: number;
|
|
14
|
+
data: any[];
|
|
15
|
+
}
|
|
16
|
+
export interface Server_Auth_Opts {
|
|
17
|
+
headers: http.IncomingHttpHeaders;
|
|
18
|
+
url: URL;
|
|
19
|
+
token?: string;
|
|
20
|
+
}
|
|
21
|
+
export type AuthFn = (data: Server_Auth_Opts) => boolean | Promise<boolean>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wxn0brp/gloves-link-server",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"author": "wxn0brP",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/wxn0brP/GlovesLink-server.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/wxn0brP/GlovesLink",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && tsc-alias"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@wxn0brp/falcon-frame": "^0.5.3",
|
|
19
|
+
"@types/bun": "*",
|
|
20
|
+
"@types/ws": "^8.18.1",
|
|
21
|
+
"tsc-alias": "*",
|
|
22
|
+
"typescript": "*"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@wxn0brp/falcon-frame": ">=0.5.3"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"@wxn0brp/falcon-frame": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"ws": "^8.18.3"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"default": "./dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./*": {
|
|
45
|
+
"types": "./dist/*.d.ts",
|
|
46
|
+
"import": "./dist/*.js",
|
|
47
|
+
"default": "./dist/*.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|