1 /** 2 * The Render Engine 3 * Socket 4 * 5 * @fileoverview A static class with helper methods for creating network sockets. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * @author: $Author: bfattori@gmail.com $ 9 * @version: $Revision: 1557 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 // The class this file defines and its required classes 34 R.Engine.define({ 35 "class":"R.util.SocketUtil", 36 "requires":[], 37 "includes":[ 38 "/../libs/socket.io.js" 39 ] 40 }); 41 42 /** 43 * @class A static class to create sockets for two-way network communication. This uses 44 * socket.IO as the socket provider to abstract the many different transport mechanisms. 45 * It is recommended that you see the example Node.JS server provided with <em>The Render Engine</em> 46 * to get an understanding of creating your own server. 47 * 48 * @static 49 */ 50 R.util.SocketUtil = /** @scope R.util.SocketUtil.prototype */{ 51 52 /** 53 * Socket connected message type 54 * @type {Number} 55 */ 56 MSG_CONNECT:1, 57 58 /** 59 * Socket disconnected message type 60 * @type {Number} 61 */ 62 MSG_DISCONNECT:2, 63 64 /** 65 * Socket acknowledges message received and handled type 66 * @type {Number} 67 */ 68 MSG_ACK:3, 69 70 /** 71 * Server broadcast message type 72 * @type {Number} 73 */ 74 MSG_SERVER:4, 75 76 /** 77 * Internal pool of sockets, organized by URL 78 * @private 79 */ 80 _socketPool:{}, 81 82 /** 83 * Create a socket for network communication. A socket is 84 * a self contained object which has methods for sending messages of 85 * various types. When you are finished with the socket, call its 86 * <code>done()</code> method to disconnect from the server and return 87 * the socket to the pool. 88 * <p/> 89 * As soon as you've created the socket, you can start sending messages. 90 * Messages will be queued until the socket is connected. A method 91 * called <tt>listener()</tt> can be attached to the socket so that 92 * messages arriving back from the socket can be handled. 93 * <p/> 94 * <pre> 95 * var socket = R.util.SocketUtil.createSocket('my.url.com/server/io.js'); 96 * socket.listener = function(type, message) { 97 * if (type == R.util.SocketUtil.MSG_CONNECT) { 98 * alert("connected!"); 99 * } 100 * }; 101 * socket.connect(); 102 * </pre> 103 * For information about using a socket, see {@link R.Socket} 104 * 105 * @param connectionURL {String} The URL to connect to 106 * @param [port] {Number} The port to connect to (default: 8090) 107 * @param [secure] {Boolean} <tt>true</tt> to use a secure connection (default: <tt>false</tt>) 108 * @return {R.Socket} A socket object 109 */ 110 createSocket:function (connectionURL, port, secure) { 111 112 // Message type for simple "send" with no acknowledgement 113 var TYPE_SEND = -1, 114 115 // Broadcast type message for all users. 116 TYPE_BROADCAST = -2; 117 118 port = typeof port == "number" ? port : undefined; 119 secure = typeof port == "boolean" ? (secure ? "1" : "0") : "0"; 120 121 // Normalize the socket url, port, and secure flag 122 var dest = connectionURL.split("/"); 123 dest = dest[dest.length - 1].replace(/[^\w]g/, "") + ":" + (port ? port.toString() : "0") + ":" + secure; 124 125 var pool = R.util.SocketUtil._socketPool[dest]; 126 if (!pool || pool.length == 0) { 127 128 if (!pool) { 129 // Create the pool 130 pool = R.util.SocketUtil._socketPool[dest] = []; 131 } 132 133 // Add the socket to the pool 134 pool.push(new R.Socket(dest, connectionURL)); 135 } 136 137 // Pop the first available socket from the pool and return it 138 return pool.pop(); 139 } 140 }; 141 142 /** 143 * @class A web socket for two-way network communication. You should not 144 * create an object from this class directly. Instead, see 145 * {@link R.util.SocketUtil#createSocket} 146 * 147 * @constructor 148 */ 149 R.Socket = function (/* ident, host */) { 150 this.id = arguments[0]; 151 var inf = this.id.split(":"); 152 this.socket = new io.Socket(arguments[1], { 153 secure:inf[2] == "1", 154 port:inf[1] == "0" ? undefined : Number(inf[1]), 155 // Really would like to avoid using Flash or ActiveX, if possible... 156 //transports: ['websocket','xhr-multipart','xhr-polling','jsonp-polling','htmlfile','flashsocket'] 157 transports:['websocket', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'], 158 rememberTransport:false 159 }); 160 this.packetNum = 1; 161 this.awaitingACK = []; 162 this.queued = []; 163 this.ready = false; 164 165 // Wire handlers into the socket 166 var self = this; 167 //-------------------------------------------------------------------------- 168 169 // Received a message from the server 170 this.socket.on('message', function (obj) { 171 if (obj.type == Socket.MSG_SERVER) { 172 // A server message, pass it along 173 self.notify(Socket.MSG_SERVER, obj.msg); 174 175 } else if (obj.type == Socket.MSG_ACK) { 176 // An acknowledgement of a previously assured message 177 var f = -1, ack = R.engine.Support.filter(this.awaitingACK, function (a, i) { 178 if (a.packetNum == obj.packetNum) { 179 f = i; 180 return true; 181 } 182 }); 183 184 // Acknowledged 185 if (f != -1) { 186 if ($.isFunction(ack[0].cb)) { 187 ack[0].cb(obj.packetNum); 188 } 189 190 // No longer waiting for ACK 191 self.awaitingACK.slice(i, 1); 192 } 193 } 194 }); 195 196 // Called when the socket creates a successful connectino 197 this.socket.on('connect', function () { 198 self.ready = true; 199 200 // Send any queued messages 201 while (this.queued.length > 0) { 202 // First in, first out 203 self.socket.send(this.queued.shift()); 204 } 205 self.notify(Socket.MSG_CONNECT, 'hello'); 206 }); 207 208 // Disconnected by the server 209 this.socket.on('disconnect', function () { 210 self.ready = false; 211 self.notify(Socket.MSG_DISCONNECT, 'goodbye'); 212 }); 213 214 }; 215 216 /** 217 * Attempt to connect the socket to its destination. Upon 218 * successful connection, the <tt>listener()</tt> method 219 * attached to the socket will be triggered. 220 */ 221 R.Socket.prototype.connect = function () { 222 this.socket.connect(); 223 }; 224 225 /** 226 * Internal method which tells the socket user that something has 227 * occurred. 228 * @param type {Number} The type of message 229 * @param message {String} A string message to pass along 230 * @private 231 */ 232 R.Socket.prototype.notify = function (type, message) { 233 if ($.isFunction(self.listener)) { 234 self.listener(type, message); 235 } 236 }; 237 238 /** 239 * Call this method to complete all communications with the socket 240 * and return it to the pool. 241 */ 242 R.Socket.prototype.done = function () { 243 // See if we're awaiting anything 244 if (this.awaitingACK.length > 0) { 245 // There are guaranteed messages which haven't ACKed yet 246 return false; 247 } 248 249 // Let the server know we're disconnecting (unless they 250 // disconnected us already) 251 if (this.ready) { 252 this.socket.send('goodbye'); 253 this.socket.disconnect(); 254 } 255 256 // All set, clean up and pool us 257 this.packetNum = 1; 258 this.awaitingACK.length = 0; 259 this.listener = undefined; 260 261 R.util.SocketUtil._socketPool[this.id].push(this); 262 }; 263 264 /** 265 * Queues the message if the socket isn't connected yet, 266 * otherwise, sends the message. 267 * @param msg {Object} The message to send 268 * @private 269 */ 270 R.Socket.prototype.queueOrSend = function (msg) { 271 // Queue messages if we're not yet connected 272 if (!this.ready) { 273 this.queued.push(msg); 274 } else { 275 this.socket.send(msg); 276 } 277 }; 278 279 /** 280 * Send a message without a guarantee that the message was 281 * received or handled by the server. 282 * @param msg {Object} The message to send 283 */ 284 R.Socket.prototype.send = function (msg) { 285 var wrap = { 286 ack:R.Socket.TYPE_SEND, 287 message:msg 288 }; 289 this.queueOrSend(wrap); 290 }; 291 292 /** 293 * Send a broadcast message to the server. Broadcast messages 294 * are sent to all connected clients and are not guaranteed. 295 * @param msg {Object} The message to send 296 */ 297 R.Socket.prototype.broadcast = function (msg) { 298 var wrap = { 299 ack:R.Socket.TYPE_BROADCAST, 300 message:msg 301 }; 302 this.queueOrSend(wrap); 303 }; 304 305 /** 306 * Sends a message with a guarantee that will be returned by 307 * the server when the message is received. 308 * 309 * @param msg {Object} The message to send 310 * @param [cb] {Function} The callback function, or <tt>null</tt>. 311 * @return {Number} The packet number 312 */ 313 R.Socket.prototype.assure = function (msg, cb) { 314 var wrap = { 315 ack:this.packetNum++, 316 message:msg 317 }; 318 this.await(wrap.ack, cb); 319 this.queueOrSend(wrap); 320 return wrap.ack; 321 }; 322 323 /* 324 Socket.prototype.orderedAssure = function(prev, msg, cb) { 325 var wrap = { 326 ack: this.packetNum++, 327 message: msg 328 }; 329 330 var curry = function(ack) { 331 if () 332 }; 333 334 this.await(wrap.ack, curry); 335 this.queueOrSend(wrap); 336 return wrap.ack; 337 }; 338 */ 339 340 /** 341 * Guaranteed messages awaiting acknowledgement register so their 342 * callback will be triggered. 343 * @param packetNum {Number} The packet number 344 * @param [cb] {Function} the callback 345 * @private 346 */ 347 R.Socket.prototype.await = function (packetNum, cb) { 348 this.awaitingACK.push({ 349 packet:packetNum, 350 trigger:cb 351 }); 352 }; 353 354 355 /** 356 * Socket connected message type 357 * @type {Number} 358 */ 359 R.Socket.MSG_CONNECT = R.util.SocketUtil.MSG_CONNECT; 360 361 /** 362 * Socket disconnected message type 363 * @type {Number} 364 */ 365 R.Socket.MSG_DISCONNECT = R.util.SocketUtil.MSG_DISCONNECT; 366 367 /** 368 * Socket acknowledges message received and handled type 369 * @type {Number} 370 */ 371 R.Socket.MSG_ACK = R.util.SocketUtil.MSG_ACK; 372 373 /** 374 * Server broadcast message type 375 * @type {Number} 376 */ 377 R.Socket.MSG_SERVER = R.util.SocketUtil.MSG_SERVER; 378 379 R.Socket.TYPE_SEND = -1; 380 381 R.Socket.TYPE_BROADCAST = -2; 382