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/README.md +10 -0
- package/package.json +12 -3
- package/src/client.d.ts +52 -0
- package/src/client.d.ts.map +1 -0
- package/src/client.js +208 -230
- package/src/client.ts +257 -0
- package/src/index.d.ts +85 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +269 -299
- package/src/index.ts +356 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
export interface StelarClientOptions {
|
|
2
|
+
reconnection?: boolean;
|
|
3
|
+
reconnectionAttempts?: number;
|
|
4
|
+
reconnectionDelay?: number;
|
|
5
|
+
heartbeatInterval?: number;
|
|
6
|
+
ackTimeout?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface StelarEmitOptions {
|
|
10
|
+
ack?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type StelarEventHandler = (data: unknown) => void;
|
|
14
|
+
export type StelarBinaryHandler = (buffer: ArrayBuffer) => void;
|
|
15
|
+
|
|
16
|
+
class StelarClient {
|
|
17
|
+
private url: string;
|
|
18
|
+
private options: Required<StelarClientOptions>;
|
|
19
|
+
private ws: WebSocket | null = null;
|
|
20
|
+
private events = new Map<string, StelarEventHandler>();
|
|
21
|
+
private _wildcardHandler: ((data: { event: string; data: unknown; isBinary?: boolean; buffer?: ArrayBuffer }) => void) | null = null;
|
|
22
|
+
private connected = false;
|
|
23
|
+
private id: string | null = null;
|
|
24
|
+
private _reconnectAttempts = 0;
|
|
25
|
+
private _hbTimer: ReturnType<typeof setInterval> | null = null;
|
|
26
|
+
private _isManualClose = false;
|
|
27
|
+
private _acks = new Map<string, StelarEventHandler>();
|
|
28
|
+
|
|
29
|
+
constructor(urlOrPort: string | number = 'localhost:3000', options: StelarClientOptions = {}) {
|
|
30
|
+
if (typeof urlOrPort === 'number') {
|
|
31
|
+
this.url = `ws://localhost:${urlOrPort}`;
|
|
32
|
+
} else if (urlOrPort.includes('://')) {
|
|
33
|
+
this.url = urlOrPort.startsWith('http') ? 'ws' + urlOrPort.slice(4) : urlOrPort;
|
|
34
|
+
} else {
|
|
35
|
+
this.url = `ws://${urlOrPort}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.options = {
|
|
39
|
+
reconnection: options.reconnection !== false,
|
|
40
|
+
reconnectionAttempts: options.reconnectionAttempts || 5,
|
|
41
|
+
reconnectionDelay: options.reconnectionDelay || 1000,
|
|
42
|
+
heartbeatInterval: options.heartbeatInterval || 30000,
|
|
43
|
+
ackTimeout: options.ackTimeout || 5000
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setUrl(url: string): this {
|
|
48
|
+
this.url = url;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
on(event: string, handler: StelarEventHandler): this {
|
|
53
|
+
this.events.set(event, handler);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
off(event: string, handler: StelarEventHandler): this {
|
|
58
|
+
if (this.events.get(event) === handler) {
|
|
59
|
+
this.events.delete(event);
|
|
60
|
+
}
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onAll(handler: (data: { event: string; data: unknown; isBinary?: boolean; buffer?: ArrayBuffer }) => void): this {
|
|
65
|
+
this._wildcardHandler = handler;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onAck(name: string, handler: StelarEventHandler): this {
|
|
70
|
+
this._acks.set(name, handler);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
emit(event: string, data?: unknown, opts: StelarEmitOptions = {}): this {
|
|
75
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
76
|
+
const payload: Record<string, unknown> = { event, data };
|
|
77
|
+
if (opts.ack) {
|
|
78
|
+
payload._ackName = opts.ack;
|
|
79
|
+
}
|
|
80
|
+
this.ws.send(JSON.stringify(payload));
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
emitBinary(event: string, data: ArrayBuffer): this {
|
|
86
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
87
|
+
const header = JSON.stringify({ event });
|
|
88
|
+
const headerBytes = new TextEncoder().encode(header);
|
|
89
|
+
const combined = new Uint8Array(headerBytes.length + 1 + data.byteLength);
|
|
90
|
+
combined.set(headerBytes, 0);
|
|
91
|
+
combined[headerBytes.length] = 0;
|
|
92
|
+
combined.set(new Uint8Array(data), headerBytes.length + 1);
|
|
93
|
+
this.ws.send(combined);
|
|
94
|
+
}
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sendFile(file: ArrayBuffer): this {
|
|
99
|
+
return this.emitBinary('file', file);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
sendImage(blob: ArrayBuffer): this {
|
|
103
|
+
return this.emitBinary('image', blob);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
request(event: string, data: unknown, ackName: string): Promise<unknown> {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const timeout = setTimeout(() => {
|
|
109
|
+
reject(new Error(`ACK '${ackName}' timeout`));
|
|
110
|
+
}, this.options.ackTimeout);
|
|
111
|
+
|
|
112
|
+
const handler: StelarEventHandler = (responseData) => {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
this._acks.delete(ackName);
|
|
115
|
+
resolve(responseData);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this._acks.set(ackName, handler);
|
|
119
|
+
this.emit(event, data, { ack: ackName });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
joinRoom(room: string): this {
|
|
124
|
+
this.emit('join-room', room);
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
leaveRoom(): this {
|
|
129
|
+
this.emit('leave-room', {});
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private startHeartbeat(): void {
|
|
134
|
+
this._hbTimer = setInterval(() => {
|
|
135
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
136
|
+
this.emit('pong', Date.now());
|
|
137
|
+
}
|
|
138
|
+
}, this.options.heartbeatInterval);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private stopHeartbeat(): void {
|
|
142
|
+
if (this._hbTimer) {
|
|
143
|
+
clearInterval(this._hbTimer);
|
|
144
|
+
this._hbTimer = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private _connect(): void {
|
|
149
|
+
this._isManualClose = false;
|
|
150
|
+
this.ws = new WebSocket(this.url);
|
|
151
|
+
|
|
152
|
+
this.ws.onopen = () => {
|
|
153
|
+
this.connected = true;
|
|
154
|
+
this._reconnectAttempts = 0;
|
|
155
|
+
const handler = this.events.get('connect');
|
|
156
|
+
if (handler) handler(undefined);
|
|
157
|
+
this.startHeartbeat();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
this.ws.binaryType = 'arraybuffer';
|
|
161
|
+
|
|
162
|
+
this.ws.onmessage = (e: MessageEvent) => {
|
|
163
|
+
try {
|
|
164
|
+
if (e.data instanceof ArrayBuffer) {
|
|
165
|
+
const view = new Uint8Array(e.data);
|
|
166
|
+
let headerEnd = -1;
|
|
167
|
+
for (let i = 0; i < view.length; i++) {
|
|
168
|
+
if (view[i] === 0) {
|
|
169
|
+
headerEnd = i;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (headerEnd === -1) return;
|
|
174
|
+
|
|
175
|
+
const headerStr = new TextDecoder().decode(view.slice(0, headerEnd));
|
|
176
|
+
const header = JSON.parse(headerStr);
|
|
177
|
+
const buffer = view.slice(headerEnd + 1);
|
|
178
|
+
|
|
179
|
+
const handler = this.events.get(header.event);
|
|
180
|
+
if (handler) {
|
|
181
|
+
handler(buffer.buffer);
|
|
182
|
+
} else if (this._wildcardHandler) {
|
|
183
|
+
this._wildcardHandler({ event: header.event, data: buffer.buffer, isBinary: true, buffer: buffer.buffer });
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const msg = JSON.parse(e.data);
|
|
189
|
+
const { event, data, _isAck } = msg;
|
|
190
|
+
|
|
191
|
+
if (event === 'ping') return;
|
|
192
|
+
|
|
193
|
+
if (_isAck && this._acks.has(event)) {
|
|
194
|
+
const handler = this._acks.get(event)!;
|
|
195
|
+
handler(data);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const handler = this.events.get(event);
|
|
200
|
+
if (handler) handler(data);
|
|
201
|
+
|
|
202
|
+
if (this._wildcardHandler) {
|
|
203
|
+
this._wildcardHandler({ event, data });
|
|
204
|
+
}
|
|
205
|
+
} catch {}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.ws.onclose = () => {
|
|
209
|
+
this.connected = false;
|
|
210
|
+
this.stopHeartbeat();
|
|
211
|
+
const handler = this.events.get('disconnect');
|
|
212
|
+
if (handler) handler(undefined);
|
|
213
|
+
|
|
214
|
+
if (!this._isManualClose && this.options.reconnection && this._reconnectAttempts < this.options.reconnectionAttempts) {
|
|
215
|
+
this._reconnectAttempts++;
|
|
216
|
+
const reconHandler = this.events.get('reconnecting');
|
|
217
|
+
if (reconHandler) reconHandler(this._reconnectAttempts);
|
|
218
|
+
setTimeout(() => this.connect(), this.options.reconnectionDelay * this._reconnectAttempts);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.ws.onerror = (err: Event) => {
|
|
223
|
+
const handler = this.events.get('error');
|
|
224
|
+
if (handler) handler(err);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
connect(callback?: () => void): this {
|
|
229
|
+
this._connect();
|
|
230
|
+
if (callback) {
|
|
231
|
+
const checkConnection = setInterval(() => {
|
|
232
|
+
if (this.connected) {
|
|
233
|
+
clearInterval(checkConnection);
|
|
234
|
+
callback();
|
|
235
|
+
}
|
|
236
|
+
}, 100);
|
|
237
|
+
}
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
disconnect(): this {
|
|
242
|
+
this._isManualClose = true;
|
|
243
|
+
this.stopHeartbeat();
|
|
244
|
+
if (this.ws) this.ws.close();
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
isConnected(): boolean {
|
|
249
|
+
return this.connected;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getUrl(): string {
|
|
253
|
+
return this.url;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default StelarClient;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { IncomingMessage, Server } from 'http';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
export interface StelarOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
server?: Server;
|
|
6
|
+
namespace?: string;
|
|
7
|
+
heartbeatInterval?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface StelarClientInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
room: string | null;
|
|
12
|
+
lastPing: number;
|
|
13
|
+
}
|
|
14
|
+
export interface StelarContext {
|
|
15
|
+
id: string;
|
|
16
|
+
socket: WebSocket;
|
|
17
|
+
req: IncomingMessage;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
buffer?: Uint8Array;
|
|
20
|
+
isBinary?: boolean;
|
|
21
|
+
event?: string;
|
|
22
|
+
error?: Error;
|
|
23
|
+
emit: (event: string, data: unknown) => void;
|
|
24
|
+
send: (respId: string, data: unknown) => void;
|
|
25
|
+
emitBinary: (event: string, buffer: ArrayBuffer) => void;
|
|
26
|
+
broadcast: (event: string, data: unknown) => void;
|
|
27
|
+
broadcastBinary: (event: string, buffer: ArrayBuffer) => void;
|
|
28
|
+
to: (room: string, event: string, data: unknown) => void;
|
|
29
|
+
toId: (id: string, event: string, data: unknown) => void;
|
|
30
|
+
getClients: (room?: string) => {
|
|
31
|
+
id: string;
|
|
32
|
+
room: string | null;
|
|
33
|
+
}[];
|
|
34
|
+
joinRoom: (room: string) => void;
|
|
35
|
+
leaveRoom: () => void;
|
|
36
|
+
ack: (ackName: string, data: unknown) => void;
|
|
37
|
+
}
|
|
38
|
+
export interface StelarMiddleware {
|
|
39
|
+
(ctx: StelarContext, next: () => void): void;
|
|
40
|
+
}
|
|
41
|
+
export type StelarEventHandler = (ctx: StelarContext) => void;
|
|
42
|
+
export type StelarWildcardHandler = (data: {
|
|
43
|
+
event: string;
|
|
44
|
+
data: StelarContext;
|
|
45
|
+
}) => void;
|
|
46
|
+
declare class StelarServer {
|
|
47
|
+
private port;
|
|
48
|
+
private server;
|
|
49
|
+
private namespace;
|
|
50
|
+
private wss;
|
|
51
|
+
private clients;
|
|
52
|
+
private events;
|
|
53
|
+
private middlewares;
|
|
54
|
+
private heartbeatInterval;
|
|
55
|
+
private _hbTimer;
|
|
56
|
+
private _wildcardHandler;
|
|
57
|
+
private _connectionHandler;
|
|
58
|
+
private _acks;
|
|
59
|
+
private _externalServers;
|
|
60
|
+
constructor(options?: StelarOptions);
|
|
61
|
+
static of(path: string, options?: StelarOptions): StelarServer;
|
|
62
|
+
use(middleware: StelarMiddleware): this;
|
|
63
|
+
on(event: string, handler: StelarEventHandler): this;
|
|
64
|
+
onAll(handler: StelarWildcardHandler): this;
|
|
65
|
+
onConnection(handler: StelarEventHandler): this;
|
|
66
|
+
onAck(name: string, handler: StelarEventHandler): this;
|
|
67
|
+
broadcast(event: string, data: unknown): this;
|
|
68
|
+
broadcastBinary(event: string, buffer: ArrayBuffer): void;
|
|
69
|
+
to(room: string, event: string, data: unknown): this;
|
|
70
|
+
toId(id: string, event: string, data: unknown): this;
|
|
71
|
+
getClients(room?: string): {
|
|
72
|
+
id: string;
|
|
73
|
+
room: string | null;
|
|
74
|
+
}[];
|
|
75
|
+
getPort(): number;
|
|
76
|
+
private runMiddlewares;
|
|
77
|
+
private startHeartbeat;
|
|
78
|
+
private handleConnection;
|
|
79
|
+
start(callback?: (port: number) => void): Promise<number>;
|
|
80
|
+
stop(): this;
|
|
81
|
+
}
|
|
82
|
+
export default StelarServer;
|
|
83
|
+
export { StelarServer };
|
|
84
|
+
export { default as StelarClient } from './client.js';
|
|
85
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|