@slates/provider 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2885 -2
- package/dist/index.d.cts +1019 -0
- package/dist/index.d.ts +1019 -9
- package/dist/index.module.js +2775 -2
- package/package.json +9 -6
- package/src/action/action.ts +92 -4
- package/src/action/attachment.ts +87 -0
- package/src/action/builder.test.ts +136 -0
- package/src/action/builder.ts +113 -15
- package/src/action/directUpload.test.ts +70 -0
- package/src/action/directUpload.ts +105 -0
- package/src/action/getFileUrlTool.ts +41 -0
- package/src/action/index.ts +4 -2
- package/src/action/publicTool.ts +36 -0
- package/src/action/scopes.test.ts +76 -0
- package/src/action/scopes.ts +103 -0
- package/src/action/tool.ts +22 -6
- package/src/action/trigger.ts +19 -11
- package/src/adapter/adapter.ts +38 -0
- package/src/adapter/index.test.ts +322 -0
- package/src/adapter/index.ts +2 -0
- package/src/adapter/spec.ts +154 -0
- package/src/auth/auth.ts +3 -3
- package/src/auth/index.ts +2 -0
- package/src/auth/oauth.test.ts +83 -0
- package/src/auth/oauth.ts +150 -0
- package/src/auth/redact.test.ts +103 -0
- package/src/auth/redact.ts +111 -0
- package/src/auth/types.ts +72 -13
- package/src/axios/http.test.ts +83 -0
- package/src/axios/index.ts +116 -10
- package/src/axios/trace.test.ts +377 -0
- package/src/axios/trace.ts +803 -0
- package/src/config/config.ts +14 -3
- package/src/context/context.ts +264 -17
- package/src/context/hook.test.ts +46 -0
- package/src/context/hook.ts +15 -6
- package/src/context/index.ts +1 -0
- package/src/context/pQueue.ts +4 -0
- package/src/crypto/hmac.test.ts +40 -0
- package/src/crypto/hmac.ts +32 -0
- package/src/crypto/index.ts +1 -0
- package/src/docs.ts +22 -0
- package/src/error/api.test.ts +114 -0
- package/src/error/api.ts +236 -0
- package/src/error/axios.ts +220 -0
- package/src/error/base.test.ts +178 -0
- package/src/error/base.ts +108 -2
- package/src/error/declaration.ts +7 -2
- package/src/error/defaults.ts +50 -0
- package/src/error/index.ts +4 -0
- package/src/error/service.ts +170 -0
- package/src/error/types.ts +75 -0
- package/src/error/utils.ts +157 -0
- package/src/file/index.test.ts +36 -0
- package/src/file/index.ts +27 -0
- package/src/index.ts +8 -0
- package/src/logger/logger.test.ts +57 -0
- package/src/logger/logger.ts +72 -11
- package/src/module/resolveDefaultExport.test.ts +32 -0
- package/src/module/resolveDefaultExport.ts +14 -0
- package/src/object/index.test.ts +38 -0
- package/src/object/index.ts +12 -0
- package/src/specification/slate.ts +40 -4
- package/src/specification/specification.ts +10 -6
- package/src/webhook/graph.test.ts +37 -0
- package/src/webhook/graph.ts +22 -0
- package/src/webhook/index.ts +2 -0
- package/src/webhook/meta.test.ts +52 -0
- package/src/webhook/meta.ts +43 -0
- package/dist/action/action.d.ts +0 -98
- package/dist/action/action.d.ts.map +0 -1
- package/dist/action/builder.d.ts +0 -27
- package/dist/action/builder.d.ts.map +0 -1
- package/dist/action/index.d.ts +0 -5
- package/dist/action/index.d.ts.map +0 -1
- package/dist/action/tool.d.ts +0 -14
- package/dist/action/tool.d.ts.map +0 -1
- package/dist/action/trigger.d.ts +0 -32
- package/dist/action/trigger.d.ts.map +0 -1
- package/dist/auth/auth.d.ts +0 -30
- package/dist/auth/auth.d.ts.map +0 -1
- package/dist/auth/index.d.ts +0 -3
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/types.d.ts +0 -148
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/axios/index.d.ts +0 -4
- package/dist/axios/index.d.ts.map +0 -1
- package/dist/config/config.d.ts +0 -26
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +0 -1
- package/dist/context/context.d.ts +0 -20
- package/dist/context/context.d.ts.map +0 -1
- package/dist/context/hook.d.ts +0 -4
- package/dist/context/hook.d.ts.map +0 -1
- package/dist/context/index.d.ts +0 -2
- package/dist/context/index.d.ts.map +0 -1
- package/dist/error/base.d.ts +0 -5
- package/dist/error/base.d.ts.map +0 -1
- package/dist/error/declaration.d.ts +0 -6
- package/dist/error/declaration.d.ts.map +0 -1
- package/dist/error/index.d.ts +0 -3
- package/dist/error/index.d.ts.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.modern.js +0 -2
- package/dist/index.modern.js.map +0 -1
- package/dist/index.module.js.map +0 -1
- package/dist/index.umd.js +0 -2
- package/dist/index.umd.js.map +0 -1
- package/dist/logger/index.d.ts +0 -2
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/logger/logger.d.ts +0 -20
- package/dist/logger/logger.d.ts.map +0 -1
- package/dist/specification/index.d.ts +0 -3
- package/dist/specification/index.d.ts.map +0 -1
- package/dist/specification/slate.d.ts +0 -15
- package/dist/specification/slate.d.ts.map +0 -1
- package/dist/specification/specification.d.ts +0 -30
- package/dist/specification/specification.d.ts.map +0 -1
- package/dist/specification/zero.d.ts +0 -13
- package/dist/specification/zero.d.ts.map +0 -1
- package/dist/tokens.d.ts +0 -34
- package/dist/tokens.d.ts.map +0 -1
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
import { AuthConfigSecretRedactor } from '../auth/redact';
|
|
3
|
+
import type { SlatePublicContext } from '../context';
|
|
4
|
+
|
|
5
|
+
export interface SlateHttpTraceTextBody {
|
|
6
|
+
contentType?: string;
|
|
7
|
+
text: string;
|
|
8
|
+
truncated?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SlateHttpTrace {
|
|
12
|
+
startedAt: string;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
request: {
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
body?: SlateHttpTraceTextBody;
|
|
19
|
+
};
|
|
20
|
+
response?: {
|
|
21
|
+
status: number;
|
|
22
|
+
statusText?: string;
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
body?: SlateHttpTraceTextBody;
|
|
25
|
+
};
|
|
26
|
+
error?: {
|
|
27
|
+
code?: string;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface SlateHttpTraceDraft {
|
|
33
|
+
context: SlatePublicContext<any>;
|
|
34
|
+
startedAt: string;
|
|
35
|
+
startedAtMs: number;
|
|
36
|
+
request: SlateHttpTrace['request'];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type TraceAwareAxiosRequestConfig = InternalAxiosRequestConfig & {
|
|
40
|
+
__slatesHttpTraceDraft?: SlateHttpTraceDraft;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let traceDraftsByConfig = new WeakMap<object, SlateHttpTraceDraft>();
|
|
44
|
+
let traceDraftsByHeaders = new WeakMap<object, SlateHttpTraceDraft>();
|
|
45
|
+
|
|
46
|
+
let TRACE_TEXT_LIMIT = 10 * 1024;
|
|
47
|
+
let REDACTED_VALUE = '[redacted]';
|
|
48
|
+
let STRUCTURED_DEPTH_LIMIT = 6;
|
|
49
|
+
let STRUCTURED_ENTRY_LIMIT = 50;
|
|
50
|
+
|
|
51
|
+
// Atoms that identify a header/field/param name as containing a secret.
|
|
52
|
+
// These are matched with token boundaries so `sig` flags `x-sig-key` but NOT `configure`,
|
|
53
|
+
// and `code` only flags `code`/`auth_code`/etc., never `encode`/`decoder`.
|
|
54
|
+
// `[-_.]?` allows compound atoms to match either with or without a separator
|
|
55
|
+
// (e.g. `apikey`, `api-key`, `api_key`, `api.key`).
|
|
56
|
+
let CORE_SECRET_KEY_ATOMS = [
|
|
57
|
+
// HTTP authentication
|
|
58
|
+
'authorization',
|
|
59
|
+
'proxy[-_.]?authorization',
|
|
60
|
+
'www[-_.]?authenticate',
|
|
61
|
+
'bearer',
|
|
62
|
+
|
|
63
|
+
// Generic tokens
|
|
64
|
+
'token',
|
|
65
|
+
'tokens',
|
|
66
|
+
'access[-_.]?token',
|
|
67
|
+
'refresh[-_.]?token',
|
|
68
|
+
'id[-_.]?token',
|
|
69
|
+
'auth[-_.]?token',
|
|
70
|
+
'bearer[-_.]?token',
|
|
71
|
+
'session[-_.]?token',
|
|
72
|
+
'api[-_.]?token',
|
|
73
|
+
'app[-_.]?token',
|
|
74
|
+
'user[-_.]?token',
|
|
75
|
+
'oauth[-_.]?token',
|
|
76
|
+
'personal[-_.]?access[-_.]?token',
|
|
77
|
+
'x[-_.]?auth[-_.]?token',
|
|
78
|
+
'x[-_.]?access[-_.]?token',
|
|
79
|
+
'x[-_.]?refresh[-_.]?token',
|
|
80
|
+
|
|
81
|
+
// API keys
|
|
82
|
+
'apikey',
|
|
83
|
+
'api[-_.]?key',
|
|
84
|
+
'app[-_.]?key',
|
|
85
|
+
'x[-_.]?api[-_.]?key',
|
|
86
|
+
'x[-_.]?app[-_.]?key',
|
|
87
|
+
|
|
88
|
+
// OAuth client credentials
|
|
89
|
+
'client[-_.]?secret',
|
|
90
|
+
'client[-_.]?key',
|
|
91
|
+
'consumer[-_.]?secret',
|
|
92
|
+
'consumer[-_.]?key',
|
|
93
|
+
|
|
94
|
+
// Cryptographic keys
|
|
95
|
+
'private[-_.]?key',
|
|
96
|
+
'privatekey',
|
|
97
|
+
'secret[-_.]?key',
|
|
98
|
+
'secretkey',
|
|
99
|
+
'secret[-_.]?access[-_.]?key',
|
|
100
|
+
'encryption[-_.]?key',
|
|
101
|
+
'decryption[-_.]?key',
|
|
102
|
+
'signing[-_.]?key',
|
|
103
|
+
'signing[-_.]?secret',
|
|
104
|
+
'master[-_.]?key',
|
|
105
|
+
'master[-_.]?secret',
|
|
106
|
+
'master[-_.]?password',
|
|
107
|
+
'shared[-_.]?secret',
|
|
108
|
+
'webhook[-_.]?secret',
|
|
109
|
+
'webhook[-_.]?signing[-_.]?secret',
|
|
110
|
+
|
|
111
|
+
// OAuth codes & PKCE
|
|
112
|
+
'auth[-_.]?code',
|
|
113
|
+
'authcode',
|
|
114
|
+
'authorization[-_.]?code',
|
|
115
|
+
'code[-_.]?verifier',
|
|
116
|
+
'code[-_.]?challenge',
|
|
117
|
+
|
|
118
|
+
// Passwords & passcodes
|
|
119
|
+
'password',
|
|
120
|
+
'passwd',
|
|
121
|
+
'pwd',
|
|
122
|
+
'passphrase',
|
|
123
|
+
'pass[-_.]?phrase',
|
|
124
|
+
'pin',
|
|
125
|
+
'passcode',
|
|
126
|
+
|
|
127
|
+
// Generic secret markers
|
|
128
|
+
'secret',
|
|
129
|
+
'secrets',
|
|
130
|
+
'credential',
|
|
131
|
+
'credentials',
|
|
132
|
+
'creds',
|
|
133
|
+
'auth',
|
|
134
|
+
'x[-_.]?auth',
|
|
135
|
+
'x[-_.]?auth[-_.]?key',
|
|
136
|
+
|
|
137
|
+
// CSRF / XSRF
|
|
138
|
+
'csrf',
|
|
139
|
+
'xsrf',
|
|
140
|
+
'csrf[-_.]?token',
|
|
141
|
+
'xsrf[-_.]?token',
|
|
142
|
+
'x[-_.]?csrf[-_.]?token',
|
|
143
|
+
'x[-_.]?xsrf[-_.]?token',
|
|
144
|
+
|
|
145
|
+
// JWT
|
|
146
|
+
'jwt',
|
|
147
|
+
|
|
148
|
+
// Sessions & cookies
|
|
149
|
+
'session[-_.]?id',
|
|
150
|
+
'sessionid',
|
|
151
|
+
'sid',
|
|
152
|
+
'cookie',
|
|
153
|
+
'cookies',
|
|
154
|
+
'set[-_.]?cookie',
|
|
155
|
+
|
|
156
|
+
// Signatures / MAC
|
|
157
|
+
'signature',
|
|
158
|
+
'x[-_.]?hub[-_.]?signature',
|
|
159
|
+
'x[-_.]?signature',
|
|
160
|
+
'sig',
|
|
161
|
+
'hmac',
|
|
162
|
+
'mac',
|
|
163
|
+
|
|
164
|
+
// Crypto artifacts
|
|
165
|
+
'salt',
|
|
166
|
+
'nonce',
|
|
167
|
+
|
|
168
|
+
// Multi-factor / verification
|
|
169
|
+
'otp',
|
|
170
|
+
'totp',
|
|
171
|
+
'mfa',
|
|
172
|
+
'mfa[-_.]?code',
|
|
173
|
+
'2fa',
|
|
174
|
+
'two[-_.]?factor',
|
|
175
|
+
'twofactor',
|
|
176
|
+
'verification[-_.]?code',
|
|
177
|
+
'verify[-_.]?code',
|
|
178
|
+
'confirm(?:ation)?[-_.]?code',
|
|
179
|
+
'sms[-_.]?code',
|
|
180
|
+
'email[-_.]?code',
|
|
181
|
+
|
|
182
|
+
// Recovery & seed phrases
|
|
183
|
+
'seed[-_.]?phrase',
|
|
184
|
+
'mnemonic',
|
|
185
|
+
'recovery[-_.]?key',
|
|
186
|
+
'recovery[-_.]?code',
|
|
187
|
+
'recovery[-_.]?phrase',
|
|
188
|
+
|
|
189
|
+
// PII / financial
|
|
190
|
+
'card[-_.]?number',
|
|
191
|
+
'card[-_.]?code',
|
|
192
|
+
'cvv',
|
|
193
|
+
'cvc',
|
|
194
|
+
'ccv',
|
|
195
|
+
'cc[-_.]?num(?:ber)?',
|
|
196
|
+
'pan',
|
|
197
|
+
'ssn',
|
|
198
|
+
'social[-_.]?security',
|
|
199
|
+
'license[-_.]?key'
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
// Extra atoms that are considered secret-like specifically in URL query strings
|
|
203
|
+
// and x-www-form-urlencoded bodies, where a lone `code` typically carries an OAuth
|
|
204
|
+
// authorization code. We keep `code` out of CORE to avoid redacting `{"code": 404}`
|
|
205
|
+
// in structured JSON responses.
|
|
206
|
+
let URL_FORM_SECRET_KEY_ATOMS = [...CORE_SECRET_KEY_ATOMS, 'code'];
|
|
207
|
+
|
|
208
|
+
let buildKeyBoundaryPattern = (atoms: string[], flags: string) =>
|
|
209
|
+
new RegExp(`(?:^|[^A-Za-z0-9])(?:${atoms.join('|')})(?=[^A-Za-z0-9]|$)`, flags);
|
|
210
|
+
|
|
211
|
+
let SECRET_KEY_PATTERN = buildKeyBoundaryPattern(CORE_SECRET_KEY_ATOMS, 'i');
|
|
212
|
+
let URL_FORM_SECRET_KEY_PATTERN = buildKeyBoundaryPattern(URL_FORM_SECRET_KEY_ATOMS, 'i');
|
|
213
|
+
|
|
214
|
+
// Explicit non-secret keys that would otherwise trip the patterns above because
|
|
215
|
+
// they embed a secret-sounding token (e.g. `token_type` contains `token`).
|
|
216
|
+
// Keys here describe the *kind* of a secret, not the secret itself.
|
|
217
|
+
let NON_SECRET_KEY_EXACT = new Set<string>([
|
|
218
|
+
'token_type',
|
|
219
|
+
'token-type',
|
|
220
|
+
'tokentype',
|
|
221
|
+
'token_kind',
|
|
222
|
+
'token-kind',
|
|
223
|
+
'tokenkind',
|
|
224
|
+
'token_ttl',
|
|
225
|
+
'token-ttl',
|
|
226
|
+
'token_expires_in',
|
|
227
|
+
'token-expires-in',
|
|
228
|
+
'token_expiry',
|
|
229
|
+
'signature_method',
|
|
230
|
+
'signature-method',
|
|
231
|
+
'signature_type',
|
|
232
|
+
'signature-type',
|
|
233
|
+
'signature_version',
|
|
234
|
+
'signature-version',
|
|
235
|
+
'auth_type',
|
|
236
|
+
'auth-type',
|
|
237
|
+
'auth_method',
|
|
238
|
+
'auth-method',
|
|
239
|
+
'authentication_type',
|
|
240
|
+
'authentication-type',
|
|
241
|
+
'authentication_method',
|
|
242
|
+
'authentication-method',
|
|
243
|
+
'grant_type',
|
|
244
|
+
'grant-type',
|
|
245
|
+
'password_strength',
|
|
246
|
+
'password-strength',
|
|
247
|
+
'password_policy',
|
|
248
|
+
'password-policy',
|
|
249
|
+
'key_type',
|
|
250
|
+
'key-type',
|
|
251
|
+
'key_name',
|
|
252
|
+
'key-name',
|
|
253
|
+
'key_id',
|
|
254
|
+
'key-id',
|
|
255
|
+
'kid'
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
let isSecretKeyName = (name: string): boolean => {
|
|
259
|
+
let normalized = name.toLowerCase();
|
|
260
|
+
if (NON_SECRET_KEY_EXACT.has(normalized)) return false;
|
|
261
|
+
return SECRET_KEY_PATTERN.test(normalized);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let isUrlFormSecretKeyName = (name: string): boolean => {
|
|
265
|
+
let normalized = name.toLowerCase();
|
|
266
|
+
if (NON_SECRET_KEY_EXACT.has(normalized)) return false;
|
|
267
|
+
return URL_FORM_SECRET_KEY_PATTERN.test(normalized);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// Patterns that identify well-known secret values regardless of the surrounding key.
|
|
271
|
+
// Keeps low false-positive risk by matching formats that are unlikely to occur otherwise.
|
|
272
|
+
let KNOWN_SECRET_VALUE_PATTERNS: RegExp[] = [
|
|
273
|
+
// JSON Web Tokens (header.payload.signature)
|
|
274
|
+
/\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\b/g,
|
|
275
|
+
// AWS access key IDs
|
|
276
|
+
/\b(?:AKIA|ASIA|AGPA|AIDA|ANPA|ANVA|AROA|APKA|ASCA)[A-Z0-9]{16}\b/g,
|
|
277
|
+
// GitHub tokens (PAT / OAuth / server-to-server / user-to-server / refresh)
|
|
278
|
+
/\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
|
|
279
|
+
// Slack tokens
|
|
280
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
281
|
+
// Stripe secret / public / restricted keys
|
|
282
|
+
/\b(?:sk|rk|pk)_(?:test|live)_[A-Za-z0-9]{16,}\b/g,
|
|
283
|
+
// OpenAI API keys
|
|
284
|
+
/\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
285
|
+
// Anthropic API keys
|
|
286
|
+
/\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
|
|
287
|
+
// Google API keys
|
|
288
|
+
/\bAIza[A-Za-z0-9_-]{35}\b/g,
|
|
289
|
+
// SendGrid API keys
|
|
290
|
+
/\bSG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
|
|
291
|
+
// Twilio API keys
|
|
292
|
+
/\bSK[0-9a-fA-F]{32}\b/g,
|
|
293
|
+
// PEM-encoded private keys (multi-line)
|
|
294
|
+
/-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g,
|
|
295
|
+
// SSH private keys (OpenSSH format)
|
|
296
|
+
/-----BEGIN OPENSSH PRIVATE KEY-----[\s\S]*?-----END OPENSSH PRIVATE KEY-----/g
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
// HTTP authentication schemes in free text, e.g. "Authorization: Bearer eyJ..."
|
|
300
|
+
let HTTP_AUTH_SCHEME_PATTERN =
|
|
301
|
+
/\b(bearer|basic|digest|token|negotiate|apikey)\s+([A-Za-z0-9_\-.~+/=]{4,})/gi;
|
|
302
|
+
|
|
303
|
+
// Generic `key=value` scan; the key is filtered via isUrlFormSecretKeyName at replace time
|
|
304
|
+
// so we can apply a deny-list for compound keys like `token_type` or `grant_type`.
|
|
305
|
+
let GENERIC_KEY_VALUE_PATTERN =
|
|
306
|
+
/(?<![A-Za-z0-9])([A-Za-z0-9][A-Za-z0-9_\-.]*)(\s*=\s*)([^&#\s;]+)/g;
|
|
307
|
+
|
|
308
|
+
// Generic `"key": "value"` scan; the key is filtered via isSecretKeyName at replace time.
|
|
309
|
+
let GENERIC_JSON_KEY_VALUE_PATTERN =
|
|
310
|
+
/"([^"\\]*(?:\\.[^"\\]*)*)"(\s*:\s*)"([^"\\]*(?:\\.[^"\\]*)*)"/g;
|
|
311
|
+
|
|
312
|
+
let SAFE_HEADER_NAMES = new Set([
|
|
313
|
+
'accept',
|
|
314
|
+
'accept-encoding',
|
|
315
|
+
'accept-language',
|
|
316
|
+
'cache-control',
|
|
317
|
+
'connection',
|
|
318
|
+
'content-encoding',
|
|
319
|
+
'content-language',
|
|
320
|
+
'content-length',
|
|
321
|
+
'content-type',
|
|
322
|
+
'date',
|
|
323
|
+
'etag',
|
|
324
|
+
'expires',
|
|
325
|
+
'if-modified-since',
|
|
326
|
+
'if-none-match',
|
|
327
|
+
'keep-alive',
|
|
328
|
+
'last-modified',
|
|
329
|
+
'location',
|
|
330
|
+
'origin',
|
|
331
|
+
'pragma',
|
|
332
|
+
'referer',
|
|
333
|
+
'referrer-policy',
|
|
334
|
+
'request-id',
|
|
335
|
+
'retry-after',
|
|
336
|
+
'server',
|
|
337
|
+
'transfer-encoding',
|
|
338
|
+
'user-agent',
|
|
339
|
+
'vary',
|
|
340
|
+
'via',
|
|
341
|
+
'x-correlation-id',
|
|
342
|
+
'x-powered-by',
|
|
343
|
+
'x-request-id',
|
|
344
|
+
'x-slates-provider',
|
|
345
|
+
'x-trace-id'
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
let SAFE_HEADER_PREFIXES = ['ratelimit-', 'x-ratelimit-'];
|
|
349
|
+
|
|
350
|
+
let isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
351
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
352
|
+
|
|
353
|
+
let isObjectLike = (value: unknown): value is object =>
|
|
354
|
+
typeof value === 'object' && value !== null;
|
|
355
|
+
|
|
356
|
+
let normalizeContentType = (value: string | undefined) =>
|
|
357
|
+
value?.split(';')[0]?.trim().toLowerCase() || undefined;
|
|
358
|
+
|
|
359
|
+
let isTextContentType = (contentType?: string) =>
|
|
360
|
+
!contentType ||
|
|
361
|
+
/(^text\/)|json|xml|html|javascript|graphql|x-www-form-urlencoded|svg/.test(contentType);
|
|
362
|
+
|
|
363
|
+
let isSafeHeaderName = (name: string) => {
|
|
364
|
+
let normalized = name.toLowerCase();
|
|
365
|
+
if (isSecretKeyName(normalized)) return false;
|
|
366
|
+
if (SAFE_HEADER_NAMES.has(normalized)) return true;
|
|
367
|
+
return SAFE_HEADER_PREFIXES.some(prefix => normalized.startsWith(prefix));
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
let truncateText = (text: string) => {
|
|
371
|
+
if (text.length <= TRACE_TEXT_LIMIT) {
|
|
372
|
+
return { text, truncated: false };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
text: `${text.slice(0, TRACE_TEXT_LIMIT)}...[truncated]`,
|
|
377
|
+
truncated: true
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
let sanitizeFreeText = (text: string, redactor?: AuthConfigSecretRedactor) => {
|
|
382
|
+
if (!text) return text;
|
|
383
|
+
|
|
384
|
+
// Literal auth-config secret values are replaced first -- this catches secrets sitting
|
|
385
|
+
// under a key name the heuristics below don't recognize as secret-like.
|
|
386
|
+
let result = redactor ? redactor.redact(text) : text;
|
|
387
|
+
|
|
388
|
+
result = result.replace(
|
|
389
|
+
HTTP_AUTH_SCHEME_PATTERN,
|
|
390
|
+
(_match, scheme: string) => `${scheme} ${REDACTED_VALUE}`
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
for (let pattern of KNOWN_SECRET_VALUE_PATTERNS) {
|
|
394
|
+
result = result.replace(pattern, REDACTED_VALUE);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
result = result.replace(
|
|
398
|
+
GENERIC_KEY_VALUE_PATTERN,
|
|
399
|
+
(match, key: string, equals: string, _value: string) =>
|
|
400
|
+
isUrlFormSecretKeyName(key) ? `${key}${equals}${REDACTED_VALUE}` : match
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
result = result.replace(
|
|
404
|
+
GENERIC_JSON_KEY_VALUE_PATTERN,
|
|
405
|
+
(match, key: string, colon: string, _value: string) =>
|
|
406
|
+
isSecretKeyName(key) ? `"${key}"${colon}"${REDACTED_VALUE}"` : match
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
return result;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
let sanitizeScalar = (value: string, redactor?: AuthConfigSecretRedactor) =>
|
|
413
|
+
truncateText(sanitizeFreeText(value, redactor));
|
|
414
|
+
|
|
415
|
+
let redactStructuredValue = (
|
|
416
|
+
value: unknown,
|
|
417
|
+
depth = 0,
|
|
418
|
+
redactor?: AuthConfigSecretRedactor
|
|
419
|
+
): unknown => {
|
|
420
|
+
if (value == null) return value;
|
|
421
|
+
if (depth >= STRUCTURED_DEPTH_LIMIT) return '[truncated]';
|
|
422
|
+
|
|
423
|
+
if (
|
|
424
|
+
typeof value === 'string' ||
|
|
425
|
+
typeof value === 'number' ||
|
|
426
|
+
typeof value === 'boolean' ||
|
|
427
|
+
typeof value === 'bigint'
|
|
428
|
+
) {
|
|
429
|
+
return typeof value === 'string' ? sanitizeFreeText(value, redactor) : value;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (Array.isArray(value)) {
|
|
433
|
+
return value
|
|
434
|
+
.slice(0, STRUCTURED_ENTRY_LIMIT)
|
|
435
|
+
.map(entry => redactStructuredValue(entry, depth + 1, redactor));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (isRecord(value)) {
|
|
439
|
+
return Object.fromEntries(
|
|
440
|
+
Object.entries(value)
|
|
441
|
+
.slice(0, STRUCTURED_ENTRY_LIMIT)
|
|
442
|
+
.map(([key, entry]) => [
|
|
443
|
+
key,
|
|
444
|
+
isSecretKeyName(key)
|
|
445
|
+
? REDACTED_VALUE
|
|
446
|
+
: redactStructuredValue(entry, depth + 1, redactor)
|
|
447
|
+
])
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return String(value);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
let maybeParseJson = (text: string, contentType?: string) => {
|
|
455
|
+
let trimmed = text.trim();
|
|
456
|
+
if (!trimmed) return null;
|
|
457
|
+
if (!contentType?.includes('json') && !/^[[{]/.test(trimmed)) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
return JSON.parse(trimmed);
|
|
463
|
+
} catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
let sanitizeTextBody = (
|
|
469
|
+
text: string,
|
|
470
|
+
contentType?: string,
|
|
471
|
+
redactor?: AuthConfigSecretRedactor
|
|
472
|
+
): SlateHttpTraceTextBody | undefined => {
|
|
473
|
+
if (!text) return undefined;
|
|
474
|
+
|
|
475
|
+
let normalizedContentType = normalizeContentType(contentType);
|
|
476
|
+
let parsedJson = maybeParseJson(text, normalizedContentType);
|
|
477
|
+
let normalizedText =
|
|
478
|
+
parsedJson !== null
|
|
479
|
+
? JSON.stringify(redactStructuredValue(parsedJson, 0, redactor))
|
|
480
|
+
: sanitizeFreeText(text, redactor);
|
|
481
|
+
let { text: truncatedText, truncated } = truncateText(normalizedText);
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
...(normalizedContentType ? { contentType: normalizedContentType } : {}),
|
|
485
|
+
text: truncatedText,
|
|
486
|
+
...(truncated ? { truncated: true } : {})
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
let isBinaryLike = (value: unknown) => {
|
|
491
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return true;
|
|
492
|
+
if (value instanceof ArrayBuffer) return true;
|
|
493
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
494
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
|
|
495
|
+
if (typeof FormData !== 'undefined' && value instanceof FormData) return true;
|
|
496
|
+
return false;
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
let serializeTextBody = (
|
|
500
|
+
value: unknown,
|
|
501
|
+
contentType?: string,
|
|
502
|
+
redactor?: AuthConfigSecretRedactor
|
|
503
|
+
): SlateHttpTraceTextBody | undefined => {
|
|
504
|
+
let normalizedContentType = normalizeContentType(contentType);
|
|
505
|
+
|
|
506
|
+
if (value == null || isBinaryLike(value) || !isTextContentType(normalizedContentType)) {
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (typeof value === 'string') {
|
|
511
|
+
return sanitizeTextBody(value, normalizedContentType, redactor);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (value instanceof URLSearchParams) {
|
|
515
|
+
return sanitizeTextBody(value.toString(), normalizedContentType, redactor);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
519
|
+
return sanitizeTextBody(String(value), normalizedContentType, redactor);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (Array.isArray(value) || isRecord(value)) {
|
|
523
|
+
return sanitizeTextBody(
|
|
524
|
+
JSON.stringify(redactStructuredValue(value, 0, redactor)),
|
|
525
|
+
'application/json',
|
|
526
|
+
redactor
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return undefined;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
let buildUrl = (baseURL: string | undefined, url: string | undefined) => {
|
|
534
|
+
if (!url) return '';
|
|
535
|
+
if (!baseURL) return url;
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
return new URL(url, baseURL).toString();
|
|
539
|
+
} catch {
|
|
540
|
+
return url;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// URLSearchParams percent-encodes `[` and `]`, which makes `[redacted]` render as
|
|
545
|
+
// `%5Bredacted%5D`. Undo that specific encoding so traces stay readable.
|
|
546
|
+
let REDACTED_ENCODED = encodeURIComponent(REDACTED_VALUE);
|
|
547
|
+
let decodeRedactedMarker = (value: string) =>
|
|
548
|
+
REDACTED_ENCODED === REDACTED_VALUE
|
|
549
|
+
? value
|
|
550
|
+
: value.split(REDACTED_ENCODED).join(REDACTED_VALUE);
|
|
551
|
+
|
|
552
|
+
let redactHashFragment = (hash: string, redactor?: AuthConfigSecretRedactor) => {
|
|
553
|
+
if (!hash?.includes('=')) return hash;
|
|
554
|
+
let withoutHash = hash.startsWith('#') ? hash.slice(1) : hash;
|
|
555
|
+
let redacted = withoutHash.replace(
|
|
556
|
+
/(?<![A-Za-z0-9])([A-Za-z0-9][A-Za-z0-9_\-.]*)=([^&]*)/g,
|
|
557
|
+
(match, key: string, rawValue: string) => {
|
|
558
|
+
if (isUrlFormSecretKeyName(key)) return `${key}=${REDACTED_VALUE}`;
|
|
559
|
+
let literalRedacted = redactor ? redactor.redact(rawValue) : rawValue;
|
|
560
|
+
return literalRedacted !== rawValue ? `${key}=${literalRedacted}` : match;
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
return redacted ? `#${redacted}` : '';
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
let sanitizeUrl = (value: string, redactor?: AuthConfigSecretRedactor) => {
|
|
567
|
+
if (!value) return value;
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
let url = new URL(value);
|
|
571
|
+
|
|
572
|
+
if (url.username || url.password) {
|
|
573
|
+
url.username = '';
|
|
574
|
+
url.password = '';
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
for (let key of Array.from(url.searchParams.keys())) {
|
|
578
|
+
if (isUrlFormSecretKeyName(key)) {
|
|
579
|
+
url.searchParams.set(key, REDACTED_VALUE);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Not a secret-looking key -- still catch a literal auth-config secret value.
|
|
584
|
+
let rawValue = url.searchParams.get(key) ?? '';
|
|
585
|
+
let literalRedacted = redactor ? redactor.redact(rawValue) : rawValue;
|
|
586
|
+
if (literalRedacted !== rawValue) url.searchParams.set(key, literalRedacted);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
url.hash = redactHashFragment(url.hash, redactor);
|
|
590
|
+
|
|
591
|
+
return decodeRedactedMarker(url.toString());
|
|
592
|
+
} catch {
|
|
593
|
+
let text = redactor ? redactor.redact(value) : value;
|
|
594
|
+
return text.replace(
|
|
595
|
+
/([?&]([^=&#]+)=)([^&#]+)/g,
|
|
596
|
+
(_match, prefix: string, key: string, rawValue: string) =>
|
|
597
|
+
isUrlFormSecretKeyName(key) ? `${prefix}${REDACTED_VALUE}` : `${prefix}${rawValue}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
let flattenHeaderValue = (value: unknown): string | undefined => {
|
|
603
|
+
if (typeof value === 'string') return value;
|
|
604
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
605
|
+
if (Array.isArray(value)) {
|
|
606
|
+
let normalized = value
|
|
607
|
+
.map(entry => flattenHeaderValue(entry))
|
|
608
|
+
.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
|
609
|
+
return normalized.length > 0 ? normalized.join(', ') : undefined;
|
|
610
|
+
}
|
|
611
|
+
return undefined;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
let headerEntries = (headers: unknown): [string, string][] => {
|
|
615
|
+
if (!headers) return [];
|
|
616
|
+
|
|
617
|
+
if (typeof (headers as { toJSON?: unknown }).toJSON === 'function') {
|
|
618
|
+
let json = (headers as { toJSON(): unknown }).toJSON();
|
|
619
|
+
if (isRecord(json)) {
|
|
620
|
+
return Object.entries(json)
|
|
621
|
+
.map(([key, value]) => {
|
|
622
|
+
let flattened = flattenHeaderValue(value);
|
|
623
|
+
return flattened ? ([key, flattened] as [string, string]) : null;
|
|
624
|
+
})
|
|
625
|
+
.filter((entry): entry is [string, string] => entry !== null);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (typeof (headers as { entries?: unknown }).entries === 'function') {
|
|
630
|
+
return Array.from(
|
|
631
|
+
(headers as { entries(): IterableIterator<[string, string]> }).entries()
|
|
632
|
+
).map(([key, value]) => [key, value]);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (isRecord(headers)) {
|
|
636
|
+
return Object.entries(headers)
|
|
637
|
+
.map(([key, value]) => {
|
|
638
|
+
let flattened = flattenHeaderValue(value);
|
|
639
|
+
return flattened ? ([key, flattened] as [string, string]) : null;
|
|
640
|
+
})
|
|
641
|
+
.filter((entry): entry is [string, string] => entry !== null);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return [];
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
let sanitizeHeaders = (headers: unknown, redactor?: AuthConfigSecretRedactor) => {
|
|
648
|
+
let entries = headerEntries(headers)
|
|
649
|
+
.map(
|
|
650
|
+
([key, value]) =>
|
|
651
|
+
[key.toLowerCase(), sanitizeScalar(value, redactor).text] as [string, string]
|
|
652
|
+
)
|
|
653
|
+
.filter(([key]) => isSafeHeaderName(key));
|
|
654
|
+
|
|
655
|
+
if (entries.length === 0) return undefined;
|
|
656
|
+
return Object.fromEntries(entries);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
let getContentType = (headers: unknown) =>
|
|
660
|
+
headerEntries(headers).find(([key]) => key.toLowerCase() === 'content-type')?.[1];
|
|
661
|
+
|
|
662
|
+
let takeTraceDraft = (config: TraceAwareAxiosRequestConfig | undefined) => {
|
|
663
|
+
if (!config) return undefined;
|
|
664
|
+
|
|
665
|
+
let draft =
|
|
666
|
+
config.__slatesHttpTraceDraft ??
|
|
667
|
+
(isObjectLike(config) ? traceDraftsByConfig.get(config) : undefined) ??
|
|
668
|
+
(isObjectLike(config.headers) ? traceDraftsByHeaders.get(config.headers) : undefined);
|
|
669
|
+
|
|
670
|
+
if (!draft) return undefined;
|
|
671
|
+
|
|
672
|
+
return draft;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
let clearTraceDraft = (
|
|
676
|
+
config: TraceAwareAxiosRequestConfig | undefined,
|
|
677
|
+
draft: SlateHttpTraceDraft
|
|
678
|
+
) => {
|
|
679
|
+
if (!config) return;
|
|
680
|
+
|
|
681
|
+
if (config.__slatesHttpTraceDraft === draft) {
|
|
682
|
+
config.__slatesHttpTraceDraft = undefined;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (isObjectLike(config)) {
|
|
686
|
+
traceDraftsByConfig.delete(config);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (isObjectLike(config.headers)) {
|
|
690
|
+
traceDraftsByHeaders.delete(config.headers);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
export let attachHttpTraceDraft = (
|
|
695
|
+
config: InternalAxiosRequestConfig,
|
|
696
|
+
context: SlatePublicContext<any>
|
|
697
|
+
) => {
|
|
698
|
+
let traceAwareConfig = config as TraceAwareAxiosRequestConfig;
|
|
699
|
+
let contentType = getContentType(config.headers);
|
|
700
|
+
let redactor = new AuthConfigSecretRedactor(context._getAuthConfigForRedaction());
|
|
701
|
+
|
|
702
|
+
traceAwareConfig.__slatesHttpTraceDraft = {
|
|
703
|
+
context,
|
|
704
|
+
startedAt: new Date().toISOString(),
|
|
705
|
+
startedAtMs: Date.now(),
|
|
706
|
+
request: {
|
|
707
|
+
method: (config.method ?? 'GET').toUpperCase(),
|
|
708
|
+
url: sanitizeUrl(buildUrl(config.baseURL, config.url), redactor),
|
|
709
|
+
...(sanitizeHeaders(config.headers, redactor)
|
|
710
|
+
? { headers: sanitizeHeaders(config.headers, redactor) }
|
|
711
|
+
: {}),
|
|
712
|
+
...(serializeTextBody(config.data, contentType, redactor)
|
|
713
|
+
? { body: serializeTextBody(config.data, contentType, redactor) }
|
|
714
|
+
: {})
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
if (isObjectLike(traceAwareConfig)) {
|
|
719
|
+
traceDraftsByConfig.set(traceAwareConfig, traceAwareConfig.__slatesHttpTraceDraft);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (isObjectLike(traceAwareConfig.headers)) {
|
|
723
|
+
traceDraftsByHeaders.set(
|
|
724
|
+
traceAwareConfig.headers,
|
|
725
|
+
traceAwareConfig.__slatesHttpTraceDraft
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return config;
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
export let recordHttpTraceFromResponse = (response: AxiosResponse) => {
|
|
733
|
+
let config = response.config as TraceAwareAxiosRequestConfig | undefined;
|
|
734
|
+
let draft = takeTraceDraft(config);
|
|
735
|
+
if (!draft) return response;
|
|
736
|
+
clearTraceDraft(config, draft);
|
|
737
|
+
|
|
738
|
+
let redactor = new AuthConfigSecretRedactor(draft.context._getAuthConfigForRedaction());
|
|
739
|
+
let contentType = getContentType(response.headers);
|
|
740
|
+
draft.context.recordHttpTrace({
|
|
741
|
+
startedAt: draft.startedAt,
|
|
742
|
+
durationMs: Math.max(Date.now() - draft.startedAtMs, 0),
|
|
743
|
+
request: draft.request,
|
|
744
|
+
response: {
|
|
745
|
+
status: response.status,
|
|
746
|
+
...(response.statusText ? { statusText: response.statusText } : {}),
|
|
747
|
+
...(sanitizeHeaders(response.headers, redactor)
|
|
748
|
+
? { headers: sanitizeHeaders(response.headers, redactor) }
|
|
749
|
+
: {}),
|
|
750
|
+
...(serializeTextBody(response.data, contentType, redactor)
|
|
751
|
+
? { body: serializeTextBody(response.data, contentType, redactor) }
|
|
752
|
+
: {})
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
return response;
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
export let recordHttpTraceFromError = (error: AxiosError) => {
|
|
760
|
+
let config = error.config as TraceAwareAxiosRequestConfig | undefined;
|
|
761
|
+
let draft = takeTraceDraft(config);
|
|
762
|
+
if (!draft) return;
|
|
763
|
+
clearTraceDraft(config, draft);
|
|
764
|
+
|
|
765
|
+
let redactor = new AuthConfigSecretRedactor(draft.context._getAuthConfigForRedaction());
|
|
766
|
+
let responseContentType = getContentType(error.response?.headers);
|
|
767
|
+
draft.context.recordHttpTrace({
|
|
768
|
+
startedAt: draft.startedAt,
|
|
769
|
+
durationMs: Math.max(Date.now() - draft.startedAtMs, 0),
|
|
770
|
+
request: draft.request,
|
|
771
|
+
...(error.response
|
|
772
|
+
? {
|
|
773
|
+
response: {
|
|
774
|
+
status: error.response.status,
|
|
775
|
+
...(error.response.statusText ? { statusText: error.response.statusText } : {}),
|
|
776
|
+
...(sanitizeHeaders(error.response.headers, redactor)
|
|
777
|
+
? { headers: sanitizeHeaders(error.response.headers, redactor) }
|
|
778
|
+
: {}),
|
|
779
|
+
...(serializeTextBody(error.response.data, responseContentType, redactor)
|
|
780
|
+
? { body: serializeTextBody(error.response.data, responseContentType, redactor) }
|
|
781
|
+
: {})
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
: {}),
|
|
785
|
+
error: {
|
|
786
|
+
...(error.code ? { code: error.code } : {}),
|
|
787
|
+
message: error.message
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// Exposed for unit tests. Not part of the public API.
|
|
793
|
+
export let __traceInternals = {
|
|
794
|
+
sanitizeFreeText,
|
|
795
|
+
sanitizeUrl,
|
|
796
|
+
sanitizeHeaders,
|
|
797
|
+
isSafeHeaderName,
|
|
798
|
+
isSecretKeyName,
|
|
799
|
+
isUrlFormSecretKeyName,
|
|
800
|
+
redactStructuredValue,
|
|
801
|
+
SECRET_KEY_PATTERN,
|
|
802
|
+
URL_FORM_SECRET_KEY_PATTERN
|
|
803
|
+
};
|