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.
@@ -0,0 +1,336 @@
1
+ import { EventEmitter } from '../events/EventEmitter.js';
2
+ import { PacketBuffer } from '../utils/PacketBuffer.js';
3
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
4
+ import { gzipSync, gunzipSync } from 'zlib';
5
+
6
+ const RECORDING_VERSION = 1;
7
+
8
+ export class PacketRecord {
9
+ constructor(options = {}) {
10
+ this.timestamp = options.timestamp || Date.now();
11
+ this.direction = options.direction || 'CLIENTBOUND';
12
+ this.state = options.state || 'PLAY';
13
+ this.packetId = options.packetId || 0;
14
+ this.packetName = options.packetName || 'Unknown';
15
+ this.data = options.data || Buffer.alloc(0);
16
+ this.size = options.size || this.data.length;
17
+ this.metadata = options.metadata || {};
18
+ }
19
+
20
+ toJSON() {
21
+ return {
22
+ timestamp: this.timestamp,
23
+ direction: this.direction,
24
+ state: this.state,
25
+ packetId: this.packetId,
26
+ packetName: this.packetName,
27
+ data: this.data.toString('base64'),
28
+ size: this.size,
29
+ metadata: this.metadata
30
+ };
31
+ }
32
+
33
+ static fromJSON(json) {
34
+ return new PacketRecord({
35
+ ...json,
36
+ data: Buffer.from(json.data, 'base64')
37
+ });
38
+ }
39
+ }
40
+
41
+ export class PacketRecorder extends EventEmitter {
42
+ constructor(options = {}) {
43
+ super();
44
+
45
+ this.records = [];
46
+ this.isRecording = false;
47
+ this.isPaused = false;
48
+ this.startTime = null;
49
+ this.maxRecords = options.maxRecords || 100000;
50
+ this.filters = options.filters || {};
51
+ this.compression = options.compression !== false;
52
+
53
+ this.stats = {
54
+ totalPackets: 0,
55
+ totalBytes: 0,
56
+ packetsByType: new Map(),
57
+ packetsByDirection: { CLIENTBOUND: 0, SERVERBOUND: 0 },
58
+ startTime: null,
59
+ endTime: null
60
+ };
61
+ }
62
+
63
+ start() {
64
+ if (this.isRecording) return this;
65
+
66
+ this.isRecording = true;
67
+ this.isPaused = false;
68
+ this.startTime = Date.now();
69
+ this.stats.startTime = this.startTime;
70
+
71
+ this.emit('start', { timestamp: this.startTime });
72
+ return this;
73
+ }
74
+
75
+ stop() {
76
+ if (!this.isRecording) return this;
77
+
78
+ this.isRecording = false;
79
+ this.stats.endTime = Date.now();
80
+
81
+ this.emit('stop', {
82
+ timestamp: this.stats.endTime,
83
+ duration: this.stats.endTime - this.stats.startTime,
84
+ totalPackets: this.stats.totalPackets
85
+ });
86
+ return this;
87
+ }
88
+
89
+ pause() {
90
+ this.isPaused = true;
91
+ this.emit('pause');
92
+ return this;
93
+ }
94
+
95
+ resume() {
96
+ this.isPaused = false;
97
+ this.emit('resume');
98
+ return this;
99
+ }
100
+
101
+ record(packet, direction, state, rawData) {
102
+ if (!this.isRecording || this.isPaused) return this;
103
+ if (this.records.length >= this.maxRecords) return this;
104
+
105
+ if (this.filters.directions && !this.filters.directions.includes(direction)) return this;
106
+ if (this.filters.states && !this.filters.states.includes(state)) return this;
107
+ if (this.filters.packetIds && !this.filters.packetIds.includes(packet.constructor.packetId)) return this;
108
+ if (this.filters.packetNames && !this.filters.packetNames.includes(packet.constructor.packetName)) return this;
109
+
110
+ const record = new PacketRecord({
111
+ timestamp: Date.now(),
112
+ direction,
113
+ state: typeof state === 'object' ? state.name : state,
114
+ packetId: packet.constructor.packetId,
115
+ packetName: packet.constructor.packetName,
116
+ data: rawData,
117
+ size: rawData.length,
118
+ metadata: {
119
+ relativeTime: Date.now() - this.startTime
120
+ }
121
+ });
122
+
123
+ this.records.push(record);
124
+
125
+ this.stats.totalPackets++;
126
+ this.stats.totalBytes += rawData.length;
127
+ this.stats.packetsByDirection[direction]++;
128
+
129
+ const typeName = packet.constructor.packetName;
130
+ this.stats.packetsByType.set(typeName, (this.stats.packetsByType.get(typeName) || 0) + 1);
131
+
132
+ this.emit('record', record);
133
+ return this;
134
+ }
135
+
136
+ clear() {
137
+ this.records = [];
138
+ this.stats = {
139
+ totalPackets: 0,
140
+ totalBytes: 0,
141
+ packetsByType: new Map(),
142
+ packetsByDirection: { CLIENTBOUND: 0, SERVERBOUND: 0 },
143
+ startTime: null,
144
+ endTime: null
145
+ };
146
+ this.emit('clear');
147
+ return this;
148
+ }
149
+
150
+ exportToFile(filePath) {
151
+ const data = {
152
+ version: RECORDING_VERSION,
153
+ exportTime: Date.now(),
154
+ stats: {
155
+ ...this.stats,
156
+ packetsByType: Object.fromEntries(this.stats.packetsByType)
157
+ },
158
+ records: this.records.map(r => r.toJSON())
159
+ };
160
+
161
+ const json = JSON.stringify(data);
162
+ const buffer = this.compression ? gzipSync(json) : Buffer.from(json);
163
+
164
+ writeFileSync(filePath, buffer);
165
+ this.emit('export', { filePath, size: buffer.length });
166
+ return this;
167
+ }
168
+
169
+ importFromFile(filePath) {
170
+ if (!existsSync(filePath)) {
171
+ throw new Error(`File not found: ${filePath}`);
172
+ }
173
+
174
+ let buffer = readFileSync(filePath);
175
+
176
+ try {
177
+ buffer = gunzipSync(buffer);
178
+ } catch (e) {
179
+ }
180
+
181
+ const data = JSON.parse(buffer.toString());
182
+
183
+ if (data.version !== RECORDING_VERSION) {
184
+ console.warn(`Recording version mismatch: ${data.version} vs ${RECORDING_VERSION}`);
185
+ }
186
+
187
+ this.records = data.records.map(r => PacketRecord.fromJSON(r));
188
+ this.stats = {
189
+ ...data.stats,
190
+ packetsByType: new Map(Object.entries(data.stats.packetsByType || {}))
191
+ };
192
+
193
+ this.emit('import', { filePath, records: this.records.length });
194
+ return this;
195
+ }
196
+
197
+ getStats() {
198
+ return {
199
+ ...this.stats,
200
+ packetsByType: Object.fromEntries(this.stats.packetsByType),
201
+ duration: this.stats.endTime ? this.stats.endTime - this.stats.startTime : Date.now() - this.stats.startTime,
202
+ recordCount: this.records.length,
203
+ avgPacketSize: this.stats.totalPackets > 0 ? Math.round(this.stats.totalBytes / this.stats.totalPackets) : 0,
204
+ packetsPerSecond: this.stats.totalPackets > 0 ?
205
+ (this.stats.totalPackets / ((this.stats.endTime || Date.now()) - this.stats.startTime) * 1000).toFixed(2) : 0
206
+ };
207
+ }
208
+
209
+ filter(predicate) {
210
+ return this.records.filter(predicate);
211
+ }
212
+
213
+ findByName(name) {
214
+ return this.records.filter(r => r.packetName === name);
215
+ }
216
+
217
+ findByDirection(direction) {
218
+ return this.records.filter(r => r.direction === direction);
219
+ }
220
+
221
+ getInTimeRange(startMs, endMs) {
222
+ const baseTime = this.stats.startTime || 0;
223
+ return this.records.filter(r => {
224
+ const relTime = r.timestamp - baseTime;
225
+ return relTime >= startMs && relTime <= endMs;
226
+ });
227
+ }
228
+ }
229
+
230
+ export class PacketReplayer extends EventEmitter {
231
+ constructor(options = {}) {
232
+ super();
233
+
234
+ this.records = options.records || [];
235
+ this.speed = options.speed || 1.0;
236
+ this.isPlaying = false;
237
+ this.isPaused = false;
238
+ this.currentIndex = 0;
239
+ this.startTime = null;
240
+ this.timeouts = [];
241
+ }
242
+
243
+ loadFromRecorder(recorder) {
244
+ this.records = [...recorder.records];
245
+ this.currentIndex = 0;
246
+ return this;
247
+ }
248
+
249
+ async play(callback) {
250
+ if (this.isPlaying) return;
251
+ if (this.records.length === 0) return;
252
+
253
+ this.isPlaying = true;
254
+ this.isPaused = false;
255
+ this.startTime = Date.now();
256
+ this.currentIndex = 0;
257
+
258
+ this.emit('play', { totalRecords: this.records.length });
259
+
260
+ const baseTime = this.records[0].timestamp;
261
+
262
+ for (let i = 0; i < this.records.length; i++) {
263
+ if (!this.isPlaying) break;
264
+
265
+ while (this.isPaused) {
266
+ await new Promise(resolve => setTimeout(resolve, 100));
267
+ if (!this.isPlaying) break;
268
+ }
269
+
270
+ const record = this.records[i];
271
+ const nextRecord = this.records[i + 1];
272
+
273
+ this.currentIndex = i;
274
+ this.emit('packet', record);
275
+
276
+ if (callback) {
277
+ await callback(record);
278
+ }
279
+
280
+ if (nextRecord) {
281
+ const delay = (nextRecord.timestamp - record.timestamp) / this.speed;
282
+ if (delay > 0) {
283
+ await new Promise(resolve => setTimeout(resolve, delay));
284
+ }
285
+ }
286
+ }
287
+
288
+ this.isPlaying = false;
289
+ this.emit('end', {
290
+ played: this.currentIndex + 1,
291
+ duration: Date.now() - this.startTime
292
+ });
293
+ }
294
+
295
+ stop() {
296
+ this.isPlaying = false;
297
+ this.isPaused = false;
298
+ this.emit('stop');
299
+ return this;
300
+ }
301
+
302
+ pause() {
303
+ this.isPaused = true;
304
+ this.emit('pause');
305
+ return this;
306
+ }
307
+
308
+ resume() {
309
+ this.isPaused = false;
310
+ this.emit('resume');
311
+ return this;
312
+ }
313
+
314
+ setSpeed(speed) {
315
+ this.speed = Math.max(0.1, Math.min(10, speed));
316
+ return this;
317
+ }
318
+
319
+ seek(index) {
320
+ this.currentIndex = Math.max(0, Math.min(index, this.records.length - 1));
321
+ return this;
322
+ }
323
+
324
+ getProgress() {
325
+ return {
326
+ current: this.currentIndex,
327
+ total: this.records.length,
328
+ percentage: this.records.length > 0 ? (this.currentIndex / this.records.length * 100).toFixed(1) : 0,
329
+ isPlaying: this.isPlaying,
330
+ isPaused: this.isPaused,
331
+ speed: this.speed
332
+ };
333
+ }
334
+ }
335
+
336
+ export default { PacketRecorder, PacketReplayer, PacketRecord };