Edit

ASP.NET Core SignalR JavaScript client

The ASP.NET Core SignalR JavaScript client library enables developers to call server-side SignalR hub code. This article describes how to use the APIs to connect to a SignalR hub, and call the JavaScript hub and client methods.

View or download sample code (how to download)

Install the SignalR client package

The SignalR JavaScript client library is delivered as an npm package. There are several ways to install the client library:

  • Run npm commands in Visual Studio Package Manager Console.
  • Run npm commands in the integrated terminal in Visual Studio Code.
  • Reference a copy of the client library hosted on a Content Delivery Network (CDN).
  • Use LibMan and install specific client library files from a CDN-hosted client library.

Install with npm

Run the following npm commands in the Package Manager Console window:

npm init -y
npm install @microsoft/signalr

npm installs the package contents in the node_modules\@microsoft\signalr\dist\browser folder.

Complete the setup:

  1. Create the wwwroot/lib/signalr folder.

  2. Copy the signalr.js file to the wwwroot/lib/signalr folder.

Now you can reference the installed SignalR JavaScript client in the <script> element. For example:

<script src="~/lib/signalr/signalr.js"></script>

Use a CDN

To use the client library without the npm prerequisite, reference a CDN-hosted copy of the client library. For example:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

The code example specifies version 6.0.1. To get the latest client library version, choose one of the following CDNs:

Install with LibMan

Another approach is to use LibMan and install only specific client library files from the CDN-hosted client library. For example, you can add only the minified JavaScript file to the project.

For details on this approach, see Add the SignalR client library.

Connect to a hub

The following code creates and starts a connection. The hub name is case insensitive:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Cross-origin connections (CORS)

Typically, browsers load connections from the same domain as the requested page. However, there are occasions when a connection to another domain is required.

For cross domain requests, the client code must use an absolute URL rather than a relative URL. When you use cross domain requests, change .withUrl("/chathub") to .withUrl("https://{App domain name}/chathub").

To prevent a malicious site from reading sensitive data from another site, cross-origin connections are disabled by default. To allow a cross-origin request, enable CORS:

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

The UseCors method must be called before calling the MapHub method.

Call hub methods from the client

JavaScript clients call public methods on hubs via the invoke method of the HubConnection. The invoke method accepts:

  • The name of the hub method.
  • Any arguments defined in the hub method.

In the following highlighted code, the method name on the hub is SendMessage. The second and third arguments passed to invoke map to the hub method's user and message arguments:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Calling hub methods from a client is supported only when using the Azure SignalR Service in Default mode. For more information, see Frequently Asked Questions (azure-signalr GitHub repository).

The invoke method returns a JavaScript Promise object. The Promise object is resolved with the return value (if any) when the method on the server returns. If the method on the server throws an error, the Promise object is rejected with the error message. To handle these cases, use async and await or the Promise object's then and catch methods.

JavaScript clients can also call public methods on hubs via the send method of the HubConnection. Unlike the invoke method, the send method doesn't wait for a response from the server. The send method returns a JavaScript Promise object. The Promise object is resolved when the message is sent to the server. If an error occurs when sending the message, the Promise object is rejected with the error message. To handle these cases, use async and await or the Promise object's then and catch methods.

Using send doesn't wait until the server receives the message, so it isn't possible to return data or errors from the server.

Call client methods from the hub

To receive messages from the hub, define a method by using the on method of the HubConnection. The on method accepts:

  • The name of the JavaScript client method.
  • Arguments the hub passes to the method.

In the following example, the method name is ReceiveMessage. The argument names are user and message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

The preceding code in connection.on runs when server-side code calls it by using the SendAsync method:

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

SignalR determines which client method to call by matching the method name and arguments defined in SendAsync and connection.on.

A best practice is to call the start method on the HubConnection after on. This approach ensures the handlers are registered before any messages are received.

Error handling and logging

Use console.error to output errors to the browser's console when the client can't connect or send a message:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Set up client-side log tracing by passing a logger and type of event to log when the connection is made. Messages are logged with the specified log level and higher. Available log levels are as follows:

Log level Logged data Description
signalR.LogLevel.Error Error messages Logs Error messages only.
signalR.LogLevel.Warning Warning messages about potential errors Logs Warning and Error messages.
signalR.LogLevel.Information Status messages without errors Logs Information, Warning, and Error messages.
signalR.LogLevel.Trace Trace messages Logs everything, including data transported between hub and client.

