Websocket no longer connecting

Created at 10 Nov 2023, 15:53
How’s your experience with the cTrader Platform?
Your feedback is crucial to cTrader's development. Please take a few seconds to share your opinion and help us improve your trading experience. Thanks!
JO

Jowin

Joined 22.10.2023

Websocket no longer connecting
10 Nov 2023, 15:53


Hi.

My websocket connecton to Open API is no longer managing to connect. It was fine up until a day or two ago. Below is the websocket service from my code, the connection url for cTrader is “wss://demo.ctraderapi.com:5036" .. when logging this.ws I can see the readyStatus = 0 , which means its still connecting. I get no 'open' message and when i try to send a message it fails as the connection is closed. There is a “_closeCode: 1006,” which is abnormal close but im not sure if the connection is actually opening and then closing at all as I am getting no error messages so I'm a bit stuck as to what to try next..



import { checkCtraderAccountAuthAndRefreshIfReq } from '$lib/auth/checkCtraderAccAuth';
import { getCTraderAuthTokensAndAccountFromSb } from '$lib/auth/initcTraderAuthTokens';
import { ExecutionEventHandler, cTraderSpotEventHandler } from '$lib/trader/cTraderEventHandlers';
import {
payloadTypeDict,
type EVENT_IDS,
type payloadTypeDictValues
} from '$lib/types/cTraderDictionaries';
import type { SpotEvent_ } from '$lib/types/cTraderEventTypes';
import type { BaseMessageType_ } from '$lib/types/openAPITypes';
import WebSocket from 'ws';


//https://github.com/websockets/ws/blob/HEAD/doc/ws.md#websocketsenddata-options-callback
//ws docs


