ai newsMi., 28. Okt.
EilmeldungOpenBMB bringt kostenlose OCR-Software MiniCPM OCR mit Fehlererkennung

5 things I learned building an embeddable AI chat widget that runs on other people's websites

I recently built an AI support chatbot for websites: a site owner uploads their FAQ, gets an assistant, and drops one tag on their site. A chat bubble appears in the corner and answers visitors' questions. The AI part was the easy bit. The hard part was that my code now runs inside websites I don't

dev.toDienstag 23:14 · 27.10.3 Min. Lesezeit
Bild: dev.to

I recently built an AI support chatbot for websites: a site owner uploads their FAQ, gets an assistant, and drops one <script> tag on their site. A chat bubble appears in the corner and answers visitors' questions.

The AI part was the easy bit. The hard part was that my code now runs inside websites I don't control. Here are the five things that took the most time to get right.

The first version used a normal <div>. On the very first test site, the host's global styles ( button {... }, * {... } ) turned my widget into a mess.

The fix is to render everything inside a Shadow DOM and reset styles at the root:

const host = document. createElement ( " div " ); host. id = " my-widget "; document. body. appendChild ( host ); const root = host. attachShadow ({ mode: " open " }); const style = document. createElement ( " style " ); style. textContent = ":host{all:initial} *{box-sizing:border-box;font-family:system-ui,sans-serif} "; root. appendChild ( style ); Enter fullscreen mode Exit fullscreen mode:host { all: initial } stops inherited styles from leaking in, and nothing you put inside the shadow root leaks out to the page.

It's tempting to use innerHTML so the bot can return bold text or links. Don't. The answer is built from text the site owner pasted in and text the visitor typed, and both can contain something like <img src=x onerror=...>.

Every message is created with textContent:

function el ( tag, className, text ) { const node = document. createElement ( tag ); if ( className ) node. className = className; if ( text ) node. textContent = text; // never innerHTML return node; } Enter fullscreen mode Exit fullscreen mode You lose rich formatting, but you can't get XSS through the chat. For a support widget that's a good trade.

The widget is identified by a public key in the script tag, so anyone can copy it onto their own site and burn the owner's AI credits. Each assistant therefore has an allow-list of origins, and the API only answers those:

export function corsHeaders ( origin: string | null, allowedOrigins: string []) { if (! origin ||! allowedOrigins. includes ( origin )) return null; // no CORS → browser blocks it return { " Access-Control-Allow-Origin ": origin, " Access-Control-Allow-Methods ": " GET, POST, OPTIONS ", " Access-Control-Allow-Headers ": " Content-Type ", Vary: " Origin ", }; } Enter fullscreen mode Exit fullscreen mode Two details that bit me:

CORS only protects against browsers, so the server still checks the allow-list and rate-limits, which brings me to:

The chat endpoint is public by design, which makes it an easy target. Every message costs real money in AI tokens. Per-IP rate limits on the widget's chat and config endpoints, plus monthly reply limits per plan enforced on the server, keep one bad actor from draining an account.

Small things made the biggest difference in how "finished" it felt:

if ( theme === " dark " ) style. textContent += dark; else if ( theme!== " light " ) style. textContent += " @media (prefers-color-scheme: dark){ " + dark + " } "; Enter fullscreen mode Exit fullscreen mode Wrapping up If you build anything that runs on other people's sites, assume the page is hostile: isolate your styles, never trust text, restrict who can call you, and rate-limit everything that costs money.

I ended up packaging the whole thing — widget, Next.js 16 dashboard, Stripe billing, Apple/Google/passwordless login and 100+ tests — as a boilerplate called SupportKit AI. There's a 1-minute demo here if you're curious: https://supportkit-ai.netlify.app

Happy to answer questions about any of these in the comments.

Quelle: dev.to (dev.to). Original öffnen