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