ASP.NET Core SignalR JavaScript クライアント

作成者: Rachel Appel

ASP.NET Core SignalR JavaScript クライアント ライブラリを使用すると、開発者はサーバー側のSignalRハブ コードを呼び出すことができます。

SignalR クライアント パッケージをインストールする

SignalR JavaScript クライアント ライブラリは、npm パッケージとして提供されます。 以下のセクションでは、クライアント ライブラリをインストールするためのさまざまな方法の概要について説明します。

npm でインストールする

パッケージ マネージャー コンソールから、次のコマンドを実行します:

npm init -y
npm install @microsoft/signalr

npm によってパッケージの内容が node_modules\@microsoft\signalr\dist\browser フォルダーにインストールされます。 wwwroot/lib/signalr フォルダーを作成します。 signalr.js ファイルを、wwwroot/lib/signalr フォルダーにコピーします。

<script> 要素で SignalR JavaScript クライアントを参照します。 次に例を示します。

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

コンテンツ配信ネットワーク (CDN) を使用する

npm の前提条件なしでクライアント ライブラリを使用するには、クライアント ライブラリの CDN でホストされているコピーを参照します。 次に例を示します。

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

クライアント ライブラリは、次の CDN で利用できます。

LibMan でインストールする

LibMan を使うと、CDN でホストされたクライアント ライブラリから特定のクライアント ライブラリ ファイルをインストールできます。 たとえば、縮小された JavaScript ファイルのみをプロジェクトに追加します。 その方法について詳しくは、「SignalR クライアント ライブラリを追加する」をご覧ください。

ハブに接続する

次のコードにより、接続が作成されて開始されます。 ハブの名前は大文字と小文字が区別されます。

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)

通常、ブラウザーでは、要求されたページと同じドメインから接続が読み込まれます。 ただし、別のドメインへの接続が必要になる場合もあります。

クロスドメイン要求を行う場合、クライアント コードでは相対 URL ではなく絶対 URL を使用する必要があります。 クロス ドメイン要求の場合は、.withUrl("/chathub").withUrl("https://{App domain name}/chathub") に変更します。

悪意のあるサイトによって別のサイトから機密データが読み取られるのを防ぐため、クロスオリジン接続は既定で無効になっています。 クロスオリジン要求を許可するには、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();

MapHub を呼び出す前に UseCors を呼び出す必要があります。

クライアントからハブ メソッドを呼び出す

JavaScript クライアントによるハブ上のパブリック メソッドの呼び出しは、HubConnectioninvoke メソッドで行われます。 invokeメソッドは次のものを受け取ります。

  • ハブ メソッドの名前。
  • ハブ メソッドで定義されているすべての引数。

次の強調表示されたコードでは、ハブのメソッド名は SendMessage です。 invoke に渡される 2 番目と 3 番目の引数は、ハブ メソッドの user および message 引数にマップします。

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

クライアントからのハブのメソッドの呼び出しは、Azure SignalRService を "既定" モードで使用している場合にのみサポートされます。 詳細については、「Frequently Asked Questions」(azure-signalr GitHub リポジトリ) をご覧ください。

invoke メソッドからは、JavaScript Promise が返されます。 Promise は、サーバー上のメソッドから返った時点で、戻り値 (ある場合) によって解決されます。 サーバー上のメソッドがエラーをスローした場合、Promise はエラー メッセージで却下されます。 これらのケースを処理するには、asyncawait または Promisethen および catch メソッドを使用します。

JavaScript クライアントによるハブ上のパブリック メソッドの呼び出しは、HubConnectionsend メソッドで行うこともできます。 invoke メソッドとは異なり、send メソッドはサーバーからの応答を待機しません。 send メソッドからは、JavaScript Promise が返されます。 Promise は、メッセージがサーバーに送信された時点で解決されます。 メッセージの送信でエラーが発生した場合、Promise はエラー メッセージで却下されます。 これらのケースを処理するには、asyncawait または Promisethen および catch メソッドを使用します。

