2

I use SignalR on a page that includes the standard google tag manager script. That script is loaded asynchronously (defer=true).

SignalR is used for some long running tasks that can be triggered on that page, so I disable the buttons that trigger those actions until SignalR is ready, i.e. when the call to connect() has returned successfully.

Some customers report that I takes forever for the buttons to be activated. Those customers are on a corporate network where google tag manager is blocked, and for some reasons the request to googletagmanager.com doesn't always return immediately with status 403. Sometimes it only returns the 403 after a timeout (like after 2 minutes).

I don't care if customers block google tag manager, but my SignalR connect call (on document ready) will stall until the call to google tag manager is finished. It shouldn't stall, because they should be logically independent and the script is loaded with defer=true.

I am lost to why the connect call won't finish sooner. Only thing I found was "don't use session state with SignalR" (e.g. SignalR doesn't use Session on server). I use the Session object in my web application, but not from the SignalR hub.

Using .NET Framework 4.5 and SignalR 2.4.1

SignalR startup on server is standard:

using Microsoft.Owin;
using Owin;
using MyNamespace;

[assembly: OwinStartup(typeof(SignalRStartup))]
namespace MyNamespace
{
    public class SignalRStartup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }
    }
}

Hub implementation doesn't do anything with the Session

public class CollectiefHub : Hub
{
    private static readonly IHubContext sHubContext = GlobalHost.ConnectionManager.GetHubContext<CollectiefHub>();
    public const string MsgCalcReady = "CalcReady";
    public const string MsgDocGenReady = "DocGenReady";

    public Task JoinGroup(string groupName)
    {
        return sHubContext.Groups.Add(Context.ConnectionId, groupName);
    }

    public Task LeaveGroup(string groupName)
    {
        return sHubContext.Groups.Remove(Context.ConnectionId, groupName);
    }

    private static async Task Send(string group, object msg)
    {
        await sHubContext.Clients.Group(group).receive(msg);
    }

    public static async void SendMessage(string groupName, object message)
    {
        await Send(groupName, message);
    }

    public static async void HandleListDone(string groupName, Dictionary<string, string> list)
    {
        await Send(groupName, new { list });
    }

    public static async void HandleListProgress(string groupName, string guid, int percentage)
    {
        await Send(groupName, new { guid, percentage });
    }
}

Javascript code:

var AsyncCalc = {
    connection: $.hubConnection(),
    collectiefHub: null,
    group: window.location.pathname.split("/")[2], // Contains identifier
    calcDoneEvent: "asyncCalcDone",
    asyncObject: null,
    connected: false,

    setupSignalR: function() {
        AsyncCalc.collectiefHub = AsyncCalc.connection.createHubProxy("collectiefHub");
        AsyncCalc.collectiefHub.on("receive", function (message) {
            // Handle specific messages here
        });

        // Start connection
        AsyncCalc._start();
    },

    _start: function () {
        AsyncCalc.connection.url = "/" + window.location.pathname.split("/")[1] + "/signalr";
        AsyncCalc.connection.start().done(function () {
            AsyncCalc.connected = true;
            AsyncCalc.collectiefHub.invoke("joinGroup", AsyncCalc.group);
            $(".async").removeClass("disabled");
        });
    }
};

$(function () {
    AsyncCalc.setupSignalR();
});

Google tag manager is simply a javascript js file that is included in the <head> section of my page, like this:

<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX"></script>

Any ideas?

Moolie
  • 569
  • 2
  • 7
  • 17
  • Can you show some of your code? This could be caused by a number of reasons. Let's see your javascript http request and signalr startup code. Are you making these requests directly from javascript (the google request), or do they go to your server first? Do they share the same HttpClient? – fix Oct 12 '21 at 16:30
  • @fix Added the relevant code. Google tag manager script is loaded by the client, as it is simply included in the head section of the page. The server is only involved in establishing the SignalR connection with the client. That's why I am puzzled by the fact that the SignalR connection isn't ready until the client call to googletagmanager.com has finished. Google tag manager itself seems irrelevant, that's just an external script that's blocked by some corporate firewall. It could be another external script, it just happens to be this script that triggers the problem. – Moolie Oct 18 '21 at 09:52

1 Answers1

0

Even if you use the defer attribute on the blocked script, the script must still attempt to run before the DOMContentLoaded event fires and that's the event that jQuery's ready function waits for. I'm not sure if the same applies to the async attribute. You want to call AsyncCalc.setupSignalR(); as soon as the buttons that need to be enabled are on the page rather than waiting for the DOMContentLoaded event. Moving your script tag to the bottom of the HTML body might be enough depending on how the buttons are loaded. If not, you can use a MutationObserver or something similar to wait for the buttons.

If the buttons load asynchronously, you could still call the AsyncCalc.setupSignalR(); function immediately and only wait for the buttons to appear to run $(".async").removeClass("disabled");.

  • I did some more debugging with Fiddler and added some logging to the console. the `DOMContentLoaded` event fires and `AsyncCalc.setupSignalR()` is called. `AsyncCalc.connection.start()` (in setupSignalR is called), but there it stalls. There is no negotiate call until every remote script is loaded. – Moolie Oct 29 '21 at 13:01
  • And while the typical signalR calls (negotiate/connect/start) don't show up in network traffic until the gtm script is loaded, other ajax calls from client to server execute just fine. The server is accepting connections, but somehow `AsyncCalc.connection.start()` doesn't result in a call to the server right away. – Moolie Oct 29 '21 at 13:16
  • Which jQuery plugin are you using to provide `$.hubConnection()`? The only one I've been able to find is deprecated. The official MS client doesn't depend on jQuery: https://www.npmjs.com/package/@microsoft/signalr – Patrick Stephansen Nov 02 '21 at 08:49