Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 129 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,122 @@ Embeddable chat widget for [GoClaw](https://goclaw.sh) AI agent gateway. Drop a
The widget connects through a **proxy server** that keeps the gateway auth token server-side. The token is never exposed to the browser.

```
Browser Widget ←→ Proxy Server (:3100) ←→ GoClaw Gateway (:9090)
(no token) (holds token) (validates token)
Browser Widget ←→ Proxy Server (:3100) ←→ GoClaw Gateway
(no token) (injects token) (validates token)
```

## Features

- **Zero dependencies** — Vanilla TypeScript, ~8KB gzipped
- **Zero dependencies** — Vanilla TypeScript, ~18KB gzipped
- **Secure by default** — Auth token never leaves the server
- **Shadow DOM isolation** — Styles never leak into or from host page
- **Framework-agnostic** — Works with React, Vue, Angular, or plain HTML
- **Real-time streaming** — Token-by-token LLM responses via WebSocket
- **Theming** — Light, dark, auto, or fully custom via CSS variables
- **Configurable** — Position, size, colors, fonts, avatars, everything
- **Auto-reconnect** — Exponential backoff with configurable retries
- **Async loading** — Non-blocking snippet pattern (like Intercom)
- **Mobile responsive** — Full-screen on small viewports
- **Accessible** — Keyboard navigation, ARIA labels

## Proxy Server Setup
## Getting Started

### Step 1: Clone & install

```bash
git clone https://github.com/nextlevelbuilder/goclaw-plugin-webchat.git
cd goclaw-plugin-webchat
npm install
```

### Step 2: Configure the proxy server

The proxy keeps your GoClaw gateway token server-side. Create a `.env` file:

```bash
cd server/
cd server
cp .env.example .env
# Edit .env: set GOCLAW_URL, GOCLAW_TOKEN, and optionally PROXY_API_KEY
```

Edit `server/.env` with your GoClaw credentials:

```env
# Required: your GoClaw gateway WebSocket URL
GOCLAW_URL=wss://your-workspace.goclaw.sh/ws

# Required: gateway auth token (never exposed to browser)
GOCLAW_TOKEN=your-gateway-token-here

# Optional: default agent ID (UUID or slug)
DEFAULT_AGENT_ID=your-agent-id

# Optional: restrict which origins can connect
# ALLOWED_ORIGINS=https://example.com,https://app.example.com

# Optional: require API key for backend-to-proxy auth (server-to-server only, never in browser!)
# PROXY_API_KEY=your-secret-key
```

Install proxy dependencies:

```bash
npm install
```

### Step 3: Start the proxy server

```bash
npm run dev
```

## Quick Start
You should see:

```
[proxy] listening on :3100
[proxy] upstream: wss://your-workspace.goclaw.sh/ws
[proxy] auth token: configured
```

### Step 4: Build the widget

In a new terminal, from the project root:

```bash
cd ..
npm run build
```

This outputs `dist/goclaw-webchat.umd.js` and `dist/goclaw-webchat.es.js`.

### Step 5: Add to your website

```html
<script src="path/to/goclaw-webchat.umd.js"></script>
<script>
GoClaw.init({
url: 'ws://localhost:3100/ws', // proxy server URL
title: 'Chat with us',
theme: 'auto',
});
</script>
```

Open the page in a browser — the chat widget appears in the bottom-right corner. No token in client code.

### Step 6: Try the examples

Open any example file in your browser while the proxy is running:

```bash
# Serve the project directory
npx serve .

# Then visit:
# http://localhost:3000/examples/proxy-mode.html — basic proxy setup
# http://localhost:3000/examples/vanilla-basic.html — simplest integration
# http://localhost:3000/examples/vanilla-customized.html — custom theme + API
# http://localhost:3000/examples/async-snippet.html — non-blocking loader
# http://localhost:3000/examples/proxy-with-api-key.html — API key auth
```

## Integration Patterns

### Script Tag (simplest)

Expand Down Expand Up @@ -84,10 +171,8 @@ const widget = init({
theme: 'dark',
});

// Programmatic control
widget.open();
widget.send('Hello!');
widget.close();
widget.destroy();
```

Expand Down Expand Up @@ -118,12 +203,19 @@ app.use(GoClawPlugin, {
});
```

### Embed Proxy in Express

If you already run Express/Node.js, embed the proxy directly instead of running a separate process. See `examples/express-embedded-proxy.ts`.

### Docker Compose

For production deployment with nginx reverse proxy, see `examples/docker-compose.yml`.

## Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `url` | `string` | *required* | Proxy server WebSocket URL (`wss://...`) |
| `apiKey` | `string` | — | API key for proxy authentication |
| `userId` | `string` | auto-generated | User identifier |
| `agentId` | `string` | — | Specific agent to chat with |
| `sessionId` | `string` | — | Resume a previous session |
Expand All @@ -140,6 +232,20 @@ app.use(GoClawPlugin, {
| `maxReconnectAttempts` | `number` | `10` | Max reconnection attempts |
| `zIndex` | `number` | `999999` | CSS z-index |

## Proxy Server Configuration

| Env Variable | Required | Default | Description |
|-------------|----------|---------|-------------|
| `GOCLAW_URL` | Yes | — | GoClaw gateway WebSocket URL |
| `GOCLAW_TOKEN` | Yes | — | Gateway auth token (kept server-side) |
| `PORT` | No | `3100` | Proxy server port |
| `DEFAULT_AGENT_ID` | No | — | Default agent for `chat.send` |
| `ALLOWED_ORIGINS` | No | `*` | Comma-separated allowed origins |
| `MAX_CONNECTIONS_PER_IP` | No | `10` | Per-IP connection limit |
| `TRUST_PROXY` | No | `false` | Trust X-Forwarded-For headers |
| `PROXY_API_KEY` | No | — | Backend-to-proxy auth (server-to-server only) |
| `LOG_LEVEL` | No | `info` | `debug` / `info` / `warn` / `error` |

## Custom Themes

```js
Expand Down Expand Up @@ -205,10 +311,17 @@ GoClaw.init({
## Development

```bash
npm install # Widget deps
npm run dev # Widget dev server with HMR
npm run build # Production build
npm run lint # Type-check

# Proxy server
cd server
npm install
npm run dev # Dev server with HMR
npm run build # Production build
npm run lint # Type-check
npm run dev # Start proxy (tsx watch)
npm run build # Compile to dist/
npm start # Run compiled proxy
```

## License
Expand Down
25 changes: 20 additions & 5 deletions docs/code-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,28 @@
- CSS custom properties for theming
- Event-driven WebSocket communication
- Builder pattern for configuration (single config object)
- Proxy-only: widget never handles auth tokens, proxy injects them server-side

### GoClaw Protocol v3 Conventions
- Parameter names use **camelCase**: `agentId`, `sessionKey`, `runId`
- Chat method: `chat.send` with `{ message, agentId, stream }` (not `content`)
- Events wrapped: `{ event: "agent", payload: { type: "chunk", payload: { content } } }`
- Connect method: `connect` with `{ protocol: 3, user_id }` — proxy adds `token`

### Security
- HTML escaping for all user content
- No `eval()` or `innerHTML` with raw user input
- Markdown renderer escapes HTML before processing
- XSS-safe link rendering (only http/https)
- No client-side tokens or API keys — all auth is server-side via proxy

## Proxy Server (server/)

### Language & Tooling
- TypeScript strict mode
- Node.js ESM (type: module in package.json)
- Runtime dependency: `ws` (WebSocket library v8.18+)
- Dev dependencies: TypeScript, tsx (development server), @types/node, @types/ws
- Runtime dependencies: `ws` (WebSocket v8.18+), `dotenv` (env loading)
- Dev dependencies: TypeScript, tsx (dev server), @types/node, @types/ws

### File Naming
- kebab-case for all source files
Expand All @@ -49,15 +57,22 @@

### Architecture Patterns
- Config-driven initialization (environment variables via `proxy-config.ts`)
- Per-IP connection tracking with exponential backoff
- `dotenv/config` loaded at entry point for `.env` file support
- Per-IP connection tracking
- Per-session message rate limiting (60 msg/min)
- WebSocket frame interception: injects `GOCLAW_TOKEN` into `connect`, `DEFAULT_AGENT_ID` into `chat.send`
- WebSocket frame buffering until upstream connection ready
- Upstream response sanitization (strips token fields)
- Graceful shutdown with drain timeout
- Non-JSON frames dropped silently

### Security
- Auth token stored server-side only, never sent to client
- Auth token (`GOCLAW_TOKEN`) stored server-side only, never sent to client
- `PROXY_API_KEY` is backend-to-backend only (Express, nginx → proxy), never from browser
- Origin validation via `ALLOWED_ORIGINS` environment variable (empty = allow all)
- Per-IP connection limits via `MAX_CONNECTIONS_PER_IP` (default: 10)
- TRUST_PROXY flag for reverse proxy (nginx, Cloudflare) deployments
- Per-session rate limiting: 60 messages per minute
- `TRUST_PROXY` flag for reverse proxy (nginx, Cloudflare) deployments
- Max frame size: 512KB
- Upstream gateway URL not included in health endpoint responses
- Connect timeout: 10 seconds for upstream connection
29 changes: 20 additions & 9 deletions docs/project-overview-pdr.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Embeddable JavaScript chat widget allowing website owners to add AI chat powered
## Tech Stack
- **Language**: TypeScript (strict mode)
- **Build**: Vite 6 (library mode), ESBuild minification
- **Output**: UMD + ESM bundles (~8KB gzipped)
- **Output**: UMD + ESM bundles (~18KB gzipped)
- **UI**: Vanilla DOM + Shadow DOM for style isolation
- **Styling**: CSS custom properties for theming
- **Protocol**: GoClaw WebSocket Protocol v3 (req/res/event frames)
Expand All @@ -15,9 +15,9 @@ Embeddable JavaScript chat widget allowing website owners to add AI chat powered

### Widget (src/)
- `src/index.ts` — Entry point, `init()` function, window auto-attach
- `src/websocket-client.ts` — WebSocket connection, auth, RPC, event handling, reconnection (supports proxy mode)
- `src/websocket-client.ts` — WebSocket connection, auth, RPC, event handling, reconnection (proxy-only)
- `src/chat-widget.ts` — Shadow DOM UI, message rendering, input handling
- `src/types.ts` — All TypeScript interfaces (includes `proxyUrl` config option)
- `src/types.ts` — All TypeScript interfaces (proxy-only config, no client-side tokens)
- `src/markdown-renderer.ts` — Lightweight markdown-to-HTML
- `src/svg-icons.ts` — Inline SVG icons
- `src/styles/theme-variables.ts` — Theme system (light/dark/auto/custom)
Expand All @@ -26,12 +26,22 @@ Embeddable JavaScript chat widget allowing website owners to add AI chat powered
- `src/wrappers/vue-wrapper.ts` — Vue 3 plugin

### Proxy Server (server/)
- `server/src/index.ts` — Server entry point, HTTP + WebSocket listener
- `server/src/index.ts` — Server entry point, loads dotenv, starts HTTP + WebSocket
- `server/src/proxy-config.ts` — Configuration from environment variables
- `server/src/proxy-server.ts` — HTTP + WebSocket server with origin validation, per-IP limits, graceful shutdown
- `server/src/websocket-proxy-session.ts` — Single proxy session: intercepts WS `connect` frame to inject gateway token, buffers upstream messages
- `server/src/proxy-server.ts` — HTTP + WebSocket server with origin validation, per-IP limits, API key auth, graceful shutdown
- `server/src/websocket-proxy-session.ts` — Single proxy session: intercepts `connect` frame to inject gateway token, injects `DEFAULT_AGENT_ID` into `chat.send`, buffers upstream messages, rate limits per session
- `server/src/connection-tracker.ts` — Per-IP connection rate limiting

### Examples (examples/)
- `examples/proxy-mode.html` — Basic proxy setup with architecture walkthrough
- `examples/vanilla-basic.html` — Simplest integration
- `examples/vanilla-customized.html` — Custom theme, bottom-left, programmatic API
- `examples/async-snippet.html` — Non-blocking Intercom-style loader
- `examples/proxy-with-api-key.html` — Backend-to-proxy API key auth
- `examples/express-embedded-proxy.ts` — Embed proxy in Express app
- `examples/docker-compose.yml` — Production nginx + proxy deployment
- `examples/examples.css` — Shared CSS for example pages

## Distribution

### Widget
Expand All @@ -43,12 +53,13 @@ Embeddable JavaScript chat widget allowing website owners to add AI chat powered
### Proxy Server
- Docker-friendly Node.js server (TypeScript)
- Can be self-hosted or deployed to any Node.js-compatible platform
- Dependency: `ws` (WebSocket library, single production dependency)
- Dependencies: `ws` (WebSocket library), `dotenv` (env loading)

## Key Decisions
- Shadow DOM over iframe: Better performance, same-origin access, no CORS issues
- Vanilla TS over framework: Zero deps, universal compatibility
- CSS custom properties: Runtime theming without rebuild
- Exponential backoff reconnect: Resilient WS connections
- Backend proxy for production: Keeps auth tokens server-side, prevents token exposure in client code
- Dual-mode architecture: Direct mode for dev/testing, proxy mode for production security
- Proxy-only architecture: All auth tokens stay server-side, no client-side token exposure
- No client-side API key: `PROXY_API_KEY` is backend-to-backend only, never sent from browser
- GoClaw Protocol params: `agentId` (camelCase), `message` (not `content`), nested `event:agent` wrapper
Loading
Loading