send を使用すると、サーバーがメッセージを受信するまでの待機が "行われません"。 このため、サーバーからデータまたはエラーを返すことはできません。

ハブからクライアントのメソッドを呼び出す

ハブからメッセージを受け取るには、HubConnectionon メソッドを使ってメソッドを定義します。

  • JavaScript クライアント メソッドの名前。
  • ハブがメソッドに渡す引数。

次の例では、メソッドの名前は ReceiveMessage です。 引数の名前は usermessage です。

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

前の connection.on のコードは、サーバー側のコードで 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);
    }
}

SignalR により、SendAsyncconnection.on で定義されているメソッド名と引数を突き合わせることで、呼び出すクライアント メソッドが決定されます。

ベスト プラクティスとしては、on の後で HubConnectionstart メソッドを呼び出します。 これにより、メッセージを受信する前にハンドラーが確実に登録されます。

エラー処理とログ記録

クライアントが接続またはメッセージを送信できない場合に、ブラウザーのコンソールにエラーを出力するには、console.error を使用します:

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

接続するときにログに記録するイベントのロガーと種類を渡すことによって、クライアント側のログ トレースを設定します。 指定したログ レベル以上のメッセージがログに記録されます。 使用可能なログ レベルは次のとおりです。

  • signalR.LogLevel.Error: エラー メッセージ。 Error メッセージのみをログに記録します。
  • signalR.LogLevel.Warning: 潜在的なエラーに関する警告メッセージ。 WarningError のメッセージをログに記録します。
  • signalR.LogLevel.Information: エラーのないメッセージ。 InformationWarningError のメッセージをログに記録します。
  • signalR.LogLevel.Trace: トレース メッセージ。 ハブとクライアント間で転送されたデータを含む、すべてをログに記録します。

ログ レベルを構成するには、HubConnectionBuilderconfigureLogging メソッド を使用します。 メッセージは、ブラウザー コンソールにログされます。

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

クライアントを再接続する

自動的に再接続する

SignalR の JavaScript クライアントは、HubConnectionBuilderWithAutomaticReconnect メソッドを使用して、自動的に再接続するように構成できます。 既定では、自動的に再接続されません。

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

パラメーターを指定しないと、WithAutomaticReconnect は、再接続の各試行の前に、それぞれ 0、2、10、30 秒間待機するようにクライアントを構成します。 4 回試行に失敗すると、再接続の試行が停止します。

再接続の試行を開始する前に HubConnectionは :

  • HubConnectionState.Reconnecting 状態に遷移し、onreconnecting コールバックを実行します。
  • Disconnected 状態に遷移せず、自動再接続を構成せずに HubConnection のように onclose コールバックをトリガーします。

再接続アプローチでは、次の機会が提供されます:

  • 接続が失われたことをユーザーに警告します。
  • UI 要素を無効にします。
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);
});

最初の 4 回の試行でクライアントが正常に再接続した場合、HubConnectionConnected 状態に戻り、onreconnected コールバックが起動されます。 これにより、接続が再確立されたことをユーザーに通知できます。

サーバーからは接続はまったく新しいものと認識されるため、onreconnected コールバックには新しい connectionId が提供されます。

HubConnectionネゴシエーションをスキップするように構成されている場合、onreconnected コールバックの connectionId パラメーターは未定義になります。

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

HubConnectionwithAutomaticReconnect によって最初の開始失敗を再試行するように構成されないため、開始失敗は手動で処理する必要があります。

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

最初の 4 回の試行でクライアントが正常に再接続しなかった場合は、HubConnectionDisconnected 状態に遷移し、onclose コールバックが起動されます。 これにより、ユーザーに通知する機会が提供されます:

  • 接続が永久に失われました。
  • ページを更新してみてください:
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);
});

