JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 150
  • Score
    100M100P100Q100413F
  • License MIT

WebSocket streaming SDK for real-time WioEX market data

Package Exports

  • @wioex/stream-sdk

Readme

@wioex/stream-sdk

npm version TypeScript License: MIT

WebSocket streaming SDK for real-time WioEX market data. Receive live stock ticker updates with a simple, event-driven API.

Features

  • 🚀 Real-time streaming - WebSocket-based live market data
  • High-performance - Optimized for high-frequency data with throttling & batching
  • 📊 Multiple stocks - Track up to 8 stocks simultaneously
  • 🔄 Auto-reconnection - Automatic reconnection with exponential backoff
  • 🎯 Type-safe - Written in TypeScript with full type definitions
  • 🌐 Universal - Works in Node.js and browsers
  • 📦 Lightweight - Minimal dependencies (52KB ESM, 20KB UMD minified)
  • 🔌 Event-driven - Simple EventEmitter-based API
  • 💪 Production-ready - Robust error handling and heartbeat mechanism
  • 🛡️ Error reporting - Automatic error tracking with batching & deduplication

✨ New in v1.6.0

  • 🎯 Zero-config browser usage - Automatic token fetching from your backend endpoint
  • 🚀 Lazy connect - No need to call connect() - just subscribe() and go!
  • 🐛 Debug mode - Detailed console logging for development
  • 🔄 Retry with backoff - Automatic retry on token fetch failures with exponential/linear backoff
  • 📍 Better error messages - Actionable error messages with platform-specific guidance

Installation

npm install @wioex/stream-sdk

Quick Start

Node.js / Backend (Super Simple! 🎉)

import { WioexStreamClient } from '@wioex/stream-sdk';

// Backend usage: Just provide your API key!
// SDK automatically fetches secure token and handles everything
const client = new WioexStreamClient({
  apiKey: process.env.WIOEX_API_KEY,  // That's it! SDK handles token fetch
  maxSymbols: 8
});

// Listen for ticker updates
client.on('ticker', (data) => {
  console.log(`${data.ticket}: $${data.last} (${data.changePercent}%)`);
});

// Connect and subscribe - SDK auto-fetches token before connecting!
await client.connect();
client.subscribe(['AAPL', 'TSLA', 'GOOGL']);

What happens behind the scenes:

  1. SDK detects Node.js environment
  2. Automatically fetches a secure token from WioEX API using your API key
  3. Connects to WebSocket stream with the token
  4. Your API key is NEVER sent via WebSocket (security first!)

💡 Pro Tip: Add autoRefreshToken: true to automatically refresh tokens before they expire!

Browser / Frontend (Zero-Config! 🎉)

<script src="https://unpkg.com/@wioex/stream-sdk/dist/wioex-stream.min.js"></script>
<script>
  // v1.6.0: Zero-config with automatic token fetching!
  const client = new WioexStream({
    tokenEndpoint: '/api/stream/token',  // Your backend endpoint
    debug: true,                          // Enable debug mode (optional)
    maxSymbols: 8
  });

  client.on('ticker', (data) => {
    console.log(`${data.ticket}: $${data.last}`);
  });

  // v1.6.0: Lazy connect - no need to call connect()!
  client.subscribe(['AAPL', 'TSLA']);  // Auto-connects and subscribes!
</script>

What's new:

  1. 🎯 Just specify tokenEndpoint - SDK fetches token automatically
  2. 🚀 No need to call connect() - lazy connect on subscribe
  3. 🐛 Debug mode for development
  4. 🔄 Automatic retry with exponential backoff on failures

🔒 Browser Security:

  • SDK automatically blocks API key usage in browsers
  • You MUST use tokens fetched from your backend
  • See SECURE_STREAMING.md for implementation guide

API Reference

Constructor

new WioexStreamClient(config: WioexStreamConfig)

Configuration Options

Option Type Default Description
apiKey string Backend only Backend (Node.js): Your WioEX API key - SDK auto-fetches token
token string Frontend only Frontend (Browser): Secure token from your backend
tokenExpiresAt number - Token expiration Unix timestamp (seconds)
tokenEndpoint string - v1.6.0: Backend endpoint URL to fetch token (browser only)
tokenFetchHeaders object - v1.6.0: Custom headers for token endpoint request
tokenFetchRetry object See below v1.6.0: Token fetch retry configuration
onTokenExpiring function - Async callback to fetch new token on expiry
autoRefreshToken boolean true* Auto-refresh token before expiry (*if onTokenExpiring provided)
refreshBeforeExpiry number 3600000 Refresh token N ms before expiry (default: 1 hour)
lazyConnect boolean true v1.6.0: Auto-connect on first subscribe (no manual connect() needed)
debug boolean false v1.6.0: Enable debug logging to console
maxSymbols number 8 Maximum symbols to track (1-8)
autoReconnect boolean true Enable auto-reconnection
reconnectDelay number 3000 Reconnection delay (ms)
maxReconnectAttempts number 0 Max reconnect attempts (0 = infinite)
streamUrl string Auto WebSocket URL (auto-generated)
errorReportingLevel string 'detailed' Error reporting level (none, minimal, standard, detailed)
performance object See below Performance optimization options

