├── .gitignore ├── README.md ├── api ├── adapters │ └── .gitkeep ├── controllers │ ├── .gitkeep │ └── UserController.js ├── models │ ├── .gitkeep │ └── User.js ├── policies │ └── authenticated.js └── services │ └── .gitkeep ├── app.js ├── assets ├── js │ └── .gitkeep ├── mixins │ ├── reset.css_hold │ └── sails.io.js ├── styles │ └── .gitkeep └── templates │ └── .gitkeep ├── config ├── adapters.js ├── application.js ├── assets.js ├── bootstrap.js ├── local.ex.js ├── locales │ └── english.js ├── policies.js ├── routes.js └── views.js ├── package.json ├── public ├── favicon.ico ├── images │ └── .gitkeep └── robots.txt └── views ├── 404.ejs ├── 500.ejs ├── home └── index.ejs ├── layout.ejs └── user ├── edit.ejs ├── index.ejs ├── new.ejs └── show.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | ######################## 2 | # sails 3 | ######################## 4 | .sails 5 | .waterline 6 | .rigging 7 | .tmp 8 | 9 | 10 | ######################## 11 | # node.js / npm 12 | ######################## 13 | lib-cov 14 | *.seed 15 | *.log 16 | *.csv 17 | *.dat 18 | *.out 19 | *.pid 20 | *.gz 21 | 22 | pids 23 | logs 24 | results 25 | 26 | npm-debug.log 27 | 28 | 29 | ######################## 30 | # misc / editors 31 | ######################## 32 | *~ 33 | *# 34 | .DS_STORE 35 | .netbeans 36 | nbproject 37 | .idea 38 | 39 | 40 | ######################## 41 | # local config 42 | ######################## 43 | config/local.js 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sails CRUD Demo 2 | --------------- 3 | 4 | This is a simple MVC CRUD app that goes with the screencasts. The screencasts are done from a Rails-centric perspective. 5 | 6 | # Introduction to Node and Sails.js - Part 1 7 | A 30,000' view of Node, Sails, and some differences from a Rails perspective. 8 | 9 | [![Imgur](http://i.imgur.com/QND5EGC.jpg)](http://www.youtube.com/watch?feature=player_detailpage&v=AcwlZQb-cmQ) 10 | 11 | # Introduction to Node and Sails.js - Part 2 12 | Installing Sails.js and creating your first controller and model. 13 | 14 | [![Imgur](http://i.imgur.com/AmO4JT9.jpg)](http://www.youtube.com/watch?feature=player_detailpage&v=60PaCpTP5L4) 15 | 16 | # Introduction to Node and Sails.js - Part 3 17 | A look at the index action and view. 18 | 19 | [![Imgur](http://i.imgur.com/GLRY1Qz.jpg)](http://www.youtube.com/watch?feature=player_detailpage&v=7XWIk9tb09Q) -------------------------------------------------------------------------------- /api/adapters/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irlnathan/sails-crud/72a71e49efb3c15ae90b524e86d6e5456753bbc3/api/adapters/.gitkeep -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irlnathan/sails-crud/72a71e49efb3c15ae90b524e86d6e5456753bbc3/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | /*--------------------- 2 | :: User 3 | -> controller 4 | ---------------------*/ 5 | var UserController = { 6 | 7 | index: function (req, res) { 8 | 9 | User.findAll(function(err, users){ 10 | if (err) return res.send(err, 500); 11 | 12 | res.view({ 13 | model: users 14 | }); 15 | }); 16 | }, 17 | 18 | 'new': function(req,res) { 19 | res.view(); 20 | }, 21 | 22 | create: function(req,res) { 23 | var params = _.extend(req.query || {}, req.params || {}, req.body || {}); 24 | 25 | User.create(params, function userCreated (err, createdUser) { 26 | 27 | if (err) return res.send(err,500); 28 | 29 | res.redirect('/user/show/'+ createdUser.id); 30 | }); 31 | }, 32 | 33 | show: function (req,res) { 34 | 35 | var id = req.param('id') 36 | 37 | if (!id) return res.send("No id specified.", 500); 38 | 39 | 40 | User.find(id, function userFound(err, user) { 41 | if(err) return res.sender(err,500); 42 | if(!user) return res.send("User "+id+" not found", 404); 43 | 44 | res.view({ 45 | user:user 46 | }) 47 | }); 48 | }, 49 | 50 | edit: function (req,res) { 51 | var id = req.param('id'); 52 | 53 | if (!id) return res.send("No id specified.",500); 54 | 55 | User.find(id, function userFound (err,user){ 56 | if (err) return res.send(err,500); 57 | if (!user) return res.send("User "+id+" not found.",404); 58 | 59 | res.view({ 60 | user: user 61 | }) 62 | }); 63 | }, 64 | 65 | 66 | update: function (req,res) { 67 | 68 | var params = _.extend(req.query || {}, req.params || {}, req.body || {}); 69 | var id = params.id; 70 | 71 | if (!id) return res.send("No id specified.",500); 72 | 73 | User.update(id, params, function userUpdated(err, updatedUser) { 74 | if (err) { 75 | res.redirect('/user/edit'); 76 | } 77 | if(!updatedUser) { 78 | res.redirect('/user/edit'); 79 | } 80 | res.redirect('/user/show/'+id); 81 | }); 82 | }, 83 | 84 | 85 | destroy: function (req,res) { 86 | var id = req.param('id'); 87 | if (!id) return res.send("No id specified.",500); 88 | 89 | User.find(id, function foundUser(err, user) { 90 | if (err) return res.send(err,500); 91 | if (!user) return res.send("No user with that idid exists.",404); 92 | 93 | User.destroy(id, function userDestroyed(err) { 94 | if (err) return res.send(err,500); 95 | 96 | return res.redirect('/user'); 97 | }); 98 | 99 | }) 100 | } 101 | 102 | 103 | }; 104 | module.exports = UserController; -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irlnathan/sails-crud/72a71e49efb3c15ae90b524e86d6e5456753bbc3/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | /*--------------------- 2 | :: User 3 | -> model 4 | ---------------------*/ 5 | module.exports = { 6 | 7 | attributes : { 8 | 9 | // Simple attribute: 10 | // name: 'STRING', 11 | 12 | // Or for more flexibility: 13 | // phoneNumber: { 14 | // type: 'STRING', 15 | // defaultValue: '555-555-5555' 16 | // } 17 | 18 | name: { 19 | type: 'STRING' 20 | }, 21 | 22 | 23 | email: { 24 | type: 'STRING' 25 | } 26 | } 27 | 28 | }; -------------------------------------------------------------------------------- /api/policies/authenticated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Allow any authenticated user. 3 | */ 4 | module.exports = function (req,res,ok) { 5 | 6 | // User is allowed, proceed to controller 7 | if (req.session.authenticated) { 8 | return ok(); 9 | } 10 | 11 | // User is not allowed 12 | else { 13 | return res.send("You are not permitted to perform this action.",403); 14 | } 15 | }; -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irlnathan/sails-crud/72a71e49efb3c15ae90b524e86d6e5456753bbc3/api/services/.gitkeep -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('sails').lift(); 2 | -------------------------------------------------------------------------------- /assets/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irlnathan/sails-crud/72a71e49efb3c15ae90b524e86d6e5456753bbc3/assets/js/.gitkeep -------------------------------------------------------------------------------- /assets/mixins/reset.css_hold: -------------------------------------------------------------------------------- 1 | /* 2 | * The purpose of this CSS reset file is to a provide a sane starting place for stylesheet development by 3 | * normalizing browser differences, eliminating margin and padding, and providing a clear-fix. 4 | */ 5 | html, body {text-align:left;font-size: 1em;} 6 | html,body,img,form,textarea,input,fieldset,div,p,div,ul,li,ol,dl,dt,dd,h1,h2,h3,h4,h5,h6,pre,code { margin: 0;padding: 0;} 7 | ul,li {list-style: none;} 8 | img {display: block;} 9 | a img{border:none;} 10 | a {text-decoration:none;font-weight: normal;font-family: inherit;} 11 | *:active,*:focus { /* Clear mozilla/ie dotted line around active links */ 12 | outline: none; 13 | -moz-outline-style: none; 14 | } 15 | h1,h2,h3,h4,h5,h6,h7 {font-weight: normal; font-size:1em;} /* Fix heading font styles and size */ 16 | div.clear {clear:both;} -------------------------------------------------------------------------------- /assets/mixins/sails.io.js: -------------------------------------------------------------------------------- 1 | /*! Socket.IO.js build:0.9.6, development. Copyright(c) 2011 LearnBoost MIT Licensed */ 2 | /* With Additional functionality for the Sails.js framework, Copyright(c) 2012 Mike McNeil MIT Licensed */ 3 | 4 | /** 5 | * socket.io 6 | * Copyright(c) 2011 LearnBoost 7 | * MIT Licensed 8 | */ 9 | 10 | (function (exports, global) { 11 | 12 | /** 13 | * IO namespace. 14 | * 15 | * @namespace 16 | */ 17 | 18 | var io = exports; 19 | 20 | /** 21 | * Socket.IO version 22 | * 23 | * @api public 24 | */ 25 | 26 | io.version = '0.9.6'; 27 | 28 | /** 29 | * Protocol implemented. 30 | * 31 | * @api public 32 | */ 33 | 34 | io.protocol = 1; 35 | 36 | /** 37 | * Available transports, these will be populated with the available transports 38 | * 39 | * @api public 40 | */ 41 | 42 | io.transports = []; 43 | 44 | /** 45 | * Keep track of jsonp callbacks. 46 | * 47 | * @api private 48 | */ 49 | 50 | io.j = []; 51 | 52 | /** 53 | * Keep track of our io.Sockets 54 | * 55 | * @api private 56 | */ 57 | io.sockets = {}; 58 | 59 | 60 | /** 61 | * Manages connections to hosts. 62 | * 63 | * @param {String} uri 64 | * @Param {Boolean} force creation of new socket (defaults to false) 65 | * @api public 66 | */ 67 | 68 | io.connect = function (host, details) { 69 | var uri = io.util.parseUri(host) 70 | , uuri 71 | , socket; 72 | 73 | if (global && global.location) { 74 | uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); 75 | uri.host = uri.host || (global.document 76 | ? global.document.domain : global.location.hostname); 77 | uri.port = uri.port || global.location.port; 78 | } 79 | 80 | uuri = io.util.uniqueUri(uri); 81 | 82 | var options = { 83 | host: uri.host 84 | , secure: 'https' == uri.protocol 85 | , port: uri.port || ('https' == uri.protocol ? 443 : 80) 86 | , query: uri.query || '' 87 | }; 88 | 89 | io.util.merge(options, details); 90 | 91 | if (options['force new connection'] || !io.sockets[uuri]) { 92 | socket = new io.Socket(options); 93 | } 94 | 95 | if (!options['force new connection'] && socket) { 96 | io.sockets[uuri] = socket; 97 | } 98 | 99 | socket = socket || io.sockets[uuri]; 100 | 101 | // if path is different from '' or / 102 | return socket.of(uri.path.length > 1 ? uri.path : ''); 103 | }; 104 | 105 | })('object' === typeof module ? module.exports : (this.io = {}), this); 106 | /** 107 | * socket.io 108 | * Copyright(c) 2011 LearnBoost 109 | * MIT Licensed 110 | */ 111 | 112 | (function (exports, global) { 113 | 114 | /** 115 | * Utilities namespace. 116 | * 117 | * @namespace 118 | */ 119 | 120 | var util = exports.util = {}; 121 | 122 | /** 123 | * Parses an URI 124 | * 125 | * @author Steven Levithan (MIT license) 126 | * @api public 127 | */ 128 | 129 | var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; 130 | 131 | var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 132 | 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 133 | 'anchor']; 134 | 135 | util.parseUri = function (str) { 136 | var m = re.exec(str || '') 137 | , uri = {} 138 | , i = 14; 139 | 140 | while (i--) { 141 | uri[parts[i]] = m[i] || ''; 142 | } 143 | 144 | return uri; 145 | }; 146 | 147 | /** 148 | * Produces a unique url that identifies a Socket.IO connection. 149 | * 150 | * @param {Object} uri 151 | * @api public 152 | */ 153 | 154 | util.uniqueUri = function (uri) { 155 | var protocol = uri.protocol 156 | , host = uri.host 157 | , port = uri.port; 158 | 159 | if ('document' in global) { 160 | host = host || document.domain; 161 | port = port || (protocol == 'https' 162 | && document.location.protocol !== 'https:' ? 443 : document.location.port); 163 | } else { 164 | host = host || 'localhost'; 165 | 166 | if (!port && protocol == 'https') { 167 | port = 443; 168 | } 169 | } 170 | 171 | return (protocol || 'http') + '://' + host + ':' + (port || 80); 172 | }; 173 | 174 | /** 175 | * Mergest 2 query strings in to once unique query string 176 | * 177 | * @param {String} base 178 | * @param {String} addition 179 | * @api public 180 | */ 181 | 182 | util.query = function (base, addition) { 183 | var query = util.chunkQuery(base || '') 184 | , components = []; 185 | 186 | util.merge(query, util.chunkQuery(addition || '')); 187 | for (var part in query) { 188 | if (query.hasOwnProperty(part)) { 189 | components.push(part + '=' + query[part]); 190 | } 191 | } 192 | 193 | return components.length ? '?' + components.join('&') : ''; 194 | }; 195 | 196 | /** 197 | * Transforms a querystring in to an object 198 | * 199 | * @param {String} qs 200 | * @api public 201 | */ 202 | 203 | util.chunkQuery = function (qs) { 204 | var query = {} 205 | , params = qs.split('&') 206 | , i = 0 207 | , l = params.length 208 | , kv; 209 | 210 | for (; i < l; ++i) { 211 | kv = params[i].split('='); 212 | if (kv[0]) { 213 | query[kv[0]] = kv[1]; 214 | } 215 | } 216 | 217 | return query; 218 | }; 219 | 220 | /** 221 | * Executes the given function when the page is loaded. 222 | * 223 | * io.util.load(function () { console.log('page loaded'); }); 224 | * 225 | * @param {Function} fn 226 | * @api public 227 | */ 228 | 229 | var pageLoaded = false; 230 | 231 | util.load = function (fn) { 232 | if ('document' in global && document.readyState === 'complete' || pageLoaded) { 233 | return fn(); 234 | } 235 | 236 | util.on(global, 'load', fn, false); 237 | }; 238 | 239 | /** 240 | * Adds an event. 241 | * 242 | * @api private 243 | */ 244 | 245 | util.on = function (element, event, fn, capture) { 246 | if (element.attachEvent) { 247 | element.attachEvent('on' + event, fn); 248 | } else if (element.addEventListener) { 249 | element.addEventListener(event, fn, capture); 250 | } 251 | }; 252 | 253 | /** 254 | * Generates the correct `XMLHttpRequest` for regular and cross domain requests. 255 | * 256 | * @param {Boolean} [xdomain] Create a request that can be used cross domain. 257 | * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. 258 | * @api private 259 | */ 260 | 261 | util.request = function (xdomain) { 262 | 263 | if (xdomain && 'undefined' != typeof XDomainRequest) { 264 | return new XDomainRequest(); 265 | } 266 | 267 | if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { 268 | return new XMLHttpRequest(); 269 | } 270 | 271 | if (!xdomain) { 272 | try { 273 | return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); 274 | } catch(e) { } 275 | } 276 | 277 | return null; 278 | }; 279 | 280 | /** 281 | * XHR based transport constructor. 282 | * 283 | * @constructor 284 | * @api public 285 | */ 286 | 287 | /** 288 | * Change the internal pageLoaded value. 289 | */ 290 | 291 | if ('undefined' != typeof window) { 292 | util.load(function () { 293 | pageLoaded = true; 294 | }); 295 | } 296 | 297 | /** 298 | * Defers a function to ensure a spinner is not displayed by the browser 299 | * 300 | * @param {Function} fn 301 | * @api public 302 | */ 303 | 304 | util.defer = function (fn) { 305 | if (!util.ua.webkit || 'undefined' != typeof importScripts) { 306 | return fn(); 307 | } 308 | 309 | util.load(function () { 310 | setTimeout(fn, 100); 311 | }); 312 | }; 313 | 314 | /** 315 | * Merges two objects. 316 | * 317 | * @api public 318 | */ 319 | 320 | util.merge = function merge (target, additional, deep, lastseen) { 321 | var seen = lastseen || [] 322 | , depth = typeof deep == 'undefined' ? 2 : deep 323 | , prop; 324 | 325 | for (prop in additional) { 326 | if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { 327 | if (typeof target[prop] !== 'object' || !depth) { 328 | target[prop] = additional[prop]; 329 | seen.push(additional[prop]); 330 | } else { 331 | util.merge(target[prop], additional[prop], depth - 1, seen); 332 | } 333 | } 334 | } 335 | 336 | return target; 337 | }; 338 | 339 | /** 340 | * Merges prototypes from objects 341 | * 342 | * @api public 343 | */ 344 | 345 | util.mixin = function (ctor, ctor2) { 346 | util.merge(ctor.prototype, ctor2.prototype); 347 | }; 348 | 349 | /** 350 | * Shortcut for prototypical and static inheritance. 351 | * 352 | * @api private 353 | */ 354 | 355 | util.inherit = function (ctor, ctor2) { 356 | function f() {}; 357 | f.prototype = ctor2.prototype; 358 | ctor.prototype = new f; 359 | }; 360 | 361 | /** 362 | * Checks if the given object is an Array. 363 | * 364 | * io.util.isArray([]); // true 365 | * io.util.isArray({}); // false 366 | * 367 | * @param Object obj 368 | * @api public 369 | */ 370 | 371 | util.isArray = Array.isArray || function (obj) { 372 | return Object.prototype.toString.call(obj) === '[object Array]'; 373 | }; 374 | 375 | /** 376 | * Intersects values of two arrays into a third 377 | * 378 | * @api public 379 | */ 380 | 381 | util.intersect = function (arr, arr2) { 382 | var ret = [] 383 | , longest = arr.length > arr2.length ? arr : arr2 384 | , shortest = arr.length > arr2.length ? arr2 : arr; 385 | 386 | for (var i = 0, l = shortest.length; i < l; i++) { 387 | if (~util.indexOf(longest, shortest[i])) 388 | ret.push(shortest[i]); 389 | } 390 | 391 | return ret; 392 | } 393 | 394 | /** 395 | * Array indexOf compatibility. 396 | * 397 | * @see bit.ly/a5Dxa2 398 | * @api public 399 | */ 400 | 401 | util.indexOf = function (arr, o, i) { 402 | 403 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; 404 | i < j && arr[i] !== o; i++) {} 405 | 406 | return j <= i ? -1 : i; 407 | }; 408 | 409 | /** 410 | * Converts enumerables to array. 411 | * 412 | * @api public 413 | */ 414 | 415 | util.toArray = function (enu) { 416 | var arr = []; 417 | 418 | for (var i = 0, l = enu.length; i < l; i++) 419 | arr.push(enu[i]); 420 | 421 | return arr; 422 | }; 423 | 424 | /** 425 | * UA / engines detection namespace. 426 | * 427 | * @namespace 428 | */ 429 | 430 | util.ua = {}; 431 | 432 | /** 433 | * Whether the UA supports CORS for XHR. 434 | * 435 | * @api public 436 | */ 437 | 438 | util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { 439 | try { 440 | var a = new XMLHttpRequest(); 441 | } catch (e) { 442 | return false; 443 | } 444 | 445 | return a.withCredentials != undefined; 446 | })(); 447 | 448 | /** 449 | * Detect webkit. 450 | * 451 | * @api public 452 | */ 453 | 454 | util.ua.webkit = 'undefined' != typeof navigator 455 | && /webkit/i.test(navigator.userAgent); 456 | 457 | })('undefined' != typeof io ? io : module.exports, this); 458 | 459 | /** 460 | * socket.io 461 | * Copyright(c) 2011 LearnBoost 462 | * MIT Licensed 463 | */ 464 | 465 | (function (exports, io) { 466 | 467 | /** 468 | * Expose constructor. 469 | */ 470 | 471 | exports.EventEmitter = EventEmitter; 472 | 473 | /** 474 | * Event emitter constructor. 475 | * 476 | * @api public. 477 | */ 478 | 479 | function EventEmitter () {}; 480 | 481 | /** 482 | * Adds a listener 483 | * 484 | * @api public 485 | */ 486 | 487 | EventEmitter.prototype.on = function (name, fn) { 488 | if (!this.$events) { 489 | this.$events = {}; 490 | } 491 | 492 | if (!this.$events[name]) { 493 | this.$events[name] = fn; 494 | } else if (io.util.isArray(this.$events[name])) { 495 | this.$events[name].push(fn); 496 | } else { 497 | this.$events[name] = [this.$events[name], fn]; 498 | } 499 | 500 | return this; 501 | }; 502 | 503 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 504 | 505 | /** 506 | * Adds a volatile listener. 507 | * 508 | * @api public 509 | */ 510 | 511 | EventEmitter.prototype.once = function (name, fn) { 512 | var self = this; 513 | 514 | function on () { 515 | self.removeListener(name, on); 516 | fn.apply(this, arguments); 517 | }; 518 | 519 | on.listener = fn; 520 | this.on(name, on); 521 | 522 | return this; 523 | }; 524 | 525 | /** 526 | * Removes a listener. 527 | * 528 | * @api public 529 | */ 530 | 531 | EventEmitter.prototype.removeListener = function (name, fn) { 532 | if (this.$events && this.$events[name]) { 533 | var list = this.$events[name]; 534 | 535 | if (io.util.isArray(list)) { 536 | var pos = -1; 537 | 538 | for (var i = 0, l = list.length; i < l; i++) { 539 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 540 | pos = i; 541 | break; 542 | } 543 | } 544 | 545 | if (pos < 0) { 546 | return this; 547 | } 548 | 549 | list.splice(pos, 1); 550 | 551 | if (!list.length) { 552 | delete this.$events[name]; 553 | } 554 | } else if (list === fn || (list.listener && list.listener === fn)) { 555 | delete this.$events[name]; 556 | } 557 | } 558 | 559 | return this; 560 | }; 561 | 562 | /** 563 | * Removes all listeners for an event. 564 | * 565 | * @api public 566 | */ 567 | 568 | EventEmitter.prototype.removeAllListeners = function (name) { 569 | // TODO: enable this when node 0.5 is stable 570 | //if (name === undefined) { 571 | //this.$events = {}; 572 | //return this; 573 | //} 574 | 575 | if (this.$events && this.$events[name]) { 576 | this.$events[name] = null; 577 | } 578 | 579 | return this; 580 | }; 581 | 582 | /** 583 | * Gets all listeners for a certain event. 584 | * 585 | * @api publci 586 | */ 587 | 588 | EventEmitter.prototype.listeners = function (name) { 589 | if (!this.$events) { 590 | this.$events = {}; 591 | } 592 | 593 | if (!this.$events[name]) { 594 | this.$events[name] = []; 595 | } 596 | 597 | if (!io.util.isArray(this.$events[name])) { 598 | this.$events[name] = [this.$events[name]]; 599 | } 600 | 601 | return this.$events[name]; 602 | }; 603 | 604 | /** 605 | * Emits an event. 606 | * 607 | * @api public 608 | */ 609 | 610 | EventEmitter.prototype.emit = function (name) { 611 | if (!this.$events) { 612 | return false; 613 | } 614 | 615 | var handler = this.$events[name]; 616 | 617 | if (!handler) { 618 | return false; 619 | } 620 | 621 | var args = Array.prototype.slice.call(arguments, 1); 622 | 623 | if ('function' == typeof handler) { 624 | handler.apply(this, args); 625 | } else if (io.util.isArray(handler)) { 626 | var listeners = handler.slice(); 627 | 628 | for (var i = 0, l = listeners.length; i < l; i++) { 629 | listeners[i].apply(this, args); 630 | } 631 | } else { 632 | return false; 633 | } 634 | 635 | return true; 636 | }; 637 | 638 | })( 639 | 'undefined' != typeof io ? io : module.exports 640 | , 'undefined' != typeof io ? io : module.parent.exports 641 | ); 642 | 643 | /** 644 | * socket.io 645 | * Copyright(c) 2011 LearnBoost 646 | * MIT Licensed 647 | */ 648 | 649 | /** 650 | * Based on JSON2 (http://www.JSON.org/js.html). 651 | */ 652 | 653 | (function (exports, nativeJSON) { 654 | "use strict"; 655 | 656 | // use native JSON if it's available 657 | if (nativeJSON && nativeJSON.parse){ 658 | return exports.JSON = { 659 | parse: nativeJSON.parse 660 | , stringify: nativeJSON.stringify 661 | } 662 | } 663 | 664 | var JSON = exports.JSON = {}; 665 | 666 | function f(n) { 667 | // Format integers to have at least two digits. 668 | return n < 10 ? '0' + n : n; 669 | } 670 | 671 | function date(d, key) { 672 | return isFinite(d.valueOf()) ? 673 | d.getUTCFullYear() + '-' + 674 | f(d.getUTCMonth() + 1) + '-' + 675 | f(d.getUTCDate()) + 'T' + 676 | f(d.getUTCHours()) + ':' + 677 | f(d.getUTCMinutes()) + ':' + 678 | f(d.getUTCSeconds()) + 'Z' : null; 679 | }; 680 | 681 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 682 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 683 | gap, 684 | indent, 685 | meta = { // table of character substitutions 686 | '\b': '\\b', 687 | '\t': '\\t', 688 | '\n': '\\n', 689 | '\f': '\\f', 690 | '\r': '\\r', 691 | '"' : '\\"', 692 | '\\': '\\\\' 693 | }, 694 | rep; 695 | 696 | 697 | function quote(string) { 698 | 699 | // If the string contains no control characters, no quote characters, and no 700 | // backslash characters, then we can safely slap some quotes around it. 701 | // Otherwise we must also replace the offending characters with safe escape 702 | // sequences. 703 | 704 | escapable.lastIndex = 0; 705 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 706 | var c = meta[a]; 707 | return typeof c === 'string' ? c : 708 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 709 | }) + '"' : '"' + string + '"'; 710 | } 711 | 712 | 713 | function str(key, holder) { 714 | 715 | // Produce a string from holder[key]. 716 | 717 | var i, // The loop counter. 718 | k, // The member key. 719 | v, // The member value. 720 | length, 721 | mind = gap, 722 | partial, 723 | value = holder[key]; 724 | 725 | // If the value has a toJSON method, call it to obtain a replacement value. 726 | 727 | if (value instanceof Date) { 728 | value = date(key); 729 | } 730 | 731 | // If we were called with a replacer function, then call the replacer to 732 | // obtain a replacement value. 733 | 734 | if (typeof rep === 'function') { 735 | value = rep.call(holder, key, value); 736 | } 737 | 738 | // What happens next depends on the value's type. 739 | 740 | switch (typeof value) { 741 | case 'string': 742 | return quote(value); 743 | 744 | case 'number': 745 | 746 | // JSON numbers must be finite. Encode non-finite numbers as null. 747 | 748 | return isFinite(value) ? String(value) : 'null'; 749 | 750 | case 'boolean': 751 | case 'null': 752 | 753 | // If the value is a boolean or null, convert it to a string. Note: 754 | // typeof null does not produce 'null'. The case is included here in 755 | // the remote chance that this gets fixed someday. 756 | 757 | return String(value); 758 | 759 | // If the type is 'object', we might be dealing with an object or an array or 760 | // null. 761 | 762 | case 'object': 763 | 764 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 765 | // so watch out for that case. 766 | 767 | if (!value) { 768 | return 'null'; 769 | } 770 | 771 | // Make an array to hold the partial results of stringifying this object value. 772 | 773 | gap += indent; 774 | partial = []; 775 | 776 | // Is the value an array? 777 | 778 | if (Object.prototype.toString.apply(value) === '[object Array]') { 779 | 780 | // The value is an array. Stringify every element. Use null as a placeholder 781 | // for non-JSON values. 782 | 783 | length = value.length; 784 | for (i = 0; i < length; i += 1) { 785 | partial[i] = str(i, value) || 'null'; 786 | } 787 | 788 | // Join all of the elements together, separated with commas, and wrap them in 789 | // brackets. 790 | 791 | v = partial.length === 0 ? '[]' : gap ? 792 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 793 | '[' + partial.join(',') + ']'; 794 | gap = mind; 795 | return v; 796 | } 797 | 798 | // If the replacer is an array, use it to select the members to be stringified. 799 | 800 | if (rep && typeof rep === 'object') { 801 | length = rep.length; 802 | for (i = 0; i < length; i += 1) { 803 | if (typeof rep[i] === 'string') { 804 | k = rep[i]; 805 | v = str(k, value); 806 | if (v) { 807 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 808 | } 809 | } 810 | } 811 | } else { 812 | 813 | // Otherwise, iterate through all of the keys in the object. 814 | 815 | for (k in value) { 816 | if (Object.prototype.hasOwnProperty.call(value, k)) { 817 | v = str(k, value); 818 | if (v) { 819 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 820 | } 821 | } 822 | } 823 | } 824 | 825 | // Join all of the member texts together, separated with commas, 826 | // and wrap them in braces. 827 | 828 | v = partial.length === 0 ? '{}' : gap ? 829 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 830 | '{' + partial.join(',') + '}'; 831 | gap = mind; 832 | return v; 833 | } 834 | } 835 | 836 | // If the JSON object does not yet have a stringify method, give it one. 837 | 838 | JSON.stringify = function (value, replacer, space) { 839 | 840 | // The stringify method takes a value and an optional replacer, and an optional 841 | // space parameter, and returns a JSON text. The replacer can be a function 842 | // that can replace values, or an array of strings that will select the keys. 843 | // A default replacer method can be provided. Use of the space parameter can 844 | // produce text that is more easily readable. 845 | 846 | var i; 847 | gap = ''; 848 | indent = ''; 849 | 850 | // If the space parameter is a number, make an indent string containing that 851 | // many spaces. 852 | 853 | if (typeof space === 'number') { 854 | for (i = 0; i < space; i += 1) { 855 | indent += ' '; 856 | } 857 | 858 | // If the space parameter is a string, it will be used as the indent string. 859 | 860 | } else if (typeof space === 'string') { 861 | indent = space; 862 | } 863 | 864 | // If there is a replacer, it must be a function or an array. 865 | // Otherwise, throw an error. 866 | 867 | rep = replacer; 868 | if (replacer && typeof replacer !== 'function' && 869 | (typeof replacer !== 'object' || 870 | typeof replacer.length !== 'number')) { 871 | throw new Error('JSON.stringify'); 872 | } 873 | 874 | // Make a fake root object containing our value under the key of ''. 875 | // Return the result of stringifying the value. 876 | 877 | return str('', {'': value}); 878 | }; 879 | 880 | // If the JSON object does not yet have a parse method, give it one. 881 | 882 | JSON.parse = function (text, reviver) { 883 | // The parse method takes a text and an optional reviver function, and returns 884 | // a JavaScript value if the text is a valid JSON text. 885 | 886 | var j; 887 | 888 | function walk(holder, key) { 889 | 890 | // The walk method is used to recursively walk the resulting structure so 891 | // that modifications can be made. 892 | 893 | var k, v, value = holder[key]; 894 | if (value && typeof value === 'object') { 895 | for (k in value) { 896 | if (Object.prototype.hasOwnProperty.call(value, k)) { 897 | v = walk(value, k); 898 | if (v !== undefined) { 899 | value[k] = v; 900 | } else { 901 | delete value[k]; 902 | } 903 | } 904 | } 905 | } 906 | return reviver.call(holder, key, value); 907 | } 908 | 909 | 910 | // Parsing happens in four stages. In the first stage, we replace certain 911 | // Unicode characters with escape sequences. JavaScript handles many characters 912 | // incorrectly, either silently deleting them, or treating them as line endings. 913 | 914 | text = String(text); 915 | cx.lastIndex = 0; 916 | if (cx.test(text)) { 917 | text = text.replace(cx, function (a) { 918 | return '\\u' + 919 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 920 | }); 921 | } 922 | 923 | // In the second stage, we run the text against regular expressions that look 924 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 925 | // because they can cause invocation, and '=' because it can cause mutation. 926 | // But just to be safe, we want to reject all unexpected forms. 927 | 928 | // We split the second stage into 4 regexp operations in order to work around 929 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 930 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 931 | // replace all simple value tokens with ']' characters. Third, we delete all 932 | // open brackets that follow a colon or comma or that begin the text. Finally, 933 | // we look to see that the remaining characters are only whitespace or ']' or 934 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 935 | 936 | if (/^[\],:{}\s]*$/ 937 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 938 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 939 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 940 | 941 | // In the third stage we use the eval function to compile the text into a 942 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 943 | // in JavaScript: it can begin a block or an object literal. We wrap the text 944 | // in parens to eliminate the ambiguity. 945 | 946 | j = eval('(' + text + ')'); 947 | 948 | // In the optional fourth stage, we recursively walk the new structure, passing 949 | // each name/value pair to a reviver function for possible transformation. 950 | 951 | return typeof reviver === 'function' ? 952 | walk({'': j}, '') : j; 953 | } 954 | 955 | // If the text is not JSON parseable, then a SyntaxError is thrown. 956 | 957 | throw new SyntaxError('JSON.parse'); 958 | }; 959 | 960 | })( 961 | 'undefined' != typeof io ? io : module.exports 962 | , typeof JSON !== 'undefined' ? JSON : undefined 963 | ); 964 | 965 | /** 966 | * socket.io 967 | * Copyright(c) 2011 LearnBoost 968 | * MIT Licensed 969 | */ 970 | 971 | (function (exports, io) { 972 | 973 | /** 974 | * Parser namespace. 975 | * 976 | * @namespace 977 | */ 978 | 979 | var parser = exports.parser = {}; 980 | 981 | /** 982 | * Packet types. 983 | */ 984 | 985 | var packets = parser.packets = [ 986 | 'disconnect' 987 | , 'connect' 988 | , 'heartbeat' 989 | , 'message' 990 | , 'json' 991 | , 'event' 992 | , 'ack' 993 | , 'error' 994 | , 'noop' 995 | ]; 996 | 997 | /** 998 | * Errors reasons. 999 | */ 1000 | 1001 | var reasons = parser.reasons = [ 1002 | 'transport not supported' 1003 | , 'client not handshaken' 1004 | , 'unauthorized' 1005 | ]; 1006 | 1007 | /** 1008 | * Errors advice. 1009 | */ 1010 | 1011 | var advice = parser.advice = [ 1012 | 'reconnect' 1013 | ]; 1014 | 1015 | /** 1016 | * Shortcuts. 1017 | */ 1018 | 1019 | var JSON = io.JSON 1020 | , indexOf = io.util.indexOf; 1021 | 1022 | /** 1023 | * Encodes a packet. 1024 | * 1025 | * @api private 1026 | */ 1027 | 1028 | parser.encodePacket = function (packet) { 1029 | var type = indexOf(packets, packet.type) 1030 | , id = packet.id || '' 1031 | , endpoint = packet.endpoint || '' 1032 | , ack = packet.ack 1033 | , data = null; 1034 | 1035 | switch (packet.type) { 1036 | case 'error': 1037 | var reason = packet.reason ? indexOf(reasons, packet.reason) : '' 1038 | , adv = packet.advice ? indexOf(advice, packet.advice) : ''; 1039 | 1040 | if (reason !== '' || adv !== '') 1041 | data = reason + (adv !== '' ? ('+' + adv) : ''); 1042 | 1043 | break; 1044 | 1045 | case 'message': 1046 | if (packet.data !== '') 1047 | data = packet.data; 1048 | break; 1049 | 1050 | case 'event': 1051 | var ev = { name: packet.name }; 1052 | 1053 | if (packet.args && packet.args.length) { 1054 | ev.args = packet.args; 1055 | } 1056 | 1057 | data = JSON.stringify(ev); 1058 | break; 1059 | 1060 | case 'json': 1061 | data = JSON.stringify(packet.data); 1062 | break; 1063 | 1064 | case 'connect': 1065 | if (packet.qs) 1066 | data = packet.qs; 1067 | break; 1068 | 1069 | case 'ack': 1070 | data = packet.ackId 1071 | + (packet.args && packet.args.length 1072 | ? '+' + JSON.stringify(packet.args) : ''); 1073 | break; 1074 | } 1075 | 1076 | // construct packet with required fragments 1077 | var encoded = [ 1078 | type 1079 | , id + (ack == 'data' ? '+' : '') 1080 | , endpoint 1081 | ]; 1082 | 1083 | // data fragment is optional 1084 | if (data !== null && data !== undefined) 1085 | encoded.push(data); 1086 | 1087 | return encoded.join(':'); 1088 | }; 1089 | 1090 | /** 1091 | * Encodes multiple messages (payload). 1092 | * 1093 | * @param {Array} messages 1094 | * @api private 1095 | */ 1096 | 1097 | parser.encodePayload = function (packets) { 1098 | var decoded = ''; 1099 | 1100 | if (packets.length == 1) 1101 | return packets[0]; 1102 | 1103 | for (var i = 0, l = packets.length; i < l; i++) { 1104 | var packet = packets[i]; 1105 | decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; 1106 | } 1107 | 1108 | return decoded; 1109 | }; 1110 | 1111 | /** 1112 | * Decodes a packet 1113 | * 1114 | * @api private 1115 | */ 1116 | 1117 | var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; 1118 | 1119 | parser.decodePacket = function (data) { 1120 | var pieces = data.match(regexp); 1121 | 1122 | if (!pieces) return {}; 1123 | 1124 | var id = pieces[2] || '' 1125 | , data = pieces[5] || '' 1126 | , packet = { 1127 | type: packets[pieces[1]] 1128 | , endpoint: pieces[4] || '' 1129 | }; 1130 | 1131 | // whether we need to acknowledge the packet 1132 | if (id) { 1133 | packet.id = id; 1134 | if (pieces[3]) 1135 | packet.ack = 'data'; 1136 | else 1137 | packet.ack = true; 1138 | } 1139 | 1140 | // handle different packet types 1141 | switch (packet.type) { 1142 | case 'error': 1143 | var pieces = data.split('+'); 1144 | packet.reason = reasons[pieces[0]] || ''; 1145 | packet.advice = advice[pieces[1]] || ''; 1146 | break; 1147 | 1148 | case 'message': 1149 | packet.data = data || ''; 1150 | break; 1151 | 1152 | case 'event': 1153 | try { 1154 | var opts = JSON.parse(data); 1155 | packet.name = opts.name; 1156 | packet.args = opts.args; 1157 | } catch (e) { } 1158 | 1159 | packet.args = packet.args || []; 1160 | break; 1161 | 1162 | case 'json': 1163 | try { 1164 | packet.data = JSON.parse(data); 1165 | } catch (e) { } 1166 | break; 1167 | 1168 | case 'connect': 1169 | packet.qs = data || ''; 1170 | break; 1171 | 1172 | case 'ack': 1173 | var pieces = data.match(/^([0-9]+)(\+)?(.*)/); 1174 | if (pieces) { 1175 | packet.ackId = pieces[1]; 1176 | packet.args = []; 1177 | 1178 | if (pieces[3]) { 1179 | try { 1180 | packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; 1181 | } catch (e) { } 1182 | } 1183 | } 1184 | break; 1185 | 1186 | case 'disconnect': 1187 | case 'heartbeat': 1188 | break; 1189 | }; 1190 | 1191 | return packet; 1192 | }; 1193 | 1194 | /** 1195 | * Decodes data payload. Detects multiple messages 1196 | * 1197 | * @return {Array} messages 1198 | * @api public 1199 | */ 1200 | 1201 | parser.decodePayload = function (data) { 1202 | // IE doesn't like data[i] for unicode chars, charAt works fine 1203 | if (data.charAt(0) == '\ufffd') { 1204 | var ret = []; 1205 | 1206 | for (var i = 1, length = ''; i < data.length; i++) { 1207 | if (data.charAt(i) == '\ufffd') { 1208 | ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); 1209 | i += Number(length) + 1; 1210 | length = ''; 1211 | } else { 1212 | length += data.charAt(i); 1213 | } 1214 | } 1215 | 1216 | return ret; 1217 | } else { 1218 | return [parser.decodePacket(data)]; 1219 | } 1220 | }; 1221 | 1222 | })( 1223 | 'undefined' != typeof io ? io : module.exports 1224 | , 'undefined' != typeof io ? io : module.parent.exports 1225 | ); 1226 | /** 1227 | * socket.io 1228 | * Copyright(c) 2011 LearnBoost 1229 | * MIT Licensed 1230 | */ 1231 | 1232 | (function (exports, io) { 1233 | 1234 | /** 1235 | * Expose constructor. 1236 | */ 1237 | 1238 | exports.Transport = Transport; 1239 | 1240 | /** 1241 | * This is the transport template for all supported transport methods. 1242 | * 1243 | * @constructor 1244 | * @api public 1245 | */ 1246 | 1247 | function Transport (socket, sessid) { 1248 | this.socket = socket; 1249 | this.sessid = sessid; 1250 | }; 1251 | 1252 | /** 1253 | * Apply EventEmitter mixin. 1254 | */ 1255 | 1256 | io.util.mixin(Transport, io.EventEmitter); 1257 | 1258 | /** 1259 | * Handles the response from the server. When a new response is received 1260 | * it will automatically update the timeout, decode the message and 1261 | * forwards the response to the onMessage function for further processing. 1262 | * 1263 | * @param {String} data Response from the server. 1264 | * @api private 1265 | */ 1266 | 1267 | Transport.prototype.onData = function (data) { 1268 | this.clearCloseTimeout(); 1269 | 1270 | // If the connection in currently open (or in a reopening state) reset the close 1271 | // timeout since we have just received data. This check is necessary so 1272 | // that we don't reset the timeout on an explicitly disconnected connection. 1273 | if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { 1274 | this.setCloseTimeout(); 1275 | } 1276 | 1277 | if (data !== '') { 1278 | // todo: we should only do decodePayload for xhr transports 1279 | var msgs = io.parser.decodePayload(data); 1280 | 1281 | if (msgs && msgs.length) { 1282 | for (var i = 0, l = msgs.length; i < l; i++) { 1283 | this.onPacket(msgs[i]); 1284 | } 1285 | } 1286 | } 1287 | 1288 | return this; 1289 | }; 1290 | 1291 | /** 1292 | * Handles packets. 1293 | * 1294 | * @api private 1295 | */ 1296 | 1297 | Transport.prototype.onPacket = function (packet) { 1298 | this.socket.setHeartbeatTimeout(); 1299 | 1300 | if (packet.type == 'heartbeat') { 1301 | return this.onHeartbeat(); 1302 | } 1303 | 1304 | if (packet.type == 'connect' && packet.endpoint == '') { 1305 | this.onConnect(); 1306 | } 1307 | 1308 | if (packet.type == 'error' && packet.advice == 'reconnect') { 1309 | this.open = false; 1310 | } 1311 | 1312 | this.socket.onPacket(packet); 1313 | 1314 | return this; 1315 | }; 1316 | 1317 | /** 1318 | * Sets close timeout 1319 | * 1320 | * @api private 1321 | */ 1322 | 1323 | Transport.prototype.setCloseTimeout = function () { 1324 | if (!this.closeTimeout) { 1325 | var self = this; 1326 | 1327 | this.closeTimeout = setTimeout(function () { 1328 | self.onDisconnect(); 1329 | }, this.socket.closeTimeout); 1330 | } 1331 | }; 1332 | 1333 | /** 1334 | * Called when transport disconnects. 1335 | * 1336 | * @api private 1337 | */ 1338 | 1339 | Transport.prototype.onDisconnect = function () { 1340 | if (this.close && this.open) this.close(); 1341 | this.clearTimeouts(); 1342 | this.socket.onDisconnect(); 1343 | return this; 1344 | }; 1345 | 1346 | /** 1347 | * Called when transport connects 1348 | * 1349 | * @api private 1350 | */ 1351 | 1352 | Transport.prototype.onConnect = function () { 1353 | this.socket.onConnect(); 1354 | return this; 1355 | } 1356 | 1357 | /** 1358 | * Clears close timeout 1359 | * 1360 | * @api private 1361 | */ 1362 | 1363 | Transport.prototype.clearCloseTimeout = function () { 1364 | if (this.closeTimeout) { 1365 | clearTimeout(this.closeTimeout); 1366 | this.closeTimeout = null; 1367 | } 1368 | }; 1369 | 1370 | /** 1371 | * Clear timeouts 1372 | * 1373 | * @api private 1374 | */ 1375 | 1376 | Transport.prototype.clearTimeouts = function () { 1377 | this.clearCloseTimeout(); 1378 | 1379 | if (this.reopenTimeout) { 1380 | clearTimeout(this.reopenTimeout); 1381 | } 1382 | }; 1383 | 1384 | /** 1385 | * Sends a packet 1386 | * 1387 | * @param {Object} packet object. 1388 | * @api private 1389 | */ 1390 | 1391 | Transport.prototype.packet = function (packet) { 1392 | this.send(io.parser.encodePacket(packet)); 1393 | }; 1394 | 1395 | /** 1396 | * Send the received heartbeat message back to server. So the server 1397 | * knows we are still connected. 1398 | * 1399 | * @param {String} heartbeat Heartbeat response from the server. 1400 | * @api private 1401 | */ 1402 | 1403 | Transport.prototype.onHeartbeat = function (heartbeat) { 1404 | this.packet({ type: 'heartbeat' }); 1405 | }; 1406 | 1407 | /** 1408 | * Called when the transport opens. 1409 | * 1410 | * @api private 1411 | */ 1412 | 1413 | Transport.prototype.onOpen = function () { 1414 | this.open = true; 1415 | this.clearCloseTimeout(); 1416 | this.socket.onOpen(); 1417 | }; 1418 | 1419 | /** 1420 | * Notifies the base when the connection with the Socket.IO server 1421 | * has been disconnected. 1422 | * 1423 | * @api private 1424 | */ 1425 | 1426 | Transport.prototype.onClose = function () { 1427 | var self = this; 1428 | 1429 | /* FIXME: reopen delay causing a infinit loop 1430 | this.reopenTimeout = setTimeout(function () { 1431 | self.open(); 1432 | }, this.socket.options['reopen delay']);*/ 1433 | 1434 | this.open = false; 1435 | this.socket.onClose(); 1436 | this.onDisconnect(); 1437 | }; 1438 | 1439 | /** 1440 | * Generates a connection url based on the Socket.IO URL Protocol. 1441 | * See for more details. 1442 | * 1443 | * @returns {String} Connection url 1444 | * @api private 1445 | */ 1446 | 1447 | Transport.prototype.prepareUrl = function () { 1448 | var options = this.socket.options; 1449 | 1450 | return this.scheme() + '://' 1451 | + options.host + ':' + options.port + '/' 1452 | + options.resource + '/' + io.protocol 1453 | + '/' + this.name + '/' + this.sessid; 1454 | }; 1455 | 1456 | /** 1457 | * Checks if the transport is ready to start a connection. 1458 | * 1459 | * @param {Socket} socket The socket instance that needs a transport 1460 | * @param {Function} fn The callback 1461 | * @api private 1462 | */ 1463 | 1464 | Transport.prototype.ready = function (socket, fn) { 1465 | fn.call(this); 1466 | }; 1467 | })( 1468 | 'undefined' != typeof io ? io : module.exports 1469 | , 'undefined' != typeof io ? io : module.parent.exports 1470 | ); 1471 | /** 1472 | * socket.io 1473 | * Copyright(c) 2011 LearnBoost 1474 | * MIT Licensed 1475 | */ 1476 | 1477 | (function (exports, io, global) { 1478 | 1479 | /** 1480 | * Expose constructor. 1481 | */ 1482 | 1483 | exports.Socket = Socket; 1484 | 1485 | /** 1486 | * Create a new `Socket.IO client` which can establish a persistent 1487 | * connection with a Socket.IO enabled server. 1488 | * 1489 | * @api public 1490 | */ 1491 | 1492 | function Socket (options) { 1493 | this.options = { 1494 | port: 80 1495 | , secure: false 1496 | , document: 'document' in global ? document : false 1497 | , resource: 'socket.io' 1498 | , transports: io.transports 1499 | , 'connect timeout': 10000 1500 | , 'try multiple transports': true 1501 | , 'reconnect': true 1502 | , 'reconnection delay': 500 1503 | , 'reconnection limit': Infinity 1504 | , 'reopen delay': 3000 1505 | , 'max reconnection attempts': 10 1506 | , 'sync disconnect on unload': true 1507 | , 'auto connect': true 1508 | , 'flash policy port': 10843 1509 | }; 1510 | 1511 | io.util.merge(this.options, options); 1512 | 1513 | this.connected = false; 1514 | this.open = false; 1515 | this.connecting = false; 1516 | this.reconnecting = false; 1517 | this.namespaces = {}; 1518 | this.buffer = []; 1519 | this.doBuffer = false; 1520 | 1521 | if (this.options['sync disconnect on unload'] && 1522 | (!this.isXDomain() || io.util.ua.hasCORS)) { 1523 | var self = this; 1524 | 1525 | io.util.on(global, 'unload', function () { 1526 | self.disconnectSync(); 1527 | }, false); 1528 | } 1529 | 1530 | if (this.options['auto connect']) { 1531 | this.connect(); 1532 | } 1533 | }; 1534 | 1535 | /** 1536 | * Apply EventEmitter mixin. 1537 | */ 1538 | 1539 | io.util.mixin(Socket, io.EventEmitter); 1540 | 1541 | 1542 | /** 1543 | * Returns a namespace listener/emitter for this socket 1544 | * 1545 | * @api public 1546 | */ 1547 | 1548 | Socket.prototype.of = function (name) { 1549 | if (!this.namespaces[name]) { 1550 | this.namespaces[name] = new io.SocketNamespace(this, name); 1551 | 1552 | if (name !== '') { 1553 | this.namespaces[name].packet({ type: 'connect' }); 1554 | } 1555 | } 1556 | 1557 | return this.namespaces[name]; 1558 | }; 1559 | 1560 | /** 1561 | * Emits the given event to the Socket and all namespaces 1562 | * 1563 | * @api private 1564 | */ 1565 | 1566 | Socket.prototype.publish = function () { 1567 | this.emit.apply(this, arguments); 1568 | 1569 | var nsp; 1570 | 1571 | for (var i in this.namespaces) { 1572 | if (this.namespaces.hasOwnProperty(i)) { 1573 | nsp = this.of(i); 1574 | nsp.$emit.apply(nsp, arguments); 1575 | } 1576 | } 1577 | }; 1578 | 1579 | /** 1580 | * Performs the handshake 1581 | * 1582 | * @api private 1583 | */ 1584 | 1585 | function empty () { }; 1586 | 1587 | Socket.prototype.handshake = function (fn) { 1588 | var self = this 1589 | , options = this.options; 1590 | 1591 | function complete (data) { 1592 | if (data instanceof Error) { 1593 | self.onError(data.message); 1594 | } else { 1595 | fn.apply(null, data.split(':')); 1596 | } 1597 | }; 1598 | 1599 | var url = [ 1600 | 'http' + (options.secure ? 's' : '') + ':/' 1601 | , options.host + ':' + options.port 1602 | , options.resource 1603 | , io.protocol 1604 | , io.util.query(this.options.query, 't=' + +new Date) 1605 | ].join('/'); 1606 | 1607 | if (this.isXDomain() && !io.util.ua.hasCORS) { 1608 | var insertAt = document.getElementsByTagName('script')[0] 1609 | , script = document.createElement('script'); 1610 | 1611 | script.src = url + '&jsonp=' + io.j.length; 1612 | insertAt.parentNode.insertBefore(script, insertAt); 1613 | 1614 | io.j.push(function (data) { 1615 | complete(data); 1616 | script.parentNode.removeChild(script); 1617 | }); 1618 | } else { 1619 | var xhr = io.util.request(); 1620 | 1621 | xhr.open('GET', url, true); 1622 | xhr.withCredentials = true; 1623 | xhr.onreadystatechange = function () { 1624 | if (xhr.readyState == 4) { 1625 | xhr.onreadystatechange = empty; 1626 | 1627 | if (xhr.status == 200) { 1628 | complete(xhr.responseText); 1629 | } else { 1630 | !self.reconnecting && self.onError(xhr.responseText); 1631 | } 1632 | } 1633 | }; 1634 | xhr.send(null); 1635 | } 1636 | }; 1637 | 1638 | /** 1639 | * Find an available transport based on the options supplied in the constructor. 1640 | * 1641 | * @api private 1642 | */ 1643 | 1644 | Socket.prototype.getTransport = function (override) { 1645 | var transports = override || this.transports, match; 1646 | 1647 | for (var i = 0, transport; transport = transports[i]; i++) { 1648 | if (io.Transport[transport] 1649 | && io.Transport[transport].check(this) 1650 | && (!this.isXDomain() || io.Transport[transport].xdomainCheck())) { 1651 | return new io.Transport[transport](this, this.sessionid); 1652 | } 1653 | } 1654 | 1655 | return null; 1656 | }; 1657 | 1658 | /** 1659 | * Connects to the server. 1660 | * 1661 | * @param {Function} [fn] Callback. 1662 | * @returns {io.Socket} 1663 | * @api public 1664 | */ 1665 | 1666 | Socket.prototype.connect = function (fn) { 1667 | if (this.connecting) { 1668 | return this; 1669 | } 1670 | 1671 | var self = this; 1672 | 1673 | this.handshake(function (sid, heartbeat, close, transports) { 1674 | self.sessionid = sid; 1675 | self.closeTimeout = close * 1000; 1676 | self.heartbeatTimeout = heartbeat * 1000; 1677 | self.transports = transports ? io.util.intersect( 1678 | transports.split(',') 1679 | , self.options.transports 1680 | ) : self.options.transports; 1681 | 1682 | self.setHeartbeatTimeout(); 1683 | 1684 | function connect (transports){ 1685 | if (self.transport) self.transport.clearTimeouts(); 1686 | 1687 | self.transport = self.getTransport(transports); 1688 | if (!self.transport) return self.publish('connect_failed'); 1689 | 1690 | // once the transport is ready 1691 | self.transport.ready(self, function () { 1692 | self.connecting = true; 1693 | self.publish('connecting', self.transport.name); 1694 | self.transport.open(); 1695 | 1696 | if (self.options['connect timeout']) { 1697 | self.connectTimeoutTimer = setTimeout(function () { 1698 | if (!self.connected) { 1699 | self.connecting = false; 1700 | 1701 | if (self.options['try multiple transports']) { 1702 | if (!self.remainingTransports) { 1703 | self.remainingTransports = self.transports.slice(0); 1704 | } 1705 | 1706 | var remaining = self.remainingTransports; 1707 | 1708 | while (remaining.length > 0 && remaining.splice(0,1)[0] != 1709 | self.transport.name) {} 1710 | 1711 | if (remaining.length){ 1712 | connect(remaining); 1713 | } else { 1714 | self.publish('connect_failed'); 1715 | } 1716 | } 1717 | } 1718 | }, self.options['connect timeout']); 1719 | } 1720 | }); 1721 | } 1722 | 1723 | connect(self.transports); 1724 | 1725 | self.once('connect', function (){ 1726 | clearTimeout(self.connectTimeoutTimer); 1727 | 1728 | fn && typeof fn == 'function' && fn(); 1729 | }); 1730 | }); 1731 | 1732 | return this; 1733 | }; 1734 | 1735 | /** 1736 | * Clears and sets a new heartbeat timeout using the value given by the 1737 | * server during the handshake. 1738 | * 1739 | * @api private 1740 | */ 1741 | 1742 | Socket.prototype.setHeartbeatTimeout = function () { 1743 | clearTimeout(this.heartbeatTimeoutTimer); 1744 | 1745 | var self = this; 1746 | this.heartbeatTimeoutTimer = setTimeout(function () { 1747 | self.transport.onClose(); 1748 | }, this.heartbeatTimeout); 1749 | }; 1750 | 1751 | /** 1752 | * Sends a message. 1753 | * 1754 | * @param {Object} data packet. 1755 | * @returns {io.Socket} 1756 | * @api public 1757 | */ 1758 | 1759 | Socket.prototype.packet = function (data) { 1760 | if (this.connected && !this.doBuffer) { 1761 | this.transport.packet(data); 1762 | } else { 1763 | this.buffer.push(data); 1764 | } 1765 | 1766 | return this; 1767 | }; 1768 | 1769 | /** 1770 | * Sets buffer state 1771 | * 1772 | * @api private 1773 | */ 1774 | 1775 | Socket.prototype.setBuffer = function (v) { 1776 | this.doBuffer = v; 1777 | 1778 | if (!v && this.connected && this.buffer.length) { 1779 | this.transport.payload(this.buffer); 1780 | this.buffer = []; 1781 | } 1782 | }; 1783 | 1784 | /** 1785 | * Disconnect the established connect. 1786 | * 1787 | * @returns {io.Socket} 1788 | * @api public 1789 | */ 1790 | 1791 | Socket.prototype.disconnect = function () { 1792 | if (this.connected || this.connecting) { 1793 | if (this.open) { 1794 | this.of('').packet({ type: 'disconnect' }); 1795 | } 1796 | 1797 | // handle disconnection immediately 1798 | this.onDisconnect('booted'); 1799 | } 1800 | 1801 | return this; 1802 | }; 1803 | 1804 | /** 1805 | * Disconnects the socket with a sync XHR. 1806 | * 1807 | * @api private 1808 | */ 1809 | 1810 | Socket.prototype.disconnectSync = function () { 1811 | // ensure disconnection 1812 | var xhr = io.util.request() 1813 | , uri = this.resource + '/' + io.protocol + '/' + this.sessionid; 1814 | 1815 | xhr.open('GET', uri, true); 1816 | 1817 | // handle disconnection immediately 1818 | this.onDisconnect('booted'); 1819 | }; 1820 | 1821 | /** 1822 | * Check if we need to use cross domain enabled transports. Cross domain would 1823 | * be a different port or different domain name. 1824 | * 1825 | * @returns {Boolean} 1826 | * @api private 1827 | */ 1828 | 1829 | Socket.prototype.isXDomain = function () { 1830 | 1831 | var port = global.location.port || 1832 | ('https:' == global.location.protocol ? 443 : 80); 1833 | 1834 | return this.options.host !== global.location.hostname 1835 | || this.options.port != port; 1836 | }; 1837 | 1838 | /** 1839 | * Called upon handshake. 1840 | * 1841 | * @api private 1842 | */ 1843 | 1844 | Socket.prototype.onConnect = function () { 1845 | if (!this.connected) { 1846 | this.connected = true; 1847 | this.connecting = false; 1848 | if (!this.doBuffer) { 1849 | // make sure to flush the buffer 1850 | this.setBuffer(false); 1851 | } 1852 | this.emit('connect'); 1853 | } 1854 | }; 1855 | 1856 | /** 1857 | * Called when the transport opens 1858 | * 1859 | * @api private 1860 | */ 1861 | 1862 | Socket.prototype.onOpen = function () { 1863 | this.open = true; 1864 | }; 1865 | 1866 | /** 1867 | * Called when the transport closes. 1868 | * 1869 | * @api private 1870 | */ 1871 | 1872 | Socket.prototype.onClose = function () { 1873 | this.open = false; 1874 | clearTimeout(this.heartbeatTimeoutTimer); 1875 | }; 1876 | 1877 | /** 1878 | * Called when the transport first opens a connection 1879 | * 1880 | * @param text 1881 | */ 1882 | 1883 | Socket.prototype.onPacket = function (packet) { 1884 | this.of(packet.endpoint).onPacket(packet); 1885 | }; 1886 | 1887 | /** 1888 | * Handles an error. 1889 | * 1890 | * @api private 1891 | */ 1892 | 1893 | Socket.prototype.onError = function (err) { 1894 | if (err && err.advice) { 1895 | if (err.advice === 'reconnect' && (this.connected || this.connecting)) { 1896 | this.disconnect(); 1897 | if (this.options.reconnect) { 1898 | this.reconnect(); 1899 | } 1900 | } 1901 | } 1902 | 1903 | this.publish('error', err && err.reason ? err.reason : err); 1904 | }; 1905 | 1906 | /** 1907 | * Called when the transport disconnects. 1908 | * 1909 | * @api private 1910 | */ 1911 | 1912 | Socket.prototype.onDisconnect = function (reason) { 1913 | var wasConnected = this.connected 1914 | , wasConnecting = this.connecting; 1915 | 1916 | this.connected = false; 1917 | this.connecting = false; 1918 | this.open = false; 1919 | 1920 | if (wasConnected || wasConnecting) { 1921 | this.transport.close(); 1922 | this.transport.clearTimeouts(); 1923 | if (wasConnected) { 1924 | this.publish('disconnect', reason); 1925 | 1926 | if ('booted' != reason && this.options.reconnect && !this.reconnecting) { 1927 | this.reconnect(); 1928 | } 1929 | } 1930 | } 1931 | }; 1932 | 1933 | /** 1934 | * Called upon reconnection. 1935 | * 1936 | * @api private 1937 | */ 1938 | 1939 | Socket.prototype.reconnect = function () { 1940 | this.reconnecting = true; 1941 | this.reconnectionAttempts = 0; 1942 | this.reconnectionDelay = this.options['reconnection delay']; 1943 | 1944 | var self = this 1945 | , maxAttempts = this.options['max reconnection attempts'] 1946 | , tryMultiple = this.options['try multiple transports'] 1947 | , limit = this.options['reconnection limit']; 1948 | 1949 | function reset () { 1950 | if (self.connected) { 1951 | for (var i in self.namespaces) { 1952 | if (self.namespaces.hasOwnProperty(i) && '' !== i) { 1953 | self.namespaces[i].packet({ type: 'connect' }); 1954 | } 1955 | } 1956 | self.publish('reconnect', self.transport.name, self.reconnectionAttempts); 1957 | } 1958 | 1959 | clearTimeout(self.reconnectionTimer); 1960 | 1961 | self.removeListener('connect_failed', maybeReconnect); 1962 | self.removeListener('connect', maybeReconnect); 1963 | 1964 | self.reconnecting = false; 1965 | 1966 | delete self.reconnectionAttempts; 1967 | delete self.reconnectionDelay; 1968 | delete self.reconnectionTimer; 1969 | delete self.redoTransports; 1970 | 1971 | self.options['try multiple transports'] = tryMultiple; 1972 | }; 1973 | 1974 | function maybeReconnect () { 1975 | if (!self.reconnecting) { 1976 | return; 1977 | } 1978 | 1979 | if (self.connected) { 1980 | return reset(); 1981 | }; 1982 | 1983 | if (self.connecting && self.reconnecting) { 1984 | return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); 1985 | } 1986 | 1987 | if (self.reconnectionAttempts++ >= maxAttempts) { 1988 | if (!self.redoTransports) { 1989 | self.on('connect_failed', maybeReconnect); 1990 | self.options['try multiple transports'] = true; 1991 | self.transport = self.getTransport(); 1992 | self.redoTransports = true; 1993 | self.connect(); 1994 | } else { 1995 | self.publish('reconnect_failed'); 1996 | reset(); 1997 | } 1998 | } else { 1999 | if (self.reconnectionDelay < limit) { 2000 | self.reconnectionDelay *= 2; // exponential back off 2001 | } 2002 | 2003 | self.connect(); 2004 | self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); 2005 | self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); 2006 | } 2007 | }; 2008 | 2009 | this.options['try multiple transports'] = false; 2010 | this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); 2011 | 2012 | this.on('connect', maybeReconnect); 2013 | }; 2014 | 2015 | })( 2016 | 'undefined' != typeof io ? io : module.exports 2017 | , 'undefined' != typeof io ? io : module.parent.exports 2018 | , this 2019 | ); 2020 | /** 2021 | * socket.io 2022 | * Copyright(c) 2011 LearnBoost 2023 | * MIT Licensed 2024 | */ 2025 | 2026 | (function (exports, io) { 2027 | 2028 | /** 2029 | * Expose constructor. 2030 | */ 2031 | 2032 | exports.SocketNamespace = SocketNamespace; 2033 | 2034 | /** 2035 | * Socket namespace constructor. 2036 | * 2037 | * @constructor 2038 | * @api public 2039 | */ 2040 | 2041 | function SocketNamespace (socket, name) { 2042 | this.socket = socket; 2043 | this.name = name || ''; 2044 | this.flags = {}; 2045 | this.json = new Flag(this, 'json'); 2046 | this.ackPackets = 0; 2047 | this.acks = {}; 2048 | }; 2049 | 2050 | /** 2051 | * Apply EventEmitter mixin. 2052 | */ 2053 | 2054 | io.util.mixin(SocketNamespace, io.EventEmitter); 2055 | 2056 | /** 2057 | * Copies emit since we override it 2058 | * 2059 | * @api private 2060 | */ 2061 | 2062 | SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; 2063 | 2064 | /** 2065 | * Creates a new namespace, by proxying the request to the socket. This 2066 | * allows us to use the synax as we do on the server. 2067 | * 2068 | * @api public 2069 | */ 2070 | 2071 | SocketNamespace.prototype.of = function () { 2072 | return this.socket.of.apply(this.socket, arguments); 2073 | }; 2074 | 2075 | /** 2076 | * Sends a packet. 2077 | * 2078 | * @api private 2079 | */ 2080 | 2081 | SocketNamespace.prototype.packet = function (packet) { 2082 | packet.endpoint = this.name; 2083 | this.socket.packet(packet); 2084 | this.flags = {}; 2085 | return this; 2086 | }; 2087 | 2088 | /** 2089 | * Sends a message 2090 | * 2091 | * @api public 2092 | */ 2093 | 2094 | SocketNamespace.prototype.send = function (data, fn) { 2095 | var packet = { 2096 | type: this.flags.json ? 'json' : 'message' 2097 | , data: data 2098 | }; 2099 | 2100 | if ('function' == typeof fn) { 2101 | packet.id = ++this.ackPackets; 2102 | packet.ack = true; 2103 | this.acks[packet.id] = fn; 2104 | } 2105 | 2106 | return this.packet(packet); 2107 | }; 2108 | 2109 | /************************************************************ 2110 | * 2111 | * Additional functionality for the Sails.js framework 2112 | * 2113 | * Simulate an HTTP request to the backend 2114 | * url: the request label (usually the destination URL) 2115 | * data: data to pass with the request 2116 | * options: optional callback or config object 2117 | * method: HTTP method 2118 | */ 2119 | SocketNamespace.prototype.request = function (url, data, options, method) { 2120 | // Remove trailing slashes and spaces 2121 | url = url.replace(/\/*\s*$/,''); 2122 | 2123 | // If url is empty, use / 2124 | if (url === '') url = '/'; 2125 | 2126 | // If options is a function, treat it as a callback 2127 | // Otherwise the "success" property will be treated as the callback 2128 | var cb; 2129 | if (typeof options === 'function') cb = options; 2130 | else cb = options.success; 2131 | 2132 | var json = io.JSON.stringify({ 2133 | url: url, 2134 | data: data, 2135 | method: method || 'get' 2136 | }); 2137 | 2138 | this.emit('message',json,function (result) { 2139 | 2140 | var parsedResult = result 2141 | try { 2142 | parsedResult = io.JSON.parse(result); 2143 | } 2144 | catch (e) { 2145 | if (typeof console !== 'undefined') { 2146 | console.log("Could not parse:",result,e); 2147 | } 2148 | throw new Error("Server response could not be parsed! "+result); 2149 | } 2150 | 2151 | // TODO: Handle errors more effectively 2152 | if (parsedResult === 404) throw new Error("404: Not found"); 2153 | if (parsedResult === 403) throw new Error("403: Forbidden"); 2154 | if (parsedResult === 500) throw new Error("500: Server error"); 2155 | 2156 | cb(parsedResult); 2157 | }); 2158 | } 2159 | /* 2160 | * 2161 | ************************************************************/ 2162 | 2163 | /** 2164 | * Emits an event 2165 | * 2166 | * @api public 2167 | */ 2168 | 2169 | SocketNamespace.prototype.emit = function (name) { 2170 | var args = Array.prototype.slice.call(arguments, 1) 2171 | , lastArg = args[args.length - 1] 2172 | , packet = { 2173 | type: 'event' 2174 | , name: name 2175 | }; 2176 | 2177 | if ('function' == typeof lastArg) { 2178 | packet.id = ++this.ackPackets; 2179 | packet.ack = 'data'; 2180 | this.acks[packet.id] = lastArg; 2181 | args = args.slice(0, args.length - 1); 2182 | } 2183 | 2184 | packet.args = args; 2185 | 2186 | return this.packet(packet); 2187 | }; 2188 | 2189 | /** 2190 | * Disconnects the namespace 2191 | * 2192 | * @api private 2193 | */ 2194 | 2195 | SocketNamespace.prototype.disconnect = function () { 2196 | if (this.name === '') { 2197 | this.socket.disconnect(); 2198 | } else { 2199 | this.packet({ type: 'disconnect' }); 2200 | this.$emit('disconnect'); 2201 | } 2202 | 2203 | return this; 2204 | }; 2205 | 2206 | /** 2207 | * Handles a packet 2208 | * 2209 | * @api private 2210 | */ 2211 | 2212 | SocketNamespace.prototype.onPacket = function (packet) { 2213 | var self = this; 2214 | 2215 | function ack () { 2216 | self.packet({ 2217 | type: 'ack' 2218 | , args: io.util.toArray(arguments) 2219 | , ackId: packet.id 2220 | }); 2221 | }; 2222 | 2223 | switch (packet.type) { 2224 | case 'connect': 2225 | this.$emit('connect'); 2226 | break; 2227 | 2228 | case 'disconnect': 2229 | if (this.name === '') { 2230 | this.socket.onDisconnect(packet.reason || 'booted'); 2231 | } else { 2232 | this.$emit('disconnect', packet.reason); 2233 | } 2234 | break; 2235 | 2236 | case 'message': 2237 | case 'json': 2238 | var params = ['message', packet.data]; 2239 | 2240 | if (packet.ack == 'data') { 2241 | params.push(ack); 2242 | } else if (packet.ack) { 2243 | this.packet({ type: 'ack', ackId: packet.id }); 2244 | } 2245 | 2246 | this.$emit.apply(this, params); 2247 | break; 2248 | 2249 | case 'event': 2250 | var params = [packet.name].concat(packet.args); 2251 | 2252 | if (packet.ack == 'data') 2253 | params.push(ack); 2254 | 2255 | this.$emit.apply(this, params); 2256 | break; 2257 | 2258 | case 'ack': 2259 | if (this.acks[packet.ackId]) { 2260 | this.acks[packet.ackId].apply(this, packet.args); 2261 | delete this.acks[packet.ackId]; 2262 | } 2263 | break; 2264 | 2265 | case 'error': 2266 | if (packet.advice){ 2267 | this.socket.onError(packet); 2268 | } else { 2269 | if (packet.reason == 'unauthorized') { 2270 | this.$emit('connect_failed', packet.reason); 2271 | } else { 2272 | this.$emit('error', packet.reason); 2273 | } 2274 | } 2275 | break; 2276 | } 2277 | }; 2278 | 2279 | /** 2280 | * Flag interface. 2281 | * 2282 | * @api private 2283 | */ 2284 | 2285 | function Flag (nsp, name) { 2286 | this.namespace = nsp; 2287 | this.name = name; 2288 | }; 2289 | 2290 | /** 2291 | * Send a message 2292 | * 2293 | * @api public 2294 | */ 2295 | 2296 | Flag.prototype.send = function () { 2297 | this.namespace.flags[this.name] = true; 2298 | this.namespace.send.apply(this.namespace, arguments); 2299 | }; 2300 | 2301 | /** 2302 | * Emit an event 2303 | * 2304 | * @api public 2305 | */ 2306 | 2307 | Flag.prototype.emit = function () { 2308 | this.namespace.flags[this.name] = true; 2309 | this.namespace.emit.apply(this.namespace, arguments); 2310 | }; 2311 | 2312 | })( 2313 | 'undefined' != typeof io ? io : module.exports 2314 | , 'undefined' != typeof io ? io : module.parent.exports 2315 | ); 2316 | 2317 | /** 2318 | * socket.io 2319 | * Copyright(c) 2011 LearnBoost 2320 | * MIT Licensed 2321 | */ 2322 | 2323 | (function (exports, io, global) { 2324 | 2325 | /** 2326 | * Expose constructor. 2327 | */ 2328 | 2329 | exports.websocket = WS; 2330 | 2331 | /** 2332 | * The WebSocket transport uses the HTML5 WebSocket API to establish an 2333 | * persistent connection with the Socket.IO server. This transport will also 2334 | * be inherited by the FlashSocket fallback as it provides a API compatible 2335 | * polyfill for the WebSockets. 2336 | * 2337 | * @constructor 2338 | * @extends {io.Transport} 2339 | * @api public 2340 | */ 2341 | 2342 | function WS (socket) { 2343 | io.Transport.apply(this, arguments); 2344 | }; 2345 | 2346 | /** 2347 | * Inherits from Transport. 2348 | */ 2349 | 2350 | io.util.inherit(WS, io.Transport); 2351 | 2352 | /** 2353 | * Transport name 2354 | * 2355 | * @api public 2356 | */ 2357 | 2358 | WS.prototype.name = 'websocket'; 2359 | 2360 | /** 2361 | * Initializes a new `WebSocket` connection with the Socket.IO server. We attach 2362 | * all the appropriate listeners to handle the responses from the server. 2363 | * 2364 | * @returns {Transport} 2365 | * @api public 2366 | */ 2367 | 2368 | WS.prototype.open = function () { 2369 | var query = io.util.query(this.socket.options.query) 2370 | , self = this 2371 | , Socket 2372 | 2373 | 2374 | if (!Socket) { 2375 | Socket = global.MozWebSocket || global.WebSocket; 2376 | } 2377 | 2378 | this.websocket = new Socket(this.prepareUrl() + query); 2379 | 2380 | this.websocket.onopen = function () { 2381 | self.onOpen(); 2382 | self.socket.setBuffer(false); 2383 | }; 2384 | this.websocket.onmessage = function (ev) { 2385 | self.onData(ev.data); 2386 | }; 2387 | this.websocket.onclose = function () { 2388 | self.onClose(); 2389 | self.socket.setBuffer(true); 2390 | }; 2391 | this.websocket.onerror = function (e) { 2392 | self.onError(e); 2393 | }; 2394 | 2395 | return this; 2396 | }; 2397 | 2398 | /** 2399 | * Send a message to the Socket.IO server. The message will automatically be 2400 | * encoded in the correct message format. 2401 | * 2402 | * @returns {Transport} 2403 | * @api public 2404 | */ 2405 | 2406 | WS.prototype.send = function (data) { 2407 | this.websocket.send(data); 2408 | return this; 2409 | }; 2410 | 2411 | /** 2412 | * Payload 2413 | * 2414 | * @api private 2415 | */ 2416 | 2417 | WS.prototype.payload = function (arr) { 2418 | for (var i = 0, l = arr.length; i < l; i++) { 2419 | this.packet(arr[i]); 2420 | } 2421 | return this; 2422 | }; 2423 | 2424 | /** 2425 | * Disconnect the established `WebSocket` connection. 2426 | * 2427 | * @returns {Transport} 2428 | * @api public 2429 | */ 2430 | 2431 | WS.prototype.close = function () { 2432 | this.websocket.close(); 2433 | return this; 2434 | }; 2435 | 2436 | /** 2437 | * Handle the errors that `WebSocket` might be giving when we 2438 | * are attempting to connect or send messages. 2439 | * 2440 | * @param {Error} e The error. 2441 | * @api private 2442 | */ 2443 | 2444 | WS.prototype.onError = function (e) { 2445 | this.socket.onError(e); 2446 | }; 2447 | 2448 | /** 2449 | * Returns the appropriate scheme for the URI generation. 2450 | * 2451 | * @api private 2452 | */ 2453 | WS.prototype.scheme = function () { 2454 | return this.socket.options.secure ? 'wss' : 'ws'; 2455 | }; 2456 | 2457 | /** 2458 | * Checks if the browser has support for native `WebSockets` and that 2459 | * it's not the polyfill created for the FlashSocket transport. 2460 | * 2461 | * @return {Boolean} 2462 | * @api public 2463 | */ 2464 | 2465 | WS.check = function () { 2466 | return ('WebSocket' in global && !('__addTask' in WebSocket)) 2467 | || 'MozWebSocket' in global; 2468 | }; 2469 | 2470 | /** 2471 | * Check if the `WebSocket` transport support cross domain communications. 2472 | * 2473 | * @returns {Boolean} 2474 | * @api public 2475 | */ 2476 | 2477 | WS.xdomainCheck = function () { 2478 | return true; 2479 | }; 2480 | 2481 | /** 2482 | * Add the transport to your public io.transports array. 2483 | * 2484 | * @api private 2485 | */ 2486 | 2487 | io.transports.push('websocket'); 2488 | 2489 | })( 2490 | 'undefined' != typeof io ? io.Transport : module.exports 2491 | , 'undefined' != typeof io ? io : module.parent.exports 2492 | , this 2493 | ); 2494 | 2495 | /** 2496 | * socket.io 2497 | * Copyright(c) 2011 LearnBoost 2498 | * MIT Licensed 2499 | */ 2500 | 2501 | (function (exports, io, global) { 2502 | 2503 | /** 2504 | * Expose constructor. 2505 | * 2506 | * @api public 2507 | */ 2508 | 2509 | exports.XHR = XHR; 2510 | 2511 | /** 2512 | * XHR constructor 2513 | * 2514 | * @costructor 2515 | * @api public 2516 | */ 2517 | 2518 | function XHR (socket) { 2519 | if (!socket) return; 2520 | 2521 | io.Transport.apply(this, arguments); 2522 | this.sendBuffer = []; 2523 | }; 2524 | 2525 | /** 2526 | * Inherits from Transport. 2527 | */ 2528 | 2529 | io.util.inherit(XHR, io.Transport); 2530 | 2531 | /** 2532 | * Establish a connection 2533 | * 2534 | * @returns {Transport} 2535 | * @api public 2536 | */ 2537 | 2538 | XHR.prototype.open = function () { 2539 | this.socket.setBuffer(false); 2540 | this.onOpen(); 2541 | this.get(); 2542 | 2543 | // we need to make sure the request succeeds since we have no indication 2544 | // whether the request opened or not until it succeeded. 2545 | this.setCloseTimeout(); 2546 | 2547 | return this; 2548 | }; 2549 | 2550 | /** 2551 | * Check if we need to send data to the Socket.IO server, if we have data in our 2552 | * buffer we encode it and forward it to the `post` method. 2553 | * 2554 | * @api private 2555 | */ 2556 | 2557 | XHR.prototype.payload = function (payload) { 2558 | var msgs = []; 2559 | 2560 | for (var i = 0, l = payload.length; i < l; i++) { 2561 | msgs.push(io.parser.encodePacket(payload[i])); 2562 | } 2563 | 2564 | this.send(io.parser.encodePayload(msgs)); 2565 | }; 2566 | 2567 | /** 2568 | * Send data to the Socket.IO server. 2569 | * 2570 | * @param data The message 2571 | * @returns {Transport} 2572 | * @api public 2573 | */ 2574 | 2575 | XHR.prototype.send = function (data) { 2576 | this.post(data); 2577 | return this; 2578 | }; 2579 | 2580 | /** 2581 | * Posts a encoded message to the Socket.IO server. 2582 | * 2583 | * @param {String} data A encoded message. 2584 | * @api private 2585 | */ 2586 | 2587 | function empty () { }; 2588 | 2589 | XHR.prototype.post = function (data) { 2590 | var self = this; 2591 | this.socket.setBuffer(true); 2592 | 2593 | function stateChange () { 2594 | if (this.readyState == 4) { 2595 | this.onreadystatechange = empty; 2596 | self.posting = false; 2597 | 2598 | if (this.status == 200){ 2599 | self.socket.setBuffer(false); 2600 | } else { 2601 | self.onClose(); 2602 | } 2603 | } 2604 | } 2605 | 2606 | function onload () { 2607 | this.onload = empty; 2608 | self.socket.setBuffer(false); 2609 | }; 2610 | 2611 | this.sendXHR = this.request('POST'); 2612 | 2613 | if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { 2614 | this.sendXHR.onload = this.sendXHR.onerror = onload; 2615 | } else { 2616 | this.sendXHR.onreadystatechange = stateChange; 2617 | } 2618 | 2619 | this.sendXHR.send(data); 2620 | }; 2621 | 2622 | /** 2623 | * Disconnects the established `XHR` connection. 2624 | * 2625 | * @returns {Transport} 2626 | * @api public 2627 | */ 2628 | 2629 | XHR.prototype.close = function () { 2630 | this.onClose(); 2631 | return this; 2632 | }; 2633 | 2634 | /** 2635 | * Generates a configured XHR request 2636 | * 2637 | * @param {String} url The url that needs to be requested. 2638 | * @param {String} method The method the request should use. 2639 | * @returns {XMLHttpRequest} 2640 | * @api private 2641 | */ 2642 | 2643 | XHR.prototype.request = function (method) { 2644 | var req = io.util.request(this.socket.isXDomain()) 2645 | , query = io.util.query(this.socket.options.query, 't=' + +new Date); 2646 | 2647 | req.open(method || 'GET', this.prepareUrl() + query, true); 2648 | 2649 | if (method == 'POST') { 2650 | try { 2651 | if (req.setRequestHeader) { 2652 | req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); 2653 | } else { 2654 | // XDomainRequest 2655 | req.contentType = 'text/plain'; 2656 | } 2657 | } catch (e) {} 2658 | } 2659 | 2660 | return req; 2661 | }; 2662 | 2663 | /** 2664 | * Returns the scheme to use for the transport URLs. 2665 | * 2666 | * @api private 2667 | */ 2668 | 2669 | XHR.prototype.scheme = function () { 2670 | return this.socket.options.secure ? 'https' : 'http'; 2671 | }; 2672 | 2673 | /** 2674 | * Check if the XHR transports are supported 2675 | * 2676 | * @param {Boolean} xdomain Check if we support cross domain requests. 2677 | * @returns {Boolean} 2678 | * @api public 2679 | */ 2680 | 2681 | XHR.check = function (socket, xdomain) { 2682 | try { 2683 | var request = io.util.request(xdomain), 2684 | usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), 2685 | socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), 2686 | isXProtocol = (socketProtocol != global.location.protocol); 2687 | if (request && !(usesXDomReq && isXProtocol)) { 2688 | return true; 2689 | } 2690 | } catch(e) {} 2691 | 2692 | return false; 2693 | }; 2694 | 2695 | /** 2696 | * Check if the XHR transport supports cross domain requests. 2697 | * 2698 | * @returns {Boolean} 2699 | * @api public 2700 | */ 2701 | 2702 | XHR.xdomainCheck = function () { 2703 | return XHR.check(null, true); 2704 | }; 2705 | 2706 | })( 2707 | 'undefined' != typeof io ? io.Transport : module.exports 2708 | , 'undefined' != typeof io ? io : module.parent.exports 2709 | , this 2710 | ); 2711 | /** 2712 | * socket.io 2713 | * Copyright(c) 2011 LearnBoost 2714 | * MIT Licensed 2715 | */ 2716 | 2717 | (function (exports, io) { 2718 | 2719 | /** 2720 | * Expose constructor. 2721 | */ 2722 | 2723 | exports.htmlfile = HTMLFile; 2724 | 2725 | /** 2726 | * The HTMLFile transport creates a `forever iframe` based transport 2727 | * for Internet Explorer. Regular forever iframe implementations will 2728 | * continuously trigger the browsers buzy indicators. If the forever iframe 2729 | * is created inside a `htmlfile` these indicators will not be trigged. 2730 | * 2731 | * @constructor 2732 | * @extends {io.Transport.XHR} 2733 | * @api public 2734 | */ 2735 | 2736 | function HTMLFile (socket) { 2737 | io.Transport.XHR.apply(this, arguments); 2738 | }; 2739 | 2740 | /** 2741 | * Inherits from XHR transport. 2742 | */ 2743 | 2744 | io.util.inherit(HTMLFile, io.Transport.XHR); 2745 | 2746 | /** 2747 | * Transport name 2748 | * 2749 | * @api public 2750 | */ 2751 | 2752 | HTMLFile.prototype.name = 'htmlfile'; 2753 | 2754 | /** 2755 | * Creates a new Ac...eX `htmlfile` with a forever loading iframe 2756 | * that can be used to listen to messages. Inside the generated 2757 | * `htmlfile` a reference will be made to the HTMLFile transport. 2758 | * 2759 | * @api private 2760 | */ 2761 | 2762 | HTMLFile.prototype.get = function () { 2763 | this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); 2764 | this.doc.open(); 2765 | this.doc.write(''); 2766 | this.doc.close(); 2767 | this.doc.parentWindow.s = this; 2768 | 2769 | var iframeC = this.doc.createElement('div'); 2770 | iframeC.className = 'socketio'; 2771 | 2772 | this.doc.body.appendChild(iframeC); 2773 | this.iframe = this.doc.createElement('iframe'); 2774 | 2775 | iframeC.appendChild(this.iframe); 2776 | 2777 | var self = this 2778 | , query = io.util.query(this.socket.options.query, 't='+ +new Date); 2779 | 2780 | this.iframe.src = this.prepareUrl() + query; 2781 | 2782 | io.util.on(window, 'unload', function () { 2783 | self.destroy(); 2784 | }); 2785 | }; 2786 | 2787 | /** 2788 | * The Socket.IO server will write script tags inside the forever 2789 | * iframe, this function will be used as callback for the incoming 2790 | * information. 2791 | * 2792 | * @param {String} data The message 2793 | * @param {document} doc Reference to the context 2794 | * @api private 2795 | */ 2796 | 2797 | HTMLFile.prototype._ = function (data, doc) { 2798 | this.onData(data); 2799 | try { 2800 | var script = doc.getElementsByTagName('script')[0]; 2801 | script.parentNode.removeChild(script); 2802 | } catch (e) { } 2803 | }; 2804 | 2805 | /** 2806 | * Destroy the established connection, iframe and `htmlfile`. 2807 | * And calls the `CollectGarbage` function of Internet Explorer 2808 | * to release the memory. 2809 | * 2810 | * @api private 2811 | */ 2812 | 2813 | HTMLFile.prototype.destroy = function () { 2814 | if (this.iframe){ 2815 | try { 2816 | this.iframe.src = 'about:blank'; 2817 | } catch(e){} 2818 | 2819 | this.doc = null; 2820 | this.iframe.parentNode.removeChild(this.iframe); 2821 | this.iframe = null; 2822 | 2823 | CollectGarbage(); 2824 | } 2825 | }; 2826 | 2827 | /** 2828 | * Disconnects the established connection. 2829 | * 2830 | * @returns {Transport} Chaining. 2831 | * @api public 2832 | */ 2833 | 2834 | HTMLFile.prototype.close = function () { 2835 | this.destroy(); 2836 | return io.Transport.XHR.prototype.close.call(this); 2837 | }; 2838 | 2839 | /** 2840 | * Checks if the browser supports this transport. The browser 2841 | * must have an `Ac...eXObject` implementation. 2842 | * 2843 | * @return {Boolean} 2844 | * @api public 2845 | */ 2846 | 2847 | HTMLFile.check = function () { 2848 | if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ 2849 | try { 2850 | var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); 2851 | return a && io.Transport.XHR.check(); 2852 | } catch(e){} 2853 | } 2854 | return false; 2855 | }; 2856 | 2857 | /** 2858 | * Check if cross domain requests are supported. 2859 | * 2860 | * @returns {Boolean} 2861 | * @api public 2862 | */ 2863 | 2864 | HTMLFile.xdomainCheck = function () { 2865 | // we can probably do handling for sub-domains, we should 2866 | // test that it's cross domain but a subdomain here 2867 | return false; 2868 | }; 2869 | 2870 | /** 2871 | * Add the transport to your public io.transports array. 2872 | * 2873 | * @api private 2874 | */ 2875 | 2876 | io.transports.push('htmlfile'); 2877 | 2878 | })( 2879 | 'undefined' != typeof io ? io.Transport : module.exports 2880 | , 'undefined' != typeof io ? io : module.parent.exports 2881 | ); 2882 | 2883 | /** 2884 | * socket.io 2885 | * Copyright(c) 2011 LearnBoost 2886 | * MIT Licensed 2887 | */ 2888 | 2889 | (function (exports, io, global) { 2890 | 2891 | /** 2892 | * Expose constructor. 2893 | */ 2894 | 2895 | exports['xhr-polling'] = XHRPolling; 2896 | 2897 | /** 2898 | * The XHR-polling transport uses long polling XHR requests to create a 2899 | * "persistent" connection with the server. 2900 | * 2901 | * @constructor 2902 | * @api public 2903 | */ 2904 | 2905 | function XHRPolling () { 2906 | io.Transport.XHR.apply(this, arguments); 2907 | }; 2908 | 2909 | /** 2910 | * Inherits from XHR transport. 2911 | */ 2912 | 2913 | io.util.inherit(XHRPolling, io.Transport.XHR); 2914 | 2915 | /** 2916 | * Merge the properties from XHR transport 2917 | */ 2918 | 2919 | io.util.merge(XHRPolling, io.Transport.XHR); 2920 | 2921 | /** 2922 | * Transport name 2923 | * 2924 | * @api public 2925 | */ 2926 | 2927 | XHRPolling.prototype.name = 'xhr-polling'; 2928 | 2929 | /** 2930 | * Establish a connection, for iPhone and Android this will be done once the page 2931 | * is loaded. 2932 | * 2933 | * @returns {Transport} Chaining. 2934 | * @api public 2935 | */ 2936 | 2937 | XHRPolling.prototype.open = function () { 2938 | var self = this; 2939 | 2940 | io.Transport.XHR.prototype.open.call(self); 2941 | return false; 2942 | }; 2943 | 2944 | /** 2945 | * Starts a XHR request to wait for incoming messages. 2946 | * 2947 | * @api private 2948 | */ 2949 | 2950 | function empty () {}; 2951 | 2952 | XHRPolling.prototype.get = function () { 2953 | if (!this.open) return; 2954 | 2955 | var self = this; 2956 | 2957 | function stateChange () { 2958 | if (this.readyState == 4) { 2959 | this.onreadystatechange = empty; 2960 | 2961 | if (this.status == 200) { 2962 | self.onData(this.responseText); 2963 | self.get(); 2964 | } else { 2965 | self.onClose(); 2966 | } 2967 | } 2968 | }; 2969 | 2970 | function onload () { 2971 | this.onload = empty; 2972 | this.onerror = empty; 2973 | self.onData(this.responseText); 2974 | self.get(); 2975 | }; 2976 | 2977 | function onerror () { 2978 | self.onClose(); 2979 | }; 2980 | 2981 | this.xhr = this.request(); 2982 | 2983 | if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { 2984 | this.xhr.onload = onload; 2985 | this.xhr.onerror = onerror; 2986 | } else { 2987 | this.xhr.onreadystatechange = stateChange; 2988 | } 2989 | 2990 | this.xhr.send(null); 2991 | }; 2992 | 2993 | /** 2994 | * Handle the unclean close behavior. 2995 | * 2996 | * @api private 2997 | */ 2998 | 2999 | XHRPolling.prototype.onClose = function () { 3000 | io.Transport.XHR.prototype.onClose.call(this); 3001 | 3002 | if (this.xhr) { 3003 | this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; 3004 | try { 3005 | this.xhr.abort(); 3006 | } catch(e){} 3007 | this.xhr = null; 3008 | } 3009 | }; 3010 | 3011 | /** 3012 | * Webkit based browsers show a infinit spinner when you start a XHR request 3013 | * before the browsers onload event is called so we need to defer opening of 3014 | * the transport until the onload event is called. Wrapping the cb in our 3015 | * defer method solve this. 3016 | * 3017 | * @param {Socket} socket The socket instance that needs a transport 3018 | * @param {Function} fn The callback 3019 | * @api private 3020 | */ 3021 | 3022 | XHRPolling.prototype.ready = function (socket, fn) { 3023 | var self = this; 3024 | 3025 | io.util.defer(function () { 3026 | fn.call(self); 3027 | }); 3028 | }; 3029 | 3030 | /** 3031 | * Add the transport to your public io.transports array. 3032 | * 3033 | * @api private 3034 | */ 3035 | 3036 | io.transports.push('xhr-polling'); 3037 | 3038 | })( 3039 | 'undefined' != typeof io ? io.Transport : module.exports 3040 | , 'undefined' != typeof io ? io : module.parent.exports 3041 | , this 3042 | ); 3043 | 3044 | /** 3045 | * socket.io 3046 | * Copyright(c) 2011 LearnBoost 3047 | * MIT Licensed 3048 | */ 3049 | 3050 | (function (exports, io, global) { 3051 | /** 3052 | * There is a way to hide the loading indicator in Firefox. If you create and 3053 | * remove a iframe it will stop showing the current loading indicator. 3054 | * Unfortunately we can't feature detect that and UA sniffing is evil. 3055 | * 3056 | * @api private 3057 | */ 3058 | 3059 | var indicator = global.document && "MozAppearance" in 3060 | global.document.documentElement.style; 3061 | 3062 | /** 3063 | * Expose constructor. 3064 | */ 3065 | 3066 | exports['jsonp-polling'] = JSONPPolling; 3067 | 3068 | /** 3069 | * The JSONP transport creates an persistent connection by dynamically 3070 | * inserting a script tag in the page. This script tag will receive the 3071 | * information of the Socket.IO server. When new information is received 3072 | * it creates a new script tag for the new data stream. 3073 | * 3074 | * @constructor 3075 | * @extends {io.Transport.xhr-polling} 3076 | * @api public 3077 | */ 3078 | 3079 | function JSONPPolling (socket) { 3080 | io.Transport['xhr-polling'].apply(this, arguments); 3081 | 3082 | this.index = io.j.length; 3083 | 3084 | var self = this; 3085 | 3086 | io.j.push(function (msg) { 3087 | self._(msg); 3088 | }); 3089 | }; 3090 | 3091 | /** 3092 | * Inherits from XHR polling transport. 3093 | */ 3094 | 3095 | io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); 3096 | 3097 | /** 3098 | * Transport name 3099 | * 3100 | * @api public 3101 | */ 3102 | 3103 | JSONPPolling.prototype.name = 'jsonp-polling'; 3104 | 3105 | /** 3106 | * Posts a encoded message to the Socket.IO server using an iframe. 3107 | * The iframe is used because script tags can create POST based requests. 3108 | * The iframe is positioned outside of the view so the user does not 3109 | * notice it's existence. 3110 | * 3111 | * @param {String} data A encoded message. 3112 | * @api private 3113 | */ 3114 | 3115 | JSONPPolling.prototype.post = function (data) { 3116 | var self = this 3117 | , query = io.util.query( 3118 | this.socket.options.query 3119 | , 't='+ (+new Date) + '&i=' + this.index 3120 | ); 3121 | 3122 | if (!this.form) { 3123 | var form = document.createElement('form') 3124 | , area = document.createElement('textarea') 3125 | , id = this.iframeId = 'socketio_iframe_' + this.index 3126 | , iframe; 3127 | 3128 | form.className = 'socketio'; 3129 | form.style.position = 'absolute'; 3130 | form.style.top = '0px'; 3131 | form.style.left = '0px'; 3132 | form.style.display = 'none'; 3133 | form.target = id; 3134 | form.method = 'POST'; 3135 | form.setAttribute('accept-charset', 'utf-8'); 3136 | area.name = 'd'; 3137 | form.appendChild(area); 3138 | document.body.appendChild(form); 3139 | 3140 | this.form = form; 3141 | this.area = area; 3142 | } 3143 | 3144 | this.form.action = this.prepareUrl() + query; 3145 | 3146 | function complete () { 3147 | initIframe(); 3148 | self.socket.setBuffer(false); 3149 | }; 3150 | 3151 | function initIframe () { 3152 | if (self.iframe) { 3153 | self.form.removeChild(self.iframe); 3154 | } 3155 | 3156 | try { 3157 | // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) 3158 | iframe = document.createElement('