Skip to content

Commit b994c6b

Browse files
authored
feat: include request URL in API error results (#483)
Thread the request URL through ResponseError and getResponse so that SocketSdkErrorResult carries a `url` field on failure. This lets consumers (e.g. socket-cli) surface which endpoint failed, making API errors much easier to debug.
1 parent b07ae0f commit b994c6b

2 files changed

Lines changed: 30 additions & 20 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@socketsecurity/sdk",
3-
"version": "1.4.95",
3+
"version": "1.4.96",
44
"packageManager": "pnpm@10.24.0",
55
"license": "MIT",
66
"description": "SDK for the Socket API client",

src/index.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export type SocketSdkErrorResult<T extends SocketSdkOperations> = Omit<
145145
> & {
146146
error: string
147147
cause?: string | undefined
148+
url?: string | undefined
148149
}
149150

150151
export type SocketSdkResult<T extends SocketSdkOperations> =
@@ -339,14 +340,16 @@ function sanitizeHeaders(
339340

340341
class ResponseError extends Error {
341342
response: IncomingMessage
342-
constructor(response: IncomingMessage, message: string = '') {
343+
url?: string | undefined
344+
constructor(response: IncomingMessage, message: string = '', url?: string) {
343345
const statusCode = response.statusCode ?? 'unknown'
344346
const statusMessage = response.statusMessage ?? 'No status message'
345347
super(
346348
`Socket API ${message || 'Request failed'} (${statusCode}): ${statusMessage}`
347349
)
348350
this.name = 'ResponseError'
349351
this.response = response
352+
this.url = url
350353
Error.captureStackTrace(this, ResponseError)
351354
}
352355
}
@@ -375,7 +378,7 @@ async function createDeleteRequest(
375378
...options
376379
})
377380
.end()
378-
const response = await getResponse(req)
381+
const response = await getResponse(req, url)
379382

380383
hooks?.onResponse?.({
381384
method,
@@ -423,7 +426,7 @@ async function createGetRequest(
423426
...options
424427
})
425428
.end()
426-
const response = await getResponse(req)
429+
const response = await getResponse(req, url)
427430

428431
hooks?.onResponse?.({
429432
method,
@@ -481,7 +484,7 @@ async function createPostRequest(
481484
req.write(body)
482485
req.end()
483486

484-
const response = await getResponse(req)
487+
const response = await getResponse(req, url)
485488

486489
hooks?.onResponse?.({
487490
method,
@@ -539,7 +542,7 @@ async function createPutRequest(
539542
req.write(body)
540543
req.end()
541544

542-
const response = await getResponse(req)
545+
const response = await getResponse(req, url)
543546

544547
hooks?.onResponse?.({
545548
method,
@@ -654,7 +657,7 @@ async function createUploadRequest(
654657
req.flushHeaders()
655658

656659
// Concurrently wait for response while we stream body.
657-
getResponse(req).then(
660+
getResponse(req, url.toString()).then(
658661
response => {
659662
hooks?.onResponse?.({
660663
method,
@@ -778,7 +781,10 @@ function getHttpModule(url: string): typeof http | typeof https {
778781
return urlObj?.protocol === 'http:' ? http : https
779782
}
780783

781-
async function getResponse(req: ClientRequest): Promise<IncomingMessage> {
784+
async function getResponse(
785+
req: ClientRequest,
786+
url?: string
787+
): Promise<IncomingMessage> {
782788
const res = await new Promise<IncomingMessage>((resolve, reject) => {
783789
const cleanup = () => {
784790
req.off('response', onResponse)
@@ -811,7 +817,7 @@ async function getResponse(req: ClientRequest): Promise<IncomingMessage> {
811817
})
812818

813819
if (!isResponseOk(res)) {
814-
throw new ResponseError(res, `${req.method} request failed`)
820+
throw new ResponseError(res, `${req.method} request failed`, url)
815821
}
816822
return res
817823
}
@@ -993,13 +999,14 @@ export class SocketSdk {
993999
queryParams?: QueryParams | undefined
9941000
): Promise<IncomingMessage> {
9951001
// Adds the first 'abort' listener to abortSignal.
1002+
const url = `${this.#baseUrl}purl?${queryToSearchParams(queryParams)}`
9961003
const req = getHttpModule(this.#baseUrl)
997-
.request(`${this.#baseUrl}purl?${queryToSearchParams(queryParams)}`, {
1004+
.request(url, {
9981005
method: 'POST',
9991006
...this.#reqOptions
10001007
})
10011008
.end(JSON.stringify(componentsObj))
1002-
return await getResponse(req)
1009+
return await getResponse(req, url)
10031010
}
10041011

10051012
async *#createBatchPurlGenerator(
@@ -1081,7 +1088,8 @@ export class SocketSdk {
10811088
success: false,
10821089
status: statusCode ?? 0,
10831090
error: error.message ?? 'Unknown error',
1084-
cause: body
1091+
cause: body,
1092+
url: error.url
10851093
} as SocketSdkErrorResult<T>
10861094
}
10871095

@@ -1472,16 +1480,14 @@ export class SocketSdk {
14721480
file?: string
14731481
): Promise<SocketSdkResult<'getOrgFullScan'>> {
14741482
try {
1483+
const url = `${this.#baseUrl}orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(fullScanId)}`
14751484
const req = getHttpModule(this.#baseUrl)
1476-
.request(
1477-
`${this.#baseUrl}orgs/${encodeURIComponent(orgSlug)}/full-scans/${encodeURIComponent(fullScanId)}`,
1478-
{
1479-
method: 'GET',
1480-
...this.#reqOptions
1481-
}
1482-
)
1485+
.request(url, {
1486+
method: 'GET',
1487+
...this.#reqOptions
1488+
})
14831489
.end()
1484-
const res = await getResponse(req)
1490+
const res = await getResponse(req, url)
14851491
if (file) {
14861492
res.pipe(createWriteStream(file))
14871493
} else {
@@ -1863,6 +1869,7 @@ export class SocketSdk {
18631869
status: number
18641870
error: string
18651871
cause?: string
1872+
url?: string
18661873
} = {
18671874
success: false,
18681875
status: statusCode ?? 0,
@@ -1871,6 +1878,9 @@ export class SocketSdk {
18711878
if (body) {
18721879
result.cause = body
18731880
}
1881+
if (e.url) {
1882+
result.url = e.url
1883+
}
18741884
return result
18751885
}
18761886
}

0 commit comments

Comments
 (0)