Skip to content

Commit d64be3e

Browse files
committed
feat: add configurable network timeout for API calls
Add timeout support using AbortController with 30s default. Configurable via SOCKET_CLI_API_TIMEOUT environment variable. Prevents CLI from hanging indefinitely on network issues.
1 parent 68e21c1 commit d64be3e

File tree

2 files changed

+54
-12
lines changed

2 files changed

+54
-12
lines changed

src/constants.mts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,25 @@ const lazyExternalPath = () => path.join(constants.distPath, 'external')
881881

882882
const lazyGithubCachePath = () => path.join(constants.socketCachePath, 'github')
883883

884-
const lazyHomePath = () => os.homedir()
884+
const lazyHomePath = () => {
885+
// Try to get home directory with multiple fallbacks
886+
try {
887+
const home = os.homedir()
888+
if (home) {return home}
889+
} catch {
890+
// os.homedir() can throw in some environments
891+
}
892+
893+
// Fallback to environment variables
894+
const homeEnv = process.env['HOME'] || process.env['USERPROFILE']
895+
if (homeEnv) {return homeEnv}
896+
897+
// Last resort: use temp directory as a fallback
898+
// This ensures the CLI can still run in containerized/restricted environments
899+
const tmpHome = path.join(os.tmpdir(), '.socket-cli-home')
900+
console.warn(`Warning: Using temporary directory as home: ${tmpHome}`)
901+
return tmpHome
902+
}
885903

886904
const lazyInstrumentWithSentryPath = () =>
887905
path.join(constants.distPath, 'instrument-with-sentry.js')

src/utils/api.mts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -348,12 +348,24 @@ export async function queryApi(path: string, apiToken: string) {
348348
throw new Error('Socket API base URL is not configured.')
349349
}
350350

351-
return await fetch(`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`, {
352-
method: 'GET',
353-
headers: {
354-
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
355-
},
356-
})
351+
// Add timeout support with AbortController
352+
// Default to 30 seconds, configurable via SOCKET_CLI_API_TIMEOUT environment variable
353+
const timeoutMs = constants.ENV.SOCKET_CLI_API_TIMEOUT || 30000
354+
355+
const controller = new AbortController()
356+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
357+
358+
try {
359+
return await fetch(`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`, {
360+
method: 'GET',
361+
headers: {
362+
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
363+
},
364+
signal: controller.signal,
365+
})
366+
} finally {
367+
clearTimeout(timeoutId)
368+
}
357369
}
358370

359371
/**
@@ -521,22 +533,34 @@ export async function sendApiRequest<T>(
521533

522534
let result
523535
try {
536+
// Add timeout support with AbortController
537+
const timeoutMs = constants.ENV.SOCKET_CLI_API_TIMEOUT || 30000
538+
539+
const controller = new AbortController()
540+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
541+
524542
const fetchOptions = {
525543
method,
526544
headers: {
527545
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
528546
'Content-Type': 'application/json',
529547
},
548+
signal: controller.signal,
530549
...(body ? { body: JSON.stringify(body) } : {}),
531550
}
532551

533552
result = await withSpinner({
534553
message: spinnerMessage,
535-
operation: async () =>
536-
await fetch(
537-
`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`,
538-
fetchOptions,
539-
),
554+
operation: async () => {
555+
try {
556+
return await fetch(
557+
`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`,
558+
fetchOptions,
559+
)
560+
} finally {
561+
clearTimeout(timeoutId)
562+
}
563+
},
540564
spinner,
541565
})
542566

0 commit comments

Comments
 (0)