diff --git a/src/ServicePulse.Host/app/index.html b/src/ServicePulse.Host/app/index.html
index 7556ce85f8..ed33e0691f 100644
--- a/src/ServicePulse.Host/app/index.html
+++ b/src/ServicePulse.Host/app/index.html
@@ -58,7 +58,7 @@
Warning!
-
+
diff --git a/src/ServicePulse.Host/app/js/services/factory.shareddata.js b/src/ServicePulse.Host/app/js/services/factory.shareddata.js
index b1e6ac034b..bbc62ac3ce 100644
--- a/src/ServicePulse.Host/app/js/services/factory.shareddata.js
+++ b/src/ServicePulse.Host/app/js/services/factory.shareddata.js
@@ -32,7 +32,7 @@
var environment = {
sc_version: undefined,
- minimum_supported_sc_version: "1.13.0",
+ minimum_supported_sc_version: "1.16.0",
is_compatible_with_sc: true,
sp_version: spVersion
};
@@ -145,4 +145,4 @@
angular.module("sc")
.service("sharedDataService", factory);
-}(window, window.angular));
\ No newline at end of file
+}(window, window.angular));
diff --git a/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-1.2.2.min.js b/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-1.2.2.min.js
deleted file mode 100644
index fc27181b7c..0000000000
--- a/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-1.2.2.min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*!
- * ASP.NET SignalR JavaScript Library v1.2.2
- * http://signalr.net/
- *
- * Copyright Microsoft Open Technologies, Inc. All rights reserved.
- * Licensed under the Apache 2.0
- * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
- *
- */
-(function(n,t,i){"use strict";function c(t,i){var u,f;if(n.isArray(t)){for(u=t.length-1;u>=0;u--)f=t[u],n.type(t)==="object"||n.type(f)==="string"&&r.transports[f]||(i.log("Invalid transport: "+f+", removing it from the transports list."),t.splice(u,1));t.length===0&&(i.log("No transports remain within the specified transport array."),t=null)}else if(n.type(t)==="object"||r.transports[t]||t==="auto"){if(t==="auto"&&r._.ieVersion<=8)return["longPolling"]}else i.log("Invalid transport: "+t.toString()+"."),t=null;return t}function l(n){return n==="http:"?80:n==="https:"?443:void 0}function h(n,t){return t.match(/:\d+$/)?t:t+":"+l(n)}if(typeof n!="function")throw new Error("SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.");if(!t.JSON)throw new Error("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 r,s,o=t.document.readyState==="complete",f=n(t),u={onStart:"onStart",onStarting:"onStarting",onReceived:"onReceived",onError:"onError",onConnectionSlow:"onConnectionSlow",onReconnecting:"onReconnecting",onReconnect:"onReconnect",onStateChanged:"onStateChanged",onDisconnect:"onDisconnect"},a={processData:!0,timeout:null,async:!0,global:!1,cache:!1},v=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,r){return i===t.state?(t.state=r,n(t).triggerHandler(u.onStateChanged,[{oldState:i,newState:r}]),!0):!1},y=function(n){return n.state===r.connectionState.disconnected},p=function(n){var u,i;n._.configuredStopReconnectingTimeout||(i=function(n){n.log("Couldn't reconnect within the configured timeout ("+n.disconnectTimeout+"ms), disconnecting."),n.stop(!1,!1)},n.reconnecting(function(){var n=this;n.state===r.connectionState.reconnecting&&(u=t.setTimeout(function(){i(n)},n.disconnectTimeout))}),n.stateChanged(function(n){n.oldState===r.connectionState.reconnecting&&t.clearTimeout(u)}),n._.configuredStopReconnectingTimeout=!0)};r=function(n,t,i){return new r.fn.init(n,t,i)},r._={defaultContentType:"application/x-www-form-urlencoded; charset=UTF-8",ieVersion:function(){var i,n;return t.navigator.appName==="Microsoft Internet Explorer"&&(n=/MSIE ([0-9]+\.[0-9]+)/.exec(t.navigator.userAgent),n&&(i=t.parseFloat(n[1]))),i}(),firefoxMajorVersion:function(n){var t=n.match(/Firefox\/(\d+)/);return!t||!t.length||t.length<2?0:parseInt(t[1],10)},configurePingInterval:function(i){var f=i._,e=function(t){n(i).triggerHandler(u.onError,[t])};!f.pingIntervalId&&f.pingInterval&&(f.pingIntervalId=t.setInterval(function(){r.transports._logic.pingServer(i).fail(e)},f.pingInterval))}},r.events=u,r.ajaxDefaults=a,r.changeState=e,r.isDisconnecting=y,r.connectionState={connecting:0,connected:1,reconnecting:2,disconnected:4},r.hub={start:function(){throw new Error("SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g. .");
+ throw new Error("SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g. .");
}
};
@@ -202,7 +259,7 @@
// Go through transport array and remove an "invalid" tranports
for (var i = requestedTransport.length - 1; i >= 0; i--) {
var transport = requestedTransport[i];
- if ($.type(requestedTransport) !== "object" && ($.type(transport) !== "string" || !signalR.transports[transport])) {
+ if ($.type(transport) !== "string" || !signalR.transports[transport]) {
connection.log("Invalid transport: " + transport + ", removing it from the transports list.");
requestedTransport.splice(i, 1);
}
@@ -213,11 +270,10 @@
connection.log("No transports remain within the specified transport array.");
requestedTransport = null;
}
- } else if ($.type(requestedTransport) !== "object" && !signalR.transports[requestedTransport] && requestedTransport !== "auto") {
+ } else if (!signalR.transports[requestedTransport] && requestedTransport !== "auto") {
connection.log("Invalid transport: " + requestedTransport.toString() + ".");
requestedTransport = null;
- }
- else if (requestedTransport === "auto" && signalR._.ieVersion <= 8) {
+ } else if (requestedTransport === "auto" && signalR._.ieVersion <= 8) {
// If we're doing an auto transport and we're IE8 then force longPolling, #1764
return ["longPolling"];
@@ -229,8 +285,7 @@
function getDefaultPort(protocol) {
if (protocol === "http:") {
return 80;
- }
- else if (protocol === "https:") {
+ } else if (protocol === "https:") {
return 443;
}
}
@@ -245,24 +300,51 @@
}
}
+ function ConnectingMessageBuffer(connection, drainCallback) {
+ var that = this,
+ buffer = [];
+
+ that.tryBuffer = function (message) {
+ if (connection.state === $.signalR.connectionState.connecting) {
+ buffer.push(message);
+
+ return true;
+ }
+
+ return false;
+ };
+
+ that.drain = function () {
+ // Ensure that the connection is connected when we drain (do not want to drain while a connection is not active)
+ if (connection.state === $.signalR.connectionState.connected) {
+ while (buffer.length > 0) {
+ drainCallback(buffer.shift());
+ }
+ }
+ };
+
+ that.clear = function () {
+ buffer = [];
+ };
+ }
+
signalR.fn = signalR.prototype = {
init: function (url, qs, logging) {
+ var $connection = $(this);
+
this.url = url;
this.qs = qs;
+ this.lastError = null;
this._ = {
keepAliveData: {},
- negotiateAbortText: "__Negotiate Aborted__",
- pingAbortText: "__Ping Aborted__",
- pingIntervalId: null,
- pingInterval: 300000,
- pollTimeoutId: null,
- reconnectTimeoutId: null,
+ connectingMessageBuffer: new ConnectingMessageBuffer(this, function (message) {
+ $connection.triggerHandler(events.onReceived, [message]);
+ }),
lastMessageAt: new Date().getTime(),
lastActiveAt: new Date().getTime(),
- beatInterval: 5000, // Default value, will only be overridden if keep alive is enabled
+ beatInterval: 5000, // Default value, will only be overridden if keep alive is enabled,
beatHandle: null,
- activePings: {},
- nextPingId: 0
+ totalTransportConnectTimeout: 0 // This will be the sum of the TransportConnectTimeout sent in response to negotiate and connection.transportConnectTimeout
};
if (typeof (logging) === "boolean") {
this.logging = logging;
@@ -270,17 +352,19 @@
},
_parseResponse: function (response) {
- var self = this;
+ var that = this;
if (!response) {
return response;
- } else if (self.ajaxDataType === "text") {
- return self.json.parse(response);
+ } else if (typeof response === "string") {
+ return that.json.parse(response);
} else {
return response;
}
},
+ _originalJson: window.JSON,
+
json: window.JSON,
isCrossDomain: function (url, against) {
@@ -316,11 +400,15 @@
state: signalR.connectionState.disconnected,
+ clientProtocol: "1.5",
+
reconnectDelay: 2000,
+ transportConnectTimeout: 0,
+
disconnectTimeout: 30000, // This should be set by the server in response to the negotiate request (30s default)
- reconnectWindow: 30000, // This should be set by the server in response to the negotiate request
+ reconnectWindow: 30000, // This should be set by the server in response to the negotiate request
keepAliveWarnAt: 2 / 3, // Warn user of slow connection if we breach the X% mark of the keep alive timeout
@@ -339,9 +427,16 @@
deferred = connection._deferral || $.Deferred(), // Check to see if there is a pre-existing deferral that's being built on, if so we want to keep using it
parser = window.document.createElement("a");
+ connection.lastError = null;
+
// Persist the deferral so that if start is called multiple times the same deferral is used.
connection._deferral = deferred;
+ if (!connection.json) {
+ // no JSON!
+ throw new Error("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.");
+ }
+
if ($.type(options) === "function") {
// Support calling with single callback parameter
callback = options;
@@ -360,7 +455,6 @@
}
connection._.config = config;
- connection._.pingInterval = config.pingInterval;
// Check to see if start is being called prior to page load
// If waitForPageLoad is true we then want to re-direct function call to the window load event
@@ -376,8 +470,7 @@
// If we're already connecting just return the same deferral as the original connection start
if (connection.state === signalR.connectionState.connecting) {
return deferred.promise();
- }
- else if (changeState(connection,
+ } else if (changeState(connection,
signalR.connectionState.disconnected,
signalR.connectionState.connecting) === false) {
// We're not connecting so try and transition into connecting.
@@ -393,15 +486,14 @@
parser.href = connection.url;
if (!parser.protocol || parser.protocol === ":") {
connection.protocol = window.document.location.protocol;
- connection.host = window.document.location.host;
- connection.baseUrl = connection.protocol + "//" + connection.host;
- }
- else {
+ connection.host = parser.host || window.document.location.host;
+ } else {
connection.protocol = parser.protocol;
connection.host = parser.host;
- connection.baseUrl = parser.protocol + "//" + parser.host;
}
+ connection.baseUrl = connection.protocol + "//" + connection.host;
+
// Set the websocket protocol
connection.wsProtocol = connection.protocol === "https:" ? "wss://" : "ws://";
@@ -423,12 +515,11 @@
connection.log("Auto detected cross domain url.");
if (config.transport === "auto") {
- // Try webSockets and longPolling since SSE doesn't support CORS
// TODO: Support XDM with foreverFrame
- config.transport = ["webSockets", "longPolling"];
+ config.transport = ["webSockets", "serverSentEvents", "longPolling"];
}
- if (config.withCredentials === undefined) {
+ if (typeof (config.withCredentials) === "undefined") {
config.withCredentials = true;
}
@@ -447,8 +538,8 @@
}
connection.withCredentials = config.withCredentials;
- connection.ajaxDataType = config.jsonp ? "jsonp" : "text";
+ connection.ajaxDataType = config.jsonp ? "jsonp" : "text";
$(connection).bind(events.onStart, function (e, data) {
if ($.type(callback) === "function") {
@@ -457,12 +548,24 @@
deferred.resolve(connection);
});
+ connection._.initHandler = signalR.transports._logic.initHandler(connection);
+
initialize = function (transports, index) {
+ var noTransportError = signalR._.error(resources.noTransportOnInit);
+
index = index || 0;
if (index >= transports.length) {
+ if (index === 0) {
+ connection.log("No transports supported by the server were selected.");
+ } else if (index === 1) {
+ connection.log("No fallback transports were selected.");
+ } else {
+ connection.log("Fallback transports exhausted.");
+ }
+
// No transport initialized successfully
- $(connection).triggerHandler(events.onError, ["SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization."]);
- deferred.reject("SignalR: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.");
+ $(connection).triggerHandler(events.onError, [noTransportError]);
+ deferred.reject(noTransportError);
// Stop the connection if it has connected and move it into the disconnected state
connection.stop();
return;
@@ -474,171 +577,175 @@
}
var transportName = transports[index],
- transport = $.type(transportName) === "object" ? transportName : signalR.transports[transportName];
+ transport = signalR.transports[transportName],
+ onFallback = function () {
+ initialize(transports, index + 1);
+ };
connection.transport = transport;
- if (transportName.indexOf("_") === 0) {
- // Private member
- initialize(transports, index + 1);
- return;
- }
-
- transport.start(connection, function () { // success
- // Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials
- var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11,
- asyncAbort = !!connection.withCredentials && isFirefox11OrGreater;
+ try {
+ connection._.initHandler.start(transport, function () { // success
+ // Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials
+ var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11,
+ asyncAbort = !!connection.withCredentials && isFirefox11OrGreater;
- // The connection was aborted while initializing transports
- if (connection.state === signalR.connectionState.disconnected) {
- return;
- }
+ connection.log("The start request succeeded. Transitioning to the connected state.");
- if (transport.supportsKeepAlive && connection._.keepAliveData.activated) {
- signalR.transports._logic.monitorKeepAlive(connection);
- }
+ if (supportsKeepAlive(connection)) {
+ signalR.transports._logic.monitorKeepAlive(connection);
+ }
- signalR.transports._logic.startHeartbeat(connection);
+ signalR.transports._logic.startHeartbeat(connection);
- // Used to ensure low activity clients maintain their authentication.
- // Must be configured once a transport has been decided to perform valid ping requests.
- signalR._.configurePingInterval(connection);
+ // Used to ensure low activity clients maintain their authentication.
+ // Must be configured once a transport has been decided to perform valid ping requests.
+ signalR._.configurePingInterval(connection);
- changeState(connection,
- signalR.connectionState.connecting,
- signalR.connectionState.connected);
+ if (!changeState(connection,
+ signalR.connectionState.connecting,
+ signalR.connectionState.connected)) {
+ connection.log("WARNING! The connection was not in the connecting state.");
+ }
- $(connection).triggerHandler(events.onStart);
+ // Drain any incoming buffered messages (messages that came in prior to connect)
+ connection._.connectingMessageBuffer.drain();
- // wire the stop handler for when the user leaves the page
- _pageWindow.bind("unload", function () {
- connection.log("Window unloading, stopping the connection.");
+ $(connection).triggerHandler(events.onStart);
- connection.stop(asyncAbort);
- });
+ // wire the stop handler for when the user leaves the page
+ _pageWindow.bind("unload", function () {
+ connection.log("Window unloading, stopping the connection.");
- if (signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11) {
- _pageWindow.bind("beforeunload", function () {
- // If connection.stop() runs in beforeunload and fails, it will also fail
- // in unload unless connection.stop() runs after a timeout.
- window.setTimeout(function () {
- connection.stop(asyncAbort);
- }, 0);
+ connection.stop(asyncAbort);
});
- }
- }, function () {
- initialize(transports, index + 1);
- });
+
+ if (isFirefox11OrGreater) {
+ // Firefox does not fire cross-domain XHRs in the normal unload handler on tab close.
+ // #2400
+ _pageWindow.bind("beforeunload", function () {
+ // If connection.stop() runs runs in beforeunload and fails, it will also fail
+ // in unload unless connection.stop() runs after a timeout.
+ window.setTimeout(function () {
+ connection.stop(asyncAbort);
+ }, 0);
+ });
+ }
+ }, onFallback);
+ }
+ catch (error) {
+ connection.log(transport.name + " transport threw '" + error.message + "' when attempting to start.");
+ onFallback();
+ }
};
- $(connection).triggerHandler(events.onStarting);
-
var url = connection.url + "/negotiate",
onFailed = function (error, connection) {
- $(connection).triggerHandler(events.onError, [error.responseText]);
- deferred.reject("SignalR: Error during negotiation request: " + error.responseText);
+ var err = signalR._.error(resources.errorOnNegotiate, error, connection._.negotiateRequest);
+
+ $(connection).triggerHandler(events.onError, err);
+ deferred.reject(err);
// Stop the connection if negotiate failed
connection.stop();
};
+ $(connection).triggerHandler(events.onStarting);
+
url = signalR.transports._logic.prepareQueryString(connection, url);
connection.log("Negotiating with '" + url + "'.");
// Save the ajax negotiate request object so we can abort it if stop is called while the request is in flight.
- connection._.negotiateRequest = $.ajax(
- $.extend({}, $.signalR.ajaxDefaults, {
- xhrFields: { withCredentials: connection.withCredentials },
- url: url,
- type: "GET",
- contentType: connection.contentType,
- data: {},
- dataType: connection.ajaxDataType,
- error: function (error, statusText) {
- // We don't want to cause any errors if we're aborting our own negotiate request.
- if (statusText !== connection._.negotiateAbortText) {
- onFailed(error, connection);
- } else {
- // This rejection will noop if the deferred has already been resolved or rejected.
- deferred.reject("Stopped the connection while negotiating.");
- }
- },
- success: function (result) {
- var res,
- keepAliveData = connection._.keepAliveData;
+ connection._.negotiateRequest = signalR.transports._logic.ajax(connection, {
+ url: url,
+ error: function (error, statusText) {
+ // We don't want to cause any errors if we're aborting our own negotiate request.
+ if (statusText !== _negotiateAbortText) {
+ onFailed(error, connection);
+ } else {
+ // This rejection will noop if the deferred has already been resolved or rejected.
+ deferred.reject(signalR._.error(resources.stoppedWhileNegotiating, null /* error */, connection._.negotiateRequest));
+ }
+ },
+ success: function (result) {
+ var res,
+ keepAliveData,
+ protocolError,
+ transports = [],
+ supportedTransports = [];
- try {
- res = connection._parseResponse(result);
- }
- catch (error) {
- onFailed(error, connection);
- return;
- }
+ try {
+ res = connection._parseResponse(result);
+ } catch (error) {
+ onFailed(signalR._.error(resources.errorParsingNegotiateResponse, error), connection);
+ return;
+ }
- keepAliveData = connection._.keepAliveData;
- connection.appRelativeUrl = res.Url;
- connection.id = res.ConnectionId;
- connection.token = res.ConnectionToken;
- connection.webSocketServerUrl = res.WebSocketServerUrl;
+ keepAliveData = connection._.keepAliveData;
+ connection.appRelativeUrl = res.Url;
+ connection.id = res.ConnectionId;
+ connection.token = res.ConnectionToken;
+ connection.webSocketServerUrl = res.WebSocketServerUrl;
- // Once the server has labeled the PersistentConnection as Disconnected, we should stop attempting to reconnect
- // after res.DisconnectTimeout seconds.
- connection.disconnectTimeout = res.DisconnectTimeout * 1000; // in ms
+ // The long poll timeout is the ConnectionTimeout plus 10 seconds
+ connection._.pollTimeout = res.ConnectionTimeout * 1000 + 10000; // in ms
+ // Once the server has labeled the PersistentConnection as Disconnected, we should stop attempting to reconnect
+ // after res.DisconnectTimeout seconds.
+ connection.disconnectTimeout = res.DisconnectTimeout * 1000; // in ms
- // If we have a keep alive
- if (res.KeepAliveTimeout) {
- // Register the keep alive data as activated
- keepAliveData.activated = true;
+ // Add the TransportConnectTimeout from the response to the transportConnectTimeout from the client to calculate the total timeout
+ connection._.totalTransportConnectTimeout = connection.transportConnectTimeout + res.TransportConnectTimeout * 1000;
- // Timeout to designate when to force the connection into reconnecting converted to milliseconds
- keepAliveData.timeout = res.KeepAliveTimeout * 1000;
+ // If we have a keep alive
+ if (res.KeepAliveTimeout) {
+ // Register the keep alive data as activated
+ keepAliveData.activated = true;
- // Timeout to designate when to warn the developer that the connection may be dead or is not responding.
- keepAliveData.timeoutWarning = keepAliveData.timeout * connection.keepAliveWarnAt;
+ // Timeout to designate when to force the connection into reconnecting converted to milliseconds
+ keepAliveData.timeout = res.KeepAliveTimeout * 1000;
- // Instantiate the frequency in which we check the keep alive. It must be short in order to not miss/pick up any changes
- connection._.beatInterval = (keepAliveData.timeout - keepAliveData.timeoutWarning) / 3;
- }
- else {
- keepAliveData.activated = false;
- }
+ // Timeout to designate when to warn the developer that the connection may be dead or is not responding.
+ keepAliveData.timeoutWarning = keepAliveData.timeout * connection.keepAliveWarnAt;
- if (!res.ProtocolVersion || res.ProtocolVersion !== "1.2") {
- $(connection).triggerHandler(events.onError, ["You are using a version of the client that isn't compatible with the server. Client version 1.2, server version " + res.ProtocolVersion + "."]);
- deferred.reject("You are using a version of the client that isn't compatible with the server. Client version 1.2, server version " + res.ProtocolVersion + ".");
- return;
- }
+ // Instantiate the frequency in which we check the keep alive. It must be short in order to not miss/pick up any changes
+ connection._.beatInterval = (keepAliveData.timeout - keepAliveData.timeoutWarning) / 3;
+ } else {
+ keepAliveData.activated = false;
+ }
- var transports = [],
- supportedTransports = [];
+ connection.reconnectWindow = connection.disconnectTimeout + (keepAliveData.timeout || 0);
- $.each(signalR.transports, function (key) {
- if (key === "webSockets" && !res.TryWebSockets) {
- // Server said don't even try WebSockets, but keep processing the loop
- return true;
- }
- supportedTransports.push(key);
- });
+ if (!res.ProtocolVersion || res.ProtocolVersion !== connection.clientProtocol) {
+ protocolError = signalR._.error(signalR._.format(resources.protocolIncompatible, connection.clientProtocol, res.ProtocolVersion));
+ $(connection).triggerHandler(events.onError, [protocolError]);
+ deferred.reject(protocolError);
- if ($.isArray(config.transport)) {
- // ordered list provided
- $.each(config.transport, function () {
- var transport = this;
- if ($.type(transport) === "object" || ($.type(transport) === "string" && $.inArray("" + transport, supportedTransports) >= 0)) {
- transports.push($.type(transport) === "string" ? "" + transport : transport);
- }
- });
- } else if ($.type(config.transport) === "object" ||
- $.inArray(config.transport, supportedTransports) >= 0) {
- // specific transport provided, as object or a named transport, e.g. "longPolling"
- transports.push(config.transport);
- } else { // default "auto"
- transports = supportedTransports;
+ return;
+ }
+
+ $.each(signalR.transports, function (key) {
+ if ((key.indexOf("_") === 0) || (key === "webSockets" && !res.TryWebSockets)) {
+ return true;
}
- initialize(transports);
+ supportedTransports.push(key);
+ });
+
+ if ($.isArray(config.transport)) {
+ $.each(config.transport, function (_, transport) {
+ if ($.inArray(transport, supportedTransports) >= 0) {
+ transports.push(transport);
+ }
+ });
+ } else if (config.transport === "auto") {
+ transports = supportedTransports;
+ } else if ($.inArray(config.transport, supportedTransports) >= 0) {
+ transports.push(config.transport);
}
- }));
+
+ initialize(transports);
+ }
+ });
return deferred.promise();
},
@@ -703,8 +810,10 @@
///
var connection = this;
$(connection).bind(events.onError, function (e, errorData, sendData) {
+ connection.lastError = errorData;
// In practice 'errorData' is the SignalR built error object.
- // In practice 'sendData' is undefined for all error events except those triggered by ajaxSend. For ajaxSend 'sendData' is the original send payload.
+ // In practice 'sendData' is undefined for all error events except those triggered by
+ // 'ajaxSend' and 'webSockets.send'.'sendData' is the original send payload.
callback.call(connection, errorData, sendData);
});
return connection;
@@ -762,8 +871,7 @@
///
var connection = this,
// Save deferral because this is always cleaned up
- deferral = connection._deferral,
- config = connection._.config;
+ deferral = connection._deferral;
// Verify that we've bound a load event.
if (connection._.deferredStartHandler) {
@@ -772,18 +880,17 @@
}
// Always clean up private non-timeout based state.
- delete connection._deferral;
delete connection._.config;
delete connection._.deferredStartHandler;
// This needs to be checked despite the connection state because a connection start can be deferred until page load.
// If we've deferred the start due to a page load we need to unbind the "onLoad" -> start event.
- if (!_pageLoaded && (!config || config.waitForPageLoad === true)) {
+ if (!_pageLoaded && (!connection._.config || connection._.config.waitForPageLoad === true)) {
connection.log("Stopping connection prior to negotiate.");
// If we have a deferral we should reject it
if (deferral) {
- deferral.reject("The connection was stopped during page load.");
+ deferral.reject(signalR._.error(resources.stoppedWhileLoading));
}
// Short-circuit because the start has not been fully started.
@@ -798,44 +905,48 @@
changeState(connection, connection.state, signalR.connectionState.disconnected);
+ // Clear this no matter what
window.clearTimeout(connection._.beatHandle);
window.clearInterval(connection._.pingIntervalId);
- window.clearTimeout(connection._.pingLoopId);
if (connection.transport) {
+ connection.transport.stop(connection);
+
if (notifyServer !== false) {
connection.transport.abort(connection, async);
}
- if (connection.transport.supportsKeepAlive && connection._.keepAliveData.activated) {
+ if (supportsKeepAlive(connection)) {
signalR.transports._logic.stopMonitoringKeepAlive(connection);
}
- connection.transport.stop(connection);
connection.transport = null;
}
if (connection._.negotiateRequest) {
// If the negotiation request has already completed this will noop.
- connection._.negotiateRequest.abort(connection._.negotiateAbortText);
+ connection._.negotiateRequest.abort(_negotiateAbortText);
delete connection._.negotiateRequest;
}
- $.each(connection._.activePings, function (i, pingXhr) {
- connection.log("Aborting ping " + i + ".");
- pingXhr.abort(connection._.pingAbortText);
- });
+ // Ensure that initHandler.stop() is called before connection._deferral is deleted
+ if (connection._.initHandler) {
+ connection._.initHandler.stop();
+ }
// Trigger the disconnect event
$(connection).triggerHandler(events.onDisconnect);
+ delete connection._deferral;
delete connection.messageId;
delete connection.groupsToken;
delete connection.id;
delete connection._.pingIntervalId;
delete connection._.lastMessageAt;
delete connection._.lastActiveAt;
- delete connection._.pingLoopId;
+
+ // Clear out our message buffer
+ connection._.connectingMessageBuffer.clear();
return connection;
},
@@ -870,11 +981,11 @@
///
(function ($, window, undefined) {
- "use strict";
var signalR = $.signalR,
events = $.signalR.events,
changeState = $.signalR.changeState,
+ startAbortText = "__Start Aborted__",
transportLogic;
signalR.transports = {};
@@ -906,65 +1017,181 @@
// Notify transport that the connection has been lost
connection.transport.lostConnection(connection);
- }
- else if (timeElapsed >= keepAliveData.timeoutWarning) {
+ } else if (timeElapsed >= keepAliveData.timeoutWarning) {
// This is to assure that the user only gets a single warning
if (!keepAliveData.userNotified) {
connection.log("Keep alive has been missed, connection may be dead/slow.");
$(connection).triggerHandler(events.onConnectionSlow);
keepAliveData.userNotified = true;
}
- }
- else {
+ } else {
keepAliveData.userNotified = false;
}
}
}
- function isConnectedOrReconnecting(connection) {
- return connection.state === signalR.connectionState.connected ||
- connection.state === signalR.connectionState.reconnecting;
- }
-
- function addConnectionData(url, connectionData) {
- var appender = url.indexOf("?") !== -1 ? "&" : "?";
+ function getAjaxUrl(connection, path) {
+ var url = connection.url + path;
- if (connectionData) {
- url += appender + "connectionData=" + window.encodeURIComponent(connectionData);
+ if (connection.transport) {
+ url += "?transport=" + connection.transport.name;
}
- return url;
+ return transportLogic.prepareQueryString(connection, url);
}
+ function InitHandler(connection) {
+ this.connection = connection;
+
+ this.startRequested = false;
+ this.startCompleted = false;
+ this.connectionStopped = false;
+ }
+
+ InitHandler.prototype = {
+ start: function (transport, onSuccess, onFallback) {
+ var that = this,
+ connection = that.connection,
+ failCalled = false;
+
+ if (that.startRequested || that.connectionStopped) {
+ connection.log("WARNING! " + transport.name + " transport cannot be started. Initialization ongoing or completed.");
+ return;
+ }
+
+ connection.log(transport.name + " transport starting.");
+
+ that.transportTimeoutHandle = window.setTimeout(function () {
+ if (!failCalled) {
+ failCalled = true;
+ connection.log(transport.name + " transport timed out when trying to connect.");
+ that.transportFailed(transport, undefined, onFallback);
+ }
+ }, connection._.totalTransportConnectTimeout);
+
+ transport.start(connection, function () {
+ if (!failCalled) {
+ that.initReceived(transport, onSuccess);
+ }
+ }, function (error) {
+ // Don't allow the same transport to cause onFallback to be called twice
+ if (!failCalled) {
+ failCalled = true;
+ that.transportFailed(transport, error, onFallback);
+ }
+
+ // Returns true if the transport should stop;
+ // false if it should attempt to reconnect
+ return !that.startCompleted || that.connectionStopped;
+ });
+ },
+
+ stop: function () {
+ this.connectionStopped = true;
+ window.clearTimeout(this.transportTimeoutHandle);
+ signalR.transports._logic.tryAbortStartRequest(this.connection);
+ },
+
+ initReceived: function (transport, onSuccess) {
+ var that = this,
+ connection = that.connection;
+
+ if (that.startRequested) {
+ connection.log("WARNING! The client received multiple init messages.");
+ return;
+ }
+
+ if (that.connectionStopped) {
+ return;
+ }
+
+ that.startRequested = true;
+ window.clearTimeout(that.transportTimeoutHandle);
+
+ connection.log(transport.name + " transport connected. Initiating start request.");
+ signalR.transports._logic.ajaxStart(connection, function () {
+ that.startCompleted = true;
+ onSuccess();
+ });
+ },
+
+ transportFailed: function (transport, error, onFallback) {
+ var connection = this.connection,
+ deferred = connection._deferral,
+ wrappedError;
+
+ if (this.connectionStopped) {
+ return;
+ }
+
+ window.clearTimeout(this.transportTimeoutHandle);
+
+ if (!this.startRequested) {
+ transport.stop(connection);
+
+ connection.log(transport.name + " transport failed to connect. Attempting to fall back.");
+ onFallback();
+ } else if (!this.startCompleted) {
+ // Do not attempt to fall back if a start request is ongoing during a transport failure.
+ // Instead, trigger an error and stop the connection.
+ wrappedError = signalR._.error(signalR.resources.errorDuringStartRequest, error);
+
+ connection.log(transport.name + " transport failed during the start request. Stopping the connection.");
+ $(connection).triggerHandler(events.onError, [wrappedError]);
+ if (deferred) {
+ deferred.reject(wrappedError);
+ }
+
+ connection.stop();
+ } else {
+ // The start request has completed, but the connection has not stopped.
+ // No need to do anything here. The transport should attempt its normal reconnect logic.
+ }
+ }
+ };
+
transportLogic = signalR.transports._logic = {
+ ajax: function (connection, options) {
+ return $.ajax(
+ $.extend(/*deep copy*/ true, {}, $.signalR.ajaxDefaults, {
+ type: "GET",
+ data: {},
+ xhrFields: { withCredentials: connection.withCredentials },
+ contentType: connection.contentType,
+ dataType: connection.ajaxDataType
+ }, options));
+ },
+
pingServer: function (connection) {
/// Pings the server
/// Connection associated with the server ping
///
var url,
- deferral = $.Deferred(),
- activePings = connection._.activePings,
- pingId = connection._.nextPingId++;
+ xhr,
+ deferral = $.Deferred();
- url = connection.url + "/ping";
- url = transportLogic.prepareQueryString(connection, url);
+ if (connection.transport) {
+ url = connection.url + "/ping";
- activePings[pingId] = $.ajax(
- $.extend({}, $.signalR.ajaxDefaults, {
- xhrFields: { withCredentials: connection.withCredentials },
+ url = transportLogic.addQs(url, connection.qs);
+
+ xhr = transportLogic.ajax(connection, {
url: url,
- type: "GET",
- contentType: connection.contentType,
- data: {},
- dataType: connection.ajaxDataType,
success: function (result) {
var data;
-
+
try {
data = connection._parseResponse(result);
}
catch (error) {
- deferral.reject("Failed to parse ping server response, stopping the connection: " + result);
+ deferral.reject(
+ signalR._.transportError(
+ signalR.resources.pingServerFailedParse,
+ connection.transport,
+ error,
+ xhr
+ )
+ );
connection.stop();
return;
}
@@ -973,70 +1200,106 @@
deferral.resolve();
}
else {
- deferral.reject("SignalR: Invalid ping response when pinging server: " + data.Response);
+ deferral.reject(
+ signalR._.transportError(
+ signalR._.format(signalR.resources.pingServerFailedInvalidResponse, result),
+ connection.transport,
+ null /* error */,
+ xhr
+ )
+ );
}
},
- error: function (error, statusText) {
+ error: function (error) {
if (error.status === 401 || error.status === 403) {
- deferral.reject("Failed to ping server. Server responded with a " + error.status + " status code, stopping the connection.");
+ deferral.reject(
+ signalR._.transportError(
+ signalR._.format(signalR.resources.pingServerFailedStatusCode, error.status),
+ connection.transport,
+ error,
+ xhr
+ )
+ );
connection.stop();
}
- else if (statusText === connection._.pingAbortText)
- {
- // We don't want to cause any errors if we're aborting our own ping request.
- // Don't reject or resolve the deferred, because we want the long-polling pingLoop to stop.
- connection.log("Ping " + pingId + " aborted.");
- }
else {
- deferral.reject("SignalR: Error pinging server: " + (error.responseText || error.statusText));
+ deferral.reject(
+ signalR._.transportError(
+ signalR.resources.pingServerFailed,
+ connection.transport,
+ error,
+ xhr
+ )
+ );
}
- },
- complete: function () {
- delete activePings[pingId];
}
- }));
+ });
+ }
+ else {
+ deferral.reject(
+ signalR._.transportError(
+ signalR.resources.noConnectionTransport,
+ connection.transport
+ )
+ );
+ }
return deferral.promise();
},
prepareQueryString: function (connection, url) {
- url = transportLogic.addQs(url, connection);
+ var preparedUrl;
+
+ // Use addQs to start since it handles the ?/& prefix for us
+ preparedUrl = transportLogic.addQs(url, "clientProtocol=" + connection.clientProtocol);
+
+ // Add the user-specified query string params if any
+ preparedUrl = transportLogic.addQs(preparedUrl, connection.qs);
+
+ if (connection.token) {
+ preparedUrl += "&connectionToken=" + window.encodeURIComponent(connection.token);
+ }
+
+ if (connection.data) {
+ preparedUrl += "&connectionData=" + window.encodeURIComponent(connection.data);
+ }
- return addConnectionData(url, connection.data);
+ return preparedUrl;
},
- addQs: function (url, connection) {
+ addQs: function (url, qs) {
var appender = url.indexOf("?") !== -1 ? "&" : "?",
firstChar;
- if (!connection.qs) {
+ if (!qs) {
return url;
}
- if (typeof (connection.qs) === "object") {
- return url + appender + $.param(connection.qs);
+ if (typeof (qs) === "object") {
+ return url + appender + $.param(qs);
}
- if (typeof (connection.qs) === "string") {
- firstChar = connection.qs.charAt(0);
+ if (typeof (qs) === "string") {
+ firstChar = qs.charAt(0);
if (firstChar === "?" || firstChar === "&") {
appender = "";
}
- return url + appender + connection.qs;
+ return url + appender + qs;
}
- throw new Error("Connections query string property must be either a string or object.");
+ throw new Error("Query string property must be either a string or object.");
},
- getUrl: function (connection, transport, reconnecting, poll) {
+ // BUG #2953: The url needs to be same otherwise it will cause a memory leak
+ getUrl: function (connection, transport, reconnecting, poll, ajaxPost) {
/// Gets the url for making a GET based connect request
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
url = baseUrl + connection.appRelativeUrl,
- qs = "transport=" + transport + "&connectionToken=" + window.encodeURIComponent(connection.token);
+ qs = "transport=" + transport;
- if (connection.groupsToken) {
+ if (!ajaxPost && connection.groupsToken) {
qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
}
@@ -1050,13 +1313,17 @@
url += "/reconnect";
}
- if (connection.messageId) {
+ if (!ajaxPost && connection.messageId) {
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
}
}
url += "?" + qs;
url = transportLogic.prepareQueryString(connection, url);
- url += "&tid=" + Math.floor(Math.random() * 11);
+
+ if (!ajaxPost) {
+ url += "&tid=" + Math.floor(Math.random() * 11);
+ }
+
return url;
},
@@ -1064,7 +1331,7 @@
return {
MessageId: minPersistentResponse.C,
Messages: minPersistentResponse.M,
- Disconnect: typeof (minPersistentResponse.D) !== "undefined" ? true : false,
+ Initialized: typeof (minPersistentResponse.S) !== "undefined" ? true : false,
ShouldReconnect: typeof (minPersistentResponse.T) !== "undefined" ? true : false,
LongPollDelay: minPersistentResponse.L,
GroupsToken: minPersistentResponse.G
@@ -1077,51 +1344,58 @@
}
},
+ stringifySend: function (connection, message) {
+ if (typeof (message) === "string" || typeof (message) === "undefined" || message === null) {
+ return message;
+ }
+ return connection.json.stringify(message);
+ },
+
ajaxSend: function (connection, data) {
- var url = connection.url + "/send" + "?transport=" + connection.transport.name + "&connectionToken=" + window.encodeURIComponent(connection.token),
+ var payload = transportLogic.stringifySend(connection, data),
+ url = getAjaxUrl(connection, "/send"),
+ xhr,
onFail = function (error, connection) {
- $(connection).triggerHandler(events.onError, [error, data]);
+ $(connection).triggerHandler(events.onError, [signalR._.transportError(signalR.resources.sendFailed, connection.transport, error, xhr), data]);
};
- url = transportLogic.prepareQueryString(connection, url);
-
- return $.ajax(
- $.extend({}, $.signalR.ajaxDefaults, {
- xhrFields: { withCredentials: connection.withCredentials },
- url: url,
- type: connection.ajaxDataType === "jsonp" ? "GET" : "POST",
- contentType: signalR._.defaultContentType,
- dataType: connection.ajaxDataType,
- data: {
- data: data
- },
- success: function (result) {
- var res;
- if (result) {
- try {
- res = connection._parseResponse(result);
- }
- catch (error) {
- onFail(error, connection);
- connection.stop();
- return;
- }
+ xhr = transportLogic.ajax(connection, {
+ url: url,
+ type: connection.ajaxDataType === "jsonp" ? "GET" : "POST",
+ contentType: signalR._.defaultContentType,
+ data: {
+ data: payload
+ },
+ success: function (result) {
+ var res;
- $(connection).triggerHandler(events.onReceived, [res]);
+ if (result) {
+ try {
+ res = connection._parseResponse(result);
}
- },
- error: function (error, textStatus) {
- if (textStatus === "abort" || textStatus === "parsererror") {
- // The parsererror happens for sends that don't return any data, and hence
- // don't write the jsonp callback to the response. This is harder to fix on the server
- // so just hack around it on the client for now.
+ catch (error) {
+ onFail(error, connection);
+ connection.stop();
return;
}
- onFail(error, connection);
+ transportLogic.triggerReceived(connection, res);
}
- }));
+ },
+ error: function (error, textStatus) {
+ if (textStatus === "abort" || textStatus === "parsererror") {
+ // The parsererror happens for sends that don't return any data, and hence
+ // don't write the jsonp callback to the response. This is harder to fix on the server
+ // so just hack around it on the client for now.
+ return;
+ }
+
+ onFail(error, connection);
+ }
+ });
+
+ return xhr;
},
ajaxAbort: function (connection, async) {
@@ -1132,57 +1406,112 @@
// Async by default unless explicitly overidden
async = typeof async === "undefined" ? true : async;
- var url = connection.url + "/abort" + "?transport=" + connection.transport.name + "&connectionToken=" + window.encodeURIComponent(connection.token);
- url = transportLogic.prepareQueryString(connection, url);
+ var url = getAjaxUrl(connection, "/abort");
- $.ajax(
- $.extend({}, $.signalR.ajaxDefaults, {
- xhrFields: { withCredentials: connection.withCredentials },
- url: url,
- async: async,
- timeout: 1000,
- type: "POST",
- contentType: connection.contentType,
- dataType: connection.ajaxDataType,
- data: {}
- }));
+ transportLogic.ajax(connection, {
+ url: url,
+ async: async,
+ timeout: 1000,
+ type: "POST"
+ });
connection.log("Fired ajax abort async = " + async + ".");
},
- processMessages: function (connection, minData) {
- var data;
- // Transport can be null if we've just closed the connection
- if (connection.transport) {
- var $connection = $(connection);
+ ajaxStart: function (connection, onSuccess) {
+ var rejectDeferred = function (error) {
+ var deferred = connection._deferral;
+ if (deferred) {
+ deferred.reject(error);
+ }
+ },
+ triggerStartError = function (error) {
+ connection.log("The start request failed. Stopping the connection.");
+ $(connection).triggerHandler(events.onError, [error]);
+ rejectDeferred(error);
+ connection.stop();
+ };
- // Update the last message time stamp
- transportLogic.markLastMessage(connection);
+ connection._.startRequest = transportLogic.ajax(connection, {
+ url: getAjaxUrl(connection, "/start"),
+ success: function (result, statusText, xhr) {
+ var data;
- if (!minData) {
- return;
+ try {
+ data = connection._parseResponse(result);
+ } catch (error) {
+ triggerStartError(signalR._.error(
+ signalR._.format(signalR.resources.errorParsingStartResponse, result),
+ error, xhr));
+ return;
+ }
+
+ if (data.Response === "started") {
+ onSuccess();
+ } else {
+ triggerStartError(signalR._.error(
+ signalR._.format(signalR.resources.invalidStartResponse, result),
+ null /* error */, xhr));
+ }
+ },
+ error: function (xhr, statusText, error) {
+ if (statusText !== startAbortText) {
+ triggerStartError(signalR._.error(
+ signalR.resources.errorDuringStartRequest,
+ error, xhr));
+ } else {
+ // Stop has been called, no need to trigger the error handler
+ // or stop the connection again with onStartError
+ connection.log("The start request aborted because connection.stop() was called.");
+ rejectDeferred(signalR._.error(
+ signalR.resources.stoppedDuringStartRequest,
+ null /* error */, xhr));
+ }
}
+ });
+ },
- data = this.maximizePersistentResponse(minData);
+ tryAbortStartRequest: function (connection) {
+ if (connection._.startRequest) {
+ // If the start request has already completed this will noop.
+ connection._.startRequest.abort(startAbortText);
+ delete connection._.startRequest;
+ }
+ },
- if (data.Disconnect) {
- connection.log("Disconnect command received from server.");
+ tryInitialize: function (persistentResponse, onInitialized) {
+ if (persistentResponse.Initialized) {
+ onInitialized();
+ }
+ },
- // Disconnected by the server
- connection.stop(false, false);
- return;
- }
+ triggerReceived: function (connection, data) {
+ if (!connection._.connectingMessageBuffer.tryBuffer(data)) {
+ $(connection).triggerHandler(events.onReceived, [data]);
+ }
+ },
+
+ processMessages: function (connection, minData, onInitialized) {
+ var data;
+
+ // Update the last message time stamp
+ transportLogic.markLastMessage(connection);
- this.updateGroups(connection, data.GroupsToken);
+ if (minData) {
+ data = transportLogic.maximizePersistentResponse(minData);
+
+ transportLogic.updateGroups(connection, data.GroupsToken);
+
+ if (data.MessageId) {
+ connection.messageId = data.MessageId;
+ }
if (data.Messages) {
$.each(data.Messages, function (index, message) {
- $connection.triggerHandler(events.onReceived, [message]);
+ transportLogic.triggerReceived(connection, message);
});
- }
- if (data.MessageId) {
- connection.messageId = data.MessageId;
+ transportLogic.tryInitialize(data, onInitialized);
}
}
},
@@ -1194,7 +1523,6 @@
if (!keepAliveData.monitoring) {
keepAliveData.monitoring = true;
- // Initialize the keep alive time stamp ping
transportLogic.markLastMessage(connection);
// Save the function so we can unbind it on stop
@@ -1206,9 +1534,8 @@
// Update Keep alive on reconnect
$(connection).bind(events.onReconnect, connection._.keepAliveData.reconnectKeepAliveUpdate);
- connection.log("Now monitoring keep alive with a warning timeout of " + keepAliveData.timeoutWarning + " and a connection lost timeout of " + keepAliveData.timeout + ".");
- }
- else {
+ connection.log("Now monitoring keep alive with a warning timeout of " + keepAliveData.timeoutWarning + ", keep alive timeout of " + keepAliveData.timeout + " and disconnecting timeout of " + connection.disconnectTimeout);
+ } else {
connection.log("Tried to monitor keep alive but it's already being monitored.");
}
},
@@ -1231,6 +1558,7 @@
},
startHeartbeat: function (connection) {
+ connection._.lastActiveAt = new Date().getTime();
beat(connection);
},
@@ -1247,6 +1575,11 @@
return false;
},
+ isConnectedOrReconnecting: function (connection) {
+ return connection.state === signalR.connectionState.connected ||
+ connection.state === signalR.connectionState.reconnecting;
+ },
+
ensureReconnectingState: function (connection) {
if (changeState(connection,
signalR.connectionState.connected,
@@ -1265,8 +1598,10 @@
verifyLastActive: function (connection) {
if (new Date().getTime() - connection._.lastActiveAt >= connection.reconnectWindow) {
- connection.log("There has not been an active server connection for an extended periord of time. Stopping connection.");
- connection.stop();
+ var message = signalR._.format(signalR.resources.reconnectWindowTimeout, new Date(connection._.lastActiveAt), connection.reconnectWindow);
+ connection.log(message);
+ $(connection).triggerHandler(events.onError, [signalR._.error(message, /* source */ "TimeoutException")]);
+ connection.stop(/* async */ false, /* notifyServer */ false);
return false;
}
@@ -1274,12 +1609,11 @@
},
reconnect: function (connection, transportName) {
- var transport = signalR.transports[transportName],
- that = this;
+ var transport = signalR.transports[transportName];
// We should only set a reconnectTimeout if we are currently connected
// and a reconnectTimeout isn't already set.
- if (isConnectedOrReconnecting(connection) && !connection._.reconnectTimeout) {
+ if (transportLogic.isConnectedOrReconnecting(connection) && !connection._.reconnectTimeout) {
// Need to verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration.
if (!transportLogic.verifyLastActive(connection)) {
return;
@@ -1292,7 +1626,7 @@
transport.stop(connection);
- if (that.ensureReconnectingState(connection)) {
+ if (transportLogic.ensureReconnectingState(connection)) {
connection.log(transportName + " reconnecting.");
transport.start(connection);
}
@@ -1300,18 +1634,26 @@
}
},
- handleParseFailure: function (connection, result, errorMessage, onFailed) {
+ handleParseFailure: function (connection, result, error, onFailed, context) {
+ var wrappedError = signalR._.transportError(
+ signalR._.format(signalR.resources.parseFailed, result),
+ connection.transport,
+ error,
+ context);
+
// If we're in the initialization phase trigger onFailed, otherwise stop the connection.
- if (connection.state === signalR.connectionState.connecting) {
+ if (onFailed && onFailed(wrappedError)) {
connection.log("Failed to parse server response while attempting to connect.");
- onFailed();
- }
- else {
- $(connection).triggerHandler(events.onError, ["SignalR: failed at parsing response: " + result + ". With error: " + errorMessage]);
+ } else {
+ $(connection).triggerHandler(events.onError, [wrappedError]);
connection.stop();
}
},
+ initHandler: function (connection) {
+ return new InitHandler(connection);
+ },
+
foreverFrame: {
count: 0,
connections: {}
@@ -1326,7 +1668,6 @@
///
(function ($, window, undefined) {
- "use strict";
var signalR = $.signalR,
events = $.signalR.events,
@@ -1336,21 +1677,33 @@
signalR.transports.webSockets = {
name: "webSockets",
- supportsKeepAlive: true,
-
- timeOut: 3000,
+ supportsKeepAlive: function () {
+ return true;
+ },
send: function (connection, data) {
- connection.socket.send(data);
+ var payload = transportLogic.stringifySend(connection, data);
+
+ try {
+ connection.socket.send(payload);
+ } catch (ex) {
+ $(connection).triggerHandler(events.onError,
+ [signalR._.transportError(
+ signalR.resources.webSocketsInvalidState,
+ connection.transport,
+ ex,
+ connection.socket
+ ),
+ data]);
+ }
},
start: function (connection, onSuccess, onFailed) {
var url,
opened = false,
that = this,
- initialSocket,
- timeOutHandle,
- reconnecting = !onSuccess;
+ reconnecting = !onSuccess,
+ $connection = $(connection);
if (!window.WebSocket) {
onFailed();
@@ -1360,8 +1713,7 @@
if (!connection.socket) {
if (connection.webSocketServerUrl) {
url = connection.webSocketServerUrl;
- }
- else {
+ } else {
url = connection.wsProtocol + connection.host;
}
@@ -1370,84 +1722,69 @@
connection.log("Connecting to websocket endpoint '" + url + "'.");
connection.socket = new window.WebSocket(url);
- // Issue #1653: Galaxy S3 Android Stock Browser fails silently to establish websocket connections.
- if (onFailed) {
- initialSocket = connection.socket;
- timeOutHandle = window.setTimeout(function () {
- if (initialSocket === connection.socket) {
- connection.log("WebSocket timed out trying to connect.");
- onFailed();
- }
- }, that.timeOut);
- }
-
connection.socket.onopen = function () {
- window.clearTimeout(timeOutHandle);
opened = true;
connection.log("Websocket opened.");
+
+ transportLogic.clearReconnectTimeout(connection);
+
+ if (changeState(connection,
+ signalR.connectionState.reconnecting,
+ signalR.connectionState.connected) === true) {
+ $connection.triggerHandler(events.onReconnect);
+ }
};
connection.socket.onclose = function (event) {
+ var error;
+
// Only handle a socket close if the close is from the current socket.
// Sometimes on disconnect the server will push down an onclose event
// to an expired socket.
- window.clearTimeout(timeOutHandle);
if (this === connection.socket) {
- if (!opened) {
- if (onFailed) {
- onFailed();
- }
- else if (reconnecting) {
- that.reconnect(connection);
- }
- return;
- }
- else if (typeof event.wasClean !== "undefined" && event.wasClean === false) {
+ if (opened && typeof event.wasClean !== "undefined" && event.wasClean === false) {
// Ideally this would use the websocket.onerror handler (rather than checking wasClean in onclose) but
// I found in some circumstances Chrome won't call onerror. This implementation seems to work on all browsers.
- $(connection).triggerHandler(events.onError, [event.reason]);
- connection.log("Unclean disconnect from websocket: " + event.reason || "[no reason given].");
- }
- else {
+ error = signalR._.transportError(
+ signalR.resources.webSocketClosed,
+ connection.transport,
+ event);
+
+ connection.log("Unclean disconnect from websocket: " + (event.reason || "[no reason given]."));
+ } else {
connection.log("Websocket closed.");
}
- that.reconnect(connection);
+ if (!onFailed || !onFailed(error)) {
+ if (error) {
+ $(connection).triggerHandler(events.onError, [error]);
+ }
+
+ that.reconnect(connection);
+ }
}
};
connection.socket.onmessage = function (event) {
- var data,
- $connection = $(connection);
+ var data;
try {
data = connection._parseResponse(event.data);
}
catch (error) {
- transportLogic.handleParseFailure(connection, event.data, error.message, onFailed);
+ transportLogic.handleParseFailure(connection, event.data, error, onFailed, event);
return;
}
-
- if (onSuccess) {
- onSuccess();
- onSuccess = null;
- } else if (changeState(connection,
- signalR.connectionState.reconnecting,
- signalR.connectionState.connected) === true) {
- $connection.triggerHandler(events.onReconnect);
- }
-
- transportLogic.clearReconnectTimeout(connection);
if (data) {
// data.M is PersistentResponse.Messages
if ($.isEmptyObject(data) || data.M) {
- transportLogic.processMessages(connection, data);
+ transportLogic.processMessages(connection, data, onSuccess);
} else {
// For websockets we need to trigger onReceived
// for callbacks to outgoing hub calls.
- $connection.triggerHandler(events.onReceived, [data]);
+ transportLogic.triggerReceived(connection, data);
}
}
};
@@ -1460,14 +1797,13 @@
lostConnection: function (connection) {
this.reconnect(connection);
-
},
stop: function (connection) {
// Don't trigger a reconnect after stopping
transportLogic.clearReconnectTimeout(connection);
- if (connection.socket !== null) {
+ if (connection.socket) {
connection.log("Closing the Websocket.");
connection.socket.close();
connection.socket = null;
@@ -1487,17 +1823,22 @@
///
(function ($, window, undefined) {
- "use strict";
var signalR = $.signalR,
events = $.signalR.events,
changeState = $.signalR.changeState,
- transportLogic = signalR.transports._logic;
+ transportLogic = signalR.transports._logic,
+ clearReconnectAttemptTimeout = function (connection) {
+ window.clearTimeout(connection._.reconnectAttemptTimeoutHandle);
+ delete connection._.reconnectAttemptTimeoutHandle;
+ };
signalR.transports.serverSentEvents = {
name: "serverSentEvents",
- supportsKeepAlive: true,
+ supportsKeepAlive: function () {
+ return true;
+ },
timeOut: 3000,
@@ -1506,8 +1847,7 @@
opened = false,
$connection = $(connection),
reconnecting = !onSuccess,
- url,
- connectTimeOut;
+ url;
if (connection.eventSource) {
connection.log("The connection already has an event source. Stopping it.");
@@ -1526,16 +1866,15 @@
try {
connection.log("Attempting to connect to SSE endpoint '" + url + "'.");
- connection.eventSource = new window.EventSource(url);
+ connection.eventSource = new window.EventSource(url, { withCredentials: connection.withCredentials });
}
catch (e) {
connection.log("EventSource failed trying to connect with error " + e.Message + ".");
if (onFailed) {
// The connection failed, call the failed callback
onFailed();
- }
- else {
- $connection.triggerHandler(events.onError, [e]);
+ } else {
+ $connection.triggerHandler(events.onError, [signalR._.transportError(signalR.resources.eventSourceFailedToConnect, connection.transport, e)]);
if (reconnecting) {
// If we were reconnecting, rather than doing initial connect, then try reconnect again
that.reconnect(connection);
@@ -1544,49 +1883,32 @@
return;
}
- // After connecting, if after the specified timeout there's no response stop the connection
- // and raise on failed
- connectTimeOut = window.setTimeout(function () {
- if (opened === false && connection.eventSource) {
- connection.log("EventSource timed out trying to connect.");
- connection.log("EventSource readyState: " + connection.eventSource.readyState + ".");
-
- if (!reconnecting) {
- that.stop(connection);
- }
-
- if (reconnecting) {
+ if (reconnecting) {
+ connection._.reconnectAttemptTimeoutHandle = window.setTimeout(function () {
+ if (opened === false) {
// If we're reconnecting and the event source is attempting to connect,
// don't keep retrying. This causes duplicate connections to spawn.
if (connection.eventSource.readyState !== window.EventSource.OPEN) {
// If we were reconnecting, rather than doing initial connect, then try reconnect again
that.reconnect(connection);
}
- } else if (onFailed) {
- onFailed();
}
- }
- },
- that.timeOut);
+ },
+ that.timeOut);
+ }
connection.eventSource.addEventListener("open", function (e) {
connection.log("EventSource connected.");
- if (connectTimeOut) {
- window.clearTimeout(connectTimeOut);
- }
-
+ clearReconnectAttemptTimeout(connection);
transportLogic.clearReconnectTimeout(connection);
if (opened === false) {
opened = true;
- if (onSuccess) {
- onSuccess();
- } else if (changeState(connection,
+ if (changeState(connection,
signalR.connectionState.reconnecting,
signalR.connectionState.connected) === true) {
- // If there's no onSuccess handler we assume this is a reconnect
$connection.triggerHandler(events.onReconnect);
}
}
@@ -1604,7 +1926,7 @@
res = connection._parseResponse(e.data);
}
catch (error) {
- transportLogic.handleParseFailure(connection, e.data, error.message, onFailed);
+ transportLogic.handleParseFailure(connection, e.data, error, onFailed, e);
return;
}
@@ -1612,32 +1934,35 @@
}, false);
connection.eventSource.addEventListener("error", function (e) {
+ var error = signalR._.transportError(
+ signalR.resources.eventSourceError,
+ connection.transport,
+ e);
+
// Only handle an error if the error is from the current Event Source.
// Sometimes on disconnect the server will push down an error event
// to an expired Event Source.
- if (this === connection.eventSource) {
- if (!opened) {
- if (onFailed) {
- onFailed();
- }
+ if (this !== connection.eventSource) {
+ return;
+ }
- return;
- }
+ if (onFailed && onFailed(error)) {
+ return;
+ }
- connection.log("EventSource readyState: " + connection.eventSource.readyState + ".");
+ connection.log("EventSource readyState: " + connection.eventSource.readyState + ".");
- if (e.eventPhase === window.EventSource.CLOSED) {
- // We don't use the EventSource's native reconnect function as it
- // doesn't allow us to change the URL when reconnecting. We need
- // to change the URL to not include the /connect suffix, and pass
- // the last message id we received.
- connection.log("EventSource reconnecting due to the server connection ending.");
- that.reconnect(connection);
- } else {
- // connection error
- connection.log("EventSource error.");
- $connection.triggerHandler(events.onError);
- }
+ if (e.eventPhase === window.EventSource.CLOSED) {
+ // We don't use the EventSource's native reconnect function as it
+ // doesn't allow us to change the URL when reconnecting. We need
+ // to change the URL to not include the /connect suffix, and pass
+ // the last message id we received.
+ connection.log("EventSource reconnecting due to the server connection ending.");
+ that.reconnect(connection);
+ } else {
+ // connection error
+ connection.log("EventSource error.");
+ $connection.triggerHandler(events.onError, [error]);
}
}, false);
},
@@ -1656,6 +1981,7 @@
stop: function (connection) {
// Don't trigger a reconnect after stopping
+ clearReconnectAttemptTimeout(connection);
transportLogic.clearReconnectTimeout(connection);
if (connection && connection.eventSource) {
@@ -1679,7 +2005,6 @@
///
(function ($, window, undefined) {
- "use strict";
var signalR = $.signalR,
events = $.signalR.events,
@@ -1718,7 +2043,7 @@
attachedTo++;
}
},
- cancel: function () {
+ cancel: function () {
// Only clear the interval if there's only one more object that the loadPreventer is attachedTo
if (attachedTo === 1) {
window.clearInterval(loadingFixIntervalId);
@@ -1734,9 +2059,9 @@
signalR.transports.foreverFrame = {
name: "foreverFrame",
- supportsKeepAlive: true,
-
- timeOut: 3000,
+ supportsKeepAlive: function () {
+ return true;
+ },
// Added as a value here so we can create tests to verify functionality
iframeClearThreshold: 50,
@@ -1747,14 +2072,16 @@
url,
frame = createFrame(),
frameLoadHandler = function () {
- connection.log("Forever frame iframe finished loading and is no longer receiving messages, reconnecting.");
- that.reconnect(connection);
+ connection.log("Forever frame iframe finished loading and is no longer receiving messages.");
+ if (!onFailed || !onFailed()) {
+ that.reconnect(connection);
+ }
};
if (window.EventSource) {
// If the browser supports SSE, don't use Forever Frame
if (onFailed) {
- connection.log("This browser supports SSE, skipping Forever Frame.");
+ connection.log("Forever Frame is not supported by SignalR on browsers with SSE support.");
onFailed();
}
return;
@@ -1770,8 +2097,8 @@
url = transportLogic.getUrl(connection, this.name);
url += "&frameId=" + frameId;
- // Set body prior to setting URL to avoid caching issues.
- window.document.body.appendChild(frame);
+ // add frame to the document prior to setting URL to avoid caching issues.
+ window.document.documentElement.appendChild(frame);
connection.log("Binding to iframe's load event.");
@@ -1793,42 +2120,27 @@
onSuccess();
};
}
-
- // After connecting, if after the specified timeout there's no response stop the connection
- // and raise on failed
- window.setTimeout(function () {
- if (connection.onSuccess) {
- connection.log("Failed to connect using forever frame source, it timed out after " + that.timeOut + "ms.");
- that.stop(connection);
-
- if (onFailed) {
- onFailed();
- }
- }
- }, that.timeOut);
},
reconnect: function (connection) {
var that = this;
- // Need to verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration.
- if (!transportLogic.verifyLastActive(connection)) {
- return;
- }
-
- window.setTimeout(function () {
- // Verify that we're ok to reconnect
- if (!transportLogic.verifyLastActive(connection)) {
- return;
- }
+ // Need to verify connection state and verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration.
+ if (transportLogic.isConnectedOrReconnecting(connection) && transportLogic.verifyLastActive(connection)) {
+ window.setTimeout(function () {
+ // Verify that we're ok to reconnect.
+ if (!transportLogic.verifyLastActive(connection)) {
+ return;
+ }
- if (connection.frame && transportLogic.ensureReconnectingState(connection)) {
- var frame = connection.frame,
- src = transportLogic.getUrl(connection, that.name, true) + "&frameId=" + connection.frameId;
- connection.log("Updating iframe src to '" + src + "'.");
- frame.src = src;
- }
- }, connection.reconnectDelay);
+ if (connection.frame && transportLogic.ensureReconnectingState(connection)) {
+ var frame = connection.frame,
+ src = transportLogic.getUrl(connection, that.name, true) + "&frameId=" + connection.frameId;
+ connection.log("Updating iframe src to '" + src + "'.");
+ frame.src = src;
+ }
+ }, connection.reconnectDelay);
+ }
},
lostConnection: function (connection) {
@@ -1841,20 +2153,36 @@
receive: function (connection, data) {
var cw,
- body;
-
- transportLogic.processMessages(connection, data);
- // Delete the script & div elements
- connection.frameMessageCount = (connection.frameMessageCount || 0) + 1;
- if (connection.frameMessageCount > signalR.transports.foreverFrame.iframeClearThreshold && connection.state === $.signalR.connectionState.connected) {
- connection.frameMessageCount = 0;
- cw = connection.frame.contentWindow || connection.frame.contentDocument;
- if (cw && cw.document && cw.document.body) {
- body = cw.document.body;
-
- // Remove all the child elements from the iframe's body to conserver memory
- while (body.firstChild) {
- body.removeChild(body.firstChild);
+ body,
+ response;
+
+ if (connection.json !== connection._originalJson) {
+ // If there's a custom JSON parser configured then serialize the object
+ // using the original (browser) JSON parser and then deserialize it using
+ // the custom parser (connection._parseResponse does that). This is so we
+ // can easily send the response from the server as "raw" JSON but still
+ // support custom JSON deserialization in the browser.
+ data = connection._originalJson.stringify(data);
+ }
+
+ response = connection._parseResponse(data);
+
+ transportLogic.processMessages(connection, response, connection.onSuccess);
+
+ // Protect against connection stopping from a callback trigger within the processMessages above.
+ if (connection.state === $.signalR.connectionState.connected) {
+ // Delete the script & div elements
+ connection.frameMessageCount = (connection.frameMessageCount || 0) + 1;
+ if (connection.frameMessageCount > signalR.transports.foreverFrame.iframeClearThreshold) {
+ connection.frameMessageCount = 0;
+ cw = connection.frame.contentWindow || connection.frame.contentDocument;
+ if (cw && cw.document && cw.document.body) {
+ body = cw.document.body;
+
+ // Remove all the child elements from the iframe's body to conserver memory
+ while (body.firstChild) {
+ body.removeChild(body.firstChild);
+ }
}
}
}
@@ -1891,6 +2219,7 @@
connection.frameId = null;
delete connection.frame;
delete connection.frameId;
+ delete connection.onSuccess;
delete connection.frameMessageCount;
connection.log("Stopping forever frame.");
}
@@ -1905,14 +2234,10 @@
},
started: function (connection) {
- if (connection.onSuccess) {
- connection.onSuccess();
- connection.onSuccess = null;
- delete connection.onSuccess;
- } else if (changeState(connection,
- signalR.connectionState.reconnecting,
- signalR.connectionState.connected) === true) {
- // If there's no onSuccess handler we assume this is a reconnect
+ if (changeState(connection,
+ signalR.connectionState.reconnecting,
+ signalR.connectionState.connected) === true) {
+
$(connection).triggerHandler(events.onReconnect);
}
}
@@ -1926,7 +2251,6 @@
///
(function ($, window, undefined) {
- "use strict";
var signalR = $.signalR,
events = $.signalR.events,
@@ -1937,51 +2261,24 @@
signalR.transports.longPolling = {
name: "longPolling",
- supportsKeepAlive: false,
+ supportsKeepAlive: function () {
+ return false;
+ },
reconnectDelay: 3000,
- init: function (connection, onComplete) {
- /// Pings the server to ensure availability
- /// Connection associated with the server ping
- /// Callback to call once initialization has completed
-
- var that = this,
- pingLoop,
- // pingFail is used to loop the re-ping behavior. When we fail we want to re-try.
- pingFail = function (reason) {
- if (isDisconnecting(connection) === false) {
- connection.log("Server ping failed because '" + reason + "', re-trying ping.");
- connection._.pingLoopId = window.setTimeout(pingLoop, that.reconnectDelay);
- }
- };
-
- connection.log("Initializing long polling connection with server.");
- pingLoop = function () {
- // Ping the server, on successful ping call the onComplete method, otherwise if we fail call the pingFail
- transportLogic.pingServer(connection).done(onComplete).fail(pingFail);
- };
-
- pingLoop();
- },
-
start: function (connection, onSuccess, onFailed) {
/// Starts the long polling connection
/// The SignalR connection to start
var that = this,
fireConnect = function () {
- tryFailConnect = fireConnect = $.noop;
+ fireConnect = $.noop;
- connection.log("Longpolling connected.");
+ connection.log("LongPolling connected.");
onSuccess();
-
- // Reset onFailed to null because it shouldn't be called again
- onFailed = null;
},
- tryFailConnect = function () {
- if (onFailed) {
- onFailed();
- onFailed = null;
+ tryFailConnect = function (error) {
+ if (onFailed(error)) {
connection.log("LongPolling failed to connect.");
return true;
}
@@ -1994,11 +2291,11 @@
window.clearTimeout(privateData.reconnectTimeoutId);
privateData.reconnectTimeoutId = null;
- if (changeState(connection,
+ if (changeState(instance,
signalR.connectionState.reconnecting,
signalR.connectionState.connected) === true) {
// Successfully reconnected!
- connection.log("Raising the reconnect event.");
+ instance.log("Raising the reconnect event");
$(instance).triggerHandler(events.onReconnect);
}
},
@@ -2010,170 +2307,169 @@
connection.stop();
}
+ connection.messageId = null;
+
privateData.reconnectTimeoutId = null;
- privateData.pollTimeoutId = null;
-
- // We start with an initialization procedure which pings the server to verify that it is there.
- // On scucessful initialization we'll then proceed with starting the transport.
- that.init(connection, function () {
- connection.messageId = null;
-
- privateData.pollTimeoutId = window.setTimeout(function () {
- (function poll(instance, raiseReconnect) {
- var messageId = instance.messageId,
- connect = (messageId === null),
- reconnecting = !connect,
- polling = !raiseReconnect,
- url = transportLogic.getUrl(instance, that.name, reconnecting, polling);
-
- // If we've disconnected during the time we've tried to re-instantiate the poll then stop.
- if (isDisconnecting(instance) === true) {
- return;
- }
- connection.log("Opening long polling request to '" + url + "'.");
- instance.pollXhr = $.ajax(
- $.extend({}, $.signalR.ajaxDefaults, {
- xhrFields: { withCredentials: connection.withCredentials },
- url: url,
- type: "GET",
- dataType: connection.ajaxDataType,
- contentType: connection.contentType,
- success: function (result) {
- var delay = 0,
- minData,
- data,
- shouldReconnect;
-
- connection.log("Long poll complete.");
-
- // Reset our reconnect errors so if we transition into a reconnecting state again we trigger
- // reconnected quickly
- reconnectErrors = 0;
-
- try {
- minData = connection._parseResponse(result);
- }
- catch (error) {
- transportLogic.handleParseFailure(instance, result, error.message, tryFailConnect);
- return;
- }
-
- // If there's currently a timeout to trigger reconnect, fire it now before processing messages
- if (privateData.reconnectTimeoutId !== null) {
- fireReconnected();
- }
-
- fireConnect();
-
- if (minData) {
- data = transportLogic.maximizePersistentResponse(minData);
- }
-
- transportLogic.processMessages(instance, minData);
-
- if (data &&
- $.type(data.LongPollDelay) === "number") {
- delay = data.LongPollDelay;
- }
-
- if (data && data.Disconnect) {
- return;
- }
-
- if (isDisconnecting(instance) === true) {
- return;
- }
-
- shouldReconnect = data && data.ShouldReconnect;
- if (shouldReconnect) {
- // Transition into the reconnecting state
- transportLogic.ensureReconnectingState(instance);
- }
-
- // We never want to pass a raiseReconnect flag after a successful poll. This is handled via the error function
- if (delay > 0) {
- privateData.pollTimeoutId = window.setTimeout(function () {
- poll(instance, shouldReconnect);
- }, delay);
- } else {
- poll(instance, shouldReconnect);
- }
- },
-
- error: function (data, textStatus) {
- // Stop trying to trigger reconnect, connection is in an error state
- // If we're not in the reconnect state this will noop
- window.clearTimeout(privateData.reconnectTimeoutId);
- privateData.reconnectTimeoutId = null;
-
- if (textStatus === "abort") {
- connection.log("Aborted xhr requst.");
- return;
- }
-
- if (!tryFailConnect()) {
- // Increment our reconnect errors, we assume all errors to be reconnect errors
- // In the case that it's our first error this will cause Reconnect to be fired
- // after 1 second due to reconnectErrors being = 1.
- reconnectErrors++;
-
- if (connection.state !== signalR.connectionState.reconnecting) {
- connection.log("An error occurred using longPolling. Status = " + textStatus + ". Response = " + data.responseText + ".");
- $(instance).triggerHandler(events.onError, [data.responseText]);
- }
-
- // We check the state here to verify that we're not in an invalid state prior to verifying Reconnect.
- // If we're not in connected or reconnecting then the next ensureReconnectingState check will fail and will return.
- // Therefore we don't want to change that failure code path.
- if ((connection.state === signalR.connectionState.connected ||
- connection.state === signalR.connectionState.reconnecting) &&
- !transportLogic.verifyLastActive(connection)) {
- return;
- }
-
- // Transition into the reconnecting state
- // If this fails then that means that the user transitioned the connection into the disconnected or connecting state within the above error handler trigger.
- if (!transportLogic.ensureReconnectingState(instance)) {
- return;
- }
-
- privateData.pollTimeoutId = window.setTimeout(function () {
- // If we've errored out we need to verify that the server is still there, so re-start initialization process
- // This will ping the server until it successfully gets a response.
- that.init(instance, function () {
- // Call poll with the raiseReconnect flag as true
- poll(instance, true);
- });
- }, that.reconnectDelay);
- }
+ privateData.pollTimeoutId = window.setTimeout(function () {
+ (function poll(instance, raiseReconnect) {
+ var messageId = instance.messageId,
+ connect = (messageId === null),
+ reconnecting = !connect,
+ polling = !raiseReconnect,
+ url = transportLogic.getUrl(instance, that.name, reconnecting, polling, true /* use Post for longPolling */),
+ postData = {};
+
+ if (instance.messageId) {
+ postData.messageId = instance.messageId;
+ }
+
+ if (instance.groupsToken) {
+ postData.groupsToken = instance.groupsToken;
+ }
+
+ // If we've disconnected during the time we've tried to re-instantiate the poll then stop.
+ if (isDisconnecting(instance) === true) {
+ return;
+ }
+
+ connection.log("Opening long polling request to '" + url + "'.");
+ instance.pollXhr = transportLogic.ajax(connection, {
+ xhrFields: {
+ onprogress: function () {
+ transportLogic.markLastMessage(connection);
+ }
+ },
+ url: url,
+ type: "POST",
+ contentType: signalR._.defaultContentType,
+ data: postData,
+ timeout: connection._.pollTimeout,
+ success: function (result) {
+ var minData,
+ delay = 0,
+ data,
+ shouldReconnect;
+
+ connection.log("Long poll complete.");
+
+ // Reset our reconnect errors so if we transition into a reconnecting state again we trigger
+ // reconnected quickly
+ reconnectErrors = 0;
+
+ try {
+ // Remove any keep-alives from the beginning of the result
+ minData = connection._parseResponse(result);
+ }
+ catch (error) {
+ transportLogic.handleParseFailure(instance, result, error, tryFailConnect, instance.pollXhr);
+ return;
+ }
+
+ // If there's currently a timeout to trigger reconnect, fire it now before processing messages
+ if (privateData.reconnectTimeoutId !== null) {
+ fireReconnected(instance);
+ }
+
+ if (minData) {
+ data = transportLogic.maximizePersistentResponse(minData);
+ }
+
+ transportLogic.processMessages(instance, minData, fireConnect);
+
+ if (data &&
+ $.type(data.LongPollDelay) === "number") {
+ delay = data.LongPollDelay;
+ }
+
+ if (isDisconnecting(instance) === true) {
+ return;
+ }
+
+ shouldReconnect = data && data.ShouldReconnect;
+ if (shouldReconnect) {
+ // Transition into the reconnecting state
+ // If this fails then that means that the user transitioned the connection into a invalid state in processMessages.
+ if (!transportLogic.ensureReconnectingState(instance)) {
+ return;
}
- }));
+ }
+
+ // We never want to pass a raiseReconnect flag after a successful poll. This is handled via the error function
+ if (delay > 0) {
+ privateData.pollTimeoutId = window.setTimeout(function () {
+ poll(instance, shouldReconnect);
+ }, delay);
+ } else {
+ poll(instance, shouldReconnect);
+ }
+ },
+ error: function (data, textStatus) {
+ var error = signalR._.transportError(signalR.resources.longPollFailed, connection.transport, data, instance.pollXhr);
- // This will only ever pass after an error has occured via the poll ajax procedure.
- if (reconnecting && raiseReconnect === true) {
- // We wait to reconnect depending on how many times we've failed to reconnect.
- // This is essentially a heuristic that will exponentially increase in wait time before
- // triggering reconnected. This depends on the "error" handler of Poll to cancel this
- // timeout if it triggers before the Reconnected event fires.
- // The Math.min at the end is to ensure that the reconnect timeout does not overflow.
- privateData.reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout));
+ // Stop trying to trigger reconnect, connection is in an error state
+ // If we're not in the reconnect state this will noop
+ window.clearTimeout(privateData.reconnectTimeoutId);
+ privateData.reconnectTimeoutId = null;
+
+ if (textStatus === "abort") {
+ connection.log("Aborted xhr request.");
+ return;
+ }
+
+ if (!tryFailConnect(error)) {
+
+ // Increment our reconnect errors, we assume all errors to be reconnect errors
+ // In the case that it's our first error this will cause Reconnect to be fired
+ // after 1 second due to reconnectErrors being = 1.
+ reconnectErrors++;
+
+ if (connection.state !== signalR.connectionState.reconnecting) {
+ connection.log("An error occurred using longPolling. Status = " + textStatus + ". Response = " + data.responseText + ".");
+ $(instance).triggerHandler(events.onError, [error]);
+ }
+
+ // We check the state here to verify that we're not in an invalid state prior to verifying Reconnect.
+ // If we're not in connected or reconnecting then the next ensureReconnectingState check will fail and will return.
+ // Therefore we don't want to change that failure code path.
+ if ((connection.state === signalR.connectionState.connected ||
+ connection.state === signalR.connectionState.reconnecting) &&
+ !transportLogic.verifyLastActive(connection)) {
+ return;
+ }
+
+ // Transition into the reconnecting state
+ // If this fails then that means that the user transitioned the connection into the disconnected or connecting state within the above error handler trigger.
+ if (!transportLogic.ensureReconnectingState(instance)) {
+ return;
+ }
+
+ // Call poll with the raiseReconnect flag as true after the reconnect delay
+ privateData.pollTimeoutId = window.setTimeout(function () {
+ poll(instance, true);
+ }, that.reconnectDelay);
+ }
}
- }(connection));
-
- // Set an arbitrary timeout to trigger onSuccess, this will alot for enough time on the server to wire up the connection.
- // Will be fixed by #1189 and this code can be modified to not be a timeout
- window.setTimeout(function () {
- // Trigger the onSuccess() method because we've now instantiated a connection
- fireConnect();
- }, 250);
- }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
- });
+ });
+
+ // This will only ever pass after an error has occured via the poll ajax procedure.
+ if (reconnecting && raiseReconnect === true) {
+ // We wait to reconnect depending on how many times we've failed to reconnect.
+ // This is essentially a heuristic that will exponentially increase in wait time before
+ // triggering reconnected. This depends on the "error" handler of Poll to cancel this
+ // timeout if it triggers before the Reconnected event fires.
+ // The Math.min at the end is to ensure that the reconnect timeout does not overflow.
+ privateData.reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout));
+ }
+ }(connection));
+ }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
lostConnection: function (connection) {
- throw new Error("Lost Connection not handled for LongPolling");
+ if (connection.pollXhr) {
+ connection.pollXhr.abort("lostConnection");
+ }
},
send: function (connection, data) {
@@ -2183,6 +2479,7 @@
stop: function (connection) {
/// Stops the long polling connection
/// The SignalR connection to stop
+
window.clearTimeout(connection._.pollTimeoutId);
window.clearTimeout(connection._.reconnectTimeoutId);
@@ -2209,10 +2506,9 @@
///
(function ($, window, undefined) {
- "use strict";
- // we use a global id for tracking callbacks so the server doesn't have to send extra info like hub name
- var eventNamespace = ".hubProxy";
+ var eventNamespace = ".hubProxy",
+ signalR = $.signalR;
function makeEventName(event) {
return event + eventNamespace;
@@ -2289,6 +2585,8 @@
};
},
+ constructor: hubProxy,
+
hasSubscriptions: function () {
return hasMembers(this._.callbackMap);
},
@@ -2297,8 +2595,8 @@
/// Wires up a callback to be invoked when a invocation request is received from the server hub.
/// The name of the hub event to register the callback for.
/// The callback to be invoked.
- var self = this,
- callbackMap = self._.callbackMap;
+ var that = this,
+ callbackMap = that._.callbackMap;
// Normalize the event name to lowercase
eventName = eventName.toLowerCase();
@@ -2310,20 +2608,20 @@
// Map the callback to our encompassed function
callbackMap[eventName][callback] = function (e, data) {
- callback.apply(self, data);
+ callback.apply(that, data);
};
- $(self).bind(makeEventName(eventName), callbackMap[eventName][callback]);
+ $(that).bind(makeEventName(eventName), callbackMap[eventName][callback]);
- return self;
+ return that;
},
off: function (eventName, callback) {
/// Removes the callback invocation request from the server hub for the given event name.
/// The name of the hub event to unregister the callback for.
/// The callback to be invoked.
- var self = this,
- callbackMap = self._.callbackMap,
+ var that = this,
+ callbackMap = that._.callbackMap,
callbackSpace;
// Normalize the event name to lowercase
@@ -2335,7 +2633,7 @@
if (callbackSpace) {
// Only unbind if there's an event bound with eventName and a callback with the specified callback
if (callbackSpace[callback]) {
- $(self).unbind(makeEventName(eventName), callbackSpace[callback]);
+ $(that).unbind(makeEventName(eventName), callbackSpace[callback]);
// Remove the callback from the callback map
delete callbackSpace[callback];
@@ -2344,53 +2642,71 @@
if (!hasMembers(callbackSpace)) {
delete callbackMap[eventName];
}
- }
- else if (!callback) { // Check if we're removing the whole event and we didn't error because of an invalid callback
- $(self).unbind(makeEventName(eventName));
+ } else if (!callback) { // Check if we're removing the whole event and we didn't error because of an invalid callback
+ $(that).unbind(makeEventName(eventName));
delete callbackMap[eventName];
}
}
- return self;
+ return that;
},
invoke: function (methodName) {
/// Invokes a server hub method with the given arguments.
/// The name of the server hub method.
- var self = this,
- connection = self.connection,
+ var that = this,
+ connection = that.connection,
args = $.makeArray(arguments).slice(1),
argValues = map(args, getArgValue),
- data = { H: self.hubName, M: methodName, A: argValues, I: connection._.invocationCallbackId },
+ data = { H: that.hubName, M: methodName, A: argValues, I: connection._.invocationCallbackId },
d = $.Deferred(),
callback = function (minResult) {
- var result = self._maximizeHubResponse(minResult);
+ var result = that._maximizeHubResponse(minResult),
+ source,
+ error;
// Update the hub state
- $.extend(self.state, result.State);
-
- if (result.Error) {
+ $.extend(that.state, result.State);
+
+ if (result.Progress) {
+ if (d.notifyWith) {
+ // Progress is only supported in jQuery 1.7+
+ d.notifyWith(that, [result.Progress.Data]);
+ } else if(!connection._.progressjQueryVersionLogged) {
+ connection.log("A hub method invocation progress update was received but the version of jQuery in use (" + $.prototype.jquery + ") does not support progress updates. Upgrade to jQuery 1.7+ to receive progress notifications.");
+ connection._.progressjQueryVersionLogged = true;
+ }
+ } else if (result.Error) {
// Server hub method threw an exception, log it & reject the deferred
if (result.StackTrace) {
connection.log(result.Error + "\n" + result.StackTrace + ".");
}
- d.rejectWith(self, [result.Error]);
+
+ // result.ErrorData is only set if a HubException was thrown
+ source = result.IsHubException ? "HubException" : "Exception";
+ error = signalR._.error(result.Error, source);
+ error.data = result.ErrorData;
+
+ connection.log(that.hubName + "." + methodName + " failed to execute. Error: " + error.message);
+ d.rejectWith(that, [error]);
} else {
// Server invocation succeeded, resolve the deferred
- d.resolveWith(self, [result.Result]);
+ connection.log("Invoked " + that.hubName + "." + methodName);
+ d.resolveWith(that, [result.Result]);
}
};
- connection._.invocationCallbacks[connection._.invocationCallbackId.toString()] = { scope: self, method: callback };
+ connection._.invocationCallbacks[connection._.invocationCallbackId.toString()] = { scope: that, method: callback };
connection._.invocationCallbackId += 1;
- if (!$.isEmptyObject(self.state)) {
- data.S = self.state;
+ if (!$.isEmptyObject(that.state)) {
+ data.S = that.state;
}
- connection.send(self.connection.json.stringify(data));
+ connection.log("Invoking " + that.hubName + "." + methodName);
+ connection.send(data);
return d.promise();
},
@@ -2399,9 +2715,15 @@
return {
State: minHubResponse.S,
Result: minHubResponse.R,
+ Progress: minHubResponse.P ? {
+ Id: minHubResponse.P.I,
+ Data: minHubResponse.P.D
+ } : null,
Id: minHubResponse.I,
+ IsHubException: minHubResponse.H,
Error: minHubResponse.E,
- StackTrace: minHubResponse.T
+ StackTrace: minHubResponse.T,
+ ErrorData: minHubResponse.D
};
}
};
@@ -2431,10 +2753,10 @@
hubConnection.fn.init = function (url, options) {
var settings = {
- qs: null,
- logging: false,
- useDefaultPath: true
- },
+ qs: null,
+ logging: false,
+ useDefaultPath: true
+ },
connection = this;
$.extend(settings, options);
@@ -2455,7 +2777,17 @@
return;
}
- if (typeof (minData.I) !== "undefined") {
+ // We have to handle progress updates first in order to ensure old clients that receive
+ // progress updates enter the return value branch and then no-op when they can't find
+ // the callback in the map (because the minData.I value will not be a valid callback ID)
+ if (typeof (minData.P) !== "undefined") {
+ // Process progress notification
+ dataCallbackId = minData.P.I.toString();
+ callback = connection._.invocationCallbacks[dataCallbackId];
+ if (callback) {
+ callback.method.call(callback.scope, minData);
+ }
+ } else if (typeof (minData.I) !== "undefined") {
// We received the return value from a server method invocation, look up callback by id and call it
dataCallbackId = minData.I.toString();
callback = connection._.invocationCallbacks[dataCallbackId];
@@ -2489,24 +2821,11 @@
connection.error(function (errData, origData) {
var callbackId, callback;
- if (connection.transport && connection.transport.name === "webSockets") {
- // WebSockets connections have all callbacks removed on reconnect instead
- // as WebSockets sends are fire & forget
- return;
- }
-
if (!origData) {
// No original data passed so this is not a send error
return;
}
- try {
- origData = connection.json.parse(origData);
- } catch (e) {
- // The original data is not a JSON payload so this is not a send error
- return;
- }
-
callbackId = origData.I;
callback = connection._.invocationCallbacks[callbackId];
@@ -2605,5 +2924,5 @@
/*global window:false */
///
(function ($, undefined) {
- $.signalR.version = "1.2.2";
+ $.signalR.version = "2.2.0";
}(window.jQuery));
diff --git a/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-2.2.0.min.js b/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-2.2.0.min.js
new file mode 100644
index 0000000000..70bbc0b045
--- /dev/null
+++ b/src/SmokeTest.SignalrClient/Scripts/jquery.signalR-2.2.0.min.js
@@ -0,0 +1,8 @@
+/*!
+ * ASP.NET SignalR JavaScript Library v2.2.0
+ * http://signalr.net/
+ *
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ *
+ */
+(function(n,t,i){function w(t,i){var u,f;if(n.isArray(t)){for(u=t.length-1;u>=0;u--)f=t[u],n.type(f)==="string"&&r.transports[f]||(i.log("Invalid transport: "+f+", removing it from the transports list."),t.splice(u,1));t.length===0&&(i.log("No transports remain within the specified transport array."),t=null)}else if(r.transports[t]||t==="auto"){if(t==="auto"&&r._.ieVersion<=8)return["longPolling"]}else i.log("Invalid transport: "+t.toString()+"."),t=null;return t}function b(n){return n==="http:"?80:n==="https:"?443:void 0}function a(n,t){return t.match(/:\d+$/)?t:t+":"+b(n)}function k(t,i){var u=this,r=[];u.tryBuffer=function(i){return t.state===n.signalR.connectionState.connecting?(r.push(i),!0):!1};u.drain=function(){if(t.state===n.signalR.connectionState.connected)while(r.length>0)i(r.shift())};u.clear=function(){r=[]}}var f={nojQuery:"jQuery was not found. Please ensure jQuery is referenced before the SignalR client JavaScript file.",noTransportOnInit:"No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.",errorOnNegotiate:"Error during negotiation request.",stoppedWhileLoading:"The connection was stopped during page load.",stoppedWhileNegotiating:"The connection was stopped during the negotiate request.",errorParsingNegotiateResponse:"Error parsing negotiate response.",errorDuringStartRequest:"Error during start request. Stopping the connection.",stoppedDuringStartRequest:"The connection was stopped during the start request.",errorParsingStartResponse:"Error parsing start response: '{0}'. Stopping the connection.",invalidStartResponse:"Invalid start response: '{0}'. Stopping the connection.",protocolIncompatible:"You are using a version of the client that isn't compatible with the server. Client version {0}, server version {1}.",sendFailed:"Send failed.",parseFailed:"Failed at parsing response: {0}",longPollFailed:"Long polling request failed.",eventSourceFailedToConnect:"EventSource failed to connect.",eventSourceError:"Error raised by EventSource",webSocketClosed:"WebSocket closed.",pingServerFailedInvalidResponse:"Invalid ping response when pinging server: '{0}'.",pingServerFailed:"Failed to ping server.",pingServerFailedStatusCode:"Failed to ping server. Server responded with status code {0}, stopping the connection.",pingServerFailedParse:"Failed to parse ping server response, stopping the connection.",noConnectionTransport:"Connection is in an invalid state, there is no transport active.",webSocketsInvalidState:"The Web Socket transport is in an invalid state, transitioning into reconnecting.",reconnectTimeout:"Couldn't reconnect within the configured timeout of {0} ms, disconnecting.",reconnectWindowTimeout:"The client has been inactive since {0} and it has exceeded the inactivity timeout of {1} ms. Stopping the connection."};if(typeof n!="function")throw new Error(f.nojQuery);var r,h,s=t.document.readyState==="complete",e=n(t),c="__Negotiate Aborted__",u={onStart:"onStart",onStarting:"onStarting",onReceived:"onReceived",onError:"onError",onConnectionSlow:"onConnectionSlow",onReconnecting:"onReconnecting",onReconnect:"onReconnect",onStateChanged:"onStateChanged",onDisconnect:"onDisconnect"},v=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))}},o=function(t,i,r){return i===t.state?(t.state=r,n(t).triggerHandler(u.onStateChanged,[{oldState:i,newState:r}]),!0):!1},y=function(n){return n.state===r.connectionState.disconnected},l=function(n){return n._.keepAliveData.activated&&n.transport.supportsKeepAlive(n)},p=function(i){var f,e;i._.configuredStopReconnectingTimeout||(e=function(t){var i=r._.format(r.resources.reconnectTimeout,t.disconnectTimeout);t.log(i);n(t).triggerHandler(u.onError,[r._.error(i,"TimeoutException")]);t.stop(!1,!1)},i.reconnecting(function(){var n=this;n.state===r.connectionState.reconnecting&&(f=t.setTimeout(function(){e(n)},n.disconnectTimeout))}),i.stateChanged(function(n){n.oldState===r.connectionState.reconnecting&&t.clearTimeout(f)}),i._.configuredStopReconnectingTimeout=!0)};r=function(n,t,i){return new r.fn.init(n,t,i)};r._={defaultContentType:"application/x-www-form-urlencoded; charset=UTF-8",ieVersion:function(){var i,n;return t.navigator.appName==="Microsoft Internet Explorer"&&(n=/MSIE ([0-9]+\.[0-9]+)/.exec(t.navigator.userAgent),n&&(i=t.parseFloat(n[1]))),i}(),error:function(n,t,i){var r=new Error(n);return r.source=t,typeof i!="undefined"&&(r.context=i),r},transportError:function(n,t,r,u){var f=this.error(n,r,u);return f.transport=t?t.name:i,f},format:function(){for(var t=arguments[0],n=0;n<\/script>.");}};e.load(function(){s=!0});r.fn=r.prototype={init:function(t,i,r){var f=n(this);this.url=t;this.qs=i;this.lastError=null;this._={keepAliveData:{},connectingMessageBuffer:new k(this,function(n){f.triggerHandler(u.onReceived,[n])}),lastMessageAt:(new Date).getTime(),lastActiveAt:(new Date).getTime(),beatInterval:5e3,beatHandle:null,totalTransportConnectTimeout:0};typeof r=="boolean"&&(this.logging=r)},_parseResponse:function(n){var t=this;return n?typeof n=="string"?t.json.parse(n):n:n},_originalJson:t.JSON,json:t.JSON,isCrossDomain:function(i,r){var u;return(i=n.trim(i),r=r||t.location,i.indexOf("http")!==0)?!1:(u=t.document.createElement("a"),u.href=i,u.protocol+a(u.protocol,u.host)!==r.protocol+a(r.protocol,r.host))},ajaxDataType:"text",contentType:"application/json; charset=UTF-8",logging:!1,state:r.connectionState.disconnected,clientProtocol:"1.5",reconnectDelay:2e3,transportConnectTimeout:0,disconnectTimeout:3e4,reconnectWindow:3e4,keepAliveWarnAt:2/3,start:function(i,h){var a=this,v={pingInterval:3e5,waitForPageLoad:!0,transport:"auto",jsonp:!1},d,y=a._deferral||n.Deferred(),b=t.document.createElement("a"),k,g;if(a.lastError=null,a._deferral=y,!a.json)throw new Error("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.");if(n.type(i)==="function"?h=i:n.type(i)==="object"&&(n.extend(v,i),n.type(v.callback)==="function"&&(h=v.callback)),v.transport=w(v.transport,a),!v.transport)throw new Error("SignalR: Invalid transport(s) specified, aborting start.");return(a._.config=v,!s&&v.waitForPageLoad===!0)?(a._.deferredStartHandler=function(){a.start(i,h)},e.bind("load",a._.deferredStartHandler),y.promise()):a.state===r.connectionState.connecting?y.promise():o(a,r.connectionState.disconnected,r.connectionState.connecting)===!1?(y.resolve(a),y.promise()):(p(a),b.href=a.url,b.protocol&&b.protocol!==":"?(a.protocol=b.protocol,a.host=b.host):(a.protocol=t.document.location.protocol,a.host=b.host||t.document.location.host),a.baseUrl=a.protocol+"//"+a.host,a.wsProtocol=a.protocol==="https:"?"wss://":"ws://",v.transport==="auto"&&v.jsonp===!0&&(v.transport="longPolling"),a.url.indexOf("//")===0&&(a.url=t.location.protocol+a.url,a.log("Protocol relative URL detected, normalizing it to '"+a.url+"'.")),this.isCrossDomain(a.url)&&(a.log("Auto detected cross domain url."),v.transport==="auto"&&(v.transport=["webSockets","serverSentEvents","longPolling"]),typeof v.withCredentials=="undefined"&&(v.withCredentials=!0),v.jsonp||(v.jsonp=!n.support.cors,v.jsonp&&a.log("Using jsonp because this browser doesn't support CORS.")),a.contentType=r._.defaultContentType),a.withCredentials=v.withCredentials,a.ajaxDataType=v.jsonp?"jsonp":"text",n(a).bind(u.onStart,function(){n.type(h)==="function"&&h.call(a);y.resolve(a)}),a._.initHandler=r.transports._logic.initHandler(a),d=function(i,s){var c=r._.error(f.noTransportOnInit);if(s=s||0,s>=i.length){s===0?a.log("No transports supported by the server were selected."):s===1?a.log("No fallback transports were selected."):a.log("Fallback transports exhausted.");n(a).triggerHandler(u.onError,[c]);y.reject(c);a.stop();return}if(a.state!==r.connectionState.disconnected){var p=i[s],h=r.transports[p],v=function(){d(i,s+1)};a.transport=h;try{a._.initHandler.start(h,function(){var i=r._.firefoxMajorVersion(t.navigator.userAgent)>=11,f=!!a.withCredentials&&i;a.log("The start request succeeded. Transitioning to the connected state.");l(a)&&r.transports._logic.monitorKeepAlive(a);r.transports._logic.startHeartbeat(a);r._.configurePingInterval(a);o(a,r.connectionState.connecting,r.connectionState.connected)||a.log("WARNING! The connection was not in the connecting state.");a._.connectingMessageBuffer.drain();n(a).triggerHandler(u.onStart);e.bind("unload",function(){a.log("Window unloading, stopping the connection.");a.stop(f)});i&&e.bind("beforeunload",function(){t.setTimeout(function(){a.stop(f)},0)})},v)}catch(w){a.log(h.name+" transport threw '"+w.message+"' when attempting to start.");v()}}},k=a.url+"/negotiate",g=function(t,i){var e=r._.error(f.errorOnNegotiate,t,i._.negotiateRequest);n(i).triggerHandler(u.onError,e);y.reject(e);i.stop()},n(a).triggerHandler(u.onStarting),k=r.transports._logic.prepareQueryString(a,k),a.log("Negotiating with '"+k+"'."),a._.negotiateRequest=r.transports._logic.ajax(a,{url:k,error:function(n,t){t!==c?g(n,a):y.reject(r._.error(f.stoppedWhileNegotiating,null,a._.negotiateRequest))},success:function(t){var i,e,h,o=[],s=[];try{i=a._parseResponse(t)}catch(c){g(r._.error(f.errorParsingNegotiateResponse,c),a);return}if(e=a._.keepAliveData,a.appRelativeUrl=i.Url,a.id=i.ConnectionId,a.token=i.ConnectionToken,a.webSocketServerUrl=i.WebSocketServerUrl,a._.pollTimeout=i.ConnectionTimeout*1e3+1e4,a.disconnectTimeout=i.DisconnectTimeout*1e3,a._.totalTransportConnectTimeout=a.transportConnectTimeout+i.TransportConnectTimeout*1e3,i.KeepAliveTimeout?(e.activated=!0,e.timeout=i.KeepAliveTimeout*1e3,e.timeoutWarning=e.timeout*a.keepAliveWarnAt,a._.beatInterval=(e.timeout-e.timeoutWarning)/3):e.activated=!1,a.reconnectWindow=a.disconnectTimeout+(e.timeout||0),!i.ProtocolVersion||i.ProtocolVersion!==a.clientProtocol){h=r._.error(r._.format(f.protocolIncompatible,a.clientProtocol,i.ProtocolVersion));n(a).triggerHandler(u.onError,[h]);y.reject(h);return}n.each(r.transports,function(n){if(n.indexOf("_")===0||n==="webSockets"&&!i.TryWebSockets)return!0;s.push(n)});n.isArray(v.transport)?n.each(v.transport,function(t,i){n.inArray(i,s)>=0&&o.push(i)}):v.transport==="auto"?o=s:n.inArray(v.transport,s)>=0&&o.push(v.transport);d(o)}}),y.promise())},starting:function(t){var i=this;return n(i).bind(u.onStarting,function(){t.call(i)}),i},send:function(n){var t=this;if(t.state===r.connectionState.disconnected)throw new Error("SignalR: Connection must be started before data can be sent. Call .start() before .send()");if(t.state===r.connectionState.connecting)throw new Error("SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.");return t.transport.send(t,n),t},received:function(t){var i=this;return n(i).bind(u.onReceived,function(n,r){t.call(i,r)}),i},stateChanged:function(t){var i=this;return n(i).bind(u.onStateChanged,function(n,r){t.call(i,r)}),i},error:function(t){var i=this;return n(i).bind(u.onError,function(n,r,u){i.lastError=r;t.call(i,r,u)}),i},disconnected:function(t){var i=this;return n(i).bind(u.onDisconnect,function(){t.call(i)}),i},connectionSlow:function(t){var i=this;return n(i).bind(u.onConnectionSlow,function(){t.call(i)}),i},reconnecting:function(t){var i=this;return n(i).bind(u.onReconnecting,function(){t.call(i)}),i},reconnected:function(t){var i=this;return n(i).bind(u.onReconnect,function(){t.call(i)}),i},stop:function(i,h){var a=this,v=a._deferral;if(a._.deferredStartHandler&&e.unbind("load",a._.deferredStartHandler),delete a._.config,delete a._.deferredStartHandler,!s&&(!a._.config||a._.config.waitForPageLoad===!0)){a.log("Stopping connection prior to negotiate.");v&&v.reject(r._.error(f.stoppedWhileLoading));return}if(a.state!==r.connectionState.disconnected)return a.log("Stopping connection."),o(a,a.state,r.connectionState.disconnected),t.clearTimeout(a._.beatHandle),t.clearInterval(a._.pingIntervalId),a.transport&&(a.transport.stop(a),h!==!1&&a.transport.abort(a,i),l(a)&&r.transports._logic.stopMonitoringKeepAlive(a),a.transport=null),a._.negotiateRequest&&(a._.negotiateRequest.abort(c),delete a._.negotiateRequest),a._.initHandler&&a._.initHandler.stop(),n(a).triggerHandler(u.onDisconnect),delete a._deferral,delete a.messageId,delete a.groupsToken,delete a.id,delete a._.pingIntervalId,delete a._.lastMessageAt,delete a._.lastActiveAt,a._.connectingMessageBuffer.clear(),a},log:function(n){v(n,this.logging)}};r.fn.init.prototype=r.fn;r.noConflict=function(){return n.connection===r&&(n.connection=h),r};n.connection&&(h=n.connection);n.connection=n.signalR=r})(window.jQuery,window),function(n,t,i){function s(n){n._.keepAliveData.monitoring&&l(n);u.markActive(n)&&(n._.beatHandle=t.setTimeout(function(){s(n)},n._.beatInterval))}function l(t){var i=t._.keepAliveData,u;t.state===r.connectionState.connected&&(u=(new Date).getTime()-t._.lastMessageAt,u>=i.timeout?(t.log("Keep alive timed out. Notifying transport that connection has been lost."),t.transport.lostConnection(t)):u>=i.timeoutWarning?i.userNotified||(t.log("Keep alive has been missed, connection may be dead/slow."),n(t).triggerHandler(f.onConnectionSlow),i.userNotified=!0):i.userNotified=!1)}function e(n,t){var i=n.url+t;return n.transport&&(i+="?transport="+n.transport.name),u.prepareQueryString(n,i)}function h(n){this.connection=n;this.startRequested=!1;this.startCompleted=!1;this.connectionStopped=!1}var r=n.signalR,f=n.signalR.events,c=n.signalR.changeState,o="__Start Aborted__",u;r.transports={};h.prototype={start:function(n,r,u){var f=this,e=f.connection,o=!1;if(f.startRequested||f.connectionStopped){e.log("WARNING! "+n.name+" transport cannot be started. Initialization ongoing or completed.");return}e.log(n.name+" transport starting.");f.transportTimeoutHandle=t.setTimeout(function(){o||(o=!0,e.log(n.name+" transport timed out when trying to connect."),f.transportFailed(n,i,u))},e._.totalTransportConnectTimeout);n.start(e,function(){o||f.initReceived(n,r)},function(t){return o||(o=!0,f.transportFailed(n,t,u)),!f.startCompleted||f.connectionStopped})},stop:function(){this.connectionStopped=!0;t.clearTimeout(this.transportTimeoutHandle);r.transports._logic.tryAbortStartRequest(this.connection)},initReceived:function(n,i){var u=this,f=u.connection;if(u.startRequested){f.log("WARNING! The client received multiple init messages.");return}u.connectionStopped||(u.startRequested=!0,t.clearTimeout(u.transportTimeoutHandle),f.log(n.name+" transport connected. Initiating start request."),r.transports._logic.ajaxStart(f,function(){u.startCompleted=!0;i()}))},transportFailed:function(i,u,e){var o=this.connection,h=o._deferral,s;this.connectionStopped||(t.clearTimeout(this.transportTimeoutHandle),this.startRequested?this.startCompleted||(s=r._.error(r.resources.errorDuringStartRequest,u),o.log(i.name+" transport failed during the start request. Stopping the connection."),n(o).triggerHandler(f.onError,[s]),h&&h.reject(s),o.stop()):(i.stop(o),o.log(i.name+" transport failed to connect. Attempting to fall back."),e()))}};u=r.transports._logic={ajax:function(t,i){return n.ajax(n.extend(!0,{},n.signalR.ajaxDefaults,{type:"GET",data:{},xhrFields:{withCredentials:t.withCredentials},contentType:t.contentType,dataType:t.ajaxDataType},i))},pingServer:function(t){var e,f,i=n.Deferred();return t.transport?(e=t.url+"/ping",e=u.addQs(e,t.qs),f=u.ajax(t,{url:e,success:function(n){var u;try{u=t._parseResponse(n)}catch(e){i.reject(r._.transportError(r.resources.pingServerFailedParse,t.transport,e,f));t.stop();return}u.Response==="pong"?i.resolve():i.reject(r._.transportError(r._.format(r.resources.pingServerFailedInvalidResponse,n),t.transport,null,f))},error:function(n){n.status===401||n.status===403?(i.reject(r._.transportError(r._.format(r.resources.pingServerFailedStatusCode,n.status),t.transport,n,f)),t.stop()):i.reject(r._.transportError(r.resources.pingServerFailed,t.transport,n,f))}})):i.reject(r._.transportError(r.resources.noConnectionTransport,t.transport)),i.promise()},prepareQueryString:function(n,i){var r;return r=u.addQs(i,"clientProtocol="+n.clientProtocol),r=u.addQs(r,n.qs),n.token&&(r+="&connectionToken="+t.encodeURIComponent(n.token)),n.data&&(r+="&connectionData="+t.encodeURIComponent(n.data)),r},addQs:function(t,i){var r=t.indexOf("?")!==-1?"&":"?",u;if(!i)return t;if(typeof i=="object")return t+r+n.param(i);if(typeof i=="string")return u=i.charAt(0),(u==="?"||u==="&")&&(r=""),t+r+i;throw new Error("Query string property must be either a string or object.");},getUrl:function(n,i,r,f,e){var h=i==="webSockets"?"":n.baseUrl,o=h+n.appRelativeUrl,s="transport="+i;return!e&&n.groupsToken&&(s+="&groupsToken="+t.encodeURIComponent(n.groupsToken)),r?(o+=f?"/poll":"/reconnect",!e&&n.messageId&&(s+="&messageId="+t.encodeURIComponent(n.messageId))):o+="/connect",o+="?"+s,o=u.prepareQueryString(n,o),e||(o+="&tid="+Math.floor(Math.random()*11)),o},maximizePersistentResponse:function(n){return{MessageId:n.C,Messages:n.M,Initialized:typeof n.S!="undefined"?!0:!1,ShouldReconnect:typeof n.T!="undefined"?!0:!1,LongPollDelay:n.L,GroupsToken:n.G}},updateGroups:function(n,t){t&&(n.groupsToken=t)},stringifySend:function(n,t){return typeof t=="string"||typeof t=="undefined"||t===null?t:n.json.stringify(t)},ajaxSend:function(t,i){var h=u.stringifySend(t,i),c=e(t,"/send"),o,s=function(t,u){n(u).triggerHandler(f.onError,[r._.transportError(r.resources.sendFailed,u.transport,t,o),i])};return o=u.ajax(t,{url:c,type:t.ajaxDataType==="jsonp"?"GET":"POST",contentType:r._.defaultContentType,data:{data:h},success:function(n){var i;if(n){try{i=t._parseResponse(n)}catch(r){s(r,t);t.stop();return}u.triggerReceived(t,i)}},error:function(n,i){i!=="abort"&&i!=="parsererror"&&s(n,t)}})},ajaxAbort:function(n,t){if(typeof n.transport!="undefined"){t=typeof t=="undefined"?!0:t;var i=e(n,"/abort");u.ajax(n,{url:i,async:t,timeout:1e3,type:"POST"});n.log("Fired ajax abort async = "+t+".")}},ajaxStart:function(t,i){var h=function(n){var i=t._deferral;i&&i.reject(n)},s=function(i){t.log("The start request failed. Stopping the connection.");n(t).triggerHandler(f.onError,[i]);h(i);t.stop()};t._.startRequest=u.ajax(t,{url:e(t,"/start"),success:function(n,u,f){var e;try{e=t._parseResponse(n)}catch(o){s(r._.error(r._.format(r.resources.errorParsingStartResponse,n),o,f));return}e.Response==="started"?i():s(r._.error(r._.format(r.resources.invalidStartResponse,n),null,f))},error:function(n,i,u){i!==o?s(r._.error(r.resources.errorDuringStartRequest,u,n)):(t.log("The start request aborted because connection.stop() was called."),h(r._.error(r.resources.stoppedDuringStartRequest,null,n)))}})},tryAbortStartRequest:function(n){n._.startRequest&&(n._.startRequest.abort(o),delete n._.startRequest)},tryInitialize:function(n,t){n.Initialized&&t()},triggerReceived:function(t,i){t._.connectingMessageBuffer.tryBuffer(i)||n(t).triggerHandler(f.onReceived,[i])},processMessages:function(t,i,r){var f;u.markLastMessage(t);i&&(f=u.maximizePersistentResponse(i),u.updateGroups(t,f.GroupsToken),f.MessageId&&(t.messageId=f.MessageId),f.Messages&&(n.each(f.Messages,function(n,i){u.triggerReceived(t,i)}),u.tryInitialize(f,r)))},monitorKeepAlive:function(t){var i=t._.keepAliveData;i.monitoring?t.log("Tried to monitor keep alive but it's already being monitored."):(i.monitoring=!0,u.markLastMessage(t),t._.keepAliveData.reconnectKeepAliveUpdate=function(){u.markLastMessage(t)},n(t).bind(f.onReconnect,t._.keepAliveData.reconnectKeepAliveUpdate),t.log("Now monitoring keep alive with a warning timeout of "+i.timeoutWarning+", keep alive timeout of "+i.timeout+" and disconnecting timeout of "+t.disconnectTimeout))},stopMonitoringKeepAlive:function(t){var i=t._.keepAliveData;i.monitoring&&(i.monitoring=!1,n(t).unbind(f.onReconnect,t._.keepAliveData.reconnectKeepAliveUpdate),t._.keepAliveData={},t.log("Stopping the monitoring of the keep alive."))},startHeartbeat:function(n){n._.lastActiveAt=(new Date).getTime();s(n)},markLastMessage:function(n){n._.lastMessageAt=(new Date).getTime()},markActive:function(n){return u.verifyLastActive(n)?(n._.lastActiveAt=(new Date).getTime(),!0):!1},isConnectedOrReconnecting:function(n){return n.state===r.connectionState.connected||n.state===r.connectionState.reconnecting},ensureReconnectingState:function(t){return c(t,r.connectionState.connected,r.connectionState.reconnecting)===!0&&n(t).triggerHandler(f.onReconnecting),t.state===r.connectionState.reconnecting},clearReconnectTimeout:function(n){n&&n._.reconnectTimeout&&(t.clearTimeout(n._.reconnectTimeout),delete n._.reconnectTimeout)},verifyLastActive:function(t){if((new Date).getTime()-t._.lastActiveAt>=t.reconnectWindow){var i=r._.format(r.resources.reconnectWindowTimeout,new Date(t._.lastActiveAt),t.reconnectWindow);return t.log(i),n(t).triggerHandler(f.onError,[r._.error(i,"TimeoutException")]),t.stop(!1,!1),!1}return!0},reconnect:function(n,i){var f=r.transports[i];if(u.isConnectedOrReconnecting(n)&&!n._.reconnectTimeout){if(!u.verifyLastActive(n))return;n._.reconnectTimeout=t.setTimeout(function(){u.verifyLastActive(n)&&(f.stop(n),u.ensureReconnectingState(n)&&(n.log(i+" reconnecting."),f.start(n)))},n.reconnectDelay)}},handleParseFailure:function(t,i,u,e,o){var s=r._.transportError(r._.format(r.resources.parseFailed,i),t.transport,u,o);e&&e(s)?t.log("Failed to parse server response while attempting to connect."):(n(t).triggerHandler(f.onError,[s]),t.stop())},initHandler:function(n){return new h(n)},foreverFrame:{count:0,connections:{}}}}(window.jQuery,window),function(n,t){var r=n.signalR,u=n.signalR.events,f=n.signalR.changeState,i=r.transports._logic;r.transports.webSockets={name:"webSockets",supportsKeepAlive:function(){return!0},send:function(t,f){var e=i.stringifySend(t,f);try{t.socket.send(e)}catch(o){n(t).triggerHandler(u.onError,[r._.transportError(r.resources.webSocketsInvalidState,t.transport,o,t.socket),f])}},start:function(e,o,s){var h,c=!1,l=this,a=!o,v=n(e);if(!t.WebSocket){s();return}e.socket||(h=e.webSocketServerUrl?e.webSocketServerUrl:e.wsProtocol+e.host,h+=i.getUrl(e,this.name,a),e.log("Connecting to websocket endpoint '"+h+"'."),e.socket=new t.WebSocket(h),e.socket.onopen=function(){c=!0;e.log("Websocket opened.");i.clearReconnectTimeout(e);f(e,r.connectionState.reconnecting,r.connectionState.connected)===!0&&v.triggerHandler(u.onReconnect)},e.socket.onclose=function(t){var i;this===e.socket&&(c&&typeof t.wasClean!="undefined"&&t.wasClean===!1?(i=r._.transportError(r.resources.webSocketClosed,e.transport,t),e.log("Unclean disconnect from websocket: "+(t.reason||"[no reason given]."))):e.log("Websocket closed."),s&&s(i)||(i&&n(e).triggerHandler(u.onError,[i]),l.reconnect(e)))},e.socket.onmessage=function(t){var r;try{r=e._parseResponse(t.data)}catch(u){i.handleParseFailure(e,t.data,u,s,t);return}r&&(n.isEmptyObject(r)||r.M?i.processMessages(e,r,o):i.triggerReceived(e,r))})},reconnect:function(n){i.reconnect(n,this.name)},lostConnection:function(n){this.reconnect(n)},stop:function(n){i.clearReconnectTimeout(n);n.socket&&(n.log("Closing the Websocket."),n.socket.close(),n.socket=null)},abort:function(n,t){i.ajaxAbort(n,t)}}}(window.jQuery,window),function(n,t){var i=n.signalR,u=n.signalR.events,e=n.signalR.changeState,r=i.transports._logic,f=function(n){t.clearTimeout(n._.reconnectAttemptTimeoutHandle);delete n._.reconnectAttemptTimeoutHandle};i.transports.serverSentEvents={name:"serverSentEvents",supportsKeepAlive:function(){return!0},timeOut:3e3,start:function(o,s,h){var c=this,l=!1,a=n(o),v=!s,y;if(o.eventSource&&(o.log("The connection already has an event source. Stopping it."),o.stop()),!t.EventSource){h&&(o.log("This browser doesn't support SSE."),h());return}y=r.getUrl(o,this.name,v);try{o.log("Attempting to connect to SSE endpoint '"+y+"'.");o.eventSource=new t.EventSource(y,{withCredentials:o.withCredentials})}catch(p){o.log("EventSource failed trying to connect with error "+p.Message+".");h?h():(a.triggerHandler(u.onError,[i._.transportError(i.resources.eventSourceFailedToConnect,o.transport,p)]),v&&c.reconnect(o));return}v&&(o._.reconnectAttemptTimeoutHandle=t.setTimeout(function(){l===!1&&o.eventSource.readyState!==t.EventSource.OPEN&&c.reconnect(o)},c.timeOut));o.eventSource.addEventListener("open",function(){o.log("EventSource connected.");f(o);r.clearReconnectTimeout(o);l===!1&&(l=!0,e(o,i.connectionState.reconnecting,i.connectionState.connected)===!0&&a.triggerHandler(u.onReconnect))},!1);o.eventSource.addEventListener("message",function(n){var t;if(n.data!=="initialized"){try{t=o._parseResponse(n.data)}catch(i){r.handleParseFailure(o,n.data,i,h,n);return}r.processMessages(o,t,s)}},!1);o.eventSource.addEventListener("error",function(n){var r=i._.transportError(i.resources.eventSourceError,o.transport,n);this===o.eventSource&&(h&&h(r)||(o.log("EventSource readyState: "+o.eventSource.readyState+"."),n.eventPhase===t.EventSource.CLOSED?(o.log("EventSource reconnecting due to the server connection ending."),c.reconnect(o)):(o.log("EventSource error."),a.triggerHandler(u.onError,[r]))))},!1)},reconnect:function(n){r.reconnect(n,this.name)},lostConnection:function(n){this.reconnect(n)},send:function(n,t){r.ajaxSend(n,t)},stop:function(n){f(n);r.clearReconnectTimeout(n);n&&n.eventSource&&(n.log("EventSource calling close()."),n.eventSource.close(),n.eventSource=null,delete n.eventSource)},abort:function(n,t){r.ajaxAbort(n,t)}}}(window.jQuery,window),function(n,t){var r=n.signalR,e=n.signalR.events,o=n.signalR.changeState,i=r.transports._logic,u=function(){var n=t.document.createElement("iframe");return n.setAttribute("style","position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;"),n},f=function(){var i=null,f=1e3,n=0;return{prevent:function(){r._.ieVersion<=8&&(n===0&&(i=t.setInterval(function(){var n=u();t.document.body.appendChild(n);t.document.body.removeChild(n);n=null},f)),n++)},cancel:function(){n===1&&t.clearInterval(i);n>0&&n--}}}();r.transports.foreverFrame={name:"foreverFrame",supportsKeepAlive:function(){return!0},iframeClearThreshold:50,start:function(n,r,e){var l=this,s=i.foreverFrame.count+=1,h,o=u(),c=function(){n.log("Forever frame iframe finished loading and is no longer receiving messages.");e&&e()||l.reconnect(n)};if(t.EventSource){e&&(n.log("Forever Frame is not supported by SignalR on browsers with SSE support."),e());return}o.setAttribute("data-signalr-connection-id",n.id);f.prevent();h=i.getUrl(n,this.name);h+="&frameId="+s;t.document.documentElement.appendChild(o);n.log("Binding to iframe's load event.");o.addEventListener?o.addEventListener("load",c,!1):o.attachEvent&&o.attachEvent("onload",c);o.src=h;i.foreverFrame.connections[s]=n;n.frame=o;n.frameId=s;r&&(n.onSuccess=function(){n.log("Iframe transport started.");r()})},reconnect:function(n){var r=this;i.isConnectedOrReconnecting(n)&&i.verifyLastActive(n)&&t.setTimeout(function(){if(i.verifyLastActive(n)&&n.frame&&i.ensureReconnectingState(n)){var u=n.frame,t=i.getUrl(n,r.name,!0)+"&frameId="+n.frameId;n.log("Updating iframe src to '"+t+"'.");u.src=t}},n.reconnectDelay)},lostConnection:function(n){this.reconnect(n)},send:function(n,t){i.ajaxSend(n,t)},receive:function(t,u){var f,e,o;if(t.json!==t._originalJson&&(u=t._originalJson.stringify(u)),o=t._parseResponse(u),i.processMessages(t,o,t.onSuccess),t.state===n.signalR.connectionState.connected&&(t.frameMessageCount=(t.frameMessageCount||0)+1,t.frameMessageCount>r.transports.foreverFrame.iframeClearThreshold&&(t.frameMessageCount=0,f=t.frame.contentWindow||t.frame.contentDocument,f&&f.document&&f.document.body)))for(e=f.document.body;e.firstChild;)e.removeChild(e.firstChild)},stop:function(n){var r=null;if(f.cancel(),n.frame){if(n.frame.stop)n.frame.stop();else try{r=n.frame.contentWindow||n.frame.contentDocument;r.document&&r.document.execCommand&&r.document.execCommand("Stop")}catch(u){n.log("Error occured when stopping foreverFrame transport. Message = "+u.message+".")}n.frame.parentNode===t.document.body&&t.document.body.removeChild(n.frame);delete i.foreverFrame.connections[n.frameId];n.frame=null;n.frameId=null;delete n.frame;delete n.frameId;delete n.onSuccess;delete n.frameMessageCount;n.log("Stopping forever frame.")}},abort:function(n,t){i.ajaxAbort(n,t)},getConnection:function(n){return i.foreverFrame.connections[n]},started:function(t){o(t,r.connectionState.reconnecting,r.connectionState.connected)===!0&&n(t).triggerHandler(e.onReconnect)}}}(window.jQuery,window),function(n,t){var r=n.signalR,u=n.signalR.events,e=n.signalR.changeState,f=n.signalR.isDisconnecting,i=r.transports._logic;r.transports.longPolling={name:"longPolling",supportsKeepAlive:function(){return!1},reconnectDelay:3e3,start:function(o,s,h){var a=this,v=function(){v=n.noop;o.log("LongPolling connected.");s()},y=function(n){return h(n)?(o.log("LongPolling failed to connect."),!0):!1},c=o._,l=0,p=function(i){t.clearTimeout(c.reconnectTimeoutId);c.reconnectTimeoutId=null;e(i,r.connectionState.reconnecting,r.connectionState.connected)===!0&&(i.log("Raising the reconnect event"),n(i).triggerHandler(u.onReconnect))},w=36e5;o.pollXhr&&(o.log("Polling xhr requests already exists, aborting."),o.stop());o.messageId=null;c.reconnectTimeoutId=null;c.pollTimeoutId=t.setTimeout(function(){(function e(s,h){var g=s.messageId,nt=g===null,k=!nt,tt=!h,d=i.getUrl(s,a.name,k,tt,!0),b={};(s.messageId&&(b.messageId=s.messageId),s.groupsToken&&(b.groupsToken=s.groupsToken),f(s)!==!0)&&(o.log("Opening long polling request to '"+d+"'."),s.pollXhr=i.ajax(o,{xhrFields:{onprogress:function(){i.markLastMessage(o)}},url:d,type:"POST",contentType:r._.defaultContentType,data:b,timeout:o._.pollTimeout,success:function(r){var h,w=0,u,a;o.log("Long poll complete.");l=0;try{h=o._parseResponse(r)}catch(b){i.handleParseFailure(s,r,b,y,s.pollXhr);return}(c.reconnectTimeoutId!==null&&p(s),h&&(u=i.maximizePersistentResponse(h)),i.processMessages(s,h,v),u&&n.type(u.LongPollDelay)==="number"&&(w=u.LongPollDelay),f(s)!==!0)&&(a=u&&u.ShouldReconnect,!a||i.ensureReconnectingState(s))&&(w>0?c.pollTimeoutId=t.setTimeout(function(){e(s,a)},w):e(s,a))},error:function(f,h){var v=r._.transportError(r.resources.longPollFailed,o.transport,f,s.pollXhr);if(t.clearTimeout(c.reconnectTimeoutId),c.reconnectTimeoutId=null,h==="abort"){o.log("Aborted xhr request.");return}if(!y(v)){if(l++,o.state!==r.connectionState.reconnecting&&(o.log("An error occurred using longPolling. Status = "+h+". Response = "+f.responseText+"."),n(s).triggerHandler(u.onError,[v])),(o.state===r.connectionState.connected||o.state===r.connectionState.reconnecting)&&!i.verifyLastActive(o))return;if(!i.ensureReconnectingState(s))return;c.pollTimeoutId=t.setTimeout(function(){e(s,!0)},a.reconnectDelay)}}}),k&&h===!0&&(c.reconnectTimeoutId=t.setTimeout(function(){p(s)},Math.min(1e3*(Math.pow(2,l)-1),w))))})(o)},250)},lostConnection:function(n){n.pollXhr&&n.pollXhr.abort("lostConnection")},send:function(n,t){i.ajaxSend(n,t)},stop:function(n){t.clearTimeout(n._.pollTimeoutId);t.clearTimeout(n._.reconnectTimeoutId);delete n._.pollTimeoutId;delete n._.reconnectTimeoutId;n.pollXhr&&(n.pollXhr.abort(),n.pollXhr=null,delete n.pollXhr)},abort:function(n,t){i.ajaxAbort(n,t)}}}(window.jQuery,window),function(n){function r(n){return n+e}function s(n,t,i){for(var f=n.length,u=[],r=0;r
-
-
+
+
diff --git a/src/SmokeTest.SignalrClient/packages.config b/src/SmokeTest.SignalrClient/packages.config
index 32f823fcb3..5de980e825 100644
--- a/src/SmokeTest.SignalrClient/packages.config
+++ b/src/SmokeTest.SignalrClient/packages.config
@@ -3,7 +3,7 @@
-
+