Permalink
Browse files

Added connection.state to javascript client.

- Added state management for all transports
- Added connection.stateChanged property to detect state changes on the connection.
- Updated Raw sample to show connection status.
- Fixes #431.
  • Loading branch information...
1 parent 47e17b6 commit bbf6e4b705fe491d8929e47c46b8e86286b24508 @davidfowl davidfowl committed Jun 5, 2012
View
94 SignalR/Scripts/jquery.signalR.js
@@ -46,6 +46,7 @@
onReceived: "onReceived",
onError: "onError",
onReconnect: "onReconnect",
+ onStateChanged: "onStateChanged",
onDisconnect: "onDisconnect"
},
log = function (msg, logging) {
@@ -62,6 +63,12 @@
} else if (window.console.log) {
window.console.log(m);
}
+ },
+ changeState = function (connection, state) {
+ if (state !== connection.state) {
+ $(connection).trigger(events.onStateChanged, [state]);
+ connection.state = state;
+ }
};
signalR = function (url, qs, logging) {
@@ -81,6 +88,14 @@
return new signalR.fn.init(url, qs, logging);
};
+ signalR.connectionState = {
+ connecting: 0,
+ connected: 1,
+ reconnecting: 2,
+ disconnecting: 3,
+ disconnected: 4
+ };
+
signalR.fn = signalR.prototype = {
init: function (url, qs, logging) {
this.url = url;
@@ -93,6 +108,8 @@
logging: false,
+ state: signalR.connectionState.disconnected,
+
reconnectDelay: 2000,
start: function (options, callback) {
@@ -108,12 +125,16 @@
deferred = $.Deferred(),
parser = document.createElement('a');
- if (connection.transport) {
+ if (connection.state === signalR.connectionState.connecting ||
+ connection.state === signalR.connectionState.connected) {
// Already started, just return
deferred.resolve(connection);
return deferred.promise();
}
+ // Set the state to connecting
+ changeState(connection, signalR.connectionState.connecting);
+
if ($.type(options) === "function") {
// Support calling with single callback parameter
callback = options;
@@ -184,8 +205,11 @@
transport.start(connection, function () {
connection.transport = transport;
+
$(connection).trigger(events.onStart);
+ changeState(connection, signalR.connectionState.connected);
+
$(window).unload(function () {
connection.stop(false /* async */);
});
@@ -208,6 +232,8 @@
error: function (error) {
$(connection).trigger(events.onError, [error.responseText]);
deferred.reject("SignalR: Error during negotiation request: " + error);
+ // Stop the connection if negotiate failed
+ connection.stop();
},
success: function (res) {
connection.appRelativeUrl = res.Url;
@@ -310,6 +336,17 @@
return connection;
},
+ stateChanged: function (callback) {
+ /// <summary>Adds a callback that will be invoked when the connection state changes</summary>
+ /// <param name="callback" type="Function">A callback function to execute when the connection state changes</param>
+ /// <returns type="signalR" />
+ var connection = this;
+ $(connection).bind(events.onStateChanged, function (e, data) {
+ callback.call(connection, data);
+ });
+ return connection;
+ },
+
error: function (callback) {
/// <summary>Adds a callback that will be invoked after an error occurs with the connection</summary>
/// <param name="callback" type="Function">A callback function to execute when an error occurs on the connection</param>
@@ -348,17 +385,29 @@
/// <returns type="signalR" />
var connection = this;
- if (connection.transport) {
- connection.transport.abort(connection, async);
- connection.transport.stop(connection);
- connection.transport = null;
+ if (connection.state === signalR.connectionState.disconnecting ||
+ connection.state === signalR.connectionState.disconnected) {
+ return;
}
- delete connection.messageId;
- delete connection.groups;
+ try {
+ changeState(connection, signalR.connectionState.disconnecting);
+
+ if (connection.transport) {
+ connection.transport.abort(connection, async);
+ connection.transport.stop(connection);
+ connection.transport = null;
+ }
- // Trigger the disconnect event
- $(connection).trigger(events.onDisconnect);
+ // Trigger the disconnect event
+ $(connection).trigger(events.onDisconnect);
+
+ delete connection.messageId;
+ delete connection.groups;
+ }
+ finally {
+ changeState(connection, signalR.connectionState.disconnected);
+ }
return connection;
},
@@ -566,6 +615,9 @@
if (onSuccess) {
onSuccess();
}
+ else {
+ changeState(connection, signalR.connectionState.connected);
+ }
};
connection.socket.onclose = function (event) {
@@ -585,6 +637,8 @@
log("Websocket closed");
}
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.stop(connection);
that.start(connection);
};
@@ -704,6 +758,8 @@
if (reconnecting) {
$connection.trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
}, false);
@@ -734,6 +790,9 @@
// to change the URL to not include the /connect suffix, and pass
// the last message id we received.
log("EventSource reconnecting due to the server connection ending");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
else {
@@ -808,6 +867,9 @@
frame.bind("readystatechange", function () {
if ($.inArray(this.readyState, ["loaded", "complete"]) >= 0) {
log("Forever frame iframe readyState changed to " + this.readyState + ", reconnecting");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
});
@@ -885,6 +947,8 @@
else {
// If there's no onSuccess handler we assume this is a reconnect
$(connection).trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
},
@@ -913,10 +977,15 @@
var messageId = instance.messageId,
connect = (messageId === null),
- url = transportLogic.getUrl(instance, that.name, !connect, raiseReconnect),
+ reconnecting = !connect,
+ url = transportLogic.getUrl(instance, that.name, reconnecting, raiseReconnect),
reconnectTimeOut = null,
reconnectFired = false;
+ if (reconnecting === true && raiseReconnect === true) {
+ changeState(connection, signalR.connectionState.reconnecting);
+ }
+
log("Attempting to connect to '" + url + "' using longPolling.");
instance.pollXhr = $.ajax({
url: url,
@@ -936,6 +1005,9 @@
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
log("Raising the reconnect event");
+
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
@@ -990,6 +1062,8 @@
if (raiseReconnect === true) {
reconnectTimeOut = window.setTimeout(function () {
if (reconnectFired === false) {
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
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";function o(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}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 s=this,h={transport:"auto",jsonp:!1},a,c=n.Deferred(),l=document.createElement("a");return s.transport?(c.resolve(s),c.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(h,u),n.type(h.callback)==="function"&&(e=h.callback)),l.href=s.url,s.baseUrl=l.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:l.protocol+"//"+l.host,o(s.url)&&(i("Auto detected cross domain url."),h.transport==="auto"&&(h.jsonp||(h.jsonp=!n.support.cors,h.jsonp&&i("Using jsonp because this browser doesn't support cors")),h.transport=h.jsonp===!0?"longPolling":["webSockets","longPolling"])),s.ajaxDataType=h.jsonp?"jsonp":"json",n(s).bind(r.onStart,function(){n.type(e)==="function"&&e.call(s),c.resolve(s)}),a=function(i,u){if(u=u||0,u>=i.length){s.transport||c.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],o=n.type(e)==="object"?e:f.transports[e];o.start(s,function(){s.transport=o,n(s).trigger(r.onStart),n(t).unload(function(){s.stop(!1)})},function(){a(i,u+1)})},t.setTimeout(function(){var t=s.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:s.ajaxDataType,error:function(t){n(s).trigger(r.onError,[t.responseText]),c.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(s.appRelativeUrl=t.Url,s.id=t.ConnectionId,s.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(s).trigger(r.onError,"SignalR: Incompatible protocol version."),c.reject("SignalR: Incompatible protocol version.");return}n(s).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(h.transport)?n.each(h.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(h.transport)==="object"||n.inArray(h.transport,i)>=0?u.push(h.transport):u=i,a(u)}})},0),c.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:function(n){i(n,this.logging)}},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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),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(f,e,o){var h,l=!1,a=this,v=!e,c,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){o();return}f.socket||(f.webSocketServerUrl?h=f.webSocketServerUrl:(s=document.location,s.protocol!=="http:"&&s.protocol!=="https:"&&(s=t.document.createElement("a"),s.href=f.url),c=s.protocol==="https:"?"wss://":"ws://",h=c+s.host),n(f).trigger(r.onSending),h+=u.getUrl(f,this.name,v),i("Connecting to websocket endpoint '"+h+"'"),f.socket=new t.WebSocket(h),f.socket.onopen=function(){l=!0,i("Websocket opened"),e&&e()},f.socket.onclose=function(t){l?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(f).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):o&&o(),a.stop(f),a.start(f)},f.socket.onmessage=function(i){var e=t.JSON.parse(i.data),o;e&&(o=n(f),e.Messages?u.processMessages(f,e):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),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting"),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect"),o&&o(),h?(i("EventSource reconnecting"),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected"),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),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),s.reconnect(f)):(i("EventSource closed"),s.stop(f)):(i("EventSource error"),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"),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. Status = "+e+". "+u.responseText),v&&clearTimeout(v),n(c).trigger(r.onError,[u.responseText]),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)
+(function(n,t){"use strict";function s(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}var f;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.";var u,o,r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onStateChanged:"onStateChanged",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))}},e=function(t,i){i!==t.state&&(n(t).trigger(r.onStateChanged,[i]),t.state=i)};u=function(n,t,i){return new u.fn.init(n,t,i)},u.connectionState={connecting:0,connected:1,reconnecting:2,disconnecting:3,disconnected:4},u.fn=u.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,state:u.connectionState.disconnected,reconnectDelay:2e3,start:function(f,o){var h=this,c={transport:"auto",jsonp:!1},v,l=n.Deferred(),a=document.createElement("a");return h.state===u.connectionState.connecting||h.state===u.connectionState.connected?(l.resolve(h),l.promise()):(e(h,u.connectionState.connecting),n.type(f)==="function"?o=f:n.type(f)==="object"&&(n.extend(c,f),n.type(c.callback)==="function"&&(o=c.callback)),a.href=h.url,h.baseUrl=a.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:a.protocol+"//"+a.host,s(h.url)&&(i("Auto detected cross domain url."),c.transport==="auto"&&(c.jsonp||(c.jsonp=!n.support.cors,c.jsonp&&i("Using jsonp because this browser doesn't support cors")),c.transport=c.jsonp===!0?"longPolling":["webSockets","longPolling"])),h.ajaxDataType=c.jsonp?"jsonp":"json",n(h).bind(r.onStart,function(){n.type(o)==="function"&&o.call(h),l.resolve(h)}),v=function(i,f){if(f=f||0,f>=i.length){h.transport||l.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var o=i[f],s=n.type(o)==="object"?o:u.transports[o];s.start(h,function(){h.transport=s,n(h).trigger(r.onStart),e(h,u.connectionState.connected),n(t).unload(function(){h.stop(!1)})},function(){v(i,f+1)})},t.setTimeout(function(){var t=h.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:h.ajaxDataType,error:function(t){n(h).trigger(r.onError,[t.responseText]),l.reject("SignalR: Error during negotiation request: "+t),h.stop()},success:function(t){if(h.appRelativeUrl=t.Url,h.id=t.ConnectionId,h.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(h).trigger(r.onError,"SignalR: Incompatible protocol version."),l.reject("SignalR: Incompatible protocol version.");return}n(h).trigger(r.onStarting);var f=[],i=[];n.each(u.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(c.transport)?n.each(c.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&f.push(n.type(t)==="string"?""+t:t)}):n.type(c.transport)==="object"||n.inArray(c.transport,i)>=0?f.push(c.transport):f=i,v(f)}})},0),l.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},stateChanged:function(t){var i=this;return n(i).bind(r.onStateChanged,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;if(i.state!==u.connectionState.disconnecting&&i.state!==u.connectionState.disconnected){try{e(i,u.connectionState.disconnecting),i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),n(i).trigger(r.onDisconnect),delete i.messageId,delete i.groups}finally{e(i,u.connectionState.disconnected)}return i}},log:function(n){i(n,this.logging)}},u.fn.init.prototype=u.fn,f={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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},u.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(o,s,h){var l,v=!1,y=this,p=!s,a,c;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){h();return}o.socket||(o.webSocketServerUrl?l=o.webSocketServerUrl:(c=document.location,c.protocol!=="http:"&&c.protocol!=="https:"&&(c=t.document.createElement("a"),c.href=o.url),a=c.protocol==="https:"?"wss://":"ws://",l=a+c.host),n(o).trigger(r.onSending),l+=f.getUrl(o,this.name,p),i("Connecting to websocket endpoint '"+l+"'"),o.socket=new t.WebSocket(l),o.socket.onopen=function(){v=!0,i("Websocket opened"),s?s():e(o,u.connectionState.connected)},o.socket.onclose=function(t){v?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(o).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):h&&h(),e(o,u.connectionState.reconnecting),y.stop(o),y.start(o)},o.socket.onmessage=function(i){var u=t.JSON.parse(i.data),e;u&&(e=n(o),u.Messages?f.processMessages(o,u):e.trigger(r.onReceived,[u]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(o,s,h){var c=this,v=!1,a=n(o),l=!s,p,y;if(o.eventSource&&(i("The connection already has an event source. Stopping it."),o.stop()),!t.EventSource){h&&(i("This browser doesn't support SSE."),h());return}a.trigger(r.onSending),p=f.getUrl(o,this.name,l);try{i("Attempting to connect to SSE endpoint '"+p+"'"),o.eventSource=new t.EventSource(p)}catch(w){i("EventSource failed trying to connect with error "+w.Message),h?h():(a.trigger(r.onError,[w]),l&&(i("EventSource reconnecting"),c.reconnect(o)));return}y=t.setTimeout(function(){v===!1&&(i("EventSource timed out trying to connect"),h&&h(),l?(i("EventSource reconnecting"),c.reconnect(o)):c.stop(o))},c.timeOut),o.eventSource.addEventListener("open",function(){i("EventSource connected"),y&&t.clearTimeout(y),v===!1&&(v=!0,s&&s(),l&&(a.trigger(r.onReconnect),e(o,u.connectionState.connected)))},!1),o.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&f.processMessages(o,t.JSON.parse(n.data))},!1),o.eventSource.addEventListener("error",function(n){if(!v){h&&h();return}i("EventSource readyState: "+o.eventSource.readyState),n.eventPhase===t.EventSource.CLOSED?o.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),e(o,u.connectionState.reconnecting),c.reconnect(o)):(i("EventSource closed"),c.stop(o)):(i("EventSource error"),a.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){f.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){f.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(o,s,h){var l=this,a=f.foreverFrame.count+=1,v,y,c=n("<iframe data-signalr-connection-id='"+o.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){h&&(i("This brower supports SSE, skipping Forever Frame."),h());return}n(o).trigger(r.onSending),v=f.getUrl(o,this.name),v+="&frameId="+a,c.prop("src",v),f.foreverFrame.connections[a]=o,i("Binding to iframe's readystatechange event."),c.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting"),e(o,u.connectionState.reconnecting),l.reconnect(o))}),o.frame=c[0],o.frameId=a,s&&(o.onSuccess=s),n("body").append(c),y=t.setTimeout(function(){o.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+l.timeOut+"ms."),l.stop(o),h&&h())},l.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var u=n.frame,t=f.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),u.src=t},n.reconnectDelay)},send:function(n,t){f.ajaxSend(n,t)},receive:f.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 f.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){f.ajaxAbort(n,t)},getConnection:function(n){return f.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):(n(t).trigger(r.onReconnect),e(t,u.connectionState.connected))}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(o,s){var l=this,c=!1;o.pollXhr&&(i("Polling xhr requests already exists, aborting."),o.stop()),o.messageId=null,t.setTimeout(function(){(function h(a,v){n(a).trigger(r.onSending);var d=a.messageId,k=d===null,b=!k,w=f.getUrl(a,l.name,b,v),p=null,y=!1;b===!0&&v===!0&&e(o,u.connectionState.reconnecting),i("Attempting to connect to '"+w+"' using longPolling."),a.pollXhr=n.ajax({url:w,global:!1,type:"GET",dataType:o.ajaxDataType,success:function(l){var w=0,p=!1;(c==!1&&(s(),c=!0),v===!0&&y===!1&&(i("Raising the reconnect event"),e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0),f.processMessages(a,l),l&&l.TransportData&&n.type(l.TransportData.LongPollDelay)==="number"&&(w=l.TransportData.LongPollDelay),l&&l.TimedOut&&(p=l.TimedOut),l&&l.Disconnect)||(w>0?t.setTimeout(function(){h(a,p)},w):h(a,p))},error:function(u,f){if(f==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling. Status = "+f+". "+u.responseText),p&&clearTimeout(p),n(a).trigger(r.onError,[u.responseText]),t.setTimeout(function(){h(a,!0)},o.reconnectDelay)}}),v===!0&&(p=t.setTimeout(function(){y===!1&&(e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0)},l.reconnectDelay))})(o),t.setTimeout(function(){c===!1&&(s(),c=!0)},150)},250)},send:function(n,t){f.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){f.ajaxAbort(n,t)}}},u.noConflict=function(){return n.connection===u&&(n.connection=o),u},n.connection&&(o=n.connection),n.connection=n.signalR=u})(window.jQuery,window)
View
11 samples/SignalR.Client.Samples/Program.cs
@@ -10,13 +10,13 @@ class Program
{
static void Main(string[] args)
{
- RunInMemoryHost();
+ // RunInMemoryHost();
// var hubConnection = new HubConnection("http://localhost:40476/");
//RunDemoHub(hubConnection);
- //RunStreamingSample();
+ RunStreamingSample();
Console.ReadKey();
}
@@ -79,7 +79,7 @@ private static void RunDemoHub(HubConnection hubConnection)
private static void RunStreamingSample()
{
- var connection = new Connection("http://localhost:40476/Raw/raw");
+ var connection = new Connection("http://localhost:40476/Raw/Raw");
connection.Received += data =>
{
@@ -91,6 +91,11 @@ private static void RunStreamingSample()
Console.WriteLine("[{0}]: Connection restablished", DateTime.Now);
};
+ connection.StateChanged += newState =>
+ {
+ Console.WriteLine("{0} => {1}", connection.State, newState);
+ };
+
connection.Error += e =>
{
Console.WriteLine(e);
View
7 samples/SignalR.Hosting.AspNet.Samples/Raw/Raw.cs
@@ -26,6 +26,13 @@ protected override Task OnConnectedAsync(IRequest request, string connectionId)
Connection.Broadcast(DateTime.Now + ": " + user + " joined")).Unwrap();
}
+ protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string connectionId)
+ {
+ string user = GetUser(connectionId);
+
+ return Connection.Broadcast(DateTime.Now + ": " + user + " reconnected");
+ }
+
protected override Task OnDisconnectAsync(string connectionId)
{
_users.Remove(connectionId);
View
18 samples/SignalR.Hosting.AspNet.Samples/Raw/crossdomain.htm
@@ -42,10 +42,26 @@
});
connection.error(function (err) {
- $("<li/>").html(err)
+ $("<li/>").html(err || "Error occurred")
.appendTo($("#messages"));
});
+ connection.stateChanged(function (state) {
+ var old = null,
+ newState = null;
+ for (var p in $.signalR.connectionState) {
+ if ($.signalR.connectionState[p] === connection.state) {
+ old = p;
+ }
+
+ if ($.signalR.connectionState[p] === state) {
+ newState = p;
+ }
+ }
+
+ $("<li/>").html(old + " => " + newState)
+ .appendTo($("#messages"));
+ });
var start = function () {
connection.start()
View
21 samples/SignalR.Hosting.AspNet.Samples/Raw/index.htm
@@ -41,8 +41,8 @@
.appendTo($("#messages"));
});
- connection.error(function (e) {
- $("<li/>").html(e.responseText)
+ connection.error(function (err) {
+ $("<li/>").html(err || "Error occurred")
.appendTo($("#messages"));
});
@@ -51,6 +51,23 @@
.prop("disabled", false);
});
+ connection.stateChanged(function (state) {
+ var old = null,
+ newState = null;
+ for (var p in $.signalR.connectionState) {
+ if ($.signalR.connectionState[p] === connection.state) {
+ old = p;
+ }
+
+ if ($.signalR.connectionState[p] === state) {
+ newState = p;
+ }
+ }
+
+ $("<li/>").html(old + " => " + newState)
+ .appendTo($("#messages"));
+ });
+
var start = function () {
connection.start({ transport: activeTransport })
.then(function () {
View
94 samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.js
@@ -46,6 +46,7 @@
onReceived: "onReceived",
onError: "onError",
onReconnect: "onReconnect",
+ onStateChanged: "onStateChanged",
onDisconnect: "onDisconnect"
},
log = function (msg, logging) {
@@ -62,6 +63,12 @@
} else if (window.console.log) {
window.console.log(m);
}
+ },
+ changeState = function (connection, state) {
+ if (state !== connection.state) {
+ $(connection).trigger(events.onStateChanged, [state]);
+ connection.state = state;
+ }
};
signalR = function (url, qs, logging) {
@@ -81,6 +88,14 @@
return new signalR.fn.init(url, qs, logging);
};
+ signalR.connectionState = {
+ connecting: 0,
+ connected: 1,
+ reconnecting: 2,
+ disconnecting: 3,
+ disconnected: 4
+ };
+
signalR.fn = signalR.prototype = {
init: function (url, qs, logging) {
this.url = url;
@@ -93,6 +108,8 @@
logging: false,
+ state: signalR.connectionState.disconnected,
+
reconnectDelay: 2000,
start: function (options, callback) {
@@ -108,12 +125,16 @@
deferred = $.Deferred(),
parser = document.createElement('a');
- if (connection.transport) {
+ if (connection.state === signalR.connectionState.connecting ||
+ connection.state === signalR.connectionState.connected) {
// Already started, just return
deferred.resolve(connection);
return deferred.promise();
}
+ // Set the state to connecting
+ changeState(connection, signalR.connectionState.connecting);
+
if ($.type(options) === "function") {
// Support calling with single callback parameter
callback = options;
@@ -184,8 +205,11 @@
transport.start(connection, function () {
connection.transport = transport;
+
$(connection).trigger(events.onStart);
+ changeState(connection, signalR.connectionState.connected);
+
$(window).unload(function () {
connection.stop(false /* async */);
});
@@ -208,6 +232,8 @@
error: function (error) {
$(connection).trigger(events.onError, [error.responseText]);
deferred.reject("SignalR: Error during negotiation request: " + error);
+ // Stop the connection if negotiate failed
+ connection.stop();
},
success: function (res) {
connection.appRelativeUrl = res.Url;
@@ -310,6 +336,17 @@
return connection;
},
+ stateChanged: function (callback) {
+ /// <summary>Adds a callback that will be invoked when the connection state changes</summary>
+ /// <param name="callback" type="Function">A callback function to execute when the connection state changes</param>
+ /// <returns type="signalR" />
+ var connection = this;
+ $(connection).bind(events.onStateChanged, function (e, data) {
+ callback.call(connection, data);
+ });
+ return connection;
+ },
+
error: function (callback) {
/// <summary>Adds a callback that will be invoked after an error occurs with the connection</summary>
/// <param name="callback" type="Function">A callback function to execute when an error occurs on the connection</param>
@@ -348,17 +385,29 @@
/// <returns type="signalR" />
var connection = this;
- if (connection.transport) {
- connection.transport.abort(connection, async);
- connection.transport.stop(connection);
- connection.transport = null;
+ if (connection.state === signalR.connectionState.disconnecting ||
+ connection.state === signalR.connectionState.disconnected) {
+ return;
}
- delete connection.messageId;
- delete connection.groups;
+ try {
+ changeState(connection, signalR.connectionState.disconnecting);
+
+ if (connection.transport) {
+ connection.transport.abort(connection, async);
+ connection.transport.stop(connection);
+ connection.transport = null;
+ }
- // Trigger the disconnect event
- $(connection).trigger(events.onDisconnect);
+ // Trigger the disconnect event
+ $(connection).trigger(events.onDisconnect);
+
+ delete connection.messageId;
+ delete connection.groups;
+ }
+ finally {
+ changeState(connection, signalR.connectionState.disconnected);
+ }
return connection;
},
@@ -566,6 +615,9 @@
if (onSuccess) {
onSuccess();
}
+ else {
+ changeState(connection, signalR.connectionState.connected);
+ }
};
connection.socket.onclose = function (event) {
@@ -585,6 +637,8 @@
log("Websocket closed");
}
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.stop(connection);
that.start(connection);
};
@@ -704,6 +758,8 @@
if (reconnecting) {
$connection.trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
}, false);
@@ -734,6 +790,9 @@
// to change the URL to not include the /connect suffix, and pass
// the last message id we received.
log("EventSource reconnecting due to the server connection ending");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
else {
@@ -808,6 +867,9 @@
frame.bind("readystatechange", function () {
if ($.inArray(this.readyState, ["loaded", "complete"]) >= 0) {
log("Forever frame iframe readyState changed to " + this.readyState + ", reconnecting");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
});
@@ -885,6 +947,8 @@
else {
// If there's no onSuccess handler we assume this is a reconnect
$(connection).trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
},
@@ -913,10 +977,15 @@
var messageId = instance.messageId,
connect = (messageId === null),
- url = transportLogic.getUrl(instance, that.name, !connect, raiseReconnect),
+ reconnecting = !connect,
+ url = transportLogic.getUrl(instance, that.name, reconnecting, raiseReconnect),
reconnectTimeOut = null,
reconnectFired = false;
+ if (reconnecting === true && raiseReconnect === true) {
+ changeState(connection, signalR.connectionState.reconnecting);
+ }
+
log("Attempting to connect to '" + url + "' using longPolling.");
instance.pollXhr = $.ajax({
url: url,
@@ -936,6 +1005,9 @@
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
log("Raising the reconnect event");
+
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
@@ -990,6 +1062,8 @@
if (raiseReconnect === true) {
reconnectTimeOut = window.setTimeout(function () {
if (reconnectFired === false) {
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
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";function o(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}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 s=this,h={transport:"auto",jsonp:!1},a,c=n.Deferred(),l=document.createElement("a");return s.transport?(c.resolve(s),c.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(h,u),n.type(h.callback)==="function"&&(e=h.callback)),l.href=s.url,s.baseUrl=l.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:l.protocol+"//"+l.host,o(s.url)&&(i("Auto detected cross domain url."),h.transport==="auto"&&(h.jsonp||(h.jsonp=!n.support.cors,h.jsonp&&i("Using jsonp because this browser doesn't support cors")),h.transport=h.jsonp===!0?"longPolling":["webSockets","longPolling"])),s.ajaxDataType=h.jsonp?"jsonp":"json",n(s).bind(r.onStart,function(){n.type(e)==="function"&&e.call(s),c.resolve(s)}),a=function(i,u){if(u=u||0,u>=i.length){s.transport||c.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],o=n.type(e)==="object"?e:f.transports[e];o.start(s,function(){s.transport=o,n(s).trigger(r.onStart),n(t).unload(function(){s.stop(!1)})},function(){a(i,u+1)})},t.setTimeout(function(){var t=s.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:s.ajaxDataType,error:function(t){n(s).trigger(r.onError,[t.responseText]),c.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(s.appRelativeUrl=t.Url,s.id=t.ConnectionId,s.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(s).trigger(r.onError,"SignalR: Incompatible protocol version."),c.reject("SignalR: Incompatible protocol version.");return}n(s).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(h.transport)?n.each(h.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(h.transport)==="object"||n.inArray(h.transport,i)>=0?u.push(h.transport):u=i,a(u)}})},0),c.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:function(n){i(n,this.logging)}},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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),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(f,e,o){var h,l=!1,a=this,v=!e,c,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){o();return}f.socket||(f.webSocketServerUrl?h=f.webSocketServerUrl:(s=document.location,s.protocol!=="http:"&&s.protocol!=="https:"&&(s=t.document.createElement("a"),s.href=f.url),c=s.protocol==="https:"?"wss://":"ws://",h=c+s.host),n(f).trigger(r.onSending),h+=u.getUrl(f,this.name,v),i("Connecting to websocket endpoint '"+h+"'"),f.socket=new t.WebSocket(h),f.socket.onopen=function(){l=!0,i("Websocket opened"),e&&e()},f.socket.onclose=function(t){l?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(f).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):o&&o(),a.stop(f),a.start(f)},f.socket.onmessage=function(i){var e=t.JSON.parse(i.data),o;e&&(o=n(f),e.Messages?u.processMessages(f,e):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),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting"),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect"),o&&o(),h?(i("EventSource reconnecting"),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected"),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),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),s.reconnect(f)):(i("EventSource closed"),s.stop(f)):(i("EventSource error"),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"),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. Status = "+e+". "+u.responseText),v&&clearTimeout(v),n(c).trigger(r.onError,[u.responseText]),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)
+(function(n,t){"use strict";function s(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}var f;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.";var u,o,r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onStateChanged:"onStateChanged",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))}},e=function(t,i){i!==t.state&&(n(t).trigger(r.onStateChanged,[i]),t.state=i)};u=function(n,t,i){return new u.fn.init(n,t,i)},u.connectionState={connecting:0,connected:1,reconnecting:2,disconnecting:3,disconnected:4},u.fn=u.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,state:u.connectionState.disconnected,reconnectDelay:2e3,start:function(f,o){var h=this,c={transport:"auto",jsonp:!1},v,l=n.Deferred(),a=document.createElement("a");return h.state===u.connectionState.connecting||h.state===u.connectionState.connected?(l.resolve(h),l.promise()):(e(h,u.connectionState.connecting),n.type(f)==="function"?o=f:n.type(f)==="object"&&(n.extend(c,f),n.type(c.callback)==="function"&&(o=c.callback)),a.href=h.url,h.baseUrl=a.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:a.protocol+"//"+a.host,s(h.url)&&(i("Auto detected cross domain url."),c.transport==="auto"&&(c.jsonp||(c.jsonp=!n.support.cors,c.jsonp&&i("Using jsonp because this browser doesn't support cors")),c.transport=c.jsonp===!0?"longPolling":["webSockets","longPolling"])),h.ajaxDataType=c.jsonp?"jsonp":"json",n(h).bind(r.onStart,function(){n.type(o)==="function"&&o.call(h),l.resolve(h)}),v=function(i,f){if(f=f||0,f>=i.length){h.transport||l.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var o=i[f],s=n.type(o)==="object"?o:u.transports[o];s.start(h,function(){h.transport=s,n(h).trigger(r.onStart),e(h,u.connectionState.connected),n(t).unload(function(){h.stop(!1)})},function(){v(i,f+1)})},t.setTimeout(function(){var t=h.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:h.ajaxDataType,error:function(t){n(h).trigger(r.onError,[t.responseText]),l.reject("SignalR: Error during negotiation request: "+t),h.stop()},success:function(t){if(h.appRelativeUrl=t.Url,h.id=t.ConnectionId,h.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(h).trigger(r.onError,"SignalR: Incompatible protocol version."),l.reject("SignalR: Incompatible protocol version.");return}n(h).trigger(r.onStarting);var f=[],i=[];n.each(u.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(c.transport)?n.each(c.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&f.push(n.type(t)==="string"?""+t:t)}):n.type(c.transport)==="object"||n.inArray(c.transport,i)>=0?f.push(c.transport):f=i,v(f)}})},0),l.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},stateChanged:function(t){var i=this;return n(i).bind(r.onStateChanged,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;if(i.state!==u.connectionState.disconnecting&&i.state!==u.connectionState.disconnected){try{e(i,u.connectionState.disconnecting),i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),n(i).trigger(r.onDisconnect),delete i.messageId,delete i.groups}finally{e(i,u.connectionState.disconnected)}return i}},log:function(n){i(n,this.logging)}},u.fn.init.prototype=u.fn,f={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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},u.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(o,s,h){var l,v=!1,y=this,p=!s,a,c;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){h();return}o.socket||(o.webSocketServerUrl?l=o.webSocketServerUrl:(c=document.location,c.protocol!=="http:"&&c.protocol!=="https:"&&(c=t.document.createElement("a"),c.href=o.url),a=c.protocol==="https:"?"wss://":"ws://",l=a+c.host),n(o).trigger(r.onSending),l+=f.getUrl(o,this.name,p),i("Connecting to websocket endpoint '"+l+"'"),o.socket=new t.WebSocket(l),o.socket.onopen=function(){v=!0,i("Websocket opened"),s?s():e(o,u.connectionState.connected)},o.socket.onclose=function(t){v?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(o).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):h&&h(),e(o,u.connectionState.reconnecting),y.stop(o),y.start(o)},o.socket.onmessage=function(i){var u=t.JSON.parse(i.data),e;u&&(e=n(o),u.Messages?f.processMessages(o,u):e.trigger(r.onReceived,[u]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(o,s,h){var c=this,v=!1,a=n(o),l=!s,p,y;if(o.eventSource&&(i("The connection already has an event source. Stopping it."),o.stop()),!t.EventSource){h&&(i("This browser doesn't support SSE."),h());return}a.trigger(r.onSending),p=f.getUrl(o,this.name,l);try{i("Attempting to connect to SSE endpoint '"+p+"'"),o.eventSource=new t.EventSource(p)}catch(w){i("EventSource failed trying to connect with error "+w.Message),h?h():(a.trigger(r.onError,[w]),l&&(i("EventSource reconnecting"),c.reconnect(o)));return}y=t.setTimeout(function(){v===!1&&(i("EventSource timed out trying to connect"),h&&h(),l?(i("EventSource reconnecting"),c.reconnect(o)):c.stop(o))},c.timeOut),o.eventSource.addEventListener("open",function(){i("EventSource connected"),y&&t.clearTimeout(y),v===!1&&(v=!0,s&&s(),l&&(a.trigger(r.onReconnect),e(o,u.connectionState.connected)))},!1),o.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&f.processMessages(o,t.JSON.parse(n.data))},!1),o.eventSource.addEventListener("error",function(n){if(!v){h&&h();return}i("EventSource readyState: "+o.eventSource.readyState),n.eventPhase===t.EventSource.CLOSED?o.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),e(o,u.connectionState.reconnecting),c.reconnect(o)):(i("EventSource closed"),c.stop(o)):(i("EventSource error"),a.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){f.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){f.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(o,s,h){var l=this,a=f.foreverFrame.count+=1,v,y,c=n("<iframe data-signalr-connection-id='"+o.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){h&&(i("This brower supports SSE, skipping Forever Frame."),h());return}n(o).trigger(r.onSending),v=f.getUrl(o,this.name),v+="&frameId="+a,c.prop("src",v),f.foreverFrame.connections[a]=o,i("Binding to iframe's readystatechange event."),c.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting"),e(o,u.connectionState.reconnecting),l.reconnect(o))}),o.frame=c[0],o.frameId=a,s&&(o.onSuccess=s),n("body").append(c),y=t.setTimeout(function(){o.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+l.timeOut+"ms."),l.stop(o),h&&h())},l.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var u=n.frame,t=f.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),u.src=t},n.reconnectDelay)},send:function(n,t){f.ajaxSend(n,t)},receive:f.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 f.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){f.ajaxAbort(n,t)},getConnection:function(n){return f.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):(n(t).trigger(r.onReconnect),e(t,u.connectionState.connected))}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(o,s){var l=this,c=!1;o.pollXhr&&(i("Polling xhr requests already exists, aborting."),o.stop()),o.messageId=null,t.setTimeout(function(){(function h(a,v){n(a).trigger(r.onSending);var d=a.messageId,k=d===null,b=!k,w=f.getUrl(a,l.name,b,v),p=null,y=!1;b===!0&&v===!0&&e(o,u.connectionState.reconnecting),i("Attempting to connect to '"+w+"' using longPolling."),a.pollXhr=n.ajax({url:w,global:!1,type:"GET",dataType:o.ajaxDataType,success:function(l){var w=0,p=!1;(c==!1&&(s(),c=!0),v===!0&&y===!1&&(i("Raising the reconnect event"),e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0),f.processMessages(a,l),l&&l.TransportData&&n.type(l.TransportData.LongPollDelay)==="number"&&(w=l.TransportData.LongPollDelay),l&&l.TimedOut&&(p=l.TimedOut),l&&l.Disconnect)||(w>0?t.setTimeout(function(){h(a,p)},w):h(a,p))},error:function(u,f){if(f==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling. Status = "+f+". "+u.responseText),p&&clearTimeout(p),n(a).trigger(r.onError,[u.responseText]),t.setTimeout(function(){h(a,!0)},o.reconnectDelay)}}),v===!0&&(p=t.setTimeout(function(){y===!1&&(e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0)},l.reconnectDelay))})(o),t.setTimeout(function(){c===!1&&(s(),c=!0)},150)},250)},send:function(n,t){f.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){f.ajaxAbort(n,t)}}},u.noConflict=function(){return n.connection===u&&(n.connection=o),u},n.connection&&(o=n.connection),n.connection=n.signalR=u})(window.jQuery,window)
View
94 samples/SignalR.Hosting.Owin.Samples/Content/Scripts/jquery.signalR.js
@@ -46,6 +46,7 @@
onReceived: "onReceived",
onError: "onError",
onReconnect: "onReconnect",
+ onStateChanged: "onStateChanged",
onDisconnect: "onDisconnect"
},
log = function (msg, logging) {
@@ -62,6 +63,12 @@
} else if (window.console.log) {
window.console.log(m);
}
+ },
+ changeState = function (connection, state) {
+ if (state !== connection.state) {
+ $(connection).trigger(events.onStateChanged, [state]);
+ connection.state = state;
+ }
};
signalR = function (url, qs, logging) {
@@ -81,6 +88,14 @@
return new signalR.fn.init(url, qs, logging);
};
+ signalR.connectionState = {
+ connecting: 0,
+ connected: 1,
+ reconnecting: 2,
+ disconnecting: 3,
+ disconnected: 4
+ };
+
signalR.fn = signalR.prototype = {
init: function (url, qs, logging) {
this.url = url;
@@ -93,6 +108,8 @@
logging: false,
+ state: signalR.connectionState.disconnected,
+
reconnectDelay: 2000,
start: function (options, callback) {
@@ -108,12 +125,16 @@
deferred = $.Deferred(),
parser = document.createElement('a');
- if (connection.transport) {
+ if (connection.state === signalR.connectionState.connecting ||
+ connection.state === signalR.connectionState.connected) {
// Already started, just return
deferred.resolve(connection);
return deferred.promise();
}
+ // Set the state to connecting
+ changeState(connection, signalR.connectionState.connecting);
+
if ($.type(options) === "function") {
// Support calling with single callback parameter
callback = options;
@@ -184,8 +205,11 @@
transport.start(connection, function () {
connection.transport = transport;
+
$(connection).trigger(events.onStart);
+ changeState(connection, signalR.connectionState.connected);
+
$(window).unload(function () {
connection.stop(false /* async */);
});
@@ -208,6 +232,8 @@
error: function (error) {
$(connection).trigger(events.onError, [error.responseText]);
deferred.reject("SignalR: Error during negotiation request: " + error);
+ // Stop the connection if negotiate failed
+ connection.stop();
},
success: function (res) {
connection.appRelativeUrl = res.Url;
@@ -310,6 +336,17 @@
return connection;
},
+ stateChanged: function (callback) {
+ /// <summary>Adds a callback that will be invoked when the connection state changes</summary>
+ /// <param name="callback" type="Function">A callback function to execute when the connection state changes</param>
+ /// <returns type="signalR" />
+ var connection = this;
+ $(connection).bind(events.onStateChanged, function (e, data) {
+ callback.call(connection, data);
+ });
+ return connection;
+ },
+
error: function (callback) {
/// <summary>Adds a callback that will be invoked after an error occurs with the connection</summary>
/// <param name="callback" type="Function">A callback function to execute when an error occurs on the connection</param>
@@ -348,17 +385,29 @@
/// <returns type="signalR" />
var connection = this;
- if (connection.transport) {
- connection.transport.abort(connection, async);
- connection.transport.stop(connection);
- connection.transport = null;
+ if (connection.state === signalR.connectionState.disconnecting ||
+ connection.state === signalR.connectionState.disconnected) {
+ return;
}
- delete connection.messageId;
- delete connection.groups;
+ try {
+ changeState(connection, signalR.connectionState.disconnecting);
+
+ if (connection.transport) {
+ connection.transport.abort(connection, async);
+ connection.transport.stop(connection);
+ connection.transport = null;
+ }
- // Trigger the disconnect event
- $(connection).trigger(events.onDisconnect);
+ // Trigger the disconnect event
+ $(connection).trigger(events.onDisconnect);
+
+ delete connection.messageId;
+ delete connection.groups;
+ }
+ finally {
+ changeState(connection, signalR.connectionState.disconnected);
+ }
return connection;
},
@@ -566,6 +615,9 @@
if (onSuccess) {
onSuccess();
}
+ else {
+ changeState(connection, signalR.connectionState.connected);
+ }
};
connection.socket.onclose = function (event) {
@@ -585,6 +637,8 @@
log("Websocket closed");
}
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.stop(connection);
that.start(connection);
};
@@ -704,6 +758,8 @@
if (reconnecting) {
$connection.trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
}, false);
@@ -734,6 +790,9 @@
// to change the URL to not include the /connect suffix, and pass
// the last message id we received.
log("EventSource reconnecting due to the server connection ending");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
else {
@@ -808,6 +867,9 @@
frame.bind("readystatechange", function () {
if ($.inArray(this.readyState, ["loaded", "complete"]) >= 0) {
log("Forever frame iframe readyState changed to " + this.readyState + ", reconnecting");
+
+ changeState(connection, signalR.connectionState.reconnecting);
+
that.reconnect(connection);
}
});
@@ -885,6 +947,8 @@
else {
// If there's no onSuccess handler we assume this is a reconnect
$(connection).trigger(events.onReconnect);
+
+ changeState(connection, signalR.connectionState.connected);
}
}
},
@@ -913,10 +977,15 @@
var messageId = instance.messageId,
connect = (messageId === null),
- url = transportLogic.getUrl(instance, that.name, !connect, raiseReconnect),
+ reconnecting = !connect,
+ url = transportLogic.getUrl(instance, that.name, reconnecting, raiseReconnect),
reconnectTimeOut = null,
reconnectFired = false;
+ if (reconnecting === true && raiseReconnect === true) {
+ changeState(connection, signalR.connectionState.reconnecting);
+ }
+
log("Attempting to connect to '" + url + "' using longPolling.");
instance.pollXhr = $.ajax({
url: url,
@@ -936,6 +1005,9 @@
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
log("Raising the reconnect event");
+
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
@@ -990,6 +1062,8 @@
if (raiseReconnect === true) {
reconnectTimeOut = window.setTimeout(function () {
if (reconnectFired === false) {
+ changeState(connection, signalR.connectionState.connected);
+
$(instance).trigger(events.onReconnect);
reconnectFired = true;
}
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";function o(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}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 s=this,h={transport:"auto",jsonp:!1},a,c=n.Deferred(),l=document.createElement("a");return s.transport?(c.resolve(s),c.promise()):(n.type(u)==="function"?e=u:n.type(u)==="object"&&(n.extend(h,u),n.type(h.callback)==="function"&&(e=h.callback)),l.href=s.url,s.baseUrl=l.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:l.protocol+"//"+l.host,o(s.url)&&(i("Auto detected cross domain url."),h.transport==="auto"&&(h.jsonp||(h.jsonp=!n.support.cors,h.jsonp&&i("Using jsonp because this browser doesn't support cors")),h.transport=h.jsonp===!0?"longPolling":["webSockets","longPolling"])),s.ajaxDataType=h.jsonp?"jsonp":"json",n(s).bind(r.onStart,function(){n.type(e)==="function"&&e.call(s),c.resolve(s)}),a=function(i,u){if(u=u||0,u>=i.length){s.transport||c.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],o=n.type(e)==="object"?e:f.transports[e];o.start(s,function(){s.transport=o,n(s).trigger(r.onStart),n(t).unload(function(){s.stop(!1)})},function(){a(i,u+1)})},t.setTimeout(function(){var t=s.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:s.ajaxDataType,error:function(t){n(s).trigger(r.onError,[t.responseText]),c.reject("SignalR: Error during negotiation request: "+t)},success:function(t){if(s.appRelativeUrl=t.Url,s.id=t.ConnectionId,s.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(s).trigger(r.onError,"SignalR: Incompatible protocol version."),c.reject("SignalR: Incompatible protocol version.");return}n(s).trigger(r.onStarting);var u=[],i=[];n.each(f.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(h.transport)?n.each(h.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(h.transport)==="object"||n.inArray(h.transport,i)>=0?u.push(h.transport):u=i,a(u)}})},0),c.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:function(n){i(n,this.logging)}},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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),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(f,e,o){var h,l=!1,a=this,v=!e,c,s;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){o();return}f.socket||(f.webSocketServerUrl?h=f.webSocketServerUrl:(s=document.location,s.protocol!=="http:"&&s.protocol!=="https:"&&(s=t.document.createElement("a"),s.href=f.url),c=s.protocol==="https:"?"wss://":"ws://",h=c+s.host),n(f).trigger(r.onSending),h+=u.getUrl(f,this.name,v),i("Connecting to websocket endpoint '"+h+"'"),f.socket=new t.WebSocket(h),f.socket.onopen=function(){l=!0,i("Websocket opened"),e&&e()},f.socket.onclose=function(t){l?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(f).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):o&&o(),a.stop(f),a.start(f)},f.socket.onmessage=function(i){var e=t.JSON.parse(i.data),o;e&&(o=n(f),e.Messages?u.processMessages(f,e):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),o?o():(c.trigger(r.onError,[y]),h&&(i("EventSource reconnecting"),s.reconnect(f)));return}a=t.setTimeout(function(){l===!1&&(i("EventSource timed out trying to connect"),o&&o(),h?(i("EventSource reconnecting"),s.reconnect(f)):s.stop(f))},s.timeOut),f.eventSource.addEventListener("open",function(){i("EventSource connected"),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),n.eventPhase===t.EventSource.CLOSED?f.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),s.reconnect(f)):(i("EventSource closed"),s.stop(f)):(i("EventSource error"),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"),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. Status = "+e+". "+u.responseText),v&&clearTimeout(v),n(c).trigger(r.onError,[u.responseText]),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)
+(function(n,t){"use strict";function s(i){var r;return(i=n.trim(i),i.indexOf("http")!==0)?!1:(r=t.document.createElement("a"),r.href=i,r.protocol+r.host!==t.location.protocol+t.location.host)}var f;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.";var u,o,r={onStart:"onStart",onStarting:"onStarting",onSending:"onSending",onReceived:"onReceived",onError:"onError",onReconnect:"onReconnect",onStateChanged:"onStateChanged",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))}},e=function(t,i){i!==t.state&&(n(t).trigger(r.onStateChanged,[i]),t.state=i)};u=function(n,t,i){return new u.fn.init(n,t,i)},u.connectionState={connecting:0,connected:1,reconnecting:2,disconnecting:3,disconnected:4},u.fn=u.prototype={init:function(n,t,i){this.url=n,this.qs=t,typeof i=="boolean"&&(this.logging=i)},ajaxDataType:"json",logging:!1,state:u.connectionState.disconnected,reconnectDelay:2e3,start:function(f,o){var h=this,c={transport:"auto",jsonp:!1},v,l=n.Deferred(),a=document.createElement("a");return h.state===u.connectionState.connecting||h.state===u.connectionState.connected?(l.resolve(h),l.promise()):(e(h,u.connectionState.connecting),n.type(f)==="function"?o=f:n.type(f)==="object"&&(n.extend(c,f),n.type(c.callback)==="function"&&(o=c.callback)),a.href=h.url,h.baseUrl=a.protocol===":"?t.document.location.protocol+"//"+t.document.location.host:a.protocol+"//"+a.host,s(h.url)&&(i("Auto detected cross domain url."),c.transport==="auto"&&(c.jsonp||(c.jsonp=!n.support.cors,c.jsonp&&i("Using jsonp because this browser doesn't support cors")),c.transport=c.jsonp===!0?"longPolling":["webSockets","longPolling"])),h.ajaxDataType=c.jsonp?"jsonp":"json",n(h).bind(r.onStart,function(){n.type(o)==="function"&&o.call(h),l.resolve(h)}),v=function(i,f){if(f=f||0,f>=i.length){h.transport||l.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");return}var o=i[f],s=n.type(o)==="object"?o:u.transports[o];s.start(h,function(){h.transport=s,n(h).trigger(r.onStart),e(h,u.connectionState.connected),n(t).unload(function(){h.stop(!1)})},function(){v(i,f+1)})},t.setTimeout(function(){var t=h.url+"/negotiate";i("Negotiating with '"+t+"'."),n.ajax({url:t,global:!1,cache:!1,type:"GET",data:{},dataType:h.ajaxDataType,error:function(t){n(h).trigger(r.onError,[t.responseText]),l.reject("SignalR: Error during negotiation request: "+t),h.stop()},success:function(t){if(h.appRelativeUrl=t.Url,h.id=t.ConnectionId,h.webSocketServerUrl=t.WebSocketServerUrl,!t.ProtocolVersion||t.ProtocolVersion!=="1.0"){n(h).trigger(r.onError,"SignalR: Incompatible protocol version."),l.reject("SignalR: Incompatible protocol version.");return}n(h).trigger(r.onStarting);var f=[],i=[];n.each(u.transports,function(n){if(n==="webSockets"&&!t.TryWebSockets)return!0;i.push(n)}),n.isArray(c.transport)?n.each(c.transport,function(){var t=this;(n.type(t)==="object"||n.type(t)==="string"&&n.inArray(""+t,i)>=0)&&f.push(n.type(t)==="string"?""+t:t)}):n.type(c.transport)==="object"||n.inArray(c.transport,i)>=0?f.push(c.transport):f=i,v(f)}})},0),l.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},stateChanged:function(t){var i=this;return n(i).bind(r.onStateChanged,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;if(i.state!==u.connectionState.disconnecting&&i.state!==u.connectionState.disconnected){try{e(i,u.connectionState.disconnecting),i.transport&&(i.transport.abort(i,t),i.transport.stop(i),i.transport=null),n(i).trigger(r.onDisconnect),delete i.messageId,delete i.groups}finally{e(i,u.connectionState.disconnected)}return i}},log:function(n){i(n,this.logging)}},u.fn.init.prototype=u.fn,f={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 o=i==="webSockets"?"":n.baseUrl,f=o+n.appRelativeUrl,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.stop();return}u.Messages&&n.each(u.Messages,function(){try{f.trigger(r.onReceived,[this])}catch(u){i("Error raising received "+u),n(t).trigger(r.onError,[u])}}),u.MessageId&&(t.messageId=u.MessageId),u.TransportData&&(t.groups=u.TransportData.Groups)}},foreverFrame:{count:0,connections:{}}},u.transports={webSockets:{name:"webSockets",send:function(n,t){n.socket.send(t)},start:function(o,s,h){var l,v=!1,y=this,p=!s,a,c;if(t.MozWebSocket&&(t.WebSocket=t.MozWebSocket),!t.WebSocket){h();return}o.socket||(o.webSocketServerUrl?l=o.webSocketServerUrl:(c=document.location,c.protocol!=="http:"&&c.protocol!=="https:"&&(c=t.document.createElement("a"),c.href=o.url),a=c.protocol==="https:"?"wss://":"ws://",l=a+c.host),n(o).trigger(r.onSending),l+=f.getUrl(o,this.name,p),i("Connecting to websocket endpoint '"+l+"'"),o.socket=new t.WebSocket(l),o.socket.onopen=function(){v=!0,i("Websocket opened"),s?s():e(o,u.connectionState.connected)},o.socket.onclose=function(t){v?typeof t.wasClean!="undefined"&&t.wasClean===!1?(n(o).trigger(r.onError,[t.reason]),i("Unclean disconnect from websocket."+t.reason)):i("Websocket closed"):h&&h(),e(o,u.connectionState.reconnecting),y.stop(o),y.start(o)},o.socket.onmessage=function(i){var u=t.JSON.parse(i.data),e;u&&(e=n(o),u.Messages?f.processMessages(o,u):e.trigger(r.onReceived,[u]))})},stop:function(n){n.socket!==null&&(n.socket.close(),n.socket=null)},abort:function(){}},serverSentEvents:{name:"serverSentEvents",timeOut:3e3,start:function(o,s,h){var c=this,v=!1,a=n(o),l=!s,p,y;if(o.eventSource&&(i("The connection already has an event source. Stopping it."),o.stop()),!t.EventSource){h&&(i("This browser doesn't support SSE."),h());return}a.trigger(r.onSending),p=f.getUrl(o,this.name,l);try{i("Attempting to connect to SSE endpoint '"+p+"'"),o.eventSource=new t.EventSource(p)}catch(w){i("EventSource failed trying to connect with error "+w.Message),h?h():(a.trigger(r.onError,[w]),l&&(i("EventSource reconnecting"),c.reconnect(o)));return}y=t.setTimeout(function(){v===!1&&(i("EventSource timed out trying to connect"),h&&h(),l?(i("EventSource reconnecting"),c.reconnect(o)):c.stop(o))},c.timeOut),o.eventSource.addEventListener("open",function(){i("EventSource connected"),y&&t.clearTimeout(y),v===!1&&(v=!0,s&&s(),l&&(a.trigger(r.onReconnect),e(o,u.connectionState.connected)))},!1),o.eventSource.addEventListener("message",function(n){n.data!=="initialized"&&f.processMessages(o,t.JSON.parse(n.data))},!1),o.eventSource.addEventListener("error",function(n){if(!v){h&&h();return}i("EventSource readyState: "+o.eventSource.readyState),n.eventPhase===t.EventSource.CLOSED?o.eventSource.readyState===t.EventSource.CONNECTING?(i("EventSource reconnecting due to the server connection ending"),e(o,u.connectionState.reconnecting),c.reconnect(o)):(i("EventSource closed"),c.stop(o)):(i("EventSource error"),a.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){f.ajaxSend(n,t)},stop:function(n){n&&n.eventSource&&(n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){f.ajaxAbort(n,t)}},foreverFrame:{name:"foreverFrame",timeOut:3e3,start:function(o,s,h){var l=this,a=f.foreverFrame.count+=1,v,y,c=n("<iframe data-signalr-connection-id='"+o.id+"' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;'></iframe>");if(t.EventSource){h&&(i("This brower supports SSE, skipping Forever Frame."),h());return}n(o).trigger(r.onSending),v=f.getUrl(o,this.name),v+="&frameId="+a,c.prop("src",v),f.foreverFrame.connections[a]=o,i("Binding to iframe's readystatechange event."),c.bind("readystatechange",function(){n.inArray(this.readyState,["loaded","complete"])>=0&&(i("Forever frame iframe readyState changed to "+this.readyState+", reconnecting"),e(o,u.connectionState.reconnecting),l.reconnect(o))}),o.frame=c[0],o.frameId=a,s&&(o.onSuccess=s),n("body").append(c),y=t.setTimeout(function(){o.onSuccess&&(i("Failed to connect using forever frame source, it timed out after "+l.timeOut+"ms."),l.stop(o),h&&h())},l.timeOut)},reconnect:function(n){var r=this;t.setTimeout(function(){var u=n.frame,t=f.getUrl(n,r.name,!0)+"&frameId="+n.frameId;i("Upating iframe src to '"+t+"'."),u.src=t},n.reconnectDelay)},send:function(n,t){f.ajaxSend(n,t)},receive:f.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 f.foreverFrame.connections[t.frameId],t.frame=null,t.frameId=null,delete t.frame,delete t.frameId,i("Stopping forever frame"))},abort:function(n,t){f.ajaxAbort(n,t)},getConnection:function(n){return f.foreverFrame.connections[n]},started:function(t){t.onSuccess?(t.onSuccess(),t.onSuccess=null,delete t.onSuccess):(n(t).trigger(r.onReconnect),e(t,u.connectionState.connected))}},longPolling:{name:"longPolling",reconnectDelay:3e3,start:function(o,s){var l=this,c=!1;o.pollXhr&&(i("Polling xhr requests already exists, aborting."),o.stop()),o.messageId=null,t.setTimeout(function(){(function h(a,v){n(a).trigger(r.onSending);var d=a.messageId,k=d===null,b=!k,w=f.getUrl(a,l.name,b,v),p=null,y=!1;b===!0&&v===!0&&e(o,u.connectionState.reconnecting),i("Attempting to connect to '"+w+"' using longPolling."),a.pollXhr=n.ajax({url:w,global:!1,type:"GET",dataType:o.ajaxDataType,success:function(l){var w=0,p=!1;(c==!1&&(s(),c=!0),v===!0&&y===!1&&(i("Raising the reconnect event"),e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0),f.processMessages(a,l),l&&l.TransportData&&n.type(l.TransportData.LongPollDelay)==="number"&&(w=l.TransportData.LongPollDelay),l&&l.TimedOut&&(p=l.TimedOut),l&&l.Disconnect)||(w>0?t.setTimeout(function(){h(a,p)},w):h(a,p))},error:function(u,f){if(f==="abort"){i("Aborted xhr requst.");return}i("An error occurred using longPolling. Status = "+f+". "+u.responseText),p&&clearTimeout(p),n(a).trigger(r.onError,[u.responseText]),t.setTimeout(function(){h(a,!0)},o.reconnectDelay)}}),v===!0&&(p=t.setTimeout(function(){y===!1&&(e(o,u.connectionState.connected),n(a).trigger(r.onReconnect),y=!0)},l.reconnectDelay))})(o),t.setTimeout(function(){c===!1&&(s(),c=!0)},150)},250)},send:function(n,t){f.ajaxSend(n,t)},stop:function(n){n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){f.ajaxAbort(n,t)}}},u.noConflict=function(){return n.connection===u&&(n.connection=o),u},n.connection&&(o=n.connection),n.connection=n.signalR=u})(window.jQuery,window)

0 comments on commit bbf6e4b

Please sign in to comment.