diff --git a/hobu_alert_system_motion/app.js b/hobu_alert_system_motion/app.js index bca5065..ea3414e 100644 --- a/hobu_alert_system_motion/app.js +++ b/hobu_alert_system_motion/app.js @@ -2,7 +2,8 @@ var Tinkerforge = require('../Tinkerforge') , Conf = require('./config.json') , Helper = require('../Adawim/helper') , Log = require('../Adawim/logging') -, http = require('http'); +, http = require('http') +, ws = require("nodejs-websocket"); Helper.each(Conf.items, function(item) { @@ -42,14 +43,15 @@ Helper.each(Conf.items, function(item) { // Register motion detected callback md.on(Tinkerforge.BrickletMotionDetector.CALLBACK_MOTION_DETECTED, - // Callback function for motion detected callback - function () { - Log.log('Motion detected'); - } + // Callback function for motion detected callback + function () { + Log.log('Motion detected'); + sendToHoBu("ping"); + } ); process.on( 'SIGINT', function() { - console.log( "\nGracefully disconnect " + HOST ); + Log.log( "Gracefully disconnect " + HOST ); ipcon.disconnect(); process.exit( ); }); @@ -73,4 +75,23 @@ function doHoBuDoorBellCall(options) { } http.request(options, callback).end(); +} + +/* private */ +function sendToHoBu(data) { + Log.debug('Send data to ' + Conf.hobu.webservice); + + try { + ws.connect(Conf.hobu.webservice, null, function(conn) { + if(Helper.isDefinedAndNotNull(conn)) { + conn.send(data, null); + } else { + Log.error('Connection is NULL.'); + } + + //TODO: disconnect!! + }); + } catch(err) { + Log.error(err.message); + } } \ No newline at end of file diff --git a/hobu_distance_switch/app.js b/hobu_distance_switch/app.js index 6fd6685..38372a1 100644 --- a/hobu_distance_switch/app.js +++ b/hobu_distance_switch/app.js @@ -53,7 +53,7 @@ Helper.each(Conf.items, function(item) { ); process.on( 'SIGINT', function() { - console.log( "\nGracefully disconnect " + HOST ); + Log.log( "Gracefully disconnect " + HOST ); ipcon.disconnect(); process.exit( ); }); diff --git a/hobu_doorbell/app.js b/hobu_doorbell/app.js index c393f47..878ae41 100644 --- a/hobu_doorbell/app.js +++ b/hobu_doorbell/app.js @@ -57,7 +57,7 @@ ai.on(Tinkerforge.BrickletAnalogInV2.CALLBACK_VOLTAGE, ); process.on( 'SIGINT', function() { - console.log( "\nGracefully disconnect " + HOST ); + Log.log( "Gracefully disconnect " + HOST ); ipcon.disconnect(); process.exit( ); }); diff --git a/node_modules/nodejs-websocket/.jshintrc b/node_modules/nodejs-websocket/.jshintrc new file mode 100644 index 0000000..5c97b17 --- /dev/null +++ b/node_modules/nodejs-websocket/.jshintrc @@ -0,0 +1,22 @@ +{ + "curly": true, + "eqeqeq": true, + "nonew": true, + "latedef": "nofunc", + "unused": true, + "noarg": true, + "asi": true, + "trailing": true, + "quotmark": true, + "strict": true, + "undef": true, + "expr": true, + "newcap": true, + "immed": true, + "node": true, + "camelcase": true, + "devel": true, + "freeze": true, + "-W033": true, + "-W058": true +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/.npmignore b/node_modules/nodejs-websocket/.npmignore new file mode 100644 index 0000000..3ed4d77 --- /dev/null +++ b/node_modules/nodejs-websocket/.npmignore @@ -0,0 +1,3 @@ +node_modules +test +samples \ No newline at end of file diff --git a/node_modules/nodejs-websocket/.travis.yml b/node_modules/nodejs-websocket/.travis.yml new file mode 100644 index 0000000..b8157e8 --- /dev/null +++ b/node_modules/nodejs-websocket/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.12" + - "4.2.1" + - "node" \ No newline at end of file diff --git a/node_modules/nodejs-websocket/Connection.js b/node_modules/nodejs-websocket/Connection.js new file mode 100644 index 0000000..62b9128 --- /dev/null +++ b/node_modules/nodejs-websocket/Connection.js @@ -0,0 +1,616 @@ +/** + * @file Represents a connection (both client and server sides) + */ +'use strict' + +var util = require('util'), + events = require('events'), + crypto = require('crypto'), + InStream = require('./InStream'), + OutStream = require('./OutStream'), + frame = require('./frame'), + Server = require('./Server') + +/** + * @class + * @param {(net.Socket|tls.CleartextStream)} socket a net or tls socket + * @param {(Server|{path:string,host:string})} parentOrUrl parent in case of server-side connection, url object in case of client-side + * @param {Function} [callback] will be added as a listener to 'connect' + * @inherits EventEmitter + * @event close the numeric code and string reason will be passed + * @event error an error object is passed + * @event text a string is passed + * @event binary a inStream object is passed + * @event pong a string is passed + * @event connect + */ +function Connection(socket, parentOrUrl, callback) { + var that = this, + connectEvent + + if (parentOrUrl instanceof Server) { + // Server-side connection + this.server = parentOrUrl + this.path = null + this.host = null + this.extraHeaders = null + } else { + // Client-side + this.server = null + this.path = parentOrUrl.path + this.host = parentOrUrl.host + this.extraHeaders = parentOrUrl.extraHeaders + } + + this.socket = socket + this.readyState = this.CONNECTING + this.buffer = new Buffer(0) + this.frameBuffer = null // string for text frames and InStream for binary frames + this.outStream = null // current allocated OutStream object for sending binary frames + this.key = null // the Sec-WebSocket-Key header + this.headers = {} // read only map of header names and values. Header names are lower-cased + + // Set listeners + socket.on('readable', function () { + that.doRead() + }) + + socket.on('error', function (err) { + that.emit('error', err) + }) + + if (!this.server) { + connectEvent = socket.constructor.name === 'CleartextStream' ? 'secureConnect' : 'connect' + socket.on(connectEvent, function () { + that.startHandshake() + }) + } + + // Close listeners + var onclose = function () { + if (that.readyState === that.CONNECTING || that.readyState === that.OPEN) { + that.emit('close', 1006, '') + } + that.readyState = this.CLOSED + if (that.frameBuffer instanceof InStream) { + that.frameBuffer.end() + that.frameBuffer = null + } + if (that.outStream instanceof OutStream) { + that.outStream.end() + that.outStream = null + } + } + socket.once('close', onclose) + socket.once('finish', onclose) + + // super constructor + events.EventEmitter.call(this) + if (callback) { + this.once('connect', callback) + } +} + +util.inherits(Connection, events.EventEmitter) +module.exports = Connection + +/** + * Minimum size of a pack of binary data to send in a single frame + * @property {number} binaryFragmentation + */ +Connection.binaryFragmentation = 512 * 1024 // .5 MiB + +/** + * The maximum size the internal Buffer can grow + * If at any time it stays bigger than this, the connection will be closed with code 1009 + * This is a security measure, to avoid memory attacks + * @property {number} maxBufferLength + */ +Connection.maxBufferLength = 2 * 1024 * 1024 // 2 MiB + +/** + * Possible ready states for the connection + * @constant {number} CONNECTING + * @constant {number} OPEN + * @constant {number} CLOSING + * @constant {number} CLOSED + */ +Connection.prototype.CONNECTING = 0 +Connection.prototype.OPEN = 1 +Connection.prototype.CLOSING = 2 +Connection.prototype.CLOSED = 3 + +/** + * Send a given string to the other side + * @param {string} str + * @param {Function} [callback] will be executed when the data is finally written out + */ +Connection.prototype.sendText = function (str, callback) { + if (this.readyState === this.OPEN) { + if (!this.outStream) { + return this.socket.write(frame.createTextFrame(str, !this.server), callback) + } + this.emit('error', new Error('You can\'t send a text frame until you finish sending binary frames')) + } + this.emit('error', new Error('You can\'t write to a non-open connection')) +} + +/** + * Request for a OutStream to send binary data + * @returns {OutStream} + */ +Connection.prototype.beginBinary = function () { + if (this.readyState === this.OPEN) { + if (!this.outStream) { + return (this.outStream = new OutStream(this, Connection.binaryFragmentation)) + } + this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames')) + } + this.emit('error', new Error('You can\'t write to a non-open connection')) +} + +/** + * Sends a binary buffer at once + * @param {Buffer} data + * @param {Function} [callback] will be executed when the data is finally written out + */ +Connection.prototype.sendBinary = function (data, callback) { + if (this.readyState === this.OPEN) { + if (!this.outStream) { + return this.socket.write(frame.createBinaryFrame(data, !this.server, true, true), callback) + } + this.emit('error', new Error('You can\'t send more binary frames until you finish sending the previous binary frames')) + } + this.emit('error', new Error('You can\'t write to a non-open connection')) +} + +/** + * Sends a text or binary frame + * @param {string|Buffer} data + * @param {Function} [callback] will be executed when the data is finally written out + */ +Connection.prototype.send = function (data, callback) { + if (typeof data === 'string') { + this.sendText(data, callback) + } else if (Buffer.isBuffer(data)) { + this.sendBinary(data, callback) + } else { + throw new TypeError('data should be either a string or a Buffer instance') + } +} + +/** + * Sends a ping to the remote + * @param {string} [data=''] - optional ping data + * @fires pong when pong reply is received + */ +Connection.prototype.sendPing = function (data) { + if (this.readyState === this.OPEN) { + return this.socket.write(frame.createPingFrame(data || '', !this.server)) + } + this.emit('error', new Error('You can\'t write to a non-open connection')) +} + +/** + * Close the connection, sending a close frame and waiting for response + * If the connection isn't OPEN, closes it without sending a close frame + * @param {number} [code] + * @param {string} [reason] + * @fires close + */ +Connection.prototype.close = function (code, reason) { + if (this.readyState === this.OPEN) { + this.socket.write(frame.createCloseFrame(code, reason, !this.server)) + this.readyState = this.CLOSING + } else if (this.readyState !== this.CLOSED) { + this.socket.end() + this.readyState = this.CLOSED + } + this.emit('close', code, reason) +} + +/** + * Reads contents from the socket and process it + * @fires connect + * @private + */ +Connection.prototype.doRead = function () { + var buffer, temp + + // Fetches the data + buffer = this.socket.read() + if (!buffer) { + // Waits for more data + return + } + + // Save to the internal buffer + this.buffer = Buffer.concat([this.buffer, buffer], this.buffer.length + buffer.length) + + if (this.readyState === this.CONNECTING) { + if (!this.readHandshake()) { + // May have failed or we're waiting for more data + return + } + } + + if (this.readyState !== this.CLOSED) { + // Try to read as many frames as possible + while ((temp = this.extractFrame()) === true) {} + if (temp === false) { + // Protocol error + this.close(1002) + } else if (this.buffer.length > Connection.maxBufferLength) { + // Frame too big + this.close(1009) + } + } +} + +/** + * Create and send a handshake as a client + * @private + */ +Connection.prototype.startHandshake = function () { + var str, i, key, headers, header + key = new Buffer(16) + for (i = 0; i < 16; i++) { + key[i] = Math.floor(Math.random() * 256) + } + this.key = key.toString('base64') + headers = { + Host: this.host, + Upgrade: 'websocket', + Connection: 'Upgrade', + 'Sec-WebSocket-Key': this.key, + 'Sec-WebSocket-Version': '13' + } + + for (header in this.extraHeaders) { + headers[header] = this.extraHeaders[header] + } + + str = this.buildRequest('GET ' + this.path + ' HTTP/1.1', headers) + this.socket.write(str) +} + +/** + * Try to read the handshake from the internal buffer + * If it succeeds, the handshake data is consumed from the internal buffer + * @returns {boolean} - whether the handshake was done + * @private + */ +Connection.prototype.readHandshake = function () { + var found = false, + i, data + + // Do the handshake and try to connect + if (this.buffer.length > Connection.maxBufferLength) { + // Too big for a handshake + this.socket.end(this.server ? 'HTTP/1.1 400 Bad Request\r\n\r\n' : undefined) + return false + } + + // Search for '\r\n\r\n' + for (i = 0; i < this.buffer.length - 3; i++) { + if (this.buffer[i] === 13 && this.buffer[i + 2] === 13 && + this.buffer[i + 1] === 10 && this.buffer[i + 3] === 10) { + found = true + break + } + } + if (!found) { + // Wait for more data + return false + } + data = this.buffer.slice(0, i + 4).toString().split('\r\n') + if (this.server ? this.answerHandshake(data) : this.checkHandshake(data)) { + this.buffer = this.buffer.slice(i + 4) + this.readyState = this.OPEN + this.emit('connect') + return true + } else { + this.socket.end(this.server ? 'HTTP/1.1 400 Bad Request\r\n\r\n' : undefined) + return false + } +} + +/** + * Read headers from HTTP protocol + * Update the Connection#headers property + * @param {string[]} lines one for each '\r\n'-separated HTTP request line + * @private + */ +Connection.prototype.readHeaders = function (lines) { + var i, match + + // Extract all headers + // Ignore bad-formed lines and ignore the first line (HTTP header) + for (i = 1; i < lines.length; i++) { + if ((match = lines[i].match(/^([a-z-]+): (.+)$/i))) { + this.headers[match[1].toLowerCase()] = match[2] + } + } +} + +/** + * Process and check a handshake answered by a server + * @param {string[]} lines one for each '\r\n'-separated HTTP request line + * @returns {boolean} if the handshake was sucessful. If not, the connection must be closed + * @private + */ +Connection.prototype.checkHandshake = function (lines) { + var key, sha1 + + // First line + if (lines.length < 4) { + return false + } + if (!lines[0].match(/^HTTP\/\d\.\d 101( .*)?$/i)) { + return false + } + + // Extract all headers + this.readHeaders(lines) + + // Validate necessary headers + if (!('upgrade' in this.headers) || + !('sec-websocket-accept' in this.headers) || + !('connection' in this.headers)) { + return false + } + if (this.headers.upgrade.toLowerCase() !== 'websocket' || + this.headers.connection.toLowerCase().split(', ').indexOf('upgrade') === -1) { + return false + } + key = this.headers['sec-websocket-accept'] + + // Check the key + sha1 = crypto.createHash('sha1') + sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') + if (key !== sha1.read().toString('base64')) { + return false + } + return true +} + +/** + * Process and answer a handshake started by a client + * @param {string[]} lines one for each '\r\n'-separated HTTP request line + * @returns {boolean} if the handshake was sucessful. If not, the connection must be closed with error 400-Bad Request + * @private + */ +Connection.prototype.answerHandshake = function (lines) { + var path, key, sha1 + + // First line + if (lines.length < 6) { + return false + } + path = lines[0].match(/^GET (.+) HTTP\/\d\.\d$/i) + if (!path) { + return false + } + this.path = path[1] + + // Extract all headers + this.readHeaders(lines) + + // Validate necessary headers + if (!('host' in this.headers) || + !('sec-websocket-key' in this.headers) || + !('upgrade' in this.headers) || + !('connection' in this.headers)) { + return false + } + if (this.headers.upgrade.toLowerCase() !== 'websocket' || + this.headers.connection.toLowerCase().split(', ').indexOf('upgrade') === -1) { + return false + } + if (this.headers['sec-websocket-version'] !== '13') { + return false + } + + this.key = this.headers['sec-websocket-key'] + + // Build and send the response + sha1 = crypto.createHash('sha1') + sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') + key = sha1.read().toString('base64') + this.socket.write(this.buildRequest('HTTP/1.1 101 Switching Protocols', { + Upgrade: 'websocket', + Connection: 'Upgrade', + 'Sec-WebSocket-Accept': key + })) + return true +} + +/** + * Try to extract frame contents from the buffer (and execute it) + * @returns {(boolean|undefined)} false=something went wrong (the connection must be closed); undefined=there isn't enough data to catch a frame; true=the frame was successfully fetched and executed + * @private + */ +Connection.prototype.extractFrame = function () { + var fin, opcode, B, HB, mask, len, payload, start, i, hasMask + + if (this.buffer.length < 2) { + return + } + + // Is this the last frame in a sequence? + B = this.buffer[0] + HB = B >> 4 + if (HB % 8) { + // RSV1, RSV2 and RSV3 must be clear + return false + } + fin = HB === 8 + opcode = B % 16 + + if (opcode !== 0 && opcode !== 1 && opcode !== 2 && + opcode !== 8 && opcode !== 9 && opcode !== 10) { + // Invalid opcode + return false + } + if (opcode >= 8 && !fin) { + // Control frames must not be fragmented + return false + } + + B = this.buffer[1] + hasMask = B >> 7 + if ((this.server && !hasMask) || (!this.server && hasMask)) { + // Frames sent by clients must be masked + return false + } + len = B % 128 + start = hasMask ? 6 : 2 + + if (this.buffer.length < start + len) { + // Not enough data in the buffer + return + } + + // Get the actual payload length + if (len === 126) { + len = this.buffer.readUInt16BE(2) + start += 2 + } else if (len === 127) { + // Warning: JS can only store up to 2^53 in its number format + len = this.buffer.readUInt32BE(2) * Math.pow(2, 32) + this.buffer.readUInt32BE(6) + start += 8 + } + if (this.buffer.length < start + len) { + return + } + + // Extract the payload + payload = this.buffer.slice(start, start + len) + if (hasMask) { + // Decode with the given mask + mask = this.buffer.slice(start - 4, start) + for (i = 0; i < payload.length; i++) { + payload[i] ^= mask[i % 4] + } + } + this.buffer = this.buffer.slice(start + len) + + // Proceeds to frame processing + return this.processFrame(fin, opcode, payload) +} + +/** + * Process a given frame received + * @param {boolean} fin + * @param {number} opcode + * @param {Buffer} payload + * @returns {boolean} false if any error occurs, true otherwise + * @fires text + * @fires binary + * @private + */ +Connection.prototype.processFrame = function (fin, opcode, payload) { + if (opcode === 8) { + // Close frame + if (this.readyState === this.CLOSING) { + this.socket.end() + } else if (this.readyState === this.OPEN) { + this.processCloseFrame(payload) + } + return true + } else if (opcode === 9) { + // Ping frame + if (this.readyState === this.OPEN) { + this.socket.write(frame.createPongFrame(payload.toString(), !this.server)) + } + return true + } else if (opcode === 10) { + // Pong frame + this.emit('pong', payload.toString()) + return true + } + + if (this.readyState !== this.OPEN) { + // Ignores if the connection isn't opened anymore + return true + } + + if (opcode === 0 && this.frameBuffer === null) { + // Unexpected continuation frame + return false + } else if (opcode !== 0 && this.frameBuffer !== null) { + // Last sequence didn't finished correctly + return false + } + + if (!opcode) { + // Get the current opcode for fragmented frames + opcode = typeof this.frameBuffer === 'string' ? 1 : 2 + } + + if (opcode === 1) { + // Save text frame + payload = payload.toString() + this.frameBuffer = this.frameBuffer ? this.frameBuffer + payload : payload + + if (fin) { + // Emits 'text' event + this.emit('text', this.frameBuffer) + this.frameBuffer = null + } + } else { + // Sends the buffer for InStream object + if (!this.frameBuffer) { + // Emits the 'binary' event + this.frameBuffer = new InStream + this.emit('binary', this.frameBuffer) + } + this.frameBuffer.addData(payload) + + if (fin) { + // Emits 'end' event + this.frameBuffer.end() + this.frameBuffer = null + } + } + + return true +} + +/** + * Process a close frame, emitting the close event and sending back the frame + * @param {Buffer} payload + * @fires close + * @private + */ +Connection.prototype.processCloseFrame = function (payload) { + var code, reason + if (payload.length >= 2) { + code = payload.readUInt16BE(0) + reason = payload.slice(2).toString() + } else { + code = 1005 + reason = '' + } + this.socket.write(frame.createCloseFrame(code, reason, !this.server)) + this.readyState = this.CLOSED + this.emit('close', code, reason) +} + +/** + * Build the header string + * @param {string} requestLine + * @param {Object} headers + * @returns {string} + * @private + */ +Connection.prototype.buildRequest = function (requestLine, headers) { + var headerString = requestLine + '\r\n', + headerName + + for (headerName in headers) { + headerString += headerName + ': ' + headers[headerName] + '\r\n' + } + + return headerString + '\r\n' +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/HISTORY.md b/node_modules/nodejs-websocket/HISTORY.md new file mode 100644 index 0000000..81a4df8 --- /dev/null +++ b/node_modules/nodejs-websocket/HISTORY.md @@ -0,0 +1,16 @@ +# 1.6.0 +* Added: `Server#close` as a short hand for `Server#socket.close` + +# 1.5.0 +* Added: `Connection#send` as a short hand for `Connection#sendText` or `Connection#sendBinary`, depending on the data type (string or Buffer) + +# 1.4.1 +* Added: example to README + +# 1.4.0 +* Added: `extraHeaders` option in `ws.connect(URL, [options], [callback])` to let one add custom headers to the HTTP handshake request + +# 1.3.0 + +* Added: `Connection#sendPing([data=''])` +* Added: `pong(data)` event \ No newline at end of file diff --git a/node_modules/nodejs-websocket/InStream.js b/node_modules/nodejs-websocket/InStream.js new file mode 100644 index 0000000..322de60 --- /dev/null +++ b/node_modules/nodejs-websocket/InStream.js @@ -0,0 +1,44 @@ +/** + * @file Simple wrapper for stream.Readable, used for receiving binary data + */ +'use strict' + +var util = require('util'), + stream = require('stream') + +/** + * Represents the readable stream for binary frames + * @class + * @event readable + * @event end + */ +function InStream() { + stream.Readable.call(this) +} + +module.exports = InStream + +util.inherits(InStream, stream.Readable) + +/** + * No logic here, the pushs are made outside _read + * @private + */ +InStream.prototype._read = function () {} + +/** + * Add more data to the stream and fires "readable" event + * @param {Buffer} data + * @private + */ +InStream.prototype.addData = function (data) { + this.push(data) +} + +/** + * Indicates there is no more data to add to the stream + * @private + */ +InStream.prototype.end = function () { + this.push(null) +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/LICENSE b/node_modules/nodejs-websocket/LICENSE new file mode 100644 index 0000000..a5e31be --- /dev/null +++ b/node_modules/nodejs-websocket/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Guilherme Souza + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/node_modules/nodejs-websocket/OutStream.js b/node_modules/nodejs-websocket/OutStream.js new file mode 100644 index 0000000..bef42d0 --- /dev/null +++ b/node_modules/nodejs-websocket/OutStream.js @@ -0,0 +1,59 @@ +/** + * @file Simple wrapper for stream.Writable, used for sending binary data + */ +'use strict' + +var util = require('util'), + stream = require('stream'), + frame = require('./frame') + +/** + * @class Represents the writable stream for binary frames + * @param {Connection} connection + * @param {number} minSize + */ +function OutStream(connection, minSize) { + var that = this + this.connection = connection + this.minSize = minSize + this.buffer = new Buffer(0) + this.hasSent = false // Indicates if any frame has been sent yet + stream.Writable.call(this) + this.on('finish', function () { + if (that.connection.readyState === that.connection.OPEN) { + // Ignore if not connected anymore + that.connection.socket.write(frame.createBinaryFrame(that.buffer, !that.connection.server, !that.hasSent, true)) + } + that.connection.outStream = null + }) +} + +module.exports = OutStream + + +util.inherits(OutStream, stream.Writable) + +/** + * @param {Buffer} chunk + * @param {string} encoding + * @param {Function} callback + * @private + */ +OutStream.prototype._write = function (chunk, encoding, callback) { + var frameBuffer + this.buffer = Buffer.concat([this.buffer, chunk], this.buffer.length + chunk.length) + if (this.buffer.length >= this.minSize) { + if (this.connection.readyState === this.connection.OPEN) { + // Ignore if not connected anymore + frameBuffer = frame.createBinaryFrame(this.buffer, !this.connection.server, !this.hasSent, false) + this.connection.socket.write(frameBuffer, encoding, callback) + } + this.buffer = new Buffer(0) + this.hasSent = true + if (this.connection.readyState !== this.connection.OPEN) { + callback() + } + } else { + callback() + } +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/README.md b/node_modules/nodejs-websocket/README.md new file mode 100644 index 0000000..eb101ec --- /dev/null +++ b/node_modules/nodejs-websocket/README.md @@ -0,0 +1,192 @@ +# Nodejs Websocket +[![Build Status](https://travis-ci.org/sitegui/nodejs-websocket.svg?branch=master)](https://travis-ci.org/sitegui/nodejs-websocket) +[![Inline docs](https://inch-ci.org/github/sitegui/nodejs-websocket.svg?branch=master)](https://inch-ci.org/github/sitegui/nodejs-websocket) +[![Dependency Status](https://david-dm.org/sitegui/nodejs-websocket.svg)](https://david-dm.org/sitegui/nodejs-websocket) + +A nodejs module for websocket server and client + +# How to use it +Install with `npm install nodejs-websocket` or put all files in a folder called "nodejs-websocket", and: +```javascript +var ws = require("nodejs-websocket") + +// Scream server example: "hi" -> "HI!!!" +var server = ws.createServer(function (conn) { + console.log("New connection") + conn.on("text", function (str) { + console.log("Received "+str) + conn.sendText(str.toUpperCase()+"!!!") + }) + conn.on("close", function (code, reason) { + console.log("Connection closed") + }) +}).listen(8001) +``` + +Se other examples inside the folder samples + +# ws +The main object, returned by `require("nodejs-websocket")`. + +## ws.createServer([options], [callback]) +Returns a new `Server` object. + +The `options` is an optional object that will be handed to net.createServer() to create an ordinary socket. +If it has a property called "secure" with value `true`, tls.createServer() will be used instead. + +The `callback` is a function which is automatically added to the `"connection"` event. + +## ws.connect(URL, [options], [callback]) +Returns a new `Connection` object, representing a websocket client connection + +`URL` is a string with the format "ws://localhost:8000/chat" (the port can be omitted) + +`options` is an object that will be passed to net.connect() (or tls.connect() if the protocol is "wss:"). +The properties "host" and "port" will be read from the `URL`. +The property `extraHeaders` will be used to add more headers to the HTTP handshake request. + +`callback` will be added as "connect" listener + +## ws.setBinaryFragmentation(bytes) +Sets the minimum size of a pack of binary data to send in a single frame (default: 512kiB) + +## ws.setMaxBufferLength(bytes) +Set the maximum size the internal Buffer can grow (default: 2MiB) +If at any time it stays bigger than this, the connection will be closed with code 1009 +This is a security measure, to avoid memory attacks + +# Server +The class that represents a websocket server, much like a HTTP server + +## server.listen(port, [host], [callback]) +Starts accepting connections on a given `port` and `host`. + +If the `host` is omitted, the server will accept connections directed to any IPv4 address (INADDR_ANY). + +A `port` value of zero will assign a random port. + +`callback` will be added as an listener for the `'listening'` event. + +## server.close([callback]) +Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. + +## server.socket +The underlying socket, returned by net.createServer or tls.createServer + +## server.connections +An Array with all connected clients. It's useful for broadcasting a message: +```javascript +function broadcast(server, msg) { + server.connections.forEach(function (conn) { + conn.sendText(msg) + }) +} +``` + +## Event: 'listening()' +Emitted when the server has been bound after calling server.listen + +## Event: 'close()' +Emitted when the server closes. Note that if connections exist, this event is not emitted until all connections are completely ended. + +## Event: 'error(errObj)' +Emitted when an error occurs. The 'close' event will be called directly following this event. + +## Event: 'connection(conn)' +Emitted when a new connection is made successfully (after the handshake have been completed). conn is an instance of Connection + +# Connection +The class that represents a connection, either a client-created (accepted by a nodejs ws server) or client connection. +The websocket protocol has two types of data frames: text and binary. +Text frames are implemented as simple send function and receive event. +Binary frames are implemented as streams: when you receive binary data, you get a ReadableStream; to send binary data, you must ask for a WritableStream and write into it. +The binary data will be divided into frames and be sent over the socket. + +You cannot send text data while sending binary data. If you try to do so, the connection will emit an "error" event + +## connection.sendText(str, [callback]) +Sends a given string to the other side. You can't send text data in the middle of a binary transmission. + +`callback` will be added as a listener to write operation over the socket + +## connection.beginBinary() +Asks the connection to begin transmitting binary data. Returns a WritableStream. +The binary transmission will end when the WritableStream finishes (like when you call .end on it) + +## connection.sendBinary(data, [callback]) +Sends a single chunk of binary data (like calling connection.beginBinary().end(data)) + +`callback` will be added as a listener to write operation over the socket + +## connection.send(data, [callback]) +Sends a given string or Buffer to the other side. This is simply an alias for `sendText()` if data is a string or `sendBinary()` if the data is a Buffer. + +`callback` will be added as a listener to write operation over the socket + +## connection.sendPing([data='']) +Sends a [ping](http://tools.ietf.org/html/rfc6455#section-5.5.2) with optional payload + +## connection.close([code, [reason]]) +Starts the closing handshake (sends a close frame) + +## connection.socket +The underlying net or tls socket + +## connection.server +If the connection was accepted by a nodejs server, a reference to it will be saved here. null otherwise + +## connection.readyState +One of these constants, representing the current state of the connection. Only an open connection can be used to send/receive data. +* connection.CONNECTING (waiting for handshake completion) +* connection.OPEN +* connection.CLOSING (waiting for the answer to a close frame) +* connection.CLOSED + +## connection.outStream +Stores the OutStream object returned by connection.beginBinary(). null if there is no current binary data beeing sent. + +## connection.path +For a connection accepted by a server, it is a string representing the path to which the connection was made (example: "/chat"). null otherwise + +## connection.headers +Read only map of header names and values. Header names are lower-cased + +## Event: 'close(code, reason)' +Emitted when the connection is closed by any side + +## Event: 'error(err)' +Emitted in case of error (like trying to send text data while still sending binary data) + +## Event: 'text(str)' +Emitted when a text is received. `str` is a string + +## Event: 'binary(inStream)' +Emitted when the beginning of binary data is received. `inStream` is a [ReadableStream](https://nodejs.org/api/stream.html#stream_class_stream_readable): +```javascript +var server = ws.createServer(function (conn) { + console.log("New connection") + conn.on("binary", function (inStream) { + // Empty buffer for collecting binary data + var data = new Buffer(0) + // Read chunks of binary data and add to the buffer + inStream.on("readable", function () { + var newData = inStream.read() + if (newData) + data = Buffer.concat([data, newData], data.length+newData.length) + }) + inStream.on("end", function () { + console.log("Received " + data.length + " bytes of binary data") + process_my_data(data) + }) + }) + conn.on("close", function (code, reason) { + console.log("Connection closed") + }) +}).listen(8001) +``` + +## Event: 'connect()' +Emitted when the connection is fully established (after the handshake) + +## Event: 'pong(data)' +Emitted when a [pong](http://tools.ietf.org/html/rfc6455#section-5.5.3) is received, usually after a ping was sent. `data` is the pong payload, as a string diff --git a/node_modules/nodejs-websocket/Server.js b/node_modules/nodejs-websocket/Server.js new file mode 100644 index 0000000..b866ff0 --- /dev/null +++ b/node_modules/nodejs-websocket/Server.js @@ -0,0 +1,113 @@ +/** + * @file Represents a websocket server + */ +'use strict' + +function nop() {} + +var util = require('util'), + net = require('net'), + tls = require('tls'), + events = require('events'), + Connection + +/** + * Creates a new ws server and starts listening for new connections + * @class + * @param {boolean} secure indicates if it should use tls + * @param {Object} [options] will be passed to net.createServer() or tls.createServer() + * @param {Function} [callback] will be added as "connection" listener + * @inherits EventEmitter + * @event listening + * @event close + * @event error an error object is passed + * @event connection a Connection object is passed + */ +function Server(secure, options, callback) { + var that = this + + if (typeof options === 'function') { + callback = options + options = undefined + } + + var onConnection = function (socket) { + var conn = new Connection(socket, that, function () { + that.connections.push(conn) + conn.removeListener('error', nop) + that.emit('connection', conn) + }) + conn.on('close', function () { + var pos = that.connections.indexOf(conn) + if (pos !== -1) { + that.connections.splice(pos, 1) + } + }) + + // Ignore errors before the connection is established + conn.on('error', nop) + } + + if (secure) { + this.socket = tls.createServer(options, onConnection) + } else { + this.socket = net.createServer(options, onConnection) + } + + this.socket.on('close', function () { + that.emit('close') + }) + this.socket.on('error', function (err) { + that.emit('error', err) + }) + this.connections = [] + + // super constructor + events.EventEmitter.call(this) + if (callback) { + this.on('connection', callback) + } +} + +util.inherits(Server, events.EventEmitter) +module.exports = Server + +Connection = require('./Connection') + +/** + * Start listening for connections + * @param {number} port + * @param {string} [host] + * @param {Function} [callback] will be added as "connection" listener + */ +Server.prototype.listen = function (port, host, callback) { + var that = this + + if (typeof host === 'function') { + callback = host + host = undefined + } + + if (callback) { + this.on('listening', callback) + } + + this.socket.listen(port, host, function () { + that.emit('listening') + }) + + return this +} + +/** + * Stops the server from accepting new connections and keeps existing connections. + * This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. + * The optional callback will be called once the 'close' event occurs. + * @param {function()} [callback] + */ +Server.prototype.close = function (callback) { + if (callback) { + this.once('close', callback) + } + this.socket.close() +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/frame.js b/node_modules/nodejs-websocket/frame.js new file mode 100644 index 0000000..f752294 --- /dev/null +++ b/node_modules/nodejs-websocket/frame.js @@ -0,0 +1,152 @@ +/** + * @file Utility functions for creating frames + */ +'use strict' + +/** + * Creates a text frame + * @param {string} data + * @param {boolean} [masked=false] if the frame should be masked + * @returns {Buffer} + * @private + */ +exports.createTextFrame = function (data, masked) { + var payload, meta + + payload = new Buffer(data) + meta = generateMetaData(true, 1, masked === undefined ? false : masked, payload) + + return Buffer.concat([meta, payload], meta.length + payload.length) +} + +/** + * Create a binary frame + * @param {Buffer} data + * @param {boolean} [masked=false] if the frame should be masked + * @param {boolean} [first=true] if this is the first frame in a sequence + * @param {boolean} [fin=true] if this is the final frame in a sequence + * @returns {Buffer} + * @private + */ +exports.createBinaryFrame = function (data, masked, first, fin) { + var payload, meta + + first = first === undefined ? true : first + masked = masked === undefined ? false : masked + if (masked) { + payload = new Buffer(data.length) + data.copy(payload) + } else { + payload = data + } + meta = generateMetaData(fin === undefined ? true : fin, first ? 2 : 0, masked, payload) + + return Buffer.concat([meta, payload], meta.length + payload.length) +} + +/** + * Create a close frame + * @param {number} code + * @param {string} [reason=''] + * @param {boolean} [masked=false] if the frame should be masked + * @returns {Buffer} + * @private + */ +exports.createCloseFrame = function (code, reason, masked) { + var payload, meta + + if (code !== undefined && code !== 1005) { + payload = new Buffer(reason === undefined ? '--' : '--' + reason) + payload.writeUInt16BE(code, 0) + } else { + payload = new Buffer(0) + } + meta = generateMetaData(true, 8, masked === undefined ? false : masked, payload) + + return Buffer.concat([meta, payload], meta.length + payload.length) +} + +/** + * Create a ping frame + * @param {string} data + * @param {boolean} [masked=false] if the frame should be masked + * @returns {Buffer} + * @private + */ +exports.createPingFrame = function (data, masked) { + var payload, meta + + payload = new Buffer(data) + meta = generateMetaData(true, 9, masked === undefined ? false : masked, payload) + + return Buffer.concat([meta, payload], meta.length + payload.length) +} + +/** + * Create a pong frame + * @param {string} data + * @param {boolean} [masked=false] if the frame should be masked + * @returns {Buffer} + * @private + */ +exports.createPongFrame = function (data, masked) { + var payload, meta + + payload = new Buffer(data) + meta = generateMetaData(true, 10, masked === undefined ? false : masked, payload) + + return Buffer.concat([meta, payload], meta.length + payload.length) +} + +/** + * Creates the meta-data portion of the frame + * If the frame is masked, the payload is altered accordingly + * @param {boolean} fin + * @param {number} opcode + * @param {boolean} masked + * @param {Buffer} payload + * @returns {Buffer} + * @private + */ +function generateMetaData(fin, opcode, masked, payload) { + var len, meta, start, mask, i + + len = payload.length + + // Creates the buffer for meta-data + meta = new Buffer(2 + (len < 126 ? 0 : (len < 65536 ? 2 : 8)) + (masked ? 4 : 0)) + + // Sets fin and opcode + meta[0] = (fin ? 128 : 0) + opcode + + // Sets the mask and length + meta[1] = masked ? 128 : 0 + start = 2 + if (len < 126) { + meta[1] += len + } else if (len < 65536) { + meta[1] += 126 + meta.writeUInt16BE(len, 2) + start += 2 + } else { + // Warning: JS doesn't support integers greater than 2^53 + meta[1] += 127 + meta.writeUInt32BE(Math.floor(len / Math.pow(2, 32)), 2) + meta.writeUInt32BE(len % Math.pow(2, 32), 6) + start += 8 + } + + // Set the mask-key + if (masked) { + mask = new Buffer(4) + for (i = 0; i < 4; i++) { + meta[start + i] = mask[i] = Math.floor(Math.random() * 256) + } + for (i = 0; i < payload.length; i++) { + payload[i] ^= mask[i % 4] + } + start += 4 + } + + return meta +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/index.js b/node_modules/nodejs-websocket/index.js new file mode 100644 index 0000000..f3ee82d --- /dev/null +++ b/node_modules/nodejs-websocket/index.js @@ -0,0 +1,100 @@ +'use strict' + +var Server = require('./Server'), + Connection = require('./Connection'), + net = require('net'), + tls = require('tls'), + url = require('url') + +/** + * Create a WebSocket server + * @param {Object} [options] will be passed to net.createServer() or tls.createServer(), with the additional property 'secure' (a boolean) + * @param {Function} callback will be added as 'connection' listener + * @returns {Server} + */ +exports.createServer = function (options, callback) { + if (typeof options === 'function' || !arguments.length) { + return new Server(false, options) + } + return new Server(Boolean(options.secure), options, callback) +} + +/** + * Create a WebSocket client + * @param {string} URL with the format 'ws://localhost:8000/chat' (the port can be ommited) + * @param {Object} [options] will be passed to net.connect() or tls.connect() + * @param {Function} callback will be added as 'connect' listener + * @returns {Connection} + */ +exports.connect = function (URL, options, callback) { + var socket + + if (typeof options === 'function') { + callback = options + options = undefined + } + options = options || {} + + URL = parseWSURL(URL) + options.port = URL.port + options.host = URL.host + + if (options.hasOwnProperty('extraHeaders')) { + URL.extraHeaders = options.extraHeaders + } + + if (URL.secure) { + socket = tls.connect(options) + } else { + socket = net.connect(options) + } + + return new Connection(socket, URL, callback) +} + +/** + * Set the minimum size of a pack of binary data to send in a single frame + * @param {number} bytes + */ +exports.setBinaryFragmentation = function (bytes) { + Connection.binaryFragmentation = bytes +} + +/** + * Set the maximum size the internal Buffer can grow, to avoid memory attacks + * @param {number} bytes + */ +exports.setMaxBufferLength = function (bytes) { + Connection.maxBufferLength = bytes +} + +/** + * Parse the WebSocket URL + * @param {string} URL + * @returns {Object} + * @private + */ +function parseWSURL(URL) { + var parts, secure + + parts = url.parse(URL) + + parts.protocol = parts.protocol || 'ws:' + if (parts.protocol === 'ws:') { + secure = false + } else if (parts.protocol === 'wss:') { + secure = true + } else { + throw new Error('Invalid protocol ' + parts.protocol + '. It must be ws or wss') + } + + parts.port = parts.port || (secure ? 443 : 80) + parts.path = parts.path || '/' + + return { + path: parts.path, + port: parts.port, + secure: secure, + host: parts.hostname + } +} \ No newline at end of file diff --git a/node_modules/nodejs-websocket/package.json b/node_modules/nodejs-websocket/package.json new file mode 100644 index 0000000..42052d6 --- /dev/null +++ b/node_modules/nodejs-websocket/package.json @@ -0,0 +1,60 @@ +{ + "name": "nodejs-websocket", + "version": "1.6.0", + "author": { + "name": "Sitegui", + "email": "sitegui@sitegui.com.br" + }, + "description": "Basic server&client approach to websocket (text and binary frames)", + "main": "./index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/sitegui/nodejs-websocket.git" + }, + "keywords": [ + "websocket", + "websocket-server", + "websocket-client" + ], + "license": "MIT", + "engines": { + "node": ">=0.10" + }, + "scripts": { + "test": "mocha -R spec -b" + }, + "devDependencies": { + "mocha": "^2.4.5", + "should": "^8.3.1" + }, + "gitHead": "7398f57a6054724418ccaa0829daddc5ff62c908", + "bugs": { + "url": "https://github.com/sitegui/nodejs-websocket/issues" + }, + "homepage": "https://github.com/sitegui/nodejs-websocket#readme", + "_id": "nodejs-websocket@1.6.0", + "_shasum": "e6db8d2e5af9e730bf70818bfd189783fe02797a", + "_from": "nodejs-websocket@*", + "_npmVersion": "2.14.20", + "_nodeVersion": "4.4.0", + "_npmUser": { + "name": "sitegui", + "email": "sitegui@sitegui.com.br" + }, + "dist": { + "shasum": "e6db8d2e5af9e730bf70818bfd189783fe02797a", + "tarball": "https://registry.npmjs.org/nodejs-websocket/-/nodejs-websocket-1.6.0.tgz" + }, + "maintainers": [ + { + "name": "sitegui", + "email": "sitegui@sitegui.com.br" + } + ], + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/nodejs-websocket-1.6.0.tgz_1462798103164_0.4242532418575138" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/nodejs-websocket/-/nodejs-websocket-1.6.0.tgz" +} diff --git a/package.json b/package.json index 2e142be..373b794 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TF-HoBu", - "version": "0.0.1", + "version": "0.0.2", "description": "", "private": true, "repository": "ssh://git@stash.adawim.com:7999/tf/nodejs.git", @@ -9,6 +9,7 @@ "errorhandler": "*", "urlencode": "*", "dateformat": "*", - "byline": "*" + "byline": "*", + "nodejs-websocket": "*" } } \ No newline at end of file