Quick start
Install the package, declare a contract, and call a hub from a component.
Requirements
- Node 20.19 or later.
@microsoft/signalr8, 9, or 10.- A server with ASP.NET Core SignalR. The older ASP.NET SignalR for .NET Framework uses a different protocol and does not work with this client.
- Svelte 5.15 or later. Svelte 4 is not supported.
Install
Install the package and its SignalR peer:
npm install @dammers/use-signalr-svelte @microsoft/signalrSet up
Write the server hub
The samples in this documentation use this hub:
using System.Collections.Concurrent;
using Microsoft.AspNetCore.SignalR;
namespace Server.Hubs;
public record ChatMessage(string RoomId, string User, string Text, DateTimeOffset SentAt);
/// <summary>Teaching hub: chat rooms built on SignalR groups.</summary>
public class RoomHub : Hub
{
private const int HistoryLimit = 50;
private static readonly ConcurrentDictionary<string, Queue<ChatMessage>> History = new();
// Adding a connection that is already in the group changes nothing, so the client can retry.
public Task JoinRoom(string roomId) => Groups.AddToGroupAsync(Context.ConnectionId, roomId);
public Task LeaveRoom(string roomId) => Groups.RemoveFromGroupAsync(Context.ConnectionId, roomId);
public Task SendMessage(string roomId, string text)
{
var message = new ChatMessage(roomId, Context.User?.Identity?.Name ?? "anonymous", text, DateTimeOffset.UtcNow);
var history = History.GetOrAdd(roomId, _ => new Queue<ChatMessage>());
lock (history)
{
history.Enqueue(message);
if (history.Count > HistoryLimit) history.Dequeue();
}
return Clients.Group(roomId).SendAsync("MessageReceived", message);
}
public ChatMessage[] GetHistory(string roomId)
{
if (!History.TryGetValue(roomId, out var history)) return [];
lock (history) return history.ToArray();
}
}Declare the contract
List each hub path, its events, and its methods. Names and argument types must match the server hub:
import { event, method } from "@dammers/use-signalr-svelte";
export interface ChatMessage {
roomId: string;
user: string;
text: string;
sentAt: string;
}
// Keys are hub paths. Names and argument types match the server hub.
export const hubs = {
"/hubs/rooms": {
events: {
MessageReceived: event<[message: ChatMessage]>(),
},
methods: {
JoinRoom: method<[roomId: string]>(),
LeaveRoom: method<[roomId: string]>(),
SendMessage: method<[roomId: string, text: string]>(),
GetHistory: method<[roomId: string], ChatMessage[]>(),
},
},
"/hubs/presence": {
lazy: true,
graceMs: 2000,
events: {
OnlineCount: event<[count: number]>(),
},
},
};Create the client
Pass the contract to createSignalRClient. The call returns the typed helpers that your components use:
import { createSignalRClient } from "@dammers/use-signalr-svelte";
import { hubs } from "./contract";
export const {
provideSignalR,
getSignalR,
keepHubAlive,
onHubEvent,
hubInvoke,
hubSend,
hubTeardown,
hubStatus,
onReconnected,
} = createSignalRClient({ hubs });Start the session
Give provideSignalR the base URL of your server and a function that returns the access token:
<script lang="ts">
import type { Snippet } from "svelte";
import { toStore } from "svelte/store";
import { provideSignalR } from "./client";
let { token, children }: { token: string | undefined; children: Snippet } = $props();
provideSignalR({
baseUrl: "https://api.example.com",
accessTokenFactory: () => token ?? "",
enabled: toStore(() => token !== undefined),
});
</script>
{@render children()}Use a hub in a component
This component shows the messages of a room and sends one message:
<script lang="ts">
import { hubInvoke, hubStatus, onHubEvent } from "./client";
import { ignoreAbort } from "./ignore-abort";
import type { ChatMessage } from "./contract";
let { roomId }: { roomId: string } = $props();
let messages = $state<ChatMessage[]>([]);
let text = $state("");
const status = hubStatus("/hubs/rooms");
const joinRoom = hubInvoke("/hubs/rooms", "JoinRoom");
const sendMessage = hubInvoke("/hubs/rooms", "SendMessage");
$effect(() => {
if ($status === "connected") joinRoom(roomId).catch(ignoreAbort);
});
onHubEvent("/hubs/rooms", "MessageReceived", (message) => {
if (message.roomId === roomId) messages.push(message);
});
async function submit(event: SubmitEvent) {
event.preventDefault();
try {
await sendMessage(roomId, text);
text = "";
} catch (e) {
ignoreAbort(e);
}
}
</script>
<ul>
{#each messages as message}
<li>{message.user}: {message.text}</li>
{/each}
</ul>
<form onsubmit={submit}>
<input bind:value={text} />
<button disabled={$status !== "connected"}>Send</button>
</form>ignoreAbort ignores the AbortError that a call gets when the component unmounts while the call waits. See Calls.
Run the example app
The repository has a runnable example app with a matching server.
Open examples/svelte.
Next, read Client config and Connection status.