ASP.NET Core SignalR Cliente JavaScript

Por Rachel Appel

A biblioteca de clientes JavaScript do SignalR do ASP.NET Core permite que os desenvolvedores chamem o código do hub do SignalR no lado do servidor.

Instalar o pacote do cliente SignalR

A biblioteca do cliente JavaScript do SignalR é fornecida como um pacote npm. As seções a seguir descrevem diferentes maneiras de instalar a biblioteca de clientes.

Instalar com npm

Execute os seguintes comandos no Console do Gerenciador de Pacotes:

npm init -y
npm install @microsoft/signalr

O npm instala o conteúdo do pacote na pasta node_modules\@microsoft\signalr\dist\browser. Crie a pasta wwwroot/lib/signalr. Copie o arquivo signalr.js para a pasta wwwroot/lib/signalr.

Referencie o cliente JavaScript do SignalR no elemento <script>. Por exemplo:

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

Usar uma CDN (Rede de Distribuição de Conteúdo)

Para usar a biblioteca de clientes sem o pré-requisito npm, faça referência a uma cópia hospedada por CDN da biblioteca de clientes. Por exemplo:

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

A biblioteca de clientes está disponível nas seguintes CDNs:

Instalar com o LibMan

O LibMan pode ser usado para instalar arquivos de biblioteca de clientes específicos da biblioteca de clientes hospedada pela CDN. Por exemplo, adicione apenas o arquivo JavaScript minificado ao projeto. Para obter detalhes sobre essa abordagem, confira Adicionar a biblioteca de clientes do SignalR.

Conectar-se a um hub

O código a seguir cria e inicia uma conexão. O nome do hub não diferencia maiúsculas de minúsculas:

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();

CORS (conexões entre origens)

Normalmente, os navegadores carregam conexões do mesmo domínio que a página solicitada. No entanto, há ocasiões em que uma conexão com outro domínio é necessária.

Ao fazer solicitações entre domínios, o código do cliente deve usar uma URL absoluta em vez de uma URL relativa. Para solicitações entre domínios, altere .withUrl("/chathub") para .withUrl("https://{App domain name}/chathub").

Para evitar que um site mal-intencionado leia dados confidenciais de outro site, as conexões entre origens são desabilitadas por padrão. Para permitir uma solicitação entre origens, habilite o 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();

UseCors deve ser chamado antes de chamar MapHub.

Chamar métodos de hub do cliente

Os clientes JavaScript chamam métodos públicos em hubs por meio do método invoke do HubConnection. O método invoke aceita:

  • O nome do método de hub.
  • Todos os argumentos definidos no método de hub.

No código realçado a seguir, o nome do método no hub é SendMessage. O segundo e o terceiro argumentos passados para o invoke mapear os argumentos user e message do método de hub:

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

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço do do Azure no modo SignalRPadrão. Para obter mais informações, confira Perguntas frequentes (repositório azure-signalr do GitHub).

O método invoke retorna um JavaScript Promise. O Promise é resolvido com o valor retornado (se houver) quando o método no servidor retorna. Se o método no servidor gerar um erro, o Promise será rejeitado com a mensagem de erro. Use async e await ou os métodos then e catch de Promise para lidar com esses casos.

Os clientes JavaScript também podem chamar métodos públicos em hubs por meio do método send do HubConnection. Ao contrário do método invoke, o método send não aguarda uma resposta do servidor. O método send retorna um JavaScript Promise. O Promise é resolvido quando a mensagem é enviada ao servidor. Se houver um erro ao enviar a mensagem, o Promise será rejeitado com a mensagem de erro. Use async e await ou os métodos then e catch de Promise para lidar com esses casos.

O uso de sendnão aguarda até que o servidor tenha recebido a mensagem. Consequentemente, não é possível retornar dados ou erros do servidor.

Chamar métodos de cliente do hub

Para receber mensagens do hub, defina um método usando o método on do HubConnection.

  • O nome do método de cliente JavaScript.
  • Argumentos que o hub passa para o método .

No exemplo a seguir, o nome do método é ReceiveMessage. Os nomes dos argumentos são user e message:

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

O código anterior em connection.on é executado quando o código do lado do servidor o chama usando o método SendAsync:

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);
    }
}

O SignalR determina qual método de cliente chamar correspondendo ao nome do método e aos argumentos definidos em SendAsync e connection.on.

Uma melhor prática é chamar o método start no HubConnection após on. Isso garante que os manipuladores sejam registrados antes que as mensagens sejam recebidas.

Registro em log e tratamento de erros

