npm package: @saytv/chat-sdk
Description: Embeddable chat widget for SayTV — a fully featured, themeable web component with framework wrappers for React and Vue.
Latest version: 2.1.6

# @saytv/chat-sdk

Embeddable chat widget for SayTV — a fully featured, themeable web component with framework wrappers for React and Vue.

## Features

- **Web Component** — Drop a `<saytv-chat>` tag into any page, framework-agnostic
- **Shadow DOM** — Fully encapsulated styles, zero CSS conflicts
- **Real-time Chat** — Pusher-powered WebSocket messaging
- **Authentication** — Built-in login, registration, and token management
- **Theming** — Light/dark modes + 70+ CSS custom properties for full control
- **Framework Wrappers** — First-class React and Vue packages
- **Mobile Bridge** — Native integration for React Native, Flutter, iOS, and Android
- **Async Loader** — Tiny (< 1 kB) loader with command queue for deferred loading
- **IIFE Bundle** — Script-tag friendly build for legacy environments

## Quick Start

```bash
npm install @saytv/chat-sdk
```

```html
<saytv-chat
  app-id="your-app-id"
  theme="light"
></saytv-chat>

<script type="module">
  import '@saytv/chat-sdk/widget';
</script>
```

## Installation

### Package Manager

```bash
# npm
npm install @saytv/chat-sdk

# pnpm
pnpm add @saytv/chat-sdk

# yarn
yarn add @saytv/chat-sdk
```

### CDN (IIFE)

```html
<script src="https://sdk.saytv.net/@saytv/chat-sdk@latest/dist/sdk.iife.js"></script>
```

The IIFE bundle exposes the SDK on `window.SayTV`.

### Async Loader

For pages where you want to defer loading the full widget:

```html
<script type="module" src="https://sdk.saytv.net/@saytv/chat-sdk@latest/dist/loader.mjs"></script>
<script>
  SayTVChat('init', {
    appId: 'your-app-id',
    authToken: 'user-token-here',
    theme: 'dark',
    height: 700,
    width: '100%',
  });
</script>
```

The loader queues commands until the full widget is loaded, then flushes them automatically. It auto-creates a `<saytv-chat>` element if one is not already on the page.

## Usage

### Web Component

```html
<saytv-chat
  app-id="your-app-id"
  auth-token="eyJhbGci..."
  built-in-auth="true"
  height="850"
  width="450px"
  theme="light"
></saytv-chat>
```

All attributes are reactive — update them at any time via `setAttribute()`:

```js
const chat = document.querySelector('saytv-chat');
chat.setAttribute('theme', 'dark');
chat.setAttribute('auth-token', newToken);
```

### Programmatic API

The main entry point exports the API client and all TypeScript types:

```ts
import { apiRequest, ApiError } from '@saytv/chat-sdk';
import type { User, Comment, Episode, Room, PaginatedResponse } from '@saytv/chat-sdk';
```

### Package Exports

| Import Path | Description |
|---|---|
| `@saytv/chat-sdk` | API client, error class, and all types |
| `@saytv/chat-sdk/widget` | Registers the `<saytv-chat>` custom element |
| `@saytv/chat-sdk/loader` | Async loader with command queue |
| `@saytv/chat-sdk/mobile` | Mobile webview HTML shell |

## Framework Wrappers

### React

```bash
npm install @saytv/chat-react
```

```tsx
import { SayTVChat, useSayTVChat } from '@saytv/chat-react';

function App() {
  const { chatRef, setToken, setTheme } = useSayTVChat();

  return (
    <SayTVChat
      ref={chatRef}
      appId="your-app-id"
      theme="light"
      height={700}
      width="100%"
      onMessageSent={(e) => console.log('Sent:', e.detail)}
      onError={(e) => console.error('Error:', e.detail)}
    />
  );
}
```

**Props:**

