Skip to content
This repository
Browse code

Fixed more issues with disconnect and clean disconnect.

- Made a new abort command so disconnect events aren't fired multiple
  times for the same client. When a clean disconnect is triggered, the
  client will send an abort command and the receiving end will only raise
  disconnect for those commands, instead of for the disconnect command.

  The disconnect command signifies an unclean disconnect and therefore
  no other event needs to be raised.

  Remove connections from the list of tracked connections when an abort command
  is received. This prevents the disconnect event to fire again for that
  connection.
- Fixed a nasty issue with the longpolling transport in the javascript client
  sending requests forever when disconnect commands are received.
  • Loading branch information...
commit 3e64c0e3e4d8855ac327fde66e10517b7f7530ee 1 parent 08fe8c2
David Fowler authored May 18, 2012
3  SignalR/CommandType.cs
@@ -4,6 +4,7 @@ public enum CommandType
4 4
     {
5 5
         AddToGroup,
6 6
         RemoveFromGroup,
7  
-        Disconnect
  7
+        Disconnect,
  8
+        Abort
8 9
     }
9 10
 }
5  SignalR/Connection.cs
@@ -18,6 +18,7 @@ public class Connection : IConnection, ITransportConnection
18 18
         private readonly HashSet<string> _groups;
19 19
         private readonly ITraceManager _trace;
20 20
         private bool _disconnected;
  21
+        private bool _aborted;
21 22
 
22 23
         public Connection(IMessageBus messageBus,
23 24
                           IJsonSerializer jsonSerializer,
@@ -87,6 +88,7 @@ private PersistentResponse GetResponse(MessageResult result)
87 88
                 MessageId = result.LastMessageId,
88 89
                 Messages = messageValues,
89 90
                 Disconnect = _disconnected,
  91
+                Aborted = _aborted,
90 92
                 TimedOut = result.TimedOut
91 93
             };
92 94
 
@@ -133,6 +135,9 @@ private void ProcessCommand(SignalCommand command)
133 135
                 case CommandType.Disconnect:
134 136
                     _disconnected = true;
135 137
                     break;
  138
+                case CommandType.Abort:
  139
+                    _aborted = true;
  140
+                    break;
136 141
             }
137 142
         }
138 143
 
10  SignalR/Infrastructure/ConnectionExtensions.cs
@@ -15,6 +15,16 @@ public static Task Close(this ITransportConnection connection)
15 15
             return connection.SendCommand(command);
16 16
         }
17 17
 
  18
+        public static Task Abort(this ITransportConnection connection)
  19
+        {
  20
+            var command = new SignalCommand
  21
+            {
  22
+                Type = CommandType.Abort
  23
+            };
  24
+
  25
+            return connection.SendCommand(command);
  26
+        }
  27
+
18 28
         public static Task SendCommand(this IConnection connection, string connectionId, SignalCommand command)