Use console.error para gerar erros no console do navegador quando o cliente não puder se conectar ou enviar uma mensagem:

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

Configure o rastreamento de log do lado do cliente passando um agente e o tipo de evento para registrar em log quando a conexão for feita. As mensagens são registradas com o nível de log especificado e superior. Os níveis de log disponíveis são os seguintes:

  • signalR.LogLevel.Error: mensagens de erro. Registra somente mensagens de Error.
  • signalR.LogLevel.Warning: mensagens de aviso sobre possíveis erros. Registra mensagens de Warning e Error.
  • signalR.LogLevel.Information: mensagens de status sem erros. Registra mensagens de Information, Warning e Error.
  • signalR.LogLevel.Trace: rastreia mensagens. Registra tudo, incluindo dados transportados entre o hub e o cliente.

Use o método configureLogging no HubConnectionBuilder para configurar o nível de log. As mensagens são registradas no console do navegador:

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

Reconectar clientes

Reconectar automaticamente

O cliente JavaScript para SignalR pode ser configurado para se reconectar automaticamente usando o método WithAutomaticReconnect no HubConnectionBuilder. Ele não se reconectará automaticamente por padrão.

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

Sem parâmetros, WithAutomaticReconnect configura o cliente para aguardar 0, 2, 10 e 30 segundos, respectivamente, antes de tentar cada tentativa de reconexão. Após quatro tentativas com falha, ele para de tentar se reconectar.

Antes de iniciar qualquer tentativa de reconexão, o HubConnection:

  • Faz a transição para o estado HubConnectionState.Reconnecting e dispara seus retornos de chamada onreconnecting.
  • Não faz a transição para o estado Disconnected e dispara seus retornos de chamada onclose como um HubConnection sem reconexão automática configurada.

A abordagem de reconexão oferece uma oportunidade para:

  • Avisar os usuários de que a conexão foi perdida.
  • Desabilitar elementos da interface do usuário.
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);
});

Se o cliente se reconectar com êxito nas primeiras quatro tentativas, o HubConnection fará a transição de volta ao estado Connected e disparará seus retornos de chamada onreconnected. Isso oferece uma oportunidade de informar aos usuários que a conexão foi restabelecida.

Como a conexão parece totalmente nova para o servidor, um novo connectionId será fornecido ao retorno de chamada onreconnected.

O parâmetro connectionId do retorno de chamada onreconnected será indefinido se o HubConnection estiver configurado para ignorar a negociação.

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 não configurará o HubConnection para repetir falhas iniciais de início, portanto, as falhas de inicialização precisam ser tratadas manualmente:

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);
    }
};

Se o cliente não se reconectar com êxito em suas quatro primeiras tentativas, o HubConnection fará a transição para o estado Disconnected e disparará seus retornos de chamada onclose. Isso oferece uma oportunidade para informar os usuários:

  • A conexão foi perdida permanentemente.
  • Tente atualizar a página:
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);
});

Para configurar um número personalizado de tentativas de reconexão antes de desconectar ou alterar o tempo de reconexão, withAutomaticReconnect aceita uma matriz de números que representam o atraso em milissegundos para aguardar antes de iniciar cada tentativa de reconexão.

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

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

O exemplo anterior configura o HubConnection para começar a tentar se conectar novamente imediatamente após a conexão ser perdida. A configuração padrão também aguarda zero segundos para tentar se reconectar.

Se a primeira tentativa de reconexão falhar, a segunda tentativa de reconexão também será iniciada imediatamente, em vez de esperar 2 segundos usando a configuração padrão.

Se a segunda tentativa de reconexão falhar, a terceira tentativa de reconexão será iniciada em 10 segundos, como a configuração padrão.

O tempo de reconexão configurado difere do comportamento padrão ao parar após a falha da terceira tentativa de reconexão em vez de tentar mais uma tentativa de reconexão em mais 30 segundos.

Para ter mais controle sobre o tempo e o número de tentativas de reconexão automática, withAutomaticReconnect aceita um objeto que implementa a interface de IRetryPolicy, que tem um único método chamado nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds usa um único argumento com o tipo RetryContext. O RetryContext tem três propriedades: previousRetryCount, elapsedMilliseconds e retryReason, que são um number, um number e um Error, respectivamente. Antes da primeira tentativa de reconexão, previousRetryCount e elapsedMilliseconds serão zero e o retryReason será o erro que causou a perda da conexão. Após cada tentativa de repetição com falha, previousRetryCount será incrementado em um, elapsedMilliseconds será atualizado para refletir o tempo gasto na reconexão até agora em milissegundos e retryReason será o erro que causou a falha da última tentativa de reconexão.

