@@ -27,6 +27,11 @@ import {
2727import { debugNs } from '@socketsecurity/lib/debug'
2828
2929import ENV from '../../constants/env.mts'
30+ import {
31+ SOCKET_DASHBOARD_URL ,
32+ SOCKET_PRICING_URL ,
33+ SOCKET_STATUS_URL ,
34+ } from '../../constants/socket.mts'
3035
3136import type { RegistryInternals } from '../../constants/types.mts'
3237
@@ -111,8 +116,8 @@ export class RateLimitError extends Error {
111116 retryAfter
112117 ? `Wait ${ retryAfter } seconds before retrying`
113118 : 'Wait a few minutes before retrying' ,
114- ' Check your API quota at https://socket.dev/dashboard' ,
115- ' Consider upgrading your plan for higher limits' ,
119+ ` Check your API quota at ${ SOCKET_DASHBOARD_URL } ` ,
120+ ` Consider upgrading your plan for higher limits at ${ SOCKET_PRICING_URL } ` ,
116121 ]
117122 }
118123}
@@ -190,6 +195,33 @@ export class ConfigError extends Error {
190195 }
191196}
192197
198+ /**
199+ * Timeout error with retry guidance.
200+ * Thrown when operations exceed time limits.
201+ */
202+ export class TimeoutError extends Error {
203+ public readonly timeoutMs ?: number | undefined
204+ public readonly elapsedMs ?: number | undefined
205+ public readonly recovery : string [ ]
206+
207+ constructor (
208+ message : string ,
209+ timeoutMs ?: number | undefined ,
210+ elapsedMs ?: number | undefined ,
211+ recovery ?: string [ ] | undefined ,
212+ ) {
213+ super ( message )
214+ this . name = 'TimeoutError'
215+ this . timeoutMs = timeoutMs
216+ this . elapsedMs = elapsedMs
217+ this . recovery = recovery || [
218+ 'Check your internet connection speed' ,
219+ 'Try again when network conditions improve' ,
220+ 'Contact support if timeouts persist' ,
221+ ]
222+ }
223+ }
224+
193225export async function captureException (
194226 exception : unknown ,
195227 hint ?: EventHintOrCaptureContext | undefined ,
@@ -353,3 +385,133 @@ export async function buildErrorCause(
353385 ? `${ message } (reason: ${ reason } )`
354386 : message
355387}
388+
389+ /**
390+ * Type guard to check if an error is a network error.
391+ */
392+ export function isNetworkError ( error : unknown ) : error is NetworkError {
393+ return error instanceof NetworkError
394+ }
395+
396+ /**
397+ * Type guard to check if an error is a timeout error.
398+ */
399+ export function isTimeoutError ( error : unknown ) : error is TimeoutError {
400+ return error instanceof TimeoutError
401+ }
402+
403+ /**
404+ * Detect network-related error codes from Node.js errors.
405+ */
406+ export function getNetworkErrorCode ( error : unknown ) : string | undefined {
407+ if ( ! isErrnoException ( error ) ) {
408+ return undefined
409+ }
410+ return error . code
411+ }
412+
413+ /**
414+ * Get network error diagnostics with actionable guidance.
415+ * Provides specific recovery steps based on error type.
416+ *
417+ * @param error - The error to diagnose
418+ * @param durationMs - Optional request duration in milliseconds
419+ * @returns Diagnostic message with recovery suggestions
420+ *
421+ * @example
422+ * const diagnostics = getNetworkErrorDiagnostics(error, 5000)
423+ * // Returns: "Connection refused. The server may be down..."
424+ */
425+ export function getNetworkErrorDiagnostics (
426+ error : unknown ,
427+ durationMs ?: number | undefined ,
428+ ) : string {
429+ const errorCode = getNetworkErrorCode ( error )
430+ const errorMessage = getErrorMessage ( error ) || String ( error )
431+
432+ // Timeout errors.
433+ if (
434+ errorCode === 'ETIMEDOUT' ||
435+ errorCode === 'ESOCKETTIMEDOUT' ||
436+ errorCode === 'ECONNRESET' ||
437+ ( durationMs && durationMs > 30_000 )
438+ ) {
439+ const timeInfo = durationMs ? ` after ${ Math . round ( durationMs / 1000 ) } s` : ''
440+ return (
441+ `Request timeout${ timeInfo } . The server took too long to respond.\n` +
442+ '💡 Try:\n' +
443+ ' • Check your internet connection speed\n' +
444+ ' • Retry the request - the server may be temporarily slow\n' +
445+ ` • Check Socket status: ${ SOCKET_STATUS_URL } \n` +
446+ ' • Contact support if timeouts persist'
447+ )
448+ }
449+
450+ // Connection refused.
451+ if ( errorCode === 'ECONNREFUSED' ) {
452+ return (
453+ 'Connection refused. The server actively rejected the connection.\n' +
454+ '💡 Try:\n' +
455+ ' • Check if you are using a proxy or VPN that may be blocking the connection\n' +
456+ ' • Verify your firewall settings\n' +
457+ ` • Check Socket status: ${ SOCKET_STATUS_URL } \n` +
458+ ' • Ensure SOCKET_CLI_API_BASE_URL is set correctly (if configured)'
459+ )
460+ }
461+
462+ // DNS resolution failures.
463+ if (
464+ errorCode === 'ENOTFOUND' ||
465+ errorCode === 'EAI_AGAIN' ||
466+ errorMessage . includes ( 'getaddrinfo' )
467+ ) {
468+ return (
469+ 'DNS resolution failed. Unable to resolve the server hostname.\n' +
470+ '💡 Try:\n' +
471+ ' • Check your internet connection\n' +
472+ ' • Verify DNS settings (try 8.8.8.8 or 1.1.1.1)\n' +
473+ ' • Check if a VPN or proxy is interfering\n' +
474+ ' • Ensure SOCKET_CLI_API_BASE_URL is correct (if configured)\n' +
475+ ' • Try again in a few moments'
476+ )
477+ }
478+
479+ // Certificate/SSL errors.
480+ if (
481+ errorCode === 'CERT_HAS_EXPIRED' ||
482+ errorCode === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' ||
483+ errorCode === 'SELF_SIGNED_CERT_IN_CHAIN' ||
484+ errorMessage . includes ( 'certificate' )
485+ ) {
486+ return (
487+ 'SSL/TLS certificate error. Unable to verify server identity.\n' +
488+ '💡 Try:\n' +
489+ ' • Check your system date and time are correct\n' +
490+ ' • Update your system certificates\n' +
491+ ' • Check if a proxy is intercepting HTTPS traffic\n' +
492+ ' • Contact your IT department if behind corporate firewall'
493+ )
494+ }
495+
496+ // Network unreachable.
497+ if ( errorCode === 'ENETUNREACH' || errorCode === 'EHOSTUNREACH' ) {
498+ return (
499+ 'Network unreachable. Cannot reach the destination network.\n' +
500+ '💡 Try:\n' +
501+ ' • Check your internet connection\n' +
502+ ' • Verify network/WiFi is connected\n' +
503+ ' • Check if VPN or firewall is blocking access\n' +
504+ ' • Try a different network'
505+ )
506+ }
507+
508+ // Generic network error with basic guidance.
509+ return (
510+ `Network error: ${ errorMessage } \n` +
511+ '💡 Try:\n' +
512+ ' • Check your internet connection\n' +
513+ ' • Verify proxy settings if using a proxy\n' +
514+ ` • Check Socket status: ${ SOCKET_STATUS_URL } \n` +
515+ ' • Try again in a few moments'
516+ )
517+ }
0 commit comments