Skip to content

Commit

Permalink
Initiate /start request after a transport is selected in the JS client
Browse files Browse the repository at this point in the history
- This is how the .NET client already behaves

#3104
  • Loading branch information
halter73 committed Jul 4, 2014
1 parent cf659b4 commit f59827a
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 82 deletions.
85 changes: 47 additions & 38 deletions src/Microsoft.AspNet.SignalR.Client.JS/jquery.signalR.core.js
Expand Up @@ -591,57 +591,66 @@
}, connection._.totalTransportConnectTimeout);

transport.start(connection, function () { // success
// Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials
var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11,
asyncAbort = !!connection.withCredentials && isFirefox11OrGreater;
var onStartSuccess = function () {
// Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials
var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11,
asyncAbort = !!connection.withCredentials && isFirefox11OrGreater;

// The connection was aborted while initializing transports
if (connection.state === signalR.connectionState.disconnected) {
return;
}
connection.log("The start request succeeded. Transitioning to the connected state.");

if (!initializationComplete) {
initializationComplete = true;
if (supportsKeepAlive(connection)) {
signalR.transports._logic.monitorKeepAlive(connection);
}

window.clearTimeout(connection._.onFailedTimeoutHandle);
signalR.transports._logic.startHeartbeat(connection);

if (supportsKeepAlive(connection)) {
signalR.transports._logic.monitorKeepAlive(connection);
}
// Used to ensure low activity clients maintain their authentication.
// Must be configured once a transport has been decided to perform valid ping requests.
signalR._.configurePingInterval(connection);

signalR.transports._logic.startHeartbeat(connection);
if (!changeState(connection,
signalR.connectionState.connecting,
signalR.connectionState.connected)) {
connection.log("WARNING! The connection was not in the connecting state.");
}

// Used to ensure low activity clients maintain their authentication.
// Must be configured once a transport has been decided to perform valid ping requests.
signalR._.configurePingInterval(connection);
// Drain any incoming buffered messages (messages that came in prior to connect)
connection._.connectingMessageBuffer.drain();

changeState(connection,
signalR.connectionState.connecting,
signalR.connectionState.connected);
$(connection).triggerHandler(events.onStart);

// Drain any incoming buffered messages (messages that came in prior to connect)
connection._.connectingMessageBuffer.drain();
// wire the stop handler for when the user leaves the page
_pageWindow.bind("unload", function () {
connection.log("Window unloading, stopping the connection.");

$(connection).triggerHandler(events.onStart);
connection.stop(asyncAbort);
});

// wire the stop handler for when the user leaves the page
_pageWindow.bind("unload", function () {
connection.log("Window unloading, stopping the connection.");
if (isFirefox11OrGreater) {
// Firefox does not fire cross-domain XHRs in the normal unload handler on tab close.
// #2400
_pageWindow.bind("beforeunload", function () {
// If connection.stop() runs runs in beforeunload and fails, it will also fail
// in unload unless connection.stop() runs after a timeout.
window.setTimeout(function () {
connection.stop(asyncAbort);
}, 0);
});
}
};

connection.stop(asyncAbort);
});
if (!initializationComplete) {
initializationComplete = true;
// Prevent transport fallback
window.clearTimeout(connection._.onFailedTimeoutHandle);

if (isFirefox11OrGreater) {
// Firefox does not fire cross-domain XHRs in the normal unload handler on tab close.
// #2400
_pageWindow.bind("beforeunload", function () {
// If connection.stop() runs runs in beforeunload and fails, it will also fail
// in unload unless connection.stop() runs after a timeout.
window.setTimeout(function () {
connection.stop(asyncAbort);
}, 0);
});
// The connection was aborted while initializing transports
if (connection.state === signalR.connectionState.disconnected) {
return;
}

connection.log(transport.name + " transport selected. Initiating start request.");
signalR.transports._logic.ajaxStart(connection, onStartSuccess);
}
}, onFailed);
}
Expand Down
Expand Up @@ -328,62 +328,57 @@
connection.log("Fired ajax abort async = " + async + ".");
},

