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