npm package: @hypersignals/dex-ws
Description: A robust WebSocket client for DEX trading applications
Latest version: 1.0.2

# @hypersignals/dex-ws

A robust WebSocket client for DEX trading applications.

## Installation

```bash
npm install @hypersignals/dex-ws
```

## Quick Start

```typescript
import DexWS from '@hypersignals/dex-ws';

const ws = new DexWS('wss://api.hyperliquid.xyz/ws');

ws.on('open', ({ isReconnect }) => {
  console.log('Connected!', isReconnect ? '(reconnected)' : '');
  ws.send({ subscribe: 'trades' });
});

ws.on('message', (data) => {
  console.log('Received:', data);
});

ws.on('close', ({ code, reason }) => {
  console.log('Disconnected:', code, reason);
});

ws.on('error', (error) => {
  console.error('Error:', error);
});

// Clean up when done
ws.destroy();
```

## Features

- **Automatic Reconnection**: Exponential backoff with configurable max retries and jitter
- **Heartbeat**: Keep connections alive with customizable ping messages
- **Visibility Handling**: Disconnect when page is hidden, reconnect when visible
- **Network Awareness**: Automatically reconnect when coming back online
- **Message Buffering**: Queue messages while disconnected with overflow strategies
- **Metrics**: Track connection attempts, messages sent/received, and uptime
- **TypeScript**: Full type definitions included

## API Reference

### Constructor

```typescript
new DexWS(url: string | (() => string), options?: DexWSOptions)
```

#### Parameters

- `url` - WebSocket URL (must start with `ws://` or `wss://`) or a function returning the URL
- `options` - Configuration options (see below)

### Options

```typescript
interface DexWSOptions {
  autoConnect?: boolean;        // Auto-connect on instantiation (default: true)
  timeout?: number;             // Connection timeout in ms (default: 30000)
  protocols?: string | string[]; // WebSocket subprotocols
  logger?: LogLevel | Logger;   // Logging configuration
  binaryType?: BinaryType;      // 'blob' or 'arraybuffer' (default: 'blob')
  reconnect?: ReconnectOptions | boolean;
  heartbeat?: HeartbeatOptions | boolean;
  visibility?: VisibilityOptions | boolean;
  network?: NetworkOptions | boolean;
  buffer?: BufferOptions | boolean;
}
```

### Feature Options

Each feature can be:
- `false` - Disabled
- `true` or `undefined` - Enabled with defaults
- `object` - Enabled with custom options merged with defaults

#### Reconnect Options

```typescript
interface ReconnectOptions {
  enabled?: boolean;            // Enable reconnection (default: true)
  maxRetries?: number;          // Max reconnection attempts (default: Infinity)
  delay?: number | ((attempt: number) => number); // Delay in ms or function
  jitter?: boolean;             // Add random jitter to delay (default: true)
}
```

**Default delay function**: `Math.min(1000 * 2^n, 30000)` (exponential backoff, max 30s)

**Jitter formula**: `baseDelay + (baseDelay * 0.2 * Math.random())`

#### Heartbeat Options

```typescript
interface HeartbeatOptions {
  enabled?: boolean;            // Enable heartbeat (default: true)
  interval?: number;            // Heartbeat interval in ms (default: 30000)
  message?: string | (() => string); // Ping message (default: '{"method":"ping"}')
  pongTimeout?: number;         // Pong timeout in ms (default: 10000)
}
```

#### Visibility Options

```typescript
interface VisibilityOptions {
  enabled?: boolean;            // Enable visibility handling (default: true)
  timeout?: number;             // Time before disconnect when hidden (default: 120000)
  verifyOnVisible?: boolean;    // Reconnect when visible (default: true)
}
```

#### Network Options

```typescript
interface NetworkOptions {
  enabled?: boolean;            // Enable network handling (default: true)
  autoReconnect?: boolean;      // Reconnect when online (default: true)
}
```

#### Buffer Options

```typescript
interface BufferOptions {
  enabled?: boolean;            // Enable message buffering (default: true)
  maxSize?: number;             // Max buffer size (default: 100)
  overflow?: OverflowStrategy;  // 'drop-oldest' | 'drop-newest' | 'error'
}
```

### Methods

#### `connect(): this`
Initiate connection. Called automatically if `autoConnect` is true.

#### `disconnect(): this`
Gracefully close the connection.

#### `destroy(): void`
Permanently destroy the instance. Cleans up all listeners and timers. Idempotent.

#### `send(data: unknown): boolean`
Send data through the WebSocket. Returns `true` if sent successfully, `false` otherwise.
- Objects are JSON-stringified automatically
- Messages are buffered if not connected (when buffering is enabled)

#### `on(event, listener): this`
Subscribe to events. Returns `this` for chaining.

