Relay · Client integration
SDK and widget
Relay provides a TypeScript core SDK, React bindings and UI, Vue composables, a script-tag widget, and a browser streaming renderer. Choose the thinnest layer that fits your product. Authentication, channel membership, and business authorization still belong to the host.
Distribution status
@tonetify/relay@0.1.0-beta.1 is published on npm under the beta tag. The package includes ESM, CommonJS, declarations, streaming styles, and an IIFE widget bundle. It is licensed under MIT and does not ship source maps.
Install the beta explicitly:
npm install @tonetify/relay@0.1.0-beta.1 Pin the exact beta version in production automation. The package is public, but Relay does not yet publish a server-to-SDK compatibility matrix or an upgrade policy.
Relay + Govern
The SDK transports the turn, Govern bounds the reply
Composed path
-
Host
Gets a scoped Relay session from Govern.
-
Relay SDK
Joins and sends the user message.
-
Govern
Handles
message.createdand chooses an effect. -
Relay
Commits and delivers the resulting reply.
Core SDK
The host must provide a JWT, tenant ID, channel ID, and WebSocket URL. apiUrl is required only by REST convenience helpers such as reaction inspection and notification mute. userId lets those helpers derive viewer-specific state.
import { ChatClient } from "@tonetify/relay";
const client = new ChatClient({
url: "wss://relay.example.com/socket",
apiUrl: "https://relay.example.com",
token,
userId
});
client.connect();
const channel = client.channel(channelId, tenantId);
channel.on("msg:new", (message) => {
console.log(message.seq, message.body);
});
await channel.join();
await channel.send("Hello from Relay", {
nonce: crypto.randomUUID()
}); Call connect() only in the browser. It throws during server-side rendering. Mount it in a React effect, Vue client scope, or equivalent browser lifecycle, and call disconnect() when the integration is disposed.
Stable core and preview surfaces
The contract-beta core covers:
- join, leave, retry-safe send, and cursor updates;
- stable
presence_state,presence_diff,msg:new,catch_up:truncated, andunread_countevents; - connection state and lifecycle events;
- notification mute helpers.
ChatChannel also exposes edit, retract, pin, unpin, thread, reaction, and typing capabilities. These are preview surfaces. Their methods are usable, but the related WebSocket event names, payloads, and recovery behavior can change during contract beta.
channel.send() generates a nonce unless the caller supplies one. If the result is uncertain and the host retries, it must retain the original nonce. Calling send() again without that nonce creates a new logical send.
React integration
Use RelayWidget for a minimal conversation UI:
import { RelayWidget } from "@tonetify/relay/react";
export function SupportChat({ token }: { token: string }) {
return (
<RelayWidget
url="wss://relay.example.com/socket"
apiUrl="https://relay.example.com"
token={token}
userId="current-user-id"
tenantId="tenant-uuid"
channelId="channel-uuid"
channelName="Support"
/>
);
} For a product-owned UI, compose ChatProvider, useChannel, useMessages, and usePresence, or use the core SDK directly. The React layer connects on mount, joins the requested channel, applies optimistic sends, and disconnects on unmount.
Script-tag widget
The build produces dist/tonetify-relay-widget.global.js. Host that exact artifact and mount it explicitly:
<div id="chat-root"></div>
<script src="/assets/tonetify-relay-widget.global.js"></script>
<script>
window.TonetifyRelay.init({
url: "wss://relay.example.com/socket",
token: "host-signed-jwt",
tenantId: "tenant-uuid",
channelId: "channel-uuid",
target: "#chat-root",
mode: "shadow",
theme: { primaryColor: "#3158d3" }
});
</script> mode accepts shadow or iframe. The iframe mode uses a same-document srcdoc; it is a styling boundary, not a cross-origin security boundary. The widget renders a minimal live timeline, presence status, connection errors, and message composer. It is not a complete support desk or moderation UI.
Calling TonetifyRelay.init() again destroys the previous singleton widget before mounting the next one.
Vue and streaming
@tonetify/relay/vue exports useChat, useChannel, useMessages, and usePresence. These composables are implemented and tested, but remain a preview integration surface. They may change before a published compatibility policy exists.
@tonetify/relay/streaming provides a browser-only renderer for progressively displaying text or Markdown, including bounded CJK tokenization. It affects presentation only. Relay still stores one final message body and does not expose a server streaming-message contract.
Recovery boundary
Phoenix reconnects the socket and rejoins channels, but the default React, Vue, and script widget stores are memory-only. A page reload loses the last known seq. The high-level bindings also do not persist or fully drain truncated catch-up windows.
For dependable history recovery, persist the newest confirmed seq, call channel.join(lastSeq), listen for catch_up:truncated, and fetch additional pages through the REST history contract. Do not claim offline storage or exactly-once client rendering from the current SDK.
The SDK does not yet include helpers for attachment upload, search, channel administration, user export, or tenant operations. Use the documented HTTP APIs for those surfaces.
Artifact verification
Before distributing a build, run:
cd sdk
npm run build
npm test
npm run typecheck
npm run pack:check These commands prove the package can be built, typed, tested, and packed from the repository. The public 0.1.0-beta.1 artifact has also been read back from npm. This does not prove browser compatibility targets, server compatibility across versions, semantic-version support, or a managed upgrade service.
Related: Realtime and reconnect · Authentication · Relay overview