19 29
         {
20 30
             return connection.Send(SignalCommand.AddCommandSuffix(connectionId), command);
7  SignalR/PersistentResponse.cs
... ...
@@ -1,4 +1,5 @@
1 1
 using System.Collections.Generic;
  2
+using Newtonsoft.Json;
2 3
 
3 4
 namespace SignalR
4 5
 {
@@ -25,6 +26,12 @@ public class PersistentResponse
25 26
         public bool Disconnect { get; set; }
26 27
 
27 28
         /// <summary>
  29
+        /// True if the connection was forcibly closed. 
  30
+        /// </summary>
  31
+        [JsonIgnore]
  32
+        public bool Aborted { get; set; }
  33
+
  34
+        /// <summary>
28 35
         /// True if the connection timed out.
29 36
         /// </summary>
30 37
         public bool TimedOut { get; set; }
22  SignalR/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
422 422
                     log("Disconnect command received from server", connection.logging);
423 423
 
424 424
                     // Disconnected by the server
425  
-                    connection.transport.stop(connection);
  425
+                    connection.stop();
426 426
                     return;
427 427
                 }
428 428
 
@@ -837,7 +837,9 @@
837 837
             start: function (connection, onSuccess, onFailed) {
838 838
                 /// <summary>Starts the long polling connection</summary>
839 839
                 /// <param name="connection" type="signalR">The SignalR connection to start</param>
840  
-                var that = this;
  840
+                var that = this,
  841
+                    initialConnectFired = false;
  842
+
841 843
                 if (connection.pollXhr) {
842 844
                     log("Polling xhr requests already exists, aborting.");
843 845
                     connection.stop();
@@ -865,6 +867,11 @@
865 867
                                 var delay = 0,
866 868
                                     timedOutReceived = false;
867 869
 
  870
+                                if (initialConnectFired == false) {
  871
+                                    onSuccess();
  872
+                                    initialConnectFired = true;
  873
+                                }
  874
+
868 875
                                 if (raiseReconnect === true) {
869 876
                                     // Fire the reconnect event if it hasn't been fired as yet
870 877
                                     if (reconnectFired === false) {
@@ -885,6 +892,10 @@
885 892
                                     timedOutReceived = data.TimedOut;
886 893
                                 }
887 894
 
  895
+                                if (data && data.Disconnect) {
  896
+                                    return;
  897
+                                }
  898
+
888 899
                                 if (delay > 0) {
889 900
                                     window.setTimeout(function () {
890 901
                                         poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
931 942
                     // Now connected
932 943
                     // There's no good way know when the long poll has actually started so
933 944
                     // we assume it only takes around 150ms (max) to start the connection
934  
-                    window.setTimeout(onSuccess, 150);
  945
+                    window.setTimeout(function () {
  946
+                        if (initialConnectFired === false) {
  947
+                            onSuccess();
  948
+                            initialConnectFired = true;
  949
+                        }
  950
+                    }, 150);
935 951
 
936 952
                 }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
937 953
             },
2  SignalR/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
6 6
 * Licensed under the MIT.
7 7
 * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
8 8
 */
9  
-(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.transport.stop(t);return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var s=this;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(e,h){n(e).trigger(r.onSending);var y=e.messageId,v=y===null,a=u.getUrl(e,s.name,!v,h),l=null,c=!1;i("Attempting to connect to '"+a+"' using longPolling."),e.pollXhr=n.ajax({url:a,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var l=0,s=!1;h===!0&&c===!1&&(i("Raising the reconnect event"),n(e).trigger(r.onReconnect),c=!0),u.processMessages(e,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(l=f.TransportData.LongPollDelay),f&&f.TimedOut&&(s=f.TimedOut),l>0?t.setTimeout(function(){o(e,s)},l):o(e,s)},error:function(u,s){if(s==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),l&&clearTimeout(l),n(e).trigger(r.onError,[u]),t.setTimeout(function(){o(e,!0)},f.reconnectDelay)}}),h===!0&&(l=t.setTimeout(function(){c===!1&&(n(e).trigger(r.onReconnect),c=!0)},s.reconnectDelay))})(f),t.setTimeout(e,150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)
  9
+(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var h=this,s=!1;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(c,l){n(c).trigger(r.onSending);var w=c.messageId,p=w===null,y=u.getUrl(c,h.name,!p,l),v=null,a=!1;i("Attempting to connect to '"+y+"' using longPolling."),c.pollXhr=n.ajax({url:y,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var v=0,h=!1;(s==!1&&(e(),s=!0),l===!0&&a===!1&&(i("Raising the reconnect event"),n(c).trigger(r.onReconnect),a=!0),u.processMessages(c,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(v=f.TransportData.LongPollDelay),f&&f.TimedOut&&(h=f.TimedOut),f&&f.Disconnect)||(v>0?t.setTimeout(function(){o(c,h)},v):o(c,h))},error:function(u,e){if(e==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),v&&clearTimeout(v),n(c).trigger(r.onError,[u]),t.setTimeout(function(){o(c,!0)},f.reconnectDelay)}}),l===!0&&(v=t.setTimeout(function(){a===!1&&(n(c).trigger(r.onReconnect),a=!0)},h.reconnectDelay))})(f),t.setTimeout(function(){s===!1&&(e(),s=!0)},150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)
12  SignalR/Transports/ForeverTransport.cs
@@ -87,7 +87,7 @@ protected Task ProcessRequestCore(ITransportConnection connection)
87 87
             }
88 88
             else if (IsAbortRequest)
89 89
             {
90  
-                return Connection.Close();
  90
+                return Connection.Abort();
91 91
             }
92 92
             else
93 93
             {
@@ -201,10 +201,11 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
201 201
                     // If the response has the Disconnect flag, just send the response and exit the loop,
202 202
                     // the server thinks connection is gone. Otherwse, send the response then re-enter the loop
203 203
                     Task sendTask = Send(response);
204  
-                    if (response.Disconnect || response.TimedOut)
  204
+                    if (response.Disconnect || response.TimedOut || response.Aborted)
205 205
                     {
206  
-                        if (response.Disconnect)
  206
+                        if (response.Aborted)
207 207
                         {
  208
+                            // If this was a clean disconnect raise the event.
208 209
                             OnDisconnect();
209 210
                         }
210 211
 
@@ -232,11 +233,6 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
232 233
                 return;
233 234
             }
234 235
 
235  
-            if (!IsTimedOut)
236  
-            {
237  
-                OnDisconnect();
238  
-            }
239  
-
240 236
             taskCompletetionSource.SetResult(null);
241 237
             return;
242 238
         }
6  SignalR/Transports/ITransportHeartBeat.cs
@@ -22,5 +22,11 @@ public interface ITransportHeartBeat
22 22
         /// </summary>
23 23
         /// <param name="connection">The connection to mark.</param>
24 24
         void MarkConnection(ITrackingConnection connection);
  25
+
  26
+        /// <summary>
  27
+        /// Removes a connection from the list of tracked connections.
  28
+        /// </summary>
  29
+        /// <param name="connection">The connection to remove.</param>
  30
+        void RemoveConnection(ITrackingConnection connection);
25 31
     }
26 32
 }
5  SignalR/Transports/LongPollingTransport.cs
@@ -112,7 +112,7 @@ public Task ProcessRequest(ITransportConnection connection)
112 112
             }
113 113
             else if (IsAbortRequest)
114 114
             {
115  
-                return Connection.Close();
  115
+                return Connection.Abort();
116 116
             }
117 117
             else
118 118
             {
@@ -213,8 +213,9 @@ private Task ProcessReceiveRequest(ITransportConnection connection, Action postR
213 213
 
214 214
             return receiveTask.Then(response =>
215 215
             {
216  
-                if (response.Disconnect)
  216
+                if (response.Aborted)
217 217
                 {
  218
+                    // If this was a clean disconnect then raise the event
218 219
                     OnDisconnect();
219 220
                 }
220 221
 
4  SignalR/Transports/TransportDisconnectBase.cs
@@ -120,6 +120,10 @@ public Task Disconnect()
120 120
 
121 121
         public Task OnDisconnect()
122 122
         {
  123
+            // When a connection is aborted (graceful disconnect) we send a command to it
  124
+            // telling to to disconnect. At that moment, we raise the disconnect event and
  125
+            // remove this connection from the heartbeat so we don't end up raising it for the same connection.
  126
+            HeartBeat.RemoveConnection(this);
123 127
             if (Interlocked.Exchange(ref _isDisconnected, 1) == 0)
124 128
             {
125 129
                 var disconnected = Disconnected; // copy before invoking event to avoid race
6  SignalR/Transports/TransportHeartBeat.cs
@@ -80,7 +80,11 @@ private void RemoveConnection(string connectionId)
80 80
             RemoveConnection(new ConnectionReference(connectionId));
81 81
         }
82 82
 
83  
-        private void RemoveConnection(ITrackingConnection connection)
  83
+        /// <summary>
  84
+        /// Removes a connection from the list of tracked connections.
  85
+        /// </summary>
  86
+        /// <param name="connection">The connection to remove.</param>
  87
+        public void RemoveConnection(ITrackingConnection connection)
84 88
         {
85 89
             // Remove the connection and associated metadata
86 90
             _connections.Remove(connection);
9  samples/SignalR.Hosting.AspNet.Samples/Raw/index.htm
@@ -46,11 +46,15 @@
46 46
                           .appendTo($("#messages"));
47 47
             });
48 48
 
  49
+            connection.disconnected(function () {
  50
+                $("#stopStart").val("Start")
  51
+                               .prop("disabled", false);
  52
+            });
49 53
 
50 54
             var start = function () {
51 55
                 connection.start({ transport: activeTransport })
52 56
                     .then(function () {
53  
-                        $("#stopStart").prop("disabled", false);
  57
+                        $("#stopStart").val("Stop").prop("disabled", false);
54 58
                     });
55 59
             };
56 60
             start();
@@ -88,11 +92,8 @@
88 92
                 $el.prop("disabled", true);
89 93
                 if ($el.val() === "Stop") {
90 94
                     connection.stop();
91  
-                    $el.val("Start")
92  
-                       .prop("disabled", false);
93 95
                 } else {
94 96
                     start();
95  
-                    $el.val("Stop");
96 97
                 }
97 98
             });
98 99
         });
22  samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
422 422
                     log("Disconnect command received from server", connection.logging);
423 423
 
424 424
                     // Disconnected by the server
425  
-                    connection.transport.stop(connection);
  425
+                    connection.stop();
426 426
                     return;
427 427
                 }