Authentication:

  • Backend (Node.js): Use apiKey - SDK automatically fetches and manages tokens
  • Frontend (Browser): Use token OR tokenEndpoint (v1.6.0) - Get from your backend (API key blocked for security)

Token Fetch Retry Options (tokenFetchRetry config) ✨ v1.6.0

Option Type Default Description
maxAttempts number 3 Maximum number of retry attempts
delay number 1000 Initial delay between retries in milliseconds
backoff 'linear' | 'exponential' 'exponential' Backoff strategy (exponential: 1s → 2s → 4s, linear: 1s → 2s → 3s)

Example:

const client = new WioexStreamClient({
  tokenEndpoint: '/api/stream/token',
  tokenFetchRetry: {
    maxAttempts: 5,
    delay: 2000,
    backoff: 'exponential',  // 2s → 4s → 8s → 16s → 32s
  },
});

Performance Options (performance config)

Option Type Default Description
tickerThrottle number 16 Throttle ticker events (ms). 0 = disabled, 16 = 60 FPS
batchTickers boolean false Enable ticker batching mode
tickerBatchSize number 10 Maximum ticker batch size
errorBatchSize number 10 Error reporting batch size
errorBatchInterval number 5000 Error flush interval (ms)
errorDeduplication boolean true Enable error deduplication

Performance Presets:

// Default (balanced - 60 FPS throttling)
const client = new WioexStreamClient({ apiKey: 'xxx' });

// High-performance (no throttling, immediate events)
const client = new WioexStreamClient({
  apiKey: 'xxx',
  performance: {
    tickerThrottle: 0,           // No throttling
    errorBatchSize: 1,           // Immediate error reporting
    errorDeduplication: false,   // No deduplication
  }
});

// High-volume (optimized for charts - 20 FPS batching)
const client = new WioexStreamClient({
  apiKey: 'xxx',
  performance: {
    tickerThrottle: 50,         // 20 FPS
    batchTickers: true,         // Enable batching
    tickerBatchSize: 20,        // Larger batches
  }
});

// Memory-optimized (aggressive batching)
const client = new WioexStreamClient({
  apiKey: 'xxx',
  performance: {
    errorBatchSize: 50,         // Large error batches
    errorBatchInterval: 10000,  // 10s flush interval
  }
});

Methods

connect(): Promise<void>

Connect to WioEX WebSocket stream. In Node.js with apiKey, automatically fetches token before connecting.

// Backend (with apiKey) - await to ensure token fetch completes
await client.connect();

// Frontend (with token) - can be called without await
client.connect();

disconnect(): void

Disconnect from WebSocket stream.

client.disconnect();

subscribe(stocks: string | string[]): void

Subscribe to stock ticker updates.

client.subscribe('AAPL');
client.subscribe(['AAPL', 'TSLA', 'GOOGL']);

Throws: Error if attempting to subscribe to more than maxSymbols.

unsubscribe(stocks: string | string[]): void

Unsubscribe from stock ticker updates.

client.unsubscribe('AAPL');
client.unsubscribe(['AAPL', 'TSLA']);

getState(): ConnectionState

Get current connection state.

const state = client.getState();
// Returns: 'disconnected' | 'connecting' | 'connected' | 'registered' | 'reconnecting' | 'failed'

getSubscribedStocks(): string[]

Get list of currently subscribed stocks.

const stocks = client.getSubscribedStocks();
console.log(stocks); // ['AAPL', 'TSLA', 'GOOGL']

getStats(): ClientStats

Get client statistics.

const stats = client.getStats();
console.log(stats);
// {
//   connectedAt: 1704067200000,
//   reconnectAttempts: 0,
//   messagesReceived: 152,
//   messagesSent: 3,
//   tickersReceived: 148,
//   subscribedStocks: ['AAPL', 'TSLA'],
//   state: 'registered'
// }

isConnected(): boolean

Check if client is connected.

if (client.isConnected()) {
  console.log('Connected!');
}

updateToken(token: string, expiresAt: number): void

Manually update authentication token (useful for token refresh implementations).

// Update token and schedule auto-refresh
client.updateToken(newToken, newExpiresAt);

Events

The client extends EventEmitter and emits the following events:

connected

Emitted when WebSocket connection is established.

client.on('connected', () => {
  console.log('Connected to WioEX stream');
});

registered

Emitted when client is registered with API key.

client.on('registered', (data) => {
  console.log('Registered:', data.message);
});

subscribed

