@replayio-app-building/netlify-recorder 0.32.0 → 0.34.0
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.d.ts +6 -1
- package/dist/index.js +312 -156
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -198,6 +198,10 @@ interface CreateRecordingRequestHandlerOptions extends FinishRequestOptions {
|
|
|
198
198
|
* after, capturing all outbound network calls and environment variable reads.
|
|
199
199
|
* The captured data is stored via the provided callbacks.
|
|
200
200
|
*
|
|
201
|
+
* Each request runs inside its own AsyncLocalStorage context so that
|
|
202
|
+
* concurrent requests in local dev (Netlify Dev) do not bleed into each
|
|
203
|
+
* other's captured network calls, environment reads, or audit trail tags.
|
|
204
|
+
*
|
|
201
205
|
* **Response timing:** When the Netlify Functions v2 `context` object is
|
|
202
206
|
* available (with `waitUntil`), the response is returned to the client
|
|
203
207
|
* **immediately** with a pre-generated `X-Replay-Request-Id` header. The
|
|
@@ -318,6 +322,7 @@ declare function databaseAuditMonitorTable(sql: SqlFunction, tableName: string,
|
|
|
318
322
|
*/
|
|
319
323
|
declare function databaseAuditDumpLogTable(sql: SqlFunction): Promise<Record<string, unknown>[]>;
|
|
320
324
|
|
|
325
|
+
declare function runInRequestContext<T>(requestId: string | null, fn: () => Promise<T>): Promise<T>;
|
|
321
326
|
declare function getCurrentRequestId(): string | null;
|
|
322
327
|
|
|
323
|
-
export { type BackendRequest, type BlobData, type CapturedData, type CreateRecordingRequestHandlerOptions, type EnsureRequestRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type HandlerResponse$1 as HandlerResponse, type NetlifyEvent, type NetlifyV2Request, type NetworkCall, type RecordingResult, type RequestContext, type RequestInfo, backendRequestsEnsureTable, backendRequestsGet, backendRequestsGetBlobUrl, backendRequestsInsert, backendRequestsList, backendRequestsUpdateStatus, createRecordingRequestHandler, createRequestRecording, databaseAuditDumpLogTable, databaseAuditEnsureLogTable, databaseAuditMonitorTable, databaseCallbacks, ensureRequestRecording, finishRequest, getCurrentRequestId, redactBlobData, startRequest };
|
|
328
|
+
export { type BackendRequest, type BlobData, type CapturedData, type CreateRecordingRequestHandlerOptions, type EnsureRequestRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type HandlerResponse$1 as HandlerResponse, type NetlifyEvent, type NetlifyV2Request, type NetworkCall, type RecordingResult, type RequestContext, type RequestInfo, backendRequestsEnsureTable, backendRequestsGet, backendRequestsGetBlobUrl, backendRequestsInsert, backendRequestsList, backendRequestsUpdateStatus, createRecordingRequestHandler, createRequestRecording, databaseAuditDumpLogTable, databaseAuditEnsureLogTable, databaseAuditMonitorTable, databaseCallbacks, ensureRequestRecording, finishRequest, getCurrentRequestId, redactBlobData, runInRequestContext, startRequest };
|
package/dist/index.js
CHANGED
|
@@ -6,17 +6,29 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/requestState.ts
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
10
|
+
var als = new AsyncLocalStorage();
|
|
11
|
+
var _fallbackRequestId = null;
|
|
12
|
+
var _fallbackCallIndex = 0;
|
|
13
|
+
function runInRequestContext(requestId, fn) {
|
|
14
|
+
return als.run(
|
|
15
|
+
{ requestId, callIndex: 0, networkCalls: null, envReads: null },
|
|
16
|
+
fn
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function getRequestStore() {
|
|
20
|
+
return als.getStore();
|
|
14
21
|
}
|
|
15
22
|
function getCurrentRequestId() {
|
|
16
|
-
|
|
23
|
+
const store = als.getStore();
|
|
24
|
+
return store ? store.requestId : _fallbackRequestId;
|
|
17
25
|
}
|
|
18
26
|
function incrementCallIndex() {
|
|
19
|
-
|
|
27
|
+
const store = als.getStore();
|
|
28
|
+
if (store) {
|
|
29
|
+
return ++store.callIndex;
|
|
30
|
+
}
|
|
31
|
+
return ++_fallbackCallIndex;
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
// src/interceptors/network.ts
|
|
@@ -40,10 +52,84 @@ function buildSetConfigQueries(requestId, callIndex) {
|
|
|
40
52
|
{ query: "SELECT set_config('app.replay_call_index', $1, true)", params: [String(callIndex)] }
|
|
41
53
|
];
|
|
42
54
|
}
|
|
55
|
+
var _realOriginalFetch = null;
|
|
56
|
+
var _captureInterceptorInstalled = false;
|
|
57
|
+
function ensureCaptureInterceptor() {
|
|
58
|
+
if (_captureInterceptorInstalled) return;
|
|
59
|
+
_captureInterceptorInstalled = true;
|
|
60
|
+
_realOriginalFetch = globalThis.fetch;
|
|
61
|
+
const captureFetch = async (input, init) => {
|
|
62
|
+
const store = getRequestStore();
|
|
63
|
+
const calls = store?.networkCalls;
|
|
64
|
+
if (!calls) {
|
|
65
|
+
return _realOriginalFetch(input, init);
|
|
66
|
+
}
|
|
67
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
68
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
69
|
+
const requestHeaders = {};
|
|
70
|
+
if (init?.headers) {
|
|
71
|
+
new Headers(init.headers).forEach((v, k) => {
|
|
72
|
+
requestHeaders[k] = v;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const requestBody = typeof init?.body === "string" ? init.body : void 0;
|
|
76
|
+
const requestId = store.requestId;
|
|
77
|
+
if (requestId && method.toUpperCase() === "POST" && isNeonSqlRequest(url, requestBody)) {
|
|
78
|
+
return await handleNeonSqlRequest(
|
|
79
|
+
_realOriginalFetch,
|
|
80
|
+
input,
|
|
81
|
+
init,
|
|
82
|
+
url,
|
|
83
|
+
method,
|
|
84
|
+
requestHeaders,
|
|
85
|
+
requestBody,
|
|
86
|
+
requestId,
|
|
87
|
+
calls
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const response = await _realOriginalFetch(input, init);
|
|
91
|
+
const responseBody = await response.clone().text();
|
|
92
|
+
const responseHeaders = {};
|
|
93
|
+
response.headers.forEach((v, k) => {
|
|
94
|
+
responseHeaders[k] = v;
|
|
95
|
+
});
|
|
96
|
+
calls.push({
|
|
97
|
+
url,
|
|
98
|
+
method,
|
|
99
|
+
requestHeaders,
|
|
100
|
+
requestBody,
|
|
101
|
+
responseStatus: response.status,
|
|
102
|
+
responseHeaders,
|
|
103
|
+
responseBody,
|
|
104
|
+
timestamp: Date.now()
|
|
105
|
+
});
|
|
106
|
+
return response;
|
|
107
|
+
};
|
|
108
|
+
globalThis.fetch = captureFetch;
|
|
109
|
+
}
|
|
43
110
|
function installNetworkInterceptor(mode, calls) {
|
|
44
|
-
const originalFetch = globalThis.fetch;
|
|
45
|
-
const consumed = /* @__PURE__ */ new Set();
|
|
46
111
|
if (mode === "capture") {
|
|
112
|
+
const store = getRequestStore();
|
|
113
|
+
if (store) {
|
|
114
|
+
ensureCaptureInterceptor();
|
|
115
|
+
store.networkCalls = calls;
|
|
116
|
+
return {
|
|
117
|
+
restore() {
|
|
118
|
+
const s = getRequestStore();
|
|
119
|
+
if (s) s.networkCalls = null;
|
|
120
|
+
},
|
|
121
|
+
consumedCount() {
|
|
122
|
+
return 0;
|
|
123
|
+
},
|
|
124
|
+
totalCount() {
|
|
125
|
+
return calls.length;
|
|
126
|
+
},
|
|
127
|
+
unconsumedIndices() {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const originalFetch2 = globalThis.fetch;
|
|
47
133
|
const captureFetch = async (input, init) => {
|
|
48
134
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
49
135
|
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
@@ -57,7 +143,7 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
57
143
|
const requestId = getCurrentRequestId();
|
|
58
144
|
if (requestId && method.toUpperCase() === "POST" && isNeonSqlRequest(url, requestBody)) {
|
|
59
145
|
return await handleNeonSqlRequest(
|
|
60
|
-
|
|
146
|
+
originalFetch2,
|
|
61
147
|
input,
|
|
62
148
|
init,
|
|
63
149
|
url,
|
|
@@ -68,7 +154,7 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
68
154
|
calls
|
|
69
155
|
);
|
|
70
156
|
}
|
|
71
|
-
const response = await
|
|
157
|
+
const response = await originalFetch2(input, init);
|
|
72
158
|
const responseBody = await response.clone().text();
|
|
73
159
|
const responseHeaders = {};
|
|
74
160
|
response.headers.forEach((v, k) => {
|
|
@@ -87,69 +173,84 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
87
173
|
return response;
|
|
88
174
|
};
|
|
89
175
|
globalThis.fetch = captureFetch;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
176
|
+
return {
|
|
177
|
+
restore() {
|
|
178
|
+
globalThis.fetch = originalFetch2;
|
|
179
|
+
},
|
|
180
|
+
consumedCount() {
|
|
181
|
+
return 0;
|
|
182
|
+
},
|
|
183
|
+
totalCount() {
|
|
184
|
+
return calls.length;
|
|
185
|
+
},
|
|
186
|
+
unconsumedIndices() {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const originalFetch = globalThis.fetch;
|
|
192
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
193
|
+
const replayFetch = async (input, init) => {
|
|
194
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : typeof input === "object" && input !== null && "url" in input ? input.url : String(input);
|
|
195
|
+
const requestBody = typeof init?.body === "string" ? init.body : void 0;
|
|
196
|
+
let matchIdx = -1;
|
|
197
|
+
for (let i = 0; i < calls.length; i++) {
|
|
198
|
+
if (consumed.has(i)) continue;
|
|
199
|
+
const c = calls[i];
|
|
200
|
+
if (c && c.url === url && c.requestBody === requestBody) {
|
|
201
|
+
matchIdx = i;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (matchIdx === -1) {
|
|
95
206
|
for (let i = 0; i < calls.length; i++) {
|
|
96
|
-
if (consumed.has(i))
|
|
97
|
-
const c = calls[i];
|
|
98
|
-
if (c && c.url === url && c.requestBody === requestBody) {
|
|
207
|
+
if (!consumed.has(i)) {
|
|
99
208
|
matchIdx = i;
|
|
100
209
|
break;
|
|
101
210
|
}
|
|
102
211
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const call = calls[matchIdx];
|
|
112
|
-
if (matchIdx === -1 || !call) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`No more recorded network calls to replay (exhausted ${calls.length} calls)`
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
consumed.add(matchIdx);
|
|
118
|
-
console.log(
|
|
119
|
-
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => ${call.responseStatus}`
|
|
212
|
+
}
|
|
213
|
+
const call = calls[matchIdx];
|
|
214
|
+
if (matchIdx === -1 || !call) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`No more recorded network calls to replay (exhausted ${calls.length} calls)`
|
|
120
217
|
);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
body: null,
|
|
138
|
-
bodyUsed: false,
|
|
139
|
-
redirected: false,
|
|
140
|
-
type: "basic",
|
|
141
|
-
url: call.url,
|
|
142
|
-
arrayBuffer: async () => new ArrayBuffer(0),
|
|
143
|
-
blob: async () => {
|
|
144
|
-
throw new Error("blob() not supported in replay");
|
|
145
|
-
},
|
|
146
|
-
formData: async () => {
|
|
147
|
-
throw new Error("formData() not supported in replay");
|
|
218
|
+
}
|
|
219
|
+
consumed.add(matchIdx);
|
|
220
|
+
console.log(
|
|
221
|
+
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => ${call.responseStatus}`
|
|
222
|
+
);
|
|
223
|
+
const body = call.responseBody ?? "";
|
|
224
|
+
const status = call.responseStatus;
|
|
225
|
+
return {
|
|
226
|
+
ok: status >= 200 && status < 300,
|
|
227
|
+
status,
|
|
228
|
+
statusText: "",
|
|
229
|
+
headers: {
|
|
230
|
+
get: (name) => (call.responseHeaders ?? {})[name.toLowerCase()] ?? null,
|
|
231
|
+
has: (name) => name.toLowerCase() in (call.responseHeaders ?? {}),
|
|
232
|
+
forEach: (cb) => {
|
|
233
|
+
for (const [k, v] of Object.entries(call.responseHeaders ?? {})) cb(v, k);
|
|
148
234
|
}
|
|
149
|
-
}
|
|
235
|
+
},
|
|
236
|
+
text: async () => body,
|
|
237
|
+
json: async () => JSON.parse(body),
|
|
238
|
+
clone: () => ({ text: async () => body, json: async () => JSON.parse(body) }),
|
|
239
|
+
body: null,
|
|
240
|
+
bodyUsed: false,
|
|
241
|
+
redirected: false,
|
|
242
|
+
type: "basic",
|
|
243
|
+
url: call.url,
|
|
244
|
+
arrayBuffer: async () => new ArrayBuffer(0),
|
|
245
|
+
blob: async () => {
|
|
246
|
+
throw new Error("blob() not supported in replay");
|
|
247
|
+
},
|
|
248
|
+
formData: async () => {
|
|
249
|
+
throw new Error("formData() not supported in replay");
|
|
250
|
+
}
|
|
150
251
|
};
|
|
151
|
-
|
|
152
|
-
|
|
252
|
+
};
|
|
253
|
+
globalThis.fetch = replayFetch;
|
|
153
254
|
return {
|
|
154
255
|
restore() {
|
|
155
256
|
globalThis.fetch = originalFetch;
|
|
@@ -215,9 +316,59 @@ async function handleNeonSqlRequest(originalFetch, input, init, url, method, req
|
|
|
215
316
|
}
|
|
216
317
|
|
|
217
318
|
// src/interceptors/environment.ts
|
|
319
|
+
var _realOriginalEnv = null;
|
|
320
|
+
var _captureProxyInstalled = false;
|
|
321
|
+
function ensureCaptureProxy() {
|
|
322
|
+
if (_captureProxyInstalled) return;
|
|
323
|
+
_captureProxyInstalled = true;
|
|
324
|
+
_realOriginalEnv = process.env;
|
|
325
|
+
process.env = new Proxy(_realOriginalEnv, {
|
|
326
|
+
get(target, prop) {
|
|
327
|
+
const value = target[prop];
|
|
328
|
+
if (typeof prop === "string" && prop !== "toJSON") {
|
|
329
|
+
const store = getRequestStore();
|
|
330
|
+
const reads = store?.envReads;
|
|
331
|
+
if (reads) {
|
|
332
|
+
reads.push({ key: prop, value, timestamp: Date.now() });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
},
|
|
337
|
+
set(target, prop, value) {
|
|
338
|
+
target[prop] = value;
|
|
339
|
+
return true;
|
|
340
|
+
},
|
|
341
|
+
defineProperty(target, prop, descriptor) {
|
|
342
|
+
if ("value" in descriptor) {
|
|
343
|
+
target[prop] = descriptor.value;
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
347
|
+
},
|
|
348
|
+
deleteProperty(target, prop) {
|
|
349
|
+
delete target[prop];
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
218
354
|
function installEnvironmentInterceptor(mode, reads) {
|
|
219
355
|
const originalEnv = process.env;
|
|
220
356
|
if (mode === "capture") {
|
|
357
|
+
const store = getRequestStore();
|
|
358
|
+
if (store) {
|
|
359
|
+
ensureCaptureProxy();
|
|
360
|
+
const now2 = Date.now();
|
|
361
|
+
for (const key of Object.keys(_realOriginalEnv)) {
|
|
362
|
+
reads.push({ key, value: _realOriginalEnv[key], timestamp: now2 });
|
|
363
|
+
}
|
|
364
|
+
store.envReads = reads;
|
|
365
|
+
return {
|
|
366
|
+
restore() {
|
|
367
|
+
const s = getRequestStore();
|
|
368
|
+
if (s) s.envReads = null;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
221
372
|
const now = Date.now();
|
|
222
373
|
for (const key of Object.keys(originalEnv)) {
|
|
223
374
|
reads.push({ key, value: originalEnv[key], timestamp: now });
|
|
@@ -246,35 +397,39 @@ function installEnvironmentInterceptor(mode, reads) {
|
|
|
246
397
|
return true;
|
|
247
398
|
}
|
|
248
399
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
readMap.set(read.key, read.value);
|
|
253
|
-
}
|
|
254
|
-
process.env = new Proxy(originalEnv, {
|
|
255
|
-
get(target, prop) {
|
|
256
|
-
if (typeof prop === "string" && readMap.has(prop)) {
|
|
257
|
-
return readMap.get(prop);
|
|
258
|
-
}
|
|
259
|
-
return target[prop];
|
|
260
|
-
},
|
|
261
|
-
set(target, prop, value) {
|
|
262
|
-
target[prop] = value;
|
|
263
|
-
return true;
|
|
264
|
-
},
|
|
265
|
-
defineProperty(target, prop, descriptor) {
|
|
266
|
-
if ("value" in descriptor) {
|
|
267
|
-
target[prop] = descriptor.value;
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
return Reflect.defineProperty(target, prop, descriptor);
|
|
271
|
-
},
|
|
272
|
-
deleteProperty(target, prop) {
|
|
273
|
-
delete target[prop];
|
|
274
|
-
return true;
|
|
400
|
+
return {
|
|
401
|
+
restore() {
|
|
402
|
+
process.env = originalEnv;
|
|
275
403
|
}
|
|
276
|
-
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const readMap = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const read of reads) {
|
|
408
|
+
readMap.set(read.key, read.value);
|
|
277
409
|
}
|
|
410
|
+
process.env = new Proxy(originalEnv, {
|
|
411
|
+
get(target, prop) {
|
|
412
|
+
if (typeof prop === "string" && readMap.has(prop)) {
|
|
413
|
+
return readMap.get(prop);
|
|
414
|
+
}
|
|
415
|
+
return target[prop];
|
|
416
|
+
},
|
|
417
|
+
set(target, prop, value) {
|
|
418
|
+
target[prop] = value;
|
|
419
|
+
return true;
|
|
420
|
+
},
|
|
421
|
+
defineProperty(target, prop, descriptor) {
|
|
422
|
+
if ("value" in descriptor) {
|
|
423
|
+
target[prop] = descriptor.value;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
427
|
+
},
|
|
428
|
+
deleteProperty(target, prop) {
|
|
429
|
+
delete target[prop];
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
278
433
|
return {
|
|
279
434
|
restore() {
|
|
280
435
|
process.env = originalEnv;
|
|
@@ -597,82 +752,81 @@ import crypto2 from "crypto";
|
|
|
597
752
|
function createRecordingRequestHandler(handler, options) {
|
|
598
753
|
return async (event, context) => {
|
|
599
754
|
const requestId = crypto2.randomUUID();
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
755
|
+
return runInRequestContext(requestId, async () => {
|
|
756
|
+
const reqContext = startRequest(event);
|
|
757
|
+
let response;
|
|
758
|
+
try {
|
|
759
|
+
response = await handler(event, context);
|
|
760
|
+
} catch (handlerErr) {
|
|
761
|
+
const errorMessage = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
|
|
762
|
+
const errorResponse = {
|
|
763
|
+
statusCode: 500,
|
|
764
|
+
body: JSON.stringify({ error: errorMessage })
|
|
765
|
+
};
|
|
766
|
+
const finishOpts2 = { ...options, requestId };
|
|
767
|
+
const ctx2 = context;
|
|
768
|
+
if (ctx2 && typeof ctx2.waitUntil === "function") {
|
|
769
|
+
ctx2.waitUntil(
|
|
770
|
+
finishRequest(reqContext, options.callbacks, errorResponse, finishOpts2).catch(
|
|
771
|
+
(finishErr) => {
|
|
772
|
+
console.error(
|
|
773
|
+
`netlify-recorder: background finishRequest failed after handler error (handler: ${options.handlerPath ?? "unknown"})`,
|
|
774
|
+
finishErr
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
);
|
|
779
|
+
} else {
|
|
780
|
+
await finishRequest(reqContext, options.callbacks, errorResponse, finishOpts2).catch(
|
|
616
781
|
(finishErr) => {
|
|
617
782
|
console.error(
|
|
618
|
-
`netlify-recorder:
|
|
783
|
+
`netlify-recorder: finishRequest failed after handler error (handler: ${options.handlerPath ?? "unknown"})`,
|
|
619
784
|
finishErr
|
|
620
785
|
);
|
|
621
786
|
}
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
finishErr
|
|
630
|
-
);
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
...errorResponse,
|
|
791
|
+
headers: {
|
|
792
|
+
...errorResponse.headers,
|
|
793
|
+
"X-Replay-Request-Id": requestId
|
|
631
794
|
}
|
|
632
|
-
|
|
795
|
+
};
|
|
633
796
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
...
|
|
797
|
+
reqContext.cleanup();
|
|
798
|
+
const responseWithHeader = {
|
|
799
|
+
...response,
|
|
637
800
|
headers: {
|
|
638
|
-
...
|
|
801
|
+
...response.headers,
|
|
639
802
|
"X-Replay-Request-Id": requestId
|
|
640
803
|
}
|
|
641
804
|
};
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
805
|
+
const finishOpts = { ...options, requestId };
|
|
806
|
+
const ctx = context;
|
|
807
|
+
if (ctx && typeof ctx.waitUntil === "function") {
|
|
808
|
+
ctx.waitUntil(
|
|
809
|
+
finishRequest(reqContext, options.callbacks, response, finishOpts).catch(
|
|
810
|
+
(err) => {
|
|
811
|
+
console.error(
|
|
812
|
+
`netlify-recorder: background finishRequest failed (handler: ${options.handlerPath ?? "unknown"})`,
|
|
813
|
+
err
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
);
|
|
818
|
+
return responseWithHeader;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
await finishRequest(reqContext, options.callbacks, response, finishOpts);
|
|
822
|
+
} catch (err) {
|
|
823
|
+
console.error(
|
|
824
|
+
`netlify-recorder: finishRequest failed (handler: ${options.handlerPath ?? "unknown"})`,
|
|
825
|
+
err
|
|
826
|
+
);
|
|
650
827
|
}
|
|
651
|
-
};
|
|
652
|
-
const finishOpts = { ...options, requestId };
|
|
653
|
-
const ctx = context;
|
|
654
|
-
if (ctx && typeof ctx.waitUntil === "function") {
|
|
655
|
-
ctx.waitUntil(
|
|
656
|
-
finishRequest(reqContext, options.callbacks, response, finishOpts).catch(
|
|
657
|
-
(err) => {
|
|
658
|
-
console.error(
|
|
659
|
-
`netlify-recorder: background finishRequest failed (handler: ${options.handlerPath ?? "unknown"})`,
|
|
660
|
-
err
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
)
|
|
664
|
-
);
|
|
665
828
|
return responseWithHeader;
|
|
666
|
-
}
|
|
667
|
-
try {
|
|
668
|
-
await finishRequest(reqContext, options.callbacks, response, finishOpts);
|
|
669
|
-
} catch (err) {
|
|
670
|
-
console.error(
|
|
671
|
-
`netlify-recorder: finishRequest failed (handler: ${options.handlerPath ?? "unknown"})`,
|
|
672
|
-
err
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
return responseWithHeader;
|
|
829
|
+
});
|
|
676
830
|
};
|
|
677
831
|
}
|
|
678
832
|
|
|
@@ -1142,6 +1296,7 @@ async function ensureRequestRecording(sql, requestId, options) {
|
|
|
1142
1296
|
method: "POST",
|
|
1143
1297
|
headers: { "Content-Type": "application/json" },
|
|
1144
1298
|
body: JSON.stringify({
|
|
1299
|
+
requestId,
|
|
1145
1300
|
blobDataUrl: request.blob_data_url,
|
|
1146
1301
|
handlerPath: request.handler_path,
|
|
1147
1302
|
commitSha: request.commit_sha,
|
|
@@ -1267,5 +1422,6 @@ export {
|
|
|
1267
1422
|
finishRequest,
|
|
1268
1423
|
getCurrentRequestId,
|
|
1269
1424
|
redactBlobData,
|
|
1425
|
+
runInRequestContext,
|
|
1270
1426
|
startRequest
|
|
1271
1427
|
};
|