mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2024-11-14 19:04:59 -05:00
refactor + createServer support
This commit is contained in:
parent
d38dad2d56
commit
3a8bfaf8e3
7 changed files with 283 additions and 126 deletions
17
README.md
17
README.md
|
@ -8,8 +8,8 @@ Parse and serialize minecraft packets, plus authentication and encryption.
|
||||||
objects.
|
objects.
|
||||||
* Send a packet by supplying fields as a JavaScript object.
|
* Send a packet by supplying fields as a JavaScript object.
|
||||||
* Supports authenticating and logging in.
|
* Supports authenticating and logging in.
|
||||||
- Supports encryption
|
- Supports encryption (client only)
|
||||||
- Supports online mode
|
- Supports online mode (client only)
|
||||||
- Supports offline mode
|
- Supports offline mode
|
||||||
* Respond to keep-alive packets.
|
* Respond to keep-alive packets.
|
||||||
* Test coverage
|
* Test coverage
|
||||||
|
@ -51,16 +51,18 @@ client.on(0x03, function(packet) {
|
||||||
var mc = require('minecraft-protocol');
|
var mc = require('minecraft-protocol');
|
||||||
var server = mc.createServer({
|
var server = mc.createServer({
|
||||||
'online-mode': true, // optional
|
'online-mode': true, // optional
|
||||||
'encryption': true, // optional
|
encryption: true, // optional
|
||||||
|
host: '0.0.0.0', // optional
|
||||||
|
port: 25565, // optional
|
||||||
});
|
});
|
||||||
server.on('connection', function(client) {
|
server.on('login', function(client) {
|
||||||
client.write(0x01, {
|
client.write(0x01, {
|
||||||
entityId: 0,
|
entityId: 0,
|
||||||
levelType: 'default',
|
levelType: 'default',
|
||||||
gameMode: 0,
|
gameMode: 0,
|
||||||
dimension: 0,
|
dimension: 0,
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
maxPlayers: 32
|
maxPlayers: server.maxPlayers
|
||||||
});
|
});
|
||||||
client.write(0x0d, {
|
client.write(0x0d, {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -71,10 +73,7 @@ server.on('connection', function(client) {
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
onGround: true
|
onGround: true
|
||||||
});
|
});
|
||||||
client.write(0x03, { message: 'Hello, world!' });
|
client.write(0x03, { message: 'Hello, ' + client.username });
|
||||||
});
|
|
||||||
server.listen();
|
|
||||||
console.log('Server listening on port', server.port);
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,18 @@ var yellow = '§e';
|
||||||
var players = [];
|
var players = [];
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
requireAuth: false,
|
'online-mode': false,
|
||||||
motd: 'Vox Industries',
|
motd: 'Vox Industries',
|
||||||
maxPlayers: 128,
|
'max-players': 127,
|
||||||
|
port: 25565,
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = mc.createServer(options);
|
var server = mc.createServer(options);
|
||||||
|
|
||||||
server.on('connection', function(client) {
|
server.on('login', function(client) {
|
||||||
var player = {
|
var player = {
|
||||||
client: client,
|
client: client,
|
||||||
username: handshake.username,
|
username: client.username,
|
||||||
index: players.length
|
index: players.length
|
||||||
};
|
};
|
||||||
players.push(player);
|
players.push(player);
|
||||||
|
@ -39,7 +40,7 @@ server.on('connection', function(client) {
|
||||||
gameMode: 1,
|
gameMode: 1,
|
||||||
dimension: 0,
|
dimension: 0,
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
maxPlayers: 128
|
maxPlayers: server.maxPlayers
|
||||||
});
|
});
|
||||||
client.write(0x0d, {
|
client.write(0x0d, {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -48,7 +49,7 @@ server.on('connection', function(client) {
|
||||||
z: 0,
|
z: 0,
|
||||||
yaw: 0,
|
yaw: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
onGround: 1
|
onGround: true
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on(0x03, function(data) {
|
client.on(0x03, function(data) {
|
||||||
|
@ -56,15 +57,14 @@ server.on('connection', function(client) {
|
||||||
broadcast(message);
|
broadcast(message);
|
||||||
console.log(message);
|
console.log(message);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
server.on('error', function(error) {
|
server.on('error', function(error) {
|
||||||
console.log('Error:', error);
|
console.log('Error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(function() {
|
server.on('listening', function() {
|
||||||
console.log('Server listening on port', server.port);
|
console.log('Server listening on port', server.socket.address().port);
|
||||||
});
|
});
|
||||||
|
|
||||||
function broadcast(message, exclude) {
|
function broadcast(message, exclude) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
var mc = require('../');
|
var mc = require('../');
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
'online-mode': false,
|
'online-mode': false, // optional
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = mc.createServer(options);
|
var server = mc.createServer(options);
|
||||||
|
|
||||||
server.on('connection', function(client) {
|
server.on('login', function(client) {
|
||||||
var addr = client.socket.remoteAddress;
|
var addr = client.socket.remoteAddress;
|
||||||
console.log('Incoming connection', '('+addr+')');
|
console.log('Incoming connection', '('+addr+')');
|
||||||
|
|
||||||
|
@ -15,6 +15,14 @@ server.on('connection', function(client) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// send init data so client will start rendering world
|
// send init data so client will start rendering world
|
||||||
|
client.write(0x01, {
|
||||||
|
entityId: 0,
|
||||||
|
levelType: 'default',
|
||||||
|
gameMode: 0,
|
||||||
|
dimension: 0,
|
||||||
|
difficulty: 2,
|
||||||
|
maxPlayers: server.maxPlayers
|
||||||
|
});
|
||||||
client.write(0x0d, {
|
client.write(0x0d, {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 1.62,
|
y: 1.62,
|
||||||
|
@ -32,6 +40,6 @@ server.on('error', function(error) {
|
||||||
console.log('Error:', error);
|
console.log('Error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(function() {
|
server.on('listening', function() {
|
||||||
console.log('Server listening on port', server.port);
|
console.log('Server listening on port', server.socket.address().port);
|
||||||
});
|
});
|
||||||
|
|
143
index.js
143
index.js
|
@ -1,5 +1,4 @@
|
||||||
var net = require('net')
|
var EventEmitter = require('events').EventEmitter
|
||||||
, EventEmitter = require('events').EventEmitter
|
|
||||||
, util = require('util')
|
, util = require('util')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, ursa = require('ursa')
|
, ursa = require('ursa')
|
||||||
|
@ -7,10 +6,92 @@ var net = require('net')
|
||||||
, superagent = require('superagent')
|
, superagent = require('superagent')
|
||||||
, Batch = require('batch')
|
, Batch = require('batch')
|
||||||
, protocol = require('./lib/protocol')
|
, protocol = require('./lib/protocol')
|
||||||
, createPacketBuffer = protocol.createPacketBuffer
|
, Client = require('./lib/client')
|
||||||
, parsePacket = protocol.parsePacket
|
, Server = require('./lib/server')
|
||||||
|
|
||||||
exports.createClient = createClient;
|
exports.createClient = createClient;
|
||||||
|
exports.createServer = createServer;
|
||||||
|
|
||||||
|
function createServer(options) {
|
||||||
|
var port = options.port != null ?
|
||||||
|
options.port :
|
||||||
|
options['server-port'] != null ?
|
||||||
|
options['server-port'] :
|
||||||
|
25565 ;
|
||||||
|
var host = options.host || '0.0.0.0';
|
||||||
|
var timeout = options.timeout || 10 * 1000;
|
||||||
|
var keepAliveInterval = options.keepAliveInterval || 4 * 1000;
|
||||||
|
var motd = options.motd || "A Minecraft server";
|
||||||
|
var onlineMode = options['online-mode'] == null ? true : options['online-mode'];
|
||||||
|
assert.ok(! onlineMode, "online mode for servers is not yet supported");
|
||||||
|
|
||||||
|
var server = new Server(options);
|
||||||
|
server.on("connection", function(client) {
|
||||||
|
client.once(0xfe, onPing);
|
||||||
|
client.on(0x02, onHandshake);
|
||||||
|
client.on(0x00, onKeepAlive);
|
||||||
|
client.on('end', onEnd);
|
||||||
|
|
||||||
|
var keepAlive = false;
|
||||||
|
var loggedIn = false;
|
||||||
|
var lastKeepAlive = null;
|
||||||
|
|
||||||
|
var keepAliveTimer = setInterval(keepAliveLoop, keepAliveInterval);
|
||||||
|
|
||||||
|
function keepAliveLoop() {
|
||||||
|
if (keepAlive) {
|
||||||
|
// check if the last keepAlive was too long ago (timeout)
|
||||||
|
if (lastKeepAlive) {
|
||||||
|
var elapsed = new Date() - lastKeepAlive;
|
||||||
|
if (elapsed > timeout) {
|
||||||
|
client.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.write(0x00, {
|
||||||
|
keepAliveId: Math.floor(Math.random() * 2147483648)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnd() {
|
||||||
|
clearInterval(keepAliveTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeepAlive(packet) {
|
||||||
|
if (keepAlive) {
|
||||||
|
lastKeepAlive = new Date();
|
||||||
|
} else {
|
||||||
|
lastKeepAlive = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPing(packet) {
|
||||||
|
if (loggedIn) return;
|
||||||
|
client.write(0xff, {
|
||||||
|
reason: [
|
||||||
|
'§1',
|
||||||
|
protocol.version,
|
||||||
|
protocol.minecraftVersion,
|
||||||
|
motd,
|
||||||
|
server.playerCount,
|
||||||
|
server.maxPlayers,
|
||||||
|
].join('\u0000')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHandshake(packet) {
|
||||||
|
assert.ok(! onlineMode);
|
||||||
|
loggedIn = true;
|
||||||
|
keepAlive = true;
|
||||||
|
client.username = packet.username;
|
||||||
|
server.emit('login', client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(port, host);
|
||||||
|
return server;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function createClient(options) {
|
function createClient(options) {
|
||||||
// defaults
|
// defaults
|
||||||
|
@ -20,7 +101,9 @@ function createClient(options) {
|
||||||
assert.ok(options.username, "username is required");
|
assert.ok(options.username, "username is required");
|
||||||
var haveCredentials = options.email && options.password;
|
var haveCredentials = options.email && options.password;
|
||||||
|
|
||||||
var client = new Client();
|
var client = new Client({
|
||||||
|
isServer: false
|
||||||
|
});
|
||||||
client.username = options.username;
|
client.username = options.username;
|
||||||
client.on('connect', function() {
|
client.on('connect', function() {
|
||||||
client.write(0x02, {
|
client.write(0x02, {
|
||||||
|
@ -30,6 +113,9 @@ function createClient(options) {
|
||||||
serverPort: port,
|
serverPort: port,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
client.on('packet', function(packet) {
|
||||||
|
console.log(packet.id, packet);
|
||||||
|
});
|
||||||
client.on(0x00, onKeepAlive);
|
client.on(0x00, onKeepAlive);
|
||||||
client.once(0xFC, onEncryptionKeyResponse);
|
client.once(0xFC, onEncryptionKeyResponse);
|
||||||
client.once(0xFD, onEncryptionKeyRequest);
|
client.once(0xFD, onEncryptionKeyRequest);
|
||||||
|
@ -133,53 +219,6 @@ function createClient(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Client(options) {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.socket = null;
|
|
||||||
this.encryptionEnabled = false;
|
|
||||||
this.cipher = null;
|
|
||||||
this.decipher = null;
|
|
||||||
}
|
|
||||||
util.inherits(Client, EventEmitter);
|
|
||||||
|
|
||||||
Client.prototype.connect = function(port, host) {
|
|
||||||
var self = this;
|
|
||||||
self.socket = net.connect(port, host, function() {
|
|
||||||
self.emit('connect');
|
|
||||||
});
|
|
||||||
var incomingBuffer = new Buffer(0);
|
|
||||||
self.socket.on('data', function(data) {
|
|
||||||
if (self.encryptionEnabled) data = new Buffer(self.decipher.update(data), 'binary');
|
|
||||||
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
|
||||||
var parsed, packet;
|
|
||||||
while (true) {
|
|
||||||
parsed = parsePacket(incomingBuffer);
|
|
||||||
if (! parsed) break;
|
|
||||||
packet = parsed.results;
|
|
||||||
incomingBuffer = incomingBuffer.slice(parsed.size);
|
|
||||||
self.emit(packet.id, packet);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.socket.on('error', function(err) {
|
|
||||||
self.emit('error', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.socket.on('close', function() {
|
|
||||||
self.emit('end');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.end = function() {
|
|
||||||
this.socket.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
Client.prototype.write = function(packetId, params) {
|
|
||||||
var buffer = createPacketBuffer(packetId, params);
|
|
||||||
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
|
|
||||||
this.socket.write(out);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function mcPubKeyToURsa(mcPubKeyBuffer) {
|
function mcPubKeyToURsa(mcPubKeyBuffer) {
|
||||||
|
|
62
lib/client.js
Normal file
62
lib/client.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
var net = require('net')
|
||||||
|
, EventEmitter = require('events').EventEmitter
|
||||||
|
, util = require('util')
|
||||||
|
, protocol = require('./protocol')
|
||||||
|
, createPacketBuffer = protocol.createPacketBuffer
|
||||||
|
, parsePacket = protocol.parsePacket
|
||||||
|
|
||||||
|
module.exports = Client;
|
||||||
|
|
||||||
|
function Client(options) {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
this.isServer = options.isServer;
|
||||||
|
this.socket = null;
|
||||||
|
this.encryptionEnabled = false;
|
||||||
|
this.cipher = null;
|
||||||
|
this.decipher = null;
|
||||||
|
}
|
||||||
|
util.inherits(Client, EventEmitter);
|
||||||
|
|
||||||
|
Client.prototype.setSocket = function(socket) {
|
||||||
|
var self = this;
|
||||||
|
self.socket = socket;
|
||||||
|
var incomingBuffer = new Buffer(0);
|
||||||
|
self.socket.on('data', function(data) {
|
||||||
|
if (self.encryptionEnabled) data = new Buffer(self.decipher.update(data), 'binary');
|
||||||
|
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
||||||
|
var parsed, packet;
|
||||||
|
while (true) {
|
||||||
|
parsed = parsePacket(incomingBuffer, self.isServer);
|
||||||
|
if (! parsed) break;
|
||||||
|
packet = parsed.results;
|
||||||
|
incomingBuffer = incomingBuffer.slice(parsed.size);
|
||||||
|
self.emit(packet.id, packet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.socket.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.socket.on('close', function() {
|
||||||
|
self.emit('end');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.prototype.connect = function(port, host) {
|
||||||
|
var self = this;
|
||||||
|
self.setSocket(net.connect(port, host, function() {
|
||||||
|
self.emit('connect');
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.prototype.end = function() {
|
||||||
|
this.socket.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.prototype.write = function(packetId, params) {
|
||||||
|
var buffer = createPacketBuffer(packetId, params, this.isServer);
|
||||||
|
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
|
||||||
|
this.socket.write(out);
|
||||||
|
};
|
|
@ -6,6 +6,7 @@ var Iconv = require('iconv').Iconv
|
||||||
var STRING_MAX_LENGTH = 240;
|
var STRING_MAX_LENGTH = 240;
|
||||||
|
|
||||||
exports.version = 51;
|
exports.version = 51;
|
||||||
|
exports.minecraftVersion = '1.4.6';
|
||||||
exports.sessionVersion = 13;
|
exports.sessionVersion = 13;
|
||||||
exports.parsePacket = parsePacket;
|
exports.parsePacket = parsePacket;
|
||||||
exports.createPacketBuffer = createPacketBuffer;
|
exports.createPacketBuffer = createPacketBuffer;
|
||||||
|
@ -446,15 +447,6 @@ var packets = {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
function get(packetId, toServer) {
|
|
||||||
var packetInfo = packets[packetId];
|
|
||||||
return Array.isArray(packetInfo) ?
|
|
||||||
packetInfo :
|
|
||||||
toServer ?
|
|
||||||
packetInfo.toServer :
|
|
||||||
packetInfo.toClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
var writers = {
|
var writers = {
|
||||||
'int': IntWriter,
|
'int': IntWriter,
|
||||||
'short': ShortWriter,
|
'short': ShortWriter,
|
||||||
|
@ -908,7 +900,7 @@ ByteArray16Writer.prototype.write = function(buffer, offset) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function ByteWriter(value) {
|
function ByteWriter(value) {
|
||||||
this.value = value;
|
this.value = value == null ? 0 : value;
|
||||||
this.size = 1;
|
this.size = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,10 +962,19 @@ IntWriter.prototype.write = function(buffer, offset) {
|
||||||
buffer.writeInt32BE(this.value, offset);
|
buffer.writeInt32BE(this.value, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPacketBuffer(packetId, params) {
|
function get(packetId, toServer) {
|
||||||
|
var packetInfo = packets[packetId];
|
||||||
|
return Array.isArray(packetInfo) ?
|
||||||
|
packetInfo :
|
||||||
|
toServer ?
|
||||||
|
packetInfo.toServer :
|
||||||
|
packetInfo.toClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPacketBuffer(packetId, params, isServer) {
|
||||||
var size = 1;
|
var size = 1;
|
||||||
var fields = [ new UByteWriter(packetId) ];
|
var fields = [ new UByteWriter(packetId) ];
|
||||||
var packet = get(packetId);
|
var packet = get(packetId, isServer);
|
||||||
packet.forEach(function(fieldInfo) {
|
packet.forEach(function(fieldInfo) {
|
||||||
var value = params[fieldInfo.name];
|
var value = params[fieldInfo.name];
|
||||||
var Writer = writers[fieldInfo.type];
|
var Writer = writers[fieldInfo.type];
|
||||||
|
@ -991,12 +992,12 @@ function createPacketBuffer(packetId, params) {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePacket(buffer) {
|
function parsePacket(buffer, isServer) {
|
||||||
if (buffer.length < 1) return null;
|
if (buffer.length < 1) return null;
|
||||||
var packetId = buffer.readUInt8(0);
|
var packetId = buffer.readUInt8(0);
|
||||||
var size = 1;
|
var size = 1;
|
||||||
var results = { id: packetId };
|
var results = { id: packetId };
|
||||||
var packetInfo = get(packetId);
|
var packetInfo = get(packetId, !isServer);
|
||||||
assert.ok(packetInfo, "Unrecognized packetId: " + packetId);
|
assert.ok(packetInfo, "Unrecognized packetId: " + packetId);
|
||||||
var i, fieldInfo, read, readResults;
|
var i, fieldInfo, read, readResults;
|
||||||
for (i = 0; i < packetInfo.length; ++i) {
|
for (i = 0; i < packetInfo.length; ++i) {
|
||||||
|
|
48
lib/server.js
Normal file
48
lib/server.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
var net = require('net')
|
||||||
|
, EventEmitter = require('events').EventEmitter
|
||||||
|
, util = require('util')
|
||||||
|
, assert = require('assert')
|
||||||
|
, Client = require('./client')
|
||||||
|
|
||||||
|
module.exports = Server;
|
||||||
|
|
||||||
|
function Server(options) {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
this.maxPlayers = options['max-players'] || 20;
|
||||||
|
this.playerCount = 0
|
||||||
|
|
||||||
|
this.socket = null;
|
||||||
|
this.cipher = null;
|
||||||
|
this.decipher = null;
|
||||||
|
}
|
||||||
|
util.inherits(Server, EventEmitter);
|
||||||
|
|
||||||
|
Server.prototype.listen = function(port, host) {
|
||||||
|
var self = this;
|
||||||
|
self.socket = net.createServer();
|
||||||
|
self.socket.on('connection', function(socket) {
|
||||||
|
var client = new Client({
|
||||||
|
isServer: true,
|
||||||
|
});
|
||||||
|
client.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
client.setSocket(socket);
|
||||||
|
self.emit('connection', client);
|
||||||
|
client.on('end', function() {
|
||||||
|
this.playerCount -= 1;
|
||||||
|
});
|
||||||
|
this.playerCount += 1;
|
||||||
|
});
|
||||||
|
self.socket.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
self.socket.on('close', function() {
|
||||||
|
self.emit('end');
|
||||||
|
});
|
||||||
|
self.socket.on('listening', function() {
|
||||||
|
self.emit('listening');
|
||||||
|
});
|
||||||
|
self.socket.listen(port, host);
|
||||||
|
};
|
Loading…
Reference in a new issue