Permalink
Browse files

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...
1 parent 08fe8c2 commit 3e64c0e3e4d8855ac327fde66e10517b7f7530ee @davidfowl davidfowl committed May 18, 2012
View
3 SignalR/CommandType.cs
@@ -4,6 +4,7 @@ public enum CommandType
{
AddToGroup,
RemoveFromGroup,
- Disconnect
+ Disconnect,
+ Abort
}
}
View
5 SignalR/Connection.cs
@@ -18,6 +18,7 @@ public class Connection : IConnection, ITransportConnection
private readonly HashSet<string> _groups;
private readonly ITraceManager _trace;
private bool _disconnected;
+ private bool _aborted;
public Connection(IMessageBus messageBus,
IJsonSerializer jsonSerializer,
@@ -87,6 +88,7 @@ private PersistentResponse GetResponse(MessageResult result)
MessageId = result.LastMessageId,
Messages = messageValues,
Disconnect = _disconnected,
+ Aborted = _aborted,
TimedOut = result.TimedOut
};
@@ -133,6 +135,9 @@ private void ProcessCommand(SignalCommand command)
case CommandType.Disconnect:
_disconnected = true;
break;
+ case CommandType.Abort:
+ _aborted = true;
+ break;
}
}
View
10 SignalR/Infrastructure/ConnectionExtensions.cs
@@ -15,6 +15,16 @@ public static Task Close(this ITransportConnection connection)
return connection.SendCommand(command);
}
+ public static Task Abort(this ITransportConnection connection)
+ {
+ var command = new SignalCommand
+ {
+ Type = CommandType.Abort
+ };
+
+ return connection.SendCommand(command);
+ }
+
public static Task SendCommand(this IConnection connection, string connectionId, SignalCommand command)
{
return connection.Send(SignalCommand.AddCommandSuffix(connectionId), command);
View
7 SignalR/PersistentResponse.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Newtonsoft.Json;
namespace SignalR
{
@@ -25,6 +26,12 @@ public class PersistentResponse
public bool Disconnect { get; set; }
/// <summary>
+ /// True if the connection was forcibly closed.
+ /// </summary>
+ [JsonIgnore]
+ public bool Aborted { get; set; }
+
+ /// <summary>
/// True if the connection timed out.
/// </summary>
public bool TimedOut { get; set; }
View
22 SignalR/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
log("Disconnect command received from server", connection.logging);
// Disconnected by the server
- connection.transport.stop(connection);
+ connection.stop();
return;
}
@@ -837,7 +837,9 @@
start: function (connection, onSuccess, onFailed) {
/// <summary>Starts the long polling connection</summary>
/// <param name="connection" type="signalR">The SignalR connection to start</param>
- var that = this;
+ var that = this,
+ initialConnectFired = false;
+
if (connection.pollXhr) {
log("Polling xhr requests already exists, aborting.");
connection.stop();
@@ -865,6 +867,11 @@
var delay = 0,
timedOutReceived = false;
+ if (initialConnectFired == false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+
if (raiseReconnect === true) {
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
@@ -885,6 +892,10 @@
timedOutReceived = data.TimedOut;
}
+ if (data && data.Disconnect) {
+ return;
+ }
+
if (delay > 0) {
window.setTimeout(function () {
poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
// Now connected
// There's no good way know when the long poll has actually started so
// we assume it only takes around 150ms (max) to start the connection
- window.setTimeout(onSuccess, 150);
+ window.setTimeout(function () {
+ if (initialConnectFired === false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+ }, 150);
}, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
View
2 SignalR/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
* Licensed under the MIT.
* https://github.com/SignalR/SignalR/blob/master/LICENSE.md
*/
-(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)
+(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)
View
12 SignalR/Transports/ForeverTransport.cs
@@ -87,7 +87,7 @@ protected Task ProcessRequestCore(ITransportConnection connection)
}
else if (IsAbortRequest)
{
- return Connection.Close();
+ return Connection.Abort();
}
else
{
@@ -201,10 +201,11 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
// If the response has the Disconnect flag, just send the response and exit the loop,
// the server thinks connection is gone. Otherwse, send the response then re-enter the loop
Task sendTask = Send(response);
- if (response.Disconnect || response.TimedOut)
+ if (response.Disconnect || response.TimedOut || response.Aborted)
{
- if (response.Disconnect)
+ if (response.Aborted)
{
+ // If this was a clean disconnect raise the event.
OnDisconnect();
}
@@ -232,11 +233,6 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
return;
}
- if (!IsTimedOut)
- {
- OnDisconnect();
- }
-
taskCompletetionSource.SetResult(null);
return;
}
View
6 SignalR/Transports/ITransportHeartBeat.cs
@@ -22,5 +22,11 @@ public interface ITransportHeartBeat
/// </summary>
/// <param name="connection">The connection to mark.</param>
void MarkConnection(ITrackingConnection connection);
+
+ /// <summary>
+ /// Removes a connection from the list of tracked connections.
+ /// </summary>
+ /// <param name="connection">The connection to remove.</param>
+ void RemoveConnection(ITrackingConnection connection);
}
}
View
5 SignalR/Transports/LongPollingTransport.cs
@@ -112,7 +112,7 @@ public Task ProcessRequest(ITransportConnection connection)
}
else if (IsAbortRequest)
{
- return Connection.Close();
+ return Connection.Abort();
}
else
{
@@ -213,8 +213,9 @@ private Task ProcessReceiveRequest(ITransportConnection connection, Action postR
return receiveTask.Then(response =>
{
- if (response.Disconnect)
+ if (response.Aborted)
{
+ // If this was a clean disconnect then raise the event
OnDisconnect();
}
View
4 SignalR/Transports/TransportDisconnectBase.cs
@@ -120,6 +120,10 @@ public Task Disconnect()
public Task OnDisconnect()
{
+ // When a connection is aborted (graceful disconnect) we send a command to it
+ // telling to to disconnect. At that moment, we raise the disconnect event and
+ // remove this connection from the heartbeat so we don't end up raising it for the same connection.
+ HeartBeat.RemoveConnection(this);
if (Interlocked.Exchange(ref _isDisconnected, 1) == 0)
{
var disconnected = Disconnected; // copy before invoking event to avoid race
View
6 SignalR/Transports/TransportHeartBeat.cs
@@ -80,7 +80,11 @@ private void RemoveConnection(string connectionId)
RemoveConnection(new ConnectionReference(connectionId));
}
- private void RemoveConnection(ITrackingConnection connection)
+ /// <summary>
+ /// Removes a connection from the list of tracked connections.
+ /// </summary>
+ /// <param name="connection">The connection to remove.</param>
+ public void RemoveConnection(ITrackingConnection connection)
{
// Remove the connection and associated metadata
_connections.Remove(connection);
View
9 samples/SignalR.Hosting.AspNet.Samples/Raw/index.htm
@@ -46,11 +46,15 @@
.appendTo($("#messages"));
});
+ connection.disconnected(function () {
+ $("#stopStart").val("Start")
+ .prop("disabled", false);
+ });
var start = function () {
connection.start({ transport: activeTransport })
.then(function () {
- $("#stopStart").prop("disabled", false);
+ $("#stopStart").val("Stop").prop("disabled", false);
});
};
start();
@@ -88,11 +92,8 @@
$el.prop("disabled", true);
if ($el.val() === "Stop") {
connection.stop();
- $el.val("Start")
- .prop("disabled", false);
} else {
start();
- $el.val("Stop");
}
});
});
View
22 samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
log("Disconnect command received from server", connection.logging);
// Disconnected by the server
- connection.transport.stop(connection);
+ connection.stop();
return;
}
@@ -837,7 +837,9 @@
start: function (connection, onSuccess, onFailed) {
/// <summary>Starts the long polling connection</summary>
/// <param name="connection" type="signalR">The SignalR connection to start</param>
- var that = this;
+ var that = this,
+ initialConnectFired = false;
+
if (connection.pollXhr) {
log("Polling xhr requests already exists, aborting.");
connection.stop();
@@ -865,6 +867,11 @@
var delay = 0,
timedOutReceived = false;
+ if (initialConnectFired == false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+
if (raiseReconnect === true) {
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
@@ -885,6 +892,10 @@
timedOutReceived = data.TimedOut;
}
+ if (data && data.Disconnect) {
+ return;
+ }
+
if (delay > 0) {
window.setTimeout(function () {
poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
// Now connected
// There's no good way know when the long poll has actually started so
// we assume it only takes around 150ms (max) to start the connection
- window.setTimeout(onSuccess, 150);
+ window.setTimeout(function () {
+ if (initialConnectFired === false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+ }, 150);
}, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
View
2 samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
* Licensed under the MIT.
* https://github.com/SignalR/SignalR/blob/master/LICENSE.md
*/
-(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)
+(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)
View
22 samples/SignalR.Hosting.Owin.Samples/Content/Scripts/jquery.signalR.js
@@ -422,7 +422,7 @@
log("Disconnect command received from server", connection.logging);
// Disconnected by the server
- connection.transport.stop(connection);
+ connection.stop();
return;
}
@@ -837,7 +837,9 @@
start: function (connection, onSuccess, onFailed) {
/// <summary>Starts the long polling connection</summary>
/// <param name="connection" type="signalR">The SignalR connection to start</param>
- var that = this;
+ var that = this,
+ initialConnectFired = false;
+
if (connection.pollXhr) {
log("Polling xhr requests already exists, aborting.");
connection.stop();
@@ -865,6 +867,11 @@
var delay = 0,
timedOutReceived = false;
+ if (initialConnectFired == false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+
if (raiseReconnect === true) {
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
@@ -885,6 +892,10 @@
timedOutReceived = data.TimedOut;
}
+ if (data && data.Disconnect) {
+ return;
+ }
+
if (delay > 0) {
window.setTimeout(function () {
poll(instance, timedOutReceived);
@@ -931,7 +942,12 @@
// Now connected
// There's no good way know when the long poll has actually started so
// we assume it only takes around 150ms (max) to start the connection
- window.setTimeout(onSuccess, 150);
+ window.setTimeout(function () {
+ if (initialConnectFired === false) {
+ onSuccess();
+ initialConnectFired = true;
+ }
+ }, 150);
}, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
View
2 samples/SignalR.Hosting.Owin.Samples/Content/Scripts/jquery.signalR.min.js
@@ -6,4 +6,4 @@
* Licensed under the MIT.
* https://github.com/SignalR/SignalR/blob/master/LICENSE.md
*/
-(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)
+(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 comments on commit 3e64c0e

Please sign in to comment.