From 9e77deb4f830e6581d5ba792b354665fdb92d62c Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Wed, 25 Mar 2026 11:22:37 +0100 Subject: [PATCH] feat: Implement socket error telemetry in SocketManager to enhance error reporting for socket.io events --- src/services/SocketManager.js | 56 +++++++++++++++++++++++++++++++++++ src/utils/jsErrorTelemetry.js | 10 ++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/services/SocketManager.js b/src/services/SocketManager.js index 05eb8d4..53f9b44 100644 --- a/src/services/SocketManager.js +++ b/src/services/SocketManager.js @@ -1,3 +1,5 @@ +import { reportJsErrorTelemetry } from '../utils/jsErrorTelemetry.js'; + class SocketManager { constructor() { this._socket = null; @@ -85,6 +87,58 @@ class SocketManager { return this.reauthPromise; } + /** + * Report socket.io client errors to /api/telemetry/js-errors (richer than promise rejections alone). + */ + _installSocketTelemetry(socket) { + const report = (eventName, err) => { + const message = + err && typeof err.message === 'string' && err.message.trim() + ? err.message + : err != null + ? String(err) + : eventName; + reportJsErrorTelemetry({ + message, + name: err && err.name, + stack: err && err.stack, + url: typeof window !== 'undefined' ? window.location.href : undefined, + context: { + type: 'socket.io', + event: eventName, + connected: Boolean(socket.connected), + ...(socket.id ? { socketId: socket.id } : {}), + ...(socket.io?.engine?.transport?.name + ? { transport: socket.io.engine.transport.name } + : {}), + }, + }); + }; + + socket.on('connect_error', (err) => { + report('connect_error', err); + }); + + socket.on('error', (err) => { + report('error', err); + }); + + if (socket.io) { + socket.io.on('reconnect_error', (err) => { + report('reconnect_error', err); + }); + socket.io.on('reconnect_failed', () => { + report('reconnect_failed', new Error('reconnect_failed')); + }); + } + + if (socket.io?.engine) { + socket.io.engine.on('error', (err) => { + report('engine_error', err); + }); + } + } + // Lazily import socket.io-client and create the socket on first use. // Subsequent calls return the same promise. _ensureSocket() { @@ -111,6 +165,8 @@ class SocketManager { this.reauthPromise = this._reauthenticate(); }); + this._installSocketTelemetry(this._socket); + // Register any listeners that arrived before the socket was ready this._preSocketListeners.forEach(({ event, callback }) => { this._socket.on(event, callback); diff --git a/src/utils/jsErrorTelemetry.js b/src/utils/jsErrorTelemetry.js index aae2dc4..948bf5b 100644 --- a/src/utils/jsErrorTelemetry.js +++ b/src/utils/jsErrorTelemetry.js @@ -5,7 +5,11 @@ const TELEMETRY_PATH = '/api/telemetry/js-errors'; -function send(payload) { +/** + * POST a single error report (same shape as one element of `errors[]`). + * Used by global handlers and by SocketManager for socket.io failures. + */ +export function reportJsErrorTelemetry(payload) { fetch(TELEMETRY_PATH, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -13,6 +17,10 @@ function send(payload) { }).catch(() => {}); } +function send(payload) { + reportJsErrorTelemetry(payload); +} + /** * Register global listeners once. Safe to call in browser only. */