lightweight-socket
Version:
lightweight socket
298 lines (292 loc) • 8.21 kB
JavaScript
// src/server.ts
import * as http from "http";
import { WebSocketServer } from "ws";
// src/event.ts
var Event = class {
constructor(name, payload) {
this.name = name;
this.payload = payload;
}
toString() {
return JSON.stringify({ name: this.name, payload: this.payload });
}
};
var Emitter = class {
constructor() {
this.listeners = {};
}
on(event, listener) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
once(event, listener) {
const onceListener = (...args) => {
this.off(event, onceListener);
listener.apply(this, args);
};
this.on(event, onceListener);
}
off(event, listener) {
if (!this.listeners[event])
return;
this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
}
};
// src/client-server.ts
import { WebSocket } from "ws";
var AbstractClient = class extends Emitter {
emit(name, ...args) {
if (name === "message")
return;
const event = new Event(name, args);
const message = event.toString();
this.socket.send(message);
}
close() {
this.socket.close();
}
receive(data) {
if (Buffer.isBuffer(data)) {
data = data.toString();
}
const parsedEvent = JSON.parse(data);
if (this.listeners[parsedEvent.name]) {
this.listeners[parsedEvent.name].forEach((listener) => listener(...parsedEvent.payload));
}
}
init() {
this.socket.on("message", this.receive.bind(this));
}
};
var Client = class extends AbstractClient {
constructor(url) {
super();
this.url = url;
this.socket = new WebSocket(`${url}`);
this.init();
this.socket.on("connect", (id) => this.id = id);
}
};
var ServerSideClient = class extends AbstractClient {
constructor(url, socket, server) {
super();
this.server = server;
this.rooms = /* @__PURE__ */ new Map();
this.socket = socket;
this.url = url;
this.id = Math.random().toString(36).substr(2, 12);
this.init();
this.rooms.set(this.id, this);
}
join(room) {
this.emit("join-room", room);
}
leave(room) {
this.emit("leave-room", room);
}
to(room) {
const adapter = new Adapter(this.server);
return adapter.of(this.url).to(room);
}
in(room) {
return this.to(room);
}
except(room) {
const adapter = new Adapter(this.server);
return adapter.of(this.url).except(room);
}
emit(name, ...args) {
if (name === "join-room" || name === "leave-room") {
if (this.listeners[name]) {
this.listeners[name].forEach((listener) => listener(...args));
}
return;
}
const adapter = new Adapter(this.server);
return adapter.of(this.url).to(this.id).emit(name, ...args);
}
};
// src/adapter.ts
var Adapter = class {
constructor(server) {
this.server = server;
this.rooms = /* @__PURE__ */ new Set();
this.exceptions = /* @__PURE__ */ new Set();
this.namespace = null;
}
to(room) {
this.rooms.add(room);
return this;
}
except(clientId) {
this.exceptions.add(clientId);
return this;
}
in(room) {
return this.to(room);
}
of(namespace) {
this.namespace = namespace;
return this;
}
emit(name, ...args) {
var _a;
const event = new Event(name, args);
const message = event.toString();
const namespace = this.server.namespaces.get((_a = this.namespace) != null ? _a : "/");
if (!namespace)
return;
this.rooms.forEach((room) => {
if (!namespace.rooms.has(room)) {
return;
}
const clients = namespace.rooms.get(room) instanceof ServerSideClient ? [namespace.rooms.get(room)] : namespace.rooms.get(room).clients;
clients.forEach((client) => {
if (this.exceptions.has(client.id))
return;
client.socket.send(message);
});
});
this.rooms.clear();
this.exceptions.clear();
this.namespace = null;
}
};
// src/namespace.ts
var Namespace = class extends Emitter {
constructor(namespace, server, ws) {
super();
this.namespace = namespace;
this.rooms = /* @__PURE__ */ new Map();
this.server = server;
this.ws = ws;
}
except(room) {
const adapter = new Adapter(this.server);
return adapter.of(this.namespace).except(room);
}
emit(name, ...args) {
if (name === "create-room" || name === "join-room" || name === "leave-room" || name === "delete-room" || name === "connection" || name === "close") {
if (this.listeners[name]) {
this.listeners[name].forEach((listener) => listener(...args));
}
return;
}
const adapter = new Adapter(this.server);
return adapter.of(this.namespace).emit(name, ...args);
}
adapter() {
return new Adapter(this.server);
}
to(room) {
const adapter = new Adapter(this.server);
return adapter.of(this.namespace).to(room);
}
in(room) {
return this.to(room);
}
};
// src/room.ts
var Room = class {
constructor(name) {
this.clients = /* @__PURE__ */ new Map();
this.id = name;
}
add(client) {
const clientId = client.id;
this.clients.set(clientId, client);
client.on("close", () => this.clients.delete(clientId));
}
remove(client) {
const clientId = client.id;
this.clients.delete(clientId);
}
};
// src/server.ts
var Server = class {
constructor(options) {
this.clients = /* @__PURE__ */ new Map();
this.namespaces = /* @__PURE__ */ new Map();
if (typeof options === "number") {
options = { port: options };
}
this.port = options.port;
this.server = http.createServer((req, res) => res.writeHead(200, { "Content-Type": "text/plain" })).listen(this.port);
this.server.on("upgrade", this.upgrade.bind(this));
const namespace = this.getNamespace("/");
this.namespaces.set("/", namespace);
}
upgrade(req, socket, head) {
const [namespacePath, query] = req.url.split("?");
const namespace = this.getNamespace(namespacePath);
const wss = namespace.ws;
wss.handleUpgrade(req, socket, head, (socket2) => this.handler(namespace, socket2, namespacePath));
}
handler(namespace, socket, namespacePath) {
const client = new ServerSideClient(namespacePath, socket, this);
this.clients.set(client.id, client);
namespace.rooms.set(client.id, client);
client.on("close", () => this.clients.delete(client.id));
client.on("join-room", (name) => {
var room = null;
if (!namespace.rooms.has(name)) {
room = new Room(name);
namespace.rooms.set(name, room);
namespace.emit("create-room", room);
}
room = namespace.rooms.get(name);
room.add(client);
client.rooms.set(name, room);
namespace.emit("join-room", room, client.id);
});
client.on("leave-room", (name) => {
if (!namespace.rooms.has(name)) {
return;
}
const room = namespace.rooms.get(name);
room.remove(client);
namespace.emit("leave-room", room, client.id);
if (room.clients.size === 0) {
namespace.emit("delete-room", room);
namespace.rooms.delete(name);
}
});
client.emit("connect", client.id);
namespace.emit("connection", client);
}
getNamespace(namespacePath) {
if (this.namespaces.has(namespacePath))
return this.namespaces.get(namespacePath);
const wss = new WebSocketServer({ noServer: true });
wss.setMaxListeners(0);
const namespace = new Namespace(namespacePath, this, wss);
this.namespaces.set(namespacePath, namespace);
return namespace;
}
emit(name, ...args) {
this.namespaces.forEach((namespace) => {
var adapter = namespace.adapter();
namespace.rooms.forEach((room) => adapter = adapter.to(room.id));
adapter.emit(name, ...args);
});
}
close() {
this.clients.forEach((client) => client.close());
this.server.close();
}
on(event, listener) {
this.namespaces.forEach((namespace) => namespace.on(event, listener));
}
of(namespace) {
return this.getNamespace(namespace);
}
};
Server.isInitialized = false;
export {
Client,
Server,
ServerSideClient
};
//# sourceMappingURL=index.mjs.map