428 428
 
@@ -837,7 +837,9 @@
837 837
             start: function (connection, onSuccess, onFailed) {
838 838
                 /// <summary>Starts the long polling connection</summary>
839 839
                 /// <param name="connection" type="signalR">The SignalR connection to start</param>
840  
-                var that = this;
  840
+                var that = this,
  841
+                    initialConnectFired = false;
  842
+
841 843
                 if (connection.pollXhr) {
842 844
                     log("Polling xhr requests already exists, aborting.");
843 845
                     connection.stop();
@@ -865,6 +867,11 @@
865 867
                                 var delay = 0,
866 868
                                     timedOutReceived = false;
867 869
 
  870
+                                if (initialConnectFired == false) {
  871
+                                    onSuccess();
  872
+                                    initialConnectFired = true;
  873
+                                }
  874
+
868 875
                                 if (raiseReconnect === true) {
869 876
                                     // Fire the reconnect event if it hasn't been fired as yet
870 877
                                     if (reconnectFired === false) {
@@ -885,6 +892,10 @@
885 892
                                     timedOutReceived = data.TimedOut;
886 893
                                 }
887 894
 
  895
+                                if (data && data.Disconnect) {
  896
+                                    return;
  897
+                                }
  898
+
888 899
                                 if (delay > 0) {
889 900
                                     window.setTimeout(function () {
890 901
                                         poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
931 942
                     // Now connected
932 943
                     // There's no good way know when the long poll has actually started so
933 944
                     // we assume it only takes around 150ms (max) to start the connection
934  
-                    window.setTimeout(onSuccess, 150);
  945
+                    window.setTimeout(function () {
  946
+                        if (initialConnectFired === false) {
  947
+                            onSuccess();
  948
+                            initialConnectFired = true;
  949
+                        }
  950
+                    }, 150);
935 951
 
936 952
                 }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
937 953
             },
2  samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
6 6
 * Licensed under the MIT.
7 7
 * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
8 8
 */
9  
-(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.transport.stop(t);return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var s=this;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(e,h){n(e).trigger(r.onSending);var y=e.messageId,v=y===null,a=u.getUrl(e,s.name,!v,h),l=null,c=!1;i("Attempting to connect to '"+a+"' using longPolling."),e.pollXhr=n.ajax({url:a,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var l=0,s=!1;h===!0&&c===!1&&(i("Raising the reconnect event"),n(e).trigger(r.onReconnect),c=!0),u.processMessages(e,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(l=f.TransportData.LongPollDelay),f&&f.TimedOut&&(s=f.TimedOut),l>0?t.setTimeout(function(){o(e,s)},l):o(e,s)},error:function(u,s){if(s==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),l&&clearTimeout(l),n(e).trigger(r.onError,[u]),t.setTimeout(function(){o(e,!0)},f.reconnectDelay)}}),h===!0&&(l=t.setTimeout(function(){c===!1&&(n(e).trigger(r.onReconnect),c=!0)},s.reconnectDelay))})(f),t.setTimeout(e,150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)
  9
+(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var h=this,s=!1;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(c,l){n(c).trigger(r.onSending);var w=c.messageId,p=w===null,y=u.getUrl(c,h.name,!p,l),v=null,a=!1;i("Attempting to connect to '"+y+"' using longPolling."),c.pollXhr=n.ajax({url:y,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var v=0,h=!1;(s==!1&&(e(),s=!0),l===!0&&a===!1&&(i("Raising the reconnect event"),n(c).trigger(r.onReconnect),a=!0),u.processMessages(c,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(v=f.TransportData.LongPollDelay),f&&f.TimedOut&&(h=f.TimedOut),f&&f.Disconnect)||(v>0?t.setTimeout(function(){o(c,h)},v):o(c,h))},error:function(u,e){if(e==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),v&&clearTimeout(v),n(c).trigger(r.onError,[u]),t.setTimeout(function(){o(c,!0)},f.reconnectDelay)}}),l===!0&&(v=t.setTimeout(function(){a===!1&&(n(c).trigger(r.onReconnect),a=!0)},h.reconnectDelay))})(f),t.setTimeout(function(){s===!1&&(e(),s=!0)},150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)
22  samples/SignalR.Hosting.Owin.Samples/Content/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
422 422
                     log("Disconnect command received from server", connection.logging);
423 423
 
424 424
                     // Disconnected by the server
425  
-                    connection.transport.stop(connection);
  425
+                    connection.stop();
426 426
                     return;
427 427
                 }
428 428
 
@@ -837,7 +837,9 @@
837 837
             start: function (connection, onSuccess, onFailed) {
838 838
                 /// <summary>Starts the long polling connection</summary>
839 839
                 /// <param name="connection" type="signalR">The SignalR connection to start</param>
840  
-                var that = this;
  840
+                var that = this,
  841
+                    initialConnectFired = false;
  842
+
841 843
                 if (connection.pollXhr) {
842 844
                     log("Polling xhr requests already exists, aborting.");
843 845
                     connection.stop();
@@ -865,6 +867,11 @@
865 867
                                 var delay = 0,
866 868
                                     timedOutReceived = false;
867 869
 
  870
+                                if (initialConnectFired == false) {
  871
+                                    onSuccess();
  872
+                                    initialConnectFired = true;
  873
+                                }
  874
+
868 875
                                 if (raiseReconnect === true) {
869 876
                                     // Fire the reconnect event if it hasn't been fired as yet
870 877
                                     if (reconnectFired === false) {
@@ -885,6 +892,10 @@
885 892
                                     timedOutReceived = data.TimedOut;
886 893
                                 }
887 894
 
  895
+                                if (data && data.Disconnect) {
  896
+                                    return;
  897
+                                }
  898
+
888 899
                                 if (delay > 0) {
889 900
                                     window.setTimeout(function () {
890 901
                                         poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
931 942
                     // Now connected
932 943
                     // There's no good way know when the long poll has actually started so
933 944
                     // we assume it only takes around 150ms (max) to start the connection
934  
-                    window.setTimeout(onSuccess, 150);
  945
+                    window.setTimeout(function () {
  946
+                        if (initialConnectFired === false) {
  947
+                            onSuccess();
  948
+                            initialConnectFired = true;
  949
+                        }
  950
+                    }, 150);
935 951
 
936 952
                 }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
937 953
             },
2  samples/SignalR.Hosting.Owin.Samples/Content/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
6 6
 * Licensed under the MIT.
7 7
 * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
8 8
 */
9  
-(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.transport.stop(t);return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var s=this;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(e,h){n(e).trigger(r.onSending);var y=e.messageId,v=y===null,a=u.getUrl(e,s.name,!v,h),l=null,c=!1;i("Attempting to connect to '"+a+"' using longPolling."),e.pollXhr=n.ajax({url:a,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var l=0,s=!1;h===!0&&c===!1&&(i("Raising the reconnect event"),n(e).trigger(r.onReconnect),c=!0),u.processMessages(e,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(l=f.TransportData.LongPollDelay),f&&f.TimedOut&&(s=f.TimedOut),l>0?t.setTimeout(function(){o(e,s)},l):o(e,s)},error:function(u,s){if(s==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),l&&clearTimeout(l),n(e).trigger(r.onError,[u]),t.setTimeout(function(){o(e,!0)},f.reconnectDelay)}}),h===!0&&(l=t.setTimeout(function(){c===!1&&(n(e).trigger(r.onReconnect),c=!0)},s.reconnectDelay))})(f),t.setTimeout(e,150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)
  9
+(function(n,t){"use strict";var f,e,r,i,u;if(typeof n!="function")throw"SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.";if(!t.JSON)throw"SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.";r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onDisconnect:"onDisconnect"},i=function(n,i){if(i!==!1){var r;typeof t.console!="undefined"&&(r="["+(new Date).toTimeString()+"] SignalR: "+n,t.console.debug?t.console.debug(r):t.console.log&&t.console.log(r))}},f=function(n,t,i){return new f.fn.init(n,t,i)},f.fn=f.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,reconnectDelay:2e3,start:function(u,e){var o=this,s={transport:"auto",xdomain:!1},c,h=n.Deferred();return o.transport?(h.resolve(o),h.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(s,u),n.type(s.callback)==="function"&&(e=s.callback)),o.ajaxDataType=s.xdomain?"jsonp":"json",n(o).bind(r.onStart,function(){n.type(e)==="function"&&e.call(o),h.resolve(o)}),c=function(i,u){if(u=u||0,u>=i.length){o.transport||h.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var e=i[u],s=n.type(e)==="object"?e:f.transports[e];s.start(o,function(){o.transport=s,n(o).trigger(r.onStart),n(t).unload(function(){o.stop(!1)})},function(){c(i,u+1)})},t.setTimeout(function(){var t=o.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:o.ajaxDataType,error:function(t){n(o).trigger(r.onError,[t]),h.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(o.appRelativeUrl=t.Url,o.id=t.ConnectionId,o.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(o).trigger(r.onError,"SignalR: Incompatible protocol version."),h.reject("SignalR: Incompatible protocol version.");return}n(o).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(s.transport)?n.each(s.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&u.push(n.type(t)==="string"?""+t:t)}):n.type(s.transport)==="object"||n.inArray(s.transport,i)>=0?u.push(s.transport):u=i,c(u)}})},0),h.promise())},starting:function(t){var i=this,u=n(i);return u.bind(r.onStarting,function(){t.call(i),u.unbind(r.onStarting)}),i},send:function(n){var t=this;if(!t.transport)throw"SignalR: Connection must be started before data can be sent. Call .start() before .send()";return t.transport.send(t,n),t},sending:function(t){var i=this;return n(i).bind(r.onSending,function(){t.call(i)}),i},received:function(t){var i=this;return n(i).bind(r.onReceived,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(r.onError,function(n,r){t.call(i,r)}),i},disconnected:function(t){var i=this;return n(i).bind(r.onDisconnect,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(r.onReconnect,function(){t.call(i)}),i},stop:function(t){var i=this;return i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),delete i.messageId,delete i.groups,n(i).trigger(r.onDisconnect),i},log:i},f.fn.init.prototype=f.fn,u={addQs:function(i,r){return r.qs?typeof r.qs=="object"?i+"&"+n.param(r.qs):typeof r.qs=="string"?i+"&"+r.qs:i+"&"+t.escape(r.qs.toString()):i},getUrl:function(n,i,r,u){var f=n.url,e="transport="+i+"&connectionId="+t.escape(n.id);return n.data&&(e+="&connectionData="+t.escape(n.data)),r?(u&&(f=f+"/reconnect"),n.messageId&&(e+="&messageId="+n.messageId),n.groups&&(e+="&groups="+t.escape(JSON.stringify(n.groups)))):f=f+"/connect",f+="?"+e,f=this.addQs(f,n)},ajaxSend:function(i,u){var f=i.url+"/send?transport="+i.transport.name+"&connectionId="+t.escape(i.id);f=this.addQs(f,i),n.ajax({url:f,global:!1,type:"POST",dataType:i.ajaxDataType,data:{data:u},success:function(t){t&&n(i).trigger(r.onReceived,[t])},error:function(t,u){u!=="abort"&&(u!=="parsererror"||i.ajaxDataType!=="jsonp")&&n(i).trigger(r.onError,[t])}})},ajaxAbort:function(r,u){if(typeof r.transport!="undefined"){u=typeof u=="undefined"?!0:u;var f=r.url+"/abort?transport="+r.transport.name+"&connectionId="+t.escape(r.id);f=this.addQs(f,r),n.ajax({url:f,async:u,timeout:1e3,global:!1,type:"POST",dataType:r.ajaxDataType,data:{}}),i("Fired ajax abort async = "+u)}},processMessages:function(t,u){var f=n(t);if(u){if(u.Disconnect){i("Disconnect command received from server",t.logging),t.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u,t.logging),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},f.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(u,f,e){var o,h=!1,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){e();return}u.socket||(u.webSocketServerUrl?o=u.webSocketServerUrl:(s=document.location.protocol==="https:"?"wss://":"ws://",o=s+document.location.host+u.appRelativeUrl),n(u).trigger(r.onSending),o+=u.data?"?connectionData="+u.data+"&transport=webSockets&connectionId="+u.id:"?transport=webSockets&connectionId="+u.id,i("Connecting to websocket endpoint '"+o+"'"),u.socket=new t.WebSocket(o),u.socket.onopen=function(){h=!0,i("Websocket opened"),f&&f()},u.socket.onclose=function(t){h?typeof t.wasClean!="undefined"&&t.wasClean===!1&&(n(u).trigger(r.onError),i("Unclean disconnect from websocket")):(e&&e(),i("Websocket closed")),u.socket=null},u.socket.onmessage=function(f){var e=t.JSON.parse(f.data),o;e&&(o=n(u),e.Messages?n.each(e.Messages,function(){try{o.trigger(r.onReceived,[this])}catch(n){i("Error raising received "+n,u.logging)}}):o.trigger(r.onReceived,[e]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(f,e,o){var s=this,l=!1,c=n(f),h=!e,v,a;if(f.eventSource&&(i("The connection already has an event source. Stopping it."),f.stop()),!t.EventSource){o&&(i("This browser doesn't support SSE."),o());return}c.trigger(r.onSending),v=u.getUrl(f,this.name,h);try{i("Attempting to connect to SSE endpoint '"+v+"'"),f.eventSource=new t.EventSource(v)}catch(y){i("EventSource failed trying to connect with error "+y.Message,f.logging),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting",f.logging),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect",f.logging),o&&o(),h?(i("EventSource reconnecting",f.logging),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected",f.logging),a&&t.clearTimeout(a),l===!1&&(l=!0,e&&e(),h&&c.trigger(r.onReconnect))},!1),f.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&u.processMessages(f,t.JSON.parse(n.data))},!1),f.eventSource.addEventListener("error",function(n){if(!l){o&&o();return}i("EventSource readyState: "+f.eventSource.readyState,f.logging),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending",f.logging),s.reconnect(f)):(i("EventSource closed",f.logging),s.stop(f)):(i("EventSource error",f.logging),c.trigger(r.onError))},!1)},reconnect:function(n){var i=this;t.setTimeout(function(){i.stop(n),i.start(n)},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){u.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(f,e,o){var h=this,c=u.foreverFrame.count+=1,l,a,s=n("<iframe data-signalr-connection-id='"+f.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){o&&(i("This brower supports SSE, skipping Forever Frame."),o());return}n(f).trigger(r.onSending),l=u.getUrl(f,this.name),l+="&frameId="+c,s.prop("src",l),u.foreverFrame.connections[c]=f,i("Binding to iframe's readystatechange event."),s.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting",f.logging),h.reconnect(f))}),f.frame=s[0],f.frameId=c,e&&(f.onSuccess=e),n("body").append(s),a=t.setTimeout(function(){f.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+h.timeOut+"ms."),h.stop(f),o&&o())},h.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var f=n.frame,t=u.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),f.src=t},n.reconnectDelay)},send:function(n,t){u.ajaxSend(n,t)},receive:u.processMessages,stop:function(t){t.frame&&(t.frame.stop?t.frame.stop():t.frame.document&&t.frame.document.execCommand&&t.frame.document.execCommand("Stop"),n(t.frame).remove(),delete u.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){u.ajaxAbort(n,t)},getConnection:function(n){return u.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):n(t).trigger(r.onReconnect)}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(f,e){var h=this,s=!1;f.pollXhr&&(i("Polling xhr requests already exists, aborting."),f.stop()),f.messageId=null,t.setTimeout(function(){(function o(c,l){n(c).trigger(r.onSending);var w=c.messageId,p=w===null,y=u.getUrl(c,h.name,!p,l),v=null,a=!1;i("Attempting to connect to '"+y+"' using longPolling."),c.pollXhr=n.ajax({url:y,global:!1,type:"GET",dataType:f.ajaxDataType,success:function(f){var v=0,h=!1;(s==!1&&(e(),s=!0),l===!0&&a===!1&&(i("Raising the reconnect event"),n(c).trigger(r.onReconnect),a=!0),u.processMessages(c,f),f&&f.TransportData&&n.type(f.TransportData.LongPollDelay)==="number"&&(v=f.TransportData.LongPollDelay),f&&f.TimedOut&&(h=f.TimedOut),f&&f.Disconnect)||(v>0?t.setTimeout(function(){o(c,h)},v):o(c,h))},error:function(u,e){if(e==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling "+u),v&&clearTimeout(v),n(c).trigger(r.onError,[u]),t.setTimeout(function(){o(c,!0)},f.reconnectDelay)}}),l===!0&&(v=t.setTimeout(function(){a===!1&&(n(c).trigger(r.onReconnect),a=!0)},h.reconnectDelay))})(f),t.setTimeout(function(){s===!1&&(e(),s=!0)},150)},250)},send:function(n,t){u.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){u.ajaxAbort(n,t)}}},f.noConflict=function(){return n.connection===f&&(n.connection=e),f},n.connection&&(e=n.connection),n.connection=n.signalR=f})(window.jQuery,window)

0 notes on commit 3e64c0e

Please sign in to comment.
Something went wrong with that request. Please try again.