UNPKG

@perion/net

Version:

A few networking utilities for MapleStory server development

134 lines 4.21 kB
const net = require('net'); const Encoder = require('./encoder.js'); const crypto = require('@perion/crypto'); const events = require('events'); let ids = 0; class Server extends events.EventEmitter { constructor({host, port}, listen=false, useDefaultCrypto=true) { super(); this.host = host; this.port = port; this.extSockets = []; this.minSocketId = Number.MAX_SAFE_INTEGER; this.useDefaultCrypto = useDefaultCrypto; this.instance = new net.Server(); this.instance.on('connection', (socket) => this.onConnection(socket)); this.instance.on('error', (err) => this.onError(err)); if (listen) this.listen({host, port}); } listen() { return new Promise((resolve, reject) => { try { this.instance.listen({port: this.port, host: this.host}, () => { // console.log(`Listening on ${host}:${port}`); this.emit('ready'); resolve(true); }); } catch (err) { reject(err); this.emit('error', err); } }); } onConnection(socket) { // console.log('Server not implemented'); if (ids == Number.MAX_SAFE_INTEGER) ids = 0; socket.id = ids++; if (this.minSocketId > socket.id) this.minSocketId = socket.id; socket.setKeepAlive(true); if (this.useDefaultCrypto) { // Set encoder const send = new crypto.AES( Buffer.from([0x01, Math.round(Math.random() * 255), 0x03, 0x04]), 83 ); const recv = new crypto.AES( Buffer.from([0x01, Math.round(Math.random() * 255), 0x03, 0x04]), 83 ); socket.encoder = new Encoder({aes: send, shanda: crypto.Shanda}); socket.decoder = new Encoder({aes: recv, shanda: crypto.Shanda}); socket.encWrite = (data) => { socket.write(socket.encoder.encode(data)); }; } socket.on('data', (data) => this.onData({socket, data})); socket.on('error', (err) => this.onError(err, socket.id)); socket.on('close', () => this.removeSocket(socket.id)); this.extSockets.push(socket); this.emit('connection', socket); } removeSocket(socketId) { this.extSockets = this.extSockets.filter(socket => socket.id !== socketId); } getSocket(socketId) { const range = ids - this.minSocketId; const diff = socketId - this.minSocketId; const length = this.extSockets.length; const startValue = Math.round((diff/range) * (length)); for (let i = 0; i < length / 2; i++) { if (this.extSockets[startValue+i] && this.extSockets[startValue+i].id === socketId) { return this.extSockets[startValue+i]; } if (this.extSockets[startValue-i] && this.extSockets[startValue-i].id === socketId) { return this.extSockets[startValue-i]; } } return null; } onError(err, socketId) { if (socketId === undefined) { // console.log('Error: ', err); this.emit('error', err); return; } // console.log('Error: ', err); this.emit('socket_error', err); if (socketId) this.removeSocket(socketId); } onData({socket, data}) { let packet; if (this.useDefaultCrypto) { packet = socket.decoder.decode(data); } else { packet = {data: data.slice(2), opcode: data.slice(0, 2).readInt16LE(0)}; } // TODO: Should we be sending the packet this.emit('packet', {socket, packet}); } broadcast({data, ids}) { return new Promise((resolve) => { if (!ids || ids.length === 0) { for (const socket of this.extSockets) { if (this.useDefaultCrypto) socket.encWrite(data); else socket.write(data); } resolve(true); return; } for (const socketId of ids) { const socket = this.getSocket(socketId); if (!socket) continue; if (this.useDefaultCrypto) socket.encWrite(data); else socket.write(data); } resolve(true); return; }); } destroy() { return new Promise((resolve, reject) => { this.instance.close((err) => { if (err) { reject(err); return; } this.emit('destroy'); resolve(true); }); }); } } module.exports = Server;