1 var _ = require('underscore'),
  2     util = require('util');
  3 
  4 /**
  5  * Creates a new wireformat
  6  * @class
  7  * A protocol that supports all the features of the websocket server.
  8  * This wireformat knows about acking and the related protocol messages.
  9  * it uses a json object to transfer, meaning everything has a property name.
 10  */
 11 var WireFormat = function(options) {
 12   var opts = options||{};
 13   this.format = opts.protocol || 'json';
 14   this.name = opts.name || "simpleJson";
 15 }
 16 
 17 _.extend(WireFormat.prototype, /** @lends WireFormat.prototype */ {
 18   /**
 19    * Parses a message from a string, and detects which type of message it is.
 20    * It detects if the message is a json string, and then works out if the message is
 21    * a protocol message or a user message and builds the appropriate data structure.
 22    *
 23    * This method is used when a message is received from a remote party.
 24    * The method is also used when draining a buffer, to read the string entries in the buffer.
 25    *
 26    * @param {String} message The string representation of the message to parse.
 27    * @returns {Object} The message object with meta data attached.
 28    */
 29   parseMessage: function(message) {
 30     if (typeof message === 'object') return message;
 31     if (this.name === "jsonProtocol") {
 32       if (!this._canBeJson(message)) 
 33         return { type: "text", content: message };
 34       try {
 35         var prsd = JSON.parse(message);
 36         if (prsd.type === "ack" || prsd.type == "needs_ack") return prsd;
 37         if (prsd.type === "ack_request") 
 38           return _.extend(prsd, { type: prsd.type, content: this.parseMessage(prsd.content)});
 39         return _.extend({type: "json"}, { content: prsd }); 
 40       } catch (e) {
 41         return { type: "text", content: message };
 42       }
 43     } else {
 44       if (!this._canBeJson(message)) 
 45         return message;
 46       try {
 47         return JSON.parse(message);
 48       } catch (e) {
 49         return message;
 50       }
 51     }
 52   },
 53   /**
 54    * Render a message to a string for sending over the socket.
 55    *
 56    * @param {Object} message The message to serialize.
 57    * @returns {String} The string representation of the message to be sent.
 58    */
 59   renderMessage: function(message) {
 60     return typeof message === "string" ? message : JSON.stringify(this.buildMessage(message));
 61   },
 62   /**
 63    * Builds a message from a string or an object and wraps it in an envelope with meta data for the protocol.
 64    * 
 65    * @param {String|Object} message The message to send.
 66    * @returns {Object} The message wrapped and ready to send.
 67    */
 68   buildMessage: function(message) {
 69     if (this.name === "jsonProtocol") {
 70       if (typeof message === 'object') {
 71         var prsd = this.parseMessage(message)
 72         if (prsd.type === "ack_request") 
 73           prsd.content = this.parseMessage(prsd.content);
 74         var built = _.extend({type: "json"}, prsd);
 75         if (built.type === "json" && !built.content) return { type: "json", content: prsd};
 76         return built;
 77       } else {
 78         return { type: "text", content: message.toString() };
 79       }
 80     } else {
 81       if (typeof message === 'object') {
 82         return this.parseMessage(message)
 83       } else {
 84         return message.toString();
 85       }
 86     }
 87   },
 88   /**
 89    * Unwraps the message from the envelope, this is used before raising the data event on a hookup client.
 90    * 
 91    * @param {Object|String} message The message for which to get the content.
 92    * @returns {String|Object} The content of the message if any.
 93    */
 94   unwrapContent: function(message) {
 95     var parsed = this.parseMessage(message);
 96     if (this.name === "jsonProtocol") {
 97       if (parsed.type === "ack_request") 
 98         return parsed.content.content;
 99       if (parsed.type === "json") {
100         delete parsed['type'];
101         return Object.keys(parsed).length === 1 && parsed.content ? parsed.content : parsed;
102       }
103       if (parsed.type === "ack" || parsed.type === "needs_ack") return parsed;
104       return parsed.content;
105     } else {
106       return parsed;
107     }
108   },
109   _canBeJson: function(message) {
110     return !!message.match(/^(?:\{|\[)/);
111   }
112 });
113 
114 module.exports.WireFormat = WireFormat;
115 
116