WebSocket Examples
Complete code examples for integrating with the Nexgent WebSocket API.
Basic JavaScript Example
A minimal example showing connection, message handling, and ping/pong.
class NexgentWebSocket {
constructor(apiUrl, token, agentId) {
this.url = this.buildUrl(apiUrl, token, agentId);
this.ws = null;
this.positions = [];
this.isConnected = false;
}
buildUrl(apiUrl, token, agentId) {
const wsUrl = apiUrl.replace(/^http/, 'ws');
return `${wsUrl}/ws?token=${encodeURIComponent(token)}&agentId=${encodeURIComponent(agentId)}`;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket opened, waiting for authentication...');
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.isConnected = false;
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
handleMessage(message) {
switch (message.type) {
case 'connected':
console.log('Connected to agent:', message.data.agentId);
this.isConnected = true;
break;
case 'initial_data':
console.log('Received', message.data.positions.length, 'positions');
this.positions = message.data.positions;
this.onPositionsUpdate(this.positions);
break;
case 'position_update':
this.handlePositionUpdate(message.data);
break;
case 'price_update_batch':
this.handlePriceUpdates(message.data.updates);
break;
case 'ping':
// Respond to server ping
this.ws.send(JSON.stringify({
type: 'pong',
timestamp: new Date().toISOString()
}));
break;
case 'error':
console.error('Server error:', message.data.message);
break;
}
}
handlePositionUpdate(data) {
const { eventType, position, positionId } = data;
switch (eventType) {
case 'position_created':
this.positions.push(position);
break;
case 'position_updated':
const updateIndex = this.positions.findIndex(p => p.id === position.id);
if (updateIndex >= 0) {
this.positions[updateIndex] = { ...this.positions[updateIndex], ...position };
}
break;
case 'position_closed':
this.positions = this.positions.filter(p => p.id !== positionId);
break;
}
this.onPositionsUpdate(this.positions);
}
handlePriceUpdates(updates) {
for (const update of updates) {
const position = this.positions.find(
p => p.tokenAddress.toLowerCase() === update.tokenAddress.toLowerCase()
);
if (position) {
position.currentPrice = update.price;
position.currentPriceUsd = update.priceUsd;
this.recalculatePL(position);
}
}
this.onPositionsUpdate(this.positions);
}
recalculatePL(position) {
const currentValue = position.currentPrice * position.purchaseAmount;
const purchaseValue = position.purchasePrice * position.purchaseAmount;
position.positionValueSol = currentValue;
position.profitLossSol = currentValue - purchaseValue;
position.profitLossPercent = (position.profitLossSol / purchaseValue) * 100;
}
// Override this method to handle position updates
onPositionsUpdate(positions) {
console.log('Positions updated:', positions.length);
}
disconnect() {
if (this.ws) {
this.ws.close(1000, 'User disconnect');
}
}
}
// Usage
const ws = new NexgentWebSocket(
'https://your-instance.com',
'your-jwt-token',
'your-agent-id'
);
ws.onPositionsUpdate = (positions) => {
// Update your UI here
console.log('Positions:', positions);
};
ws.connect();React Hook Example
A React hook for managing WebSocket connections with automatic reconnection.
import { useState, useEffect, useRef, useCallback } from 'react';
interface Position {
id: string;
tokenAddress: string;
tokenSymbol: string;
purchasePrice: number;
purchaseAmount: number;
currentPrice?: number;
currentPriceUsd?: number;
profitLossPercent?: number;
}
interface UseWebSocketOptions {
autoConnect?: boolean;
reconnectInterval?: number;
maxReconnectAttempts?: number;
}
interface UseWebSocketReturn {
positions: Position[];
isConnected: boolean;
isConnecting: boolean;
error: string | null;
connect: () => void;
disconnect: () => void;
}
export function useNexgentWebSocket(
apiUrl: string,
token: string | null,
agentId: string | null,
options: UseWebSocketOptions = {}
): UseWebSocketReturn {
const {
autoConnect = true,
reconnectInterval = 5000,
maxReconnectAttempts = 5,
} = options;
const [positions, setPositions] = useState<Position[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
const [error, setError] = useState<string | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const reconnectAttemptsRef = useRef(0);
const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
const getWebSocketUrl = useCallback(() => {
if (!token || !agentId) return null;
const wsUrl = apiUrl.replace(/^http/, 'ws');
return `${wsUrl}/ws?token=${encodeURIComponent(token)}&agentId=${encodeURIComponent(agentId)}`;
}, [apiUrl, token, agentId]);
const connect = useCallback(() => {
const url = getWebSocketUrl();
if (!url) {
setError('Missing token or agentId');
return;
}
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
setIsConnecting(true);
setError(null);
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
console.log('[WS] Connection opened');
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'connected':
setIsConnected(true);
setIsConnecting(false);
reconnectAttemptsRef.current = 0;
break;
case 'initial_data':
setPositions(message.data.positions || []);
break;
case 'position_update':
handlePositionUpdate(message.data);
break;
case 'price_update_batch':
handlePriceUpdates(message.data.updates);
break;
case 'ping':
ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() }));
break;
case 'error':
setError(message.data.message);
break;
}
};
ws.onclose = (event) => {
setIsConnected(false);
setIsConnecting(false);
wsRef.current = null;
// Auto-reconnect with exponential backoff
if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) {
const delay = reconnectInterval * Math.pow(1.5, reconnectAttemptsRef.current);
reconnectTimeoutRef.current = setTimeout(() => {
reconnectAttemptsRef.current++;
connect();
}, delay);
}
};
ws.onerror = () => {
setError('WebSocket connection error');
setIsConnecting(false);
};
}, [getWebSocketUrl, reconnectInterval, maxReconnectAttempts]);
const handlePositionUpdate = useCallback((data: any) => {
setPositions(prev => {
switch (data.eventType) {
case 'position_created':
return [...prev, data.position];
case 'position_updated':
return prev.map(p => p.id === data.position.id ? { ...p, ...data.position } : p);
case 'position_closed':
return prev.filter(p => p.id !== data.positionId);
default:
return prev;
}
});
}, []);
const handlePriceUpdates = useCallback((updates: any[]) => {
setPositions(prev => prev.map(position => {
const update = updates.find(
u => u.tokenAddress.toLowerCase() === position.tokenAddress.toLowerCase()
);
if (!update) return position;
const currentValue = update.price * position.purchaseAmount;
const purchaseValue = position.purchasePrice * position.purchaseAmount;
return {
...position,
currentPrice: update.price,
currentPriceUsd: update.priceUsd,
profitLossPercent: ((currentValue - purchaseValue) / purchaseValue) * 100,
};
}));
}, []);
const disconnect = useCallback(() => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (wsRef.current) {
wsRef.current.close(1000, 'User disconnect');
wsRef.current = null;
}
setIsConnected(false);
setPositions([]);
}, []);
// Auto-connect
useEffect(() => {
if (autoConnect && token && agentId) {
connect();
}
return () => disconnect();
}, [autoConnect, token, agentId, connect, disconnect]);
return {
positions,
isConnected,
isConnecting,
error,
connect,
disconnect,
};
}Usage in Component
function PositionsTable({ agentId }: { agentId: string }) {
const { data: session } = useSession();
const {
positions,
isConnected,
isConnecting,
error,
} = useNexgentWebSocket(
process.env.NEXT_PUBLIC_API_URL!,
session?.accessToken ?? null,
agentId,
{ autoConnect: true }
);
if (error) {
return <div className="text-red-500">Error: {error}</div>;
}
if (isConnecting) {
return <div>Connecting...</div>;
}
return (
<div>
<div className="mb-4">
Status: {isConnected ? '🟢 Connected' : '🔴 Disconnected'}
</div>
<table>
<thead>
<tr>
<th>Token</th>
<th>Amount</th>
<th>Entry Price</th>
<th>Current Price</th>
<th>P/L %</th>
</tr>
</thead>
<tbody>
{positions.map(position => (
<tr key={position.id}>
<td>{position.tokenSymbol}</td>
<td>{position.purchaseAmount.toLocaleString()}</td>
<td>${position.purchasePrice.toFixed(8)}</td>
<td>${position.currentPrice?.toFixed(8) ?? '-'}</td>
<td className={position.profitLossPercent >= 0 ? 'text-green-500' : 'text-red-500'}>
{position.profitLossPercent?.toFixed(2) ?? '-'}%
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}Python Example
For backend integrations or scripts.
import asyncio
import json
import websockets
from datetime import datetime
class NexgentWebSocket:
def __init__(self, api_url: str, token: str, agent_id: str):
ws_url = api_url.replace('http', 'ws', 1)
self.url = f"{ws_url}/ws?token={token}&agentId={agent_id}"
self.positions = []
self.is_connected = False
async def connect(self):
async with websockets.connect(self.url) as ws:
self.ws = ws
# Handle messages
async for message in ws:
await self.handle_message(json.loads(message))
async def handle_message(self, message: dict):
msg_type = message.get('type')
data = message.get('data', {})
if msg_type == 'connected':
print(f"Connected to agent: {data.get('agentId')}")
self.is_connected = True
elif msg_type == 'initial_data':
self.positions = data.get('positions', [])
print(f"Received {len(self.positions)} positions")
await self.on_positions_update(self.positions)
elif msg_type == 'position_update':
await self.handle_position_update(data)
elif msg_type == 'price_update_batch':
await self.handle_price_updates(data.get('updates', []))
elif msg_type == 'ping':
await self.ws.send(json.dumps({
'type': 'pong',
'timestamp': datetime.utcnow().isoformat() + 'Z'
}))
elif msg_type == 'error':
print(f"Server error: {data.get('message')}")
async def handle_position_update(self, data: dict):
event_type = data.get('eventType')
position = data.get('position')
position_id = data.get('positionId')
if event_type == 'position_created':
self.positions.append(position)
elif event_type == 'position_updated':
for i, p in enumerate(self.positions):
if p['id'] == position['id']:
self.positions[i] = {**p, **position}
break
elif event_type == 'position_closed':
self.positions = [p for p in self.positions if p['id'] != position_id]
await self.on_positions_update(self.positions)
async def handle_price_updates(self, updates: list):
for update in updates:
token_address = update['tokenAddress'].lower()
for position in self.positions:
if position['tokenAddress'].lower() == token_address:
position['currentPrice'] = update['price']
position['currentPriceUsd'] = update['priceUsd']
await self.on_positions_update(self.positions)
async def on_positions_update(self, positions: list):
"""Override this method to handle position updates"""
for p in positions:
pnl = p.get('profitLossPercent', 0)
print(f" {p['tokenSymbol']}: {pnl:.2f}%")
# Usage
async def main():
ws = NexgentWebSocket(
api_url='https://your-instance.com',
token='your-jwt-token',
agent_id='your-agent-id'
)
await ws.connect()
if __name__ == '__main__':
asyncio.run(main())Best Practices
1. Batch Price Updates
Queue price updates and apply them in batches to avoid excessive re-renders:
// Instead of updating on every price message
let pendingUpdates = new Map();
let updateTimeout = null;
function queuePriceUpdate(tokenAddress, price, priceUsd) {
pendingUpdates.set(tokenAddress.toLowerCase(), { price, priceUsd });
if (!updateTimeout) {
updateTimeout = setTimeout(() => {
applyPendingUpdates();
updateTimeout = null;
}, 100); // Batch every 100ms
}
}
function applyPendingUpdates() {
// Apply all pending updates at once
setPositions(prev => prev.map(p => {
const update = pendingUpdates.get(p.tokenAddress.toLowerCase());
return update ? { ...p, ...update } : p;
}));
pendingUpdates.clear();
}2. Preserve Data During Reconnection
Don't clear positions when automatically reconnecting:
ws.onclose = (event) => {
if (event.code === 1000) {
// Clean close - clear positions
setPositions([]);
} else {
// Unexpected close - keep positions, reconnect
scheduleReconnect();
}
};3. Handle Token Expiration
Refresh your JWT before it expires and reconnect with the new token.