1- import path from 'node:path '
1+ 'use strict '
22
3- import { FormData } from 'formdata-node'
4- import { fileFromPath } from 'formdata-node/file-from-path'
5- import got from 'got'
3+ const path = require ( 'node:path' )
64
7- import { handleApiError } from './lib/helpers.js'
5+ const { ErrorWithCause } = require ( 'pony-cause' )
86
97/**
108 * @template {keyof import('./types/api').operations} T
@@ -27,10 +25,16 @@ import { handleApiError } from './lib/helpers.js'
2725 * @property {string } [baseUrl]
2826 */
2927
30- export class SocketSdk {
31- /** @type {import('got').Got } */
28+ class SocketSdk {
29+ /** @type {import('got').Got|undefined } */
3230 #client
3331
32+ /** @type {typeof import('got').HTTPError|undefined } */
33+ #HTTPError
34+
35+ /** @type {import('got').ExtendOptions } */
36+ #gotOptions
37+
3438 /**
3539 * @param {string } apiKey
3640 * @param {SocketSdkOptions } options
@@ -42,12 +46,29 @@ export class SocketSdk {
4246 baseUrl = 'https://api.socket.dev/v0/' ,
4347 } = options
4448
45- this . #client = got . extend ( {
49+ this . #gotOptions = {
4650 prefixUrl : baseUrl ,
4751 retry : { limit : 0 } ,
4852 username : apiKey ,
4953 ...( agent ? { agent } : { } ) ,
50- } )
54+ }
55+ }
56+
57+ /**
58+ * @returns {Promise<import('got').Got> }
59+ */
60+ async #getClient ( ) {
61+ if ( ! this . #client) {
62+ const {
63+ default : got ,
64+ HTTPError,
65+ } = await import ( 'got' )
66+
67+ this . #HTTPError = HTTPError
68+ this . #client = got . extend ( this . #gotOptions)
69+ }
70+
71+ return this . #client
5172 }
5273
5374 /**
@@ -59,6 +80,16 @@ export class SocketSdk {
5980 const basePath = path . resolve ( process . cwd ( ) , pathsRelativeTo )
6081 const absoluteFilePaths = filePaths . map ( filePath => path . resolve ( basePath , filePath ) )
6182
83+ const [
84+ { FormData } ,
85+ { fileFromPath } ,
86+ client
87+ ] = await Promise . all ( [
88+ import ( 'formdata-node' ) ,
89+ import ( 'formdata-node/file-from-path' ) ,
90+ this . #getClient( ) ,
91+ ] )
92+
6293 const body = new FormData ( )
6394
6495 const files = await Promise . all ( absoluteFilePaths . map ( absoluteFilePath => fileFromPath ( absoluteFilePath ) ) )
@@ -72,11 +103,11 @@ export class SocketSdk {
72103 }
73104
74105 try {
75- const data = await this . # client. put ( 'report/upload' , { body } ) . json ( )
106+ const data = await client . put ( 'report/upload' , { body } ) . json ( )
76107
77108 return { success : true , status : 200 , data }
78109 } catch ( err ) {
79- return /** @type {SocketSdkErrorType<'createReport'> } */ ( handleApiError ( err ) )
110+ return /** @type {SocketSdkErrorType<'createReport'> } */ ( this . # handleApiError( err ) )
80111 }
81112 }
82113
@@ -90,10 +121,11 @@ export class SocketSdk {
90121 const versionParam = encodeURIComponent ( version )
91122
92123 try {
93- const data = await this . #client. get ( `npm/${ pkgParam } /${ versionParam } /score` ) . json ( )
124+ const client = await this . #getClient( )
125+ const data = await client . get ( `npm/${ pkgParam } /${ versionParam } /score` ) . json ( )
94126 return { success : true , status : 200 , data }
95127 } catch ( err ) {
96- return /** @type {SocketSdkErrorType<'getScoreByNPMPackage'> } */ ( handleApiError ( err ) )
128+ return /** @type {SocketSdkErrorType<'getScoreByNPMPackage'> } */ ( this . # handleApiError( err ) )
97129 }
98130 }
99131
@@ -107,20 +139,22 @@ export class SocketSdk {
107139 const versionParam = encodeURIComponent ( version )
108140
109141 try {
110- const data = await this . #client. get ( `npm/${ pkgParam } /${ versionParam } /issues` ) . json ( )
142+ const client = await this . #getClient( )
143+ const data = await client . get ( `npm/${ pkgParam } /${ versionParam } /issues` ) . json ( )
111144 return { success : true , status : 200 , data }
112145 } catch ( err ) {
113- return /** @type {SocketSdkErrorType<'getIssuesByNPMPackage'> } */ ( handleApiError ( err ) )
146+ return /** @type {SocketSdkErrorType<'getIssuesByNPMPackage'> } */ ( this . # handleApiError( err ) )
114147 }
115148 }
116149
117150 /** @returns {Promise<SocketSdkResultType<'getReportList'>> } */
118151 async getReportList ( ) {
119152 try {
120- const data = await this . #client. get ( 'report/list' ) . json ( )
153+ const client = await this . #getClient( )
154+ const data = await client . get ( 'report/list' ) . json ( )
121155 return { success : true , status : 200 , data }
122156 } catch ( err ) {
123- return /** @type {SocketSdkErrorType<'getReportList'> } */ ( handleApiError ( err ) )
157+ return /** @type {SocketSdkErrorType<'getReportList'> } */ ( this . # handleApiError( err ) )
124158 }
125159 }
126160
@@ -132,20 +166,75 @@ export class SocketSdk {
132166 const idParam = encodeURIComponent ( id )
133167
134168 try {
135- const data = await this . #client. get ( `report/view/${ idParam } ` ) . json ( )
169+ const client = await this . #getClient( )
170+ const data = await client . get ( `report/view/${ idParam } ` ) . json ( )
136171 return { success : true , status : 200 , data }
137172 } catch ( err ) {
138- return /** @type {SocketSdkErrorType<'getReport'> } */ ( handleApiError ( err ) )
173+ return /** @type {SocketSdkErrorType<'getReport'> } */ ( this . # handleApiError( err ) )
139174 }
140175 }
141176
142177 /** @returns {Promise<SocketSdkResultType<'getQuota'>> } */
143178 async getQuota ( ) {
144179 try {
145- const data = await this . #client. get ( 'quota' ) . json ( )
180+ const client = await this . #getClient( )
181+ const data = await client . get ( 'quota' ) . json ( )
146182 return { success : true , status : 200 , data }
147183 } catch ( err ) {
148- return /** @type {SocketSdkErrorType<'getQuota'> } */ ( handleApiError ( err ) )
184+ return /** @type {SocketSdkErrorType<'getQuota'> } */ ( this . # handleApiError( err ) )
149185 }
150186 }
187+
188+ /**
189+ * @param {unknown } err
190+ * @returns {{ success: false, status: number, error: Record<string,unknown> } }
191+ */
192+ #handleApiError ( err ) {
193+ if ( this . #HTTPError && err instanceof this . #HTTPError) {
194+ if ( err . response . statusCode >= 500 ) {
195+ throw new ErrorWithCause ( 'API returned an error' , { cause : err } )
196+ }
197+
198+ return {
199+ success : false ,
200+ status : err . response . statusCode ,
201+ error : this . #getApiErrorDescription( err )
202+ }
203+ }
204+
205+ throw new ErrorWithCause ( 'Unexpected error when calling API' , { cause : err } )
206+ }
207+
208+ /**
209+ * @param {import('got').HTTPError } err
210+ * @returns {Record<string,unknown> }
211+ */
212+ #getApiErrorDescription ( err ) {
213+ /** @type {unknown } */
214+ let rawBody
215+
216+ try {
217+ rawBody = JSON . parse ( /** @type {string } */ ( err . response . body ) )
218+ } catch ( cause ) {
219+ throw new ErrorWithCause ( 'Could not parse API error response' , { cause } )
220+ }
221+
222+ const errorDescription = ensureObject ( rawBody ) ? rawBody [ 'error' ] : undefined
223+
224+ if ( ! ensureObject ( errorDescription ) ) {
225+ throw new Error ( 'Invalid body on API error response' )
226+ }
227+
228+ return errorDescription
229+ }
230+ }
231+
232+ /**
233+ * @param {unknown } value
234+ * @returns {value is { [key: string]: unknown } }
235+ */
236+ function ensureObject ( value ) {
237+ return ! ! ( value && typeof value === 'object' && ! Array . isArray ( value ) )
151238}
239+
240+ module . exports = { SocketSdk }
0 commit comments