@replayio-app-building/netlify-recorder 0.49.0 → 0.51.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 +3 -1
- package/dist/index.js +114 -62
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -74,8 +74,10 @@ interface NetworkCall {
|
|
|
74
74
|
timestamp: number;
|
|
75
75
|
/** Epoch ms when the fetch call started. */
|
|
76
76
|
startTime: number;
|
|
77
|
-
/** Epoch ms when the response was received. */
|
|
77
|
+
/** Epoch ms when the response was received. 0 if response was never awaited (fire-and-forget). */
|
|
78
78
|
endTime: number;
|
|
79
|
+
/** True when the call was initiated but the response was not awaited by the handler. */
|
|
80
|
+
pending?: boolean;
|
|
79
81
|
}
|
|
80
82
|
interface EnvRead {
|
|
81
83
|
key: string;
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ function buildSetConfigQueries(requestId, callIndex) {
|
|
|
55
55
|
{ query: "SELECT set_config('app.replay_call_index', $1, true)", params: [String(callIndex)] }
|
|
56
56
|
];
|
|
57
57
|
}
|
|
58
|
-
function patchHttpModules(mode, calls, consumed) {
|
|
58
|
+
function patchHttpModules(mode, calls, consumed, silent) {
|
|
59
59
|
const origHttpRequest = http.request;
|
|
60
60
|
const origHttpsRequest = https.request;
|
|
61
61
|
function makeInterceptedRequest(mod, origRequest, protocol, args) {
|
|
@@ -87,7 +87,7 @@ function patchHttpModules(mode, calls, consumed) {
|
|
|
87
87
|
const urlStr = `${protocol}//${host}${port}${path}`;
|
|
88
88
|
const method = (options.method || "GET").toUpperCase();
|
|
89
89
|
if (mode === "replay") {
|
|
90
|
-
return replayHttpRequest(urlStr, method, calls, consumed, callback);
|
|
90
|
+
return replayHttpRequest(urlStr, method, calls, consumed, callback, silent);
|
|
91
91
|
}
|
|
92
92
|
const req = origRequest.call(mod, ...args);
|
|
93
93
|
const bodyChunks = [];
|
|
@@ -102,37 +102,46 @@ function patchHttpModules(mode, calls, consumed) {
|
|
|
102
102
|
return origEnd(chunk, ...rest);
|
|
103
103
|
};
|
|
104
104
|
const startTime = Date.now();
|
|
105
|
+
const requestHeaders = {};
|
|
106
|
+
if (options.headers) {
|
|
107
|
+
for (const [k, v] of Object.entries(options.headers)) {
|
|
108
|
+
if (v !== void 0) requestHeaders[k] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const store = getRequestStore();
|
|
112
|
+
const targetCalls = store?.networkCalls ?? calls;
|
|
113
|
+
const entry = {
|
|
114
|
+
url: urlStr,
|
|
115
|
+
method,
|
|
116
|
+
requestHeaders,
|
|
117
|
+
requestBody: bodyChunks.length > 0 ? Buffer.concat(bodyChunks).toString("utf-8") : void 0,
|
|
118
|
+
responseStatus: 0,
|
|
119
|
+
responseHeaders: {},
|
|
120
|
+
responseBody: void 0,
|
|
121
|
+
timestamp: startTime,
|
|
122
|
+
startTime,
|
|
123
|
+
endTime: 0,
|
|
124
|
+
pending: true
|
|
125
|
+
};
|
|
126
|
+
targetCalls.push(entry);
|
|
105
127
|
req.on("response", (res) => {
|
|
106
128
|
const resChunks = [];
|
|
107
129
|
res.on("data", (chunk) => resChunks.push(chunk));
|
|
108
130
|
res.on("end", () => {
|
|
109
131
|
const endTime = Date.now();
|
|
110
|
-
const requestHeaders = {};
|
|
111
|
-
if (options.headers) {
|
|
112
|
-
for (const [k, v] of Object.entries(options.headers)) {
|
|
113
|
-
if (v !== void 0) requestHeaders[k] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
132
|
const responseHeaders = {};
|
|
117
133
|
if (res.headers) {
|
|
118
134
|
for (const [k, v] of Object.entries(res.headers)) {
|
|
119
135
|
if (v !== void 0) responseHeaders[k] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
120
136
|
}
|
|
121
137
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
responseStatus: res.statusCode ?? 0,
|
|
130
|
-
responseHeaders,
|
|
131
|
-
responseBody: Buffer.concat(resChunks).toString("utf-8"),
|
|
132
|
-
timestamp: endTime,
|
|
133
|
-
startTime,
|
|
134
|
-
endTime
|
|
135
|
-
});
|
|
138
|
+
entry.requestBody = bodyChunks.length > 0 ? Buffer.concat(bodyChunks).toString("utf-8") : void 0;
|
|
139
|
+
entry.responseStatus = res.statusCode ?? 0;
|
|
140
|
+
entry.responseHeaders = responseHeaders;
|
|
141
|
+
entry.responseBody = Buffer.concat(resChunks).toString("utf-8");
|
|
142
|
+
entry.timestamp = endTime;
|
|
143
|
+
entry.endTime = endTime;
|
|
144
|
+
entry.pending = false;
|
|
136
145
|
});
|
|
137
146
|
});
|
|
138
147
|
return req;
|
|
@@ -148,7 +157,7 @@ function patchHttpModules(mode, calls, consumed) {
|
|
|
148
157
|
https.request = origHttpsRequest;
|
|
149
158
|
};
|
|
150
159
|
}
|
|
151
|
-
function replayHttpRequest(url, _method, calls, consumed, callback) {
|
|
160
|
+
function replayHttpRequest(url, _method, calls, consumed, callback, silent) {
|
|
152
161
|
let matchIdx = -1;
|
|
153
162
|
for (let i = 0; i < calls.length; i++) {
|
|
154
163
|
if (consumed.has(i)) continue;
|
|
@@ -173,10 +182,19 @@ function replayHttpRequest(url, _method, calls, consumed, callback) {
|
|
|
173
182
|
);
|
|
174
183
|
}
|
|
175
184
|
consumed.add(matchIdx);
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
185
|
+
const isPending = call.pending || call.endTime === 0 && call.responseStatus === 0;
|
|
186
|
+
if (!silent) {
|
|
187
|
+
if (isPending) {
|
|
188
|
+
console.log(
|
|
189
|
+
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => (fire-and-forget, no response captured)`
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
const duration = call.endTime - call.startTime;
|
|
193
|
+
console.log(
|
|
194
|
+
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => ${call.responseStatus} (original: ${duration}ms)`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
180
198
|
const body = call.responseBody ?? "";
|
|
181
199
|
const fakeRes = new Readable({
|
|
182
200
|
read() {
|
|
@@ -184,7 +202,7 @@ function replayHttpRequest(url, _method, calls, consumed, callback) {
|
|
|
184
202
|
this.push(null);
|
|
185
203
|
}
|
|
186
204
|
});
|
|
187
|
-
fakeRes.statusCode = call.responseStatus;
|
|
205
|
+
fakeRes.statusCode = isPending ? 200 : call.responseStatus;
|
|
188
206
|
fakeRes.statusMessage = "";
|
|
189
207
|
fakeRes.headers = {};
|
|
190
208
|
if (call.responseHeaders) {
|
|
@@ -244,6 +262,20 @@ function ensureCaptureInterceptor() {
|
|
|
244
262
|
);
|
|
245
263
|
}
|
|
246
264
|
const startTime = Date.now();
|
|
265
|
+
const entry = {
|
|
266
|
+
url,
|
|
267
|
+
method,
|
|
268
|
+
requestHeaders,
|
|
269
|
+
requestBody,
|
|
270
|
+
responseStatus: 0,
|
|
271
|
+
responseHeaders: {},
|
|
272
|
+
responseBody: void 0,
|
|
273
|
+
timestamp: startTime,
|
|
274
|
+
startTime,
|
|
275
|
+
endTime: 0,
|
|
276
|
+
pending: true
|
|
277
|
+
};
|
|
278
|
+
calls.push(entry);
|
|
247
279
|
const response = await _realOriginalFetch(input, init);
|
|
248
280
|
const endTime = Date.now();
|
|
249
281
|
const responseBody = await response.clone().text();
|
|
@@ -251,23 +283,18 @@ function ensureCaptureInterceptor() {
|
|
|
251
283
|
response.headers.forEach((v, k) => {
|
|
252
284
|
responseHeaders[k] = v;
|
|
253
285
|
});
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
responseHeaders,
|
|
261
|
-
responseBody,
|
|
262
|
-
timestamp: endTime,
|
|
263
|
-
startTime,
|
|
264
|
-
endTime
|
|
265
|
-
});
|
|
286
|
+
entry.responseStatus = response.status;
|
|
287
|
+
entry.responseHeaders = responseHeaders;
|
|
288
|
+
entry.responseBody = responseBody;
|
|
289
|
+
entry.timestamp = endTime;
|
|
290
|
+
entry.endTime = endTime;
|
|
291
|
+
entry.pending = false;
|
|
266
292
|
return response;
|
|
267
293
|
};
|
|
268
294
|
globalThis.fetch = captureFetch;
|
|
269
295
|
}
|
|
270
|
-
function installNetworkInterceptor(mode, calls) {
|
|
296
|
+
function installNetworkInterceptor(mode, calls, options) {
|
|
297
|
+
const silent = options?.silent ?? false;
|
|
271
298
|
if (mode === "capture") {
|
|
272
299
|
const store = getRequestStore();
|
|
273
300
|
if (store) {
|
|
@@ -317,6 +344,20 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
317
344
|
);
|
|
318
345
|
}
|
|
319
346
|
const startTime = Date.now();
|
|
347
|
+
const entry = {
|
|
348
|
+
url,
|
|
349
|
+
method,
|
|
350
|
+
requestHeaders,
|
|
351
|
+
requestBody,
|
|
352
|
+
responseStatus: 0,
|
|
353
|
+
responseHeaders: {},
|
|
354
|
+
responseBody: void 0,
|
|
355
|
+
timestamp: startTime,
|
|
356
|
+
startTime,
|
|
357
|
+
endTime: 0,
|
|
358
|
+
pending: true
|
|
359
|
+
};
|
|
360
|
+
calls.push(entry);
|
|
320
361
|
const response = await originalFetch2(input, init);
|
|
321
362
|
const endTime = Date.now();
|
|
322
363
|
const responseBody = await response.clone().text();
|
|
@@ -324,18 +365,12 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
324
365
|
response.headers.forEach((v, k) => {
|
|
325
366
|
responseHeaders[k] = v;
|
|
326
367
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
responseHeaders,
|
|
334
|
-
responseBody,
|
|
335
|
-
timestamp: endTime,
|
|
336
|
-
startTime,
|
|
337
|
-
endTime
|
|
338
|
-
});
|
|
368
|
+
entry.responseStatus = response.status;
|
|
369
|
+
entry.responseHeaders = responseHeaders;
|
|
370
|
+
entry.responseBody = responseBody;
|
|
371
|
+
entry.timestamp = endTime;
|
|
372
|
+
entry.endTime = endTime;
|
|
373
|
+
entry.pending = false;
|
|
339
374
|
return response;
|
|
340
375
|
};
|
|
341
376
|
globalThis.fetch = captureFetch;
|
|
@@ -385,12 +420,21 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
385
420
|
);
|
|
386
421
|
}
|
|
387
422
|
consumed.add(matchIdx);
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
423
|
+
const isPending = call.pending || call.endTime === 0 && call.responseStatus === 0;
|
|
424
|
+
if (!silent) {
|
|
425
|
+
if (isPending) {
|
|
426
|
+
console.log(
|
|
427
|
+
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => (fire-and-forget, no response captured)`
|
|
428
|
+
);
|
|
429
|
+
} else {
|
|
430
|
+
const duration = call.endTime - call.startTime;
|
|
431
|
+
console.log(
|
|
432
|
+
` [network-replay] Consumed call ${consumed.size}/${calls.length}: ${call.method} ${call.url} => ${call.responseStatus} (original: ${duration}ms)`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
392
436
|
const body = call.responseBody ?? "";
|
|
393
|
-
const status = call.responseStatus;
|
|
437
|
+
const status = isPending ? 200 : call.responseStatus;
|
|
394
438
|
return {
|
|
395
439
|
ok: status >= 200 && status < 300,
|
|
396
440
|
status,
|
|
@@ -420,7 +464,7 @@ function installNetworkInterceptor(mode, calls) {
|
|
|
420
464
|
};
|
|
421
465
|
};
|
|
422
466
|
globalThis.fetch = replayFetch;
|
|
423
|
-
const restoreHttp = patchHttpModules("replay", calls, consumed);
|
|
467
|
+
const restoreHttp = patchHttpModules("replay", calls, consumed, silent);
|
|
424
468
|
return {
|
|
425
469
|
restore() {
|
|
426
470
|
globalThis.fetch = originalFetch;
|
|
@@ -1249,9 +1293,9 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo, p
|
|
|
1249
1293
|
console.log(` [warm-up] Replaying ${precedingBlobs.length} preceding request(s) to populate module-level state\u2026`);
|
|
1250
1294
|
for (let i = 0; i < precedingBlobs.length; i++) {
|
|
1251
1295
|
const pb = precedingBlobs[i];
|
|
1252
|
-
|
|
1253
|
-
const netH = installNetworkInterceptor("replay", pb.capturedData.networkCalls);
|
|
1296
|
+
const netH = installNetworkInterceptor("replay", pb.capturedData.networkCalls, { silent: true });
|
|
1254
1297
|
const envH = installEnvironmentInterceptor("replay", pb.capturedData.envReads);
|
|
1298
|
+
let warmupError;
|
|
1255
1299
|
try {
|
|
1256
1300
|
if (isV2) {
|
|
1257
1301
|
const url = pb.requestInfo.rawUrl ?? `https://localhost${pb.requestInfo.url}`;
|
|
@@ -1280,10 +1324,18 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo, p
|
|
|
1280
1324
|
isBase64Encoded: pb.requestInfo.isBase64Encoded ?? false
|
|
1281
1325
|
});
|
|
1282
1326
|
}
|
|
1283
|
-
} catch {
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
warmupError = err instanceof Error ? err.message : String(err);
|
|
1284
1329
|
}
|
|
1330
|
+
const consumed = netH.consumedCount();
|
|
1331
|
+
const total = netH.totalCount();
|
|
1285
1332
|
netH.restore();
|
|
1286
1333
|
envH.restore();
|
|
1334
|
+
if (warmupError) {
|
|
1335
|
+
console.error(` [warm-up ${i + 1}/${precedingBlobs.length}] ${pb.requestInfo.method} ${pb.requestInfo.url} \u2014 ERROR: ${warmupError} (${consumed}/${total} calls)`);
|
|
1336
|
+
} else {
|
|
1337
|
+
console.log(` [warm-up ${i + 1}/${precedingBlobs.length}] ${pb.requestInfo.method} ${pb.requestInfo.url} \u2014 OK (${consumed}/${total} network calls)`);
|
|
1338
|
+
}
|
|
1287
1339
|
}
|
|
1288
1340
|
console.log(` [warm-up] Done.`);
|
|
1289
1341
|
}
|