diff --git a/README.md b/README.md index 5e4b037..cbd13d2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,104 @@ Netcode ======= -A simple client & server binary-encoded websocket communication system for web video games. +> A simple JavaScript client & server binary-encoded websocket communication system aimed towards web video games development. + +Features: +- 🔌 Server / Client duo, for node and the browser, that handle Websocket connection and communication. +- ⚡️ Handle the binary encoding and decoding of your data, with performances in mind. +- 📢 Listen for event dispatched over websocket with simple `on`/`off` event emitter system. +- 💬 Fallback to JSON for easy debugging. +- 🗜[COMMING SOON] set a tick-rate and group events for lesser data consumption. + +## Requirements + +- Node >= v8.0.0 ## Installation `npm add netcode` -## Usage +## Get started + +### Define a list of events + +The server and the client __must__ share the same events. +An event is defined by its _unique_ name and the corresponding codec, responsible for encoding and decoding the data. + +```javascript +// events.js +import Int8Codec from 'netcode/src/encoder/codec/Int8Codec'; +import StringCodec from 'netcode/src/encoder/codec/StringCodec'; + +export default [ + ['id', new Int8Codec()], + ['say', new StringCodec()], +]; +``` + +In this example, the event list define how to send the following events over websocket: + +- `client.send('id', 255);` +- `client.send('say', 'Hello world!');` + +Then you'll be able to listen to this events on the client as follow: + +- `client.on('id', id => { /* Do something */ });` +- `client.on('say', sentence => { /* Do something */ });` + +Now let's create a server and an client that use this event list. + +### Setup a Server + +We setup a server specifying the port and host on which the server will listen and the type of encoder to use. +Here we use a BinaryEncoder to communicate in binary over websocket, with the previously configured event list. + +```javascript +import Server from 'netcode/src/server/Server'; +import BinaryEncoder from 'netcode/src/server/BinaryEncoder'; +import events from './envents'; + +// Listen on localhost:8080 +const server = new Server(8080, 'localhost', new BinaryEncoder(events)); + +server.on('client:join', client => { + client.on('say', sentence => console.log(sentence)); + client.send('id', client.id); +}); +``` + +Now we've got a server running at `localhost:8080` that listen for a `say` text event and send a `id` integer event to every client that connects. + +_See an [full example of server setup](demo-server.js)._ + +### Write a Client + +Now we write a client, for the browser, that connects to our running server on `ws://localhost:8080` and use a BinaryEncoder with the same event list as the server. + +```javascript +import Client from 'netcode/src/client/Client'; +import BinaryEncoder from 'netcode/src/client/BinaryEncoder'; +import events from './envents'; + +const client = new Client('ws://localhost:8080', new BinaryEncoder(events)) + +client.on('open', () => { + client.on('id', id => console.log(`My id is ${id}.`)); + client.send('say', 'Hello world!'); +}); +``` + +Now we've got client that listen for the `id` event and sent a sentence in a `say` event. + +Connection is alive and well! -### Server side (node) +_See an [full example of client setup](demo-client.js)._ -See an [example of server setup](demo-server.js). +## Complete documentation -### Client side (browser) +To go further, see in-depth documentation and how-to's. -See an [example of client setup](demo-client.js). +- [Full API reference](doc/API.md). +- [Default codecs and how-to write your own](doc/codecs.md). +- [About packaging and setting up your own webpack configuration](doc/packaging.md). +- [Use netcode over a custom domain name and/or secured SSL connection](doc/ssl.md). diff --git a/demo-client.js b/demo-client.js index 56bdef7..0afcf21 100644 --- a/demo-client.js +++ b/demo-client.js @@ -4,7 +4,6 @@ window.addEventListener('load', () => { // Register your events const encoder = new BinaryEncoder([ - ['open', new Codec()], ['id', new Int16Codec()], ['ping', new LongIntCodec(6)], ['pong', new LongIntCodec(6)], @@ -17,12 +16,12 @@ window.addEventListener('load', () => { let ping; // Listen for a "pong" event - client.addEventListener('pong', pong => { + client.on('pong', pong => { console.info('pong: %s ms', pong - ping); }); // Listen for an "id" event - client.addEventListener('id', id => { + client.on('id', id => { console.log('connected with id %s', id); ping = Date.now(); @@ -31,7 +30,7 @@ window.addEventListener('load', () => { }); // Listen for an "inverse" event - client.addEventListener('inverse', status => { + client.on('inverse', status => { // Answer with an "inverse" event client.send('inverse', !status); console.log('Inverse received: %s', status); @@ -41,17 +40,19 @@ window.addEventListener('load', () => { }); // Listen for a "greeting" event - client.addEventListener('greeting', message => { + client.on('greeting', message => { console.log('Servers geets you: "%s"', message); }); // Listen for oppening connection - client.addEventListener('open', () => { + client.on('open', () => { console.info('Connection open.'); + + setTimeout(() => client.close(), 20 * 1000); }); // Listen for connection close - client.addEventListener('close', () => { + client.on('close', () => { console.info('Connection closed.'); }); }); diff --git a/demo-server.js b/demo-server.js index 057f4cb..d1acdf1 100644 --- a/demo-server.js +++ b/demo-server.js @@ -2,7 +2,6 @@ const { Server, BinaryEncoder, Codec, Int16Codec, LongIntCodec, BooleanCodec, St // Register your events const encoder = new BinaryEncoder([ - ['open', new Codec()], ['id', new Int16Codec()], ['ping', new LongIntCodec(6)], ['pong', new LongIntCodec(6)], @@ -14,11 +13,11 @@ const encoder = new BinaryEncoder([ const server = new Server(process.argv[2], 'localhost', encoder); // Listen for new clients -server.addListener('client:join', client => { +server.on('client:join', client => { console.log('Client %s joined.', client.id); // Listen for "ping" event - client.addListener('ping', ping => { + client.on('ping', ping => { // Answer with a "pong" event client.send('pong', Date.now()); console.log('Client %s ping received: %s.', client.id, ping); @@ -28,12 +27,12 @@ server.addListener('client:join', client => { }); // Listen for "inverse" event - client.addListener('inverse', status => { + client.on('inverse', status => { console.log('Client %s inverse received: %s.', client.id, status); }); // Listen for "greeting" event - client.addListener('greeting', message => { + client.on('greeting', message => { console.log('Client %s geets you: "%s"', client.id, message); // Send a "greeting" event client.send('greeting', 'Hello, I\'m server!'); @@ -44,7 +43,7 @@ server.addListener('client:join', client => { }); // Listen for disconnecting clients -server.addListener('client:leave', client => { +server.on('client:leave', client => { console.log('Client %s left.', client.id); }); diff --git a/doc/API.md b/doc/API.md new file mode 100644 index 0000000..ec01b30 --- /dev/null +++ b/doc/API.md @@ -0,0 +1,79 @@ +## Server + +### Constructor + +The `Server` class takes the following arguments: + +| Parameter | Type | Default value | Description | +| --------- | ------------------------------ | ----------------- | ---------------------------------------------------- | +| port | _Number_ | 8080 | Port to listen on. | +| host | _String_ | 0.0.0.0 | Host to listen on. | +| encoder | _BinaryEncoder \| JsonEncoder_ | new JsonEncoder() | Encoder to use to read/write event messages. | +| ping | _Number_ | 30 | Ping frequency in seconds (0 for no ping). | +| maxLength | _Number_ | 512 | Paquet max length in bit (should be a power of two). | +| protocols | _Array String[]_ | `['websocket']` | Protocols tu use | + +### Methods + +#### on(name, callback) + +Listen for event. + +- `name {String}` The name of the event to listen to. +- `callback {Function}` The callback to execute when the event occure. + + +#### off(name, callback) + +Remove listener for this event / callback. + +_Note: If you're on node `< 10.0.0`, use `removeListener` method instead of `off`_ + +### Events + +| Name | Callback parameters | Description | +|------|---------------------|---| +| `ready` | | Server is listening and ready to accept connections. | +| `client:join` | client _Client_ | New connected client. | +| `client:leave` | client _Client_ | Client left. | +| `error` | error _Error_ | An error occured. | + +## Client + +### Methods + +#### send(name, data) + +Send data to the other end of the Websocket. + +- `name {String}` The name of the event (must be one from the list passed to the BinaryEncoder). +- `data {Number|String|Boolean|Object}` Any data handled by the corresponding Codec. + +#### close() + +Close the connexion. + +#### on(name, callback) + +Listen for event. + +- `name {String}` The name of the event to listen to. +- `callback {Function}` The callback to execute when the event occure. + + +#### off(name, callback) + +Remove listener for this event / callback. + +_Note: If you're on node `< 10.0.0`, use `removeListener` method instead of `off`_ + +### Events + +| Name | Callback parameters | Description | +|---|---|---| +| `open` | client _Client_ | Client connection is open and ready to transmit. | +| `error` | - error _Error_
- client _Client_ | An error occurred. | +| `close` | client _{Client_} | Client connection is closed. | +| * | - eventData _{Number\|String\|Boolean\|Object}_
- client _Client_ | Every event sent through the websocket pipe will emit an event on the other end of the socket. | + +_Note: `open`, `error` and `close` are reserved event names and the Encoder will throw an exeption if you define a custom event with either of these names._ \ No newline at end of file diff --git a/doc/codecs.md b/doc/codecs.md new file mode 100644 index 0000000..985e88a --- /dev/null +++ b/doc/codecs.md @@ -0,0 +1,54 @@ +# Codecs + +Codecs are responsible for encoding your events data into ArrayBuffer that will be transmitted over the binary websocket connection and decoded back into usable data on the other side. + +You will probably need to write your own codecs but a few standard needs are alreay adressed by the simple codecs that follow: + +## Packaged codecs + +Packaged codecs are available in the `netcode/server` and `netcode/client` packages or as source code in `netcode/src/encoder/codec` folder (see [Note on packaging](#Note on packaging)) + +| Class | Data format | Example | Size (in byte) | +| -------------------------- | ---------------------------------- | ------------------------------------------------------------ | ----------------------- | +| `Codec` | No data (just send the event name) | `['pause', new Codec()]`
`send('pause')` | 0 | +| `BooleanCodec` | `true\|false` | `['active', new BooleanCodec()]`
`send('active', true)` | 1 | +| `StringCodec` | String up to 255 characters | `['player:name', new StringCodec()]`
`send('player:name', 'DarkShadow73')` | 1 + (String length * 2) | +| `LongStringCodec` | String up to 65536 characters | `['url', new LongStringCodec()]`
`send('url', 'https://my.long.url/hash/xxx...')` | 2 + (String length * 2) | +| `Int8Codec` | Integer from 0 to 255 | `['id', new Int8Codec()]`
`send('id', 42)` | 1 | +| `Int16Codec` | Integer from 0 to 65536 | `['score', new Int16Codec()]`
`send('score', 9999)` | 2 | +| `Int32Codec` | Integer from 0 to 4294967295 | `['position', new Int32Codec()]`
`send('position', 4294967295)` | 4 | +| `LongIntCodec(byteLength)` | Integer encoded as string | `['timestamp', new LongIntCodec(13)`]
`send('timestamp', Date.now())` | byteLength | + +## Custom codecs + +Some of your events will certainly have more complex data structure that just one of the above single scalar example. Good news is you can write your own codecs! + +Your custom codec must extends the `Codec` class of Netcode and implement the 3 following methods: + +- `getByteLength(data)`: return the number of byte in the event for the given data (your event byte length may vary over the data, like for abitrary strings for example). +- `encoder(buffer, offset, data)` encode the given data into the provided buffer, starting at the given offset (in byte). +- `decode(buffer, offset)`: read and return the data contained in the provided buffer, starting at the given offset. + +Let's say we want to send an event with the given format: + +`send('position', { id: player.id, x: 12345, y: 2354 });` + +We'll need to rite a codec that can transmit these values in an ArrayBuffer. + +For this example, I'm gonna chose to encode: + +- The player ID as an unsigned integer on 1 byte (UInt8) +- The x and y position on 2 byte each (Uint16) + +So the byte length of my event is fixed: 1 + 2 + 2 = 5 bytes. Let implement the `getByteLengthMethod` + +``` +getByteLength() { + return Uint8Array.BYTES_PER_ELEMENT + Uint16Array.BYTES_PER_ELEMENT * 2; +} +``` + +## Debugging + +The BinaryEncoder should emit errors if anything wrong happen durring encoding or decoding, but even so, working with binary data can be tricky. +You can replace your `BinaryEncoder` with a `JsonEncoder` at any time without any other change to you code to switch to a JSON communication and to help you find out what's wrong with transmitted data. diff --git a/doc/packaging.md b/doc/packaging.md new file mode 100644 index 0000000..804239e --- /dev/null +++ b/doc/packaging.md @@ -0,0 +1,89 @@ +# Note on packaging + +Netcode provides you with 2 pre-packaged versions of the library for _Node_ and for the _browser_, so you can use it out of the box. +You can also _import the ES6 source code_ directly and manage the packaging yourself with a tool like Webpack. + +Let's see an example of both these setups: + +## Using the pre-packaged libraries + +On the server-side (node): + +```javascript +const { + Server, + BinaryEncoder, + Int16Codec, + BooleanCodec, + StringCodec, + // ... +} = require('netcode/server');` +``` + +And in the browser as well: + +```html + + + + + +``` + +## Using the ES6 source code + +Configure Webpack to include the source code of _netcode_ so that Babel will compile this ES6 code as well. + +```javascript +module.exports = { + //... + module: { + rules: [{ + // ... + include: '/node_modules/netcode/src/', + }] + } +}; +``` + +_See a [full Webpack/Babel config example](doc/webpack.config.js)._ + +Now you can import source file from _netcode_ directly in your sources and it will be compiled as well: + +```javascript +// src/server.js +import Server from 'netcode/src/server/Server'; +import BinaryEncoder from 'netcode/src/server/BinaryEncoder'; // Import from src/server! +import BooleanCodec from 'netcode/src/encoder/codec/BooleanCodec'; + +new Server( + 8080, + 'localhost', + new BinaryEncoder([ + ['foo', new BooleanEncoder()] + ]) +); +``` + +```javascript +// src/client.js +import Client from 'netcode/src/client/Client'; +import BinaryEncoder from 'netcode/src/client/BinaryEncoder'; // Import from src/client! +import BooleanCodec from 'netcode/src/encoder/codec/BooleanCodec'; + +new Client( + 'ws://localhost:8080', + new BinaryEncoder([ + ['foo', new BooleanCodec()] + ]) +); +``` diff --git a/doc/ssl.md b/doc/ssl.md new file mode 100644 index 0000000..a90e3d6 --- /dev/null +++ b/doc/ssl.md @@ -0,0 +1,39 @@ +# SSL and custom hostname + +In production, you'll want your server to be run behind a real domain name and possibly on a secured SSL connection. + +So instead of connecting the `ws://localhost:8000`, you'll want something like `wss://my-game.io/live/`. + +Here's how to configure an Nginx proxy server to do just that: + +- Server setup : `new Server(8042, 'localhost', new BinaryEncoder(events));` +- Client setup : `new Client('wss://my-game.io/live/', new BinaryEncoder(events))` +- Nginx configuration : + +```nginx +server { + listen 443 ssl http2; + server_name my-game.io; + root /home/tom32i/my-game/client; + # ... the rest of the server and SSL configuration + + location /live/ { + # Proxy for the running node server + proxy_pass http://localhost:8042/;# The actual node server adress + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + location / { + # Serve the static HTML/JS/CSS assets + index index.html; + } +} +``` diff --git a/doc/webpack.config.js b/doc/webpack.config.js new file mode 100644 index 0000000..5ee6187 --- /dev/null +++ b/doc/webpack.config.js @@ -0,0 +1,37 @@ +const webpack = require('webpack'); + +const rules = [ + { + test: /\.js$/, + exclude: /node_modules/, + include: '/node_modules/netcode/src/', + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } + } + } +]; + +const clientConfig = { + target: 'web', + entry: './src/client.js', + output: { + filename: 'client.js', + path: `${__dirname}/dist/`, + }, + module: { rules } +}; + +const serverConfig = { + target: 'node', + entry: './src/server.js', + output: { + filename: 'server.js', + path: `${__dirname}/dist/server/`, + }, + module: { rules } +}; + +module.exports = [ serverConfig, clientConfig ]; diff --git a/index.html b/index.html index a81deca..444e6a0 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - + Netcode Demo diff --git a/package.json b/package.json index 7c3c25f..78516c2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "engines" : { + "node" : ">=8.0.0" + }, "keywords": [ "netcode", "websocket", diff --git a/src/client/BinaryEncoder.js b/src/client/BinaryEncoder.js new file mode 100644 index 0000000..e468585 --- /dev/null +++ b/src/client/BinaryEncoder.js @@ -0,0 +1,3 @@ +import BinaryEncoder from 'netcode/src/encoder/BinaryEncoder'; + +export default BinaryEncoder; diff --git a/src/client/Client.js b/src/client/Client.js index ff1f10c..8d50a25 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -39,14 +39,13 @@ export default class Client extends EventEmitter { */ close() { this.socket.close(); - //this.onClose(); } /** * On connexion open */ onOpen() { - this.emit('open'); + this.emit('open', this); } /** @@ -68,7 +67,7 @@ export default class Client extends EventEmitter { this.socket.removeEventListener('close', this.onClose); this.socket.removeEventListener('error', this.onError); this.socket.removeEventListener('message', this.onMessage); - this.emit('close'); + this.emit('close', this); } /** @@ -77,6 +76,6 @@ export default class Client extends EventEmitter { * @param {Error} error */ onError(error) { - this.emit('error', error); + this.emit('error', error, this); } } diff --git a/src/client/index.js b/src/client/index.js index d0c0bb4..2c9e9c3 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -8,6 +8,7 @@ import Int32Codec from 'netcode/src/encoder/codec/Int32Codec'; import LongIntCodec from 'netcode/src/encoder/codec/LongIntCodec'; import BooleanCodec from 'netcode/src/encoder/codec/BooleanCodec'; import StringCodec from 'netcode/src/encoder/codec/StringCodec'; +import LongStringCodec from 'netcode/src/encoder/codec/LongStringCodec'; module.exports = { Client, @@ -20,4 +21,5 @@ module.exports = { LongIntCodec, BooleanCodec, StringCodec, + LongStringCodec, }; diff --git a/src/encoder/BinaryEncoder.js b/src/encoder/BinaryEncoder.js index 9b88b18..9de9599 100644 --- a/src/encoder/BinaryEncoder.js +++ b/src/encoder/BinaryEncoder.js @@ -8,6 +8,13 @@ export default class BinaryEncoder { */ static get binaryType() { return 'arraybuffer'; } + /** + * Reserved event names + * + * @return {String[]} + */ + static get reservedEvents() { return ['open', 'close', 'error']; } + /** * @param {Array} handlers */ @@ -20,6 +27,10 @@ export default class BinaryEncoder { handlers.forEach(([name, handler], index) => { handler.id = index; handler.name = name; + + if (this.constructor.reservedEvents.includes(name)) { + throw new Error(`"${name}" is a reserved event name.`); + } }); } diff --git a/src/encoder/JsonEncoder.js b/src/encoder/JsonEncoder.js index 3a41fc2..cdbcf21 100644 --- a/src/encoder/JsonEncoder.js +++ b/src/encoder/JsonEncoder.js @@ -6,6 +6,13 @@ export default class JsonEncoder { */ static get binaryType() { return 'blob'; } + /** + * Reserved event names + * + * @return {String[]} + */ + static get reservedEvents() { return ['open', 'close', 'error']; } + /** * Encode * @@ -15,6 +22,10 @@ export default class JsonEncoder { * @return {String} */ encode(name, data) { + if (this.constructor.reservedEvents.includes(name)) { + throw new Error(`"${name}" is a reserved event name.`); + } + return JSON.stringify({ name, data }); } diff --git a/src/encoder/codec/LongIntCodec.js b/src/encoder/codec/LongIntCodec.js index 36617aa..ea21c42 100644 --- a/src/encoder/codec/LongIntCodec.js +++ b/src/encoder/codec/LongIntCodec.js @@ -6,7 +6,7 @@ import Codec from './Codec'; */ export default class LongIntCodec extends Codec { /** - * @param {Number} byteLength} + * @param {Number} byteLength */ constructor(byteLength) { super(); diff --git a/src/encoder/codec/LongStringCodec.js b/src/encoder/codec/LongStringCodec.js new file mode 100644 index 0000000..69c570c --- /dev/null +++ b/src/encoder/codec/LongStringCodec.js @@ -0,0 +1,34 @@ +import Codec from './Codec'; + +/** + * String codec (limited to 65536 chars) + */ +export default class LongStringCodec extends Codec { + /** + * @type {Number} + */ + getByteLength(data) { + return 2 + data.length * 2; + } + + /** + * {@inheritdoc} + */ + encode(buffer, offset, data) { + const view = new DataView(buffer, offset, this.getByteLength(data)); + + view.setUint16(0, data.length); + + Array.from(data).forEach((letter, index) => view.setUint16(2 + (index * 2), letter.charCodeAt(0))); + } + + /** + * {@inheritdoc} + */ + decode(buffer, offset) { + const view = new DataView(buffer, offset); + const length = view.getUint16(0); + + return new Array(length).fill(null).map((value, index) => String.fromCharCode(view.getUint16(2 + index * 2))).join(''); + } +} diff --git a/src/encoder/codec/index.js b/src/encoder/codec/index.js index 2bdbd7e..2b06a36 100644 --- a/src/encoder/codec/index.js +++ b/src/encoder/codec/index.js @@ -5,6 +5,7 @@ import Int16Codec from 'netcode/src/encoder/codec/Int16Codec'; import Int32Codec from 'netcode/src/encoder/codec/Int32Codec'; import LongIntCodec from 'netcode/src/encoder/codec/LongIntCodec'; import StringCodec from 'netcode/src/encoder/codec/StringCodec'; +import LongStringCodec from 'netcode/src/encoder/codec/LongStringCodec'; module.exports = { Codec, @@ -14,4 +15,5 @@ module.exports = { Int32Codec, LongIntCodec, StringCodec, + LongStringCodec, }; diff --git a/src/server/Beacon.js b/src/server/Beacon.js new file mode 100644 index 0000000..d6948d9 --- /dev/null +++ b/src/server/Beacon.js @@ -0,0 +1,40 @@ +/** + * Send ping at fixed interval + */ +export default class Beacon { + /** + * @param {Socket} socket + * @param {Number} frequency Frequency in second + */ + constructor(socket, frequency = 30) { + this.socket = socket; + this.frequency = frequency * 1000; + this.interval = null; + + this.start = this.start.bind(this); + this.ping = this.ping.bind(this); + this.stop = this.stop.bind(this); + + this.socket.on('open', this.start); + this.socket.on('close', this.stop); + } + + start() { + if (!this.interval) { + this.interval = setInterval(this.ping, this.frequency); + } + } + + stop() { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } + + ping() { + if (this.socket) { + this.socket.ping(); + } + } +} diff --git a/src/server/Client.js b/src/server/Client.js index a790abb..82ac80c 100644 --- a/src/server/Client.js +++ b/src/server/Client.js @@ -7,30 +7,25 @@ export default class Client extends EventEmitter { * @param {WebSocket} socket * @param {String} ip * @param {Encoder} encoder - * @param {Number} pingFrequency Ping frequency in milliseconds */ - constructor(socket, ip, encoder, pingFrequency = 0) { + constructor(socket, ip, encoder) { super(); this.id = ++INDEX; this.ip = ip; this.socket = socket; this.encoder = encoder; - this.pingInterval = null; + this.onOpen = this.onOpen.bind(this); this.onMessage = this.onMessage.bind(this); this.onError = this.onError.bind(this); this.onClose = this.onClose.bind(this); - this.ping = this.ping.bind(this); + this.socket.on('open', this.onOpen); this.socket.on('message', this.onMessage); this.socket.on('error', this.onError); this.socket.on('close', this.onClose); - if (pingFrequency > 0) { - this.socket.on('open', () => this.startPing(pingFrequency)); - } - this.socket.send = this.encoder.constructor.binaryType === 'arraybuffer' ? this.socket.binary : this.socket.text; this.socket.start(); @@ -56,30 +51,10 @@ export default class Client extends EventEmitter { } /** - * Start ping at given interval - * - * @param {Number} frequency Ping frequency in milliseconds + * On socket open */ - startPing(frequency) { - if (frequency) { - this.pingInterval = setInterval(this.ping, frequency); - } - } - - ping() { - if (this.socket) { - this.socket.ping(); - } - } - - /** - * Stop ping interval - */ - stopPing() { - if (this.pingInterval) { - clearInterval(this.pingInterval); - this.pingInterval = null; - } + onOpen() { + this.emit('open', this); } /** @@ -103,7 +78,8 @@ export default class Client extends EventEmitter { * @param {Event} event */ onError(event) { - console.error(`Client ${this.id}: `, event.message); + console.error(`[ERROR] In socket for client ${this.id}: `, event.message); + this.emit('error', new Error(event.message), this); this.close(); } @@ -111,7 +87,6 @@ export default class Client extends EventEmitter { * On close */ onClose() { - this.stopPing(); this.socket = null; this.emit('close', this); } diff --git a/src/server/Server.js b/src/server/Server.js index 542d87d..618fb02 100644 --- a/src/server/Server.js +++ b/src/server/Server.js @@ -3,21 +3,22 @@ import EventEmitter from 'events'; import WebSocket from 'websocket-driver'; import JsonEncoder from 'netcode/src/encoder/JsonEncoder'; import Client from 'netcode/src/server/Client'; +import Beacon from 'netcode/src/server/Beacon'; export default class Server extends EventEmitter { /** - * @param {Number} port Port - * @param {String} host Host - * @param {JsonEncoder|BinaryEncoder} encoder + * @param {Number} port Port to listen on + * @param {String} host Host to listen on + * @param {JsonEncoder|BinaryEncoder} encoder Encoder to use to read/write event messages * @param {Number} ping Ping frequency in seconds (0 for no ping) * @param {Number} maxLength Paquet max length in bit * @param {Array} protocols Supported protocols */ - constructor(port = 8080, host = 'localhost', encoder = new JsonEncoder(), ping = 0, maxLength = Math.pow(2, 9) - 1, protocols = ['websocket']) { + constructor(port = 8080, host = '0.0.0.0', encoder = new JsonEncoder(), ping = 30, maxLength = Math.pow(2, 9), protocols = ['websocket']) { super(); this.onUpgrade = this.onUpgrade.bind(this); - //this.onRequest = this.onRequest.bind(this); + this.onRequest = this.onRequest.bind(this); this.onError = this.onError.bind(this); this.removeClient = this.removeClient.bind(this); @@ -30,9 +31,9 @@ export default class Server extends EventEmitter { protocols, }; - this.server.on('error', this.onError); this.server.on('upgrade', this.onUpgrade); - //this.server.on('request', this.onRequest); + this.server.on('request', this.onRequest); + this.server.on('error', this.onError); this.start(port, host); } @@ -55,7 +56,7 @@ export default class Server extends EventEmitter { */ addClient(client) { this.clients.set(client.id, client); - client.addListener('close', this.removeClient); + client.on('close', this.removeClient); this.emit('client:join', client); } @@ -88,7 +89,11 @@ export default class Server extends EventEmitter { driver.io.write(body); socket.pipe(driver.io).pipe(socket); - this.addClient(new Client(driver, ip, this.encoder, this.ping * 1000)); + if (this.ping) { + new Beacon(driver, this.ping); + } + + this.addClient(new Client(driver, ip, this.encoder)); } /** @@ -97,19 +102,14 @@ export default class Server extends EventEmitter { * @param {Request} request * @param {Response} response */ - /*onRequest(request, response) { + onRequest(request, response) { switch (request.url) { - case '/': - response.writeHead(200, { 'Content-Type': 'application/json' }); - response.end(JSON.stringify(this.getStatus())); - break; - default: response.writeHead(404); response.end(); break; } - }*/ + } /** * On error diff --git a/src/server/index.js b/src/server/index.js index 01c6a5d..2854143 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -8,6 +8,7 @@ import Int32Codec from 'netcode/src/encoder/codec/Int32Codec'; import LongIntCodec from 'netcode/src/encoder/codec/LongIntCodec'; import BooleanCodec from 'netcode/src/encoder/codec/BooleanCodec'; import StringCodec from 'netcode/src/encoder/codec/StringCodec'; +import LongStringCodec from 'netcode/src/encoder/codec/LongStringCodec'; module.exports = { Server, @@ -20,4 +21,5 @@ module.exports = { BooleanCodec, LongIntCodec, StringCodec, + LongStringCodec, };