-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathsdk.mts
More file actions
263 lines (233 loc) · 7.97 KB
/
sdk.mts
File metadata and controls
263 lines (233 loc) · 7.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/**
* Socket SDK utilities for Socket CLI.
* Manages SDK initialization and configuration for API communication.
*
* Authentication:
* - Interactive password prompt for missing tokens
* - Supports environment variable (SOCKET_CLI_API_TOKEN)
* - Validates token format and presence
*
* Proxy Support:
* - Automatic proxy agent selection
* - HTTP/HTTPS proxy configuration
* - Respects SOCKET_CLI_API_PROXY environment variable
*
* SDK Setup:
* - createSocketSdk: Create configured SDK instance
* - getDefaultApiToken: Retrieve API token from config/env
* - getDefaultProxyUrl: Retrieve proxy URL from config/env
* - getPublicApiToken: Get public API token constant
* - setupSdk: Initialize Socket SDK with authentication
*
* User Agent:
* - Automatic user agent generation from package.json
* - Includes CLI version and platform information
*/
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
import isInteractive from '@socketregistry/is-interactive/index.cjs'
import { SOCKET_PUBLIC_API_TOKEN } from '@socketsecurity/lib/constants/socket'
import { getDefaultLogger } from '@socketsecurity/lib/logger'
import { password } from '@socketsecurity/lib/stdio/prompts'
import { isNonEmptyString } from '@socketsecurity/lib/strings'
import { isUrl } from '@socketsecurity/lib/url'
import { pluralize } from '@socketsecurity/lib/words'
import { createUserAgentFromPkgJson, SocketSdk } from '@socketsecurity/sdk'
import {
CONFIG_KEY_API_BASE_URL,
CONFIG_KEY_API_PROXY,
CONFIG_KEY_API_TOKEN,
} from '../../constants/config.mts'
import ENV from '../../constants/env.mts'
import { TOKEN_PREFIX_LENGTH } from '../../constants/socket.mts'
import { getConfigValueOrUndef } from '../config.mts'
import { debugApiRequest, debugApiResponse } from '../debug.mts'
import { trackCliEvent } from '../telemetry/integration.mts'
import type { CResult } from '../../types.mts'
import type {
FileValidationResult,
RequestInfo,
ResponseInfo,
} from '@socketsecurity/sdk'
const logger = getDefaultLogger()
const TOKEN_VISIBLE_LENGTH = 5
// The Socket API server that should be used for operations.
export function getDefaultApiBaseUrl(): string | undefined {
const baseUrl =
ENV.SOCKET_CLI_API_BASE_URL ||
getConfigValueOrUndef(CONFIG_KEY_API_BASE_URL) ||
undefined
return isUrl(baseUrl) ? baseUrl : undefined
}
// The Socket API server that should be used for operations.
export function getDefaultProxyUrl(): string | undefined {
const apiProxy =
ENV.SOCKET_CLI_API_PROXY ||
getConfigValueOrUndef(CONFIG_KEY_API_PROXY) ||
undefined
return isUrl(apiProxy) ? apiProxy : undefined
}
// This Socket API token should be stored globally for the duration of the CLI execution.
let _defaultToken: string | undefined
export function getDefaultApiToken(): string | undefined {
if (ENV.SOCKET_CLI_NO_API_TOKEN) {
_defaultToken = undefined
return _defaultToken
}
const key =
ENV.SOCKET_CLI_API_TOKEN ||
getConfigValueOrUndef(CONFIG_KEY_API_TOKEN) ||
_defaultToken
_defaultToken = isNonEmptyString(key) ? key : undefined
return _defaultToken
}
export function getPublicApiToken(): string {
return (
getDefaultApiToken() || ENV.SOCKET_CLI_API_TOKEN || SOCKET_PUBLIC_API_TOKEN
)
}
export function getVisibleTokenPrefix(): string {
const apiToken = getDefaultApiToken()
return apiToken
? apiToken.slice(
TOKEN_PREFIX_LENGTH,
TOKEN_PREFIX_LENGTH + TOKEN_VISIBLE_LENGTH,
)
: ''
}
export function hasDefaultApiToken(): boolean {
return !!getDefaultApiToken()
}
export type SetupSdkOptions = {
apiBaseUrl?: string | undefined
apiProxy?: string | undefined
apiToken?: string | undefined
}
export async function setupSdk(
options?: SetupSdkOptions | undefined,
): Promise<CResult<SocketSdk>> {
const opts = { __proto__: null, ...options } as SetupSdkOptions
let { apiToken = getDefaultApiToken() } = opts
if (typeof apiToken !== 'string' && isInteractive()) {
apiToken = await password({
message:
'Enter your Socket.dev API token (not saved, use socket login to persist)',
})
_defaultToken = apiToken
}
if (!apiToken) {
return {
ok: false,
message: 'Auth Error',
cause: 'You need to provide an API token. Run `socket login` first.',
}
}
let { apiProxy } = opts
if (!isUrl(apiProxy)) {
apiProxy = getDefaultProxyUrl()
}
const { apiBaseUrl = getDefaultApiBaseUrl() } = opts
// Usage of HttpProxyAgent vs. HttpsProxyAgent based on the chart at:
// https://github.com/delvedor/hpagent?tab=readme-ov-file#usage
const ProxyAgent = apiBaseUrl?.startsWith('http:')
? HttpProxyAgent
: HttpsProxyAgent
const timeout = ENV.SOCKET_CLI_API_TIMEOUT || undefined
const sdkOptions = {
...(apiProxy ? { agent: new ProxyAgent({ proxy: apiProxy }) } : {}),
...(apiBaseUrl ? { baseUrl: apiBaseUrl } : {}),
...(timeout ? { timeout } : {}),
// Add HTTP request hooks for telemetry and debugging.
hooks: {
onRequest: (info: RequestInfo) => {
// Skip tracking for telemetry submission endpoints to prevent infinite loop.
const isTelemetryEndpoint =
info.url.includes('/telemetry') || info.url.includes('/events')
if (ENV.SOCKET_CLI_DEBUG) {
// Debug logging.
debugApiRequest(info.method, info.url, info.timeout)
}
if (!isTelemetryEndpoint) {
// Track API request event.
void trackCliEvent('api_request', process.argv, {
method: info.method,
timeout: info.timeout,
url: info.url,
})
}
},
onResponse: (info: ResponseInfo) => {
// Skip tracking for telemetry submission endpoints to prevent infinite loop.
const isTelemetryEndpoint =
info.url.includes('/telemetry') || info.url.includes('/events')
if (!isTelemetryEndpoint) {
// Track API response event.
const metadata = {
duration: info.duration,
method: info.method,
status: info.status,
statusText: info.statusText,
url: info.url,
}
if (info.error) {
// Track as error event if request failed.
void trackCliEvent('api_error', process.argv, {
...metadata,
error_message: info.error.message,
error_type: info.error.constructor.name,
})
} else {
// Track as successful response.
void trackCliEvent('api_response', process.argv, metadata)
}
}
if (ENV.SOCKET_CLI_DEBUG) {
// Debug logging.
debugApiResponse(info.url, info.status, info.error, {
method: info.method,
url: info.url,
durationMs: info.duration,
headers: info.headers,
})
}
},
},
onFileValidation: (
_validPaths: string[],
invalidPaths: string[],
_context: {
operation:
| 'createDependenciesSnapshot'
| 'createFullScan'
| 'uploadManifestFiles'
orgSlug?: string | undefined
[key: string]: unknown
},
): FileValidationResult => {
if (invalidPaths.length > 0) {
logger.warn(
`Skipped ${invalidPaths.length} ${pluralize('file', { count: invalidPaths.length })} that could not be read`,
)
logger.substep(
'This may occur with Yarn Berry PnP virtual filesystem or pnpm symlinks',
)
}
// Continue with valid files.
return { shouldContinue: true }
},
userAgent: createUserAgentFromPkgJson({
name: ENV.INLINED_SOCKET_CLI_NAME || 'socket',
version: ENV.INLINED_SOCKET_CLI_VERSION || '0.0.0',
homepage: ENV.INLINED_SOCKET_CLI_HOMEPAGE || 'https://socket.dev/cli',
}),
}
if (ENV.SOCKET_CLI_DEBUG) {
logger.info(
`[DEBUG] ${new Date().toISOString()} SDK options: ${JSON.stringify(sdkOptions)}`,
)
}
const sdk = new SocketSdk(apiToken, sdkOptions)
return {
ok: true,
data: sdk,
}
}