nextRetryDelayInMilliseconds deve retornar um número que represente o número de milissegundos a serem aguardados antes da próxima tentativa de reconexão ou null se o HubConnection deve parar de se reconectar.

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();

Como alternativa, o código pode ser escrito para reconectar o cliente manualmente, conforme demonstrado na seção a seguir.

Reconectar manualmente

O código a seguir demonstra uma abordagem típica de reconexão manual:

  1. Uma função (nesse caso, a função start) é criada para iniciar a conexão.
  2. Chame a função start no manipulador de eventos onclose da conexão.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

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

As implementações de produção normalmente usam um back-off exponencial ou repetem um número especificado de vezes.

Guia de suspensão do navegador

Alguns navegadores têm um recurso de congelamento ou suspensão de guia para reduzir o uso de recursos do computador para guias inativas. Isso pode fazer com que conexões do SignalR sejam fechadas e possam resultar em uma experiência de usuário indesejada. Os navegadores usam heurística para descobrir se uma guia deve ser colocada em suspensão, como:

  • Reprodução de áudio
  • Manter um bloqueio da Web
  • Manter um bloqueio de IndexedDB
  • Estar conectado a um dispositivo USB
  • Capturar vídeo ou áudio
  • Estar espelhado
  • Capturar uma janela ou exibição

A heurística do navegador pode mudar ao longo do tempo e pode diferir entre navegadores. Verifique a matriz de suporte e descubra qual método funciona melhor para seus cenários.

Para evitar colocar um aplicativo em suspensão, o aplicativo deve disparar uma das heurísticas que o navegador usa.

O exemplo de código a seguir mostra como usar um Bloqueio da Web para manter uma guia ativa e evitar um fechamento de conexão inesperado.

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;
    });
}

Para o exemplo de código anterior:

  • Os Bloqueios da Web são experimentais. A verificação condicional confirma que o navegador dá suporte a Bloqueios da Web.
  • O resolvedor de promessas, lockResolver, é armazenado para que o bloqueio possa ser liberado quando for aceitável para a suspensão da guia.
  • Ao fechar a conexão, o bloqueio é liberado chamando lockResolver(). Quando o bloqueio é liberado, a guia tem permissão para suspensão.

Recursos adicionais

Por Rachel Appel

A biblioteca de clientes JavaScript do SignalR do ASP.NET Core permite que os desenvolvedores chamem o código do hub do lado do servidor.

Exibir ou baixar código de exemplo (como baixar)

Instalar o pacote do cliente SignalR

A biblioteca do cliente JavaScript do SignalR é fornecida como um pacote npm. As seções a seguir descrevem diferentes maneiras de instalar a biblioteca de clientes.

Instalar com npm

Para o Visual Studio, execute os comandos a seguir no Console do Gerenciador de Pacotes enquanto estiver na pasta raiz. Para o Visual Studio Code, execute os comandos a seguir no Terminal Integrado.

npm init -y
npm install @microsoft/signalr

O npm instala o conteúdo do pacote na pasta node_modules\@microsoft\signalr\dist\browser. Crie uma nova pasta chamada signalr na pasta wwwroot\lib. Copie o arquivo signalr.js para a pasta wwwroot/lib/signalr.

Referencie o cliente JavaScript do SignalR no elemento <script>. Por exemplo:

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

Usar uma CDN (Rede de Distribuição de Conteúdo)

Para usar a biblioteca de clientes sem o pré-requisito npm, faça referência a uma cópia hospedada por CDN da biblioteca de clientes. Por exemplo:

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

A biblioteca de clientes está disponível nas seguintes CDNs:

Instalar com o LibMan

O LibMan pode ser usado para instalar arquivos de biblioteca de clientes específicos da biblioteca de clientes hospedada pela CDN. Por exemplo, adicione apenas o arquivo JavaScript minificado ao projeto. Para obter detalhes sobre essa abordagem, confira Adicionar a biblioteca de clientes do SignalR.

Conectar-se a um hub

O código a seguir cria e inicia uma conexão. O nome do hub não diferencia maiúsculas de minúsculas:

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();

Conexões entre origens

Normalmente, os navegadores carregam conexões do mesmo domínio que a página solicitada. No entanto, há ocasiões em que uma conexão com outro domínio é necessária.

Importante

O código do cliente deve usar uma URL absoluta em vez de uma URL relativa. Alterar .withUrl("/chathub") para .withUrl("https://myappurl/chathub").

Para evitar que um site mal-intencionado leia dados confidenciais de outro site, as conexões entre origens são desabilitadas por padrão. Para permitir uma solicitação entre origens, habilite-a na classe Startup:

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");
            });
        }
    }
}