| Prop | Type | Required | Description |
|---|---|---|---|
| `appId` | `string` | No | App identifier (resolves API URL) |
| `env` | `'production' \| 'staging'` | No | Environment (default: `'production'`) |
| `apiUrl` | `string` | No | API base URL (overrides appId/env) |
| `authToken` | `string` | No | User access token |
| `builtInAuth` | `boolean` | No | Enable built-in login UI (default: `true`) |
| `height` | `number` | No | Height in pixels (default: `850`) |
| `width` | `string` | No | CSS width (default: `'450px'`) |
| `theme` | `'light' \| 'dark'` | No | Color theme (default: `'light'`) |
| `className` | `string` | No | CSS class on wrapper div |
| `style` | `CSSProperties` | No | Inline styles on wrapper div |
| `onMessageSent` | `(e: CustomEvent) => void` | No | Message sent callback |
| `onUserLogin` | `(e: CustomEvent) => void` | No | Login callback |
| `onUserLogout` | `(e: CustomEvent) => void` | No | Logout callback |
| `onError` | `(e: CustomEvent) => void` | No | Error callback |

**Hook:**

```ts
const { chatRef, getElement, setToken, setTheme } = useSayTVChat();
```

- `chatRef` — Pass to `<SayTVChat ref={chatRef}>`
- `getElement()` — Returns the underlying `<saytv-chat>` HTMLElement
- `setToken(token)` — Update the access token
- `setTheme(theme)` — Switch between `'light'` and `'dark'`

### Vue

```bash
npm install @saytv/chat-vue
```

```vue
<script setup lang="ts">
import { SayTVChat, useSayTVChat } from '@saytv/chat-vue';

const { chatRef, setToken, setTheme } = useSayTVChat();
</script>

<template>
  <SayTVChat
    ref="chatRef"
    app-id="your-app-id"
    theme="light"
    :height="700"
    width="100%"
    @message-sent="(e) => console.log('Sent:', e.detail)"
    @error="(e) => console.error('Error:', e.detail)"
  />
</template>
```

**Props:** Same as React (except `className`/`style`). Uses kebab-case in templates.

**Composable:**

```ts
const { chatRef, getElement, setToken, setTheme } = useSayTVChat();
```

## Mobile Integration

The SDK includes a native bridge for embedding the chat widget inside mobile app webviews.

### Supported Platforms

| Platform | Detection |
|---|---|
| React Native | `window.ReactNativeWebView` |
| Flutter | `window.flutter_inappwebview` |
| iOS (WKWebView) | `window.webkit.messageHandlers.saytvBridge` |
| Android (WebView) | `window.SayTVAndroidBridge` |

### Mobile HTML Shell

Use the pre-built mobile HTML for webview integration:

```ts
import mobileHtml from '@saytv/chat-sdk/mobile';
```

The shell listens for an `INIT_CONFIG` postMessage to configure the widget with your API URL and token.

## Theming

The widget uses CSS custom properties scoped inside its Shadow DOM. Override them via inline styles on the `<saytv-chat>` element:

```html
<saytv-chat
  app-id="your-app-id"
  theme="light"
  style="--stv-color-primary: #4f46e5; --stv-color-accent: #10b981;"
></saytv-chat>
```

### Dark Mode

Set `theme="dark"` to activate the built-in dark theme. The dark theme overrides background, text, and border colors automatically.

### CSS Custom Properties Reference

#### Colors

| Property | Default (Light) | Description |
|---|---|---|
| `--stv-color-primary` | `#7e171c` | Primary brand color |
| `--stv-color-primary-dark` | `#5d1014` | Darker primary variant |
| `--stv-color-primary-light` | `#ad0000` | Lighter primary variant |
| `--stv-color-accent` | `#0143d9` | Accent / link color |
| `--stv-color-danger` | `#dc3545` | Error / danger color |
| `--stv-color-success` | `#28a745` | Success color |
| `--stv-color-warning` | `#ffc107` | Warning color |
| `--stv-color-info` | `#87ceeb` | Info color |

#### Backgrounds

