ASP.
NET Core SignalR JavaScript client
By Melaku Michael
The [Link] Core SignalR JavaScript client library enables developers to call server-side
SignalR hub code.
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
Visual Studio
Visual Studio Code
Visual Studio for Mac
Run the following commands from Package Manager Console:
BashCopy
npm init -y
npm install @microsoft/signalr
npm installs the package contents in the node_modules\@microsoft\signalr\dist\
browser folder. Create the wwwroot/lib/signalr folder. Copy the [Link] file to
the wwwroot/lib/signalr folder.
Reference the SignalR JavaScript client in the <script> element. For example:
HTMLCopy
<script src="~/lib/signalr/[Link]"></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:
HTMLCopy
<script src="[Link]
[Link]"></script>
The client library is available on the following CDNs:
cdnjs
jsDelivr
unpkg
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:
JavaScriptCopy
const connection = new [Link]()
.withUrl("/chathub")
.configureLogging([Link])
.build();
async function start() {
try {
await [Link]();
[Link]("SignalR Connected.");
} catch (err) {
[Link](err);
setTimeout(start, 5000);
}
};
[Link](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.
When making cross domain requests, the client code must use an absolute URL instead
of a relative URL. For cross domain requests,
change .withUrl("/chathub") to .withUrl("[Link] 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:
C#Copy
using [Link];
var builder = [Link](args);
[Link]();
[Link]();
[Link](options =>
{
[Link](
builder =>
{
[Link]("[Link]
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
});
var app = [Link]();
if (![Link]())
{
[Link]("/Error");
[Link]();
}
[Link]();
[Link]();
[Link]();
[Link]();
// UseCors must be called before MapHub.
[Link]();
[Link]();
[Link]<ChatHub>("/chatHub");
[Link]();
UseCors must be called before calling MapHub.
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:
JavaScriptCopy
try {
await [Link]("SendMessage", user, message);
} catch (err) {
[Link](err);
}
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.
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:
JavaScriptCopy
[Link]("ReceiveMessage", (user, message) => {
const li = [Link]("li");
[Link] = `${user}: ${message}`;
[Link]("messageList").appendChild(li);
});
The preceding code in [Link] runs when server-side code calls it using
the SendAsync method:
C#Copy
using [Link];
namespace [Link];
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await [Link]("ReceiveMessage", user, message);
}
}
SignalR determines which client method to call by matching the method name and
arguments defined in SendAsync and [Link].
A best practice is to call the start method on the HubConnection after on. Doing so
ensures the handlers are registered before any messages are received.
Error handling and logging
Use [Link] to output errors to the browser's console when the client can't connect
or send a message:
JavaScriptCopy
try {
await [Link]("SendMessage", user, message);
} catch (err) {
[Link](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:
[Link]: Error messages. Logs Error messages only.
[Link]: Warning messages about potential errors. Logs Warning,
and Error messages.
[Link]: Status messages without errors. Logs Information, Warning,
and Error messages.
[Link]: 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:
JavaScriptCopy
const connection = new [Link]()
.withUrl("/chathub")
.configureLogging([Link])
.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.
JavaScriptCopy
const connection = new [Link]()
.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 reconnect attempts, the HubConnection:
Transitions to the [Link] state and fires
its onreconnecting callbacks.
Doesn't transition to the Disconnected state and trigger its onclose callbacks like
a HubConnection without automatic reconnect configured.
The reconnect approach provides an opportunity to:
Warn users that the connection has been lost.
Disable UI elements.
JavaScriptCopy
[Link](error => {
[Link]([Link] === [Link]);
[Link]("messageInput").disabled = true;
const li = [Link]("li");
[Link] = `Connection lost due to error "${error}". Reconnecting.`;
[Link]("messageList").appendChild(li);
});
If the client successfully reconnects within its first four attempts,
the HubConnection transitions 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 is provided to
the onreconnected callback.
The onreconnected callback's connectionId parameter is undefined if the HubConnection is
configured to skip negotiation.
JavaScriptCopy
[Link](connectionId => {
[Link]([Link] === [Link]);
[Link]("messageInput").disabled = false;
const li = [Link]("li");
[Link] = `Connection reestablished. Connected with connectionId "$
{connectionId}".`;
[Link]("messageList").appendChild(li);
});
withAutomaticReconnect won't configure the HubConnection to retry initial start failures, so
start failures need to be handled manually:
JavaScriptCopy
async function start() {
try {
await [Link]();
[Link]([Link] === [Link]);
[Link]("SignalR Connected.");
} catch (err) {
[Link]([Link] === [Link]);
[Link](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
provides an opportunity to inform users:
The connection has been permanently lost.
Try refreshing the page:
JavaScriptCopy
[Link](error => {
[Link]([Link] === [Link]);
[Link]("messageInput").disabled = true;
const li = [Link]("li");
[Link] = `Connection closed due to error "${error}". Try refreshing this page
to restart the connection.`;
[Link]("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.
JavaScriptCopy
const connection = new [Link]()
.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
seconds to attempt reconnecting.
If the first reconnect attempt fails, the second reconnect attempt also starts immediately
instead of waiting 2 seconds using the default configuration.
If the second reconnect attempt fails, the third reconnect attempt start in 10 seconds
which is the same as the default configuration.
The configured reconnection timing differs from the default behavior by stopping after
the third reconnect attempt failure instead of trying one more reconnect attempt in
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, 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.
JavaScriptCopy
const connection = new [Link]()
.withUrl("/chathub")
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: retryContext => {
if ([Link] < 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 [Link]() * 10000;
} else {
// If we've been reconnecting for more than 60 seconds so far, stop
reconnecting.
return null;
}
}
})
.build();
Alternatively, code can be written that reconnects the client manually as demonstrated
in the following section.
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.
JavaScriptCopy
async function start() {
try {
await [Link]();
[Link]("SignalR Connected.");
} catch (err) {
[Link](err);
setTimeout(start, 5000);
}
};
[Link](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
Browser heuristics may change over time and can differ between 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.
JavaScriptCopy
var lockResolver;
if (navigator && [Link] && [Link]) {
const promise = new Promise((res) => {
lockResolver = res;
});
[Link]('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
View or download sample code (how to download)
JavaScript API reference
JavaScript tutorial
WebPack and TypeScript tutorial
Hubs
.NET client
Publish to Azure
Cross-Origin Requests (CORS)
Azure SignalR Service serverless documentation
Troubleshoot connection errors