Chamar métodos de hub do cliente

Os clientes JavaScript chamam métodos públicos em hubs por meio do método invoke do HubConnection. O método invoke aceita:

  • O nome do método de hub.
  • Todos os argumentos definidos no método de hub.

No exemplo a seguir, o nome do método mo hub é SendMessage. O segundo e o terceiro argumentos passados para o invoke mapear os argumentos user e message do método de hub:

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

Observação

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço SignalR do Azure no modo Padrão. Para obter mais informações, confira Perguntas frequentes (repositório azure-signalr do GitHub).

O método invoke retorna um JavaScript Promise. O Promise é resolvido com o valor retornado (se houver) quando o método no servidor retorna. Se o método no servidor gerar um erro, o Promise será rejeitado com a mensagem de erro. Use async e await ou os métodos then e catch de Promise para lidar com esses casos.

Os clientes JavaScript também podem chamar métodos públicos em hubs por meio do método send do HubConnection. Ao contrário do método invoke, o método send não aguarda uma resposta do servidor. O método send retorna um JavaScript Promise. O Promise é resolvido quando a mensagem é enviada ao servidor. Se houver um erro ao enviar a mensagem, o Promise será rejeitado com a mensagem de erro. Use async e await ou os métodos then e catch de Promise para lidar com esses casos.

Observação

O uso de send não aguarda até que o servidor tenha recebido a mensagem. Consequentemente, não é possível retornar dados ou erros do servidor.

Chamar métodos de cliente do hub

Para receber mensagens do hub, defina um método usando o método on do HubConnection.

  • O nome do método de cliente JavaScript.
  • Argumentos que o hub passa para o método .

No exemplo a seguir, o nome do método é ReceiveMessage. Os nomes dos argumentos são user e message:

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

O código anterior em connection.on é executado quando o código do lado do servidor o chama usando o método SendAsync:

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

O SignalR determina qual método de cliente chamar correspondendo ao nome do método e aos argumentos definidos em SendAsync e connection.on.

Observação

Como uma melhor prática, chame o método start no HubConnection após on. Isso garante que os manipuladores sejam registrados antes que as mensagens sejam recebidas.

Registro em log e tratamento de erros

Use try e catch com async e await ou o método catch de Promise para lidar com erros do lado do cliente. Use console.error para gerar erros no console do navegador:

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

Configure o rastreamento de log do lado do cliente passando um agente e o tipo de evento para registrar em log quando a conexão for feita. As mensagens são registradas com o nível de log especificado e superior. Os níveis de log disponíveis são os seguintes:

  • signalR.LogLevel.Error: mensagens de erro. Registra somente mensagens de Error.
  • signalR.LogLevel.Warning: mensagens de aviso sobre possíveis erros. Registra mensagens de Warning e Error.
  • signalR.LogLevel.Information: mensagens de status sem erros. Registra mensagens de Information, Warning e Error.
  • signalR.LogLevel.Trace: rastreia mensagens. Registra tudo, incluindo dados transportados entre o hub e o cliente.

Use o método configureLogging no HubConnectionBuilder para configurar o nível de log. As mensagens são registradas no console do navegador:

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

Reconectar clientes

Reconectar automaticamente

O cliente JavaScript para SignalR pode ser configurado para se reconectar automaticamente usando o método withAutomaticReconnect no HubConnectionBuilder. Ele não se reconectará automaticamente por padrão.

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

Sem parâmetros, withAutomaticReconnect() configura o cliente para aguardar 0, 2, 10 e 30 segundos, respectivamente, antes de tentar cada tentativa de reconexão, parando após quatro tentativas com falha.

Antes de iniciar qualquer tentativa de reconexão, o HubConnection fará a transição para o estado HubConnectionState.Reconnecting e disparará seus retornos de chamada onreconnecting em vez de fazer a transição para o estado Disconnected e disparará seus retornos de chamada onclose como um HubConnection sem reconexão automática configurada. Isso oferece uma oportunidade de avisar os usuários de que a conexão foi perdida e desabilitar elementos da interface do usuário.

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);
});

Se o cliente se reconectar com êxito em suas quatro primeiras tentativas, o HubConnection fará a transição de volta para o estado Connected e disparará seus retornos de chamada onreconnected. Isso oferece uma oportunidade de informar aos usuários que a conexão foi restabelecida.

Como a conexão parece totalmente nova para o servidor, um novo connectionId será fornecido aos retornos de chamada onreconnected.

Aviso