Use the configureLogging method on HubConnectionBuilder to configure the log level. Messages are logged to the browser console:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Reconnect clients

To reconnect with clients, you can set up automatic reconnection or configure the connection manually.

Automatically reconnect

The JavaScript client for SignalR can be configured to automatically reconnect by using the WithAutomaticReconnect method on HubConnectionBuilder. It doesn't automatically reconnect by default.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Without any parameters, WithAutomaticReconnect configures the client to wait 0, 2, 10, and 30 seconds respectively before trying each reconnect attempt. After four failed attempts, it stops trying to reconnect.

Before starting any reconnection, the HubConnection:

  • Transitions to the HubConnectionState.Reconnecting state and fires its onreconnecting callbacks.
  • Doesn't transition to the Disconnected state or trigger its onclose callbacks like a HubConnection without automatic reconnect configured.

The reconnection approach provides an opportunity to warn users that the connection is lost and to disable UI elements.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

If the client successfully reconnects within its first four attempts, the HubConnection transitions back to the Connected state and fires its onreconnected callbacks. This approach provides an opportunity to inform users the connection is now reestablished.

Because the connection looks entirely new to the server, a new connectionId is provided to the onreconnected callback.

The onreconnected callback's connectionId parameter is undefined if the HubConnection is configured to skip negotiation.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect doesn't configure the HubConnection to retry initial start failures, so start failures need to be handled manually:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

If the client doesn't successfully reconnect within its first four attempts, the HubConnection transitions to the Disconnected state and fires its onclose callbacks. This approach provides an opportunity to inform users that the connection is permanently lost and to try refreshing the page:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

To configure a custom number of reconnect attempts before disconnecting or change the reconnection timing, withAutomaticReconnect accepts an array of numbers representing the delay in milliseconds to wait before starting each reconnect attempt.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

The preceding example configures the HubConnection to start attempting reconnects immediately after the connection is lost. The default configuration also waits zero (0) seconds to attempt reconnecting.

  • If the first reconnect attempt fails, the second reconnect attempt also starts immediately instead of waiting 2 seconds as defined in the default configuration.

  • If the second reconnect attempt fails, the third reconnect attempt starts in 10 seconds, which is the same behavior defined in the default configuration.

  • The configured reconnection timing differs from the default behavior by stopping after the third reconnect attempt failure. In the default configuration, one more reconnect attempt is made after another 30 seconds.

For more control over the timing and number of automatic reconnect attempts, withAutomaticReconnect accepts an object implementing the IRetryPolicy interface, which has a single method named nextRetryDelayInMilliseconds. nextRetryDelayInMilliseconds takes a single argument with the type RetryContext. The RetryContext has three properties: previousRetryCount (type number), elapsedMilliseconds (type number), and retryReason (type Error).

  • Before the first reconnect attempt, previousRetryCount and elapsedMilliseconds are both zero (0), and retryReason is the Error that caused the lost connection.

  • After each failed retry attempt, previousRetryCount increments by one, elapsedMilliseconds updates to reflect the amount of time spent reconnecting so far in milliseconds, and retryReason is the Error that caused the last reconnect attempt to fail.

nextRetryDelayInMilliseconds must return either a number representing the number of milliseconds to wait before the next reconnect attempt or null if the HubConnection should stop reconnecting.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Alternatively, you can write code to reconnect the client manually, as demonstrated in the next section.

Manually reconnect

The following code demonstrates a typical manual reconnection approach:

  1. To start the connection, create a function. In this case, the start function.

  2. Call the start function in the connection's onclose event handler.

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Production implementations typically use an exponential back-off or retry a specified number of times.

Browser sleeping tab

Some browsers have a tab freezing or tab sleeping feature that reduces computer resource usage for inactive tabs. However, this functionality can cause SignalR connections to close and cause an undesirable user experience.

Browsers implement heuristics to determine if a tab should be put to sleep, such as:

  • Playing audio
  • Holding a web lock
  • Holding an IndexedDB lock
  • Being connected to a USB device
  • Capturing video or audio
  • Being mirrored
  • Capturing a window or display

Heuristics can change over time and also differ across browsers. Check the support matrix and figure out what method works best for your scenarios.

To avoid putting an app to sleep, the app should trigger one of the heuristics that the browser uses.

The following code example shows how to use a Web Lock to keep a tab awake and avoid an unexpected connection closure:

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

