@agentdeskbot/react
v1.0.0
Published
AgentDesk AI chat widget — React & Next.js SDK
Maintainers
Readme
@agentdeskbot/react
Drop-in React & Next.js SDK for the AgentDesk AI chat widget.
Embed a fully-typed, RAG-grounded support bot in your React app in under 30 seconds — no <script> tag wrangling, no useEffect boilerplate, no SSR gymnastics.
Table of Contents
- Features
- Quick Start
- Installation
- Usage by Framework
- Props API
- Lifecycle Events
- Advanced
- How it works
- Troubleshooting
- Migrating to v0.1.0
- Related Packages
- License
Features
- One component, zero config —
<AgentDeskWidget botId="…" />is everything you need. - SSR-safe for Next.js — dedicated
/nextjssubpath withnext/dynamic({ ssr: false })baked in. - Strictly typed — full TypeScript surface, including inferred prop types and event payloads.
- Zero runtime deps outside React —
react,react-dom, andnext(optional) are externalized. - Tree-shakable —
"sideEffects": false; ESM and CJS bundles ship with.d.tsdeclarations. - Idempotent injection — the SDK deduplicates scripts so re-mounts (StrictMode, HMR) don't double-load the widget.
- Origin-validated events —
postMessagelisteners verifyevent.originand thebotIdpayload before invoking callbacks.
Quick Start
import { AgentDeskWidget } from '@agentdeskbot/react';
export default function App() {
return (
<>
<YourApp />
<AgentDeskWidget botId="YOUR_BOT_ID" />
</>
);
}That's it. The widget will mount as a floating launcher bubble in the bottom-right corner of your page.
Installation
# npm
npm install @agentdeskbot/react
# yarn
yarn add @agentdeskbot/react
# pnpm
pnpm add @agentdeskbot/reactPeer dependencies (automatically installed alongside in modern setups)
| Package | Required | Optional | Supported versions |
| --- | --- | --- | --- |
| react | ✅ | — | >= 18.0.0 |
| react-dom | ✅ | — | >= 18.0.0 |
| next | — | ✅ | >= 13.0.0 (only required if you import from /nextjs) |
Note: If you are on an older React (≤ 17) or Next.js (≤ 12), the widget will still work but you'll need to either upgrade or fall back to the manual script-tag approach below.
Usage by Framework
Plain React (CRA, Vite, Remix)
// src/App.tsx
import { AgentDeskWidget } from '@agentdeskbot/react';
export default function App() {
return (
<>
<main>{/* your app */}</main>
<AgentDeskWidget botId="YOUR_BOT_ID" />
</>
);
}No special directives, no provider, no config — just import and render.
Next.js App Router
For App Router projects, import from the /nextjs subpath. This wrapper uses next/dynamic with ssr: false so the widget is never executed on the server, eliminating window is not defined errors.
// app/layout.tsx
import { AgentDeskWidget } from '@agentdeskbot/react/nextjs';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<AgentDeskWidget botId="YOUR_BOT_ID" />
</body>
</html>
);
}Why a separate subpath? Next.js' App Router treats files with
'use client'as Client Components, butnext/dynamic({ ssr: false })is the only reliable way to guarantee the inner module never executes during SSR. The/nextjsentry handles that for you automatically.
Next.js Pages Router
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { AgentDeskWidget } from '@agentdeskbot/react/nextjs';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<AgentDeskWidget botId="YOUR_BOT_ID" />
</>
);
}Cross-origin / Self-hosted Embeds
If your AgentDesk backend runs on a different domain than the page embedding the widget (CDN, separate staging domain, white-label deployment), point apiOrigin and scriptSrc at the correct hosts:
<AgentDeskWidget
botId="YOUR_BOT_ID"
apiOrigin="https://support.yourapp.com"
scriptSrc="https://support.yourapp.com/widget.js"
/>| Prop | When you need it |
| --- | --- |
| apiOrigin | The widget is embedded on app.com but the chat backend lives on support.yourapp.com. |
| scriptSrc | Same as above, but for the actual widget.js bundle URL. |
Props API
| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| botId | string | — | ✅ | The Bot ID from your AgentDesk dashboard. |
| mode | 'launcher' \| 'inline' | 'launcher' | — | 'launcher' = floating bubble (default). 'inline' = fills the nearest positioned ancestor. |
| scriptSrc | string | 'https://agentdeskbot.vercel.app/widget.js' | — | URL to the compiled widget.js file. Override for custom or self-hosted deployments. |
| apiOrigin | string | 'https://agentdeskbot.vercel.app' | — | Base URL of your AgentDesk backend. Required for custom or self-hosted deployments. |
| configUrl | string | {apiOrigin}/api/widget/config/{botId} | — | Fully-qualified override for the widget config fetch endpoint. |
| theme | string | — | — | Optional theme name for the widget (e.g. 'webchat-v1'). Mount-only. |
| cspNonce | string | — | — | Optional CSP nonce to apply to the injected script and dynamic styles. Mount-only. |
| position | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | — | Optional fixed position for the launcher bubble and widget pane. |
| className | string | — | — | Optional custom HTML class name to apply to the host custom element. |
| onOpen | () => void | — | — | Callback invoked when the user opens the chat widget. |
| onClose | () => void | — | — | Callback invoked when the user closes the chat widget. |
| onReady | () => void | — | — | Callback invoked when configuration loads successfully and widget is ready. |
| onError | (error: { message: string }) => void | — | — | Callback invoked when widget fails to load config or establish connection. |
| onMessageSent | (message: { text: string }) => void | — | — | Callback invoked when the user/customer sends a message. |
| onWidgetInjected | () => void | — | — | Callback invoked when the widget custom element is injected into the DOM. |
WidgetMode reference
import type { WidgetMode } from '@agentdeskbot/react'; // re-exported from @agentdeskbot/core
type WidgetMode = 'launcher' | 'inline';Lifecycle Events
The component supports a rich set of lifecycle callbacks to track user interaction and widget status. Internally, these callbacks are wired to the widget's postMessage protocol with origin verification.
<AgentDeskWidget
botId="YOUR_BOT_ID"
onOpen={() => {
analytics.track('agentdesk_widget_opened');
}}
onClose={() => {
analytics.track('agentdesk_widget_closed');
}}
onReady={() => {
console.log('AgentDesk widget is ready to chat!');
}}
onError={({ message }) => {
console.error('Failed to load AgentDesk widget:', message);
}}
onMessageSent={({ text }) => {
analytics.track('agentdesk_message_sent', { length: text.length });
}}
onWidgetInjected={() => {
console.log('Widget custom element has been injected into the DOM.');
}}
/>Fresh Callback Identities: All callbacks are tracked dynamically. Changing callback function identities or state variables referenced inside your callbacks on subsequent renders is fully supported, safe, and will never cause the widget script to re-inject, unmount, or lose listener mappings.
Advanced
Multiple bots on the same page
The SDK deduplicates scripts by botId, so mounting two <AgentDeskWidget> instances with different botId values will inject two independent widget bundles:
<>
<AgentDeskWidget botId="SALES_BOT_ID" mode="inline" />
<AgentDeskWidget botId="SUPPORT_BOT_ID" mode="launcher" />
</>Mounting the same botId twice (e.g. under StrictMode, or across HMR boundaries) is a no-op — only the first injection runs, and both component instances' callbacks are registered and fired.
Recommended Multi-Bot Patterns
When mounting multiple bots on the same page:
- Avoid Launcher Overlaps: Do not mount more than one bot in
launchermode at the same position. If you must have multiple launchers, use thepositionprop to space them out (e.g., one on thebottom-rightand one on thebottom-left). - Prefer Inline Mode: For secondary assistants or context-specific support, prefer
mode="inline"to render the bot within a sidebar, dashboard tab, or modal, keeping the main floating launcher clean. - Event Attribution: Use
onMessageSentandonOpencallbacks on each widget to attribute user interactions and analytics tracking to the specificbotId.
Programmatic control
The component renders null and owns the widget lifecycle internally. If you need imperative APIs (open/close, send messages), use the manual script-tag approach and call into the custom element directly:
const el = document.querySelector('agentdesk-widget') as HTMLElement & {
open?: () => void;
close?: () => void;
};
el.open?.();Manual script-tag fallback
If you can't (or don't want to) install the React package, drop the script directly into your HTML:
<script
src="https://agentdeskbot.vercel.app/widget.js"
data-bot-id="YOUR_BOT_ID"
data-theme="webchat-v1"
data-mode="launcher"
async
></script>The same data-* attributes used by the React SDK are honored by the script.
How it works
Under the hood, the React component:
- Deduplicates — On mount, it scans
documentfor existingscript[data-agentdesk]tags and bails early if one for the currentbotIdis already present. - Injects — Otherwise it appends a
<script>element pointing towidget.jsand tags it withdata-agentdeskanddata-bot-id. - Listens — It registers a single global
messageevent listener onwindowand forwards events to the appropriate callback registries matching thebotIdand origin checks. - Cleans up — On unmount, the component removes its listener registration. If the last component referencing a
botIdunmounts, the script tag and custom element are removed from the DOM.
React tree mount
└─ <AgentDeskWidget botId="…" />
├─ injects <script data-agentdesk data-bot-id="…" src="https://agentdeskbot.vercel.app/widget.js" />
├─ registers component callback in global dispatch Set
└─ renders nullTroubleshooting
You are importing from @agentdeskbot/react in a Server Component context. Switch to the /nextjs subpath:
import { AgentDeskWidget } from '@agentdeskbot/react/nextjs';Using @agentdeskbot/react/nextjs utilizes next/dynamic with ssr: false to ensure the widget bundle is only executed client-side.
By default, the SDK requests https://agentdeskbot.vercel.app/widget.js. If you are using a self-hosted or white-labeled deployment, specify your custom absolute URL using the scriptSrc prop:
<AgentDeskWidget
botId="YOUR_BOT_ID"
scriptSrc="https://support.yourapp.com/widget.js"
/>If your page enforces a strict CSP, inline scripts or dynamically injected scripts might be blocked. You can propagate a CSP cryptographic nonce directly to the injected script tag using the cspNonce prop:
<AgentDeskWidget
botId="YOUR_BOT_ID"
cspNonce="random_cryptographic_nonce_string"
/>This nonce is applied to the injected <script> tag and passed down to dynamically loaded styles.
If the widget loads but displays a loading/connection error, the widget configuration endpoint might be unreachable. Check the browser console network tab. By default, it requests {apiOrigin}/api/widget/config/{botId}. You can override this endpoint entirely using the configUrl prop:
<AgentDeskWidget
botId="YOUR_BOT_ID"
configUrl="https://api.mysupport.com/custom/widget-config"
/>For security, the message listener validates event.origin against the page origin, apiOrigin prop, and scriptSrc prop.
If you are running the widget backend on api.support.com but embedding it on app.com, ensure you pass the correct apiOrigin and scriptSrc props so the origins are explicitly whitelisted:
<AgentDeskWidget
botId="YOUR_BOT_ID"
apiOrigin="https://api.support.com"
scriptSrc="https://api.support.com/widget.js"
/>React 18 StrictMode intentionally double-invokes effects in development to surface side-effect bugs. The SDK is designed to handle this — it deduplicates scripts by botId. If you still see two widgets, make sure you aren't rendering two different <AgentDeskWidget> components with the same botId on the page.
If your monorepo's package manager doesn't hoist react correctly (rare with npm/yarn/pnpm workspaces), add an explicit react peer dependency in your app's package.json. The SDK declares react and react-dom as peer dependencies with no optional flag.
Migrating to v0.1.0
Breaking change (v0.1.0): The widget's
postMessagetarget origin is now strictlywindow.location.origin(previously*). If you were listening foragentdesk-widget-open/agentdesk-widget-closeevents directly viawindow.addEventListener('message', …), your listener must be on the same origin as the page mounting the widget, or it will silently drop events.
The official React and Vue SDKs handle this for you — no action is required unless you wrote a custom adapter.
Related Packages
| Package | Description |
| --- | --- |
| @agentdeskbot/core | Shared TypeScript types (used internally). |
| @agentdeskbot/vue | Vue 3 & Nuxt 3 SDK for the same widget. |
License
MIT © AgentDesk
