@oxpulse/chat-widget
v0.24.2
Published
OxPulse Chat embed widget — Custom Element + iframe mode
Maintainers
Readme
@oxpulse/chat-widget
Embeddable OxPulse chat widget - <oxpulse-chat> Custom Element with an iframe embed entry point.
Use it to add a chat panel to any page without a framework, or import it into React/Vue/Svelte. The package bundles @oxpulse/chat-sdk and @oxpulse/voice-core as runtime dependencies, so you do not need to install them separately.
Install
npm install @oxpulse/chat-widget@0
# or
pnpm add @oxpulse/chat-widget@0
# or
yarn add @oxpulse/chat-widget@0Quickstart
Script tag (CDN)
Load from a versioned CDN path. The crossorigin attribute is required when integrity is present.
<script type="module"
src="https://cdn.oxpulse.chat/widget/0.21/index.js"
crossorigin="anonymous"></script>
<oxpulse-chat
app-id="your-app-id"
jwt="token-from-your-backend"
room-id="chat-room-id">
</oxpulse-chat>Pin to a specific version path in production and add a Subresource Integrity hash. /widget/latest/ is a convenience alias only.
npm / bundler
import '@oxpulse/chat-widget';
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'oxpulse-chat': React.HTMLAttributes<HTMLElement> & {
'app-id': string;
jwt: string;
'room-id': string;
mode?: 'inline' | 'iframe';
theme?: 'light' | 'dark' | 'auto';
lang?: string;
};
}
}
}
export function ChatPanel({ appId, jwt, roomId }) {
return <oxpulse-chat app-id={appId} jwt={jwt} room-id={roomId} />;
}Programmatic
import { mount } from '@oxpulse/chat-widget';
const widget = mount(document.getElementById('chat-container'), {
appId: 'your-app-id',
jwt: await fetchJwtFromYourBackend(),
roomId: 'support-room',
theme: 'dark',
});
// later
widget.destroy();Attributes / config
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| app-id | string | yes | - | OxPulse app ID |
| jwt | string | yes | - | Signed SDK JWT from your backend |
| room-id | string | yes | - | Room to open |
| mode | inline | iframe | no | inline | Render mode (iframe is experimental) |
| theme | light | dark | auto | no | auto | Colour scheme |
| lang | BCP 47 string | no | auto | Locale override |
| self-uid | string | no | - | UID of the current user (for own-reaction chips) |
| base-url | string | no | https://oxpulse.chat | Server base URL |
| allow-anon-read | boolean | no | false | Mint a short-lived anon-read token when jwt is absent |
| allow-write | boolean | no | false | Enable named-write compose UI |
| write-mint-endpoint | string | when allow-write | - | Your backend endpoint that mints a named-write token |
| reactions-enabled | boolean | no | true | Show reaction UI |
| pinned-messages-enabled | boolean | no | true | Show pinned messages banner + pin/unpin buttons (inline mode only) |
| seller-catalog | boolean (presence) | no | off | Opt-in: render the seller product-catalog picker button in the composer toolbar. When present, the widget constructs an SDKCatalogClient from the same jwt + base-url it already uses and passes it to the Composer. The JWT must carry catalog:read:* scope (server-enforced). Only active when a composer is mounted (write-capable client). |
mode='iframe' is experimental and does not yet support allow-write.
Events
Attach to the <oxpulse-chat> element:
const el = document.querySelector('oxpulse-chat');
el.addEventListener('oxpulse-chat:ready', (ev) => {
console.log('ready', ev.detail.roomId);
});
el.addEventListener('oxpulse-chat:token-expired', async () => {
el.refreshToken(await fetchFreshToken());
});
el.addEventListener('oxpulse-chat:error', (ev) => {
console.error(ev.detail.code, ev.detail.message);
});| Event | detail | When |
|---|---|---|
| oxpulse-chat:ready | { roomId } | Widget connected and passed origin check |
| oxpulse-chat:error | WidgetError | Unrecoverable error |
| oxpulse-chat:token-expired | { roomId } | Server returned 401; call element.refreshToken() |
| oxpulse-chat:message-sent | { roomId, msgId } | A named-write message was sent |
| oxpulse-chat:write-error | WidgetError | A named-write send/reaction failed |
| oxpulse-chat:attachment-error | { msgId, attachmentId, reason } | Attachment hydration reached final failure |
| oxpulse-chat:decrypt-error | { roomId, msgId, seq, reason } | A row with an unsealError was rendered |
| oxpulse-chat:reconnect-exhausted | { roomId, attempts } | Reconnector gave up after 10 retries |
Origin allowlist
Your JWT must include an aud_origins claim listing allowed embed origins. Configure this when minting tokens via POST /api/sdk/tokens.
{
"aud_origins": ["https://yoursite.com", "https://*.yoursite.com"],
"sub": "user-123",
"exp": 1748000000
}Localhost is always allowed in dev mode (inline, hostname === localhost).
API surface
OxpulseChatElement- the<oxpulse-chat>custom element classdefineElement()- register the custom element (called automatically on script-tag load)mount(target, MountOptions)- programmatic mount; returns{ destroy: () => void }WidgetConfig/MountOptions/WidgetEventMap/ProductMetaWidgetError/OriginNotAllowedError/WidgetErrorCodecheckOrigin(config)/decodeJwtPayload(jwt)/matchOriginPattern(origin, pattern)/OriginCheckResultisParentMessage(data)/isIframeMessage(data)sendToParent(msg)/onParentMessage(handler)/onIframeMessage(iframe, handler)ParentMessage/IframeMessage
The iframe entry point is available at @oxpulse/chat-widget/iframe.
Security
- Origin check runs client-side before any network call (prevents misconfiguration).
- Server enforces the same allowlist on all API requests.
- iframe mode uses
sandbox="allow-scripts allow-same-origin".
License
AGPL-3.0-or-later. See LICENSE.