In the preceding code:

  • Web Locks are experimental. The conditional check confirms the browser supports Web Locks.
  • The JavaScript promise resolver (lockResolver) is stored so the lock can be released when it's acceptable for the tab to sleep.
  • When the connection is closing, the lock is released by calling lockResolver(). When the lock is released, the tab is allowed to sleep.

By Rachel Appel

The ASP.NET Core SignalR JavaScript client library enables developers to call server-side hub code.

View or download sample code (how to download)

Install the SignalR client package

The SignalR JavaScript client library is delivered as an npm package. The following sections outline different ways to install the client library.

Install with npm

For Visual Studio, run the following commands from Package Manager Console while in the root folder. For Visual Studio Code, run the following commands from the Integrated Terminal.

npm init -y
npm install @microsoft/signalr

npm installs the package contents in the node_modules\@microsoft\signalr\dist\browser folder. Create a new folder named signalr under the wwwroot\lib folder. Copy the signalr.js file to the wwwroot\lib\signalr folder.

Reference the SignalR JavaScript client in the <script> element. For example:

<script src="~/lib/signalr/signalr.js"></script>

Use a Content Delivery Network (CDN)

To use the client library without the npm prerequisite, reference a CDN-hosted copy of the client library. For example:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

The client library is available on the following CDNs:

Install with LibMan

LibMan can be used to install specific client library files from the CDN-hosted client library. For example, only add the minified JavaScript file to the project. For details on that approach, see Add the SignalR client library.

Connect to a hub

The following code creates and starts a connection. The hub's name is case insensitive:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Cross-origin connections

Typically, browsers load connections from the same domain as the requested page. However, there are occasions when a connection to another domain is required.

Important

The client code must use an absolute URL instead of a relative URL. Change .withUrl("/chathub") to .withUrl("https://myappurl/chathub").

To prevent a malicious site from reading sensitive data from another site, cross-origin connections are disabled by default. To allow a cross-origin request, enable it in the Startup class:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

Call hub methods from the client

JavaScript clients call public methods on hubs via the invoke method of the HubConnection. The invoke method accepts:

  • The name of the hub method.
  • Any arguments defined in the hub method.

In the following example, the method name on the hub is SendMessage. The second and third arguments passed to invoke map to the hub method's user and message arguments:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Note

Calling hub methods from a client is only supported when using the Azure SignalR Service in Default mode. For more information, see Frequently Asked Questions (azure-signalr GitHub repository).

The invoke method returns a JavaScript Promise. The Promise is resolved with the return value (if any) when the method on the server returns. If the method on the server throws an error, the Promise is rejected with the error message. Use async and await or the Promise's then and catch methods to handle these cases.

JavaScript clients can also call public methods on hubs via the send method of the HubConnection. Unlike the invoke method, the send method doesn't wait for a response from the server. The send method returns a JavaScript Promise. The Promise is resolved when the message has been sent to the server. If there is an error sending the message, the Promise is rejected with the error message. Use async and await or the Promise's then and catch methods to handle these cases.

Note

Using send doesn't wait until the server has received the message. Consequently, it's not possible to return data or errors from the server.

Call client methods from the hub

To receive messages from the hub, define a method using the on method of the HubConnection.

  • The name of the JavaScript client method.
  • Arguments the hub passes to the method.

In the following example, the method name is ReceiveMessage. The argument names are user and message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

The preceding code in connection.on runs when server-side code calls it using the SendAsync method:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR determines which client method to call by matching the method name and arguments defined in SendAsync and connection.on.

Note

As a best practice, call the start method on the HubConnection after on. Doing so ensures your handlers are registered before any messages are received.

Error handling and logging

Use try and catch with async and await or the Promise's catch method to handle client-side errors. Use console.error to output errors to the browser's console:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Set up client-side log tracing by passing a logger and type of event to log when the connection is made. Messages are logged with the specified log level and higher. Available log levels are as follows:

  • signalR.LogLevel.Error: Error messages. Logs Error messages only.
  • signalR.LogLevel.Warning: Warning messages about potential errors. Logs Warning, and Error messages.
  • signalR.LogLevel.Information: Status messages without errors. Logs Information, Warning, and Error messages.
  • signalR.LogLevel.Trace: Trace messages. Logs everything, including data transported between hub and client.

Use the configureLogging method on HubConnectionBuilder to configure the log level. Messages are logged to the browser console:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Reconnect clients

