diff --git a/packages/socket-server/src/socket/index.ts b/packages/socket-server/src/socket/index.ts index 66738a1df..1957f6e19 100644 --- a/packages/socket-server/src/socket/index.ts +++ b/packages/socket-server/src/socket/index.ts @@ -3,8 +3,9 @@ import { AxiosInstance } from 'axios' import { TransactionEventHandler } from '@modules/transactions' import { SwitchNetworkEventHandler } from '../modules/switchNetwork' -import { DatabaseClass } from '@utils/database' + import { SocketEvents, SocketUsernames } from '../types' +import { DatabaseClass, retryWithBackoff } from '@src/utils' // import Redis from 'ioredis' // import { createAdapter } from '@socket.io/redis-adapter' @@ -79,16 +80,37 @@ export const setupSocket = (io: SocketIOServer, database: DatabaseClass, api: Ax socket.on(SocketEvents.CONNECTION_STATE, async () => { const { sessionId, request_id } = socket.handshake.auth const connectorRoom = `${sessionId}:${SocketUsernames.CONNECTOR}:${request_id}` - const { data: connected } = await api.get(`/connections/${sessionId}/state`) - - io.to(connectorRoom).emit(SocketEvents.CONNECTION_STATE, { - username: SocketUsernames.CONNECTOR, - request_id, - room: sessionId, - to: SocketUsernames.CONNECTOR, - type: SocketEvents.CONNECTION_STATE, - data: connected, - }) + + try { + const connectionStateUrl = `/connections/${sessionId}/state` + const { data: connected } = await retryWithBackoff(() => api.get(connectionStateUrl), connectionStateUrl) + + console.log('[SOCKET] [CONNECTION_STATE] Connected state for session', sessionId, '->', connected) + + io.to(connectorRoom).emit(SocketEvents.CONNECTION_STATE, { + username: SocketUsernames.CONNECTOR, + request_id, + room: sessionId, + to: SocketUsernames.CONNECTOR, + type: SocketEvents.CONNECTION_STATE, + data: connected, + }) + } catch (error) { + console.error('[SOCKET] [CONNECTION_STATE] Error:', { + message: error.message, + status: error.response?.status, + url: error.config?.url, + }) + // Returns an error response to the client + io.to(connectorRoom).emit(SocketEvents.CONNECTION_STATE, { + username: SocketUsernames.CONNECTOR, + request_id, + room: sessionId, + to: SocketUsernames.CONNECTOR, + type: SocketEvents.CONNECTION_STATE, + data: false, + }) + } }) // Lidar com mensagens recebidas do cliente diff --git a/packages/socket-server/src/utils/index.ts b/packages/socket-server/src/utils/index.ts new file mode 100644 index 000000000..0cfc2856c --- /dev/null +++ b/packages/socket-server/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './database' +export * from './retryWithBackoff' diff --git a/packages/socket-server/src/utils/index.ts.ts b/packages/socket-server/src/utils/index.ts.ts deleted file mode 100644 index e1ddd7b2f..000000000 --- a/packages/socket-server/src/utils/index.ts.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './database' diff --git a/packages/socket-server/src/utils/retryWithBackoff.ts b/packages/socket-server/src/utils/retryWithBackoff.ts new file mode 100644 index 000000000..f400e9d96 --- /dev/null +++ b/packages/socket-server/src/utils/retryWithBackoff.ts @@ -0,0 +1,52 @@ +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + +type RetryOptions = { + retries?: number + baseDelay?: number +} + +const DEFAULT_RETRIES = 3 +const DEFAULT_BASE_DELAY = 300 // ms + +/** + * Executes an async function with retry attempts and exponential backoff for 5xx errors. + * + * - Retries only for HTTP 5xx errors (status >= 500 and < 600). + * - The delay between attempts increases exponentially and includes random jitter. + * - By default, tries up to 3 times and starts with a 500ms delay. + * - Logs the url (if provided) on each retry attempt. + * + * @template T The return type of the async function + * @param fn The async function to execute + * @param url The url related to the request (for logging) + * @param options.retries Maximum number of attempts (default: 3) + * @param options.baseDelay Base delay in ms for exponential backoff (default: 300) + * @returns The result of the async function if any attempt succeeds + * @throws Rethrows the original error if not a 5xx error or if all attempts fail + */ +async function retryWithBackoff(fn: () => Promise, url?: string, { retries = DEFAULT_RETRIES, baseDelay = DEFAULT_BASE_DELAY }: RetryOptions = {}): Promise { + for (let attempt = 0; attempt <= retries; attempt++) { + try { + return await fn() + } catch (error) { + const status = error?.response?.status + + if (!status || status < 500 || status >= 600) { + throw error + } + + if (attempt === retries) { + throw error + } + + const jitter = Math.random() * 100 + const delay = baseDelay * Math.pow(2, attempt) + jitter + + console.warn(`[RETRY] Attempt ${attempt + 1}/${retries + 1} failed (status ${status})${url ? ` for URL: ${url}` : ''}. Retrying in ${Math.round(delay)}ms...`) + + await sleep(delay) + } + } +} + +export { retryWithBackoff }