test-proxy-recorder 0.1.8 → 0.1.10
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/README.md +2 -2
- package/dist/{index-CBjvm5rb.d.cts → index-CjM3evKb.d.cts} +1 -1
- package/dist/{index-CBjvm5rb.d.ts → index-CjM3evKb.d.ts} +1 -1
- package/dist/index.cjs +184 -81
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.mjs +183 -81
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/proxy.js +183 -81
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -5,6 +5,7 @@ import http from 'http';
|
|
|
5
5
|
import https from 'https';
|
|
6
6
|
import httpProxy from 'http-proxy';
|
|
7
7
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
8
|
+
import crypto from 'crypto';
|
|
8
9
|
import filenamify from 'filenamify';
|
|
9
10
|
|
|
10
11
|
// src/cli.ts
|
|
@@ -76,7 +77,6 @@ async function saveRecordingSession(recordingsDir2, session) {
|
|
|
76
77
|
`Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
|
|
77
78
|
);
|
|
78
79
|
}
|
|
79
|
-
var QUERY_HASH_LENGTH = 8;
|
|
80
80
|
function getReqID(req) {
|
|
81
81
|
const urlParts = req.url.split("?");
|
|
82
82
|
const pathname = urlParts[0];
|
|
@@ -91,7 +91,7 @@ function generateQueryHash(query) {
|
|
|
91
91
|
if (!query) {
|
|
92
92
|
return "";
|
|
93
93
|
}
|
|
94
|
-
const hash =
|
|
94
|
+
const hash = crypto.createHash("md5").update(query).digest("hex").slice(0, 16);
|
|
95
95
|
return `_${hash}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -120,24 +120,25 @@ var ProxyServer = class {
|
|
|
120
120
|
proxy;
|
|
121
121
|
currentSession;
|
|
122
122
|
recordingsDir;
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
// Track replay
|
|
123
|
+
recordingIdCounter;
|
|
124
|
+
// Unique ID for each recording entry
|
|
125
|
+
replaySessions;
|
|
126
|
+
// Track multiple concurrent replay sessions by recording ID
|
|
127
127
|
constructor(targets2, recordingsDir2) {
|
|
128
128
|
this.targets = targets2;
|
|
129
129
|
this.currentTargetIndex = 0;
|
|
130
130
|
this.mode = Modes.transparent;
|
|
131
131
|
this.recordingId = null;
|
|
132
|
+
this.recordingIdCounter = 0;
|
|
132
133
|
this.replayId = null;
|
|
133
134
|
this.modeTimeout = null;
|
|
134
135
|
this.currentSession = null;
|
|
135
136
|
this.recordingsDir = recordingsDir2;
|
|
136
|
-
this.
|
|
137
|
-
this.replaySequenceMap = /* @__PURE__ */ new Map();
|
|
137
|
+
this.replaySessions = /* @__PURE__ */ new Map();
|
|
138
138
|
this.proxy = httpProxy.createProxyServer({
|
|
139
139
|
secure: false,
|
|
140
|
-
changeOrigin: true
|
|
140
|
+
changeOrigin: true,
|
|
141
|
+
ws: true
|
|
141
142
|
});
|
|
142
143
|
this.setupProxyEventHandlers();
|
|
143
144
|
}
|
|
@@ -205,6 +206,43 @@ var ProxyServer = class {
|
|
|
205
206
|
this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
|
|
206
207
|
return target;
|
|
207
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Extract recording ID from request cookie
|
|
211
|
+
* Used for concurrent replay session routing
|
|
212
|
+
* @param req The incoming HTTP request
|
|
213
|
+
* @returns The recording ID from cookie, or null if not found
|
|
214
|
+
*/
|
|
215
|
+
getRecordingIdFromCookie(req) {
|
|
216
|
+
const cookies = req.headers.cookie;
|
|
217
|
+
if (!cookies) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const match = cookies.match(/proxy-recording-id=([^;]+)/);
|
|
221
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get or create a replay session state for a given recording ID
|
|
225
|
+
* @param recordingId The recording ID to get/create session for
|
|
226
|
+
* @returns The replay session state
|
|
227
|
+
*/
|
|
228
|
+
getOrCreateReplaySession(recordingId) {
|
|
229
|
+
let session = this.replaySessions.get(recordingId);
|
|
230
|
+
if (session) {
|
|
231
|
+
session.lastAccessTime = Date.now();
|
|
232
|
+
} else {
|
|
233
|
+
session = {
|
|
234
|
+
recordingId,
|
|
235
|
+
servedRecordingIdsByKey: /* @__PURE__ */ new Map(),
|
|
236
|
+
loadedSession: null,
|
|
237
|
+
lastAccessTime: Date.now()
|
|
238
|
+
};
|
|
239
|
+
this.replaySessions.set(recordingId, session);
|
|
240
|
+
console.log(
|
|
241
|
+
`[CONCURRENT REPLAY] Created new session for recording: ${recordingId}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return session;
|
|
245
|
+
}
|
|
208
246
|
parseGetParams(req) {
|
|
209
247
|
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
|
210
248
|
const mode = url.searchParams.get("mode");
|
|
@@ -221,16 +259,25 @@ var ProxyServer = class {
|
|
|
221
259
|
let data;
|
|
222
260
|
if (req.method === "GET") {
|
|
223
261
|
data = this.parseGetParams(req);
|
|
224
|
-
} else {
|
|
262
|
+
} else if (req.method === "POST") {
|
|
225
263
|
const body = await readRequestBody(req);
|
|
226
|
-
console.log(
|
|
264
|
+
console.log(`MODE CHANGE (${req.method})`, body);
|
|
227
265
|
data = JSON.parse(body);
|
|
266
|
+
} else {
|
|
267
|
+
return;
|
|
228
268
|
}
|
|
229
269
|
const { mode, id, timeout: requestTimeout } = data;
|
|
230
270
|
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
231
271
|
this.clearModeTimeout();
|
|
232
272
|
await this.switchMode(mode, id);
|
|
233
273
|
this.setupModeTimeout(timeout);
|
|
274
|
+
if (mode === Modes.replay && id) {
|
|
275
|
+
res.setHeader(
|
|
276
|
+
"Set-Cookie",
|
|
277
|
+
`proxy-recording-id=${encodeURIComponent(id)}; HttpOnly; Path=/; SameSite=Lax`
|
|
278
|
+
);
|
|
279
|
+
console.log(`[CONCURRENT REPLAY] Set cookie for recording: ${id}`);
|
|
280
|
+
}
|
|
234
281
|
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
235
282
|
success: true,
|
|
236
283
|
mode: this.mode,
|
|
@@ -245,14 +292,12 @@ var ProxyServer = class {
|
|
|
245
292
|
}
|
|
246
293
|
}
|
|
247
294
|
clearModeTimeout() {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.modeTimeout = null;
|
|
251
|
-
}
|
|
295
|
+
clearTimeout(this.modeTimeout || 0);
|
|
296
|
+
this.modeTimeout = null;
|
|
252
297
|
}
|
|
253
298
|
async switchMode(mode, id) {
|
|
254
|
-
|
|
255
|
-
|
|
299
|
+
console.log(`Switching to ${mode.toUpperCase()} mode`);
|
|
300
|
+
if (this.currentSession && this.mode === Modes.record) {
|
|
256
301
|
await this.saveCurrentSession(true);
|
|
257
302
|
console.log("Session saved, continuing with mode switch");
|
|
258
303
|
}
|
|
@@ -262,11 +307,17 @@ var ProxyServer = class {
|
|
|
262
307
|
break;
|
|
263
308
|
}
|
|
264
309
|
case Modes.record: {
|
|
310
|
+
if (!id) {
|
|
311
|
+
throw new Error("Record ID is required");
|
|
312
|
+
}
|
|
265
313
|
this.switchToRecordMode(id);
|
|
266
314
|
break;
|
|
267
315
|
}
|
|
268
316
|
case Modes.replay: {
|
|
269
|
-
|
|
317
|
+
if (!id) {
|
|
318
|
+
throw new Error("Replay ID is required");
|
|
319
|
+
}
|
|
320
|
+
await this.switchToReplayMode(id);
|
|
270
321
|
break;
|
|
271
322
|
}
|
|
272
323
|
default: {
|
|
@@ -283,36 +334,33 @@ var ProxyServer = class {
|
|
|
283
334
|
console.log("Switched to transparent mode");
|
|
284
335
|
}
|
|
285
336
|
switchToRecordMode(id) {
|
|
286
|
-
if (!id) {
|
|
287
|
-
throw new Error("Record ID is required");
|
|
288
|
-
}
|
|
289
337
|
this.mode = Modes.record;
|
|
290
338
|
this.recordingId = id;
|
|
291
339
|
this.replayId = null;
|
|
292
340
|
this.currentSession = { id, recordings: [], websocketRecordings: [] };
|
|
293
|
-
this.requestSequenceMap.clear();
|
|
294
341
|
console.log(`Switched to record mode with ID: ${id}`);
|
|
295
342
|
}
|
|
296
|
-
switchToReplayMode(id) {
|
|
297
|
-
if (!id) {
|
|
298
|
-
throw new Error("Replay ID is required");
|
|
299
|
-
}
|
|
343
|
+
async switchToReplayMode(id) {
|
|
300
344
|
this.mode = Modes.replay;
|
|
301
345
|
this.replayId = id;
|
|
302
346
|
this.recordingId = null;
|
|
303
347
|
this.currentSession = null;
|
|
304
|
-
this.
|
|
348
|
+
const session = this.replaySessions.get(id);
|
|
349
|
+
if (session) {
|
|
350
|
+
session.servedRecordingIdsByKey.clear();
|
|
351
|
+
console.log(`Reset served recordings tracker for session: ${id}`);
|
|
352
|
+
} else {
|
|
353
|
+
this.getOrCreateReplaySession(id);
|
|
354
|
+
}
|
|
305
355
|
console.log(`Switched to replay mode with ID: ${id}`);
|
|
306
356
|
}
|
|
307
357
|
setupModeTimeout(timeout) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}, timeout);
|
|
315
|
-
}
|
|
358
|
+
this.modeTimeout = setTimeout(async () => {
|
|
359
|
+
console.log("Timeout reached, switching back to transparent mode");
|
|
360
|
+
await this.saveCurrentSession(true);
|
|
361
|
+
this.switchToTransparentMode();
|
|
362
|
+
this.modeTimeout = null;
|
|
363
|
+
}, timeout);
|
|
316
364
|
}
|
|
317
365
|
async saveCurrentSession(filterIncomplete = false) {
|
|
318
366
|
if (!this.currentSession) {
|
|
@@ -338,6 +386,8 @@ var ProxyServer = class {
|
|
|
338
386
|
return;
|
|
339
387
|
}
|
|
340
388
|
const key = getReqID(req);
|
|
389
|
+
const recordingId = this.recordingIdCounter++;
|
|
390
|
+
req.__recordingId = recordingId;
|
|
341
391
|
const record = {
|
|
342
392
|
request: {
|
|
343
393
|
method: req.method,
|
|
@@ -347,44 +397,57 @@ var ProxyServer = class {
|
|
|
347
397
|
},
|
|
348
398
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
349
399
|
key,
|
|
350
|
-
|
|
351
|
-
// Temporary, will be set when response arrives
|
|
400
|
+
recordingId
|
|
352
401
|
};
|
|
353
402
|
this.currentSession.recordings.push(record);
|
|
354
403
|
console.log(
|
|
355
404
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
356
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
405
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, recordingId: ${recordingId}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
357
406
|
);
|
|
358
407
|
}
|
|
359
408
|
updateRequestBodySync(req, body) {
|
|
360
409
|
if (!this.currentSession) {
|
|
361
410
|
return;
|
|
362
411
|
}
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
(
|
|
412
|
+
const recordingId = req.__recordingId;
|
|
413
|
+
if (recordingId === void 0) {
|
|
414
|
+
console.error(
|
|
415
|
+
`updateRequestBodySync: No recording ID found on request ${req.method} ${req.url}`
|
|
416
|
+
);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const record = this.currentSession.recordings.find(
|
|
420
|
+
(r) => r.recordingId === recordingId
|
|
366
421
|
);
|
|
367
422
|
if (!record) {
|
|
368
423
|
console.error(
|
|
369
|
-
`updateRequestBodySync: Could not find
|
|
424
|
+
`updateRequestBodySync: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
370
425
|
);
|
|
371
426
|
return;
|
|
372
427
|
}
|
|
373
428
|
record.request.body = body || null;
|
|
374
429
|
console.log(
|
|
375
|
-
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
|
|
430
|
+
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars, recordingId: ${recordingId})`
|
|
376
431
|
);
|
|
377
432
|
}
|
|
378
433
|
async recordResponse(req, proxyRes) {
|
|
379
434
|
if (!this.currentSession) {
|
|
380
435
|
return;
|
|
381
436
|
}
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
(
|
|
437
|
+
const recordingId = req.__recordingId;
|
|
438
|
+
if (recordingId === void 0) {
|
|
439
|
+
console.error(
|
|
440
|
+
`recordResponse: No recording ID found on request ${req.method} ${req.url}`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const record = this.currentSession.recordings.find(
|
|
445
|
+
(r) => r.recordingId === recordingId
|
|
385
446
|
);
|
|
386
447
|
if (!record) {
|
|
387
|
-
console.error(
|
|
448
|
+
console.error(
|
|
449
|
+
`recordResponse: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
450
|
+
);
|
|
388
451
|
return;
|
|
389
452
|
}
|
|
390
453
|
const chunks = [];
|
|
@@ -398,34 +461,28 @@ var ProxyServer = class {
|
|
|
398
461
|
headers: proxyRes.headers,
|
|
399
462
|
body: body || null
|
|
400
463
|
};
|
|
401
|
-
console.log(
|
|
464
|
+
console.log(
|
|
465
|
+
`Recorded: ${req.method} ${req.url} (recordingId: ${recordingId})`
|
|
466
|
+
);
|
|
402
467
|
});
|
|
403
468
|
}
|
|
404
469
|
async recordResponseData(req, proxyRes, body) {
|
|
405
470
|
if (!this.currentSession) {
|
|
406
471
|
return false;
|
|
407
472
|
}
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
(r) => r.key === key && !r.response
|
|
411
|
-
);
|
|
412
|
-
if (!record) {
|
|
413
|
-
const host = req.headers.host || "unknown";
|
|
414
|
-
const recordsWithKey = this.currentSession.recordings.filter(
|
|
415
|
-
(r) => r.key === key
|
|
416
|
-
);
|
|
473
|
+
const recordingId = req.__recordingId;
|
|
474
|
+
if (recordingId === void 0) {
|
|
417
475
|
console.error(
|
|
418
|
-
`
|
|
419
|
-
);
|
|
420
|
-
console.error(
|
|
421
|
-
` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
|
|
476
|
+
`recordResponseData: No recording ID found on request ${req.method} ${req.url}`
|
|
422
477
|
);
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
const record = this.currentSession.recordings.find(
|
|
481
|
+
(r) => r.recordingId === recordingId
|
|
482
|
+
);
|
|
483
|
+
if (!record) {
|
|
423
484
|
console.error(
|
|
424
|
-
`
|
|
425
|
-
recordsWithKey.map((r) => ({
|
|
426
|
-
seq: r.sequence,
|
|
427
|
-
hasResponse: !!r.response
|
|
428
|
-
}))
|
|
485
|
+
`recordResponseData: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
429
486
|
);
|
|
430
487
|
return false;
|
|
431
488
|
}
|
|
@@ -434,34 +491,78 @@ var ProxyServer = class {
|
|
|
434
491
|
headers: proxyRes.headers,
|
|
435
492
|
body: body || null
|
|
436
493
|
};
|
|
437
|
-
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
438
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
439
|
-
record.sequence = currentSequence;
|
|
440
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
441
494
|
console.log(
|
|
442
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url} (
|
|
495
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (recordingId: ${recordingId})`
|
|
443
496
|
);
|
|
444
497
|
return true;
|
|
445
498
|
}
|
|
446
499
|
async handleReplayRequest(req, res) {
|
|
500
|
+
const recordingId = this.getRecordingIdFromCookie(req) || this.replayId;
|
|
501
|
+
if (!recordingId) {
|
|
502
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
503
|
+
res.writeHead(HTTP_STATUS_BAD_REQUEST, {
|
|
504
|
+
"Content-Type": "application/json",
|
|
505
|
+
...corsHeaders
|
|
506
|
+
});
|
|
507
|
+
res.end(JSON.stringify({ error: "No replay session active" }));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
447
510
|
const key = getReqID(req);
|
|
448
|
-
const filePath = getRecordingPath(this.recordingsDir,
|
|
511
|
+
const filePath = getRecordingPath(this.recordingsDir, recordingId);
|
|
449
512
|
try {
|
|
450
|
-
const
|
|
513
|
+
const sessionState = this.getOrCreateReplaySession(recordingId);
|
|
514
|
+
if (!sessionState.loadedSession) {
|
|
515
|
+
sessionState.loadedSession = await loadRecordingSession(filePath);
|
|
516
|
+
console.log(`[REPLAY] Loaded recording session: ${recordingId}`);
|
|
517
|
+
}
|
|
518
|
+
const session = sessionState.loadedSession;
|
|
519
|
+
if (!sessionState.servedRecordingIdsByKey.has(key)) {
|
|
520
|
+
sessionState.servedRecordingIdsByKey.set(key, /* @__PURE__ */ new Set());
|
|
521
|
+
}
|
|
522
|
+
const servedForThisKey = sessionState.servedRecordingIdsByKey.get(key);
|
|
451
523
|
const host = req.headers.host || "unknown";
|
|
452
|
-
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.
|
|
524
|
+
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.recordingId - b.recordingId);
|
|
453
525
|
if (recordsWithKey.length === 0) {
|
|
454
|
-
|
|
455
|
-
|
|
526
|
+
const errorMsg = `No recording found for ${key} at ${req.method} ${host}${req.url}`;
|
|
527
|
+
console.error(`[REPLAY ERROR] ${errorMsg} (session: ${recordingId})`);
|
|
528
|
+
console.error(
|
|
529
|
+
`[REPLAY ERROR] This request was not made during recording - possible test non-determinism`
|
|
530
|
+
);
|
|
531
|
+
const errorResponse = {
|
|
532
|
+
error: "No recording found",
|
|
533
|
+
message: errorMsg,
|
|
534
|
+
key,
|
|
535
|
+
sessionId: recordingId
|
|
536
|
+
};
|
|
537
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
538
|
+
res.writeHead(HTTP_STATUS_NOT_FOUND, {
|
|
539
|
+
"Content-Type": "application/json",
|
|
540
|
+
...corsHeaders
|
|
541
|
+
});
|
|
542
|
+
res.end(JSON.stringify(errorResponse));
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const requestCount = servedForThisKey.size + 1;
|
|
546
|
+
console.log(
|
|
547
|
+
`[REPLAY REQUEST #${requestCount}] ${req.method} ${req.url} (session: ${recordingId}, total: ${recordsWithKey.length}, served: ${servedForThisKey.size})`
|
|
548
|
+
);
|
|
549
|
+
let record;
|
|
550
|
+
for (const rec of recordsWithKey) {
|
|
551
|
+
if (!servedForThisKey.has(rec.recordingId)) {
|
|
552
|
+
record = rec;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (!record) {
|
|
557
|
+
console.log(
|
|
558
|
+
`[REPLAY WARNING] All ${recordsWithKey.length} recordings already served for ${key} (session: ${recordingId}), reusing last one`
|
|
456
559
|
);
|
|
560
|
+
record = recordsWithKey[recordsWithKey.length - 1];
|
|
457
561
|
}
|
|
458
|
-
|
|
459
|
-
const recordIndex = usageCount % recordsWithKey.length;
|
|
460
|
-
const record = recordsWithKey[recordIndex];
|
|
562
|
+
servedForThisKey.add(record.recordingId);
|
|
461
563
|
console.log(
|
|
462
|
-
`
|
|
564
|
+
`[REPLAY SERVING] recordingId: ${record.recordingId}, session: ${recordingId}, body_len: ${record.response?.body?.length || 0}`
|
|
463
565
|
);
|
|
464
|
-
this.replaySequenceMap.set(key, usageCount + 1);
|
|
465
566
|
if (!record.response) {
|
|
466
567
|
throw new Error(
|
|
467
568
|
`No response recorded for this request: ${req.method} ${host}${req.url}`
|
|
@@ -527,6 +628,7 @@ var ProxyServer = class {
|
|
|
527
628
|
this.proxy.web(req, res, { target });
|
|
528
629
|
}
|
|
529
630
|
}
|
|
631
|
+
// TODO: check if can handle streaming requests
|
|
530
632
|
async bufferAndProxyRequest(req, res, target) {
|
|
531
633
|
const chunks = [];
|
|
532
634
|
req.on("data", (chunk) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "test-proxy-recorder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright testing framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|