O parâmetro connectionId do retorno de chamada onreconnected será indefinido se o HubConnection estiver configurado para ignorar a negociação.

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() não configurará o HubConnection para repetir falhas iniciais de início, portanto, as falhas de inicialização precisam ser tratadas manualmente:

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);
    }
};

Se o cliente não se reconectar com êxito em suas quatro primeiras tentativas, o HubConnection fará a transição para o estado Disconnected e disparará seus retornos de chamada onclose. Isso oferece uma oportunidade para informar aos usuários que a conexão foi perdida permanentemente e recomenda atualizar a página:

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);
});

Para configurar um número personalizado de tentativas de reconexão antes de desconectar ou alterar o tempo de reconexão, withAutomaticReconnect aceita uma matriz de números que representam o atraso em milissegundos para aguardar antes de iniciar cada tentativa de reconexão.

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

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

O exemplo anterior configura o HubConnection para começar a tentar se conectar novamente imediatamente após a conexão ser perdida. Isso também é verdadeiro para a configuração padrão.

Se a primeira tentativa de reconexão falhar, a segunda tentativa de reconexão também será iniciada imediatamente em vez de aguardar 2 segundos como ocorreria na configuração padrão.

Se a segunda tentativa de reconexão falhar, a terceira tentativa de reconexão será iniciada em 10 segundos, o que é, novamente, como a configuração padrão.

Em seguida, o comportamento personalizado diverge novamente do comportamento padrão, parando após a falha da terceira tentativa de reconexão, em vez de tentar mais uma tentativa de reconexão em mais 30 segundos, como faria na configuração padrão.

Se você quiser ter ainda mais controle sobre o tempo e o número de tentativas de reconexão automática, withAutomaticReconnect aceitará um objeto que implementa a interface IRetryPolicy, que tem um único método chamado nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds usa um único argumento com o tipo RetryContext. O RetryContext tem três propriedades: previousRetryCount, elapsedMilliseconds e retryReason, que são um number, um number e um Error, respectivamente. Antes da primeira tentativa de reconexão, previousRetryCount e elapsedMilliseconds serão zero e o retryReason será o erro que causou a perda da conexão. Após cada tentativa de repetição com falha, previousRetryCount será incrementado em um, elapsedMilliseconds será atualizado para refletir o tempo gasto na reconexão até agora em milissegundos e retryReason será o erro que causou a falha da última tentativa de reconexão.

nextRetryDelayInMilliseconds deve retornar um número que represente o número de milissegundos a serem aguardados antes da próxima tentativa de reconexão ou null se o HubConnection deve parar de se reconectar.

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();

Como alternativa, você pode escrever código que reconectará o cliente manualmente, conforme demonstrado em Reconectar manualmente.

Reconectar manualmente

O código a seguir demonstra uma abordagem típica de reconexão manual:

  1. Uma função (nesse caso, a função start) é criada para iniciar a conexão.
  2. Chame a função start no manipulador de eventos onclose da conexão.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

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

As implementações de produção normalmente usam um back-off exponencial ou repetem um número especificado de vezes.

Guia de suspensão do navegador

Alguns navegadores têm um recurso de congelamento ou suspensão de guia para reduzir o uso de recursos do computador para guias inativas. Isso pode fazer com que conexões do SignalR sejam fechadas e possam resultar em uma experiência de usuário indesejada. Os navegadores usam heurística para descobrir se uma guia deve ser colocada em suspensão, como:

  • Reprodução de áudio
  • Manter um bloqueio da Web
  • Manter um bloqueio de IndexedDB
  • Estar conectado a um dispositivo USB
  • Capturar vídeo ou áudio
  • Estar espelhado
  • Capturar uma janela ou exibição

Observação

Essas heurísticas podem mudar ao longo do tempo ou diferir entre navegadores. Verifique a matriz de suporte e descubra qual método funciona melhor para seus cenários.

Para evitar colocar um aplicativo em suspensão, o aplicativo deve disparar uma das heurísticas que o navegador usa.

O exemplo de código a seguir mostra como usar um Bloqueio da Web para manter uma guia ativa e evitar um fechamento de conexão inesperado.

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;
    });
}

Para o exemplo de código anterior:

  • Os Bloqueios da Web são experimentais. A verificação condicional confirma que o navegador dá suporte a Bloqueios da Web.
  • O resolvedor de promessas, (lockResolver), é armazenado para que o bloqueio possa ser liberado quando for aceitável para a suspensão da guia.
  • Ao fechar a conexão, o bloqueio é liberado chamando lockResolver(). Quando o bloqueio é liberado, a guia tem permissão para suspensão.

Recursos adicionais