packet-events-js 1.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.
Files changed (40) hide show
  1. package/README.md +398 -0
  2. package/package.json +31 -0
  3. package/src/auth/AuthHandler.js +138 -0
  4. package/src/auth/MojangAPI.js +186 -0
  5. package/src/client/MinecraftClient.js +336 -0
  6. package/src/crypto/Encryption.js +125 -0
  7. package/src/events/EventEmitter.js +267 -0
  8. package/src/events/PacketEvent.js +78 -0
  9. package/src/index.js +18 -0
  10. package/src/manager/PacketManager.js +258 -0
  11. package/src/protocol/ConnectionState.js +37 -0
  12. package/src/protocol/PacketDirection.js +8 -0
  13. package/src/protocol/ProtocolVersion.js +141 -0
  14. package/src/protocol/packets/Packet.js +119 -0
  15. package/src/protocol/packets/PacketRegistry.js +145 -0
  16. package/src/protocol/packets/handshake/HandshakePacket.js +44 -0
  17. package/src/protocol/packets/index.js +265 -0
  18. package/src/protocol/packets/login/DisconnectPacket.js +71 -0
  19. package/src/protocol/packets/login/EncryptionRequestPacket.js +47 -0
  20. package/src/protocol/packets/login/EncryptionResponsePacket.js +34 -0
  21. package/src/protocol/packets/login/LoginStartPacket.js +35 -0
  22. package/src/protocol/packets/login/LoginSuccessPacket.js +61 -0
  23. package/src/protocol/packets/login/SetCompressionPacket.js +29 -0
  24. package/src/protocol/packets/play/ChatPacket.js +238 -0
  25. package/src/protocol/packets/play/ChunkPacket.js +122 -0
  26. package/src/protocol/packets/play/EntityPacket.js +302 -0
  27. package/src/protocol/packets/play/KeepAlivePacket.js +55 -0
  28. package/src/protocol/packets/play/PlayerPositionPacket.js +266 -0
  29. package/src/protocol/packets/status/PingPacket.js +29 -0
  30. package/src/protocol/packets/status/PongPacket.js +29 -0
  31. package/src/protocol/packets/status/StatusRequestPacket.js +20 -0
  32. package/src/protocol/packets/status/StatusResponsePacket.js +58 -0
  33. package/src/protocol/types/NBT.js +594 -0
  34. package/src/protocol/types/Position.js +125 -0
  35. package/src/protocol/types/TextComponent.js +355 -0
  36. package/src/protocol/types/UUID.js +105 -0
  37. package/src/protocol/types/VarInt.js +144 -0
  38. package/src/protocol/types/index.js +5 -0
  39. package/src/utils/Logger.js +207 -0
  40. package/src/utils/PacketBuffer.js +389 -0
