diff --git a/clients/ts/signalr/src/HubConnection.ts b/clients/ts/signalr/src/HubConnection.ts index c69a5d24fd..6bc618c8db 100644 --- a/clients/ts/signalr/src/HubConnection.ts +++ b/clients/ts/signalr/src/HubConnection.ts @@ -10,6 +10,14 @@ import { Arg, Subject } from "./Utils"; const DEFAULT_TIMEOUT_IN_MS: number = 30 * 1000; +/** Describes the current state of the {@link HubConnection} to the server. */ +export enum HubConnectionState { + /** The hub connection is disconnected. */ + Disconnected, + /** The hub connection is connected. */ + Connected, +} + /** Represents a connection to a SignalR Hub. */ export class HubConnection { private readonly connection: IConnection; @@ -22,6 +30,7 @@ export class HubConnection { private closedCallbacks: Array<(error?: Error) => void>; private timeoutHandle: NodeJS.Timer; private receivedHandshakeResponse: boolean; + private connectionState: HubConnectionState; /** The server timeout in milliseconds. * @@ -58,6 +67,12 @@ export class HubConnection { this.methods = {}; this.closedCallbacks = []; this.id = 0; + this.connectionState = HubConnectionState.Disconnected; + } + + /** Indicates the state of the {@link HubConnection} to the server. */ + get state(): HubConnectionState { + return this.connectionState; } /** Starts the connection. @@ -85,6 +100,8 @@ export class HubConnection { // defensively cleanup timeout in case we receive a message from the server before we finish start this.cleanupTimeout(); this.configureTimeout(); + + this.connectionState = HubConnectionState.Connected; } /** Stops the connection. @@ -380,6 +397,8 @@ export class HubConnection { const callbacks = this.callbacks; this.callbacks = {}; + this.connectionState = HubConnectionState.Disconnected; + Object.keys(callbacks) .forEach((key) => { const callback = callbacks[key]; diff --git a/clients/ts/signalr/src/index.ts b/clients/ts/signalr/src/index.ts index 548a0a5929..1872133098 100644 --- a/clients/ts/signalr/src/index.ts +++ b/clients/ts/signalr/src/index.ts @@ -10,7 +10,7 @@ export { AbortSignal } from "./AbortController"; export { HttpError, TimeoutError } from "./Errors"; export { DefaultHttpClient, HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; export { IHttpConnectionOptions } from "./IHttpConnectionOptions"; -export { HubConnection } from "./HubConnection"; +export { HubConnection, HubConnectionState } from "./HubConnection"; export { HubConnectionBuilder } from "./HubConnectionBuilder"; export { MessageType, MessageHeaders, HubMessage, HubMessageBase, HubInvocationMessage, InvocationMessage, StreamInvocationMessage, StreamItemMessage, CompletionMessage, PingMessage, CloseMessage, CancelInvocationMessage, IHubProtocol } from "./IHubProtocol"; export { ILogger, LogLevel } from "./ILogger"; diff --git a/clients/ts/signalr/tests/HubConnection.test.ts b/clients/ts/signalr/tests/HubConnection.test.ts index ef588078b6..c3ff32e224 100644 --- a/clients/ts/signalr/tests/HubConnection.test.ts +++ b/clients/ts/signalr/tests/HubConnection.test.ts @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -import { HubConnection } from "../src/HubConnection"; +import { HubConnection, HubConnectionState } from "../src/HubConnection"; import { IConnection } from "../src/IConnection"; import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol"; import { ILogger, LogLevel } from "../src/ILogger"; @@ -34,6 +34,33 @@ describe("HubConnection", () => { await hubConnection.stop(); } }); + + it("state connected", async () => { + const connection = new TestConnection(); + const hubConnection = createHubConnection(connection); + expect(hubConnection.state).toBe(HubConnectionState.Disconnected); + try { + await hubConnection.start(); + expect(hubConnection.state).toBe(HubConnectionState.Connected); + } finally { + await hubConnection.stop(); + } + }); + }); + + describe("stop", () => { + it("state disconnected", async () => { + const connection = new TestConnection(); + const hubConnection = createHubConnection(connection); + expect(hubConnection.state).toBe(HubConnectionState.Disconnected); + try { + await hubConnection.start(); + expect(hubConnection.state).toBe(HubConnectionState.Connected); + } finally { + await hubConnection.stop(); + expect(hubConnection.state).toBe(HubConnectionState.Disconnected); + } + }); }); describe("send", () => { @@ -834,6 +861,21 @@ describe("HubConnection", () => { hubConnection.stop(); } }); + + it("state disconnected", async () => { + const connection = new TestConnection(); + const hubConnection = createHubConnection(connection); + try { + let state: HubConnectionState; + hubConnection.onclose((e) => state = hubConnection.state); + // Typically this would be called by the transport + connection.onclose(); + + expect(state).toBe(HubConnectionState.Disconnected); + } finally { + hubConnection.stop(); + } + }); }); describe("keepAlive", () => { diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionState.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionState.cs index 279c5a5ffc..7a230bd70e 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionState.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionState.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// Disconnected, /// - /// The hub connection is open. + /// The hub connection is connected. /// Connected }