@s1chat/sdk
v0.1.1
Published
Headless livestream chat SDK for s1chat (protocol-only, browser bundle).
Maintainers
Readme
@s1chat/sdk
Headless livechat SDK for s1chat. Pure protocol — no UI, no theme, no copy.
The TypeScript SDK that 30+ FE landing pages use to talk to s1chat-core. You bring the UI; the SDK gives you a typed connection, identity, room lifecycle, message I/O, and reconnection.
| | | | --- | --- | | Status | Functional (K1–K3 complete). Browser runtime not yet smoke-tested live; see caveats. | | Bundle | ESM 31KB · IIFE 34KB · ~10KB gzipped (under the 18KB budget) | | Targets | ES2020, evergreen browsers | | Deps | Zero runtime deps — no @rocket.chat/* leak into the browser |
Install
yarn add @s1chat/sdk
# or
npm install @s1chat/sdkOr load directly via <script> (exposes window.S1ChatSDK):
<script src="https://unpkg.com/@s1chat/sdk/dist/index.global.js"></script>Quick start
import { S1Chat } from '@s1chat/sdk';
const chat = await S1Chat.init({
siteId: 'landing-blackfriday-2026',
host: 'https://chat.s1.example',
});
// Guest mode — SDK generates and persists a token in localStorage.
await chat.identify();
// SSO mode — your landing's BE mints an HMAC-signed token.
// await chat.identify({ ssoToken: '<jwt from your landing BE>' });
const room = await chat.openRoom();
chat.on('message', (m) => {
console.log(`[${m.from.kind}] ${m.text}`);
});
chat.on('agentJoined', (a) => console.log(`Agent ${a.name} joined`));
chat.on('roomClosed', () => console.log('Chat ended'));
await chat.send('Hello!');Public API
All methods are on the S1Chat instance returned by init(). Types are
exported alongside.
| method | purpose |
| --- | --- |
| S1Chat.init(opts) | Fetch site config, return a configured client. Does NOT open the WebSocket. |
| chat.identify(opts?) | Upsert visitor. ssoToken for authenticated, nothing for guest. Returns Visitor. |
| chat.openRoom() | Open or resume a livechat room + subscribe to the message stream. Returns Room. |
| chat.send(text) | Send a chat message. Queues during reconnect and flushes on reconnect. |
| chat.loadHistory(opts?) | Fetch prior messages via REST (paginated). Returns Message[]. |
| chat.setTyping(active) | Publish typing/stopped indicator to the agent. No-op without a room. |
| chat.on(event, cb) | Subscribe to events. Returns an unsubscribe fn. |
| chat.close() | Tear down the WebSocket. Keeps the visitor token for next session. |
| chat.logout() | close() + wipe the persisted visitor token. Use for "start new chat" or sign-out. |
| chat.state | Read-only current state ('idle' \| 'connecting' \| 'ready' \| 'reconnecting' \| 'closed'). |
| S1Chat.getPersistedRoomId(siteId) | Static helper — useful for showing "resume?" UI before init. |
Events
| event | payload | when |
| --- | --- | --- |
| message | Message | New message in the room (visitor's echo or agent reply). |
| agentTyping | Agent | Agent is currently typing. Fires repeatedly; debounce client-side. |
| agentJoined | Agent | Agent took the inquiry and joined the room. |
| roomClosed | void | Server closed the room. Local chat.room is nulled. |
| banned | string | Server rejected the visitor as banned. State transitions to closed. |
| stateChange | S1ChatState | Any state-machine transition. Useful for showing connection status UI. |
State machine
idle ──init──► connecting ──identify+room──► ready ◄──► reconnecting
│ │ │
└──────────────────────────┴──────────► closedidle— only briefly, beforeinit()resolvesconnecting— config fetched, WS not yet opened or room not yet subscribedready— WS open, room subscribed,send()works synchronouslyreconnecting— WS dropped;send()queues into the outbox, flushed on reconnectclosed— terminal; callS1Chat.init()again to start a new session
Persistence
The SDK persists two values per siteId so a page reload (or new tab)
can resume the same visitor + room:
| key | storage | when set | when cleared |
| --- | --- | --- | --- |
| s1chat:<siteId>:visitorToken | localStorage (+ cookie fallback) | first identify() | logout() |
| s1chat:<siteId>:roomId | localStorage (+ cookie fallback) | first openRoom() | close(), logout(), server-side roomClosed |
Examples
| folder | what |
| --- | --- |
| examples/vanilla-html/ | Single-file HTML page using <script src=".../index.global.js"> — copy-paste runnable. |
| examples/nuxt/ | A Vue 3 useS1Chat composable + <Chat> SFC. Works in Vite+Vue and Nuxt 3. |
| examples/react/ | A React <Chat> component using the SDK directly. Drop into your own Vite/Next.js app. |
SSO setup
If your landing site authenticates visitors, your BE mints a JWT-like
HMAC-signed token (HS256) and your FE passes it to identify():
// Server-side (your landing BE)
import { createHmac } from 'crypto';
const now = Math.floor(Date.now() / 1000);
// 1. Encode the payload as base64url — this is what the HMAC signs.
const payloadB64 = Buffer.from(JSON.stringify({
siteId: 'landing-blackfriday-2026',
externalUserId: 'user_42',
name: 'Alice',
email: '[email protected]',
iat: now, // required: issued-at (seconds)
exp: now + 300, // 5 min validity
})).toString('base64url');
// 2. Sign the base64url payload string (not the raw JSON).
const sig = createHmac('sha256', process.env.S1CHAT_HMAC_KEY)
.update(payloadB64)
.digest('base64url');
const ssoToken = `${payloadB64}.${sig}`;The HMAC key is the one s1chat-core returned when you created the Site.
Rotation is supported via POST /api/v1/s1c/sites/:siteId/rotate-secret;
previous key remains valid for the grace period (default 60min).
Full sequence + key rotation flow: ../../docs/usage.md §4 and §10.
Caveats
- Browser runtime untested: the SDK passes 57/57 unit tests under happy-dom and builds clean, but full visitor↔agent flow via a real browser has not been smoke-tested. K4 examples are the first chance to do that. Open an issue at the parent repo if you hit anything.
- No file upload yet:
features.fileUploadis reported byinit()but the SDK has nosendFile()method. Coming in Phase-5. - No read receipts yet: same —
features.readReceiptflags it, no method exposed. - Agent typing is not debounced: every
user-activityevent firesagentTyping. Add your own "stopped typing after N ms idle" timer in the consuming component (the SDK is protocol-only, no UX baked in).
Scripts
| script | what it does |
| --- | --- |
| yarn workspace @s1chat/sdk build | ESM + CJS + IIFE bundles via tsup |
| yarn workspace @s1chat/sdk dev | tsup --watch |
| yarn workspace @s1chat/sdk test | vitest (happy-dom) |
| yarn workspace @s1chat/sdk typecheck | tsc --noEmit |
| yarn workspace @s1chat/sdk lint | eslint |
Project context
- Parent repo overview: ../../README.md
- Architecture + SDK contract: ../../docs/solution-design.md §9
- End-to-end usage guide: ../../docs/usage.md
- Protocol-only boundary rule: ../../.claude/rules/protocol-only-boundary.md
License
TBD — inherits from the parent repo's choice.