| Property | Default | Description |
|---|---|---|
| `--stv-bg-primary` | `#ffffff` | Main background |
| `--stv-bg-secondary` | `#eae7e1` | Secondary background |
| `--stv-bg-dark` | `#212529` | Dark surfaces |
| `--stv-bg-light` | `#f5f5f5` | Light surfaces |
| `--stv-bg-hover` | `#f0f0f0` | Hover state |
| `--stv-bg-input` | `#ffffff` | Input fields |
| `--stv-bg-chat-bubble` | `#28292d` | Chat message bubbles |
| `--stv-bg-overlay` | `rgba(0,0,0,0.75)` | Modal overlays |

#### Text

| Property | Default | Description |
|---|---|---|
| `--stv-text-primary` | `#1a1a1a` | Primary text |
| `--stv-text-secondary` | `#4a4a4a` | Secondary text |
| `--stv-text-muted` | `#6f6f6f` | Muted / hint text |
| `--stv-text-light` | `#ffffff` | Text on dark backgrounds |
| `--stv-text-link` | `#0143d9` | Link text |
| `--stv-text-placeholder` | `#999999` | Input placeholders |

#### Typography

| Property | Default | Description |
|---|---|---|
| `--stv-font-primary` | `'Lato', system-ui, sans-serif` | Body font |
| `--stv-font-heading` | `'Exo', 'Lato', sans-serif` | Heading font |
| `--stv-font-size-xs` | `0.6875rem` | Extra small text |
| `--stv-font-size-sm` | `0.8125rem` | Small text |
| `--stv-font-size-base` | `0.875rem` | Base text size |
| `--stv-font-size-md` | `1rem` | Medium text |
| `--stv-font-size-lg` | `1.25rem` | Large text |

#### Spacing

| Property | Default |
|---|---|
| `--stv-spacing-xs` | `0.25rem` |
| `--stv-spacing-sm` | `0.5rem` |
| `--stv-spacing-md` | `1rem` |
| `--stv-spacing-lg` | `1.5rem` |
| `--stv-spacing-xl` | `2rem` |
| `--stv-spacing-2xl` | `3rem` |

#### Borders

| Property | Default | Description |
|---|---|---|
| `--stv-border-color` | `#dddddd` | Default border |
| `--stv-border-focus` | `#0143d9` | Focus ring color |
| `--stv-border-error` | `#dc3545` | Validation error border |
| `--stv-border-radius-sm` | `0.5rem` | Small radius |
| `--stv-border-radius-md` | `0.75rem` | Medium radius |
| `--stv-border-radius-lg` | `1rem` | Large radius |

#### Shadows

| Property | Default |
|---|---|
| `--stv-shadow-sm` | `0 1px 2px rgba(0,0,0,0.06)` |
| `--stv-shadow-md` | `0 2px 8px rgba(0,0,0,0.1)` |
| `--stv-shadow-lg` | `0 4px 16px rgba(0,0,0,0.12)` |
| `--stv-shadow-xl` | `0 8px 32px rgba(0,0,0,0.16)` |

#### Transitions

| Property | Default |
|---|---|
| `--stv-transition-fast` | `150ms ease` |
| `--stv-transition-normal` | `250ms ease` |
| `--stv-transition-slow` | `400ms ease` |

## Configuration Reference

| Attribute | Type | Default | Description |
|---|---|---|---|
| `app-id` | `string` | `''` | App identifier (resolves API URL) |
| `env` | `'production' \| 'staging'` | `'production'` | Environment |
| `apiurl` | `string` | — | API base URL (overrides app-id/env) |
| `auth-token` | `string` | — | User access token |
| `built-in-auth` | `'true' \| 'false'` | `'true'` | Show login/register UI |
| `height` | `number` | `850` | Widget height in pixels |
| `width` | `string` | `'450px'` | Widget width (any CSS unit) |
| `theme` | `'light' \| 'dark'` | `'light'` | Color theme |
| `fanzone` | `'true' \| 'false'` | `'false'` | Enable fanzone features |
| `mode` | `'page-chat'` | — | Widget mode (page-chat embeds chat on current page) |
| `page-id` | `string` | Current URL | Page identifier (used with `mode="page-chat"`) |
| `page-name` | `string` | Document title | Page display name (used with `mode="page-chat"`) |

## License

Proprietary. All rights reserved.