First prepare the connection and chat flow mapping.
This complete basic example loads the SignalR client from your website.
Place its file at /js/signalr.min.js and replace the hub address, skill
and language.
<label>Name <input id="customerName" maxlength="120" required></label>
<button id="startChat" type="button">Start chat</button>
<p id="chatStatus" role="status">Not connected yet</p>
<ul id="messages" aria-live="polite"></ul>
<label>Message <input id="message" maxlength="4000" disabled></label>
<button id="sendMessage" type="button" disabled>Send</button>
<button id="endChat" type="button" disabled>End chat</button>
<script src="/js/signalr.min.js"></script>
<script type="module">
const status = document.getElementById("chatStatus");
const start = document.getElementById("startChat");
const send = document.getElementById("sendMessage");
const end = document.getElementById("endChat");
const message = document.getElementById("message");
const chat = new signalR.HubConnectionBuilder()
.withUrl("https://kontakt.example.org/chatHub")
.build();
function disableSending() {
message.disabled = true;
send.disabled = true;
}
chat.on("OnStateChangedAsync", (state) => {
status.textContent = `Chat state: ${state}`;
const active = [1, 2, 3, 14].includes(state);
message.disabled = !active;
send.disabled = !active;
});
chat.on("OnPositionChangedAsync", (position) => {
status.textContent = `Queue position: ${position}`;
});
chat.on("AddMessageAsync", (participant, htmlMessage) => {
const parsed = new DOMParser().parseFromString(htmlMessage, "text/html");
const item = document.createElement("li");
item.textContent = `${participant}: ${parsed.body.textContent ?? ""}`;
document.getElementById("messages").append(item);
});
chat.onclose(() => {
disableSending();
end.disabled = true;
status.textContent = "Connection closed. Start a new chat explicitly.";
});
start.addEventListener("click", async () => {
const name = document.getElementById("customerName").value.trim();
if (!name) return;
start.disabled = true;
try {
await chat.start();
status.textContent = "Chat requested; waiting for processing.";
const accepted = await chat.invoke("Register", name, "Support", "de-DE");
if (!accepted) throw new Error("The chat request could not be submitted.");
end.disabled = false;
} catch (error) {
await chat.stop();
status.textContent = error.message;
start.disabled = false;
}
});
send.addEventListener("click", async () => {
const text = message.value.trim();
if (!text) return;
send.disabled = true;
try {
await chat.invoke("AddMessage", text);
message.value = "";
} catch (error) {
status.textContent = error.message;
} finally {
if (!message.disabled) send.disabled = false;
}
});
end.addEventListener("click", async () => {
disableSending();
await chat.stop();
});
</script>
The form length limits are examples for your website, not guaranteed hub product limits. The example displays incoming messages as text and does not retain HTML formatting.
The button remains disabled after starting a chat to prevent duplicate registration of the session. For a production website, add an explicit function to start a new session and understandable text for your flow’s states. See the chatHub reference.