tryInitialize: function (connection, persistentResponse, onInitialized) {
var startUrl,
xhr,
rejectDeferred = function (error) {
ajaxStart: function (connection, onSuccess) {
var rejectDeferred = function (error) {
var deferred = connection._deferral;
if (deferred) {
deferred.reject(error);
}
},
triggerStartError = function (error) {
connection.log("The start request failed. Stopping the connection.");
$(connection).triggerHandler(events.onError, [error]);
rejectDeferred(error);
connection.stop();
};

if (persistentResponse.Initialized) {
startUrl = getAjaxUrl(connection, "/start");

xhr = transportLogic.ajax(connection, {
url: startUrl,
success: function (result) {
var data;

try {
data = connection._parseResponse(result);
} catch (error) {
triggerStartError(signalR._.error(
signalR._.format(signalR.resources.errorParsingStartResponse, result),
error, xhr));
return;
}

if (data.Response === "started") {
onInitialized();
} else {
triggerStartError(signalR._.error(
signalR._.format(signalR.resources.invalidStartResponse, result),
null /* error */, xhr));
}
},
error: function (error, statusText) {
if (statusText !== startAbortText) {
triggerStartError(signalR._.error(
signalR.resources.errorDuringStartRequest,
error, xhr));
} else {
// Stop has been called
rejectDeferred(signalR._.error(
signalR.resources.stoppedDuringStartRequest,
null /* error */, xhr));
}
connection._.startRequest = transportLogic.ajax(connection, {
url: getAjaxUrl(connection, "/start"),
success: function (result, statusText, xhr) {
var data;

try {
data = connection._parseResponse(result);
} catch (error) {
triggerStartError(signalR._.error(
signalR._.format(signalR.resources.errorParsingStartResponse, result),
error, xhr));
return;
}
});

connection._.startRequest = xhr;
}
if (data.Response === "started") {
onSuccess();
} else {
triggerStartError(signalR._.error(
signalR._.format(signalR.resources.invalidStartResponse, result),
null /* error */, xhr));
}
},
error: function (xhr, statusText, error) {
if (statusText !== startAbortText) {
triggerStartError(signalR._.error(
signalR.resources.errorDuringStartRequest,
error, xhr));
} else {
// Stop has been called, no need to trigger the error handler
// or stop the connection again with onStartError
connection.log("The start request aborted because connection.stop() was called.");
rejectDeferred(signalR._.error(
signalR.resources.stoppedDuringStartRequest,
null /* error */, xhr));
}
}
});
},

tryAbortStartRequest: function (connection) {
Expand All @@ -394,6 +389,12 @@
}
},

tryInitialize: function (persistentResponse, onInitialized) {
if (persistentResponse.Initialized) {
onInitialized();
}
},

triggerReceived: function (connection, data) {
if (!connection._.connectingMessageBuffer.tryBuffer(data)) {
$(connection).triggerHandler(events.onReceived, [data]);
Expand All @@ -420,7 +421,7 @@
transportLogic.triggerReceived(connection, message);
});

transportLogic.tryInitialize(connection, data, onInitialized);
transportLogic.tryInitialize(data, onInitialized);
}
}
},
Expand Down
Expand Up @@ -77,6 +77,43 @@ QUnit.asyncTimeoutTest("Client defines array of transports, server does not supp
};
});

QUnit.asyncTimeoutTest("Client does not fall back if the start request hangs.", testUtilities.defaultTestTimeout * 2, function (end, assert, testName) {
var connection = testUtilities.createHubConnection(end, assert, testName)
savedAjaxStart = $.signalR.transports._logic.ajaxStart,
ajaxStartCalled = false,
startTransport = null;

$.signalR.transports._logic.ajaxStart = function () {
var ajaxStartArgs = arguments;

if (ajaxStartCalled) {
assert.fail("ajaxStart was unexpectedly called twice.");
end();
return;
}

ajaxStartCalled = true;
startTransport = connection.transport.name;

assert.comment("ajaxStart was called after receiving an init message over the " + startTransport + " transport.");
assert.comment("Delaying completion of ajaxStart for 5 seconds to allow for an invalid fallback/timeout.")

window.setTimeout(function () {
savedAjaxStart.apply(undefined, ajaxStartArgs);
}, 5000);
}

connection.start().done(function () {
assert.equal(connection.transport.name, startTransport, "Connection established over the " + startTransport + " transport!");
end();
});

// Cleanup
return function () {
$.signalR.transports._logic.ajaxStart = savedAjaxStart;
connection.stop();
};
});

QUnit.module("Fallback Facts", testUtilities.transports.webSockets.enabled);

Expand Down

0 comments on commit f59827a

Please sign in to comment.