切断の前にカスタムの再接続試行回数を構成したり、再接続のタイミングを変更したりするため、withAutomaticReconnect は、再接続の各試行を始めるまでの待機時間 (ミリ秒) を表す値の配列を受け取ります。

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

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

上の例では、接続が失われたらすぐに再接続を試み始めるよう、HubConnection を構成しています。 既定の構成では、再接続を試みるために 0 秒も待機します。

再接続の 1 回目の試行が失敗した場合は、既定の構成を使用した場合のように 2 秒待つことなく、2 回目の再接続の試行がすぐに開始されます。

再接続の 2 回目の試行が失敗した場合、3 回目の再接続の試行は 10 秒後に開始されます。これは、既定の構成と同様です。

構成された再接続のタイミングは、既定の動作とは異なり、30 秒してからもう 1 回再接続を試みるのではなく、3 回目の再接続の試行が失敗したら停止します。

自動再接続試行のタイミングと回数をさらに細かく制御したい場合は、withAutomaticReconnectIRetryPolicy インターフェイスが実装されているオブジェクトを受け取ります。このインターフェイスには、nextRetryDelayInMilliseconds という名前のメソッドが 1 つ含まれます。

nextRetryDelayInMilliseconds は、RetryContext 型の引数を 1 つ受け取ります。 RetryContext には 3 つのプロパティ previousRetryCountelapsedMillisecondsretryReason があり、それぞれ numbernumberError です。 最初の再接続試行の前は、previousRetryCountelapsedMilliseconds はどちらも 0 であり、retryReason は接続が失われる原因となった Error になっています。 再試行が失敗するたびに、previousRetryCount は 1 ずつインクリメントされ、elapsedMilliseconds はそれまでの再接続に費やされた時間を反映するミリ秒数に更新され、retryReason には最後の再接続試行が失敗した原因である Error が設定されます。

nextRetryDelayInMilliseconds では、次の再接続試行の前に待機するミリ秒数を表す数値を返すか、HubConnection が再接続を停止する必要がある場合は null を返す必要があります。

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

または、次のセクションで示すように、クライアントを手動で再接続するコードを記述することもできます。

手動で再接続する

次のコードは、一般的な手動再接続の方法を示したものです。

  1. 接続を開始するための関数 (この場合は start 関数) を作成します。
  2. 接続の onclose イベント ハンドラーで、start 関数を呼び出します。
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

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

通常、運用環境の実装では、指数バックオフを使用するか、指定した回数だけ再試行します。

ブラウザーの休止タブ

一部のブラウザーには、非アクティブなタブのコンピューター リソースの使用量を減らすために、タブを凍結または休止する機能があります。 これにより、SignalR の接続が閉じ、望ましくないユーザー エクスペリエンスが発生する可能性があります。 ブラウザーではヒューリスティックを使用して、次のようなものをタブで休止する必要があるかどうかが確認されます。

  • オーディオの再生
  • Web ロックの保持
  • IndexedDB ロックの保持
  • USB デバイスへの接続
  • ビデオまたはオーディオのキャプチャ
  • ミラーリング
  • ウィンドウまたはディスプレイのキャプチャ

ブラウザーのヒューリスティックは、時間が経つと変化したり、ブラウザー間で異なる場合があります。 サポート マトリックスを調べて、シナリオに最適な方法を確認してください。

アプリを休止状態にしないようにするには、ブラウザーで使用されるヒューリスティックの 1 つをアプリでトリガーする必要があります。

次のコード例では、Web ロックを使用してタブの動作状態を維持し、予期せず接続が閉じられないようにする方法を示します。

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

上のコード例の場合:

  • Web ロックは実験段階です。 条件チェックにより、ブラウザーで Web ロックがサポートされていることが確認されます。
  • タブが休止することが許容される場合にロックを解放できるよう、Promise リゾルバー (lockResolver) が格納されます。
  • 接続を閉じるときは、lockResolver() を呼び出すことによってロックが解放されます。 ロックが解放されると、タブは休止できるようになります。

その他のリソース

