├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── hello │ ├── Application.java │ ├── CustomSubProtocolWebSocketHandler.java │ ├── SessionHandler.java │ └── WebSocketConfig.java └── resources └── static ├── index.html ├── sockjs-0.3.4.js └── stomp.js /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-websocket-disconnect 2 | 1. Run the application using: mvn spring-boot:run 3 | 2. Point your browser at http://localhost:8080 and click "Connect" button 4 |
There is job on background, which closes websocket session each 10 seconds 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | spring-websocket-disconnect 7 | spring-websocket-disconnect 8 | 0.1.0 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.2.5.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-websocket 20 | 21 | 22 | org.springframework 23 | spring-messaging 24 | 25 | 26 | 27 | 28 | 1.8 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/hello/Application.java: -------------------------------------------------------------------------------- 1 | package hello; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/hello/CustomSubProtocolWebSocketHandler.java: -------------------------------------------------------------------------------- 1 | package hello; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.messaging.MessageChannel; 7 | import org.springframework.messaging.SubscribableChannel; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.socket.WebSocketSession; 10 | import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; 11 | 12 | import java.io.IOException; 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.ScheduledExecutorService; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | public class CustomSubProtocolWebSocketHandler extends SubProtocolWebSocketHandler { 20 | private static final Logger LOGGER = LoggerFactory.getLogger(CustomSubProtocolWebSocketHandler.class); 21 | 22 | @Autowired 23 | private SessionHandler sessionHandler; 24 | 25 | public CustomSubProtocolWebSocketHandler(MessageChannel clientInboundChannel, SubscribableChannel clientOutboundChannel) { 26 | super(clientInboundChannel, clientOutboundChannel); 27 | } 28 | 29 | 30 | @Override 31 | public void afterConnectionEstablished(WebSocketSession session) throws Exception { 32 | LOGGER.info("New websocket connection was established"); 33 | sessionHandler.register(session); 34 | super.afterConnectionEstablished(session); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/hello/SessionHandler.java: -------------------------------------------------------------------------------- 1 | package hello; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.socket.WebSocketSession; 7 | 8 | import java.io.IOException; 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ScheduledExecutorService; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @Service 16 | public class SessionHandler { 17 | private static final Logger LOGGER = LoggerFactory.getLogger(SessionHandler.class); 18 | private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); 19 | 20 | private final Map sessionMap = new ConcurrentHashMap<>(); 21 | 22 | public SessionHandler() { 23 | scheduler.scheduleAtFixedRate(new Runnable() { 24 | @Override 25 | public void run() { 26 | sessionMap.keySet().forEach(k -> { 27 | try { 28 | sessionMap.get(k).close(); 29 | sessionMap.remove(k); 30 | } catch (IOException e) { 31 | LOGGER.error("Error while closing websocket session: {}", e); 32 | } 33 | }); 34 | } 35 | }, 10, 10, TimeUnit.SECONDS); 36 | } 37 | 38 | public void register(WebSocketSession session) { 39 | sessionMap.put(session.getId(), session); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/hello/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package hello; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.messaging.converter.MessageConverter; 6 | import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; 7 | import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; 8 | import org.springframework.messaging.simp.config.ChannelRegistration; 9 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 10 | import org.springframework.web.socket.WebSocketHandler; 11 | import org.springframework.web.socket.config.annotation.*; 12 | 13 | import java.util.List; 14 | 15 | @Configuration 16 | @EnableWebSocketMessageBroker 17 | public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport implements WebSocketMessageBrokerConfigurer { 18 | 19 | @Override 20 | public void configureWebSocketTransport(WebSocketTransportRegistration registry) { 21 | super.configureWebSocketTransport(registry); 22 | } 23 | 24 | @Override 25 | public boolean configureMessageConverters(List messageConverters) { 26 | return super.configureMessageConverters(messageConverters); 27 | } 28 | 29 | @Override 30 | public void configureClientInboundChannel(ChannelRegistration registration) { 31 | super.configureClientInboundChannel(registration); 32 | } 33 | 34 | @Override 35 | public void configureClientOutboundChannel(ChannelRegistration registration) { 36 | super.configureClientOutboundChannel(registration); 37 | } 38 | 39 | 40 | @Override 41 | public void addArgumentResolvers(List argumentResolvers) { 42 | super.addArgumentResolvers(argumentResolvers); 43 | } 44 | 45 | @Override 46 | public void addReturnValueHandlers(List returnValueHandlers) { 47 | super.addReturnValueHandlers(returnValueHandlers); 48 | } 49 | 50 | @Override 51 | public void configureMessageBroker(MessageBrokerRegistry config) { 52 | config.enableSimpleBroker("/topic"); 53 | config.setApplicationDestinationPrefixes("/app"); 54 | } 55 | 56 | @Override 57 | public void registerStompEndpoints(StompEndpointRegistry registry) { 58 | registry.addEndpoint("/hello").withSockJS(); 59 | } 60 | 61 | @Bean 62 | public WebSocketHandler subProtocolWebSocketHandler() { 63 | return new CustomSubProtocolWebSocketHandler(clientInboundChannel(), clientOutboundChannel()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello WebSocket 5 | 6 | 7 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/static/sockjs-0.3.4.js: -------------------------------------------------------------------------------- 1 | /* SockJS client, version 0.3.4, http://sockjs.org, MIT License 2 | 3 | Copyright (c) 2011-2012 VMware, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | // JSON2 by Douglas Crockford (minified). 25 | var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c 1) { 71 | this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) ); 72 | } else { 73 | delete this._listeners[eventType]; 74 | } 75 | return; 76 | } 77 | return; 78 | }; 79 | 80 | REventTarget.prototype.dispatchEvent = function (event) { 81 | var t = event.type; 82 | var args = Array.prototype.slice.call(arguments, 0); 83 | if (this['on'+t]) { 84 | this['on'+t].apply(this, args); 85 | } 86 | if (this._listeners && t in this._listeners) { 87 | for(var i=0; i < this._listeners[t].length; i++) { 88 | this._listeners[t][i].apply(this, args); 89 | } 90 | } 91 | }; 92 | // [*] End of lib/reventtarget.js 93 | 94 | 95 | // [*] Including lib/simpleevent.js 96 | /* 97 | * ***** BEGIN LICENSE BLOCK ***** 98 | * Copyright (c) 2011-2012 VMware, Inc. 99 | * 100 | * For the license see COPYING. 101 | * ***** END LICENSE BLOCK ***** 102 | */ 103 | 104 | var SimpleEvent = function(type, obj) { 105 | this.type = type; 106 | if (typeof obj !== 'undefined') { 107 | for(var k in obj) { 108 | if (!obj.hasOwnProperty(k)) continue; 109 | this[k] = obj[k]; 110 | } 111 | } 112 | }; 113 | 114 | SimpleEvent.prototype.toString = function() { 115 | var r = []; 116 | for(var k in this) { 117 | if (!this.hasOwnProperty(k)) continue; 118 | var v = this[k]; 119 | if (typeof v === 'function') v = '[function]'; 120 | r.push(k + '=' + v); 121 | } 122 | return 'SimpleEvent(' + r.join(', ') + ')'; 123 | }; 124 | // [*] End of lib/simpleevent.js 125 | 126 | 127 | // [*] Including lib/eventemitter.js 128 | /* 129 | * ***** BEGIN LICENSE BLOCK ***** 130 | * Copyright (c) 2011-2012 VMware, Inc. 131 | * 132 | * For the license see COPYING. 133 | * ***** END LICENSE BLOCK ***** 134 | */ 135 | 136 | var EventEmitter = function(events) { 137 | var that = this; 138 | that._events = events || []; 139 | that._listeners = {}; 140 | }; 141 | EventEmitter.prototype.emit = function(type) { 142 | var that = this; 143 | that._verifyType(type); 144 | if (that._nuked) return; 145 | 146 | var args = Array.prototype.slice.call(arguments, 1); 147 | if (that['on'+type]) { 148 | that['on'+type].apply(that, args); 149 | } 150 | if (type in that._listeners) { 151 | for(var i = 0; i < that._listeners[type].length; i++) { 152 | that._listeners[type][i].apply(that, args); 153 | } 154 | } 155 | }; 156 | 157 | EventEmitter.prototype.on = function(type, callback) { 158 | var that = this; 159 | that._verifyType(type); 160 | if (that._nuked) return; 161 | 162 | if (!(type in that._listeners)) { 163 | that._listeners[type] = []; 164 | } 165 | that._listeners[type].push(callback); 166 | }; 167 | 168 | EventEmitter.prototype._verifyType = function(type) { 169 | var that = this; 170 | if (utils.arrIndexOf(that._events, type) === -1) { 171 | utils.log('Event ' + JSON.stringify(type) + 172 | ' not listed ' + JSON.stringify(that._events) + 173 | ' in ' + that); 174 | } 175 | }; 176 | 177 | EventEmitter.prototype.nuke = function() { 178 | var that = this; 179 | that._nuked = true; 180 | for(var i=0; i= 3000 && code <= 4999); 266 | }; 267 | 268 | // See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/ 269 | // and RFC 2988. 270 | utils.countRTO = function (rtt) { 271 | var rto; 272 | if (rtt > 100) { 273 | rto = 3 * rtt; // rto > 300msec 274 | } else { 275 | rto = rtt + 200; // 200msec < rto <= 300msec 276 | } 277 | return rto; 278 | } 279 | 280 | utils.log = function() { 281 | if (_window.console && console.log && console.log.apply) { 282 | console.log.apply(console, arguments); 283 | } 284 | }; 285 | 286 | utils.bind = function(fun, that) { 287 | if (fun.bind) { 288 | return fun.bind(that); 289 | } else { 290 | return function() { 291 | return fun.apply(that, arguments); 292 | }; 293 | } 294 | }; 295 | 296 | utils.flatUrl = function(url) { 297 | return url.indexOf('?') === -1 && url.indexOf('#') === -1; 298 | }; 299 | 300 | utils.amendUrl = function(url) { 301 | var dl = _document.location; 302 | if (!url) { 303 | throw new Error('Wrong url for SockJS'); 304 | } 305 | if (!utils.flatUrl(url)) { 306 | throw new Error('Only basic urls are supported in SockJS'); 307 | } 308 | 309 | // '//abc' --> 'http://abc' 310 | if (url.indexOf('//') === 0) { 311 | url = dl.protocol + url; 312 | } 313 | // '/abc' --> 'http://localhost:80/abc' 314 | if (url.indexOf('/') === 0) { 315 | url = dl.protocol + '//' + dl.host + url; 316 | } 317 | // strip trailing slashes 318 | url = url.replace(/[/]+$/,''); 319 | return url; 320 | }; 321 | 322 | // IE doesn't support [].indexOf. 323 | utils.arrIndexOf = function(arr, obj){ 324 | for(var i=0; i < arr.length; i++){ 325 | if(arr[i] === obj){ 326 | return i; 327 | } 328 | } 329 | return -1; 330 | }; 331 | 332 | utils.arrSkip = function(arr, obj) { 333 | var idx = utils.arrIndexOf(arr, obj); 334 | if (idx === -1) { 335 | return arr.slice(); 336 | } else { 337 | var dst = arr.slice(0, idx); 338 | return dst.concat(arr.slice(idx+1)); 339 | } 340 | }; 341 | 342 | // Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df 343 | utils.isArray = Array.isArray || function(value) { 344 | return {}.toString.call(value).indexOf('Array') >= 0 345 | }; 346 | 347 | utils.delay = function(t, fun) { 348 | if(typeof t === 'function') { 349 | fun = t; 350 | t = 0; 351 | } 352 | return setTimeout(fun, t); 353 | }; 354 | 355 | 356 | // Chars worth escaping, as defined by Douglas Crockford: 357 | // https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196 358 | var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 359 | json_lookup = { 360 | "\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003", 361 | "\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007", 362 | "\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r", 363 | "\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011", 364 | "\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015", 365 | "\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019", 366 | "\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d", 367 | "\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\", 368 | "\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", 369 | "\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086", 370 | "\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a", 371 | "\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e", 372 | "\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092", 373 | "\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096", 374 | "\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a", 375 | "\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e", 376 | "\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601", 377 | "\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f", 378 | "\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d", 379 | "\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029", 380 | "\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d", 381 | "\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061", 382 | "\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065", 383 | "\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069", 384 | "\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d", 385 | "\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0", 386 | "\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4", 387 | "\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8", 388 | "\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc", 389 | "\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"}; 390 | 391 | // Some extra characters that Chrome gets wrong, and substitutes with 392 | // something else on the wire. 393 | var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g, 394 | extra_lookup; 395 | 396 | // JSON Quote string. Use native implementation when possible. 397 | var JSONQuote = (JSON && JSON.stringify) || function(string) { 398 | json_escapable.lastIndex = 0; 399 | if (json_escapable.test(string)) { 400 | string = string.replace(json_escapable, function(a) { 401 | return json_lookup[a]; 402 | }); 403 | } 404 | return '"' + string + '"'; 405 | }; 406 | 407 | // This may be quite slow, so let's delay until user actually uses bad 408 | // characters. 409 | var unroll_lookup = function(escapable) { 410 | var i; 411 | var unrolled = {} 412 | var c = [] 413 | for(i=0; i<65536; i++) { 414 | c.push( String.fromCharCode(i) ); 415 | } 416 | escapable.lastIndex = 0; 417 | c.join('').replace(escapable, function (a) { 418 | unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 419 | return ''; 420 | }); 421 | escapable.lastIndex = 0; 422 | return unrolled; 423 | }; 424 | 425 | // Quote string, also taking care of unicode characters that browsers 426 | // often break. Especially, take care of unicode surrogates: 427 | // http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates 428 | utils.quote = function(string) { 429 | var quoted = JSONQuote(string); 430 | 431 | // In most cases this should be very fast and good enough. 432 | extra_escapable.lastIndex = 0; 433 | if(!extra_escapable.test(quoted)) { 434 | return quoted; 435 | } 436 | 437 | if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable); 438 | 439 | return quoted.replace(extra_escapable, function(a) { 440 | return extra_lookup[a]; 441 | }); 442 | } 443 | 444 | var _all_protocols = ['websocket', 445 | 'xdr-streaming', 446 | 'xhr-streaming', 447 | 'iframe-eventsource', 448 | 'iframe-htmlfile', 449 | 'xdr-polling', 450 | 'xhr-polling', 451 | 'iframe-xhr-polling', 452 | 'jsonp-polling']; 453 | 454 | utils.probeProtocols = function() { 455 | var probed = {}; 456 | for(var i=0; i<_all_protocols.length; i++) { 457 | var protocol = _all_protocols[i]; 458 | // User can have a typo in protocol name. 459 | probed[protocol] = SockJS[protocol] && 460 | SockJS[protocol].enabled(); 461 | } 462 | return probed; 463 | }; 464 | 465 | utils.detectProtocols = function(probed, protocols_whitelist, info) { 466 | var pe = {}, 467 | protocols = []; 468 | if (!protocols_whitelist) protocols_whitelist = _all_protocols; 469 | for(var i=0; i 0) { 479 | maybe_push(protos); 480 | } 481 | } 482 | } 483 | 484 | // 1. Websocket 485 | if (info.websocket !== false) { 486 | maybe_push(['websocket']); 487 | } 488 | 489 | // 2. Streaming 490 | if (pe['xhr-streaming'] && !info.null_origin) { 491 | protocols.push('xhr-streaming'); 492 | } else { 493 | if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { 494 | protocols.push('xdr-streaming'); 495 | } else { 496 | maybe_push(['iframe-eventsource', 497 | 'iframe-htmlfile']); 498 | } 499 | } 500 | 501 | // 3. Polling 502 | if (pe['xhr-polling'] && !info.null_origin) { 503 | protocols.push('xhr-polling'); 504 | } else { 505 | if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { 506 | protocols.push('xdr-polling'); 507 | } else { 508 | maybe_push(['iframe-xhr-polling', 509 | 'jsonp-polling']); 510 | } 511 | } 512 | return protocols; 513 | } 514 | // [*] End of lib/utils.js 515 | 516 | 517 | // [*] Including lib/dom.js 518 | /* 519 | * ***** BEGIN LICENSE BLOCK ***** 520 | * Copyright (c) 2011-2012 VMware, Inc. 521 | * 522 | * For the license see COPYING. 523 | * ***** END LICENSE BLOCK ***** 524 | */ 525 | 526 | // May be used by htmlfile jsonp and transports. 527 | var MPrefix = '_sockjs_global'; 528 | utils.createHook = function() { 529 | var window_id = 'a' + utils.random_string(8); 530 | if (!(MPrefix in _window)) { 531 | var map = {}; 532 | _window[MPrefix] = function(window_id) { 533 | if (!(window_id in map)) { 534 | map[window_id] = { 535 | id: window_id, 536 | del: function() {delete map[window_id];} 537 | }; 538 | } 539 | return map[window_id]; 540 | } 541 | } 542 | return _window[MPrefix](window_id); 543 | }; 544 | 545 | 546 | 547 | utils.attachMessage = function(listener) { 548 | utils.attachEvent('message', listener); 549 | }; 550 | utils.attachEvent = function(event, listener) { 551 | if (typeof _window.addEventListener !== 'undefined') { 552 | _window.addEventListener(event, listener, false); 553 | } else { 554 | // IE quirks. 555 | // According to: http://stevesouders.com/misc/test-postmessage.php 556 | // the message gets delivered only to 'document', not 'window'. 557 | _document.attachEvent("on" + event, listener); 558 | // I get 'window' for ie8. 559 | _window.attachEvent("on" + event, listener); 560 | } 561 | }; 562 | 563 | utils.detachMessage = function(listener) { 564 | utils.detachEvent('message', listener); 565 | }; 566 | utils.detachEvent = function(event, listener) { 567 | if (typeof _window.addEventListener !== 'undefined') { 568 | _window.removeEventListener(event, listener, false); 569 | } else { 570 | _document.detachEvent("on" + event, listener); 571 | _window.detachEvent("on" + event, listener); 572 | } 573 | }; 574 | 575 | 576 | var on_unload = {}; 577 | // Things registered after beforeunload are to be called immediately. 578 | var after_unload = false; 579 | 580 | var trigger_unload_callbacks = function() { 581 | for(var ref in on_unload) { 582 | on_unload[ref](); 583 | delete on_unload[ref]; 584 | }; 585 | }; 586 | 587 | var unload_triggered = function() { 588 | if(after_unload) return; 589 | after_unload = true; 590 | trigger_unload_callbacks(); 591 | }; 592 | 593 | // 'unload' alone is not reliable in opera within an iframe, but we 594 | // can't use `beforeunload` as IE fires it on javascript: links. 595 | utils.attachEvent('unload', unload_triggered); 596 | 597 | utils.unload_add = function(listener) { 598 | var ref = utils.random_string(8); 599 | on_unload[ref] = listener; 600 | if (after_unload) { 601 | utils.delay(trigger_unload_callbacks); 602 | } 603 | return ref; 604 | }; 605 | utils.unload_del = function(ref) { 606 | if (ref in on_unload) 607 | delete on_unload[ref]; 608 | }; 609 | 610 | 611 | utils.createIframe = function (iframe_url, error_callback) { 612 | var iframe = _document.createElement('iframe'); 613 | var tref, unload_ref; 614 | var unattach = function() { 615 | clearTimeout(tref); 616 | // Explorer had problems with that. 617 | try {iframe.onload = null;} catch (x) {} 618 | iframe.onerror = null; 619 | }; 620 | var cleanup = function() { 621 | if (iframe) { 622 | unattach(); 623 | // This timeout makes chrome fire onbeforeunload event 624 | // within iframe. Without the timeout it goes straight to 625 | // onunload. 626 | setTimeout(function() { 627 | if(iframe) { 628 | iframe.parentNode.removeChild(iframe); 629 | } 630 | iframe = null; 631 | }, 0); 632 | utils.unload_del(unload_ref); 633 | } 634 | }; 635 | var onerror = function(r) { 636 | if (iframe) { 637 | cleanup(); 638 | error_callback(r); 639 | } 640 | }; 641 | var post = function(msg, origin) { 642 | try { 643 | // When the iframe is not loaded, IE raises an exception 644 | // on 'contentWindow'. 645 | if (iframe && iframe.contentWindow) { 646 | iframe.contentWindow.postMessage(msg, origin); 647 | } 648 | } catch (x) {}; 649 | }; 650 | 651 | iframe.src = iframe_url; 652 | iframe.style.display = 'none'; 653 | iframe.style.position = 'absolute'; 654 | iframe.onerror = function(){onerror('onerror');}; 655 | iframe.onload = function() { 656 | // `onload` is triggered before scripts on the iframe are 657 | // executed. Give it few seconds to actually load stuff. 658 | clearTimeout(tref); 659 | tref = setTimeout(function(){onerror('onload timeout');}, 2000); 660 | }; 661 | _document.body.appendChild(iframe); 662 | tref = setTimeout(function(){onerror('timeout');}, 15000); 663 | unload_ref = utils.unload_add(cleanup); 664 | return { 665 | post: post, 666 | cleanup: cleanup, 667 | loaded: unattach 668 | }; 669 | }; 670 | 671 | utils.createHtmlfile = function (iframe_url, error_callback) { 672 | var doc = new ActiveXObject('htmlfile'); 673 | var tref, unload_ref; 674 | var iframe; 675 | var unattach = function() { 676 | clearTimeout(tref); 677 | }; 678 | var cleanup = function() { 679 | if (doc) { 680 | unattach(); 681 | utils.unload_del(unload_ref); 682 | iframe.parentNode.removeChild(iframe); 683 | iframe = doc = null; 684 | CollectGarbage(); 685 | } 686 | }; 687 | var onerror = function(r) { 688 | if (doc) { 689 | cleanup(); 690 | error_callback(r); 691 | } 692 | }; 693 | var post = function(msg, origin) { 694 | try { 695 | // When the iframe is not loaded, IE raises an exception 696 | // on 'contentWindow'. 697 | if (iframe && iframe.contentWindow) { 698 | iframe.contentWindow.postMessage(msg, origin); 699 | } 700 | } catch (x) {}; 701 | }; 702 | 703 | doc.open(); 704 | doc.write('' + 705 | 'document.domain="' + document.domain + '";' + 706 | ''); 707 | doc.close(); 708 | doc.parentWindow[WPrefix] = _window[WPrefix]; 709 | var c = doc.createElement('div'); 710 | doc.body.appendChild(c); 711 | iframe = doc.createElement('iframe'); 712 | c.appendChild(iframe); 713 | iframe.src = iframe_url; 714 | tref = setTimeout(function(){onerror('timeout');}, 15000); 715 | unload_ref = utils.unload_add(cleanup); 716 | return { 717 | post: post, 718 | cleanup: cleanup, 719 | loaded: unattach 720 | }; 721 | }; 722 | // [*] End of lib/dom.js 723 | 724 | 725 | // [*] Including lib/dom2.js 726 | /* 727 | * ***** BEGIN LICENSE BLOCK ***** 728 | * Copyright (c) 2011-2012 VMware, Inc. 729 | * 730 | * For the license see COPYING. 731 | * ***** END LICENSE BLOCK ***** 732 | */ 733 | 734 | var AbstractXHRObject = function(){}; 735 | AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']); 736 | 737 | AbstractXHRObject.prototype._start = function(method, url, payload, opts) { 738 | var that = this; 739 | 740 | try { 741 | that.xhr = new XMLHttpRequest(); 742 | } catch(x) {}; 743 | 744 | if (!that.xhr) { 745 | try { 746 | that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); 747 | } catch(x) {}; 748 | } 749 | if (_window.ActiveXObject || _window.XDomainRequest) { 750 | // IE8 caches even POSTs 751 | url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); 752 | } 753 | 754 | // Explorer tends to keep connection open, even after the 755 | // tab gets closed: http://bugs.jquery.com/ticket/5280 756 | that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); 757 | try { 758 | that.xhr.open(method, url, true); 759 | } catch(e) { 760 | // IE raises an exception on wrong port. 761 | that.emit('finish', 0, ''); 762 | that._cleanup(); 763 | return; 764 | }; 765 | 766 | if (!opts || !opts.no_credentials) { 767 | // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : 768 | // "This never affects same-site requests." 769 | that.xhr.withCredentials = 'true'; 770 | } 771 | if (opts && opts.headers) { 772 | for(var key in opts.headers) { 773 | that.xhr.setRequestHeader(key, opts.headers[key]); 774 | } 775 | } 776 | 777 | that.xhr.onreadystatechange = function() { 778 | if (that.xhr) { 779 | var x = that.xhr; 780 | switch (x.readyState) { 781 | case 3: 782 | // IE doesn't like peeking into responseText or status 783 | // on Microsoft.XMLHTTP and readystate=3 784 | try { 785 | var status = x.status; 786 | var text = x.responseText; 787 | } catch (x) {}; 788 | // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 789 | if (status === 1223) status = 204; 790 | 791 | // IE does return readystate == 3 for 404 answers. 792 | if (text && text.length > 0) { 793 | that.emit('chunk', status, text); 794 | } 795 | break; 796 | case 4: 797 | var status = x.status; 798 | // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 799 | if (status === 1223) status = 204; 800 | 801 | that.emit('finish', status, x.responseText); 802 | that._cleanup(false); 803 | break; 804 | } 805 | } 806 | }; 807 | that.xhr.send(payload); 808 | }; 809 | 810 | AbstractXHRObject.prototype._cleanup = function(abort) { 811 | var that = this; 812 | if (!that.xhr) return; 813 | utils.unload_del(that.unload_ref); 814 | 815 | // IE needs this field to be a function 816 | that.xhr.onreadystatechange = function(){}; 817 | 818 | if (abort) { 819 | try { 820 | that.xhr.abort(); 821 | } catch(x) {}; 822 | } 823 | that.unload_ref = that.xhr = null; 824 | }; 825 | 826 | AbstractXHRObject.prototype.close = function() { 827 | var that = this; 828 | that.nuke(); 829 | that._cleanup(true); 830 | }; 831 | 832 | var XHRCorsObject = utils.XHRCorsObject = function() { 833 | var that = this, args = arguments; 834 | utils.delay(function(){that._start.apply(that, args);}); 835 | }; 836 | XHRCorsObject.prototype = new AbstractXHRObject(); 837 | 838 | var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) { 839 | var that = this; 840 | utils.delay(function(){ 841 | that._start(method, url, payload, { 842 | no_credentials: true 843 | }); 844 | }); 845 | }; 846 | XHRLocalObject.prototype = new AbstractXHRObject(); 847 | 848 | 849 | 850 | // References: 851 | // http://ajaxian.com/archives/100-line-ajax-wrapper 852 | // http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx 853 | var XDRObject = utils.XDRObject = function(method, url, payload) { 854 | var that = this; 855 | utils.delay(function(){that._start(method, url, payload);}); 856 | }; 857 | XDRObject.prototype = new EventEmitter(['chunk', 'finish']); 858 | XDRObject.prototype._start = function(method, url, payload) { 859 | var that = this; 860 | var xdr = new XDomainRequest(); 861 | // IE caches even POSTs 862 | url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); 863 | 864 | var onerror = xdr.ontimeout = xdr.onerror = function() { 865 | that.emit('finish', 0, ''); 866 | that._cleanup(false); 867 | }; 868 | xdr.onprogress = function() { 869 | that.emit('chunk', 200, xdr.responseText); 870 | }; 871 | xdr.onload = function() { 872 | that.emit('finish', 200, xdr.responseText); 873 | that._cleanup(false); 874 | }; 875 | that.xdr = xdr; 876 | that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); 877 | try { 878 | // Fails with AccessDenied if port number is bogus 879 | that.xdr.open(method, url); 880 | that.xdr.send(payload); 881 | } catch(x) { 882 | onerror(); 883 | } 884 | }; 885 | 886 | XDRObject.prototype._cleanup = function(abort) { 887 | var that = this; 888 | if (!that.xdr) return; 889 | utils.unload_del(that.unload_ref); 890 | 891 | that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress = 892 | that.xdr.onload = null; 893 | if (abort) { 894 | try { 895 | that.xdr.abort(); 896 | } catch(x) {}; 897 | } 898 | that.unload_ref = that.xdr = null; 899 | }; 900 | 901 | XDRObject.prototype.close = function() { 902 | var that = this; 903 | that.nuke(); 904 | that._cleanup(true); 905 | }; 906 | 907 | // 1. Is natively via XHR 908 | // 2. Is natively via XDR 909 | // 3. Nope, but postMessage is there so it should work via the Iframe. 910 | // 4. Nope, sorry. 911 | utils.isXHRCorsCapable = function() { 912 | if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { 913 | return 1; 914 | } 915 | // XDomainRequest doesn't work if page is served from file:// 916 | if (_window.XDomainRequest && _document.domain) { 917 | return 2; 918 | } 919 | if (IframeTransport.enabled()) { 920 | return 3; 921 | } 922 | return 4; 923 | }; 924 | // [*] End of lib/dom2.js 925 | 926 | 927 | // [*] Including lib/sockjs.js 928 | /* 929 | * ***** BEGIN LICENSE BLOCK ***** 930 | * Copyright (c) 2011-2012 VMware, Inc. 931 | * 932 | * For the license see COPYING. 933 | * ***** END LICENSE BLOCK ***** 934 | */ 935 | 936 | var SockJS = function(url, dep_protocols_whitelist, options) { 937 | if (this === _window) { 938 | // makes `new` optional 939 | return new SockJS(url, dep_protocols_whitelist, options); 940 | } 941 | 942 | var that = this, protocols_whitelist; 943 | that._options = {devel: false, debug: false, protocols_whitelist: [], 944 | info: undefined, rtt: undefined}; 945 | if (options) { 946 | utils.objectExtend(that._options, options); 947 | } 948 | that._base_url = utils.amendUrl(url); 949 | that._server = that._options.server || utils.random_number_string(1000); 950 | if (that._options.protocols_whitelist && 951 | that._options.protocols_whitelist.length) { 952 | protocols_whitelist = that._options.protocols_whitelist; 953 | } else { 954 | // Deprecated API 955 | if (typeof dep_protocols_whitelist === 'string' && 956 | dep_protocols_whitelist.length > 0) { 957 | protocols_whitelist = [dep_protocols_whitelist]; 958 | } else if (utils.isArray(dep_protocols_whitelist)) { 959 | protocols_whitelist = dep_protocols_whitelist 960 | } else { 961 | protocols_whitelist = null; 962 | } 963 | if (protocols_whitelist) { 964 | that._debug('Deprecated API: Use "protocols_whitelist" option ' + 965 | 'instead of supplying protocol list as a second ' + 966 | 'parameter to SockJS constructor.'); 967 | } 968 | } 969 | that._protocols = []; 970 | that.protocol = null; 971 | that.readyState = SockJS.CONNECTING; 972 | that._ir = createInfoReceiver(that._base_url); 973 | that._ir.onfinish = function(info, rtt) { 974 | that._ir = null; 975 | if (info) { 976 | if (that._options.info) { 977 | // Override if user supplies the option 978 | info = utils.objectExtend(info, that._options.info); 979 | } 980 | if (that._options.rtt) { 981 | rtt = that._options.rtt; 982 | } 983 | that._applyInfo(info, rtt, protocols_whitelist); 984 | that._didClose(); 985 | } else { 986 | that._didClose(1002, 'Can\'t connect to server', true); 987 | } 988 | }; 989 | }; 990 | // Inheritance 991 | SockJS.prototype = new REventTarget(); 992 | 993 | SockJS.version = "0.3.4"; 994 | 995 | SockJS.CONNECTING = 0; 996 | SockJS.OPEN = 1; 997 | SockJS.CLOSING = 2; 998 | SockJS.CLOSED = 3; 999 | 1000 | SockJS.prototype._debug = function() { 1001 | if (this._options.debug) 1002 | utils.log.apply(utils, arguments); 1003 | }; 1004 | 1005 | SockJS.prototype._dispatchOpen = function() { 1006 | var that = this; 1007 | if (that.readyState === SockJS.CONNECTING) { 1008 | if (that._transport_tref) { 1009 | clearTimeout(that._transport_tref); 1010 | that._transport_tref = null; 1011 | } 1012 | that.readyState = SockJS.OPEN; 1013 | that.dispatchEvent(new SimpleEvent("open")); 1014 | } else { 1015 | // The server might have been restarted, and lost track of our 1016 | // connection. 1017 | that._didClose(1006, "Server lost session"); 1018 | } 1019 | }; 1020 | 1021 | SockJS.prototype._dispatchMessage = function(data) { 1022 | var that = this; 1023 | if (that.readyState !== SockJS.OPEN) 1024 | return; 1025 | that.dispatchEvent(new SimpleEvent("message", {data: data})); 1026 | }; 1027 | 1028 | SockJS.prototype._dispatchHeartbeat = function(data) { 1029 | var that = this; 1030 | if (that.readyState !== SockJS.OPEN) 1031 | return; 1032 | that.dispatchEvent(new SimpleEvent('heartbeat', {})); 1033 | }; 1034 | 1035 | SockJS.prototype._didClose = function(code, reason, force) { 1036 | var that = this; 1037 | if (that.readyState !== SockJS.CONNECTING && 1038 | that.readyState !== SockJS.OPEN && 1039 | that.readyState !== SockJS.CLOSING) 1040 | throw new Error('INVALID_STATE_ERR'); 1041 | if (that._ir) { 1042 | that._ir.nuke(); 1043 | that._ir = null; 1044 | } 1045 | 1046 | if (that._transport) { 1047 | that._transport.doCleanup(); 1048 | that._transport = null; 1049 | } 1050 | 1051 | var close_event = new SimpleEvent("close", { 1052 | code: code, 1053 | reason: reason, 1054 | wasClean: utils.userSetCode(code)}); 1055 | 1056 | if (!utils.userSetCode(code) && 1057 | that.readyState === SockJS.CONNECTING && !force) { 1058 | if (that._try_next_protocol(close_event)) { 1059 | return; 1060 | } 1061 | close_event = new SimpleEvent("close", {code: 2000, 1062 | reason: "All transports failed", 1063 | wasClean: false, 1064 | last_event: close_event}); 1065 | } 1066 | that.readyState = SockJS.CLOSED; 1067 | 1068 | utils.delay(function() { 1069 | that.dispatchEvent(close_event); 1070 | }); 1071 | }; 1072 | 1073 | SockJS.prototype._didMessage = function(data) { 1074 | var that = this; 1075 | var type = data.slice(0, 1); 1076 | switch(type) { 1077 | case 'o': 1078 | that._dispatchOpen(); 1079 | break; 1080 | case 'a': 1081 | var payload = JSON.parse(data.slice(1) || '[]'); 1082 | for(var i=0; i < payload.length; i++){ 1083 | that._dispatchMessage(payload[i]); 1084 | } 1085 | break; 1086 | case 'm': 1087 | var payload = JSON.parse(data.slice(1) || 'null'); 1088 | that._dispatchMessage(payload); 1089 | break; 1090 | case 'c': 1091 | var payload = JSON.parse(data.slice(1) || '[]'); 1092 | that._didClose(payload[0], payload[1]); 1093 | break; 1094 | case 'h': 1095 | that._dispatchHeartbeat(); 1096 | break; 1097 | } 1098 | }; 1099 | 1100 | SockJS.prototype._try_next_protocol = function(close_event) { 1101 | var that = this; 1102 | if (that.protocol) { 1103 | that._debug('Closed transport:', that.protocol, ''+close_event); 1104 | that.protocol = null; 1105 | } 1106 | if (that._transport_tref) { 1107 | clearTimeout(that._transport_tref); 1108 | that._transport_tref = null; 1109 | } 1110 | 1111 | while(1) { 1112 | var protocol = that.protocol = that._protocols.shift(); 1113 | if (!protocol) { 1114 | return false; 1115 | } 1116 | // Some protocols require access to `body`, what if were in 1117 | // the `head`? 1118 | if (SockJS[protocol] && 1119 | SockJS[protocol].need_body === true && 1120 | (!_document.body || 1121 | (typeof _document.readyState !== 'undefined' 1122 | && _document.readyState !== 'complete'))) { 1123 | that._protocols.unshift(protocol); 1124 | that.protocol = 'waiting-for-load'; 1125 | utils.attachEvent('load', function(){ 1126 | that._try_next_protocol(); 1127 | }); 1128 | return true; 1129 | } 1130 | 1131 | if (!SockJS[protocol] || 1132 | !SockJS[protocol].enabled(that._options)) { 1133 | that._debug('Skipping transport:', protocol); 1134 | } else { 1135 | var roundTrips = SockJS[protocol].roundTrips || 1; 1136 | var to = ((that._options.rto || 0) * roundTrips) || 5000; 1137 | that._transport_tref = utils.delay(to, function() { 1138 | if (that.readyState === SockJS.CONNECTING) { 1139 | // I can't understand how it is possible to run 1140 | // this timer, when the state is CLOSED, but 1141 | // apparently in IE everythin is possible. 1142 | that._didClose(2007, "Transport timeouted"); 1143 | } 1144 | }); 1145 | 1146 | var connid = utils.random_string(8); 1147 | var trans_url = that._base_url + '/' + that._server + '/' + connid; 1148 | that._debug('Opening transport:', protocol, ' url:'+trans_url, 1149 | ' RTO:'+that._options.rto); 1150 | that._transport = new SockJS[protocol](that, trans_url, 1151 | that._base_url); 1152 | return true; 1153 | } 1154 | } 1155 | }; 1156 | 1157 | SockJS.prototype.close = function(code, reason) { 1158 | var that = this; 1159 | if (code && !utils.userSetCode(code)) 1160 | throw new Error("INVALID_ACCESS_ERR"); 1161 | if(that.readyState !== SockJS.CONNECTING && 1162 | that.readyState !== SockJS.OPEN) { 1163 | return false; 1164 | } 1165 | that.readyState = SockJS.CLOSING; 1166 | that._didClose(code || 1000, reason || "Normal closure"); 1167 | return true; 1168 | }; 1169 | 1170 | SockJS.prototype.send = function(data) { 1171 | var that = this; 1172 | if (that.readyState === SockJS.CONNECTING) 1173 | throw new Error('INVALID_STATE_ERR'); 1174 | if (that.readyState === SockJS.OPEN) { 1175 | that._transport.doSend(utils.quote('' + data)); 1176 | } 1177 | return true; 1178 | }; 1179 | 1180 | SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { 1181 | var that = this; 1182 | that._options.info = info; 1183 | that._options.rtt = rtt; 1184 | that._options.rto = utils.countRTO(rtt); 1185 | that._options.info.null_origin = !_document.domain; 1186 | var probed = utils.probeProtocols(); 1187 | that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); 1188 | }; 1189 | // [*] End of lib/sockjs.js 1190 | 1191 | 1192 | // [*] Including lib/trans-websocket.js 1193 | /* 1194 | * ***** BEGIN LICENSE BLOCK ***** 1195 | * Copyright (c) 2011-2012 VMware, Inc. 1196 | * 1197 | * For the license see COPYING. 1198 | * ***** END LICENSE BLOCK ***** 1199 | */ 1200 | 1201 | var WebSocketTransport = SockJS.websocket = function(ri, trans_url) { 1202 | var that = this; 1203 | var url = trans_url + '/websocket'; 1204 | if (url.slice(0, 5) === 'https') { 1205 | url = 'wss' + url.slice(5); 1206 | } else { 1207 | url = 'ws' + url.slice(4); 1208 | } 1209 | that.ri = ri; 1210 | that.url = url; 1211 | var Constructor = _window.WebSocket || _window.MozWebSocket; 1212 | 1213 | that.ws = new Constructor(that.url); 1214 | that.ws.onmessage = function(e) { 1215 | that.ri._didMessage(e.data); 1216 | }; 1217 | // Firefox has an interesting bug. If a websocket connection is 1218 | // created after onunload, it stays alive even when user 1219 | // navigates away from the page. In such situation let's lie - 1220 | // let's not open the ws connection at all. See: 1221 | // https://github.com/sockjs/sockjs-client/issues/28 1222 | // https://bugzilla.mozilla.org/show_bug.cgi?id=696085 1223 | that.unload_ref = utils.unload_add(function(){that.ws.close()}); 1224 | that.ws.onclose = function() { 1225 | that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken")); 1226 | }; 1227 | }; 1228 | 1229 | WebSocketTransport.prototype.doSend = function(data) { 1230 | this.ws.send('[' + data + ']'); 1231 | }; 1232 | 1233 | WebSocketTransport.prototype.doCleanup = function() { 1234 | var that = this; 1235 | var ws = that.ws; 1236 | if (ws) { 1237 | ws.onmessage = ws.onclose = null; 1238 | ws.close(); 1239 | utils.unload_del(that.unload_ref); 1240 | that.unload_ref = that.ri = that.ws = null; 1241 | } 1242 | }; 1243 | 1244 | WebSocketTransport.enabled = function() { 1245 | return !!(_window.WebSocket || _window.MozWebSocket); 1246 | }; 1247 | 1248 | // In theory, ws should require 1 round trip. But in chrome, this is 1249 | // not very stable over SSL. Most likely a ws connection requires a 1250 | // separate SSL connection, in which case 2 round trips are an 1251 | // absolute minumum. 1252 | WebSocketTransport.roundTrips = 2; 1253 | // [*] End of lib/trans-websocket.js 1254 | 1255 | 1256 | // [*] Including lib/trans-sender.js 1257 | /* 1258 | * ***** BEGIN LICENSE BLOCK ***** 1259 | * Copyright (c) 2011-2012 VMware, Inc. 1260 | * 1261 | * For the license see COPYING. 1262 | * ***** END LICENSE BLOCK ***** 1263 | */ 1264 | 1265 | var BufferedSender = function() {}; 1266 | BufferedSender.prototype.send_constructor = function(sender) { 1267 | var that = this; 1268 | that.send_buffer = []; 1269 | that.sender = sender; 1270 | }; 1271 | BufferedSender.prototype.doSend = function(message) { 1272 | var that = this; 1273 | that.send_buffer.push(message); 1274 | if (!that.send_stop) { 1275 | that.send_schedule(); 1276 | } 1277 | }; 1278 | 1279 | // For polling transports in a situation when in the message callback, 1280 | // new message is being send. If the sending connection was started 1281 | // before receiving one, it is possible to saturate the network and 1282 | // timeout due to the lack of receiving socket. To avoid that we delay 1283 | // sending messages by some small time, in order to let receiving 1284 | // connection be started beforehand. This is only a halfmeasure and 1285 | // does not fix the big problem, but it does make the tests go more 1286 | // stable on slow networks. 1287 | BufferedSender.prototype.send_schedule_wait = function() { 1288 | var that = this; 1289 | var tref; 1290 | that.send_stop = function() { 1291 | that.send_stop = null; 1292 | clearTimeout(tref); 1293 | }; 1294 | tref = utils.delay(25, function() { 1295 | that.send_stop = null; 1296 | that.send_schedule(); 1297 | }); 1298 | }; 1299 | 1300 | BufferedSender.prototype.send_schedule = function() { 1301 | var that = this; 1302 | if (that.send_buffer.length > 0) { 1303 | var payload = '[' + that.send_buffer.join(',') + ']'; 1304 | that.send_stop = that.sender(that.trans_url, payload, function(success, abort_reason) { 1305 | that.send_stop = null; 1306 | if (success === false) { 1307 | that.ri._didClose(1006, 'Sending error ' + abort_reason); 1308 | } else { 1309 | that.send_schedule_wait(); 1310 | } 1311 | }); 1312 | that.send_buffer = []; 1313 | } 1314 | }; 1315 | 1316 | BufferedSender.prototype.send_destructor = function() { 1317 | var that = this; 1318 | if (that._send_stop) { 1319 | that._send_stop(); 1320 | } 1321 | that._send_stop = null; 1322 | }; 1323 | 1324 | var jsonPGenericSender = function(url, payload, callback) { 1325 | var that = this; 1326 | 1327 | if (!('_send_form' in that)) { 1328 | var form = that._send_form = _document.createElement('form'); 1329 | var area = that._send_area = _document.createElement('textarea'); 1330 | area.name = 'd'; 1331 | form.style.display = 'none'; 1332 | form.style.position = 'absolute'; 1333 | form.method = 'POST'; 1334 | form.enctype = 'application/x-www-form-urlencoded'; 1335 | form.acceptCharset = "UTF-8"; 1336 | form.appendChild(area); 1337 | _document.body.appendChild(form); 1338 | } 1339 | var form = that._send_form; 1340 | var area = that._send_area; 1341 | var id = 'a' + utils.random_string(8); 1342 | form.target = id; 1343 | form.action = url + '/jsonp_send?i=' + id; 1344 | 1345 | var iframe; 1346 | try { 1347 | // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) 1348 | iframe = _document.createElement('