UNPKG

signalr

Version:

Microsoft ASP.NET SignalR JavaScript Client

1,049 lines (863 loc) 140 kB
/* 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