Your website can open the DobroDesk widget without exposing a support credential or duplicating the launcher.
Install the published package
The browser package is published at https://www.npmjs.com/package/@dobrodesk/widget/v/0.3.1. Keep the package version in your lockfile and copy the public Integration ID from Admin > Channels > Website widgets.
npm install @dobrodesk/widget@0.3.1Configure and open the widget
After installation, copy the public Integration ID from Admin > Channels > Website widgets and configure the widget from your application.
Use launcher: hidden when your application owns the button. The built-in launcher hides while the panel is open and restores focus when it closes. Omit a reduceMotion override so the widget follows the visitor's current operating-system preference, including live prefers-reduced-motion changes.
import { createDobroDeskWidget } from "@dobrodesk/widget";
const widget = createDobroDeskWidget({
integrationId: "wgt_0123456789abcdef0123456789abcdef",
locale: "auto",
launcher: "hidden",
});
await widget.ready;
widget.open();Use a custom trigger or inline container
Keep launcher: hidden when your product owns the trigger, then connect that control to open and close the same widget client. For an in-product panel, give the container an explicit width and height before creating the client so its layout does not jump while the frame loads.
The custom trigger stays keyboard reachable and exposes aria-controls and aria-expanded. Do not create a second client when a route remounts. Reuse the existing client and keep the same customer session and draft.
import { createDobroDeskWidget } from "@dobrodesk/widget";
const supportSlot = document.querySelector("#support-slot");
const helpButton = document.querySelector("#help-button");
if (!(supportSlot instanceof HTMLElement) || !(helpButton instanceof HTMLButtonElement)) {
throw new Error("Support widget host elements are missing");
}
const widget = createDobroDeskWidget({
integrationId: "wgt_0123456789abcdef0123456789abcdef",
launcher: "hidden",
container: supportSlot,
});
helpButton.addEventListener("click", () => widget.toggle());
widget.onStateChange((state) => {
helpButton.setAttribute("aria-expanded", String(state.visible));
});
await widget.ready;Handle loading and signed identity
The widget frame owns customer-facing loading indicators. Use onLoadingChange for page-level state such as disabling a surrounding checkout action. Loading phases include initialization, content and session loading, message delivery, assistant waiting, action execution and reconnecting.
Anonymous web sessions are supported. For a signed customer, your backend must issue a short-lived HS256 identity JWT and the browser may pass it with the known email. Never place the signing secret or a support API token in browser code. Call logout before the authenticated host session ends or another customer can use the browser.
const stopLoading = widget.onLoadingChange((loading) => {
checkoutButton.disabled = loading.active;
});
// The token is returned by your backend. This function belongs to your app.
widget.setIdentityToken(await loadIdentityTokenFromYourBackend());
widget.prefill({ email: currentUser.email, name: currentUser.name });Handle loading, visibility and accessibility
Use onLoadingChange for host-level disabled states while the widget renders message-level loading indicators inside its own surface. Keep the inline slot mounted while a message is pending so the widget can reconcile retries and prevent duplicate sends.
Call hide or show when navigation temporarily removes the surface from view. Destroy only when the page or account really ends. The widget follows prefers-reduced-motion and reacts to changes. A hidden launcher and an inline panel still need a visible keyboard reachable close path.
- On a temporary route hide, call widget.hide() and retain the client and container.
- On remount, reuse the same client and call widget.show() after the container is connected.
- On account logout, call widget.logout() and widget.destroy(), then create a new client for the next account.
Verify the integration
- Open the page from an allowed origin and check that the panel opens once and the hidden launcher remains hidden.
- Send a test message from a private browser session, then confirm that it reaches the configured Inbox without creating a second conversation.
- Disable the network briefly and wait for the widget's reconnecting state. Let the frame retry its own pending operation. Do not submit the same customer message from a second control while the first request is unresolved.
- Switch the operating-system reduced-motion setting while the widget is open and confirm that later transitions honor the new preference.