@@ -0,0 +1,267 @@
1
+ export const EventPriority = Object.freeze({
2
+ LOWEST: 0,
3
+ LOW: 1,
4
+ NORMAL: 2,
5
+ HIGH: 3,
6
+ HIGHEST: 4,
7
+ MONITOR: 5
8
+ });
9
+
10
+ export class CancellableEvent {
11
+ constructor() {
12
+ this._cancelled = false;
13
+ }
14
+
15
+ get cancelled() {
16
+ return this._cancelled;
17
+ }
18
+
19
+ cancel() {
20
+ this._cancelled = true;
21
+ }
22
+
23
+ setCancelled(cancelled) {
24
+ this._cancelled = cancelled;
25
+ }
26
+ }
27
+
28
+ export class PacketEvent extends CancellableEvent {
29
+
30
+ constructor(packet, user) {
31
+ super();
32
+ this.packet = packet;
33
+ this.user = user;
34
+ this._timestamp = Date.now();
35
+ }
36
+
37
+ get timestamp() {
38
+ return this._timestamp;
39
+ }
40
+
41
+ withPacket(newPacket) {
42
+ const event = new PacketEvent(newPacket, this.user);
43
+ event._cancelled = this._cancelled;
44
+ return event;
45
+ }
46
+ }
47
+
48
+ class EventListener {
49
+ constructor(callback, priority, once = false) {
50
+ this.callback = callback;
51
+ this.priority = priority;
52
+ this.once = once;
53
+ this.removed = false;
54
+ }
55
+ }
56
+
57
+ export class EventEmitter {
58
+ constructor() {
59
+
60
+ this._listeners = new Map();
61
+
62
+ this._wildcardListeners = new Map();
63
+
64
+ this._maxListeners = 100;
65
+ }
66
+
67
+ setMaxListeners(n) {
68
+ this._maxListeners = n;
69
+ return this;
70
+ }
71
+
72
+ on(event, callback, options = {}) {
73
+ const { priority = EventPriority.NORMAL, once = false } = options;
74
+
75
+ if (!this._listeners.has(event)) {
76
+ this._listeners.set(event, []);
77
+ }
78
+
79
+ const listeners = this._listeners.get(event);
80
+
81
+ if (listeners.length >= this._maxListeners) {
82
+ console.warn(`MaxListenersExceededWarning: Possible EventEmitter memory leak detected. ` +
83
+ `${listeners.length} ${event} listeners added.`);
84
+ }
85
+
86
+ const listener = new EventListener(callback, priority, once);
87
+ listeners.push(listener);
88
+
89
+ listeners.sort((a, b) => b.priority - a.priority);
90
+
91
+ return this;
92
+ }
93
+
94
+ once(event, callback, options = {}) {
95
+ return this.on(event, callback, { ...options, once: true });
96
+ }
97
+
98
+ off(event, callback) {
99
+ if (!this._listeners.has(event)) return this;
100
+
101
+ const listeners = this._listeners.get(event);
102
+ const index = listeners.findIndex(l => l.callback === callback);
103
+
104
+ if (index !== -1) {
105
+ listeners[index].removed = true;
106
+ listeners.splice(index, 1);
107
+ }
108
+
109
+ return this;
110
+ }
111
+
112
+ removeListener(event, callback) {
113
+ return this.off(event, callback);
114
+ }
115
+
116
+ removeAllListeners(event) {
117
+ if (event) {
118
+ this._listeners.delete(event);
119
+ } else {
120
+ this._listeners.clear();
121
+ }
122
+ return this;
123
+ }
124
+
125
+ emit(event, ...args) {
126
+ if (!this._listeners.has(event)) return false;
127
+
128
+ const listeners = this._listeners.get(event);
129
+ const toRemove = [];
130
+
131
+ for (const listener of listeners) {
132
+ if (listener.removed) continue;
133
+
134
+ try {
135
+ listener.callback(...args);
136
+ } catch (error) {
137
+ this._handleError(error, event);
138
+ }
139
+
140
+ if (listener.once) {
141
+ toRemove.push(listener);
142
+ }
143
+
144
+ if (args[0] instanceof CancellableEvent &&
145
+ args[0].cancelled &&
146
+ listener.priority !== EventPriority.MONITOR) {
147
+ break;
148
+ }
149
+ }
150
+
151
+ for (const listener of toRemove) {
152
+ const index = listeners.indexOf(listener);
153
+ if (index !== -1) {
154
+ listeners.splice(index, 1);
155
+ }
156
+ }
157
+
158
+ return listeners.length > 0;
159
+ }
160
+
161
+ async emitAsync(event, ...args) {
162
+ if (!this._listeners.has(event)) return false;
163
+
164
+ const listeners = [...this._listeners.get(event)];
165
+ const toRemove = [];
166
+
167
+ for (const listener of listeners) {
168
+ if (listener.removed) continue;
169
+
170
+ try {
171
+ await listener.callback(...args);
172
+ } catch (error) {
173
+ this._handleError(error, event);
174
+ }
175
+
176
+ if (listener.once) {
177
+ toRemove.push(listener);
178
+ }
179
+
180
+ if (args[0] instanceof CancellableEvent &&
181
+ args[0].cancelled &&
182
+ listener.priority !== EventPriority.MONITOR) {
183
+ break;
184
+ }
185
+ }
186
+
187
+ const currentListeners = this._listeners.get(event);
188
+ if (currentListeners) {
189
+ for (const listener of toRemove) {
190
+ const index = currentListeners.indexOf(listener);
191
+ if (index !== -1) {
192
+ currentListeners.splice(index, 1);
193
+ }
194
+ }
195
+ }
196
+
197
+ return listeners.length > 0;
198
+ }
199
+
200
+ listenerCount(event) {
201
+ return this._listeners.get(event)?.length || 0;
202
+ }
203
+
204
+ eventNames() {
205
+ return Array.from(this._listeners.keys());
206
+ }
207
+
208
+ listeners(event) {
209
+ return (this._listeners.get(event) || []).map(l => l.callback);
210
+ }
211
+
212
+ waitFor(event, timeout) {
213
+ return new Promise((resolve, reject) => {
214
+ const handler = (...args) => {
215
+ if (timeoutId) clearTimeout(timeoutId);
216
+ resolve(args.length === 1 ? args[0] : args);
217
+ };
218
+
219
+ let timeoutId;
220
+ if (timeout) {
221
+ timeoutId = setTimeout(() => {
222
+ this.off(event, handler);
223
+ reject(new Error(`Timeout waiting for event: ${event}`));
224
+ }, timeout);
225
+ }
226
+
227
+ this.once(event, handler);
228
+ });
229
+ }
230
+
231
+ pipe(target, events) {
232
+ const handlers = new Map();
233
+
234
+ for (const event of events) {
235
+ const handler = (...args) => target.emit(event, ...args);
236
+ handlers.set(event, handler);
237
+ this.on(event, handler);
238
+ }
239
+
240
+ return () => {
241
+ for (const [event, handler] of handlers) {
242
+ this.off(event, handler);
243
+ }
244
+ };
245
+ }
246
+
247
+ _handleError(error, event) {
248
+ if (this._listeners.has('error')) {
249
+ this.emit('error', error, event);
250
+ } else {
251
+ console.error(`Error in event listener for "${event}":`, error);
252
+ }
253
+ }
254
+ }
255
+
256
+ export class TypedEventEmitter extends EventEmitter {
257
+
258
+ on(event, callback, options) {
259
+ return super.on(event, callback, options);
260
+ }
261
+
262
+ emit(event, ...args) {
263
+ return super.emit(event, ...args);
264
+ }
265
+ }
266
+
267
+ export default EventEmitter;
@@ -0,0 +1,78 @@
1
+ import { CancellableEvent } from './EventEmitter.js';
2
+
3
+ export class BasePacketEvent extends CancellableEvent {
4
+
5
+ constructor(connection, packet) {
6
+ super();
7
+ this.connection = connection;
8
+ this.packet = packet;
9
+ this._timestamp = Date.now();
10
+ this._lastUsedPacket = packet;
11
+ }
12
+
13
+ get timestamp() {
14
+ return this._timestamp;
15
+ }
16
+
17
+ get user() {
18
+ return this.connection?.user || null;
19
+ }
20
+
21
+ get packetId() {
22
+ return this.packet?.packetId ?? -1;
23
+ }
24
+
25
+ get packetName() {
26
+ return this.packet?.packetName ?? 'Unknown';
27
+ }
28
+
29
+ markDirty() {
30
+ this._lastUsedPacket = this.packet;
31
+ }
32
+
33
+ get lastUsedPacket() {
34
+ return this._lastUsedPacket;
35
+ }
36
+ }
37
+
38
+ export class PacketReceiveEvent extends BasePacketEvent {
39
+ constructor(connection, packet) {
40
+ super(connection, packet);
41
+ this.direction = 'SERVERBOUND';
42
+ }
43
+
44
+ getPacket(packetClass) {
45
+ if (this.packet instanceof packetClass) {
46
+ return this.packet;
47
+ }
48
+ return null;
49
+ }
50
+ }
51
+
52
+ export class PacketSendEvent extends BasePacketEvent {
53
+ constructor(connection, packet) {
54
+ super(connection, packet);
55
+ this.direction = 'CLIENTBOUND';
56
+ }
57
+
58
+ getPacket(packetClass) {
59
+ if (this.packet instanceof packetClass) {
60
+ return this.packet;
61
+ }
62
+ return null;
63
+ }
64
+ }
65
+
66
+ export const PacketEventType = Object.freeze({
67
+ RECEIVE: 'packet_receive',
68
+ SEND: 'packet_send',
69
+ PRE_PROCESS: 'packet_pre_process',
70
+ POST_PROCESS: 'packet_post_process'
71
+ });
72
+
73
+ export default {
74
+ BasePacketEvent,
75
+ PacketReceiveEvent,
76
+ PacketSendEvent,
77
+ PacketEventType
78
+ };
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { MinecraftClient } from './client/MinecraftClient.js';
2
+ export { PacketManager } from './manager/PacketManager.js';
3
+ export { EventEmitter, EventPriority } from './events/EventEmitter.js';
4
+ export { PacketReceiveEvent, PacketSendEvent } from './events/PacketEvent.js';
5
+ export { ProtocolVersion } from './protocol/ProtocolVersion.js';
6
+ export { ConnectionState } from './protocol/ConnectionState.js';
7
+ export { PacketDirection } from './protocol/PacketDirection.js';
8
+
9
+ export * from './protocol/types/index.js';
10
+
11
+ export * from './protocol/packets/index.js';
12
+
13
+ export { PacketBuffer } from './utils/PacketBuffer.js';
14
+ export { Logger, LogLevel } from './utils/Logger.js';
15
+
16
+ export { MinecraftEncryption, EncryptedConnection } from './crypto/Encryption.js';
17
+ export { MojangAPI } from './auth/MojangAPI.js';
18
+ export { AuthHandler, ClientAuthHandler, AuthMode } from './auth/AuthHandler.js';
@@ -0,0 +1,258 @@
1
+ import { createInflate, createDeflate } from 'zlib';
2
+ import { promisify } from 'util';
3
+ import { PacketBuffer } from '../utils/PacketBuffer.js';
4
+ import { PacketRegistry, defaultRegistry } from '../protocol/packets/PacketRegistry.js';
5
+ import { ConnectionState } from '../protocol/ConnectionState.js';
6
+ import { PacketDirection } from '../protocol/PacketDirection.js';
7
+ import { EventEmitter, EventPriority } from '../events/EventEmitter.js';
8
+ import { PacketReceiveEvent, PacketSendEvent } from '../events/PacketEvent.js';
9
+ import { Logger } from '../utils/Logger.js';
10
+ import { encodeVarInt, decodeVarInt, getVarIntSize } from '../protocol/types/VarInt.js';
11
+
12
+ const logger = Logger.getLogger('PacketManager');
13
+
14
+ export class PacketManager extends EventEmitter {
15
+
16
+ constructor(options = {}) {
17
+ super();
18
+
19
+ this.registry = options.registry || defaultRegistry;
20
+ this.state = ConnectionState.HANDSHAKING;
21
+ this.compressionThreshold = -1;
22
+ this.encryptionEnabled = false;
23
+
24
+ this._cipher = null;
25
+ this._decipher = null;
26
+
27
+ this._receiveBuffer = Buffer.alloc(0);
28
+ }
29
+
30
+ setState(state) {
31
+ const oldState = this.state;
32
+ this.state = state;
33
+ logger.debug(`State changed: ${oldState.name} -> ${state.name}`);
34
+ this.emit('stateChange', { oldState, newState: state });
35
+ }
36
+
37
+ setCompression(threshold) {
38
+ this.compressionThreshold = threshold;
39
+ logger.debug(`Compression enabled with threshold: ${threshold}`);
40
+ }
41
+
42
+ setEncryption(cipher, decipher) {
43
+ this._cipher = cipher;
44
+ this._decipher = decipher;
45
+ this.encryptionEnabled = true;
46
+ logger.debug('Encryption enabled');
47
+ }
48
+
49
+ encrypt(data) {
50
+ if (!this.encryptionEnabled || !this._cipher) {
51
+ return data;
52
+ }
53
+ return this._cipher.update(data);
54
+ }
55
+
56
+ decrypt(data) {
57
+ if (!this.encryptionEnabled || !this._decipher) {
58
+ return data;
59
+ }
60
+ return this._decipher.update(data);
61
+ }
62
+
63
+ async compress(data) {
64
+ return new Promise((resolve, reject) => {
65
+ const chunks = [];
66
+ const deflate = createDeflate();
67
+
68
+ deflate.on('data', chunk => chunks.push(chunk));
69
+ deflate.on('end', () => resolve(Buffer.concat(chunks)));
70
+ deflate.on('error', reject);
71
+
72
+ deflate.end(data);
73
+ });
74
+ }
75
+
76
+ async decompress(data) {
77
+ return new Promise((resolve, reject) => {
78
+ const chunks = [];
79
+ const inflate = createInflate();
80
+
81
+ inflate.on('data', chunk => chunks.push(chunk));
82
+ inflate.on('end', () => resolve(Buffer.concat(chunks)));
83
+ inflate.on('error', reject);
84
+
85
+ inflate.end(data);
86
+ });
87
+ }
88
+
89
+ async encodePacket(packet, context = {}) {
90
+
91
+ const packetData = packet.encode(context);
92
+
93
+ if (this.compressionThreshold >= 0) {
94
+ if (packetData.length >= this.compressionThreshold) {
95
+
96
+ const compressed = await this.compress(packetData);
97
+
98
+ const dataLengthVarInt = encodeVarInt(packetData.length);
99
+ const totalLength = dataLengthVarInt.length + compressed.length;
100
+ const lengthVarInt = encodeVarInt(totalLength);
101
+
102
+ return Buffer.concat([lengthVarInt, dataLengthVarInt, compressed]);
103
+ } else {
104
+
105
+ const dataLengthVarInt = encodeVarInt(0);
106
+ const totalLength = dataLengthVarInt.length + packetData.length;
107
+ const lengthVarInt = encodeVarInt(totalLength);
108
+
109
+ return Buffer.concat([lengthVarInt, dataLengthVarInt, packetData]);
110
+ }
111
+ } else {
112
+
113
+ const lengthVarInt = encodeVarInt(packetData.length);
114
+ return Buffer.concat([lengthVarInt, packetData]);
115
+ }
116
+ }
117
+
118
+ addReceivedData(data) {
119
+
120
+ const decrypted = this.decrypt(data);
121
+
122
+ this._receiveBuffer = Buffer.concat([this._receiveBuffer, decrypted]);
123
+ }
124
+
125
+ async readPackets(direction = PacketDirection.SERVERBOUND, context = {}) {
126
+ const packets = [];
127
+
128
+ while (this._receiveBuffer.length > 0) {
129
+ try {
130
+
131
+ const { value: packetLength, bytesRead: lengthBytes } =
132
+ decodeVarInt(this._receiveBuffer, 0);
133
+
134
+ if (this._receiveBuffer.length < lengthBytes + packetLength) {
135
+ break;
136
+ }
137
+
138
+ let packetData = this._receiveBuffer.subarray(
139
+ lengthBytes,
140
+ lengthBytes + packetLength
141
+ );
142
+
143
+ if (this.compressionThreshold >= 0) {
144
+ const { value: dataLength, bytesRead: dataLengthBytes } =
145
+ decodeVarInt(packetData, 0);
146
+
147
+ if (dataLength > 0) {
148
+
149
+ packetData = await this.decompress(
150
+ packetData.subarray(dataLengthBytes)
151
+ );
152
+ } else {
153
+
154
+ packetData = packetData.subarray(dataLengthBytes);
155
+ }
156
+ }
157
+
158
+ const { value: packetId, bytesRead: idBytes } =
159
+ decodeVarInt(packetData, 0);
160
+
161
+ const payloadData = packetData.subarray(idBytes);
162
+
163
+ const packet = this.registry.createPacket(
164
+ this.state,
165
+ direction,
166
+ packetId,
167
+ payloadData,
168
+ context
169
+ );
170
+
171
+ if (packet) {
172
+ packets.push(packet);
173
+
174
+ const event = new PacketReceiveEvent(context.connection, packet);
175
+ await this.emitAsync('packet', event);
176
+ await this.emitAsync(`packet:${packet.packetName}`, event);
177
+ } else {
178
+
179
+ logger.trace(
180
+ `Unknown packet: 0x${packetId.toString(16)} in state ${this.state.name}`
181
+ );
182
+
183
+ this.emit('unknownPacket', {
184
+ state: this.state,
185
+ direction,
186
+ packetId,
187
+ data: payloadData
188
+ });
189
+ }
190
+
191
+ this._receiveBuffer = this._receiveBuffer.subarray(lengthBytes + packetLength);
192
+
193
+ } catch (error) {
194
+ if (error.message.includes('underflow')) {
195
+ break;
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ return packets;
202
+ }
203
+
204
+ async sendPacket(packet, writeFn, context = {}) {
205
+
206
+ const event = new PacketSendEvent(context.connection, packet);
207
+ await this.emitAsync('packetSend', event);
208
+ await this.emitAsync(`packetSend:${packet.packetName}`, event);
209
+
210
+ if (event.cancelled) {
211
+ logger.trace(`Packet cancelled: ${packet.packetName}`);
212
+ return false;
213
+ }
214
+
215
+ let data = await this.encodePacket(event.packet, context);
216
+
217
+ data = this.encrypt(data);
218
+
219
+ await writeFn(data);
220
+
221
+ logger.trace(`Sent packet: ${packet.packetName}`);
222
+ return true;
223
+ }
224
+
225
+ onPacket(packetClass, callback, options = {}) {
226
+ const packetName = packetClass.packetName || packetClass.name;
227
+
228
+ this.on(`packet:${packetName}`, (event) => {
229
+ if (event.packet instanceof packetClass) {
230
+ callback(event);
231
+ }
232
+ }, options);
233
+
234
+ return this;
235
+ }
236
+
237
+ onPacketSend(packetClass, callback, options = {}) {
238
+ const packetName = packetClass.packetName || packetClass.name;
239
+
240
+ this.on(`packetSend:${packetName}`, (event) => {
241
+ if (event.packet instanceof packetClass) {
242
+ callback(event);
243
+ }
244
+ }, options);
245
+
246
+ return this;
247
+ }
248
+
249
+ clearBuffer() {
250
+ this._receiveBuffer = Buffer.alloc(0);
251
+ }
252
+
253
+ get bufferSize() {
254
+ return this._receiveBuffer.length;
255
+ }
256
+ }
257
+
258
+ export default PacketManager;
@@ -0,0 +1,37 @@
1
+ export const ConnectionState = Object.freeze({
2
+
3
+ HANDSHAKING: {
4
+ id: -1,
5
+ name: 'HANDSHAKING'
6
+ },
7
+
8
+ STATUS: {
9
+ id: 1,
10
+ name: 'STATUS'
11
+ },
12
+
13
+ LOGIN: {
14
+ id: 2,
15
+ name: 'LOGIN'
16
+ },
17
+
18
+ CONFIGURATION: {
19
+ id: 3,
20
+ name: 'CONFIGURATION'
21
+ },
22
+
23
+ PLAY: {
24
+ id: 4,
25
+ name: 'PLAY'
26
+ }
27
+ });
28
+
29
+ export function getStateById(id) {
30
+ return Object.values(ConnectionState).find(state => state.id === id);
31
+ }
32
+
33
+ export function getStateByName(name) {
34
+ return ConnectionState[name.toUpperCase()];
35
+ }
36
+
37
+ export default ConnectionState;
@@ -0,0 +1,8 @@
1
+ export const PacketDirection = Object.freeze({
2
+
3
+ SERVERBOUND: 'SERVERBOUND',
4
+
5
+ CLIENTBOUND: 'CLIENTBOUND'
6
+ });
7
+
8
+ export default PacketDirection;