mirror of https://github.com/Radarr/Radarr
2194 lines
88 KiB
JavaScript
2194 lines
88 KiB
JavaScript
/* jquery.signalR.core.js */
|
|
/*global window:false */
|
|
/*!
|
|
* ASP.NET SignalR JavaScript Library v1.1.3
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
/// <reference path="Scripts/jquery-1.6.4.js" />
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
if (typeof ($) !== "function") {
|
|
// no jQuery!
|
|
throw new Error("SignalR: jQuery not found. Please ensure jQuery is referenced before the SignalR.js file.");
|
|
}
|
|
|
|
if (!window.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.");
|
|
}
|
|
|
|
var signalR,
|
|
_connection,
|
|
_pageLoaded = (window.document.readyState === "complete"),
|
|
_pageWindow = $(window),
|
|
|
|
events = {
|
|
onStart: "onStart",
|
|
onStarting: "onStarting",
|
|
onReceived: "onReceived",
|
|
onError: "onError",
|
|
onConnectionSlow: "onConnectionSlow",
|
|
onReconnecting: "onReconnecting",
|
|
onReconnect: "onReconnect",
|
|
onStateChanged: "onStateChanged",
|
|
onDisconnect: "onDisconnect"
|
|
},
|
|
|
|
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;
|
|
},
|
|
|
|
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) {
|
|
connection.log("Couldn't reconnect within the configured timeout (" + connection.disconnectTimeout + "ms), disconnecting.");
|
|
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;
|
|
})()
|
|
};
|
|
|
|
signalR.events = events;
|
|
|
|
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/hubs'></script>.");
|
|
}
|
|
};
|
|
|
|
_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(requestedTransport) !== "object" && ($.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 ($.type(requestedTransport) !== "object" && !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);
|
|
}
|
|
}
|
|
|
|
signalR.fn = signalR.prototype = {
|
|
init: function (url, qs, logging) {
|
|
this.url = url;
|
|
this.qs = qs;
|
|
this._ = {};
|
|
if (typeof (logging) === "boolean") {
|
|
this.logging = logging;
|
|
}
|
|
},
|
|
|
|
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);
|
|
if (url.indexOf("http") !== 0) {
|
|
return false;
|
|
}
|
|
|
|
against = against || window.location;
|
|
|
|
// 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: "json",
|
|
|
|
contentType: "application/json; charset=UTF-8",
|
|
|
|
logging: false,
|
|
|
|
state: signalR.connectionState.disconnected,
|
|
|
|
keepAliveData: {},
|
|
|
|
reconnectDelay: 2000,
|
|
|
|
disconnectTimeout: 30000, // This should be set by the server in response to the negotiate request (30s default)
|
|
|
|
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 = {
|
|
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");
|
|
|
|
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.");
|
|
}
|
|
|
|
// 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) {
|
|
_pageWindow.load(function () {
|
|
connection._deferral = deferred;
|
|
connection.start(options, callback);
|
|
});
|
|
return deferred.promise();
|
|
}
|
|
|
|
configureStopReconnectingTimeout(connection);
|
|
|
|
if (changeState(connection,
|
|
signalR.connectionState.disconnected,
|
|
signalR.connectionState.connecting) === false) {
|
|
// Already started, just return
|
|
deferred.resolve(connection);
|
|
return deferred.promise();
|
|
}
|
|
|
|
// Resolve the full url
|
|
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.protocol = parser.protocol;
|
|
connection.host = parser.host;
|
|
connection.baseUrl = parser.protocol + "//" + parser.host;
|
|
}
|
|
|
|
// Set the websocket protocol
|
|
connection.wsProtocol = connection.protocol === "https:" ? "wss://" : "ws://";
|
|
|
|
// 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";
|
|
}
|
|
|
|
if (this.isCrossDomain(connection.url)) {
|
|
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"];
|
|
}
|
|
|
|
// 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 (!config.jsonp) {
|
|
config.jsonp = !$.support.cors;
|
|
|
|
if (config.jsonp) {
|
|
connection.log("Using jsonp because this browser doesn't support CORS");
|
|
}
|
|
}
|
|
|
|
connection.contentType = signalR._.defaultContentType;
|
|
}
|
|
|
|
connection.ajaxDataType = config.jsonp ? "jsonp" : "json";
|
|
|
|
$(connection).bind(events.onStart, function (e, data) {
|
|
if ($.type(callback) === "function") {
|
|
callback.call(connection);
|
|
}
|
|
deferred.resolve(connection);
|
|
});
|
|
|
|
initialize = function (transports, index) {
|
|
index = index || 0;
|
|
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."]);
|
|
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();
|
|
}
|
|
return;
|
|
}
|
|
|
|
var transportName = transports[index],
|
|
transport = $.type(transportName) === "object" ? transportName : signalR.transports[transportName];
|
|
|
|
if (transportName.indexOf("_") === 0) {
|
|
// Private member
|
|
initialize(transports, index + 1);
|
|
return;
|
|
}
|
|
|
|
transport.start(connection, function () { // success
|
|
if (transport.supportsKeepAlive && connection.keepAliveData.activated) {
|
|
signalR.transports._logic.monitorKeepAlive(connection);
|
|
}
|
|
|
|
connection.transport = transport;
|
|
|
|
changeState(connection,
|
|
signalR.connectionState.connecting,
|
|
signalR.connectionState.connected);
|
|
|
|
$(connection).triggerHandler(events.onStart);
|
|
|
|
_pageWindow.unload(function () { // failure
|
|
connection.stop(false /* async */);
|
|
});
|
|
|
|
}, function () {
|
|
initialize(transports, index + 1);
|
|
});
|
|
};
|
|
|
|
var url = connection.url + "/negotiate";
|
|
|
|
url = signalR.transports._logic.addQs(url, connection);
|
|
|
|
connection.log("Negotiating with '" + url + "'.");
|
|
$.ajax({
|
|
url: url,
|
|
global: false,
|
|
cache: false,
|
|
type: "GET",
|
|
contentType: connection.contentType,
|
|
data: {},
|
|
dataType: connection.ajaxDataType,
|
|
error: function (error) {
|
|
$(connection).triggerHandler(events.onError, [error.responseText]);
|
|
deferred.reject("SignalR: Error during negotiation request: " + error.responseText);
|
|
// Stop the connection if negotiate failed
|
|
connection.stop();
|
|
},
|
|
success: function (res) {
|
|
var 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
|
|
|
|
|
|
// 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
|
|
keepAliveData.checkInterval = (keepAliveData.timeout - keepAliveData.timeoutWarning) / 3;
|
|
}
|
|
else {
|
|
keepAliveData.activated = false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
$(connection).triggerHandler(events.onStarting);
|
|
|
|
var transports = [],
|
|
supportedTransports = [];
|
|
|
|
$.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 ($.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;
|
|
}
|
|
initialize(transports);
|
|
}
|
|
});
|
|
|
|
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 each time data is sent on the connection</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, data) {
|
|
callback.call(connection, data);
|
|
});
|
|
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;
|
|
|
|
if (connection.state === signalR.connectionState.disconnected) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (connection.transport) {
|
|
if (notifyServer !== false) {
|
|
connection.transport.abort(connection, async);
|
|
}
|
|
|
|
if (connection.transport.supportsKeepAlive && connection.keepAliveData.activated) {
|
|
signalR.transports._logic.stopMonitoringKeepAlive(connection);
|
|
}
|
|
|
|
connection.transport.stop(connection);
|
|
connection.transport = null;
|
|
}
|
|
|
|
// Trigger the disconnect event
|
|
$(connection).triggerHandler(events.onDisconnect);
|
|
|
|
delete connection.messageId;
|
|
delete connection.groupsToken;
|
|
|
|
// Remove the ID and the deferral on stop, this is to ensure that if a connection is restarted it takes on a new id/deferral.
|
|
delete connection.id;
|
|
delete connection._deferral;
|
|
}
|
|
finally {
|
|
changeState(connection, connection.state, signalR.connectionState.disconnected);
|
|
}
|
|
|
|
return connection;
|
|
},
|
|
|
|
log: function (msg) {
|
|
log(msg, this.logging);
|
|
}
|
|
};
|
|
|
|
signalR.fn.init.prototype = signalR.fn;
|
|
|
|
signalR.noConflict = function () {
|
|
/// <summary>Reinstates the original value of $.connection and returns the signalR object for manual assignment</summary>
|
|
/// <returns type="signalR" />
|
|
if ($.connection === signalR) {
|
|
$.connection = _connection;
|
|
}
|
|
return signalR;
|
|
};
|
|
|
|
if ($.connection) {
|
|
_connection = $.connection;
|
|
}
|
|
|
|
$.connection = $.signalR = signalR;
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.transports.common.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.core.js" />
|
|
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
var signalR = $.signalR,
|
|
events = $.signalR.events,
|
|
changeState = $.signalR.changeState;
|
|
|
|
signalR.transports = {};
|
|
|
|
function checkIfAlive(connection) {
|
|
var keepAliveData = connection.keepAliveData,
|
|
diff,
|
|
timeElapsed;
|
|
|
|
// Only check if we're connected
|
|
if (connection.state === signalR.connectionState.connected) {
|
|
diff = new Date();
|
|
|
|
diff.setTime(diff - keepAliveData.lastKeepAlive);
|
|
timeElapsed = diff.getTime();
|
|
|
|
// Check if the keep alive has completely timed out
|
|
if (timeElapsed >= keepAliveData.timeout) {
|
|
connection.log("Keep alive timed out. Notifying transport that connection has been lost.");
|
|
|
|
// Notify transport that the connection has been lost
|
|
connection.transport.lostConnection(connection);
|
|
}
|
|
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 {
|
|
keepAliveData.userNotified = false;
|
|
}
|
|
}
|
|
|
|
// Verify we're monitoring the keep alive
|
|
// We don't want this as a part of the inner if statement above because we want keep alives to continue to be checked
|
|
// in the event that the server comes back online (if it goes offline).
|
|
if (keepAliveData.monitoring) {
|
|
window.setTimeout(function () {
|
|
checkIfAlive(connection);
|
|
}, keepAliveData.checkInterval);
|
|
}
|
|
}
|
|
|
|
function isConnectedOrReconnecting(connection) {
|
|
return connection.state === signalR.connectionState.connected ||
|
|
connection.state === signalR.connectionState.reconnecting;
|
|
}
|
|
|
|
signalR.transports._logic = {
|
|
pingServer: function (connection, transport) {
|
|
/// <summary>Pings the server</summary>
|
|
/// <param name="connection" type="signalr">Connection associated with the server ping</param>
|
|
/// <returns type="signalR" />
|
|
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
|
|
url = baseUrl + connection.appRelativeUrl + "/ping",
|
|
deferral = $.Deferred();
|
|
|
|
url = this.addQs(url, connection);
|
|
|
|
$.ajax({
|
|
url: url,
|
|
global: false,
|
|
cache: false,
|
|
type: "GET",
|
|
contentType: connection.contentType,
|
|
data: {},
|
|
dataType: connection.ajaxDataType,
|
|
success: function (data) {
|
|
if (data.Response === "pong") {
|
|
deferral.resolve();
|
|
}
|
|
else {
|
|
deferral.reject("SignalR: Invalid ping response when pinging server: " + (data.responseText || data.statusText));
|
|
}
|
|
},
|
|
error: function (data) {
|
|
deferral.reject("SignalR: Error pinging server: " + (data.responseText || data.statusText));
|
|
}
|
|
});
|
|
|
|
return deferral.promise();
|
|
},
|
|
|
|
addQs: function (url, connection) {
|
|
var appender = url.indexOf("?") !== -1 ? "&" : "?",
|
|
firstChar;
|
|
|
|
if (!connection.qs) {
|
|
return url;
|
|
}
|
|
|
|
if (typeof (connection.qs) === "object") {
|
|
return url + appender + $.param(connection.qs);
|
|
}
|
|
|
|
if (typeof (connection.qs) === "string") {
|
|
firstChar = connection.qs.charAt(0);
|
|
|
|
if (firstChar === "?" || firstChar === "&") {
|
|
appender = "";
|
|
}
|
|
|
|
return url + appender + connection.qs;
|
|
}
|
|
|
|
throw new Error("Connections query string property must be either a string or object.");
|
|
},
|
|
|
|
getUrl: function (connection, transport, reconnecting, poll) {
|
|
/// <summary>Gets the url for making a GET based connect request</summary>
|
|
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
|
|
url = baseUrl + connection.appRelativeUrl,
|
|
qs = "transport=" + transport + "&connectionToken=" + window.encodeURIComponent(connection.token);
|
|
|
|
if (connection.data) {
|
|
qs += "&connectionData=" + window.encodeURIComponent(connection.data);
|
|
}
|
|
|
|
if (connection.groupsToken) {
|
|
qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
|
|
}
|
|
|
|
if (!reconnecting) {
|
|
url += "/connect";
|
|
} else {
|
|
if (poll) {
|
|
// longPolling transport specific
|
|
url += "/poll";
|
|
} else {
|
|
url += "/reconnect";
|
|
}
|
|
|
|
if (connection.messageId) {
|
|
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
|
|
}
|
|
}
|
|
url += "?" + qs;
|
|
url = this.addQs(url, connection);
|
|
url += "&tid=" + Math.floor(Math.random() * 11);
|
|
return url;
|
|
},
|
|
|
|
maximizePersistentResponse: function (minPersistentResponse) {
|
|
return {
|
|
MessageId: minPersistentResponse.C,
|
|
Messages: minPersistentResponse.M,
|
|
Disconnect: typeof (minPersistentResponse.D) !== "undefined" ? true : false,
|
|
TimedOut: typeof (minPersistentResponse.T) !== "undefined" ? true : false,
|
|
LongPollDelay: minPersistentResponse.L,
|
|
GroupsToken: minPersistentResponse.G
|
|
};
|
|
},
|
|
|
|
updateGroups: function (connection, groupsToken) {
|
|
if (groupsToken) {
|
|
connection.groupsToken = groupsToken;
|
|
}
|
|
},
|
|
|
|
ajaxSend: function (connection, data) {
|
|
var url = connection.url + "/send" + "?transport=" + connection.transport.name + "&connectionToken=" + window.encodeURIComponent(connection.token);
|
|
url = this.addQs(url, connection);
|
|
return $.ajax({
|
|
url: url,
|
|
global: false,
|
|
type: connection.ajaxDataType === "jsonp" ? "GET" : "POST",
|
|
contentType: signalR._.defaultContentType,
|
|
dataType: connection.ajaxDataType,
|
|
data: {
|
|
data: data
|
|
},
|
|
success: function (result) {
|
|
if (result) {
|
|
$(connection).triggerHandler(events.onReceived, [result]);
|
|
}
|
|
},
|
|
error: function (errData, 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;
|
|
}
|
|
$(connection).triggerHandler(events.onError, [errData, data]);
|
|
}
|
|
});
|
|
},
|
|
|
|
ajaxAbort: function (connection, async) {
|
|
if (typeof (connection.transport) === "undefined") {
|
|
return;
|
|
}
|
|
|
|
// 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 = this.addQs(url, connection);
|
|
$.ajax({
|
|
url: url,
|
|
async: async,
|
|
timeout: 1000,
|
|
global: false,
|
|
type: "POST",
|
|
contentType: connection.contentType,
|
|
dataType: connection.ajaxDataType,
|
|
data: {}
|
|
});
|
|
|
|
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);
|
|
|
|
// If our transport supports keep alive then we need to update the last keep alive time stamp.
|
|
// Very rarely the transport can be null.
|
|
if (connection.transport.supportsKeepAlive && connection.keepAliveData.activated) {
|
|
this.updateKeepAlive(connection);
|
|
}
|
|
|
|
if (!minData) {
|
|
return;
|
|
}
|
|
|
|
data = this.maximizePersistentResponse(minData);
|
|
|
|
if (data.Disconnect) {
|
|
connection.log("Disconnect command received from server");
|
|
|
|
// Disconnected by the server
|
|
connection.stop(false, false);
|
|
return;
|
|
}
|
|
|
|
this.updateGroups(connection, data.GroupsToken);
|
|
|
|
if (data.Messages) {
|
|
$.each(data.Messages, function (index, message) {
|
|
$connection.triggerHandler(events.onReceived, [message]);
|
|
});
|
|
}
|
|
|
|
if (data.MessageId) {
|
|
connection.messageId = data.MessageId;
|
|
}
|
|
}
|
|
},
|
|
|
|
monitorKeepAlive: function (connection) {
|
|
var keepAliveData = connection.keepAliveData,
|
|
that = this;
|
|
|
|
// If we haven't initiated the keep alive timeouts then we need to
|
|
if (!keepAliveData.monitoring) {
|
|
keepAliveData.monitoring = true;
|
|
|
|
// Initialize the keep alive time stamp ping
|
|
that.updateKeepAlive(connection);
|
|
|
|
// Save the function so we can unbind it on stop
|
|
connection.keepAliveData.reconnectKeepAliveUpdate = function () {
|
|
that.updateKeepAlive(connection);
|
|
};
|
|
|
|
// 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);
|
|
// Start the monitoring of the keep alive
|
|
checkIfAlive(connection);
|
|
}
|
|
else {
|
|
connection.log("Tried to monitor keep alive but it's already being monitored");
|
|
}
|
|
},
|
|
|
|
stopMonitoringKeepAlive: function (connection) {
|
|
var keepAliveData = connection.keepAliveData;
|
|
|
|
// Only attempt to stop the keep alive monitoring if its being monitored
|
|
if (keepAliveData.monitoring) {
|
|
// Stop monitoring
|
|
keepAliveData.monitoring = false;
|
|
|
|
// Remove the updateKeepAlive function from the reconnect event
|
|
$(connection).unbind(events.onReconnect, connection.keepAliveData.reconnectKeepAliveUpdate);
|
|
|
|
// Clear all the keep alive data
|
|
connection.keepAliveData = {};
|
|
connection.log("Stopping the monitoring of the keep alive");
|
|
}
|
|
},
|
|
|
|
updateKeepAlive: function (connection) {
|
|
connection.keepAliveData.lastKeepAlive = new Date();
|
|
},
|
|
|
|
ensureReconnectingState: function (connection) {
|
|
if (changeState(connection,
|
|
signalR.connectionState.connected,
|
|
signalR.connectionState.reconnecting) === true) {
|
|
$(connection).triggerHandler(events.onReconnecting);
|
|
}
|
|
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: {}
|
|
}
|
|
};
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.transports.webSockets.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.transports.common.js" />
|
|
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
var signalR = $.signalR,
|
|
events = $.signalR.events,
|
|
changeState = $.signalR.changeState,
|
|
transportLogic = signalR.transports._logic;
|
|
|
|
signalR.transports.webSockets = {
|
|
name: "webSockets",
|
|
|
|
supportsKeepAlive: true,
|
|
|
|
send: function (connection, data) {
|
|
connection.socket.send(data);
|
|
},
|
|
|
|
start: function (connection, onSuccess, onFailed) {
|
|
var url,
|
|
opened = false,
|
|
that = this,
|
|
reconnecting = !onSuccess,
|
|
$connection = $(connection);
|
|
|
|
if (!window.WebSocket) {
|
|
onFailed();
|
|
return;
|
|
}
|
|
|
|
if (!connection.socket) {
|
|
if (connection.webSocketServerUrl) {
|
|
url = connection.webSocketServerUrl;
|
|
}
|
|
else {
|
|
url = connection.wsProtocol + connection.host;
|
|
}
|
|
|
|
url += transportLogic.getUrl(connection, this.name, reconnecting);
|
|
|
|
connection.log("Connecting to websocket endpoint '" + url + "'");
|
|
connection.socket = new window.WebSocket(url);
|
|
connection.socket.onopen = function () {
|
|
opened = true;
|
|
connection.log("Websocket opened");
|
|
|
|
transportLogic.clearReconnectTimeout(connection);
|
|
|
|
if (onSuccess) {
|
|
onSuccess();
|
|
} else if (changeState(connection,
|
|
signalR.connectionState.reconnecting,
|
|
signalR.connectionState.connected) === true) {
|
|
$connection.triggerHandler(events.onReconnect);
|
|
}
|
|
};
|
|
|
|
connection.socket.onclose = function (event) {
|
|
// 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 === connection.socket) {
|
|
if (!opened) {
|
|
if (onFailed) {
|
|
onFailed();
|
|
}
|
|
else if (reconnecting) {
|
|
that.reconnect(connection);
|
|
}
|
|
return;
|
|
}
|
|
else if (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);
|
|
}
|
|
else {
|
|
connection.log("Websocket closed");
|
|
}
|
|
|
|
that.reconnect(connection);
|
|
}
|
|
};
|
|
|
|
connection.socket.onmessage = function (event) {
|
|
var data = window.JSON.parse(event.data),
|
|
$connection = $(connection);
|
|
|
|
if (data) {
|
|
// data.M is PersistentResponse.Messages
|
|
if ($.isEmptyObject(data) || data.M) {
|
|
transportLogic.processMessages(connection, data);
|
|
} else {
|
|
// For websockets we need to trigger onReceived
|
|
// for callbacks to outgoing hub calls.
|
|
$connection.triggerHandler(events.onReceived, [data]);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
},
|
|
|
|
reconnect: function (connection) {
|
|
transportLogic.reconnect(connection, this.name);
|
|
},
|
|
|
|
lostConnection: function (connection) {
|
|
this.reconnect(connection);
|
|
|
|
},
|
|
|
|
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();
|
|
connection.socket = null;
|
|
}
|
|
},
|
|
|
|
abort: function (connection) {
|
|
}
|
|
};
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.transports.serverSentEvents.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.transports.common.js" />
|
|
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
var signalR = $.signalR,
|
|
events = $.signalR.events,
|
|
changeState = $.signalR.changeState,
|
|
transportLogic = signalR.transports._logic;
|
|
|
|
signalR.transports.serverSentEvents = {
|
|
name: "serverSentEvents",
|
|
|
|
supportsKeepAlive: true,
|
|
|
|
timeOut: 3000,
|
|
|
|
start: function (connection, onSuccess, onFailed) {
|
|
var that = this,
|
|
opened = false,
|
|
$connection = $(connection),
|
|
reconnecting = !onSuccess,
|
|
url,
|
|
connectTimeOut;
|
|
|
|
if (connection.eventSource) {
|
|
connection.log("The connection already has an event source. Stopping it.");
|
|
connection.stop();
|
|
}
|
|
|
|
if (!window.EventSource) {
|
|
if (onFailed) {
|
|
connection.log("This browser doesn't support SSE.");
|
|
onFailed();
|
|
}
|
|
return;
|
|
}
|
|
|
|
url = transportLogic.getUrl(connection, this.name, reconnecting);
|
|
|
|
try {
|
|
connection.log("Attempting to connect to SSE endpoint '" + url + "'");
|
|
connection.eventSource = new window.EventSource(url);
|
|
}
|
|
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]);
|
|
if (reconnecting) {
|
|
// If we were reconnecting, rather than doing initial connect, then try reconnect again
|
|
that.reconnect(connection);
|
|
}
|
|
}
|
|
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.log("EventSource timed out trying to connect");
|
|
connection.log("EventSource readyState: " + connection.eventSource.readyState);
|
|
|
|
if (!reconnecting) {
|
|
that.stop(connection);
|
|
}
|
|
|
|
if (reconnecting) {
|
|
// 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.CONNECTING &&
|
|
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);
|
|
|
|
connection.eventSource.addEventListener("open", function (e) {
|
|
connection.log("EventSource connected");
|
|
|
|
if (connectTimeOut) {
|
|
window.clearTimeout(connectTimeOut);
|
|
}
|
|
|
|
transportLogic.clearReconnectTimeout(connection);
|
|
|
|
if (opened === false) {
|
|
opened = true;
|
|
|
|
if (onSuccess) {
|
|
onSuccess();
|
|
} else 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);
|
|
}
|
|
}
|
|
}, false);
|
|
|
|
connection.eventSource.addEventListener("message", function (e) {
|
|
// process messages
|
|
if (e.data === "initialized") {
|
|
return;
|
|
}
|
|
|
|
transportLogic.processMessages(connection, window.JSON.parse(e.data));
|
|
}, false);
|
|
|
|
connection.eventSource.addEventListener("error", function (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();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}, false);
|
|
},
|
|
|
|
reconnect: function (connection) {
|
|
transportLogic.reconnect(connection, this.name);
|
|
},
|
|
|
|
lostConnection: function (connection) {
|
|
this.reconnect(connection);
|
|
},
|
|
|
|
send: function (connection, data) {
|
|
transportLogic.ajaxSend(connection, data);
|
|
},
|
|
|
|
stop: function (connection) {
|
|
// Don't trigger a reconnect after stopping
|
|
transportLogic.clearReconnectTimeout(connection);
|
|
|
|
if (connection && connection.eventSource) {
|
|
connection.log("EventSource calling close()");
|
|
connection.eventSource.close();
|
|
connection.eventSource = null;
|
|
delete connection.eventSource;
|
|
}
|
|
},
|
|
|
|
abort: function (connection, async) {
|
|
transportLogic.ajaxAbort(connection, async);
|
|
}
|
|
};
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.transports.foreverFrame.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.transports.common.js" />
|
|
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
var signalR = $.signalR,
|
|
events = $.signalR.events,
|
|
changeState = $.signalR.changeState,
|
|
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 = $("<iframe style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;' src=''></iframe>");
|
|
|
|
$("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",
|
|
|
|
supportsKeepAlive: true,
|
|
|
|
timeOut: 3000,
|
|
|
|
start: function (connection, onSuccess, onFailed) {
|
|
var that = this,
|
|
frameId = (transportLogic.foreverFrame.count += 1),
|
|
url,
|
|
frame = $("<iframe data-signalr-connection-id='" + connection.id + "' style='position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;' src=''></iframe>");
|
|
|
|
if (window.EventSource) {
|
|
// If the browser supports SSE, don't use Forever Frame
|
|
if (onFailed) {
|
|
connection.log("This browser supports SSE, skipping Forever Frame.");
|
|
onFailed();
|
|
}
|
|
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);
|
|
url += "&frameId=" + frameId;
|
|
|
|
// Set body prior to setting URL to avoid caching issues.
|
|
$("body").append(frame);
|
|
|
|
frame.prop("src", url);
|
|
transportLogic.foreverFrame.connections[frameId] = connection;
|
|
|
|
connection.log("Binding to iframe's readystatechange event.");
|
|
frame.bind("readystatechange", function () {
|
|
if ($.inArray(this.readyState, ["loaded", "complete"]) >= 0) {
|
|
connection.log("Forever frame iframe readyState changed to " + this.readyState + ", reconnecting");
|
|
|
|
that.reconnect(connection);
|
|
}
|
|
});
|
|
|
|
connection.frame = frame[0];
|
|
connection.frameId = frameId;
|
|
|
|
if (onSuccess) {
|
|
connection.onSuccess = 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;
|
|
window.setTimeout(function () {
|
|
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) {
|
|
this.reconnect(connection);
|
|
},
|
|
|
|
send: function (connection, data) {
|
|
transportLogic.ajaxSend(connection, data);
|
|
},
|
|
|
|
receive: function (connection, data) {
|
|
var cw;
|
|
|
|
transportLogic.processMessages(connection, data);
|
|
// Delete the script & div elements
|
|
connection.frameMessageCount = (connection.frameMessageCount || 0) + 1;
|
|
if (connection.frameMessageCount > 50) {
|
|
connection.frameMessageCount = 0;
|
|
cw = connection.frame.contentWindow || connection.frame.contentDocument;
|
|
if (cw && cw.document) {
|
|
$("body", cw.document).empty();
|
|
}
|
|
}
|
|
},
|
|
|
|
stop: function (connection) {
|
|
var cw = null;
|
|
|
|
// Stop attempting to prevent loading icon
|
|
loadPreventer.cancel();
|
|
|
|
if (connection.frame) {
|
|
if (connection.frame.stop) {
|
|
connection.frame.stop();
|
|
} else {
|
|
try {
|
|
cw = connection.frame.contentWindow || connection.frame.contentDocument;
|
|
if (cw.document && cw.document.execCommand) {
|
|
cw.document.execCommand("Stop");
|
|
}
|
|
}
|
|
catch (e) {
|
|
connection.log("SignalR: Error occured when stopping foreverFrame transport. Message = " + e.message);
|
|
}
|
|
}
|
|
$(connection.frame).remove();
|
|
delete transportLogic.foreverFrame.connections[connection.frameId];
|
|
connection.frame = null;
|
|
connection.frameId = null;
|
|
delete connection.frame;
|
|
delete connection.frameId;
|
|
connection.log("Stopping forever frame");
|
|
}
|
|
},
|
|
|
|
abort: function (connection, async) {
|
|
transportLogic.ajaxAbort(connection, async);
|
|
},
|
|
|
|
getConnection: function (id) {
|
|
return transportLogic.foreverFrame.connections[id];
|
|
},
|
|
|
|
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
|
|
$(connection).triggerHandler(events.onReconnect);
|
|
}
|
|
}
|
|
};
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.transports.longPolling.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.transports.common.js" />
|
|
|
|
(function ($, window) {
|
|
"use strict";
|
|
|
|
var signalR = $.signalR,
|
|
events = $.signalR.events,
|
|
changeState = $.signalR.changeState,
|
|
isDisconnecting = $.signalR.isDisconnecting,
|
|
transportLogic = signalR.transports._logic;
|
|
|
|
signalR.transports.longPolling = {
|
|
name: "longPolling",
|
|
|
|
supportsKeepAlive: false,
|
|
|
|
reconnectDelay: 3000,
|
|
|
|
init: function (connection, onComplete) {
|
|
/// <summary>Pings the server to ensure availability</summary>
|
|
/// <param name="connection" type="signalr">Connection associated with the server ping</param>
|
|
/// <param name="onComplete" type="Function">Callback to call once initialization has completed</param>
|
|
|
|
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("SignalR: Server ping failed because '" + reason + "', re-trying ping.");
|
|
window.setTimeout(pingLoop, that.reconnectDelay);
|
|
}
|
|
};
|
|
|
|
connection.log("SignalR: 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, that.name).done(onComplete).fail(pingFail);
|
|
};
|
|
|
|
pingLoop();
|
|
},
|
|
|
|
start: function (connection, onSuccess, onFailed) {
|
|
/// <summary>Starts the long polling connection</summary>
|
|
/// <param name="connection" type="signalR">The SignalR connection to start</param>
|
|
var that = this,
|
|
initialConnectedFired = false,
|
|
fireConnect = function () {
|
|
if (initialConnectedFired) {
|
|
return;
|
|
}
|
|
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.");
|
|
connection.stop();
|
|
}
|
|
|
|
// 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;
|
|
|
|
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("Attempting to connect to '" + url + "' using longPolling.");
|
|
instance.pollXhr = $.ajax({
|
|
url: url,
|
|
global: false,
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
// We never want to pass a raiseReconnect flag after a successful poll. This is handled via the error function
|
|
if (delay > 0) {
|
|
window.setTimeout(function () {
|
|
poll(instance, false);
|
|
}, delay);
|
|
} else {
|
|
poll(instance, false);
|
|
}
|
|
},
|
|
|
|
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]);
|
|
}
|
|
|
|
// Transition into the reconnecting state
|
|
transportLogic.ensureReconnectingState(instance);
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// 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.
|
|
reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout));
|
|
}
|
|
}(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
|
|
});
|
|
},
|
|
|
|
lostConnection: function (connection) {
|
|
throw new Error("Lost Connection not handled for LongPolling");
|
|
},
|
|
|
|
send: function (connection, data) {
|
|
transportLogic.ajaxSend(connection, data);
|
|
},
|
|
|
|
stop: function (connection) {
|
|
/// <summary>Stops the long polling connection</summary>
|
|
/// <param name="connection" type="signalR">The SignalR connection to stop</param>
|
|
if (connection.pollXhr) {
|
|
connection.pollXhr.abort();
|
|
connection.pollXhr = null;
|
|
delete connection.pollXhr;
|
|
}
|
|
},
|
|
|
|
abort: function (connection, async) {
|
|
transportLogic.ajaxAbort(connection, async);
|
|
}
|
|
};
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.hubs.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.core.js" />
|
|
|
|
(function ($, window) {
|
|
"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";
|
|
|
|
function makeEventName(event) {
|
|
return event + eventNamespace;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
function getArgValue(a) {
|
|
return $.isFunction(a) ? null : ($.type(a) === "undefined" ? null : a);
|
|
}
|
|
|
|
function hasMembers(obj) {
|
|
for (var key in obj) {
|
|
// If we have any properties in our callback map then we have callbacks and can exit the loop via return
|
|
if (obj.hasOwnProperty(key)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function clearInvocationCallbacks(connection, error) {
|
|
/// <param name="connection" type="hubConnection" />
|
|
var callbacks = connection._.invocationCallbacks,
|
|
callback;
|
|
|
|
connection.log("Clearing hub invocation callbacks with error: " + error);
|
|
|
|
// Reset the callback cache now as we have a local var referencing it
|
|
connection._.invocationCallbackId = 0;
|
|
delete connection._.invocationCallbacks;
|
|
connection._.invocationCallbacks = {};
|
|
|
|
// Loop over the callbacks and invoke them.
|
|
// We do this using a local var reference and *after* we've cleared the cache
|
|
// so that if a fail callback itself tries to invoke another method we don't
|
|
// end up with its callback in the list we're looping over.
|
|
for (var callbackId in callbacks) {
|
|
callback = callbacks[callbackId];
|
|
callback.method.call(callback.scope, { E: error });
|
|
}
|
|
}
|
|
|
|
// hubProxy
|
|
function hubProxy(hubConnection, hubName) {
|
|
/// <summary>
|
|
/// Creates a new proxy object for the given hub connection that can be used to invoke
|
|
/// methods on server hubs and handle client method invocation requests from the server.
|
|
/// </summary>
|
|
return new hubProxy.fn.init(hubConnection, hubName);
|
|
}
|
|
|
|
hubProxy.fn = hubProxy.prototype = {
|
|
init: function (connection, hubName) {
|
|
this.state = {};
|
|
this.connection = connection;
|
|
this.hubName = hubName;
|
|
this._ = {
|
|
callbackMap: {}
|
|
};
|
|
},
|
|
|
|
hasSubscriptions: function () {
|
|
return hasMembers(this._.callbackMap);
|
|
},
|
|
|
|
on: function (eventName, callback) {
|
|
/// <summary>Wires up a callback to be invoked when a invocation request is received from the server hub.</summary>
|
|
/// <param name="eventName" type="String">The name of the hub event to register the callback for.</param>
|
|
/// <param name="callback" type="Function">The callback to be invoked.</param>
|
|
var self = this,
|
|
callbackMap = self._.callbackMap;
|
|
|
|
// Normalize the event name to lowercase
|
|
eventName = eventName.toLowerCase();
|
|
|
|
// If there is not an event registered for this callback yet we want to create its event space in the callback map.
|
|
if (!callbackMap[eventName]) {
|
|
callbackMap[eventName] = {};
|
|
}
|
|
|
|
// Map the callback to our encompassed function
|
|
callbackMap[eventName][callback] = function (e, data) {
|
|
callback.apply(self, data);
|
|
};
|
|
|
|
$(self).bind(makeEventName(eventName), callbackMap[eventName][callback]);
|
|
|
|
return self;
|
|
},
|
|
|
|
off: function (eventName, callback) {
|
|
/// <summary>Removes the callback invocation request from the server hub for the given event name.</summary>
|
|
/// <param name="eventName" type="String">The name of the hub event to unregister the callback for.</param>
|
|
/// <param name="callback" type="Function">The callback to be invoked.</param>
|
|
var self = this,
|
|
callbackMap = self._.callbackMap,
|
|
callbackSpace;
|
|
|
|
// Normalize the event name to lowercase
|
|
eventName = eventName.toLowerCase();
|
|
|
|
callbackSpace = callbackMap[eventName];
|
|
|
|
// Verify that there is an event space to unbind
|
|
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]);
|
|
|
|
// Remove the callback from the callback map
|
|
delete callbackSpace[callback];
|
|
|
|
// Check if there are any members left on the event, if not we need to destroy it.
|
|
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));
|
|
|
|
delete callbackMap[eventName];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
invoke: function (methodName) {
|
|
/// <summary>Invokes a server hub method with the given arguments.</summary>
|
|
/// <param name="methodName" type="String">The name of the server hub method.</param>
|
|
|
|
var self = this,
|
|
connection = self.connection,
|
|
args = $.makeArray(arguments).slice(1),
|
|
argValues = map(args, getArgValue),
|
|
data = { H: self.hubName, M: methodName, A: argValues, I: connection._.invocationCallbackId },
|
|
d = $.Deferred(),
|
|
callback = function (minResult) {
|
|
var result = self._maximizeHubResponse(minResult);
|
|
|
|
// Update the hub state
|
|
$.extend(self.state, result.State);
|
|
|
|
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]);
|
|
} else {
|
|
// Server invocation succeeded, resolve the deferred
|
|
d.resolveWith(self, [result.Result]);
|
|
}
|
|
};
|
|
|
|
connection._.invocationCallbacks[connection._.invocationCallbackId.toString()] = { scope: self, method: callback };
|
|
connection._.invocationCallbackId += 1;
|
|
|
|
if (!$.isEmptyObject(self.state)) {
|
|
data.S = self.state;
|
|
}
|
|
|
|
connection.send(window.JSON.stringify(data));
|
|
|
|
return d.promise();
|
|
},
|
|
|
|
_maximizeHubResponse: function (minHubResponse) {
|
|
return {
|
|
State: minHubResponse.S,
|
|
Result: minHubResponse.R,
|
|
Id: minHubResponse.I,
|
|
Error: minHubResponse.E,
|
|
StackTrace: minHubResponse.T
|
|
};
|
|
}
|
|
};
|
|
|
|
hubProxy.fn.init.prototype = hubProxy.fn;
|
|
|
|
// hubConnection
|
|
function hubConnection(url, options) {
|
|
/// <summary>Creates a new hub connection.</summary>
|
|
/// <param name="url" type="String">[Optional] The hub route url, defaults to "/signalr".</param>
|
|
/// <param name="options" type="Object">[Optional] Settings to use when creating the hubConnection.</param>
|
|
var settings = {
|
|
qs: null,
|
|
logging: false,
|
|
useDefaultPath: true
|
|
};
|
|
|
|
$.extend(settings, options);
|
|
|
|
if (!url || settings.useDefaultPath) {
|
|
url = (url || "") + "/signalr";
|
|
}
|
|
return new hubConnection.fn.init(url, settings);
|
|
}
|
|
|
|
hubConnection.fn = hubConnection.prototype = $.connection();
|
|
|
|
hubConnection.fn.init = function (url, options) {
|
|
var settings = {
|
|
qs: null,
|
|
logging: false,
|
|
useDefaultPath: true
|
|
},
|
|
connection = this;
|
|
|
|
$.extend(settings, options);
|
|
|
|
// Call the base constructor
|
|
$.signalR.fn.init.call(connection, url, settings.qs, settings.logging);
|
|
|
|
// Object to store hub proxies for this connection
|
|
connection.proxies = {};
|
|
|
|
connection._.invocationCallbackId = 0;
|
|
connection._.invocationCallbacks = {};
|
|
|
|
// Wire up the received handler
|
|
connection.received(function (minData) {
|
|
var data, proxy, dataCallbackId, callback, hubName, eventName;
|
|
if (!minData) {
|
|
return;
|
|
}
|
|
|
|
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];
|
|
if (callback) {
|
|
// Delete the callback from the proxy
|
|
connection._.invocationCallbacks[dataCallbackId] = null;
|
|
delete connection._.invocationCallbacks[dataCallbackId];
|
|
|
|
// Invoke the callback
|
|
callback.method.call(callback.scope, minData);
|
|
}
|
|
} else {
|
|
data = this._maximizeClientHubInvocation(minData);
|
|
|
|
// We received a client invocation request, i.e. broadcast from server hub
|
|
connection.log("Triggering client hub event '" + data.Method + "' on hub '" + data.Hub + "'.");
|
|
|
|
// Normalize the names to lowercase
|
|
hubName = data.Hub.toLowerCase();
|
|
eventName = data.Method.toLowerCase();
|
|
|
|
// Trigger the local invocation event
|
|
proxy = this.proxies[hubName];
|
|
|
|
// Update the hub state
|
|
$.extend(proxy.state, data.State);
|
|
$(proxy).triggerHandler(makeEventName(eventName), [data.Args]);
|
|
}
|
|
});
|
|
|
|
connection.error(function (errData, origData) {
|
|
var data, 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 {
|
|
data = window.JSON.parse(origData);
|
|
if (!data.I) {
|
|
// The original data doesn't have a callback ID so not a send error
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// The original data is not a JSON payload so this is not a send error
|
|
return;
|
|
}
|
|
|
|
callbackId = data.I;
|
|
callback = connection._.invocationCallbacks[callbackId];
|
|
|
|
// Invoke the callback with an error to reject the promise
|
|
callback.method.call(callback.scope, { E: errData });
|
|
|
|
// Delete the callback
|
|
connection._.invocationCallbacks[callbackId] = null;
|
|
delete connection._.invocationCallbacks[callbackId];
|
|
});
|
|
|
|
connection.reconnecting(function () {
|
|
if (connection.transport && connection.transport.name === "webSockets") {
|
|
clearInvocationCallbacks(connection, "Connection started reconnecting before invocation result was received.");
|
|
}
|
|
});
|
|
|
|
connection.disconnected(function () {
|
|
clearInvocationCallbacks(connection, "Connection was disconnected before invocation result was received.");
|
|
});
|
|
};
|
|
|
|
hubConnection.fn._maximizeClientHubInvocation = function (minClientHubInvocation) {
|
|
return {
|
|
Hub: minClientHubInvocation.H,
|
|
Method: minClientHubInvocation.M,
|
|
Args: minClientHubInvocation.A,
|
|
State: minClientHubInvocation.S
|
|
};
|
|
};
|
|
|
|
hubConnection.fn._registerSubscribedHubs = function () {
|
|
/// <summary>
|
|
/// Sets the starting event to loop through the known hubs and register any new hubs
|
|
/// that have been added to the proxy.
|
|
/// </summary>
|
|
|
|
if (!this._subscribedToHubs) {
|
|
this._subscribedToHubs = true;
|
|
this.starting(function () {
|
|
// Set the connection's data object with all the hub proxies with active subscriptions.
|
|
// These proxies will receive notifications from the server.
|
|
var subscribedHubs = [];
|
|
|
|
$.each(this.proxies, function (key) {
|
|
if (this.hasSubscriptions()) {
|
|
subscribedHubs.push({ name: key });
|
|
}
|
|
});
|
|
|
|
this.data = window.JSON.stringify(subscribedHubs);
|
|
});
|
|
}
|
|
};
|
|
|
|
hubConnection.fn.createHubProxy = function (hubName) {
|
|
/// <summary>
|
|
/// Creates a new proxy object for the given hub connection that can be used to invoke
|
|
/// methods on server hubs and handle client method invocation requests from the server.
|
|
/// </summary>
|
|
/// <param name="hubName" type="String">
|
|
/// The name of the hub on the server to create the proxy for.
|
|
/// </param>
|
|
|
|
// Normalize the name to lowercase
|
|
hubName = hubName.toLowerCase();
|
|
|
|
var proxy = this.proxies[hubName];
|
|
if (!proxy) {
|
|
proxy = hubProxy(this, hubName);
|
|
this.proxies[hubName] = proxy;
|
|
}
|
|
|
|
this._registerSubscribedHubs();
|
|
|
|
return proxy;
|
|
};
|
|
|
|
hubConnection.fn.init.prototype = hubConnection.fn;
|
|
|
|
$.hubConnection = hubConnection;
|
|
|
|
}(window.jQuery, window));
|
|
/* jquery.signalR.version.js */
|
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
/*global window:false */
|
|
/// <reference path="jquery.signalR.core.js" />
|
|
(function ($) {
|
|
$.signalR.version = "1.1.3";
|
|
}(window.jQuery));
|