Overview
Exchange Web uses WebSocket connections for real-time trading data. The WsManager class implements a singleton pattern to manage a single persistent WebSocket connection across the entire application.
WsManager Singleton
The WsManager provides centralized WebSocket management with callback-based message routing.
Class Structure
From apps/web/src/utils/ws_manager.ts:4-24:
export class WsManager {
private ws : WebSocket ;
private static instance : WsManager ;
private bufferedMessages : any [] = [];
private callbacks : any = {};
private id : number ;
private initialized : boolean = false ;
private constructor () {
this . ws = new WebSocket ( BASE_URL );
this . bufferedMessages = [];
this . id = 1 ;
this . init ();
}
public static getInstance () {
if ( ! this . instance ) {
this . instance = new WsManager ();
}
return this . instance ;
}
The singleton pattern ensures only one WebSocket connection exists, preventing multiple connections and reducing server load.
Connection Configuration
From apps/web/src/utils/ws_manager.ts:1-2:
export const BASE_URL = "wss://exchange.jogeshwar.xyz/ws" ;
Initialization
The WsManager initializes the WebSocket connection and sets up message handling.
Init Method
From apps/web/src/utils/ws_manager.ts:26-53:
init () {
this . ws . onopen = () => {
this . initialized = true ;
this . bufferedMessages . forEach (( message ) => {
this . ws . send ( JSON . stringify ( message ));
});
this . bufferedMessages = [];
};
this . ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
const type = message . data . e ;
if ( this . callbacks [ type ]) {
this . callbacks [ type ]. forEach (({ callback } : { callback : any }) => {
if ( type === "depth" ) {
const updatedBids = message . data . b ;
const updatedAsks = message . data . a ;
callback ({ bids: updatedBids , asks: updatedAsks });
}
if ( type === "trade" ) {
const trades = message . data ;
callback ( trades );
}
});
}
};
}
Connection Lifecycle
WebSocket Creation
Constructor creates WebSocket connection to exchange server
Open Handler
Sets initialized flag and sends buffered messages
Message Handler
Routes incoming messages to registered callbacks
Message Buffering
Messages sent before connection is established are buffered and sent once connected.
Send Message Method
From apps/web/src/utils/ws_manager.ts:55-65:
sendMessage ( message : any ) {
const messageToSend = {
... message ,
id: this . id ++ ,
};
if ( ! this . initialized ) {
this . bufferedMessages . push ( messageToSend );
return ;
}
this . ws . send ( JSON . stringify ( messageToSend ));
}
Message buffering ensures no subscription requests are lost during the connection establishment phase.
Buffering Flow
Callback System
The callback system allows multiple components to subscribe to WebSocket events.
Callback Structure
callbacks = {
"depth" : [
{ callback: function1 , id: "DEPTH-SOL_USDC" },
{ callback: function2 , id: "DEPTH-SOL_USDCC" }
],
"trade" : [
{ callback: function3 , id: "TRADE-SOL_USDC" }
]
}
Register Callback
From apps/web/src/utils/ws_manager.ts:67-71:
async registerCallback ( type : string , callback : any , id : string ) {
this . callbacks [ type ] = this . callbacks [ type ] || [];
this . callbacks [ type ]. push ({ callback , id });
// "ticker" => callback
}
Deregister Callback
From apps/web/src/utils/ws_manager.ts:73-82:
async deRegisterCallback ( type : string , id : string ) {
if ( this . callbacks [ type ]) {
const index = this . callbacks [ type ]. findIndex (
( callback : any ) => callback . id === id
);
if ( index !== - 1 ) {
this . callbacks [ type ]. splice ( index , 1 );
}
}
}
Always deregister callbacks in component cleanup to prevent memory leaks and duplicate event handling.
Event Types
The WebSocket server sends two main event types:
depth Order book updates with bids and asks
trade New trade executions with price and quantity
Depth Event
Message structure:
{
"data" : {
"e" : "depth" ,
"b" : [[ "100.5" , "10" ], [ "100.4" , "5" ]],
"a" : [[ "100.6" , "8" ], [ "100.7" , "12" ]]
}
}
Processing from apps/web/src/utils/ws_manager.ts:40-44:
if ( type === "depth" ) {
const updatedBids = message . data . b ;
const updatedAsks = message . data . a ;
callback ({ bids: updatedBids , asks: updatedAsks });
}
Trade Event
Message structure:
{
"data" : {
"e" : "trade" ,
"t" : 12345 ,
"m" : false ,
"p" : "100.5" ,
"q" : "1.5" ,
"T" : 1699564800000
}
}
Processing from apps/web/src/utils/ws_manager.ts:46-49:
if ( type === "trade" ) {
const trades = message . data ;
callback ( trades );
}
Subscription Management
Components subscribe and unsubscribe to market data streams.
Subscribe Example
From apps/web/src/components/Depth.tsx:125-133:
WsManager . getInstance (). sendMessage ({
method: "SUBSCRIBE" ,
params: [ `depth. ${ market } ` ],
});
WsManager . getInstance (). sendMessage ({
method: "SUBSCRIBE" ,
params: [ `trade. ${ market } ` ],
});
Unsubscribe Example
From apps/web/src/components/Depth.tsx:182-191:
WsManager . getInstance (). sendMessage ({
method: "UNSUBSCRIBE" ,
params: [ `depth. ${ market } ` ],
});
WsManager . getInstance (). sendMessage ({
method: "UNSUBSCRIBE" ,
params: [ `trade. ${ market } ` ],
});
Component Integration
Here’s a complete example of WebSocket integration in a component.
Full Integration Pattern
From apps/web/src/components/Depth.tsx:
useEffect (() => {
// Register depth callback
WsManager . getInstance (). registerCallback (
"depth" ,
( data : any ) => {
setBids (( originalBids ) => {
// Update bid logic
});
setAsks (( originalAsks ) => {
// Update ask logic
});
},
`DEPTH- ${ market } `
);
// Register trade callback
WsManager . getInstance (). registerCallback (
"trade" ,
( data : any ) => {
const newTrade : Trade = {
id: data . t ,
isBuyerMaker: data . m ,
price: data . p ,
quantity: data . q ,
quoteQuantity: data . q ,
timestamp: data . T ,
};
setPrice ( data . p );
setTrades (( oldTrades ) => {
const newTrades = [ ... oldTrades ];
newTrades . unshift ( newTrade );
newTrades . pop ();
return newTrades ;
});
},
`TRADE- ${ market } `
);
// Subscribe to events
WsManager . getInstance (). sendMessage ({
method: "SUBSCRIBE" ,
params: [ `depth. ${ market } ` ],
});
WsManager . getInstance (). sendMessage ({
method: "SUBSCRIBE" ,
params: [ `trade. ${ market } ` ],
});
// Cleanup function
return () => {
WsManager . getInstance (). deRegisterCallback ( "depth" , `DEPTH- ${ market } ` );
WsManager . getInstance (). sendMessage ({
method: "UNSUBSCRIBE" ,
params: [ `depth. ${ market } ` ],
});
WsManager . getInstance (). deRegisterCallback ( "trade" , `TRADE- ${ market } ` );
WsManager . getInstance (). sendMessage ({
method: "UNSUBSCRIBE" ,
params: [ `trade. ${ market } ` ],
});
};
}, [ market ]);
Message Flow
Complete Message Flow Diagram
Best Practices
Always use unique IDs when registering callbacks to enable proper cleanup: const callbackId = `DEPTH- ${ market } ` ;
WsManager . getInstance (). registerCallback ( "depth" , callback , callbackId );
Always deregister callbacks and unsubscribe in useEffect cleanup: useEffect (() => {
// Register and subscribe
return () => {
// Deregister and unsubscribe
};
}, [ market ]);
Consider implementing reconnection logic for production: this . ws . onclose = () => {
console . log ( 'Connection closed, reconnecting...' );
setTimeout (() => this . reconnect (), 1000 );
};
Validate message structure before processing: if ( message && message . data && message . data . e ) {
const type = message . data . e ;
// Process message
}
Error Handling
Consider adding error handlers for production:
this . ws . onerror = ( error ) => {
console . error ( 'WebSocket error:' , error );
// Notify user or trigger reconnection
};
this . ws . onclose = ( event ) => {
console . log ( 'WebSocket closed:' , event . code , event . reason );
// Implement reconnection strategy
};
Single Connection Singleton pattern ensures one connection for all components
Message Buffering Prevents message loss during connection phase
Callback Routing Efficient message distribution to multiple subscribers
Unique IDs Auto-incrementing message IDs for tracking
Next Steps
State Management Learn how WebSocket data updates application state
Components See how components use WebSocket data