signalr
Version:
Microsoft ASP.NET SignalR JavaScript Client
1,049 lines (863 loc) • 140 kB
JavaScript
/* jquery.signalR.core.js */
/*global window:false */
/*!
* ASP.NET SignalR JavaScript Library 2.4.3
* http://signalr.net/
*
* Copyright (c) .NET Foundation. All rights reserved.
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
*
*/
/// <reference path="Scripts/jquery-1.6.4.js" />
/// <reference path="jquery.signalR.version.js" />
(function ($, window, undefined) {
var resources = {
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.",
errorRedirectionExceedsLimit: "Negotiate redirection limit exceeded.",
errorDuringStartRequest: "Error during start request. Stopping the connection.",
errorFromServer: "Error message received from the server: '{0}'.",
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}.",
aspnetCoreSignalrServer: "Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.",
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.",
jsonpNotSupportedWithAccessToken: "The JSONP protocol does not support connections that require a Bearer token to connect, such as the Azure SignalR Service."
};
if (typeof ($) !== "function") {
// no jQuery!
throw new Error(resources.nojQuery);
}
var signalR,
_connection,
_pageLoaded = (window.document.readyState === "complete"),
_pageWindow = $(window),
_negotiateAbortText = "__Negotiate Aborted__",
events = {
onStart: "onStart",
onStarting: "onStarting",
onReceived: "onReceived",
onError: "onError",
onConnectionSlow: "onConnectionSlow",
onReconnecting: "onReconnecting",
onReconnect: "onReconnect",
onStateChanged: "onStateChanged",
onDisconnect: "onDisconnect"
},
ajaxDefaults = {
processData: true,
timeout: null,
async: true,
global: false,
cache: false
},
log = function (msg, logging) {
if (logging === false) {
return;
}
var m;
if (typeof (window.console) === "undefined") {
return;
}
m = "[" + new Date().toTimeString() + "] SignalR: " + msg;
if (window.console.debug) {
window.console.debug(m);
} else if (window.console.log) {
window.console.log(m);
}
},
changeState = function (connection, expectedState, newState) {
if (expectedState === connection.state) {
connection.state = newState;
$(connection).triggerHandler(events.onStateChanged, [{ oldState: expectedState, newState: newState }]);
return true;
}
return false;
},
isDisconnecting = function (connection) {
return connection.state === signalR.connectionState.disconnected;
},
supportsKeepAlive = function (connection) {
return connection._.keepAliveData.activated &&
connection.transport.supportsKeepAlive(connection);
},
configureStopReconnectingTimeout = function (connection) {
var stopReconnectingTimeout,
onReconnectTimeout;
// Check if this connection has already been configured to stop reconnecting after a specified timeout.
// Without this check if a connection is stopped then started events will be bound multiple times.
if (!connection._.configuredStopReconnectingTimeout) {
onReconnectTimeout = function (connection) {
var message = signalR._.format(signalR.resources.reconnectTimeout, connection.disconnectTimeout);
connection.log(message);
$(connection).triggerHandler(events.onError, [signalR._.error(message, /* source */ "TimeoutException")]);
connection.stop(/* async */ false, /* notifyServer */ false);
};
connection.reconnecting(function () {
var connection = this;
// Guard against state changing in a previous user defined even handler
if (connection.state === signalR.connectionState.reconnecting) {
stopReconnectingTimeout = window.setTimeout(function () { onReconnectTimeout(connection); }, connection.disconnectTimeout);
}
});
connection.stateChanged(function (data) {
if (data.oldState === signalR.connectionState.reconnecting) {
// Clear the pending reconnect timeout check
window.clearTimeout(stopReconnectingTimeout);
}
});
connection._.configuredStopReconnectingTimeout = true;
}
};
signalR = function (url, qs, logging) {
/// <summary>Creates a new SignalR connection for the given url</summary>
/// <param name="url" type="String">The URL of the long polling endpoint</param>
/// <param name="qs" type="Object">
/// [Optional] Custom querystring parameters to add to the connection URL.
/// If an object, every non-function member will be added to the querystring.
/// If a string, it's added to the QS as specified.
/// </param>
/// <param name="logging" type="Boolean">
/// [Optional] A flag indicating whether connection logging is enabled to the browser
/// console/log. Defaults to false.
/// </param>
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;
})(),
error: function (message, source, context) {
var e = new Error(message);
e.source = source;
if (typeof context !== "undefined") {
e.context = context;
}
return e;
},
transportError: function (message, transport, source, context) {
var e = this.error(message, source, context);
e.transport = transport ? transport.name : undefined;
return e;
},
format: function () {
/// <summary>Usage: format("Hi {0}, you are {1}!", "Foo", 100) </summary>
var s = arguments[0];
for (var i = 0; i < arguments.length - 1; i++) {
s = s.replace("{" + i + "}", arguments[i + 1]);
}
return s;
},
firefoxMajorVersion: function (userAgent) {
// Firefox user agents: http://useragentstring.com/pages/Firefox/
var matches = userAgent.match(/Firefox\/(\d+)/);
if (!matches || !matches.length || matches.length < 2) {
return 0;
}
return parseInt(matches[1], 10 /* radix */);
},
configurePingInterval: function (connection) {
var config = connection._.config,
onFail = function (error) {
$(connection).triggerHandler(events.onError, [error]);
};
if (config && !connection._.pingIntervalId && config.pingInterval) {
connection._.pingIntervalId = window.setInterval(function () {
signalR.transports._logic.pingServer(connection).fail(onFail);
}, config.pingInterval);
}
}
};
signalR.events = events;
signalR.resources = resources;
signalR.ajaxDefaults = ajaxDefaults;
signalR.changeState = changeState;
signalR.isDisconnecting = isDisconnecting;
signalR.connectionState = {
connecting: 0,
connected: 1,
reconnecting: 2,
disconnected: 4
};
signalR.hub = {
start: function () {
// This will get replaced with the real hub connection start method when hubs is referenced correctly
throw new Error("SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g. <script src='/signalr/js'></script>.");
}
};
// .on() was added in version 1.7.0, .load() was removed in version 3.0.0 so we fallback to .load() if .on() does
// not exist to not break existing applications
if (typeof _pageWindow.on === "function") {
_pageWindow.on("load", function () { _pageLoaded = true; });
}
else {
_pageWindow.load(function () { _pageLoaded = true; });
}
function validateTransport(requestedTransport, connection) {
/// <summary>Validates the requested transport by cross checking it with the pre-defined signalR.transports</summary>
/// <param name="requestedTransport" type="Object">The designated transports that the user has specified.</param>
/// <param name="connection" type="signalR">The connection that will be using the requested transports. Used for logging purposes.</param>
/// <returns type="Object" />
if ($.isArray(requestedTransport)) {
// Go through transport array and remove an "invalid" tranports
for (var i = requestedTransport.length - 1; i >= 0; i--) {
var transport = requestedTransport[i];
if ($.type(transport) !== "string" || !signalR.transports[transport]) {
connection.log("Invalid transport: " + transport + ", removing it from the transports list.");
requestedTransport.splice(i, 1);
}
}
// Verify we still have transports left, if we dont then we have invalid transports
if (requestedTransport.length === 0) {
connection.log("No transports remain within the specified transport array.");
requestedTransport = null;
}
} else if (!signalR.transports[requestedTransport] && requestedTransport !== "auto") {
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;
}
function getDefaultPort(protocol) {
if (protocol === "http:") {
return 80;
} else if (protocol === "https:") {
return 443;
}
}
function addDefaultPort(protocol, url) {
// Remove ports from url. We have to check if there's a / or end of line
// following the port in order to avoid removing ports such as 8080.
if (url.match(/:\d+$/)) {
return url;
} else {
return url + ":" + getDefaultPort(protocol);
}
}
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: {},
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,
beatHandle: null,
totalTransportConnectTimeout: 0, // This will be the sum of the TransportConnectTimeout sent in response to negotiate and connection.transportConnectTimeout
redirectQs: null
};
if (typeof (logging) === "boolean") {
this.logging = logging;
}
},
_parseResponse: function (response) {
var that = this;
if (!response) {
return response;
} else if (typeof response === "string") {
return that.json.parse(response);
} else {
return response;
}
},
_originalJson: window.JSON,
json: window.JSON,
isCrossDomain: function (url, against) {
/// <summary>Checks if url is cross domain</summary>
/// <param name="url" type="String">The base URL</param>
/// <param name="against" type="Object">
/// An optional argument to compare the URL against, if not specified it will be set to window.location.
/// If specified it must contain a protocol and a host property.
/// </param>
var link;
url = $.trim(url);
against = against || window.location;
if (url.indexOf("http") !== 0) {
return false;
}
// Create an anchor tag.
link = window.document.createElement("a");
link.href = url;
// When checking for cross domain we have to special case port 80 because the window.location will remove the
return link.protocol + addDefaultPort(link.protocol, link.host) !== against.protocol + addDefaultPort(against.protocol, against.host);
},
ajaxDataType: "text",
contentType: "application/json; charset=UTF-8",
logging: false,
state: signalR.connectionState.disconnected,
clientProtocol: "2.1",
// We want to support older servers since the 2.0 change is to support redirection results, which isn't
// really breaking in the protocol. So if a user updates their client to 2.0 protocol version there's
// no reason they can't still connect to a 1.5 server. The 2.1 protocol is sent by the client so the SignalR
// service knows the client will use they query string returned via the RedirectUrl for subsequent requests.
// It doesn't matter whether the server reflects back 2.1 or continues using 2.0 as the protocol version.
supportedProtocols: ["1.5", "2.0", "2.1"],
negotiateRedirectSupportedProtocols: ["2.0", "2.1"],
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
keepAliveWarnAt: 2 / 3, // Warn user of slow connection if we breach the X% mark of the keep alive timeout
start: function (options, callback) {
/// <summary>Starts the connection</summary>
/// <param name="options" type="Object">Options map</param>
/// <param name="callback" type="Function">A callback function to execute when the connection has started</param>
var connection = this,
config = {
pingInterval: 300000,
waitForPageLoad: true,
transport: "auto",
jsonp: false
},
initialize,
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"),
setConnectionUrl = function (connection, url) {
if (connection.url === url && connection.baseUrl) {
// when the url related properties are already set
return;
}
connection.url = url;
// Resolve the full url
parser.href = connection.url;
if (!parser.protocol || parser.protocol === ":") {
connection.protocol = window.document.location.protocol;
connection.host = parser.host || window.document.location.host;
} else {
connection.protocol = parser.protocol;
connection.host = parser.host;
}
connection.baseUrl = connection.protocol + "//" + connection.host;
// Set the websocket protocol
connection.wsProtocol = connection.protocol === "https:" ? "wss://" : "ws://";
// If the url is protocol relative, prepend the current windows protocol to the url.
if (connection.url.indexOf("//") === 0) {
connection.url = window.location.protocol + connection.url;
connection.log("Protocol relative URL detected, normalizing it to '" + connection.url + "'.");
}
if (connection.isCrossDomain(connection.url)) {
connection.log("Auto detected cross domain url.");
if (config.transport === "auto") {
// Cross-domain does not support foreverFrame
config.transport = ["webSockets", "serverSentEvents", "longPolling"];
}
if (typeof connection.withCredentials === "undefined") {
connection.withCredentials = true;
}
// Determine if jsonp is the only choice for negotiation, ajaxSend and ajaxAbort.
// i.e. if the browser doesn't supports CORS
// If it is, ignore any preference to the contrary, and switch to jsonp.
if (!$.support.cors) {
connection.ajaxDataType = "jsonp";
connection.log("Using jsonp because this browser doesn't support CORS.");
}
connection.contentType = signalR._.defaultContentType;
}
};
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;
} else if ($.type(options) === "object") {
$.extend(config, options);
if ($.type(config.callback) === "function") {
callback = config.callback;
}
}
config.transport = validateTransport(config.transport, connection);
// If the transport is invalid throw an error and abort start
if (!config.transport) {
throw new Error("SignalR: Invalid transport(s) specified, aborting start.");
}
connection._.config = config;
// 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
if (!_pageLoaded && config.waitForPageLoad === true) {
connection._.deferredStartHandler = function () {
connection.start(options, callback);
};
_pageWindow.bind("load", connection._.deferredStartHandler);
return deferred.promise();
}
// 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,
signalR.connectionState.disconnected,
signalR.connectionState.connecting) === false) {
// We're not connecting so try and transition into connecting.
// If we fail to transition then we're either in connected or reconnecting.
deferred.resolve(connection);
return deferred.promise();
}
configureStopReconnectingTimeout(connection);
// If jsonp with no/auto transport is specified, then set the transport to long polling
// since that is the only transport for which jsonp really makes sense.
// Some developers might actually choose to specify jsonp for same origin requests
// as demonstrated by Issue #623.
if (config.transport === "auto" && config.jsonp === true) {
config.transport = "longPolling";
}
connection.withCredentials = config.withCredentials;
// Save the original url so that we can reset it when we stop and restart the connection
connection._originalUrl = connection.url;
connection.ajaxDataType = config.jsonp ? "jsonp" : "text";
setConnectionUrl(connection, connection.url);
$(connection).bind(events.onStart, function (e, data) {
if ($.type(callback) === "function") {
callback.call(connection);
}
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, [noTransportError]);
deferred.reject(noTransportError);
// Stop the connection if it has connected and move it into the disconnected state
connection.stop();
return;
}
// The connection was aborted
if (connection.state === signalR.connectionState.disconnected) {
return;
}
var transportName = transports[index],
transport = signalR.transports[transportName],
onFallback = function () {
initialize(transports, index + 1);
};
connection.transport = transport;
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 = true;
connection.log("The start request succeeded. Transitioning to the connected state.");
if (supportsKeepAlive(connection)) {
signalR.transports._logic.monitorKeepAlive(connection);
}
if (connection._.keepAliveData.activated) {
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);
if (!changeState(connection,
signalR.connectionState.connecting,
signalR.connectionState.connected)) {
connection.log("WARNING! The connection was not in the connecting state.");
}
// Drain any incoming buffered messages (messages that came in prior to connect)
connection._.connectingMessageBuffer.drain();
$(connection).triggerHandler(events.onStart);
// wire the stop handler for when the user leaves the page
_pageWindow.bind("unload", function () {
connection.log("Window unloading, stopping the connection.");
connection.stop(asyncAbort);
});
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();
}
};
var url = connection.url + "/negotiate",
onFailed = function (error, connection) {
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 = function () {
var res,
redirects = 0,
MAX_REDIRECTS = 100,
keepAliveData,
protocolError,
transports = [],
supportedTransports = [],
negotiate = function (connection, onSuccess) {
var url = signalR.transports._logic.prepareQueryString(connection, connection.url + "/negotiate");
connection.log("Negotiating with '" + url + "'.");
var options = {
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: onSuccess
};
if (connection.accessToken) {
options.headers = { "Authorization": "Bearer " + connection.accessToken };
}
return signalR.transports._logic.ajax(connection, options);
},
callback = function (result) {
try {
res = connection._parseResponse(result);
} catch (error) {
onFailed(signalR._.error(resources.errorParsingNegotiateResponse, error), connection);
return;
}
// Check if the server is an ASP.NET Core app
if (res.availableTransports) {
protocolError = signalR._.error(resources.aspnetCoreSignalrServer);
$(connection).triggerHandler(events.onError, [protocolError]);
deferred.reject(protocolError);
return;
}
if (!res.ProtocolVersion || (connection.supportedProtocols.indexOf(res.ProtocolVersion) === -1)) {
protocolError = signalR._.error(signalR._.format(resources.protocolIncompatible, connection.clientProtocol, res.ProtocolVersion));
$(connection).triggerHandler(events.onError, [protocolError]);
deferred.reject(protocolError);
return;
}
// Check for a redirect response (which must have a ProtocolVersion of 2.0 or greater)
// ProtocolVersion 2.1 is the highest supported by the client, so we can just check for 2.0 or 2.1 for now
// instead of trying to do proper version string comparison in JavaScript.
if (connection.negotiateRedirectSupportedProtocols.indexOf(res.ProtocolVersion) !== -1) {
if (res.Error) {
protocolError = signalR._.error(signalR._.format(resources.errorFromServer, res.Error));
$(connection).triggerHandler(events.onError, [protocolError]);
deferred.reject(protocolError);
return;
}
else if (res.RedirectUrl) {
if (redirects === MAX_REDIRECTS) {
onFailed(signalR._.error(resources.errorRedirectionExceedsLimit), connection);
return;
}
if (config.transport === "auto") {
// Redirected connections do not support foreverFrame
config.transport = ["webSockets", "serverSentEvents", "longPolling"];
}
connection.log("Received redirect to: " + res.RedirectUrl);
connection.accessToken = res.AccessToken;
var splitUrlAndQs = res.RedirectUrl.split("?", 2);
setConnectionUrl(connection, splitUrlAndQs[0]);
// Update redirectQs with query string from only the most recent RedirectUrl.
connection._.redirectQs = splitUrlAndQs.length === 2 ? splitUrlAndQs[1] : null;
if (connection.ajaxDataType === "jsonp" && connection.accessToken) {
onFailed(signalR._.error(resources.jsonpNotSupportedWithAccessToken), connection);
return;
}
redirects++;
negotiate(connection, callback);
return;
}
}
keepAliveData = connection._.keepAliveData;
connection.appRelativeUrl = res.Url;
connection.id = res.ConnectionId;
connection.token = res.ConnectionToken;
connection.webSocketServerUrl = res.WebSocketServerUrl;
// 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
// Add the TransportConnectTimeout from the response to the transportConnectTimeout from the client to calculate the total timeout
connection._.totalTransportConnectTimeout = connection.transportConnectTimeout + res.TransportConnectTimeout * 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 force the connection into reconnecting converted to milliseconds
keepAliveData.timeout = res.KeepAliveTimeout * 1000;
// Timeout to designate when to warn the developer that the connection may be dead or is not responding.
keepAliveData.timeoutWarning = keepAliveData.timeout * connection.keepAliveWarnAt;
// 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;
}
connection.reconnectWindow = connection.disconnectTimeout + (keepAliveData.timeout || 0);
$.each(signalR.transports, function (key) {
if ((key.indexOf("_") === 0) || (key === "webSockets" && !res.TryWebSockets)) {
return true;
}
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 negotiate(connection, callback);
}();
return deferred.promise();
},
starting: function (callback) {
/// <summary>Adds a callback that will be invoked before anything is sent over the connection</summary>
/// <param name="callback" type="Function">A callback function to execute before the connection is fully instantiated.</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onStarting, function (e, data) {
callback.call(connection);
});
return connection;
},
send: function (data) {
/// <summary>Sends data over the connection</summary>
/// <param name="data" type="String">The data to send over the connection</param>
/// <returns type="signalR" />
var connection = this;
if (connection.state === signalR.connectionState.disconnected) {
// Connection hasn't been started yet
throw new Error("SignalR: Connection must be started before data can be sent. Call .start() before .send()");
}
if (connection.state === signalR.connectionState.connecting) {
// Connection hasn't been started yet
throw new Error("SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.");
}
connection.transport.send(connection, data);
// REVIEW: Should we return deferred here?
return connection;
},
received: function (callback) {
/// <summary>Adds a callback that will be invoked after anything is received over the connection</summary>
/// <param name="callback" type="Function">A callback function to execute when any data is received on the connection</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onReceived, function (e, data) {
callback.call(connection, data);
});
return connection;
},
stateChanged: function (callback) {
/// <summary>Adds a callback that will be invoked when the connection state changes</summary>
/// <param name="callback" type="Function">A callback function to execute when the connection state changes</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onStateChanged, function (e, data) {
callback.call(connection, data);
});
return connection;
},
error: function (callback) {
/// <summary>Adds a callback that will be invoked after an error occurs with the connection</summary>
/// <param name="callback" type="Function">A callback function to execute when an error occurs on the connection</param>
/// <returns type="signalR" />
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' and 'webSockets.send'.'sendData' is the original send payload.
callback.call(connection, errorData, sendData);
});
return connection;
},
disconnected: function (callback) {
/// <summary>Adds a callback that will be invoked when the client disconnects</summary>
/// <param name="callback" type="Function">A callback function to execute when the connection is broken</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onDisconnect, function (e, data) {
callback.call(connection);
});
return connection;
},
connectionSlow: function (callback) {
/// <summary>Adds a callback that will be invoked when the client detects a slow connection</summary>
/// <param name="callback" type="Function">A callback function to execute when the connection is slow</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onConnectionSlow, function (e, data) {
callback.call(connection);
});
return connection;
},
reconnecting: function (callback) {
/// <summary>Adds a callback that will be invoked when the underlying transport begins reconnecting</summary>
/// <param name="callback" type="Function">A callback function to execute when the connection enters a reconnecting state</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onReconnecting, function (e, data) {
callback.call(connection);
});
return connection;
},
reconnected: function (callback) {
/// <summary>Adds a callback that will be invoked when the underlying transport reconnects</summary>
/// <param name="callback" type="Function">A callback function to execute when the connection is restored</param>
/// <returns type="signalR" />
var connection = this;
$(connection).bind(events.onReconnect, function (e, data) {
callback.call(connection);
});
return connection;
},
stop: function (async, notifyServer) {
/// <summary>Stops listening</summary>
/// <param name="async" type="Boolean">Whether or not to asynchronously abort the connection</param>
/// <param name="notifyServer" type="Boolean">Whether we want to notify the server that we are aborting the connection</param>
/// <returns type="signalR" />
var connection = this,
// Save deferral because this is always cleaned up
deferral = connection._deferral;
// Verify that we've bound a load event.
if (connection._.deferredStartHandler) {
// Unbind the event.
_pageWindow.unbind("load", connection._.deferredStartHandler);
}
// Always clean up private non-timeout based state.
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 && (!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(signalR._.error(resources.stoppedWhileLoading));
}
// Short-circuit because the start has not been fully started.
return;
}
if (connection.state === signalR.connectionState.disconnected) {
return;
}
connection.log("Stopping connection.");
// Clear this no matter what
window.clearTimeout(connection._.beatHandle);
window.clearInterval(connection._.pingIntervalId);
if (connection.transport) {
connection.transport.stop(connection);
if (notifyServer !== false) {
connection.transport.abort(connection, async);
}
if (supportsKeepAlive(connection)) {
signalR.transports._logic.stopMonitoringKeepAlive(connection);
}
connection.transport = null;
}
if (connection._.negotiateRequest) {
// If the negotiation request has already completed this will noop.
connection._.negotiateRequest.abort(_negotiateAbortText);
delete connection._.negotiateRequest;
}
// Ensure that initHandler.stop() is called before connection._deferral is deleted
if (connection._.initHandler) {
connection._.initHandler.stop();
}
delete connection._deferral;
delete connection.messageId;
delete connection.groupsToken;
delete connection.id;
delete connection._.pingIntervalId;
delete connection._.lastMessageAt;
delete connection._.lastActiveAt;
// Clear out our message buffer
connection._.connectingMessageBuffer.clear();
// Clean up this event
$(connection).unbind(events.onStart);
// Reset the URL and clear the access token
de