packet-events-js 1.0.0 → 1.0.1
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 +358 -14
- package/package.json +2 -3
- package/src/advanced/ConnectionManager.js +486 -0
- package/src/advanced/MiddlewareSystem.js +416 -0
- package/src/advanced/PacketRecorder.js +336 -0
- package/src/advanced/ProtocolAnalyzer.js +396 -0
- package/src/advanced/index.js +32 -0
- package/src/auth/AuthHandler.js +246 -0
- package/src/index.js +3 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { EventEmitter } from '../events/EventEmitter.js';
|
|
2
|
+
|
|
3
|
+
export class MiddlewareContext {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.packet = options.packet;
|
|
6
|
+
this.direction = options.direction;
|
|
7
|
+
this.state = options.state;
|
|
8
|
+
this.rawData = options.rawData;
|
|
9
|
+
this.client = options.client;
|
|
10
|
+
this.timestamp = Date.now();
|
|
11
|
+
this.metadata = {};
|
|
12
|
+
this.cancelled = false;
|
|
13
|
+
this.modified = false;
|
|
14
|
+
this.errors = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cancel() {
|
|
18
|
+
this.cancelled = true;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setMetadata(key, value) {
|
|
23
|
+
this.metadata[key] = value;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getMetadata(key) {
|
|
28
|
+
return this.metadata[key];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
markModified() {
|
|
32
|
+
this.modified = true;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addError(error) {
|
|
37
|
+
this.errors.push(error);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Middleware {
|
|
43
|
+
constructor(name, options = {}) {
|
|
44
|
+
this.name = name;
|
|
45
|
+
this.enabled = true;
|
|
46
|
+
this.priority = options.priority || 0;
|
|
47
|
+
this.filters = options.filters || {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
shouldProcess(ctx) {
|
|
51
|
+
if (!this.enabled) return false;
|
|
52
|
+
|
|
53
|
+
if (this.filters.directions && !this.filters.directions.includes(ctx.direction)) return false;
|
|
54
|
+
if (this.filters.states && !this.filters.states.includes(ctx.state)) return false;
|
|
55
|
+
if (this.filters.packetNames && !this.filters.packetNames.includes(ctx.packet?.constructor?.packetName)) return false;
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async process(ctx, next) {
|
|
61
|
+
return next();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
enable() {
|
|
65
|
+
this.enabled = true;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
disable() {
|
|
70
|
+
this.enabled = false;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class LoggingMiddleware extends Middleware {
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
super('logging', options);
|
|
78
|
+
this.logger = options.logger || console;
|
|
79
|
+
this.logLevel = options.logLevel || 'debug';
|
|
80
|
+
this.format = options.format || 'simple';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async process(ctx, next) {
|
|
84
|
+
const start = Date.now();
|
|
85
|
+
|
|
86
|
+
if (this.format === 'detailed') {
|
|
87
|
+
this.logger.log(`[${ctx.direction}] ${ctx.packet?.constructor?.packetName} (${ctx.rawData?.length || 0} bytes)`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await next();
|
|
91
|
+
|
|
92
|
+
const duration = Date.now() - start;
|
|
93
|
+
|
|
94
|
+
if (this.format === 'detailed') {
|
|
95
|
+
this.logger.log(` └─ Processed in ${duration}ms, cancelled=${ctx.cancelled}, modified=${ctx.modified}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class RateLimitMiddleware extends Middleware {
|
|
101
|
+
constructor(options = {}) {
|
|
102
|
+
super('rateLimit', options);
|
|
103
|
+
this.maxPackets = options.maxPackets || 100;
|
|
104
|
+
this.windowMs = options.windowMs || 1000;
|
|
105
|
+
this.perPacketType = options.perPacketType || false;
|
|
106
|
+
this.counters = new Map();
|
|
107
|
+
this.onLimit = options.onLimit || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async process(ctx, next) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const key = this.perPacketType ? ctx.packet?.constructor?.packetName : 'global';
|
|
113
|
+
|
|
114
|
+
let counter = this.counters.get(key);
|
|
115
|
+
if (!counter || now - counter.windowStart > this.windowMs) {
|
|
116
|
+
counter = { count: 0, windowStart: now };
|
|
117
|
+
this.counters.set(key, counter);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
counter.count++;
|
|
121
|
+
|
|
122
|
+
if (counter.count > this.maxPackets) {
|
|
123
|
+
ctx.cancel();
|
|
124
|
+
ctx.setMetadata('rateLimited', true);
|
|
125
|
+
ctx.setMetadata('rateLimitKey', key);
|
|
126
|
+
|
|
127
|
+
if (this.onLimit) {
|
|
128
|
+
this.onLimit(ctx, key, counter.count);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await next();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getStats() {
|
|
137
|
+
const stats = {};
|
|
138
|
+
for (const [key, counter] of this.counters) {
|
|
139
|
+
stats[key] = {
|
|
140
|
+
count: counter.count,
|
|
141
|
+
remaining: Math.max(0, this.maxPackets - counter.count),
|
|
142
|
+
windowMs: this.windowMs
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return stats;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
reset() {
|
|
149
|
+
this.counters.clear();
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class ValidationMiddleware extends Middleware {
|
|
155
|
+
constructor(options = {}) {
|
|
156
|
+
super('validation', options);
|
|
157
|
+
this.validators = new Map();
|
|
158
|
+
this.strict = options.strict || false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
addValidator(packetName, validator) {
|
|
162
|
+
this.validators.set(packetName, validator);
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async process(ctx, next) {
|
|
167
|
+
const packetName = ctx.packet?.constructor?.packetName;
|
|
168
|
+
const validator = this.validators.get(packetName);
|
|
169
|
+
|
|
170
|
+
if (validator) {
|
|
171
|
+
try {
|
|
172
|
+
const result = await validator(ctx.packet, ctx);
|
|
173
|
+
|
|
174
|
+
if (result === false) {
|
|
175
|
+
ctx.cancel();
|
|
176
|
+
ctx.addError({ type: 'validation', message: 'Validation failed' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (typeof result === 'object' && result.valid === false) {
|
|
181
|
+
ctx.cancel();
|
|
182
|
+
ctx.addError({ type: 'validation', message: result.message || 'Validation failed' });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {
|
|
186
|
+
ctx.addError({ type: 'validation', message: e.message });
|
|
187
|
+
if (this.strict) {
|
|
188
|
+
ctx.cancel();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await next();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class TransformMiddleware extends Middleware {
|
|
199
|
+
constructor(options = {}) {
|
|
200
|
+
super('transform', options);
|
|
201
|
+
this.transformers = new Map();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
addTransformer(packetName, transformer) {
|
|
205
|
+
this.transformers.set(packetName, transformer);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async process(ctx, next) {
|
|
210
|
+
const packetName = ctx.packet?.constructor?.packetName;
|
|
211
|
+
const transformer = this.transformers.get(packetName);
|
|
212
|
+
|
|
213
|
+
if (transformer) {
|
|
214
|
+
try {
|
|
215
|
+
const result = await transformer(ctx.packet, ctx);
|
|
216
|
+
if (result !== undefined && result !== ctx.packet) {
|
|
217
|
+
ctx.packet = result;
|
|
218
|
+
ctx.markModified();
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
ctx.addError({ type: 'transform', message: e.message });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await next();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class CacheMiddleware extends Middleware {
|
|
230
|
+
constructor(options = {}) {
|
|
231
|
+
super('cache', options);
|
|
232
|
+
this.cache = new Map();
|
|
233
|
+
this.ttl = options.ttl || 60000;
|
|
234
|
+
this.maxSize = options.maxSize || 1000;
|
|
235
|
+
this.keyGenerator = options.keyGenerator || ((ctx) => ctx.packet?.constructor?.packetName);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async process(ctx, next) {
|
|
239
|
+
const key = this.keyGenerator(ctx);
|
|
240
|
+
const cached = this.cache.get(key);
|
|
241
|
+
|
|
242
|
+
if (cached && Date.now() - cached.timestamp < this.ttl) {
|
|
243
|
+
ctx.setMetadata('cached', true);
|
|
244
|
+
ctx.setMetadata('cachedData', cached.data);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await next();
|
|
249
|
+
|
|
250
|
+
if (!ctx.cancelled) {
|
|
251
|
+
if (this.cache.size >= this.maxSize) {
|
|
252
|
+
const oldestKey = this.cache.keys().next().value;
|
|
253
|
+
this.cache.delete(oldestKey);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.cache.set(key, {
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
data: ctx.packet
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
clear() {
|
|
264
|
+
this.cache.clear();
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getStats() {
|
|
269
|
+
return {
|
|
270
|
+
size: this.cache.size,
|
|
271
|
+
maxSize: this.maxSize,
|
|
272
|
+
ttl: this.ttl
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export class MetricsMiddleware extends Middleware {
|
|
278
|
+
constructor(options = {}) {
|
|
279
|
+
super('metrics', options);
|
|
280
|
+
this.metrics = {
|
|
281
|
+
processed: 0,
|
|
282
|
+
cancelled: 0,
|
|
283
|
+
errors: 0,
|
|
284
|
+
totalTime: 0,
|
|
285
|
+
byPacketType: new Map()
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async process(ctx, next) {
|
|
290
|
+
const start = Date.now();
|
|
291
|
+
const packetName = ctx.packet?.constructor?.packetName || 'unknown';
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await next();
|
|
295
|
+
} finally {
|
|
296
|
+
const duration = Date.now() - start;
|
|
297
|
+
|
|
298
|
+
this.metrics.processed++;
|
|
299
|
+
this.metrics.totalTime += duration;
|
|
300
|
+
|
|
301
|
+
if (ctx.cancelled) this.metrics.cancelled++;
|
|
302
|
+
if (ctx.errors.length > 0) this.metrics.errors++;
|
|
303
|
+
|
|
304
|
+
const typeMetrics = this.metrics.byPacketType.get(packetName) || { count: 0, time: 0 };
|
|
305
|
+
typeMetrics.count++;
|
|
306
|
+
typeMetrics.time += duration;
|
|
307
|
+
this.metrics.byPacketType.set(packetName, typeMetrics);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getMetrics() {
|
|
312
|
+
return {
|
|
313
|
+
...this.metrics,
|
|
314
|
+
byPacketType: Object.fromEntries(this.metrics.byPacketType),
|
|
315
|
+
avgProcessingTime: this.metrics.processed > 0 ?
|
|
316
|
+
(this.metrics.totalTime / this.metrics.processed).toFixed(2) : 0
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
reset() {
|
|
321
|
+
this.metrics = {
|
|
322
|
+
processed: 0,
|
|
323
|
+
cancelled: 0,
|
|
324
|
+
errors: 0,
|
|
325
|
+
totalTime: 0,
|
|
326
|
+
byPacketType: new Map()
|
|
327
|
+
};
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export class MiddlewarePipeline extends EventEmitter {
|
|
333
|
+
constructor() {
|
|
334
|
+
super();
|
|
335
|
+
this.middlewares = [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
use(middleware) {
|
|
339
|
+
if (typeof middleware === 'function') {
|
|
340
|
+
const fn = middleware;
|
|
341
|
+
middleware = new Middleware('anonymous');
|
|
342
|
+
middleware.process = async (ctx, next) => {
|
|
343
|
+
await fn(ctx, next);
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.middlewares.push(middleware);
|
|
348
|
+
this.middlewares.sort((a, b) => b.priority - a.priority);
|
|
349
|
+
|
|
350
|
+
this.emit('add', middleware);
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
remove(name) {
|
|
355
|
+
const index = this.middlewares.findIndex(m => m.name === name);
|
|
356
|
+
if (index !== -1) {
|
|
357
|
+
const removed = this.middlewares.splice(index, 1)[0];
|
|
358
|
+
this.emit('remove', removed);
|
|
359
|
+
}
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get(name) {
|
|
364
|
+
return this.middlewares.find(m => m.name === name);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async execute(ctx) {
|
|
368
|
+
const chain = this.middlewares.filter(m => m.shouldProcess(ctx));
|
|
369
|
+
|
|
370
|
+
const executeMiddleware = async (index) => {
|
|
371
|
+
if (index >= chain.length || ctx.cancelled) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const middleware = chain[index];
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
await middleware.process(ctx, () => executeMiddleware(index + 1));
|
|
379
|
+
} catch (e) {
|
|
380
|
+
ctx.addError({ middleware: middleware.name, message: e.message });
|
|
381
|
+
this.emit('error', { middleware: middleware.name, error: e, ctx });
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
this.emit('beforeExecute', ctx);
|
|
386
|
+
await executeMiddleware(0);
|
|
387
|
+
this.emit('afterExecute', ctx);
|
|
388
|
+
|
|
389
|
+
return ctx;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async process(options) {
|
|
393
|
+
const ctx = new MiddlewareContext(options);
|
|
394
|
+
return this.execute(ctx);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
list() {
|
|
398
|
+
return this.middlewares.map(m => ({
|
|
399
|
+
name: m.name,
|
|
400
|
+
enabled: m.enabled,
|
|
401
|
+
priority: m.priority
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export default {
|
|
407
|
+
MiddlewarePipeline,
|
|
408
|
+
MiddlewareContext,
|
|
409
|
+
Middleware,
|
|
410
|
+
LoggingMiddleware,
|
|
411
|
+
RateLimitMiddleware,
|
|
412
|
+
ValidationMiddleware,
|
|
413
|
+
TransformMiddleware,
|
|
414
|
+
CacheMiddleware,
|
|
415
|
+
MetricsMiddleware
|
|
416
|
+
};
|