2013-05-05 21:24:33 +00:00
/* jquery.signalR.core.js */
/*global window:false */
/ * !
2013-08-13 02:51:47 +00:00
* ASP . NET SignalR JavaScript Library v1 . 1.3
2013-05-05 21:24:33 +00:00
* 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 ;
2013-08-13 02:51:47 +00:00
// Timeout to designate when to warn the developer that the connection may be dead or is not responding.
2013-05-05 21:24:33 +00:00
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 {
i f ( 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." ) ;
} ,
2013-05-15 05:49:31 +00:00
getUrl : function ( connection , transport , reconnecting , poll ) {
2013-05-05 21:24:33 +00:00
/// <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 {
2013-05-15 05:49:31 +00:00
if ( poll ) {
// longPolling transport specific
2013-05-05 21:24:33 +00:00
url += "/poll" ;
2013-05-15 05:49:31 +00:00
} else {
url += "/reconnect" ;
2013-05-05 21:24:33 +00:00
}
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 ;
}
2013-08-13 02:51:47 +00:00
$ ( connection ) . triggerHandler ( events . onError , [ errData , data ] ) ;
2013-05-05 21:24:33 +00:00
}
} ) ;
} ,
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" ) ;
2013-05-15 05:49:31 +00:00
} ,
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 ;
2013-05-05 21:24:33 +00:00
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 ,
2013-05-15 05:49:31 +00:00
polling = ! raiseReconnect ,
url = transportLogic . getUrl ( instance , that . name , reconnecting , polling ) ;
2013-05-05 21:24:33 +00:00
// 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 ;
2013-05-15 05:49:31 +00:00
// 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 ( ) ;
}
2013-05-05 21:24:33 +00:00
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 ) {
2013-05-15 05:49:31 +00:00
// 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 ;
2013-05-05 21:24:33 +00:00
if ( textStatus === "abort" ) {
connection . log ( "Aborted xhr requst." ) ;
return ;
}
2013-05-15 05:49:31 +00:00
// 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 ++ ;
2013-05-05 21:24:33 +00:00
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 ) ;
} ) ;
}
} ) ;
2013-05-15 05:49:31 +00:00
2013-05-05 21:24:33 +00:00
// This will only ever pass after an error has occured via the poll ajax procedure.
if ( reconnecting && raiseReconnect === true ) {
2013-05-15 05:49:31 +00:00
// 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 ) ) ;
2013-05-05 21:24:33 +00:00
}
} ( 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
2013-08-13 02:51:47 +00:00
var eventNamespace = ".hubProxy" ;
2013-05-05 21:24:33 +00:00
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 ;
}
2013-08-13 02:51:47 +00:00
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 } ) ;
}
}
2013-05-05 21:24:33 +00:00
// 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 ,
2013-08-13 02:51:47 +00:00
connection = self . connection ,
2013-05-05 21:24:33 +00:00
args = $ . makeArray ( arguments ) . slice ( 1 ) ,
argValues = map ( args , getArgValue ) ,
2013-08-13 02:51:47 +00:00
data = { H : self . hubName , M : methodName , A : argValues , I : connection . _ . invocationCallbackId } ,
2013-05-05 21:24:33 +00:00
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 ) {
2013-08-13 02:51:47 +00:00
connection . log ( result . Error + "\n" + result . StackTrace ) ;
2013-05-05 21:24:33 +00:00
}
d . rejectWith ( self , [ result . Error ] ) ;
} else {
// Server invocation succeeded, resolve the deferred
d . resolveWith ( self , [ result . Result ] ) ;
}
} ;
2013-08-13 02:51:47 +00:00
connection . _ . invocationCallbacks [ connection . _ . invocationCallbackId . toString ( ) ] = { scope : self , method : callback } ;
connection . _ . invocationCallbackId += 1 ;
2013-05-05 21:24:33 +00:00
if ( ! $ . isEmptyObject ( self . state ) ) {
data . S = self . state ;
}
2013-08-13 02:51:47 +00:00
connection . send ( window . JSON . stringify ( data ) ) ;
2013-05-05 21:24:33 +00:00
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 = {
2013-08-13 02:51:47 +00:00
qs : null ,
logging : false ,
useDefaultPath : true
} ,
2013-05-05 21:24:33 +00:00
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 = { } ;
2013-08-13 02:51:47 +00:00
connection . _ . invocationCallbackId = 0 ;
connection . _ . invocationCallbacks = { } ;
2013-05-05 21:24:33 +00:00
// 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 ( ) ;
2013-08-13 02:51:47 +00:00
callback = connection . _ . invocationCallbacks [ dataCallbackId ] ;
2013-05-05 21:24:33 +00:00
if ( callback ) {
// Delete the callback from the proxy
2013-08-13 02:51:47 +00:00
connection . _ . invocationCallbacks [ dataCallbackId ] = null ;
delete connection . _ . invocationCallbacks [ dataCallbackId ] ;
2013-05-05 21:24:33 +00:00
// 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 ] ) ;
}
} ) ;
2013-08-13 02:51:47 +00:00
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." ) ;
} ) ;
2013-05-05 21:24:33 +00:00
} ;
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 ( $ ) {
2013-08-13 02:51:47 +00:00
$ . signalR . version = "1.1.3" ;
2013-05-05 21:24:33 +00:00
} ( window . jQuery ) ) ;