export class WebSocketServiceClass {
private ws!: WebSocket;
// eslint-disable-next-line
private queue: Map<string, (data: any) => void>;
private connectionUrl: string;
private eventIds: EVENT_IDS[] = [];
public connected: boolean;


constructor() {
this.queue = new Map();
this.connectionUrl = import.meta.env.VITE_CTRADER_CONNECTION_URL; this.initializeWebSocket();
this.eventIds = [2107, 2120, 2123, 2126, 2131, 2132, 2141, 2147, 2148, 2155, 2164, 2171, 2172];
this.connected = false;
}


private initializeWebSocket() {
console.log('Initializing WebSocket connection to cTrader...', this.connectionUrl);
this.ws = new WebSocket(this.connectionUrl);


this.ws.on('message', this.handleMessage.bind(this));


this.ws.on('open', async () => {
this.connected = true;
console.log(`WebSocket connection opened to ${this.connectionUrl}`);
const authTokensAndAccount = await getCTraderAuthTokensAndAccountFromSb();


if (authTokensAndAccount?.account && authTokensAndAccount?.tokens?.accessToken) {
await checkCtraderAccountAuthAndRefreshIfReq(
authTokensAndAccount.account.ctidTraderAccountId
);
}
});


this.ws.on('close', (code, reason) => {
this.connected = false;
console.log('WebSocket connection closed: ', code, reason.toString());


// Attempt to restart the WebSocket connection
console.log('Attempting to reconnect websocket in 5 seconds...');
setTimeout(() => {
this.initializeWebSocket();
}, 5000);
});


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


//eslint-disable-next-line
public async send(baseMessage: BaseMessageType_): Promise<any> {
const { payload, clientMsgId, payloadType } = baseMessage;


// Check if the WebSocket is in the "OPEN" state
if (this.ws.readyState === WebSocket.OPEN) {
// Construct the completed message with the supplied data
const completeMessage: BaseMessageType_ = {
clientMsgId,
payloadType,
payload: {
clientId: import.meta.env.VITE_OPEN_API_CLIENT_ID,
clientSecret: import.meta.env.VITE_OPEN_API_SECRET,
...payload
}
};


// eslint-disable-next-line
const promise = new Promise<any>((resolve) => {
this.queue.set(clientMsgId, resolve);
this.ws.send(JSON.stringify({ ...completeMessage }));
});


return promise;
} else {
// WebSocket is not in the "OPEN" state, handle the error accordingly
return Promise.reject(new Error('Owen: WebSocket is not connected.'));
}
}


private handleMessage(data: WebSocket.Data) {
const message = JSON.parse(data.toString());
const clientMsgId = message.clientMsgId;
const payloadType: payloadTypeDictValues | undefined = message.payloadType;


//divert event messages to the event handler
if (payloadType && this.eventIds.includes(payloadType as EVENT_IDS)) {
this.handleEventMessage(data);
return;
}


//Log errors
if (message.payload?.errorCode) {
message.payload?.errorCode === 'ALREADY_LOGGED_IN'
? console.log('Already logged in to cTracer account.')
: console.log(
`Error response from cTrader: Error code: ${message.payload.errorCode}, Description: ${message.payload.description}`
);
}


if (this.queue.has(clientMsgId)) {
const resolve = this.queue.get(clientMsgId);
if (resolve) {
resolve(message);
}
this.queue.delete(clientMsgId);
}
}


private handleEventMessage(data: WebSocket.Data) {
const message = JSON.parse(data.toString());
const payloadType: EVENT_IDS = message.payloadType;


switch (payloadType) {
case payloadTypeDict.SPOT_EVENT:
cTraderSpotEventHandler(message.payload as SpotEvent_['payload']); //ProtoOASpotEvent - (price stream event)
break;
case payloadTypeDict.SYMBOL_CHANGED_EVENT:
console.log('SYMBOL_CHANGED_EVENT - Symbols changed: ', message.payload?.symbolId);
break;
case payloadTypeDict.ORDER_ERROR_EVENT:
console.log(
`Order error event, message: ${message.payload?.description}, code: ${message.payload?.errorCode}`
);
break;
case payloadTypeDict.EXECUTION_EVENT:
ExecutionEventHandler(message);
break;
default:
console.log('Unhandled event message recieved from cTrader:', message);
}
}
}

This below is the websocket logged after instantiating it:

WebSocket {
 _events: [Object: null prototype] {},
 _eventsCount: 0,
 _maxListeners: undefined,
 _binaryType: 'nodebuffer',
 _closeCode: 1006,
 _closeFrameReceived: false,
 _closeFrameSent: false,
 _closeMessage: <Buffer >,
 _closeTimer: null,
 _extensions: {},
 _paused: false,
 _protocol: '',
 _readyState: 0,
 _receiver: null,
 _sender: null,
 _socket: null,
 _bufferedAmount: 0,
 _isServer: false,
 _redirects: 0,
 _url: 'wss://demo.ctraderapi.com:5036/',
 _req: ClientRequest {
   _events: [Object: null prototype] {
     error: [Function (anonymous)],
     response: [Function (anonymous)],
     upgrade: [Function (anonymous)]
   },
   _eventsCount: 3,
   _maxListeners: undefined,
   outputData: [ [Object] ],
   outputSize: 233,
   writable: true,
   destroyed: false,
   _last: true,
   chunkedEncoding: false,
   shouldKeepAlive: true,
   maxRequestsOnConnectionReached: false,
   _defaultKeepAlive: true,
   useChunkedEncodingByDefault: false,
   sendDate: false,
   _removedConnection: false,
   _removedContLen: false,
   _removedTE: false,
   _contentLength: 0,
   _hasBody: true,
   _trailer: '',
   finished: true,
   _headerSent: true,
   _closed: false,
   socket: null,
   _header: 'GET / HTTP/1.1\r\n' +
     'Sec-WebSocket-Version: 13\r\n' +
     'Sec-WebSocket-Key: 0xpKKrylwaCDSf+pq2vZaA==\r\n' +
     'Connection: Upgrade\r\n' +
     'Upgrade: websocket\r\n' +
     'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n' +
     'Host: demo.ctraderapi.com:5036\r\n' +
     '\r\n',
   _keepAliveTimeout: 0,
   _onPendingData: [Function: nop],
   agent: undefined,
   socketPath: undefined,
   method: 'GET',
   maxHeaderSize: undefined,
   insecureHTTPParser: undefined,
   path: '/',
   _ended: false,
   res: null,
   aborted: false,
   timeoutCb: null,
   upgradeOrConnect: false,
   parser: null,
   maxHeadersCount: null,
   reusedSocket: false,
   host: 'demo.ctraderapi.com',
   protocol: 'https:',
   [Symbol(kCapture)]: false,
   [Symbol(kNeedDrain)]: false,
   [Symbol(corked)]: 0,
   [Symbol(kOutHeaders)]: [Object: null prototype] {
     'sec-websocket-version': [Array],
     'sec-websocket-key': [Array],
     connection: [Array],
     upgrade: [Array],
     'sec-websocket-extensions': [Array],
     host: [Array]
   }
 },
 [Symbol(kCapture)]: false
}

@Jowin