1
- import path from 'node:path '
1
+ 'use strict '
2
2
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' )
6
4
7
- import { handleApiError } from './lib/helpers.js'
5
+ const { ErrorWithCause } = require ( 'pony-cause' )
8
6
9
7
/**
10
8
* @template {keyof import('./types/api').operations} T
@@ -27,10 +25,16 @@ import { handleApiError } from './lib/helpers.js'
27
25
* @property {string } [baseUrl]
28
26
*/
29
27
30
- export class SocketSdk {
31
- /** @type {import('got').Got } */
28
+ class SocketSdk {
29
+ /** @type {import('got').Got|undefined } */
32
30
#client
33
31
32
+ /** @type {typeof import('got').HTTPError|undefined } */
33
+ #HTTPError
34
+
35
+ /** @type {import('got').ExtendOptions } */
36
+ #gotOptions
37
+
34
38
/**
35
39
* @param {string } apiKey
36
40
* @param {SocketSdkOptions } options
@@ -42,12 +46,29 @@ export class SocketSdk {
42
46
baseUrl = 'https://api.socket.dev/v0/' ,
43
47
} = options
44
48
45
- this . #client = got . extend ( {
49
+ this . #gotOptions = {
46
50
prefixUrl : baseUrl ,
47
51
retry : { limit : 0 } ,
48
52
username : apiKey ,
49
53
...( 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
51
72
}
52
73
53
74
/**
@@ -59,6 +80,16 @@ export class SocketSdk {
59
80
const basePath = path . resolve ( process . cwd ( ) , pathsRelativeTo )
60
81
const absoluteFilePaths = filePaths . map ( filePath => path . resolve ( basePath , filePath ) )
61
82
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
+
62
93
const body = new FormData ( )
63
94
64
95
const files = await Promise . all ( absoluteFilePaths . map ( absoluteFilePath => fileFromPath ( absoluteFilePath ) ) )
@@ -72,11 +103,11 @@ export class SocketSdk {
72
103
}
73
104
74
105
try {
75
- const data = await this . # client. put ( 'report/upload' , { body } ) . json ( )
106
+ const data = await client . put ( 'report/upload' , { body } ) . json ( )
76
107
77
108
return { success : true , status : 200 , data }
78
109
} catch ( err ) {
79
- return /** @type {SocketSdkErrorType<'createReport'> } */ ( handleApiError ( err ) )
110
+ return /** @type {SocketSdkErrorType<'createReport'> } */ ( this . # handleApiError( err ) )
80
111
}
81
112
}
82
113
@@ -90,10 +121,11 @@ export class SocketSdk {
90
121
const versionParam = encodeURIComponent ( version )
91
122
92
123
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 ( )
94
126
return { success : true , status : 200 , data }
95
127
} catch ( err ) {
96
- return /** @type {SocketSdkErrorType<'getScoreByNPMPackage'> } */ ( handleApiError ( err ) )
128
+ return /** @type {SocketSdkErrorType<'getScoreByNPMPackage'> } */ ( this . # handleApiError( err ) )
97
129
}
98
130
}
99
131
@@ -107,20 +139,22 @@ export class SocketSdk {
107
139
const versionParam = encodeURIComponent ( version )
108
140
109
141
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 ( )
111
144
return { success : true , status : 200 , data }
112
145
} catch ( err ) {
113
- return /** @type {SocketSdkErrorType<'getIssuesByNPMPackage'> } */ ( handleApiError ( err ) )
146
+ return /** @type {SocketSdkErrorType<'getIssuesByNPMPackage'> } */ ( this . # handleApiError( err ) )
114
147
}
115
148
}
116
149
117
150
/** @returns {Promise<SocketSdkResultType<'getReportList'>> } */
118
151
async getReportList ( ) {
119
152
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 ( )
121
155
return { success : true , status : 200 , data }
122
156
} catch ( err ) {
123
- return /** @type {SocketSdkErrorType<'getReportList'> } */ ( handleApiError ( err ) )
157
+ return /** @type {SocketSdkErrorType<'getReportList'> } */ ( this . # handleApiError( err ) )
124
158
}
125
159
}
126
160
@@ -132,20 +166,75 @@ export class SocketSdk {
132
166
const idParam = encodeURIComponent ( id )
133
167
134
168
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 ( )
136
171
return { success : true , status : 200 , data }
137
172
} catch ( err ) {
138
- return /** @type {SocketSdkErrorType<'getReport'> } */ ( handleApiError ( err ) )
173
+ return /** @type {SocketSdkErrorType<'getReport'> } */ ( this . # handleApiError( err ) )
139
174
}
140
175
}
141
176
142
177
/** @returns {Promise<SocketSdkResultType<'getQuota'>> } */
143
178
async getQuota ( ) {
144
179
try {
145
- const data = await this . #client. get ( 'quota' ) . json ( )
180
+ const client = await this . #getClient( )
181
+ const data = await client . get ( 'quota' ) . json ( )
146
182
return { success : true , status : 200 , data }
147
183
} catch ( err ) {
148
- return /** @type {SocketSdkErrorType<'getQuota'> } */ ( handleApiError ( err ) )
184
+ return /** @type {SocketSdkErrorType<'getQuota'> } */ ( this . # handleApiError( err ) )
149
185
}
150
186
}
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 ) )
151
238
}
239
+
240
+ module . exports = { SocketSdk }
0 commit comments