feat: Implement socket error telemetry in SocketManager to enhance error reporting for socket.io events

This commit is contained in:
sebseb7
2026-03-25 11:22:37 +01:00
parent 5515a59fa1
commit 9e77deb4f8
2 changed files with 65 additions and 1 deletions

View File

@@ -1,3 +1,5 @@
import { reportJsErrorTelemetry } from '../utils/jsErrorTelemetry.js';
class SocketManager { class SocketManager {
constructor() { constructor() {
this._socket = null; this._socket = null;
@@ -85,6 +87,58 @@ class SocketManager {
return this.reauthPromise; 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. // Lazily import socket.io-client and create the socket on first use.
// Subsequent calls return the same promise. // Subsequent calls return the same promise.
_ensureSocket() { _ensureSocket() {
@@ -111,6 +165,8 @@ class SocketManager {
this.reauthPromise = this._reauthenticate(); this.reauthPromise = this._reauthenticate();
}); });
this._installSocketTelemetry(this._socket);
// Register any listeners that arrived before the socket was ready // Register any listeners that arrived before the socket was ready
this._preSocketListeners.forEach(({ event, callback }) => { this._preSocketListeners.forEach(({ event, callback }) => {
this._socket.on(event, callback); this._socket.on(event, callback);

View File

@@ -5,7 +5,11 @@
const TELEMETRY_PATH = '/api/telemetry/js-errors'; 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, { fetch(TELEMETRY_PATH, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -13,6 +17,10 @@ function send(payload) {
}).catch(() => {}); }).catch(() => {});
} }
function send(payload) {
reportJsErrorTelemetry(payload);
}
/** /**
* Register global listeners once. Safe to call in browser only. * Register global listeners once. Safe to call in browser only.
*/ */