← Home

#JS Core SDK

Framework-agnostic TypeScript client for BootDesk Chat. Handles HTTP messaging, real-time broadcasting, typed events, and web push notifications.

#Installation

npm install @bootdesk/js-web-adapter-core

Optional peer deps for broadcasting:

npm install pusher-js     # for PusherBroadcastClient
npm install laravel-echo  # for LaravelEchoBroadcastClient

#WebChatClient

The main client. Connect to your chat backend, send/receive messages, manage reactions.

#Setup

import { WebChatClient } from "@bootdesk/js-web-adapter-core";

const client = new WebChatClient({
  apiUrl: "https://your-app.com",
  userId: "user-123",
  userName: "Alice",
  features: {
    editMessages: true,
    deleteMessages: true,
    reactions: true,
  },
});

await client.connect();

#Configuration

Option Type Default Description
apiUrl string required Backend base URL
userId string required Current user ID (sent as X-User-Id header)
userName string required Display name (sent as X-User-Name header)
verifyToken string? Sent as X-Verify-Token for webhook verification
broadcastClient BroadcastClient? Pusher or Laravel Echo instance for real-time
headers Record<string,string>? {} Extra HTTP headers
conversationId string? auto Manually set conversation ID
features.editMessages boolean? false Enables editMessage()
features.deleteMessages boolean? false Enables deleteMessage()
features.reactions boolean? false Enables addReaction()/removeReaction()

Custom endpoints (all have sensible defaults):

const client = new WebChatClient({
  apiUrl: "https://your-app.com",
  userId: "user-1",
  userName: "Alice",
  endpoints: {
    sendMessage: "/api/webhooks/web",
    loadMessages: "/api/chat/messages",
    editMessage: "/api/chat/messages/{id}/edit",
    deleteMessage: "/api/chat/messages/{id}",
    addReaction: "/api/chat/messages/{id}/reactions",
    removeReaction: "/api/chat/messages/{id}/reactions/{emoji}",
  },
});

#Sending & Receiving

// Send a message
await client.sendMessage("Hello, world!");

// Load history
const { messages, hasMore, loadMore } = await client.loadMessages({ limit: 30 });

// Edit / delete
await client.editMessage("msg-123", "Updated text");
await client.deleteMessage("msg-456");

// Reactions
await client.addReaction("msg-123", "👍");
await client.removeReaction("msg-123", "👍");

// Card actions
await client.sendAction("msg-123", "button-1", "payload");

#Events

All on* methods return an Unsubscribe function. Call it to detach.

const unsub = client.onMessagePosted((event) => {
  console.log(event.messageId, event.text);
});

client.onMessageEdited((event) => {
  console.log("Edited:", event.messageId, event.newText);
});

client.onMessageDeleted((event) => {
  console.log("Deleted:", event.messageId);
});

client.onReactionAdded(({ messageId, emoji, user }) => {});
client.onReactionRemoved(({ messageId, emoji, user }) => {});

client.onTypingStarted(({ userId }) => {});
client.onStreamingChunk(({ messageId, chunk, isFinal }) => {});

// Cleanup
unsub();

#Raw Event Listener

client.addEventListener("message:added", (event) => {});

Internal event types: message:added, message:edited, message:deleted, reaction:added, reaction:removed, typing:started, typing:stopped, streaming:started, streaming:chunk, streaming:complete, dm:requested.

#Reconfiguration

Update the client's identity after construction — useful for pre-entry flows where user info is collected via a form before chat starts:

client.reconfigure({
  userId: "user-abc",
  userName: "Alice",
  verifyToken: "encrypted-token",
  conversationId: "conv-xyz",
  headers: { "X-Custom": "value" },
});

Updates HTTP headers (X-User-Id, X-User-Name, X-Verify-Token) and internal state. Fields not included are left unchanged.

#Optimistic Updates

sendMessage() adds your message to local state immediately — before the server responds. The server response can include events[] that the client dispatches. Duplicate message.posted events with the same messageId are ignored.

#Thread IDs

Canonical format: web:{userId}:{conversationId}.

const threadId = client.getThreadId();
// "web:user-123:a1b2c3d4-..."

#Real-Time Broadcasting

#Pusher

import { WebChatClient, PusherBroadcastClient } from "@bootdesk/js-web-adapter-core";

const broadcast = new PusherBroadcastClient({
  key: "pusher-key",
  cluster: "us2",
});

const client = new WebChatClient({
  apiUrl: "https://your-app.com",
  userId: "user-123",
  userName: "Alice",
  broadcastClient: broadcast,
});

#Laravel Echo

import Echo from "laravel-echo";
import Pusher from "pusher-js";
import { LaravelEchoBroadcastClient } from "@bootdesk/js-web-adapter-core";

const echo = new Echo({ broadcaster: "pusher", key: "pusher-key" });
const broadcast = new LaravelEchoBroadcastClient(echo);

const client = new WebChatClient({
  apiUrl: "https://your-app.com",
  userId: "user-123",
  userName: "Alice",
  broadcastClient: broadcast,
});

#HTTP Client

The HttpClient is also exported directly:

import { HttpClient } from "@bootdesk/js-web-adapter-core";

const http = new HttpClient({
  apiUrl: "https://your-app.com",
  headers: { Authorization: "Bearer token" },
  timeout: 15000,
});

// All methods accept AbortSignal
const { data } = await http.get("/api/chat/messages", { limit: "30" });
await http.post("/api/webhooks/web", { text: "Hello" });
await http.del("/api/chat/messages/123");

#Web Push Notifications

Requires a service worker:

import { PushManager, createPushSubscriptionHandlers } from "@bootdesk/js-web-adapter-core";

const push = new PushManager({
  ...createPushSubscriptionHandlers(fetch),
  serviceWorkerUrl: "/chat-service-worker.js",
});

await push.initialize();

push.onStatusChange((status) => {
  console.log("Push status:", status);
  // "unsupported" | "denied" | "default" | "subscribing" | "subscribed" | "error"
});

push.onMessage((data) => {
  console.log("Push:", data.threadId, data.preview);
});

// Subscribe (triggers browser permission prompt)
await push.subscribe();

// Unsubscribe
await push.unsubscribe();

#Typed Events

All events extend ChatEvent with fields type, threadId, timestamp. Parse raw JSON from the server:

import { parseChatEvent, MessagePostedEvent } from "@bootdesk/js-web-adapter-core";

const event = parseChatEvent(rawJson);
if (event instanceof MessagePostedEvent) {
  console.log(event.text);
}

Available event classes:

Class Fields
MessagePostedEvent messageId, text, author, card?, attachments?
MessageEditedEvent messageId, newText, card?
MessageDeletedEvent messageId
ReactionAddedEvent messageId, emoji, user
ReactionRemovedEvent messageId, emoji, user
TypingStartedEvent userId
StreamingChunkEvent messageId, chunk, isFinal
DMRequestedEvent userId
UnknownEvent fallback for unrecognised types

#Utilities

import { generateId, generateConversationId } from "@bootdesk/js-web-adapter-core";

generateId(); // "a1b2c3d4-..." — uses crypto.randomUUID