#### `off(event, listener): this`
Unsubscribe from events. Returns `this` for chaining.

#### `once(event, listener): this`
Subscribe to a single event occurrence. Returns `this` for chaining.

#### `removeAllListeners(event?): this`
Remove all listeners for an event, or all listeners if no event specified.

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `status` | `ConnectionState` | Current connection state |
| `connected` | `boolean` | True if currently connected |
| `endpoint` | `string` | The WebSocket URL |
| `config` | `DexWSOptions` | Current configuration |
| `bufferSize` | `number` | Number of buffered messages |
| `metrics` | `Metrics` | Connection metrics |
| `destroyed` | `boolean` | True if instance is destroyed |

### Events

| Event | Payload | Description |
|-------|---------|-------------|
| `open` | `{ isReconnect: boolean }` | Connection opened |
| `close` | `{ code: number, reason: string }` | Connection closed |
| `message` | `unknown` | Message received (auto-parsed if JSON) |
| `error` | `Error` | Error occurred |
| `status` | `{ status: ConnectionState, previousStatus: ConnectionState }` | Status changed |

**Note**: `on('status', fn)` immediately emits the current status.

### Connection States

```typescript
type ConnectionState =
  | 'disconnected'   // Not connected
  | 'connecting'     // Initial connection in progress
  | 'connected'      // Connected
  | 'reconnecting'   // Reconnection in progress
  | 'terminated';    // Instance destroyed
```

### Metrics

```typescript
interface Metrics {
  connectionAttempts: number;   // Total connection attempts
  messagesSent: number;         // Total messages sent
  messagesReceived: number;     // Total messages received
  lastConnectedAt: number | null;     // Timestamp of last connection
  lastDisconnectedAt: number | null;  // Timestamp of last disconnection
  uptime: number;               // Total connected time in ms
}
```

## Defaults Summary

| Feature | Default |
|---------|---------|
| `autoConnect` | `true` |
| `timeout` | `30000` (30s) |
| `binaryType` | `'blob'` |
| `reconnect.maxRetries` | `Infinity` |
| `reconnect.delay` | Exponential backoff, max 30s |
| `reconnect.jitter` | `true` (0-20% random) |
| `heartbeat.interval` | `30000` (30s) |
| `heartbeat.message` | `'{"method":"ping"}'` |
| `heartbeat.pongTimeout` | `10000` (10s) |
| `visibility.timeout` | `120000` (2 min) |
| `visibility.verifyOnVisible` | `true` |
| `network.autoReconnect` | `true` |
| `buffer.maxSize` | `100` |
| `buffer.overflow` | `'drop-oldest'` |

## Close Codes

The following close codes will **NOT** trigger automatic reconnection:
- `1000` - Normal closure
- `1008` - Policy violation
- `1009` - Message too big
- `1010` - Missing extension
- `1011` - Internal error
- `1015` - TLS handshake failure

All other close codes will trigger reconnection (if enabled).

## Factory Function

```typescript
import { connect } from '@dex-ws/core';

const ws = connect('wss://api.example.com/ws', { /* options */ });
```

## Examples

### Custom Reconnection Strategy

```typescript
const ws = new DexWS('wss://api.example.com/ws', {
  reconnect: {
    maxRetries: 10,
    delay: (attempt) => Math.min(500 * Math.pow(1.5, attempt), 10000),
    jitter: true,
  },
});
```

### Custom Heartbeat

```typescript
let seq = 0;
const ws = new DexWS('wss://api.example.com/ws', {
  heartbeat: {
    interval: 15000,
    message: () => JSON.stringify({ type: 'ping', seq: ++seq }),
    pongTimeout: 5000,
  },
});
```

### Disable Features

```typescript
const ws = new DexWS('wss://api.example.com/ws', {
  reconnect: false,      // No auto-reconnect
  heartbeat: false,      // No heartbeat
  visibility: false,     // Ignore page visibility
  network: false,        // Ignore network changes
  buffer: false,         // No message buffering
});
```

### Custom Logger

```typescript
const ws = new DexWS('wss://api.example.com/ws', {
  logger: {
    debug: (...args) => console.debug('[WS]', ...args),
    info: (...args) => console.info('[WS]', ...args),
    warn: (...args) => console.warn('[WS]', ...args),
    error: (...args) => console.error('[WS]', ...args),
  },
});

// Or use built-in levels: 'debug' | 'info' | 'warn' | 'error' | 'none'
const ws2 = new DexWS('wss://api.example.com/ws', { logger: 'debug' });
```

### Dynamic URL

```typescript
let serverIndex = 0;
const servers = ['wss://server1.com/ws', 'wss://server2.com/ws'];

const ws = new DexWS(() => servers[serverIndex++ % servers.length]);
```

## License

MIT