作成者: Rachel Appel

ASP.NET Core SignalR JavaScript クライアント ライブラリを使用すると、開発者はサーバー側のハブ コードを呼び出すことができます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

SignalR クライアント パッケージをインストールする

SignalR JavaScript クライアント ライブラリは、npm パッケージとして提供されます。 以下のセクションでは、クライアント ライブラリをインストールするためのさまざまな方法の概要について説明します。

npm でインストールする

Visual Studio の場合は、ルート フォルダーにいる間にパッケージ マネージャー コンソールから次のコマンドを実行します。 Visual Studio Code の場合は、統合ターミナルから次のコマンドを実行します。

npm init -y
npm install @microsoft/signalr

npm によってパッケージの内容が node_modules\@microsoft\signalr\dist\browser フォルダーにインストールされます。 wwwroot\lib フォルダーの下に、signalr という名前の新しいフォルダーを作成します。 signalr.js ファイルを、wwwroot\lib\signalr フォルダーにコピーします。

<script> 要素で SignalR JavaScript クライアントを参照します。 次に例を示します。

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

コンテンツ配信ネットワーク (CDN) を使用する

npm の前提条件なしでクライアント ライブラリを使用するには、クライアント ライブラリの CDN でホストされているコピーを参照します。 次に例を示します。

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

クライアント ライブラリは、次の CDN で利用できます。

LibMan でインストールする

LibMan を使うと、CDN でホストされたクライアント ライブラリから特定のクライアント ライブラリ ファイルをインストールできます。 たとえば、縮小された JavaScript ファイルのみをプロジェクトに追加します。 その方法について詳しくは、「SignalR クライアント ライブラリを追加する」をご覧ください。

ハブに接続する

次のコードにより、接続が作成されて開始されます。 ハブの名前は大文字と小文字が区別されます。

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

クロスオリジン接続

通常、ブラウザーでは、要求されたページと同じドメインから接続が読み込まれます。 ただし、別のドメインへの接続が必要になる場合もあります。

重要

相対 URL ではなく絶対 URL をクライアント コードで使用する必要があります。 .withUrl("/chathub").withUrl("https://myappurl/chathub") に変更します。

悪意のあるサイトによって別のサイトから機密データが読み取られるのを防ぐため、クロスオリジン接続は既定で無効になっています。 クロスオリジン要求を許可するには、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");
            });
        }
    }
}

クライアントからハブ メソッドを呼び出す

JavaScript クライアントによるハブ上のパブリック メソッドの呼び出しは、HubConnectioninvoke メソッドで行われます。 invokeメソッドは次のものを受け取ります。

  • ハブ メソッドの名前。
  • ハブ メソッドで定義されているすべての引数。

次の例では、ハブでのメソッド名は SendMessage です。 invoke に渡される 2 番目と 3 番目の引数は、ハブ メソッドの user および message 引数にマップします。

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

Note

クライアントからのハブのメソッドの呼び出しは、SignalRAzure Service を "既定" モードで使用している場合にのみサポートされます。 詳細については、「Frequently Asked Questions」(azure-signalr GitHub リポジトリ) をご覧ください。

invoke メソッドからは、JavaScript Promise が返されます。 Promise は、サーバー上のメソッドから返った時点で、戻り値 (ある場合) によって解決されます。 サーバー上のメソッドがエラーをスローした場合、Promise はエラー メッセージで却下されます。 これらのケースを処理するには、asyncawait または Promisethen および catch メソッドを使用します。

JavaScript クライアントによるハブ上のパブリック メソッドの呼び出しは、HubConnectionsend メソッドで行うこともできます。 invoke メソッドとは異なり、send メソッドはサーバーからの応答を待機しません。 send メソッドからは、JavaScript Promise が返されます。 Promise は、メッセージがサーバーに送信された時点で解決されます。 メッセージの送信でエラーが発生した場合、Promise はエラー メッセージで却下されます。 これらのケースを処理するには、asyncawait または Promisethen および catch メソッドを使用します。