Emitted when subscribed to stocks.

client.on('subscribed', (stocks: string[]) => {
  console.log('Subscribed to:', stocks);
});

unsubscribed

Emitted when unsubscribed from stocks.

client.on('unsubscribed', (stocks: string[]) => {
  console.log('Unsubscribed from:', stocks);
});

ticker

Emitted when ticker data is received.

client.on('ticker', (data: TickerData) => {
  console.log(`${data.ticket}: $${data.last}`);
});

TickerData Structure:

interface TickerData {
  ticket: string;        // Stock symbol
  last: string;          // Last price
  open: string;          // Opening price
  high: string;          // High price
  low: string;           // Low price
  volume: string;        // Trading volume
  bid: string;           // Bid price
  ask: string;           // Ask price
  change: string;        // Price change
  changePercent: string; // Change percentage
  timestamp: number;     // Unix timestamp
}

error

Emitted when an error occurs.

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

disconnected

Emitted when connection is closed.

client.on('disconnected', (code: number, reason: string) => {
  console.log(`Disconnected (${code}): ${reason}`);
});

reconnecting

Emitted when attempting to reconnect.

client.on('reconnecting', (attempt: number) => {
  console.log(`Reconnecting... (attempt ${attempt})`);
});

stateChange

Emitted when connection state changes.

client.on('stateChange', (state: ConnectionState) => {
  console.log(`State: ${state}`);
});

tokenExpiring

Emitted when token is about to expire (before auto-refresh).

client.on('tokenExpiring', ({ currentToken, expiresAt }) => {
  console.log(`Token expiring at ${new Date(expiresAt * 1000)}`);
});

tokenRefreshed

Emitted when token has been successfully refreshed.

client.on('tokenRefreshed', ({ token, expiresAt }) => {
  console.log(`Token refreshed! New expiry: ${new Date(expiresAt * 1000)}`);
});

tokenRefreshFailed

Emitted when token refresh fails.

client.on('tokenRefreshFailed', (error: Error) => {
  console.error('Token refresh failed:', error.message);
});

tokenFetchStarted ✨ v1.6.0

Emitted when token fetch starts (from endpoint or API).

client.on('tokenFetchStarted', (source: 'endpoint' | 'api') => {
  console.log(`Fetching token from ${source}...`);
});

tokenFetchSucceeded ✨ v1.6.0

Emitted when token fetch succeeds.

client.on('tokenFetchSucceeded', ({ token, expiresAt, source }) => {
  console.log(`Token fetched from ${source}`);
  console.log(`Expires at: ${new Date(expiresAt * 1000)}`);
});

tokenFetchFailed ✨ v1.6.0

Emitted when token fetch fails (will retry if configured).

client.on('tokenFetchFailed', (error: Error, attempt: number, willRetry: boolean) => {
  console.error(`Token fetch failed (attempt ${attempt}):`, error.message);
  if (willRetry) {
    console.log('Will retry...');
  }
});

Examples

Basic Usage (Backend)

import { WioexStreamClient } from '@wioex/stream-sdk';

// Super simple! Just provide API key, SDK handles token automatically
const client = new WioexStreamClient({
  apiKey: process.env.WIOEX_API_KEY,
  maxSymbols: 5
});

client.on('ticker', (data) => {
  const change = parseFloat(data.changePercent);
  const arrow = change >= 0 ? '↑' : '↓';
  console.log(`${arrow} ${data.ticket}: $${data.last} (${change.toFixed(2)}%)`);
});

await client.connect();  // SDK auto-fetches token here
client.subscribe(['AAPL', 'TSLA', 'GOOGL', 'MSFT', 'AMZN']);

Dynamic Subscription Management

// Get token first
const { token } = await fetch('/api/stream/token', { method: 'POST' })
  .then(r => r.json());

const client = new WioexStreamClient({
  token,
  maxSymbols: 3
});

client.on('registered', () => {
  // Subscribe to initial stocks
  client.subscribe(['AAPL', 'TSLA', 'GOOGL']);
});

// After 30 seconds, switch to different stocks
setTimeout(() => {
  client.unsubscribe(['GOOGL']);
  client.subscribe(['MSFT']);
}, 30000);

client.connect();

Error Handling

const { token } = await fetch('/api/stream/token', { method: 'POST' })
  .then(r => r.json());

const client = new WioexStreamClient({
  token,
  autoReconnect: true,
  maxReconnectAttempts: 5
});

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

client.on('disconnected', (code, reason) => {
  console.log(`Disconnected: ${reason}`);
  if (code !== 1000) {
    console.log('Unexpected disconnection');
  }
});

client.on('reconnecting', (attempt) => {
  console.log(`Reconnecting... attempt ${attempt}`);
});

client.connect();

Statistics Monitoring

const { token } = await fetch('/api/stream/token', { method: 'POST' })
  .then(r => r.json());

