Mini Kabibi Habibi
/*
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
*
* 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.
*
*/
(function () {
"use strict";
var util = require("util"),
events = require("events");
/**
* @private
* @constructor
* A WebSocket connection to a client. This is a private constructor.
* Callers should use the ConnectionManager.createConnection function
* instead.
*
* @param {WebSocket} ws The WebSocket representing the client
* @param {Logger} logger
*/
function Connection(ws, logger) {
events.EventEmitter.call(this);
this._ws = ws;
this._logger = logger;
this._connected = true;
this._ws.on("message", this._receive.bind(this));
this._ws.once("close", this.close.bind(this));
}
util.inherits(Connection, events.EventEmitter);
/**
* @private
* @type {boolean}
* Whether the connection is connected.
*/
Connection.prototype._connected = false;
/**
* @private
* @type {WebSocket}
* The connection's WebSocket
*/
Connection.prototype._ws = null;
/**
* @private
* @type {Logger}
*/
Connection.prototype._logger = null;
/**
* @private
* Sends a message over the WebSocket. Called by public sendX commands.
* @param {string} type Message type. Currently supported types are
"event", "commandResponse", "commandError", "error"
* @param {object} message Message body, must be JSON.stringify-able
*/
Connection.prototype._send = function (type, message) {
if (this._ws && this._connected) {
try {
this._ws.send(JSON.stringify({type: type, message: message}));
} catch (e) {
this._logger.error("Unable to stringify message: " + e.message);
}
}
};
/**
* @private
* Sends a binary message over the WebSocket. Implicitly interpreted as a
* message of type "commandResponse".
* @param {Buffer} message
*/
Connection.prototype._sendBinary = function (message) {
if (this._ws && this._connected) {
this._ws.send(message, {binary: true, mask: false});
}
};
/**
* @private
* Receive event handler for the WebSocket. Responsible for parsing
* message and handing it off to the appropriate handler.
* @param {string} message Message received by WebSocket
*/
Connection.prototype._receive = function (message) {
try {
var m = JSON.parse(message);
if (m.id !== null && m.id !== undefined && m.domain && m.command) {
// okay if m.parameters is null/undefined
this.emit("command", m.id, m.domain, m.command, m.parameters);
} else {
this.sendError("Malformed message: " + message);
}
} catch (parseError) {
this.sendError("Unable to parse message: " + message);
}
};
/**
* Closes the connection and does necessary cleanup
*/
Connection.prototype.close = function () {
if (this._ws) {
try {
this._ws.close();
} catch (e) { }
}
this._connected = false;
this.emit("close");
};
/**
* Sends an Error message
* @param {object} message Error message. Must be JSON.stringify-able.
*/
Connection.prototype.sendError = function (message) {
this._send("error", {message: message});
};
/**
* Sends a response to a command execution
* @param {number} id unique ID of the command that was executed. ID is
* generated by the client when the command is issued.
* @param {object|Buffer} response Result of the command execution. Must
* either be JSON.stringify-able or a raw Buffer. In the latter case,
* the result will be sent as a binary response.
*/
Connection.prototype.sendCommandResponse = function (id, response) {
if (Buffer.isBuffer(response)) {
// Assume the id is an unsigned 32-bit integer, which is encoded
// as a four-byte header
var header = new Buffer(4);
header.writeUInt32LE(id, 0);
// Prepend the header to the message
var message = Buffer.concat([header, response], response.length + 4);
this._sendBinary(message);
} else {
this._send("commandResponse", {id: id, response: response });
}
};
/**
* Sends a progress notification to a command execution
* @param {number} id unique ID of the command that was executed. ID is
* generated by the client when the command is issued.
* @param {object} progress Result of the command progress. Must be
* JSON.stringify-able.
*/
Connection.prototype.sendCommandProgress = function (id, progress) {
this._send("commandProgress", {id: id, progress: progress });
};
/**
* Sends a response indicating that an error occurred during command
* execution
* @param {number} id unique ID of the command that was executed. ID is
* generated by the client when the command is issued.
* @param {string} message Error message
* @param {?object} stack Call stack from the exception, if possible. Must
* be JSON.stringify-able.
*/
Connection.prototype.sendCommandError = function (id, message, stack) {
this._send("commandError", {id: id, message: message, stack: stack});
};
/**
* Sends an event message
* @param {number} id unique ID for the event.
* @param {string} domain Domain of the event.
* @param {string} event Name of the event
* @param {object} parameters Event parameters. Must be JSON.stringify-able.
*/
Connection.prototype.sendEventMessage = function (id, domain, event, parameters) {
this._send("event", {
id: id,
domain: domain,
event: event,
parameters: parameters
});
};
module.exports = Connection;
}());