stelar-time-real 1.2.2 → 2.0.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,311 +1,281 @@
1
1
  import { createServer } from 'http';
2
2
  import { WebSocketServer } from 'ws';
3
-
4
3
  class StelarServer {
5
- constructor(options = {}) {
6
- this.port = options.port || 3000;
7
- this.server = options.server || null;
8
- this.namespace = options.namespace || '/';
9
- this.wss = null;
10
- this.clients = new Map();
11
- this.events = {};
12
- this.middlewares = [];
13
- this.heartbeatInterval = options.heartbeatInterval || 30000;
14
- this._hbTimer = null;
15
- this._wildcardHandler = null;
16
- this._connectionHandler = null;
17
- this._acks = {};
18
- this._parent = null;
19
- this._children = new Map();
20
- }
21
-
22
- static of(path, options = {}) {
23
- const ns = new StelarServer({ ...options, namespace: path });
24
- ns._parent = this;
25
- return ns;
26
- }
27
-
28
- use(middleware) {
29
- this.middlewares.push(middleware);
30
- return this;
31
- }
32
-
33
- on(event, handler) {
34
- this.events[event] = handler;
35
- return this;
36
- }
37
-
38
- onAll(handler) {
39
- this._wildcardHandler = handler;
40
- return this;
41
- }
42
-
43
- onConnection(handler) {
44
- this._connectionHandler = handler;
45
- return this;
46
- }
47
-
48
- onAck(name, handler) {
49
- this._acks[name] = handler;
50
- return this;
51
- }
52
-
53
- broadcast(event, data) {
54
- this.clients.forEach((info, client) => {
55
- client.send(JSON.stringify({ event, data }));
56
- });
57
- return this;
58
- }
59
-
60
- broadcastBinary(event, buffer) {
61
- const header = JSON.stringify({ event, _binary: true });
62
- const headerBytes = new TextEncoder().encode(header);
63
- const combined = new Uint8Array(headerBytes.length + 1 + buffer.byteLength);
64
- combined.set(headerBytes, 0);
65
- combined[headerBytes.length] = 0;
66
- combined.set(new Uint8Array(buffer), headerBytes.length + 1);
67
-
68
- this.clients.forEach((info, client) => {
69
- client.send(combined);
70
- });
71
- return this;
72
- }
73
-
74
- to(room, event, data) {
75
- this.clients.forEach((info, client) => {
76
- if (info.room === room) {
77
- client.send(JSON.stringify({ event, data }));
78
- }
79
- });
80
- return this;
81
- }
82
-
83
- toId(id, event, data) {
84
- this.clients.forEach((info, client) => {
85
- if (info.id === id) {
86
- client.send(JSON.stringify({ event, data }));
87
- }
88
- });
89
- return this;
90
- }
91
-
92
- getClients(room) {
93
- const list = [];
94
- this.clients.forEach((info, client) => {
95
- if (!room || info.room === room) list.push({ id: info.id, room: info.room });
96
- });
97
- return list;
98
- }
99
-
100
- getPort() {
101
- const address = this.server?.address();
102
- if (address && typeof address === 'object') {
103
- return address.port;
4
+ constructor(options = {}) {
5
+ this.server = null;
6
+ this.wss = null;
7
+ this.clients = new Map();
8
+ this.events = new Map();
9
+ this.middlewares = [];
10
+ this._hbTimer = null;
11
+ this._wildcardHandler = null;
12
+ this._connectionHandler = null;
13
+ this._acks = new Map();
14
+ this._externalServers = new WeakSet();
15
+ this.port = options.port || 3000;
16
+ this.server = options.server || null;
17
+ this.namespace = options.namespace || '/';
18
+ this.heartbeatInterval = options.heartbeatInterval || 30000;
104
19
  }
105
- return this.port;
106
- }
107
-
108
- _runMiddlewares(ctx, next) {
109
- const run = (i) => {
110
- if (i >= this.middlewares.length) return next();
111
- this.middlewares[i](ctx, () => run(i + 1));
112
- };
113
- run(0);
114
- }
115
-
116
- _startHeartbeat() {
117
- this._hbTimer = setInterval(() => {
118
- this.clients.forEach((info, client) => {
119
- if (info.lastPing && Date.now() - info.lastPing > this.heartbeatInterval * 2) {
120
- client.close();
121
- this.clients.delete(client);
122
- } else {
123
- client.send(JSON.stringify({ event: 'ping', data: Date.now() }));
124
- }
125
- });
126
- }, this.heartbeatInterval);
127
- }
128
-
129
- _handleConnection(client, req) {
130
- const urlPath = new URL(req.url, `http://localhost`).pathname;
131
- const nsPath = this.namespace === '/' ? '/' : this.namespace;
132
-
133
- if (nsPath !== '/' && urlPath !== nsPath) {
134
- client.close();
135
- return;
20
+ static of(path, options = {}) {
21
+ return new StelarServer({ ...options, namespace: path });
136
22
  }
137
-
138
- const clientId = Math.random().toString(36).substring(7);
139
- const clientInfo = { id: clientId, room: null, lastPing: Date.now() };
140
- this.clients.set(client, clientInfo);
141
-
142
- const ctx = {
143
- id: clientId,
144
- socket: client,
145
- req,
146
- emit: (evt, d) => client.send(JSON.stringify({ event: evt, data: d })),
147
- send: (respId, d) => client.send(JSON.stringify({ event: respId, data: d, _isAck: true })),
148
- emitBinary: (evt, buffer) => client.send(buffer),
149
- broadcast: (evt, d) => this.broadcast(evt, d),
150
- broadcastBinary: (evt, buffer) => this.broadcastBinary(evt, buffer),
151
- to: (room, evt, d) => this.to(room, evt, d),
152
- toId: (id, evt, d) => this.toId(id, evt, d),
153
- getClients: (room) => this.getClients(room),
154
- joinRoom: (room) => {
155
- clientInfo.room = room;
156
- client.send(JSON.stringify({ event: 'joined-room', data: room }));
157
- },
158
- leaveRoom: () => {
159
- clientInfo.room = null;
160
- },
161
- ack: (ackName, data) => {
162
- const ackHandler = this._acks[ackName];
163
- if (ackHandler) {
164
- const result = ackHandler({ ...ctx, data });
165
- if (result !== undefined) {
166
- client.send(JSON.stringify({ event: ackName, data: result, _isAck: true }));
167
- }
168
- }
169
- }
170
- };
171
-
172
- this._runMiddlewares(ctx, () => {
173
- if (this._connectionHandler) {
174
- this._connectionHandler(ctx);
175
- }
176
- });
177
-
178
- client.on('message', (raw, isBinary) => {
179
- if (isBinary) {
180
- try {
181
- const view = new Uint8Array(raw);
182
- let headerEnd = -1;
183
- for (let i = 0; i < view.length; i++) {
184
- if (view[i] === 0) {
185
- headerEnd = i;
186
- break;
23
+ use(middleware) {
24
+ this.middlewares.push(middleware);
25
+ return this;
26
+ }
27
+ on(event, handler) {
28
+ this.events.set(event, handler);
29
+ return this;
30
+ }
31
+ onAll(handler) {
32
+ this._wildcardHandler = handler;
33
+ return this;
34
+ }
35
+ onConnection(handler) {
36
+ this._connectionHandler = handler;
37
+ return this;
38
+ }
39
+ onAck(name, handler) {
40
+ this._acks.set(name, handler);
41
+ return this;
42
+ }
43
+ broadcast(event, data) {
44
+ this.clients.forEach((_, client) => {
45
+ client.send(JSON.stringify({ event, data }));
46
+ });
47
+ return this;
48
+ }
49
+ broadcastBinary(event, buffer) {
50
+ const header = JSON.stringify({ event, _binary: true });
51
+ const headerBytes = new TextEncoder().encode(header);
52
+ const combined = new Uint8Array(headerBytes.length + 1 + buffer.byteLength);
53
+ combined.set(headerBytes, 0);
54
+ combined[headerBytes.length] = 0;
55
+ combined.set(new Uint8Array(buffer), headerBytes.length + 1);
56
+ this.clients.forEach((_, client) => {
57
+ client.send(combined);
58
+ });
59
+ }
60
+ to(room, event, data) {
61
+ this.clients.forEach((info, client) => {
62
+ if (info.room === room) {
63
+ client.send(JSON.stringify({ event, data }));
187
64
  }
188
- }
189
- if (headerEnd === -1) return;
190
-
191
- const headerStr = new TextDecoder().decode(view.slice(0, headerEnd));
192
- const header = JSON.parse(headerStr);
193
- const data = view.slice(headerEnd + 1);
194
-
195
- const eventCtx = { ...ctx, data, buffer: data, isBinary: true };
196
-
197
- if (this.events[header.event]) {
198
- this.events[header.event](eventCtx);
199
- }
200
- if (this._wildcardHandler) {
201
- this._wildcardHandler({ event: header.event, data: eventCtx });
202
- }
203
- } catch (e) {}
204
- return;
205
- }
206
-
207
- try {
208
- const msg = JSON.parse(raw);
209
- const { event, data } = msg;
210
-
211
- if (event === 'pong') {
212
- clientInfo.lastPing = Date.now();
213
- return;
214
- }
215
-
216
- if (event === 'join-room') {
217
- clientInfo.room = data;
218
- client.send(JSON.stringify({ event: 'joined-room', data }));
219
- }
220
-
221
- if (event === 'leave-room') {
222
- clientInfo.room = null;
223
- client.send(JSON.stringify({ event: 'left-room', data }));
224
- }
225
-
226
- if (msg._ackName && this._acks[msg._ackName]) {
227
- const ackHandler = this._acks[msg._ackName];
228
- const result = ackHandler({ ...ctx, data });
229
- if (result !== undefined) {
230
- client.send(JSON.stringify({ event: msg._ackName, data: result, _isAck: true }));
231
- }
232
- return;
233
- }
234
-
235
- const eventCtx = { ...ctx, data };
236
-
237
- if (this.events[event]) {
238
- this.events[event](eventCtx);
65
+ });
66
+ return this;
67
+ }
68
+ toId(id, event, data) {
69
+ this.clients.forEach((info, client) => {
70
+ if (info.id === id) {
71
+ client.send(JSON.stringify({ event, data }));
72
+ }
73
+ });
74
+ return this;
75
+ }
76
+ getClients(room) {
77
+ const list = [];
78
+ this.clients.forEach((info) => {
79
+ if (!room || info.room === room)
80
+ list.push({ id: info.id, room: info.room });
81
+ });
82
+ return list;
83
+ }
84
+ getPort() {
85
+ const address = this.server?.address();
86
+ if (address && typeof address === 'object') {
87
+ return address.port;
239
88
  }
240
-
241
- if (this._wildcardHandler) {
242
- this._wildcardHandler({ event, data: eventCtx });
89
+ return this.port;
90
+ }
91
+ runMiddlewares(ctx, next) {
92
+ const run = (i) => {
93
+ if (i >= this.middlewares.length)
94
+ return next();
95
+ this.middlewares[i](ctx, () => run(i + 1));
96
+ };
97
+ run(0);
98
+ }
99
+ startHeartbeat() {
100
+ this._hbTimer = setInterval(() => {
101
+ this.clients.forEach((info, client) => {
102
+ if (info.lastPing && Date.now() - info.lastPing > this.heartbeatInterval * 2) {
103
+ client.close();
104
+ this.clients.delete(client);
105
+ }
106
+ else {
107
+ client.send(JSON.stringify({ event: 'ping', data: Date.now() }));
108
+ }
109
+ });
110
+ }, this.heartbeatInterval);
111
+ }
112
+ handleConnection(client, req) {
113
+ const urlPath = new URL(req.url || '/', 'http://localhost').pathname;
114
+ const nsPath = this.namespace === '/' ? '/' : this.namespace;
115
+ if (nsPath !== '/' && urlPath !== nsPath) {
116
+ client.close();
117
+ return;
243
118
  }
244
- } catch (e) {}
245
- });
246
-
247
- client.on('close', () => {
248
- const clientInfo = this.clients.get(client);
249
- if (this.events['disconnect'] && clientInfo) {
250
- this.events['disconnect']({ id: clientInfo.id });
251
- }
252
- this.clients.delete(client);
253
- });
254
-
255
- client.on('error', (err) => {
256
- if (this.events['error']) {
257
- this.events['error']({ id: clientId, error: err });
258
- }
259
- });
260
- }
261
-
262
- start(callback) {
263
- return new Promise((resolve) => {
264
- const startServer = (httpServer) => {
265
- this.server = httpServer;
266
- this.wss = new WebSocketServer({ server: httpServer });
267
- this.wss.on('connection', (client, req) => this._handleConnection(client, req));
268
- this._startHeartbeat();
269
-
270
- const finalPort = this.getPort();
271
- if (callback) callback(finalPort);
272
- resolve(finalPort);
273
- };
274
-
275
- if (this.server) {
276
- startServer(this.server);
277
- } else {
278
- const tryListen = (port) => {
279
- this.server = createServer((req, res) => {
280
- res.writeHead(200, { 'Content-Type': 'text/plain' });
281
- res.end('Stelar Time Real Server');
282
- });
283
-
284
- this.server.on('error', (err) => {
285
- if (err.code === 'EADDRINUSE' && port < 65535) {
286
- tryListen(port + 1);
119
+ const clientId = Math.random().toString(36).substring(7);
120
+ const clientInfo = { id: clientId, room: null, lastPing: Date.now() };
121
+ this.clients.set(client, clientInfo);
122
+ const ctx = {
123
+ id: clientId,
124
+ socket: client,
125
+ req,
126
+ emit: (evt, d) => client.send(JSON.stringify({ event: evt, data: d })),
127
+ send: (respId, d) => client.send(JSON.stringify({ event: respId, data: d, _isAck: true })),
128
+ emitBinary: (evt, buffer) => client.send(buffer),
129
+ broadcast: (evt, d) => this.broadcast(evt, d),
130
+ broadcastBinary: (evt, buffer) => this.broadcastBinary(evt, buffer),
131
+ to: (room, evt, d) => this.to(room, evt, d),
132
+ toId: (id, evt, d) => this.toId(id, evt, d),
133
+ getClients: (room) => this.getClients(room),
134
+ joinRoom: (room) => {
135
+ clientInfo.room = room;
136
+ client.send(JSON.stringify({ event: 'joined-room', data: room }));
137
+ },
138
+ leaveRoom: () => {
139
+ clientInfo.room = null;
140
+ },
141
+ ack: (ackName, data) => {
142
+ const ackHandler = this._acks.get(ackName);
143
+ if (ackHandler) {
144
+ const result = ackHandler({ ...ctx, data });
145
+ if (result !== undefined) {
146
+ client.send(JSON.stringify({ event: ackName, data: result, _isAck: true }));
147
+ }
148
+ }
287
149
  }
288
- });
289
-
290
- this.server.listen(port, () => {
291
- this.port = port;
292
- startServer(this.server);
293
- });
294
150
  };
295
- tryListen(this.port);
296
- }
297
- });
298
- }
299
-
300
- stop() {
301
- if (this._hbTimer) clearInterval(this._hbTimer);
302
- if (this.wss) this.wss.close();
303
- if (this.server && !this._externalServer) this.server.close();
304
- return this;
305
- }
151
+ this.runMiddlewares(ctx, () => {
152
+ if (this._connectionHandler) {
153
+ this._connectionHandler(ctx);
154
+ }
155
+ });
156
+ client.on('message', (raw, isBinary) => {
157
+ if (isBinary) {
158
+ try {
159
+ const view = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
160
+ let headerEnd = -1;
161
+ for (let i = 0; i < view.length; i++) {
162
+ if (view[i] === 0) {
163
+ headerEnd = i;
164
+ break;
165
+ }
166
+ }
167
+ if (headerEnd === -1)
168
+ return;
169
+ const headerStr = new TextDecoder().decode(view.slice(0, headerEnd));
170
+ const header = JSON.parse(headerStr);
171
+ const data = view.slice(headerEnd + 1);
172
+ const eventCtx = { ...ctx, data, buffer: data, isBinary: true };
173
+ const handler = this.events.get(header.event);
174
+ if (handler) {
175
+ handler(eventCtx);
176
+ }
177
+ else if (this._wildcardHandler) {
178
+ this._wildcardHandler({ event: header.event, data: eventCtx });
179
+ }
180
+ }
181
+ catch { }
182
+ return;
183
+ }
184
+ try {
185
+ const msg = JSON.parse(raw.toString());
186
+ const { event, data } = msg;
187
+ if (event === 'pong') {
188
+ clientInfo.lastPing = Date.now();
189
+ return;
190
+ }
191
+ if (event === 'join-room') {
192
+ clientInfo.room = data;
193
+ client.send(JSON.stringify({ event: 'joined-room', data }));
194
+ }
195
+ if (event === 'leave-room') {
196
+ clientInfo.room = null;
197
+ client.send(JSON.stringify({ event: 'left-room', data }));
198
+ }
199
+ if (msg._ackName && this._acks.has(msg._ackName)) {
200
+ const ackHandler = this._acks.get(msg._ackName);
201
+ const result = ackHandler({ ...ctx, data });
202
+ if (result !== undefined) {
203
+ client.send(JSON.stringify({ event: msg._ackName, data: result, _isAck: true }));
204
+ }
205
+ return;
206
+ }
207
+ const eventCtx = { ...ctx, data };
208
+ const handler = this.events.get(event);
209
+ if (handler) {
210
+ handler(eventCtx);
211
+ }
212
+ else if (this._wildcardHandler) {
213
+ this._wildcardHandler({ event, data: eventCtx });
214
+ }
215
+ }
216
+ catch { }
217
+ });
218
+ client.on('close', () => {
219
+ const info = this.clients.get(client);
220
+ if (this.events.has('disconnect') && info) {
221
+ const handler = this.events.get('disconnect');
222
+ handler({ id: info.id, socket: client, req: req, emit: () => { }, send: () => { }, emitBinary: () => { }, broadcast: () => { }, broadcastBinary: () => { }, to: () => { }, toId: () => { }, getClients: () => [], joinRoom: () => { }, leaveRoom: () => { }, ack: () => { } });
223
+ }
224
+ this.clients.delete(client);
225
+ });
226
+ client.on('error', (err) => {
227
+ if (this.events.has('error')) {
228
+ const handler = this.events.get('error');
229
+ handler({ id: clientId, socket: client, req: req, emit: () => { }, send: () => { }, emitBinary: () => { }, broadcast: () => { }, broadcastBinary: () => { }, to: () => { }, toId: () => { }, getClients: () => [], joinRoom: () => { }, leaveRoom: () => { }, ack: () => { }, error: err });
230
+ }
231
+ });
232
+ }
233
+ start(callback) {
234
+ return new Promise((resolve) => {
235
+ const startServer = (httpServer) => {
236
+ this.server = httpServer;
237
+ this.wss = new WebSocketServer({ server: httpServer });
238
+ this.wss.on('connection', (client, req) => this.handleConnection(client, req));
239
+ this.startHeartbeat();
240
+ const finalPort = this.getPort();
241
+ if (callback)
242
+ callback(finalPort);
243
+ resolve(finalPort);
244
+ };
245
+ if (this.server) {
246
+ this._externalServers.add(this.server);
247
+ startServer(this.server);
248
+ }
249
+ else {
250
+ const tryListen = (port) => {
251
+ this.server = createServer((_, res) => {
252
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
253
+ res.end('Stelar Time Real Server');
254
+ });
255
+ this.server.on('error', (err) => {
256
+ if (err.code === 'EADDRINUSE' && port < 65535) {
257
+ tryListen(port + 1);
258
+ }
259
+ });
260
+ this.server.listen(port, () => {
261
+ this.port = port;
262
+ startServer(this.server);
263
+ });
264
+ };
265
+ tryListen(this.port);
266
+ }
267
+ });
268
+ }
269
+ stop() {
270
+ if (this._hbTimer)
271
+ clearInterval(this._hbTimer);
272
+ if (this.wss)
273
+ this.wss.close();
274
+ if (this.server && !this._externalServers.has(this.server))
275
+ this.server.close();
276
+ return this;
277
+ }
306
278
  }
307
-
308
- import StelarClient from './client.js';
309
-
310
279
  export default StelarServer;
311
- export { StelarServer, StelarClient };
280
+ export { StelarServer };
281
+ export { default as StelarClient } from './client.js';