Skip to main content

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 the company_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.ts adopts 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=1AI 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 on intent: '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.