diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index f4bc0bb96526..4af2f9c92908 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -22,8 +22,10 @@ using Microsoft.AspNetCore.SignalR.Client.Internal; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR.Client; @@ -152,7 +154,7 @@ public partial class HubConnection : IAsyncDisposable /// /// The client times out if it hasn't heard from the server for `this` long. /// - public TimeSpan ServerTimeout { get; set; } = DefaultServerTimeout; + public TimeSpan ServerTimeout { get; set; } /// /// Gets or sets the interval at which the client sends ping messages. @@ -160,7 +162,7 @@ public partial class HubConnection : IAsyncDisposable /// /// Sending any message resets the timer to the start of the interval. /// - public TimeSpan KeepAliveInterval { get; set; } = DefaultKeepAliveInterval; + public TimeSpan KeepAliveInterval { get; set; } /// /// Gets or sets the timeout for the initial handshake. @@ -227,6 +229,12 @@ public HubConnection(IConnectionFactory connectionFactory, _state = new ReconnectingConnectionState(_logger); _logScope = new ConnectionLogScope(); + + var options = serviceProvider.GetService>(); + + ServerTimeout = options?.Value.ServerTimeout ?? DefaultServerTimeout; + + KeepAliveInterval = options?.Value.KeepAliveInterval ?? DefaultKeepAliveInterval; } /// diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionBuilderExtensions.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionBuilderExtensions.cs index 5979127fa4e4..6dbd4fd98e22 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionBuilderExtensions.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionBuilderExtensions.cs @@ -63,4 +63,28 @@ public static IHubConnectionBuilder WithAutomaticReconnect(this IHubConnectionBu hubConnectionBuilder.Services.AddSingleton(retryPolicy); return hubConnectionBuilder; } + + /// + /// Configures ServerTimeout for the . + /// + /// The to configure. + /// ServerTimeout for the . + /// The same instance of the for chaining. + public static IHubConnectionBuilder WithServerTimeout(this IHubConnectionBuilder hubConnectionBuilder, TimeSpan timeout) + { + hubConnectionBuilder.Services.Configure(o => o.ServerTimeout = timeout); + return hubConnectionBuilder; + } + + /// + /// Configures KeepAliveInterval for the . + /// + /// The to configure. + /// KeepAliveInterval for the . + /// The same instance of the for chaining. + public static IHubConnectionBuilder WithKeepAliveInterval(this IHubConnectionBuilder hubConnectionBuilder, TimeSpan interval) + { + hubConnectionBuilder.Services.Configure(o => o.KeepAliveInterval = interval); + return hubConnectionBuilder; + } } diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionOptions.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionOptions.cs new file mode 100644 index 000000000000..5ebae3958898 --- /dev/null +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionOptions.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.SignalR.Client; + +/// +/// Configures timeouts for the . +/// +internal sealed class HubConnectionOptions +{ + /// + /// Configures ServerTimeout for the . + /// + public TimeSpan? ServerTimeout { get; set; } + + /// + /// Configures KeepAliveInterval for the . + /// + public TimeSpan? KeepAliveInterval { get; set; } +} diff --git a/src/SignalR/clients/csharp/Client.Core/src/PublicAPI.Unshipped.txt b/src/SignalR/clients/csharp/Client.Core/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..a9937988d626 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/PublicAPI.Unshipped.txt +++ b/src/SignalR/clients/csharp/Client.Core/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +static Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilderExtensions.WithKeepAliveInterval(this Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder! hubConnectionBuilder, System.TimeSpan interval) -> Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder! +static Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilderExtensions.WithServerTimeout(this Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder! hubConnectionBuilder, System.TimeSpan timeout) -> Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder! diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionBuilderTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionBuilderTests.cs index f9473ae9a1b5..cb0938bf5720 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionBuilderTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionBuilderTests.cs @@ -77,4 +77,46 @@ public void AddMessagePackProtocolSetsHubProtocolToMsgPack() Assert.IsType(serviceProvider.GetService()); } + + [Fact] + public void CanConfigureServerTimeout() + { + var serverTimeout = TimeSpan.FromMinutes(1); + var builder = new HubConnectionBuilder(); + builder.WithUrl("http://example.com") + .WithServerTimeout(serverTimeout); + + var connection = builder.Build(); + + Assert.Equal(serverTimeout, connection.ServerTimeout); + } + + [Fact] + public void CanConfigureKeepAliveInterval() + { + var keepAliveInterval = TimeSpan.FromMinutes(1); + var builder = new HubConnectionBuilder(); + builder.WithUrl("http://example.com") + .WithKeepAliveInterval(keepAliveInterval); + + var connection = builder.Build(); + + Assert.Equal(keepAliveInterval, connection.KeepAliveInterval); + } + + [Fact] + public void CanConfigureServerTimeoutAndKeepAliveInterval() + { + var serverTimeout = TimeSpan.FromMinutes(2); + var keepAliveInterval = TimeSpan.FromMinutes(3); + var builder = new HubConnectionBuilder(); + builder.WithUrl("http://example.com") + .WithServerTimeout(serverTimeout) + .WithKeepAliveInterval(keepAliveInterval); + + var connection = builder.Build(); + + Assert.Equal(serverTimeout, connection.ServerTimeout); + Assert.Equal(keepAliveInterval, connection.KeepAliveInterval); + } } diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts index b257ec5eafa9..ef17a3ccfa07 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts @@ -92,17 +92,29 @@ export class HubConnection { // create method that can be used by HubConnectionBuilder. An "internal" constructor would just // be stripped away and the '.d.ts' file would have no constructor, which is interpreted as a // public parameter-less constructor. - public static create(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IRetryPolicy): HubConnection { - return new HubConnection(connection, logger, protocol, reconnectPolicy); + public static create( + connection: IConnection, + logger: ILogger, + protocol: IHubProtocol, + reconnectPolicy?: IRetryPolicy, + serverTimeoutInMilliseconds?: number, + keepAliveIntervalInMilliseconds?: number): HubConnection { + return new HubConnection(connection, logger, protocol, reconnectPolicy, serverTimeoutInMilliseconds, keepAliveIntervalInMilliseconds); } - private constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol, reconnectPolicy?: IRetryPolicy) { + private constructor( + connection: IConnection, + logger: ILogger, + protocol: IHubProtocol, + reconnectPolicy?: IRetryPolicy, + serverTimeoutInMilliseconds?: number, + keepAliveIntervalInMilliseconds?: number) { Arg.isRequired(connection, "connection"); Arg.isRequired(logger, "logger"); Arg.isRequired(protocol, "protocol"); - this.serverTimeoutInMilliseconds = DEFAULT_TIMEOUT_IN_MS; - this.keepAliveIntervalInMilliseconds = DEFAULT_PING_INTERVAL_IN_MS; + this.serverTimeoutInMilliseconds = serverTimeoutInMilliseconds ?? DEFAULT_TIMEOUT_IN_MS; + this.keepAliveIntervalInMilliseconds = keepAliveIntervalInMilliseconds ?? DEFAULT_PING_INTERVAL_IN_MS; this._logger = logger; this._protocol = protocol; diff --git a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts index 2cf4e09a3dbf..8cd9c1361440 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -39,6 +39,9 @@ function parseLogLevel(name: string): LogLevel { /** A builder for configuring {@link @microsoft/signalr.HubConnection} instances. */ export class HubConnectionBuilder { + private _serverTimeoutInMilliseconds?: number; + private _keepAliveIntervalInMilliseconds ?: number; + /** @internal */ public protocol?: IHubProtocol; /** @internal */ @@ -183,6 +186,30 @@ export class HubConnectionBuilder { return this; } + /** Configures {@link @microsoft/signalr.HubConnection.serverTimeoutInMilliseconds} for the {@link @microsoft/signalr.HubConnection}. + * + * @returns The {@link @microsoft/signalr.HubConnectionBuilder} instance, for chaining. + */ + public withServerTimeout(milliseconds: number): HubConnectionBuilder { + Arg.isRequired(milliseconds, "milliseconds"); + + this._serverTimeoutInMilliseconds = milliseconds; + + return this; + } + + /** Configures {@link @microsoft/signalr.HubConnection.keepAliveIntervalInMilliseconds} for the {@link @microsoft/signalr.HubConnection}. + * + * @returns The {@link @microsoft/signalr.HubConnectionBuilder} instance, for chaining. + */ + public withKeepAliveInterval(milliseconds: number): HubConnectionBuilder { + Arg.isRequired(milliseconds, "milliseconds"); + + this._keepAliveIntervalInMilliseconds = milliseconds; + + return this; + } + /** Creates a {@link @microsoft/signalr.HubConnection} from the configuration options specified in this builder. * * @returns {HubConnection} The configured {@link @microsoft/signalr.HubConnection}. @@ -208,7 +235,9 @@ export class HubConnectionBuilder { connection, this.logger || NullLogger.instance, this.protocol || new JsonHubProtocol(), - this.reconnectPolicy); + this.reconnectPolicy, + this._serverTimeoutInMilliseconds, + this._keepAliveIntervalInMilliseconds); } } diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts index d342038f2a59..a04e00e1259c 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnectionBuilder.test.ts @@ -384,6 +384,28 @@ describe("HubConnectionBuilder", () => { expect(builder.reconnectPolicy!.nextRetryDelayInMilliseconds(retryContextFinal)).toBe(null); }); + + it("can configure serverTimeoutInMilliseconds for HubConnection", async () => { + const milliseconds = 60000; + + const connection = createConnectionBuilder() + .withUrl("http://example.com") + .withServerTimeout(milliseconds) + .build(); + + expect(connection.serverTimeoutInMilliseconds).toBe(milliseconds); + }); + + it("can configure keepAliveIntervalInMilliseconds for HubConnection", async () => { + const milliseconds = 60000; + + const connection = createConnectionBuilder() + .withUrl("http://example.com") + .withKeepAliveInterval(milliseconds) + .build(); + + expect(connection.keepAliveIntervalInMilliseconds).toBe(milliseconds); + }); }); class CaptureLogger implements ILogger {