const client = new WioexStreamClient({ token });

client.connect();
client.subscribe(['AAPL', 'TSLA']);

// Log statistics every minute
setInterval(() => {
  const stats = client.getStats();
  console.log('Statistics:', {
    uptime: Date.now() - stats.connectedAt!,
    messagesReceived: stats.messagesReceived,
    tickersReceived: stats.tickersReceived,
    subscribedStocks: stats.subscribedStocks
  });
}, 60000);

Token Auto-Refresh (Backend)

// Backend: Automatic token refresh with API key
const client = new WioexStreamClient({
  apiKey: process.env.WIOEX_API_KEY,

  // Enable auto-refresh (default: true)
  autoRefreshToken: true,

  // Refresh 1 hour before expiry (default: 3600000ms)
  refreshBeforeExpiry: 3600000,

  // Callback for custom token fetching
  onTokenExpiring: async () => {
    console.log('Fetching new token...');

    const response = await fetch(
      `https://api.wioex.com/v1/stream/token?api_key=${process.env.WIOEX_API_KEY}`,
      { method: 'POST' }
    );
    const data = await response.json();

    return {
      token: data.token,
      expiresAt: data.expires_at
    };
  }
});

// Monitor token refresh events
client.on('tokenExpiring', ({ expiresAt }) => {
  console.log(`Token expiring at ${new Date(expiresAt * 1000)}`);
});

client.on('tokenRefreshed', ({ expiresAt }) => {
  console.log(`Token refreshed! Valid until ${new Date(expiresAt * 1000)}`);
});

client.on('tokenRefreshFailed', (error) => {
  console.error('Token refresh failed:', error);
});

await client.connect();
client.subscribe(['AAPL', 'TSLA']);

WebSocket Protocol

The SDK communicates with WioEX WebSocket API using JSON messages:

Connection

wss://stream.wioex.com/rs/i/{max_symbols}/websocket

Registration

{
  "action": "register",
  "token": "your-temporary-token"
}

Response:

{
  "status": "success",
  "type": "registered",
  "message": "Client registered successfully"
}

Subscribe

{
  "action": "subscribe",
  "stocks": ["AAPL", "TSLA", "GOOGL"]
}

Response:

{
  "status": "success",
  "type": "subscribed",
  "message": "Subscribed to stocks",
  "stocks": ["AAPL", "TSLA", "GOOGL"]
}

Ticker Update

{
  "sarex": {
    "_p": { "e": "d" },
    "ticker": {
      "ticket": "AAPL",
      "last": "185.25",
      "open": "183.50",
      "high": "186.00",
      "low": "183.00",
      "volume": "52478963",
      "bid": "185.20",
      "ask": "185.30",
      "change": "1.75",
      "changePercent": "0.95",
      "timestamp": 1704067200000
    }
  }
}

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import {
  WioexStreamClient,
  WioexStreamConfig,
  TickerData,
  ConnectionState,
  ClientStats
} from '@wioex/stream-sdk';

// Get token (from backend)
const { token } = await fetch('/api/stream/token', { method: 'POST' })
  .then(r => r.json());

const config: WioexStreamConfig = {
  token,
  maxSymbols: 8
};

const client = new WioexStreamClient(config);

client.on('ticker', (data: TickerData) => {
  // TypeScript knows the exact structure of data
  console.log(data.ticket, data.last, data.changePercent);
});

Browser Support

The SDK works in all modern browsers that support WebSocket:

  • Chrome 16+
  • Firefox 11+
  • Safari 7+
  • Edge 12+
  • Opera 12.1+

Node.js Support

Requires Node.js 16 or higher.

Development

# Install dependencies
npm install

# Build the package
npm run build

# Run linter
npm run lint

# Run tests
npm test

# Development mode (watch)
npm run dev

Examples

See the examples/ directory for complete examples:

  • Node.js Example: examples/node-example.js
  • Browser Example: examples/browser-example.html
  • Next.js App Router Example ✨ v1.6.0: examples/nextjs-app-router/ - Full Next.js 15 example with token endpoint, lazy connect, and debug mode

To run the Node.js example:

cd examples
WIOEX_API_KEY=your-api-key node node-example.js

To run the browser example:

  1. Build the package: npm run build
  2. Open examples/browser-example.html in a browser
  3. Enter your API key and click Connect

To run the Next.js example:

cd examples/nextjs-app-router
cp .env.example .env.local
# Edit .env.local and add your WIOEX_API_KEY
npm install
npm run dev
# Open http://localhost:3000

See examples/nextjs-app-router/README.md for detailed documentation.

License

MIT License - see LICENSE file for details.

Support

Publishing to NPM

To publish this package to NPM:

# Login to NPM
npm login

# Build the package
npm run build

# Publish to NPM
npm publish --access public

Made with ❤️ by WioEX