Note

send を使うと、サーバーがメッセージを受信するまで待機しません。 このため、サーバーからデータまたはエラーを返すことはできません。

ハブからクライアントのメソッドを呼び出す

ハブからメッセージを受け取るには、HubConnectionon メソッドを使ってメソッドを定義します。

  • JavaScript クライアント メソッドの名前。
  • ハブがメソッドに渡す引数。

次の例では、メソッドの名前は ReceiveMessage です。 引数の名前は usermessage です。

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

前の connection.on のコードは、サーバー側のコードで SendAsync メソッドを使用してそれが呼び出された時点で実行されます。

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

SignalR により、SendAsyncconnection.on で定義されているメソッド名と引数を突き合わせることで、呼び出すクライアント メソッドが決定されます。

Note

ベスト プラクティスとしては、on の後で HubConnectionstart メソッドを呼び出します。 これにより、メッセージを受信する前にハンドラーが確実に登録されます。

エラー処理とログ記録

クライアント側のエラーを処理するには、trycatch を、asyncawait または Promisecatch メソッドと共に使用します。 ブラウザーのコンソールにエラーを出力するには、console.error を使います。

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

接続するときにログに記録するイベントのロガーと種類を渡すことによって、クライアント側のログ トレースを設定します。 指定したログ レベル以上のメッセージがログに記録されます。 使用可能なログ レベルは次のとおりです。

  • signalR.LogLevel.Error: エラー メッセージ。 Error メッセージのみをログに記録します。
  • signalR.LogLevel.Warning: 潜在的なエラーに関する警告メッセージ。 WarningError のメッセージをログに記録します。
  • signalR.LogLevel.Information: エラーのないメッセージ。 InformationWarningError のメッセージをログに記録します。
  • signalR.LogLevel.Trace: トレース メッセージ。 ハブとクライアント間で転送されたデータを含む、すべてをログに記録します。

ログ レベルを構成するには、HubConnectionBuilderconfigureLogging メソッド を使用します。 メッセージは、ブラウザー コンソールにログされます。

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

クライアントを再接続する

自動的に再接続する

SignalR の JavaScript クライアントは、HubConnectionBuilderwithAutomaticReconnect メソッドを使用して、自動的に再接続するように構成できます。 既定では、自動的に再接続されません。

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

パラメーターを指定しないで withAutomaticReconnect() を使用すると、クライアントは、再接続の各試行の前に、それぞれ 0、2、10、30 秒間待機し、試行が 4 回失敗すると停止するように構成されます。

自動再接続が構成されていない HubConnection の場合は Disconnected 状態に遷移して onclose コールバックがトリガーされますが、再接続が試みられる場合は代わりに、開始前に HubConnectionHubConnectionState.Reconnecting 状態に遷移して onreconnecting コールバックが起動されます。 これにより、接続が失われたことをユーザーに警告し、UI 要素を無効にすることができます。

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

最初の 4 回の試行でクライアントが正常に再接続した場合、HubConnectionConnected 状態に戻り、onreconnected コールバックが起動されます。 これにより、接続が再確立されたことをユーザーに通知できます。

サーバーからは接続はまったく新しいものと認識されるため、onreconnected コールバックには新しい connectionId が提供されます。

警告

HubConnectionネゴシエーションをスキップするように構成されている場合、onreconnected コールバックの connectionId パラメーターは未定義になります。

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

HubConnectionwithAutomaticReconnect() によって最初の開始失敗を再試行するように構成されないため、開始失敗は手動で処理する必要があります。

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

最初の 4 回の試行でクライアントが正常に再接続しなかった場合は、HubConnectionDisconnected 状態に遷移し、onclose コールバックが起動されます。 これにより、接続が完全に失われたことをユーザーに通知し、ページの更新を勧めることができます。

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

