npm package: @nimbusai/webchat-sdk
Description: Embeddable WebChat SDK with Shadow DOM isolation and WebSocket messaging
Latest version: 1.0.5

# Nimbus WebChat SDK

A modern, embeddable WebChat SDK with Shadow DOM isolation and real-time WebSocket messaging. Built with TypeScript, Tailwind CSS, and Lucide icons.

[![npm version](https://badge.fury.io/js/%40nimbus%2Fwebchat-sdk.svg)](https://badge.fury.io/js/%40nimbus%2Fwebchat-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript 5.7](https://img.shields.io/badge/typescript-5.7-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Tailwind CSS 3.4](https://img.shields.io/badge/tailwindcss-3.4-06B6D4?logo=tailwindcss&logoColor=white)](https://tailwindcss.com/)
[![Shadow DOM](https://img.shields.io/badge/shadow_dom-isolated-8B5CF6)](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM)

---

## Screenshots

### Full Dashboard View
<p align="center">
  <img src="screenshoots/full-screen.png" alt="Full Application Dashboard" width="800">
</p>

### Theme Variations
<p align="center">
  <img src="screenshoots/welcome-message.png" alt="Light Theme - Welcome Message" width="300">
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img src="screenshoots/black-edition.png" alt="Dark Theme - Black Edition" width="300">
</p>

### Layout Options
<p align="center">
  <img src="screenshoots/bottom.png" alt="Bottom Position with Typing Indicator" width="300">
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img src="screenshoots/sidepanel.png" alt="Sidepanel Integration" width="300">
</p>

### File Upload & Media Handling
<p align="center">
  <img src="screenshoots/send-json.png" alt="JSON Data Exchange" width="300">
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img src="screenshoots/describe-pictures.png" alt="AI Image Description" width="300">
</p>

<p align="center">
  <img src="screenshoots/max-size.png" alt="File Size Limit" width="300">
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img src="screenshoots/uploaded-file-preview.png" alt="File Preview" width="300">
</p>

## Features

- **Zero dependencies** (except Lucide icons)
- **Fully customizable** UI and theming
- **Responsive design** with mobile support
- **Shadow DOM isolation** prevents CSS conflicts
- **Real-time WebSocket** messaging
- **TypeScript support** with full type definitions
- **Multiple formats** (ESM, CommonJS, UMD)
- **Flexible icons** (Lucide icons or custom images)
- **Automatic reconnection** with configurable retries
- **Optional message history** loading with resume conversation
- **Multiple layouts** (floating popup or sidepanel)
- **File upload support** with image previews (config-driven)
- **Character limits** with counter display
- **Developer-friendly** debug mode and logging

## Tech Stack

| Technology | Version | Purpose |
|---|---|---|
| [TypeScript](https://www.typescriptlang.org/) | `^5.7.2` | Type-safe development |
| [Tailwind CSS](https://tailwindcss.com/) | `^3.4.17` | Utility-first styling (pre-compiled into Shadow DOM) |
| [tsup](https://tsup.egoist.dev/) | `^8.3.5` | Bundler (ESM + CJS + IIFE) |
| [PostCSS](https://postcss.org/) | `^8.4.49` | CSS processing |
| [Autoprefixer](https://github.com/postcss/autoprefixer) | `^10.4.20` | Vendor prefix automation |
| [Vitest](https://vitest.dev/) | `^2.1.8` | Unit testing |
| [Lucide](https://lucide.dev/) | inline SVGs | Icons (Shadow DOM compatible) |

## Installation

### npm / yarn / pnpm

```bash
npm install @nimbusai/webchat-sdk
# or
yarn add @nimbusai/webchat-sdk
# or
pnpm add @nimbusai/webchat-sdk
```

```typescript
import { NimbusChat } from "@nimbusai/webchat-sdk";

const chat = new NimbusChat({
  agent_version_id: "550e8400-e29b-41d4-a716-446655440000",
});

chat.open();
```

### CDN (script tag)

```html
<script src="https://cdn.nimbus.ai/sdk/nimbus-chat.umd.global.js"></script>
<script>
  NimbusChat.init({
    agent_version_id: "550e8400-e29b-41d4-a716-446655440000",
  });
</script>
```

The CDN bundle exposes a global `NimbusChat` object with a singleton `init()` method — safe to call multiple times.

## Quick Start

```typescript
const chat = new NimbusChat({
  agent_version_id: "550e8400-e29b-41d4-a716-446655440000",
  style: { position: "bottom-right" }
});
```

The widget renders a floating chat bubble in the corner of the page. Click it to open the chat panel. The WebSocket connection is established lazily — only when the user opens the chat for the first time.

## API Reference

### Initialization

```javascript
// NPM usage
const chat = new NimbusChat(config);

// CDN usage
const chat = NimbusChat.init(config);
```

### Methods

| Method | Description |
|--------|-------------|
| `chat.open()` | Show the chat widget |
| `chat.close()` | Hide the chat widget |
| `chat.toggle()` | Toggle chat widget visibility |
| `chat.destroy()` | Remove the widget from DOM completely |

---

## Configuration Reference

The SDK provides extensive configuration options to customize every aspect of the chat widget. All properties are optional except `agent_version_id`.

### Required Configuration

| Property | Type | Description |
|----------|------|-------------|
| `agent_version_id` | `string` | **Required** - Your agent ID (UUID) |

### Core Settings

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `dns` | `string` | `"api.nimbus.ai/api/v1/webchat"` | API endpoint for chat services |
| `debug` | `boolean` | `false` | Enable debug logging to console. When `true`, the widget auto-opens on init. |
| `allowNewChat` | `boolean` | `false` | Show "New Chat" button in header |

### Style Configuration

Controls layout, dimensions, and visual appearance of the widget:

```javascript
style: {
  position: "bottom-right",
  width: "380px",             // CSS width string (e.g. "380px", "100%")
  height: "560px",            // CSS height string (window mode only)
  font: '"Inter Variable", sans-serif',  // Global font family override
  background: "#ffffff",      // CSS color or image URL
  mobile: {
    position: "bottom-right", // Override position on mobile devices
    breakpoint: "480px"       // Viewport width to trigger mobile mode
  }
}
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `style.position` | `ChatPosition` | `"bottom-right"` | Widget position on page. <br>Options: `"bottom-right"`, `"bottom-left"`, `"sidepanel-left"`, `"sidepanel-right"` |
| `style.width` | `string` | `"380px"` | Chat window width (CSS value) |
| `style.height` | `string` | `"560px"` | Chat window height (CSS value, window mode only) |
| `style.font` | `string` | System font | Global font family override |
| `style.background` | `string` | `"#f3f1ef"` | Chat body background (CSS color or image URL) |
| `style.mobile.position` | `ChatPosition` | Same as `style.position` | Widget position on mobile devices |
| `style.mobile.breakpoint` | `string` | `undefined` | Viewport width breakpoint for mobile mode (e.g. `"480px"`) |

### Theme Configuration

Global theme colors used throughout the widget:

```javascript
theme: {
  primary: "#ffce1c",      // Primary color (buttons, headers, bubbles)
  secondary: "#f3f1ef"     // Secondary color (text, icons on primary bg)
}
```

### Chat Bubble Settings

The floating trigger button that opens/closes the chat:

```javascript
bubble: {
  position: "bottom-right",
  autoHide: false,           // Hide bubble when chat is open
  icon: {
    img: "message-circle",   // Lucide icon or image URL
    size: { width: 35, height: 35 }
  }
}
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `position` | `BubblePosition` | `"bottom-right"` | Bubble position. <br>Options: `"bottom-right"`, `"bottom-left"` |
| `autoHide` | `boolean` | `false` | Hide the bubble when the chat panel is open |
| `icon` | `IconConfig \| null` | Default Lucide icon | Custom icon for the bubble |

### Header Configuration

```javascript
header: {
  icon: {
    img: "https://example.com/logo.svg",  // Custom image URL or Lucide icon
    size: { width: 125, height: 19 }
  },
  text: {
    value: "Support Chat",
    color: "#1e293b",
    font: "Inter",
  },
  color: {
    primary: "#f3f1ef",    // Header background color
    secondary: "#ffce1c"   // Close button icon color
  }
}
```

### Welcome Message

Shown when chat is empty, automatically hidden after first message:

```javascript
welcome: {
  display: true,          // Show/hide welcome message
  preTitle: {
    value: "Welcome to :",
    text: { color: "#1e293b", font: "" }
  },
  title: {
    value: " Nimbus Chat!",
    text: { color: "#1e293b", font: "" }
  },
  subtitle: {
    value: "Send a message to start a conversation",
    text: { color: "gray", font: "" }
  }
}
```

### Message Styling

Configure user and bot message bubbles independently:

```javascript
userMessage: {
  background: "#DCF8C6",
  width: "80%",             // Max width of message bubble (CSS value). Default: "80%"
  text: {
    color: "#111B21",
    font: "",
    size: 13
  },
  icon: {
    img: "user",  // Lucide icon or custom image URL
    size: { width: 20, height: 20 }
  }
}

botMessage: {
  background: "#FFFFFF",
  width: "80%",             // Max width of message bubble (CSS value). Default: "80%"
  text: {
    color: "#111B21",
    font: "",
    size: 13
  },
  icon: {
    img: "bot",  // Lucide icon or custom image URL
    size: { width: 20, height: 20 }
  }
}
```

> **Note**: Set `icon: null` to explicitly hide the avatar. Omit `icon` entirely to use the default.

### Input Configuration

```javascript
input: {
  placeholder: "Ask Nimbus",
  expandable: true,        // Auto-expanding textarea (up to 8 lines)
  text: {
    color: "#1e293b",
    font: "",
  },
  background: {
    primary: "white",      // Input field background
    secondary: "#f3f1ef"   // Container background
  },
  upload: {                // File upload settings (omit or set null to disable)
    maxFileSize: 5242880,  // 5MB in bytes
    errorDisplayDuration: 2000,  // Error message duration (ms)
    allowedFileTypes: [
      "image/jpeg",
      "image/png",
      "image/gif",
      "application/pdf"
    ],
    icon: {
      img: "paperclip",
      size: { width: 20, height: 20 }
    }
  },
  maxCharacters: {         // Character limit with counter
    limit: 10000,          // Maximum characters allowed
    text: {
      color: "gray",
      size: 12
    }
  }
}
```

> **Note**: File upload is config-driven. Omit the `upload` property or set it to `null` to disable file uploads. Provide an `upload` object to enable the upload button.

### Send Button

```javascript
sendButton: {
  align: false,            // true = input and button on same row
  icon: {
    img: "send",          // Lucide icon or custom image URL
    size: { width: 20, height: 20 }
  }
}
```

### Reconnection Settings

Control automatic reconnection behavior:

```javascript
reconnect: {
  attempts: 5,      // Maximum reconnection attempts
  timeout: 5000     // Delay between attempts (ms)
}
```

### Wait for Reply Configuration

Control message sending while waiting for bot responses. Works independently from the typing indicator.

```javascript
waitForReply: {
  timeout: 30000,          // Max wait time (ms) before allowing next message
  firstReply: false        // Wait for bot's first message in new chat
}
```

> **Note**: Omit `waitForReply` to disable input blocking entirely.

### Typing Indicator Configuration

Show a typing indicator when waiting for bot responses. Independent from `waitForReply` — you can use either or both.

```javascript
isTypingIndicator: {
  position: "bottom",
  title: {
    value: "AI Assistant is typing...",
    text: {
      color: "#1e293b",
      font: ""
    }
  }
}
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `position` | `"top" \| "bottom"` | `"top"` | Where to display the indicator. <br>Options: `"top"` (header status area), `"bottom"` (above input area) |
| `title.value` | `string` | `"AI Assistant is typing..."` | Indicator text |
| `title.text` | `TextConfig` | `{ color: "#1e293b" }` | Text styling |

> **Note**: Omit `isTypingIndicator` to disable the typing indicator.

### Resume Conversation

When provided, enables loading message history on reconnect and shows a "Show More" button to load older messages. Omit or set to `null` to disable (default). The `flow_id` is always persisted and reused regardless.

```javascript
resumeConversation: {
  messagesPerPage: 10,       // Messages loaded per page
  showMore: {
    value: "Show More",
    sticky: true,            // Stick to top of message list
    background: "#FFF4CC",
    text: {
      color: "black",
      size: 13
    },
    icon: {
      img: "chevron-down",
      size: { width: 16, height: 16 }
    }
  }
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `messagesPerPage` | `number` | `10` | Number of messages loaded per page when scrolling history |
| `showMore.value` | `string` | `"Show More"` | Button label text |
| `showMore.sticky` | `boolean` | `true` | Stick button to top of message list while scrolling |
| `showMore.background` | `string` | theme secondary | Button background color |
| `showMore.text` | `TextConfig` | `{ color: "black", size: 13 }` | Button text styling |
| `showMore.icon` | `IconConfig` | `{ img: "chevron-down", size: 16x16 }` | Button icon |

---

## Icon System

The SDK supports two types of icons throughout all configuration:

### Lucide Icons (Recommended)

Text-based icon names that render as inline SVG:

```javascript
icon: {
  img: "message-circle",
  size: { width: 24, height: 24 },
  color: "#ffffff"
}
```

**Popular Lucide Icons:**
- `"message-circle"` - Chat bubble
- `"send"` - Send arrow
- `"x"` - Close/X mark
- `"rotate-cw"` - Refresh/new chat
- `"chevron-down"` / `"chevron-up"` - Arrows
- `"user"` - User avatar
- `"bot"` - Bot avatar
- `"paperclip"` - File attachment

[Browse all 1000+ Lucide icons →](https://lucide.dev/icons/)

### Custom Images

URL-based custom images:

```javascript
icon: {
  img: "https://example.com/logo.png",
  size: { width: 32, height: 32 }
  // color is ignored for image URLs
}
```

### Hiding Icons

```javascript
icon: null  // Explicitly hide the icon
// or
// Omit the icon property entirely to use default
```

---

## Shared Type Definitions

### TextConfig

Used for styling text elements throughout the SDK:

```typescript
interface TextConfig {
  display?: boolean;  // Show/hide text (not used everywhere)
  value?: string;     // The text content
  color?: string;     // CSS color value
  font?: string;      // Font family override
  size?: number;      // Font size in pixels
}
```

### IconConfig

Used for all icons in the SDK:

```typescript
interface IconConfig {
  img?: string;    // Lucide icon name or image URL
  size?: {
    width: number;   // Width in pixels
    height: number;  // Height in pixels
  };
  color?: string;  // Icon color (CSS color). Only applies to Lucide icons, ignored for image URLs.
}
```

### ColorPair

Used for primary/secondary color combinations:

```typescript
interface ColorPair {
  primary?: string;    // Primary color (usually background)
  secondary?: string;  // Secondary color (usually foreground)
}
```

---

## Complete Configuration Example

```javascript
const chat = new NimbusChat({
  // Required
  agent_version_id: "550e8400-e29b-41d4-a716-446655440000",

  // Core Settings
  dns: "api.nimbus.ai/api/v1/chat/ws",
  debug: true,
  allowNewChat: true,

  // Theme
  theme: {
    primary: "#ffce1c",
    secondary: "#f3f1ef"
  },

  // Style
  style: {
    position: "bottom-right",
    width: "450px",
    height: "500px",
    font: "",
    background: "#f3f1ef",
    mobile: {
      position: "bottom-right",
      breakpoint: "480px"
    }
  },

  // Chat Bubble
  bubble: {
    position: "bottom-right",
    autoHide: false,
    icon: {
      img: "message-circle",
      size: { width: 35, height: 35 }
    }
  },

  // Header
  header: {
    icon: {
      img: "https://example.com/logo.svg",
      size: { width: 125, height: 19 }
    },
    text: {
      value: "",
      color: "#1e293b",
      font: "",
    },
    color: {
      primary: "#f3f1ef",
      secondary: "#ffce1c"
    }
  },

  // Welcome Message
  welcome: {
    display: true,
    preTitle: {
      value: "Welcome to :",
      text: { color: "#1e293b", font: "" }
    },
    title: {
      value: " Nimbus Chat!",
      text: { color: "#1e293b", font: "" }
    },
    subtitle: {
      value: "Send a message to start a conversation",
      text: { color: "gray", font: "" }
    }
  },

  // Messages
  userMessage: {
    background: "#DCF8C6",
    width: "80%",
    text: { color: "#111B21", font: "", size: 13 },
    icon: {
      img: "user",
      size: { width: 20, height: 20 }
    }
  },

  botMessage: {
    background: "#FFFFFF",
    width: "80%",
    text: { color: "#111B21", font: "", size: 13 },
    icon: {
      img: "bot",
      size: { width: 20, height: 20 }
    }
  },

  // Input Area
  input: {
    placeholder: "Ask Nimbus",
    expandable: true,
    text: {
      color: "#1e293b",
      font: "",
    },
    background: {
      primary: "white",
      secondary: "#f3f1ef"
    },
    maxCharacters: {
      limit: 10000,
      text: {
        color: "gray",
        font: "",
        size: 12
      }
    },
    upload: null
  },

  // Send Button
  sendButton: {
    align: false,
    icon: {
      img: "send",
      size: { width: 20, height: 20 }
    }
  },

  // Reconnection
  reconnect: {
    attempts: 5,
    timeout: 5000
  },

  // Wait for Reply
  waitForReply: {
    timeout: 30000,
    firstReply: false
  },

  // Typing Indicator (independent from waitForReply)
  isTypingIndicator: {
    position: "bottom",
    title: {
      value: "AI Assistant is typing...",
      text: { color: "#1e293b", font: "" }
    }
  },

  // Resume Conversation (omit or set null to disable)
  resumeConversation: {
    messagesPerPage: 10,
    showMore: {
      value: "Show More",
      sticky: true,
      background: "#FFF4CC",
      text: {
        color: "black",
        font: "",
        size: 13
      },
      icon: {
        img: "chevron-down",
        size: { width: 16, height: 16 }
      }
    }
  },
});
```

---

## Debug Mode

Enable comprehensive logging for development and troubleshooting:

```javascript
const chat = new NimbusChat({
  agent_version_id: "your-uuid-here",
  debug: true
});
```

**Debug mode provides:**
- WebSocket connection events
- Message send/receive logs
- Session state changes
- Configuration resolution details
- Error stack traces

---

## Development

### Prerequisites

- Node.js >= 18
- npm >= 9

### Setup

```bash
git clone https://github.com/nichemarket/chat-sdk.git
cd chat-sdk
npm install
```

### Scripts

```bash
npm run build          # Full build (CSS + TypeScript)
npm run build:css      # Compile Tailwind only
npm run build:ts       # Compile TypeScript only
npm run type-check     # Type-check without emitting
npm run dev            # Watch mode
npm test               # Run tests
npm run test:watch     # Watch mode tests
```

### Testing

The SDK uses [Vitest](https://vitest.dev/) with `jsdom` for unit testing. Tests cover configuration resolution, validation, core modules, UI utilities, and DOM components.

```bash
npm test               # Run all tests
npm run test:watch     # Run in watch mode
```

**Test suite structure:**

| File | Covers |
|---|---|
| `tests/types/resolveConfig.test.ts` | Config resolution, defaults, overrides, deep merging |
| `tests/utils/validateConfig.test.ts` | Required fields, type validation, nested config validation |
| `tests/core/EventBus.test.ts` | Pub/sub, `on`/`off`/`once`/`emit`, error isolation, typed events |
| `tests/core/ChatSession.test.ts` | Session ID, messages, history load/merge, dedup, clear |
| `tests/utils/FileUtils.test.ts` | File size formatting, Base64 conversion |
| `tests/utils/DOMBuilder.test.ts` | Element creation, props, chaining, append, conditionals, fragments |
| `tests/ui/ThemeManager.test.ts` | CSS variables, theme colors, backgrounds, fonts, input styles |
| `tests/utils/IconRenderer.test.ts` | Lucide SVG rendering, URL images, unknown icons, wrapper/simple modes |

### Common Development Commands

```bash
# Clean installation (recommended for troubleshooting)
rm -rf node_modules package-lock.json && npm install && npm run build

# Fast build and serve for testing
npm run build && npx http-server -p 8081 -c-1
```

### Code Standards

**Logging**: Always use the custom logger instead of `console.*` statements:

```typescript
import { createLogger } from './utils/logger';

// For utility files - create logger at top level
const logger = createLogger('ComponentName');

// For classes - create logger in constructor
class MyComponent {
  private logger: Logger;

  constructor(debug = false) {
    this.logger = createLogger('MyComponent', debug);
  }
}

// Usage
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');
```

### Output Files

| Format | File | Size |
|---|---|---|
| ESM | `dist/index.mjs` | ~48 KB |
| CJS | `dist/index.js` | ~49 KB |
| IIFE/UMD | `dist/nimbus-chat.umd.global.js` | ~49 KB |
| Types | `dist/index.d.ts` | ~8.5 KB |

### Project Structure

```
chat-sdk/
├── src/
│   ├── core/              # EventBus, ChatSession, WebSocketManager, ApiClient
│   ├── icons/             # Lucide SVG icon definitions
│   ├── styles/            # Tailwind globals + compiled CSS
│   ├── types/             # TypeScript interfaces (config, message)
│   ├── ui/                # UI components (Header, MessageList, InputArea, etc.)
│   ├── utils/             # Utilities (logger, validator, parser, etc.)
│   ├── NimbusChat.ts      # Main SDK orchestrator
│   └── index.ts           # Public exports
├── examples/
│   ├── cdn-usage.html         # CDN integration example (full config)
│   ├── cdn-usage-basic.html   # CDN integration example (minimal config)
│   └── cdn-usage-black.html   # CDN integration example (dark theme)
├── dist/                  # Build output
├── tsconfig.json
├── tsup.config.js
├── tailwind.config.js
├── postcss.config.js
└── package.json
```

---

## Architecture

The SDK uses **Shadow DOM** (open mode) to fully isolate styles from the host page. Tailwind CSS is pre-compiled at build time and injected into the shadow root at runtime alongside CSS custom properties for theming.

### Component Architecture

```
NimbusChat (orchestrator)
├── EventBus (pub/sub messaging)
├── ChatSession (message state + history)
├── WebSocketManager (connection lifecycle)
├── ApiClient (REST API)
└── UI Components
    ├── ShadowContainer (DOM isolation)
    ├── ThemeManager (CSS custom properties)
    ├── ChatWindow/SidepanelWindow (containers)
    ├── Header
    ├── MessageList
    ├── InputArea (with FileUpload)
    ├── MessageBubble
    └── ChatBubble (floating trigger)
```

### Connection Lifecycle

1. **Initial Connection**: Client opens WebSocket with `agent_version_id` (and `flow_id` if available from localStorage)
2. **Connected**: Server responds with `{ "type": "connected", "flow_id": "uuid" }`
3. **Persistence**: `flow_id` is always stored in localStorage and reused on reconnect
4. **History Loading**: If `resumeConversation` is configured (not null), fetches message history via REST API after connection
5. **Messaging**: Simple message exchange with `direction: "inbound"` (user) and `direction: "outbound"` (bot)

### WebSocket Events

**Incoming:**
- `connected` - Connection established, provides `flow_id`
- `message` - New message from server (`direction: "outbound"`)

**Outgoing:**
- `message` - User message (`direction: "inbound"`)

### Message Schema

```javascript
// User message (sent to server)
{ "type": "message", "direction": "inbound", "content": "Hello!" }

// Bot message (received from server)
{ "type": "message", "direction": "outbound", "content": "Hi there!" }

// Connected event (received from server)
{ "type": "connected", "flow_id": "550e8400-e29b-41d4-a716-446655440000" }
```

---

## License

[MIT](LICENSE)