Automatically reconnect

The JavaScript client for SignalR can be configured to automatically reconnect using the withAutomaticReconnect method on HubConnectionBuilder. It won't automatically reconnect by default.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Without any parameters, withAutomaticReconnect() configures the client to wait 0, 2, 10, and 30 seconds respectively before trying each reconnect attempt, stopping after four failed attempts.

Before starting any reconnect attempts, the HubConnection will transition to the HubConnectionState.Reconnecting state and fire its onreconnecting callbacks instead of transitioning to the Disconnected state and triggering its onclose callbacks like a HubConnection without automatic reconnect configured. This provides an opportunity to warn users that the connection has been lost and to disable UI elements.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

If the client successfully reconnects within its first four attempts, the HubConnection will transition back to the Connected state and fire its onreconnected callbacks. This provides an opportunity to inform users the connection has been reestablished.

Since the connection looks entirely new to the server, a new connectionId will be provided to the onreconnected callback.

Warning

The onreconnected callback's connectionId parameter will be undefined if the HubConnection was configured to skip negotiation.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect() won't configure the HubConnection to retry initial start failures, so start failures need to be handled manually:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

If the client doesn't successfully reconnect within its first four attempts, the HubConnection will transition to the Disconnected state and fire its onclose callbacks. This provides an opportunity to inform users the connection has been permanently lost and recommend refreshing the page:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

In order to configure a custom number of reconnect attempts before disconnecting or change the reconnect timing, withAutomaticReconnect accepts an array of numbers representing the delay in milliseconds to wait before starting each reconnect attempt.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

The preceding example configures the HubConnection to start attempting reconnects immediately after the connection is lost. This is also true for the default configuration.

If the first reconnect attempt fails, the second reconnect attempt will also start immediately instead of waiting 2 seconds like it would in the default configuration.

If the second reconnect attempt fails, the third reconnect attempt will start in 10 seconds which is again like the default configuration.

The custom behavior then diverges again from the default behavior by stopping after the third reconnect attempt failure instead of trying one more reconnect attempt in another 30 seconds like it would in the default configuration.

If you want even more control over the timing and number of automatic reconnect attempts, withAutomaticReconnect accepts an object implementing the IRetryPolicy interface, which has a single method named nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds takes a single argument with the type RetryContext. The RetryContext has three properties: previousRetryCount, elapsedMilliseconds and retryReason which are a number, a number and an Error respectively. Before the first reconnect attempt, both previousRetryCount and elapsedMilliseconds will be zero, and the retryReason will be the Error that caused the connection to be lost. After each failed retry attempt, previousRetryCount will be incremented by one, elapsedMilliseconds will be updated to reflect the amount of time spent reconnecting so far in milliseconds, and the retryReason will be the Error that caused the last reconnect attempt to fail.

nextRetryDelayInMilliseconds must return either a number representing the number of milliseconds to wait before the next reconnect attempt or null if the HubConnection should stop reconnecting.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Alternatively, you can write code that will reconnect your client manually as demonstrated in Manually reconnect.

Manually reconnect

The following code demonstrates a typical manual reconnection approach:

  1. A function (in this case, the start function) is created to start the connection.
  2. Call the start function in the connection's onclose event handler.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Production implementations typically use an exponential back-off or retry a specified number of times.

Browser sleeping tab

Some browsers have a tab freezing or sleeping feature to reduce computer resource usage for inactive tabs. This can cause SignalR connections to close and may result in an unwanted user experience. Browsers use heuristics to figure out if a tab should be put to sleep, such as:

  • Playing audio
  • Holding a web lock
  • Holding an IndexedDB lock
  • Being connected to a USB device
  • Capturing video or audio
  • Being mirrored
  • Capturing a window or display

Note

These heuristics may change over time or differ between browsers. Check your support matrix and figure out what method works best for your scenarios.

To avoid putting an app to sleep, the app should trigger one of the heuristics that the browser uses.

The following code example shows how to use a Web Lock to keep a tab awake and avoid an unexpected connection closure.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

For the preceding code example:

  • Web Locks are experimental. The conditional check confirms that the browser supports Web Locks.
  • The promise resolver (lockResolver) is stored so that the lock can be released when it's acceptable for the tab to sleep.
  • When closing the connection, the lock is released by calling lockResolver(). When the lock is released, the tab is allowed to sleep.

Additional resources