切断の前にカスタムの再接続試行回数を構成したり、再接続のタイミングを変更したりするため、withAutomaticReconnect は、再接続の各試行を始めるまでの待機時間 (ミリ秒) を表す値の配列を受け取ります。

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

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

上の例では、接続が失われたらすぐに再接続を試み始めるよう、HubConnection を構成しています。 これは、既定の構成でも同じです。

再接続の 1 回目の試行が失敗した場合は、既定の構成のように 2 秒待つことなく、2 回目の再接続の試行がすぐに開始されます。

再接続の 2 回目の試行が失敗した場合、3 回目の再接続の試行は 10 秒後に開始されます。これは、既定の構成と同様です。

その後のカスタム動作は、再び既定の動作とは異なり、既定の構成のようにさらに 30 秒してからもう 1 回再接続を試みるのではなく、3 回目の再接続の試行が失敗したら停止します。

自動再接続試行のタイミングと回数をさらに細かく制御したい場合は、withAutomaticReconnectIRetryPolicy インターフェイスが実装されているオブジェクトを受け取ります。このインターフェイスには、nextRetryDelayInMilliseconds という名前のメソッドが 1 つ含まれます。

nextRetryDelayInMilliseconds は、RetryContext 型の引数を 1 つ受け取ります。 RetryContext には 3 つのプロパティ previousRetryCountelapsedMillisecondsretryReason があり、それぞれ numbernumberError です。 最初の再接続試行の前は、previousRetryCountelapsedMilliseconds はどちらも 0 であり、retryReason は接続が失われる原因となった Error になっています。 再試行が失敗するたびに、previousRetryCount は 1 ずつインクリメントされ、elapsedMilliseconds はそれまでの再接続に費やされた時間を反映するミリ秒数に更新され、retryReason には最後の再接続試行が失敗した原因である Error が設定されます。

nextRetryDelayInMilliseconds では、次の再接続試行の前に待機するミリ秒数を表す数値を返すか、HubConnection が再接続を停止する必要がある場合は null を返す必要があります。

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

または、「手動で再接続する」で示されているように、クライアントを手動で再接続するコードを記述することもできます。

手動で再接続する

次のコードは、一般的な手動再接続の方法を示したものです。

  1. 接続を開始するための関数 (この場合は start 関数) を作成します。
  2. 接続の onclose イベント ハンドラーで、start 関数を呼び出します。
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

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

通常、運用環境の実装では、指数バックオフを使用するか、指定した回数だけ再試行します。

ブラウザーの休止タブ

一部のブラウザーには、非アクティブなタブのコンピューター リソースの使用量を減らすために、タブを凍結または休止する機能があります。 これにより、SignalR の接続が閉じ、望ましくないユーザー エクスペリエンスが発生する可能性があります。 ブラウザーではヒューリスティックを使用して、次のようなものをタブで休止する必要があるかどうかが確認されます。

  • オーディオの再生
  • Web ロックの保持
  • IndexedDB ロックの保持
  • USB デバイスへの接続
  • ビデオまたはオーディオのキャプチャ
  • ミラーリング
  • ウィンドウまたはディスプレイのキャプチャ

Note

これらのヒューリスティックは、時間が経つと変化したり、ブラウザー間で異なる場合があります。 サポート マトリックスを調べて、シナリオに最適な方法を確認してください。

アプリを休止状態にしないようにするには、ブラウザーで使用されるヒューリスティックの 1 つをアプリでトリガーする必要があります。

次のコード例では、Web ロックを使用してタブの動作状態を維持し、予期せず接続が閉じられないようにする方法を示します。

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

上のコード例の場合:

  • Web ロックは実験段階です。 条件チェックにより、ブラウザーで Web ロックがサポートされていることが確認されます。
  • タブが休止することが許容される場合にロックを解放できるよう、Promise リゾルバー (lockResolver) が格納されます。
  • 接続を閉じるときは、lockResolver() を呼び出すことによってロックが解放されます。 ロックが解放されると、タブは休止できるようになります。

その他のリソース