widget — Embeddable Visitor Widget
The lightweight Vue 3 app that customers install on their sites. It launches chat/calls, talks to the AI agents, and captures the rrweb recording.
- Stack: Vue 3 + TS + Vite, Pinia, axios, rrweb, vue-tel-input, ElevenLabs client, Twilio Video. Deliberately lean (no heavy dashboard deps).
- Output: a static SPA/iframe bundle, served by nginx (Docker) and fetched fresh by customer sites — no auto-updater (unlike desktop).
Embedding
A loader snippet on the customer page mounts the widget in an iframe (#app).
Install methods (script tag, GTM, WordPress, Shopify) are covered in the customer
docs; internally the key points are:
- The widget holds a persisted
user_session_id(localStorage UUID, survives refreshes) and thecompany_id. - It communicates with the host page via
postMessage(services/postMessage.ts) — resize, user-interaction signals (to suppress auto-trigger), and the page summary used for page rules. - Agent K landing:
stores/widget.tsadopts a session id from a/k/<id>path (survives GHL redirects) or?knock_id=param, so an Agent K click continues the known lead's session. - Consent gating lives in the host-page loader (
backend/public/widget.js), not this repo — see Consent Gating.
Headless SDK
backend/public/kk-sdk.js (window.KnockKnockSDK) embeds the widget iframe with
&headless=1 — AI chat + AI voice only, no widget UI/video/human escalation.
Bridge: src/services/headlessBridge.ts; postMessage protocol prefixed
knockknock:sdk_ — commands sdk_chat_send, sdk_voice_start/end/mute; events
sdk_ready, sdk_ack, sdk_error, sdk_chat_message/typing/seen,
sdk_voice_status/transcript/level/mute_state. requestCall(phone, opts) posts
POST /ai-outbound/sdk-trigger (E.164 required; secret stays server-side;
rejects outbound_unavailable / rate_limited / request_failed).
Structure (src/)
main.ts create app, init Pinia; rrweb lazy-imported only when the
parent sends the cross-origin-record message (~350KB saved)
stores/widget.ts the main store — most widget state & lifecycle, page rules,
auto-triggers, Agent K session adoption, spoken-link extraction
services/
api.ts fetch-based HTTP client
socket.ts Socket.IO connections (backend, ms-communication; the
ms-sessions socket is no longer opened — it had no consumer)
call.ts Twilio video call setup & permissions
aiCall.ts AI voice call; @elevenlabs/client + Ultravox dynamically
imported at call start
headlessBridge.ts headless-SDK postMessage bridge
postMessage.ts parent-window IPC
sound.ts ringtones/notifications
components/ Widget (launcher), ChatWidget, IncomingCallScreen,
AICallTakeoverScreen, ProductSlider
composables/ useChatLogic, useI18n
i18n/messages.ts single-file locales — en | sv | ar (RTL via [dir='rtl'] CSS)
What it captures & sends
- Session recording: rrweb events emitted to the parent and streamed to
ms-sessions (
widget:event). - Sockets (keyed by
company_id+user_session_id):- backend — company config, session lifecycle.
- ms-communication — chat messages, AI chat streaming, chatbot flow, and
server-pushed Nox auto-engage (
nox-auto-engage-message).
- REST:
/companies/{id},/files/{id}/get,/user-session/{id}/,calculate-lead-auto-connect-message. - Events: open/close, call start/end, chat expand, user-interaction, permission prompts.
Call & chat UX
- Video: Twilio Video (WebRTC) with mute toggles.
- AI voice: ElevenLabs realtime — streaming transcripts, frequency
visualization, one/two-way modes, AI-call takeover screen, agent language
auto-switch (
language_detection). Spoken-link extraction (stores/widget.ts): phonetic URL reconstruction from transcripts ("dot"/"slash" tokens), TLD allowlist for bare hosts, "at"-preceded hosts treated as emails not URLs, links auto-dismiss after 10s (VOICE_CALL_LINKS_TTL_MS). - Chat: Socket.IO message stream supporting human agents, an AI chatbot
(multi-turn flow with options/products/booking), and hybrid escalation
(AI → human queue with a ~3-min fallback). Phone inputs use
vue-tel-input(E.164) in pre-chat/chat/voice screens. - Auto-triggers:
ai_auto_trigger.allow_second_message(default true) lets the auto-connect agent send a follow-up onintent: 'other'; Nox admin-forced engage bypasses heuristic suppressors (cooldowns, 48h agent-connected window, URL rules) but not hard blockers (tab hidden, user interacting, call active). - Products/booking: inline product cards (Shopify/custom) and calendar booking (Calendly/Google/GHL) during chat or AI call.
Versioning & deployment
npm run version:bump (--minor/--major) bumps package.json. npm run build
type-checks and produces the dist/ bundle. Multi-stage Docker (Node 22 builder →
nginx Alpine) pushed to ECR via buildspec.yml. Customers always pull the latest
bundle from the CDN/domain.