diff --git a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js
index 04f59274..d81abe46 100644
--- a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js
+++ b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js
@@ -1,8 +1,8 @@
-///#source 1 1 /ClientSource/Scripts/Modules/jQuery-SignalR/jquery.signalR-1.0.1.js
+///#source 1 1 /ClientSource/Scripts/Modules/jQuery-SignalR/jquery.signalR-1.1.0.js
/* jquery.signalR.core.js */
/*global window:false */
/*!
- * ASP.NET SignalR JavaScript Library v1.0.1
+ * ASP.NET SignalR JavaScript Library v1.1.0
* http://signalr.net/
*
* Copyright Microsoft Open Technologies, Inc. All rights reserved.
@@ -121,6 +121,26 @@
return new signalR.fn.init(url, qs, logging);
};
+ signalR._ = {
+ defaultContentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ ieVersion: (function () {
+ var version,
+ matches;
+
+ if (window.navigator.appName === 'Microsoft Internet Explorer') {
+ // Check if the user agent has the pattern "MSIE (one or more numbers).(one or more numbers)";
+ matches = /MSIE ([0-9]+\.[0-9]+)/.exec(window.navigator.userAgent);
+
+ if (matches) {
+ version = window.parseFloat(matches[1]);
+ }
+ }
+
+ // undefined value means not IE
+ return version;
+ })()
+ };
+
signalR.events = events;
signalR.changeState = changeState;
@@ -148,6 +168,7 @@
/// The designated transports that the user has specified.
/// The connection that will be using the requested transports. Used for logging purposes.
///
+
if ($.isArray(requestedTransport)) {
// Go through transport array and remove an "invalid" tranports
for (var i = requestedTransport.length - 1; i >= 0; i--) {
@@ -167,6 +188,12 @@
connection.log("Invalid transport: " + requestedTransport.toString());
requestedTransport = null;
}
+ else if (requestedTransport === "auto" && signalR._.ieVersion <= 8)
+ {
+ // If we're doing an auto transport and we're IE8 then force longPolling, #1764
+ return ["longPolling"];
+
+ }
return requestedTransport;
}
@@ -226,6 +253,8 @@
ajaxDataType: "json",
+ contentType: "application/json; charset=UTF-8",
+
logging: false,
state: signalR.connectionState.disconnected,
@@ -332,6 +361,8 @@
connection.log("Using jsonp because this browser doesn't support CORS");
}
}
+
+ connection.contentType = signalR._.defaultContentType;
}
connection.ajaxDataType = config.jsonp ? "jsonp" : "json";
@@ -348,7 +379,7 @@
if (index >= transports.length) {
if (!connection.transport) {
// 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.");
+ $(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.");
// Stop the connection if it has connected and move it into the disconnected state
connection.stop();
@@ -397,6 +428,7 @@
global: false,
cache: false,
type: "GET",
+ contentType: connection.contentType,
data: {},
dataType: connection.ajaxDataType,
error: function (error) {
@@ -437,7 +469,7 @@
}
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 + ".");
+ $(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;
}
@@ -709,6 +741,11 @@
}
}
+ function isConnectedOrReconnecting(connection) {
+ return connection.state === signalR.connectionState.connected ||
+ connection.state === signalR.connectionState.reconnecting;
+ }
+
signalR.transports._logic = {
pingServer: function (connection, transport) {
/// Pings the server
@@ -725,6 +762,7 @@
global: false,
cache: false,
type: "GET",
+ contentType: connection.contentType,
data: {},
dataType: connection.ajaxDataType,
success: function (data) {
@@ -746,7 +784,7 @@
addQs: function (url, connection) {
var appender = url.indexOf("?") !== -1 ? "&" : "?",
firstChar;
-
+
if (!connection.qs) {
return url;
}
@@ -768,7 +806,7 @@
throw new Error("Connections query string property must be either a string or object.");
},
- getUrl: function (connection, transport, reconnecting, appendReconnectUrl) {
+ getUrl: function (connection, transport, reconnecting, poll) {
/// Gets the url for making a GET based connect request
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
url = baseUrl + connection.appRelativeUrl,
@@ -783,11 +821,15 @@
}
if (!reconnecting) {
- url = url + "/connect";
+ url += "/connect";
} else {
- if (appendReconnectUrl) {
- url = url + "/reconnect";
+ if (poll) {
+ // longPolling transport specific
+ url += "/poll";
+ } else {
+ url += "/reconnect";
}
+
if (connection.messageId) {
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
}
@@ -822,6 +864,7 @@
url: url,
global: false,
type: connection.ajaxDataType === "jsonp" ? "GET" : "POST",
+ contentType: signalR._.defaultContentType,
dataType: connection.ajaxDataType,
data: {
data: data
@@ -859,6 +902,7 @@
timeout: 1000,
global: false,
type: "POST",
+ contentType: connection.contentType,
dataType: connection.ajaxDataType,
data: {}
});
@@ -895,8 +939,8 @@
this.updateGroups(connection, data.GroupsToken);
if (data.Messages) {
- $.each(data.Messages, function () {
- $connection.triggerHandler(events.onReceived, [this]);
+ $.each(data.Messages, function (index, message) {
+ $connection.triggerHandler(events.onReceived, [message]);
});
}
@@ -964,6 +1008,32 @@
return connection.state === signalR.connectionState.reconnecting;
},
+ clearReconnectTimeout: function (connection) {
+ if (connection && connection._.reconnectTimeout) {
+ window.clearTimeout(connection._.reconnectTimeout);
+ delete connection._.reconnectTimeout;
+ }
+ },
+
+ reconnect: function (connection, transportName) {
+ var transport = signalR.transports[transportName],
+ that = this;
+
+ // We should only set a reconnectTimeout if we are currently connected
+ // and a reconnectTimeout isn't already set.
+ if (isConnectedOrReconnecting(connection) && !connection._.reconnectTimeout) {
+
+ connection._.reconnectTimeout = window.setTimeout(function () {
+ transport.stop(connection);
+
+ if (that.ensureReconnectingState(connection)) {
+ connection.log(transportName + " reconnecting");
+ transport.start(connection);
+ }
+ }, connection.reconnectDelay);
+ }
+ },
+
foreverFrame: {
count: 0,
connections: {}
@@ -990,10 +1060,6 @@
supportsKeepAlive: true,
- attemptingReconnect: false,
-
- currentSocketID: 0,
-
send: function (connection, data) {
connection.socket.send(data);
},
@@ -1022,14 +1088,11 @@
connection.log("Connecting to websocket endpoint '" + url + "'");
connection.socket = new window.WebSocket(url);
- connection.socket.ID = ++that.currentSocketID;
connection.socket.onopen = function () {
opened = true;
connection.log("Websocket opened");
- if (that.attemptingReconnect) {
- that.attemptingReconnect = false;
- }
+ transportLogic.clearReconnectTimeout(connection);
if (onSuccess) {
onSuccess();
@@ -1044,7 +1107,7 @@
// 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.
- if (this.ID === that.currentSocketID) {
+ if (this === connection.socket) {
if (!opened) {
if (onFailed) {
onFailed();
@@ -1087,24 +1150,7 @@
},
reconnect: function (connection) {
- var that = this;
-
- if (connection.state !== signalR.connectionState.disconnected) {
- if (!that.attemptingReconnect) {
- that.attemptingReconnect = true;
- }
-
- window.setTimeout(function () {
- if (that.attemptingReconnect) {
- that.stop(connection);
- }
-
- if (transportLogic.ensureReconnectingState(connection)) {
- connection.log("Websocket reconnecting");
- that.start(connection);
- }
- }, connection.reconnectDelay);
- }
+ transportLogic.reconnect(connection, this.name);
},
lostConnection: function (connection) {
@@ -1113,6 +1159,9 @@
},
stop: function (connection) {
+ // Don't trigger a reconnect after stopping
+ transportLogic.clearReconnectTimeout(connection);
+
if (connection.socket !== null) {
connection.log("Closing the Websocket");
connection.socket.close();
@@ -1144,10 +1193,6 @@
supportsKeepAlive: true,
- reconnectTimeout: false,
-
- currentEventSourceID: 0,
-
timeOut: 3000,
start: function (connection, onSuccess, onFailed) {
@@ -1176,7 +1221,6 @@
try {
connection.log("Attempting to connect to SSE endpoint '" + url + "'");
connection.eventSource = new window.EventSource(url);
- connection.eventSource.ID = ++that.currentEventSourceID;
}
catch (e) {
connection.log("EventSource failed trying to connect with error " + e.Message);
@@ -1227,9 +1271,7 @@
window.clearTimeout(connectTimeOut);
}
- if (that.reconnectTimeout) {
- window.clearTimeout(that.reconnectTimeout);
- }
+ transportLogic.clearReconnectTimeout(connection);
if (opened === false) {
opened = true;
@@ -1258,7 +1300,7 @@
// 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.ID === that.currentEventSourceID) {
+ if (this === connection.eventSource) {
if (!opened) {
if (onFailed) {
onFailed();
@@ -1286,16 +1328,7 @@
},
reconnect: function (connection) {
- var that = this;
-
- that.reconnectTimeout = window.setTimeout(function () {
- that.stop(connection);
-
- if (transportLogic.ensureReconnectingState(connection)) {
- connection.log("EventSource reconnecting");
- that.start(connection);
- }
- }, connection.reconnectDelay);
+ transportLogic.reconnect(connection, this.name);
},
lostConnection: function (connection) {
@@ -1307,14 +1340,17 @@
},
stop: function (connection) {
+ // Don't trigger a reconnect after stopping
+ transportLogic.clearReconnectTimeout(connection);
+
if (connection && connection.eventSource) {
connection.log("EventSource calling close()");
- connection.eventSource.ID = null;
connection.eventSource.close();
connection.eventSource = null;
delete connection.eventSource;
}
},
+
abort: function (connection, async) {
transportLogic.ajaxAbort(connection, async);
}
@@ -1333,7 +1369,46 @@
var signalR = $.signalR,
events = $.signalR.events,
changeState = $.signalR.changeState,
- transportLogic = signalR.transports._logic;
+ transportLogic = signalR.transports._logic,
+ // Used to prevent infinite loading icon spins in older versions of ie
+ // We build this object inside a closure so we don't pollute the rest of
+ // the foreverFrame transport with unnecessary functions/utilities.
+ loadPreventer = (function () {
+ var loadingFixIntervalId = null,
+ loadingFixInterval = 1000,
+ attachedTo = 0;
+
+ return {
+ prevent: function () {
+ // Prevent additional iframe removal procedures from newer browsers
+ if (signalR._.ieVersion <= 8) {
+ // We only ever want to set the interval one time, so on the first attachedTo
+ if (attachedTo === 0) {
+ // Create and destroy iframe every 3 seconds to prevent loading icon, super hacky
+ loadingFixIntervalId = window.setInterval(function () {
+ var tempFrame = $("");
+
+ $("body").append(tempFrame);
+ tempFrame.remove();
+ tempFrame = null;
+ }, loadingFixInterval);
+ }
+
+ attachedTo++;
+ }
+ },
+ cancel: function () {
+ // Only clear the interval if there's only one more object that the loadPreventer is attachedTo
+ if (attachedTo === 1) {
+ window.clearInterval(loadingFixIntervalId);
+ }
+
+ if (attachedTo > 0) {
+ attachedTo--;
+ }
+ }
+ };
+ })();
signalR.transports.foreverFrame = {
name: "foreverFrame",
@@ -1357,6 +1432,9 @@
return;
}
+ // Start preventing loading icon
+ // This will only perform work if the loadPreventer is not attached to another connection.
+ loadPreventer.prevent();
// Build the url
url = transportLogic.getUrl(connection, this.name);
@@ -1435,6 +1513,10 @@
stop: function (connection) {
var cw = null;
+
+ // Stop attempting to prevent loading icon
+ loadPreventer.cancel();
+
if (connection.frame) {
if (connection.frame.stop) {
connection.frame.stop();
@@ -1540,7 +1622,23 @@
initialConnectedFired = true;
onSuccess();
connection.log("Longpolling connected");
- };
+ },
+ reconnectErrors = 0,
+ reconnectTimeoutId = null,
+ fireReconnected = function (instance) {
+ window.clearTimeout(reconnectTimeoutId);
+ reconnectTimeoutId = null;
+
+ if (changeState(connection,
+ signalR.connectionState.reconnecting,
+ signalR.connectionState.connected) === true) {
+ // Successfully reconnected!
+ connection.log("Raising the reconnect event");
+ $(instance).triggerHandler(events.onReconnect);
+ }
+ },
+ // 1 hour
+ maxFireReconnectedTimeout = 3600000;
if (connection.pollXhr) {
connection.log("Polling xhr requests already exists, aborting.");
@@ -1557,7 +1655,8 @@
var messageId = instance.messageId,
connect = (messageId === null),
reconnecting = !connect,
- url = transportLogic.getUrl(instance, that.name, reconnecting, raiseReconnect);
+ 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) {
@@ -1571,10 +1670,20 @@
cache: false,
type: "GET",
dataType: connection.ajaxDataType,
+ contentType: connection.contentType,
success: function (minData) {
var delay = 0,
data;
+ // Reset our reconnect errors so if we transition into a reconnecting state again we trigger
+ // reconnected quickly
+ reconnectErrors = 0;
+
+ // If there's currently a timeout to trigger reconnect, fire it now before processing messages
+ if (reconnectTimeoutId !== null) {
+ fireReconnected();
+ }
+
fireConnect();
if (minData) {
@@ -1607,11 +1716,21 @@
},
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(reconnectTimeoutId);
+ reconnectTimeoutId = null;
+
if (textStatus === "abort") {
connection.log("Aborted xhr requst.");
return;
}
+ // 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 + ". " + data.responseText);
$(instance).triggerHandler(events.onError, [data.responseText]);
@@ -1629,15 +1748,15 @@
}
});
+
// This will only ever pass after an error has occured via the poll ajax procedure.
if (reconnecting && raiseReconnect === true) {
- if (changeState(connection,
- signalR.connectionState.reconnecting,
- signalR.connectionState.connected) === true) {
- // Successfully reconnected!
- connection.log("Raising the reconnect event");
- $(instance).triggerHandler(events.onReconnect);
- }
+ // 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.
+ reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout));
}
}(connection));
@@ -1693,20 +1812,17 @@
return event + eventNamespace;
}
- // Array.prototype.map
- if (!Array.prototype.hasOwnProperty("map")) {
- Array.prototype.map = function (fun, thisp) {
- var arr = this,
- i,
- length = arr.length,
- result = [];
- for (i = 0; i < length; i += 1) {
- if (arr.hasOwnProperty(i)) {
- result[i] = fun.call(thisp, arr[i], i, arr);
- }
+ // Equivalent to Array.prototype.map
+ function map(arr, fun, thisp) {
+ var i,
+ length = arr.length,
+ result = [];
+ for (i = 0; i < length; i += 1) {
+ if (arr.hasOwnProperty(i)) {
+ result[i] = fun.call(thisp, arr[i], i, arr);
}
- return result;
- };
+ }
+ return result;
}
function getArgValue(a) {
@@ -1815,7 +1931,7 @@
var self = this,
args = $.makeArray(arguments).slice(1),
- argValues = args.map(getArgValue),
+ argValues = map(args, getArgValue),
data = { H: self.hubName, M: methodName, A: argValues, I: callbackId },
d = $.Deferred(),
callback = function (minResult) {
@@ -2004,6 +2120,6 @@
/*global window:false */
///
(function ($) {
- $.signalR.version = "1.0.1";
+ $.signalR.version = "1.1.0";
}(window.jQuery));
diff --git a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js.bundle b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js.bundle
index d56aa373..15db738b 100644
--- a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js.bundle
+++ b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.js.bundle
@@ -1,4 +1,4 @@
- /ClientSource/Scripts/Modules/jQuery-SignalR/jquery.signalR-1.0.1.js
+ /ClientSource/Scripts/Modules/jQuery-SignalR/jquery.signalR-1.1.0.js
\ No newline at end of file
diff --git a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.min.js b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.min.js
index c1a776b2..92a0495d 100644
--- a/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.min.js
+++ b/Disco.Web/ClientSource/Scripts/Modules/jQuery-SignalR.min.js
@@ -1,2 +1,2 @@
-(function(n,t){"use strict";function a(t,r){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"&&i.transports[f]||(r.log("Invalid transport: "+f+", removing it from the transports list."),t.splice(u,1));t.length===0&&(r.log("No transports remain within the specified transport array."),t=null)}else n.type(t)==="object"||i.transports[t]||t==="auto"||(r.log("Invalid transport: "+t.toString()),t=null);return t}function v(n){return n==="http:"?80:n==="https:"?443:void 0}function s(n,t){return t.match(/:\d+$/)?t:t+":"+v(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 i,e,o=t.document.readyState==="complete",f=n(t),r={onStart:"onStart",onStarting:"onStarting",onReceived:"onReceived",onError:"onError",onConnectionSlow:"onConnectionSlow",onReconnecting:"onReconnecting",onReconnect:"onReconnect",onStateChanged:"onStateChanged",onDisconnect:"onDisconnect"},h=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))}},u=function(t,i,u){return i===t.state?(t.state=u,n(t).triggerHandler(r.onStateChanged,[{oldState:i,newState:u}]),!0):!1},c=function(n){return n.state===i.connectionState.disconnected},l=function(n){var r,u;n._.configuredStopReconnectingTimeout||(u=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===i.connectionState.reconnecting&&(r=t.setTimeout(function(){u(n)},n.disconnectTimeout))}),n.stateChanged(function(n){n.oldState===i.connectionState.reconnecting&&t.clearTimeout(r)}),n._.configuredStopReconnectingTimeout=!0)};i=function(n,t,r){return new i.fn.init(n,t,r)},i.events=r,i.changeState=u,i.isDisconnecting=c,i.connectionState={connecting:0,connected:1,reconnecting:2,disconnected:4},i.hub={start:function(){throw new Error("SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g.