stelar-time-real 1.0.3 → 1.1.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/README.md +94 -3
- package/package.json +1 -1
- package/src/client.js +67 -2
- package/src/index.js +42 -0
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Tu propio sistema de tiempo real personalizado. Una librería ligera y sin depen
|
|
|
13
13
|
- 🎯 **Personalizable** - Vos controlás todo, nada de código ajeno
|
|
14
14
|
- 🔌 **Compatible** - Funciona con Express, Fastify, HTTP nativo, etc.
|
|
15
15
|
- 💓 **Heartbeat incluido** - Detecta desconexiones automáticamente
|
|
16
|
+
- 🌐 **Namespaces** - Múltiples canales independientes (`/chat`, `/game`, etc.)
|
|
17
|
+
- ⚡ **ACK ultra rápido** - Request-response con Promesas, sin overhead
|
|
16
18
|
|
|
17
19
|
## Instalación
|
|
18
20
|
|
|
@@ -190,15 +192,78 @@ stelar.on('mensaje', (ctx) => {
|
|
|
190
192
|
ctx.socket // WebSocket del cliente
|
|
191
193
|
ctx.req // Request HTTP original
|
|
192
194
|
ctx.data // Datos recibidos
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
// Métodos disponibles:
|
|
195
197
|
ctx.emit('evento', data) // Enviar solo a este cliente
|
|
198
|
+
ctx.send('respuesta', data) // Responder a un ACK
|
|
196
199
|
ctx.broadcast('evento', data) // Enviar a todos
|
|
197
200
|
ctx.to('sala', 'evento', data) // Enviar a una sala
|
|
198
201
|
ctx.toId('id', 'evento', data) // Enviar a un cliente específico
|
|
199
202
|
ctx.getClients('sala') // Ver clientes en sala
|
|
200
203
|
ctx.joinRoom('sala') // Unir a sala
|
|
201
204
|
ctx.leaveRoom() // Salir de sala
|
|
205
|
+
ctx.ack('miAck', data) // Responder a un ACK personalizado
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Namespaces
|
|
210
|
+
|
|
211
|
+
Crear canales independientes:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
import { StelarServer } from 'stelar-time-real';
|
|
215
|
+
|
|
216
|
+
// Namespace principal
|
|
217
|
+
const main = new StelarServer({ server, namespace: '/' });
|
|
218
|
+
|
|
219
|
+
// Namespace de chat
|
|
220
|
+
const chat = StelarServer.of('/chat', { server });
|
|
221
|
+
chat.on('message', (ctx) => {
|
|
222
|
+
ctx.broadcast('message', ctx.data);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Namespace de game
|
|
226
|
+
const game = StelarServer.of('/game', { server });
|
|
227
|
+
game.on('move', (ctx) => {
|
|
228
|
+
ctx.to(ctx.data.room, 'move', ctx.data);
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### ACK (Request-Response)
|
|
233
|
+
|
|
234
|
+
Sistema ultra eficiente con Promesas:
|
|
235
|
+
|
|
236
|
+
**Servidor:**
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
// Registrar un ACK handler
|
|
240
|
+
stelar.onAck('getUser', (ctx) => {
|
|
241
|
+
return { id: ctx.data.id, name: 'John' };
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// O con lógica más compleja
|
|
245
|
+
stelar.onAck('saveData', (ctx) => {
|
|
246
|
+
const result = saveToDatabase(ctx.data);
|
|
247
|
+
return { success: true, id: result.id };
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Cliente:**
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// Usando request() - retorna Promise
|
|
255
|
+
const user = await client.request('getUser', { id: 1 }, 'userData');
|
|
256
|
+
console.log(user); // { id: 1, name: 'John' }
|
|
257
|
+
|
|
258
|
+
// O emitiendo con callback
|
|
259
|
+
client.emit('getUser', { id: 1 }, { ack: 'userData' });
|
|
260
|
+
client.on('userData', (data) => {
|
|
261
|
+
console.log(data);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ACK desde el servidor al cliente
|
|
265
|
+
client.onAck('serverPush', (data) => {
|
|
266
|
+
console.log('El servidor envió:', data);
|
|
202
267
|
});
|
|
203
268
|
```
|
|
204
269
|
|
|
@@ -255,11 +320,32 @@ client.onAll(({ event, data }) => {
|
|
|
255
320
|
});
|
|
256
321
|
```
|
|
257
322
|
|
|
258
|
-
**`.
|
|
259
|
-
|
|
323
|
+
**`.onAck(name, handler)`**
|
|
324
|
+
Escuchar respuestas de ACK del servidor.
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
client.onAck('userData', (data) => {
|
|
328
|
+
console.log('Datos recibidos:', data);
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**`.emit(event, data, opts)`**
|
|
333
|
+
Enviar eventos al servidor. Soporta `opts.ack` para ACKs.
|
|
260
334
|
|
|
261
335
|
```javascript
|
|
262
336
|
client.emit('chat', { mensaje: 'Hola!' });
|
|
337
|
+
client.emit('getUser', { id: 1 }, { ack: 'userData' });
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**`.request(event, data, ackName)`**
|
|
341
|
+
Enviar y esperar respuesta como Promise.
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
const result = await client.request('getUser', { id: 1 }, 'userData');
|
|
345
|
+
console.log(result); // { id: 1, name: 'John' }
|
|
346
|
+
|
|
347
|
+
// Con timeout opcional
|
|
348
|
+
const client = new StelarClient(3000, { ackTimeout: 10000 });
|
|
263
349
|
```
|
|
264
350
|
|
|
265
351
|
**`.joinRoom(room)`**
|
|
@@ -428,6 +514,11 @@ client.connect();
|
|
|
428
514
|
|
|
429
515
|
## Changelog
|
|
430
516
|
|
|
517
|
+
### v1.1.0
|
|
518
|
+
- Namespaces: `StelarServer.of('/chat')` para múltiples canales
|
|
519
|
+
- ACK personalizado: `ctx.ack('nombre', data)` y `client.request(event, data, 'ackName')`
|
|
520
|
+
- Sistema de request-response con Promesas, ultra eficiente
|
|
521
|
+
|
|
431
522
|
### v1.0.3
|
|
432
523
|
- Exportación mejorada: un solo import para servidor y cliente
|
|
433
524
|
- `import { StelarServer, StelarClient } from 'stelar-time-real'`
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -12,7 +12,8 @@ class StelarClient {
|
|
|
12
12
|
reconnection: options.reconnection !== false,
|
|
13
13
|
reconnectionAttempts: options.reconnectionAttempts || 5,
|
|
14
14
|
reconnectionDelay: options.reconnectionDelay || 1000,
|
|
15
|
-
heartbeatInterval: options.heartbeatInterval || 30000
|
|
15
|
+
heartbeatInterval: options.heartbeatInterval || 30000,
|
|
16
|
+
ackTimeout: options.ackTimeout || 5000
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
this.ws = null;
|
|
@@ -23,6 +24,65 @@ class StelarClient {
|
|
|
23
24
|
this._reconnectAttempts = 0;
|
|
24
25
|
this._hbTimer = null;
|
|
25
26
|
this._isManualClose = false;
|
|
27
|
+
this._acks = {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setUrl(url) {
|
|
31
|
+
this.url = url;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
on(event, handler) {
|
|
36
|
+
this.events[event] = handler;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onAll(handler) {
|
|
41
|
+
this._wildcardHandler = handler;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onAck(name, handler) {
|
|
46
|
+
this._acks[name] = handler;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
emit(event, data, opts = {}) {
|
|
51
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
52
|
+
const payload = { event, data };
|
|
53
|
+
if (opts.ack) {
|
|
54
|
+
payload._ackName = opts.ack;
|
|
55
|
+
}
|
|
56
|
+
this.ws.send(JSON.stringify(payload));
|
|
57
|
+
}
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
request(event, data, ackName) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const timeout = setTimeout(() => {
|
|
64
|
+
reject(new Error(`ACK '${ackName}' timeout`));
|
|
65
|
+
}, this.options.ackTimeout);
|
|
66
|
+
|
|
67
|
+
const handler = (responseData) => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
this.off(ackName, handler);
|
|
70
|
+
resolve(responseData);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.on(ackName, handler);
|
|
74
|
+
this.emit(event, data, { ack: ackName });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
joinRoom(room) {
|
|
79
|
+
this.emit('join-room', room);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
leaveRoom() {
|
|
84
|
+
this.emit('leave-room', {});
|
|
85
|
+
return this;
|
|
26
86
|
}
|
|
27
87
|
|
|
28
88
|
setUrl(url) {
|
|
@@ -86,10 +146,15 @@ class StelarClient {
|
|
|
86
146
|
this.ws.onmessage = (e) => {
|
|
87
147
|
try {
|
|
88
148
|
const msg = JSON.parse(e.data);
|
|
89
|
-
const { event, data } = msg;
|
|
149
|
+
const { event, data, _isAck } = msg;
|
|
90
150
|
|
|
91
151
|
if (event === 'ping') return;
|
|
92
152
|
|
|
153
|
+
if (_isAck && this._acks[event]) {
|
|
154
|
+
this._acks[event](data);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
93
158
|
if (this.events[event]) this.events[event](data);
|
|
94
159
|
|
|
95
160
|
if (this._wildcardHandler) {
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ class StelarServer {
|
|
|
5
5
|
constructor(options = {}) {
|
|
6
6
|
this.port = options.port || 3000;
|
|
7
7
|
this.server = options.server || null;
|
|
8
|
+
this.namespace = options.namespace || '/';
|
|
8
9
|
this.wss = null;
|
|
9
10
|
this.clients = new Map();
|
|
10
11
|
this.events = {};
|
|
@@ -13,6 +14,15 @@ class StelarServer {
|
|
|
13
14
|
this._hbTimer = null;
|
|
14
15
|
this._wildcardHandler = null;
|
|
15
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;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
use(middleware) {
|
|
@@ -35,6 +45,11 @@ class StelarServer {
|
|
|
35
45
|
return this;
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
onAck(name, handler) {
|
|
49
|
+
this._acks[name] = handler;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
broadcast(event, data) {
|
|
39
54
|
this.clients.forEach((info, client) => {
|
|
40
55
|
client.send(JSON.stringify({ event, data }));
|
|
@@ -98,6 +113,14 @@ class StelarServer {
|
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
_handleConnection(client, req) {
|
|
116
|
+
const urlPath = new URL(req.url, `http://localhost`).pathname;
|
|
117
|
+
const nsPath = this.namespace === '/' ? '/' : this.namespace;
|
|
118
|
+
|
|
119
|
+
if (nsPath !== '/' && urlPath !== nsPath) {
|
|
120
|
+
client.close();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
const clientId = Math.random().toString(36).substring(7);
|
|
102
125
|
const clientInfo = { id: clientId, room: null, lastPing: Date.now() };
|
|
103
126
|
this.clients.set(client, clientInfo);
|
|
@@ -107,6 +130,7 @@ class StelarServer {
|
|
|
107
130
|
socket: client,
|
|
108
131
|
req,
|
|
109
132
|
emit: (evt, d) => client.send(JSON.stringify({ event: evt, data: d })),
|
|
133
|
+
send: (respId, d) => client.send(JSON.stringify({ event: respId, data: d, _isAck: true })),
|
|
110
134
|
broadcast: (evt, d) => this.broadcast(evt, d),
|
|
111
135
|
to: (room, evt, d) => this.to(room, evt, d),
|
|
112
136
|
toId: (id, evt, d) => this.toId(id, evt, d),
|
|
@@ -117,6 +141,15 @@ class StelarServer {
|
|
|
117
141
|
},
|
|
118
142
|
leaveRoom: () => {
|
|
119
143
|
clientInfo.room = null;
|
|
144
|
+
},
|
|
145
|
+
ack: (ackName, data) => {
|
|
146
|
+
const ackHandler = this._acks[ackName];
|
|
147
|
+
if (ackHandler) {
|
|
148
|
+
const result = ackHandler({ ...ctx, data });
|
|
149
|
+
if (result !== undefined) {
|
|
150
|
+
client.send(JSON.stringify({ event: ackName, data: result, _isAck: true }));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
120
153
|
}
|
|
121
154
|
};
|
|
122
155
|
|
|
@@ -146,6 +179,15 @@ class StelarServer {
|
|
|
146
179
|
client.send(JSON.stringify({ event: 'left-room', data }));
|
|
147
180
|
}
|
|
148
181
|
|
|
182
|
+
if (msg._ackName && this._acks[msg._ackName]) {
|
|
183
|
+
const ackHandler = this._acks[msg._ackName];
|
|
184
|
+
const result = ackHandler({ ...ctx, data });
|
|
185
|
+
if (result !== undefined) {
|
|
186
|
+
client.send(JSON.stringify({ event: msg._ackName, data: result, _isAck: true }));
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
149
191
|
const eventCtx = { ...ctx, data };
|
|
150
192
|
|
|
151
193
|
if (this.events[event]) {
|