test-proxy-recorder 0.1.5 → 0.1.6

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 CHANGED
@@ -138,14 +138,17 @@ var ProxyServer = class {
138
138
  this.proxy.on("error", this.handleProxyError.bind(this));
139
139
  this.proxy.on("proxyRes", this.handleProxyResponse.bind(this));
140
140
  }
141
- handleProxyError(err, _req, res) {
141
+ handleProxyError(err, req, res) {
142
142
  console.error("Proxy error:", err);
143
143
  if (!(res instanceof http__default.default.ServerResponse)) {
144
144
  return;
145
145
  }
146
146
  if (!res.headersSent) {
147
+ const origin = req.headers.origin;
147
148
  res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
148
- "Content-Type": "application/json"
149
+ "Content-Type": "application/json",
150
+ "Access-Control-Allow-Origin": origin || "*",
151
+ "Access-Control-Allow-Credentials": "true"
149
152
  });
150
153
  }
151
154
  res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
@@ -201,7 +204,7 @@ var ProxyServer = class {
201
204
  async switchMode(mode, id) {
202
205
  if (this.currentSession) {
203
206
  console.log("Switching mode, saving current session first");
204
- await this.saveCurrentSession();
207
+ await this.saveCurrentSession(true);
205
208
  console.log("Session saved, continuing with mode switch");
206
209
  }
207
210
  switch (mode) {
@@ -256,13 +259,13 @@ var ProxyServer = class {
256
259
  if (timeout && timeout > 0) {
257
260
  this.modeTimeout = setTimeout(async () => {
258
261
  console.log("Timeout reached, switching back to transparent mode");
259
- await this.saveCurrentSession();
262
+ await this.saveCurrentSession(true);
260
263
  this.switchToTransparentMode();
261
264
  this.modeTimeout = null;
262
265
  }, timeout);
263
266
  }
264
267
  }
265
- async saveCurrentSession() {
268
+ async saveCurrentSession(filterIncomplete = false) {
266
269
  if (!this.currentSession) {
267
270
  console.log("No current session to save");
268
271
  return;
@@ -271,13 +274,27 @@ var ProxyServer = class {
271
274
  console.log("Session has no recordings, skipping save");
272
275
  return;
273
276
  }
277
+ if (filterIncomplete) {
278
+ const incompleteCount = this.currentSession.recordings.filter(
279
+ (r) => !r.response
280
+ ).length;
281
+ if (incompleteCount > 0) {
282
+ console.log(
283
+ `Removing ${incompleteCount} incomplete recording(s) without responses`
284
+ );
285
+ this.currentSession.recordings = this.currentSession.recordings.filter(
286
+ (r) => r.response
287
+ );
288
+ }
289
+ }
274
290
  console.log(
275
291
  `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
276
292
  );
277
293
  await saveRecordingSession(this.recordingsDir, this.currentSession);
278
294
  }
279
- async saveRequestRecord(req, body) {
295
+ saveRequestRecordSync(req, body) {
280
296
  if (!this.currentSession) {
297
+ console.log("saveRequestRecordSync: No current session");
281
298
  return;
282
299
  }
283
300
  const key = getReqID(req);
@@ -295,6 +312,30 @@ var ProxyServer = class {
295
312
  sequence: currentSequence
296
313
  };
297
314
  this.currentSession.recordings.push(record);
315
+ console.log(
316
+ // eslint-disable-next-line sonarjs/no-nested-template-literals
317
+ `saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${currentSequence}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
318
+ );
319
+ }
320
+ updateRequestBodySync(req, body) {
321
+ if (!this.currentSession) {
322
+ console.log("updateRequestBodySync: No current session");
323
+ return;
324
+ }
325
+ const key = getReqID(req);
326
+ const record = this.currentSession.recordings.findLast(
327
+ (r) => r.key === key && !r.response
328
+ );
329
+ if (!record) {
330
+ console.error(
331
+ `updateRequestBodySync: Could not find request record for ${req.method} ${req.url}`
332
+ );
333
+ return;
334
+ }
335
+ record.request.body = body || null;
336
+ console.log(
337
+ `updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
338
+ );
298
339
  }
299
340
  async recordResponse(req, proxyRes) {
300
341
  if (!this.currentSession) {
@@ -323,49 +364,105 @@ var ProxyServer = class {
323
364
  console.log(`Recorded: ${req.method} ${req.url}`);
324
365
  });
325
366
  }
367
+ async recordResponseData(req, proxyRes, body) {
368
+ if (!this.currentSession) {
369
+ console.log("recordResponseData: No current session");
370
+ return false;
371
+ }
372
+ const key = getReqID(req);
373
+ const record = this.currentSession.recordings.findLast(
374
+ (r) => r.key === key && !r.response
375
+ );
376
+ if (!record) {
377
+ const host = req.headers.host || "unknown";
378
+ const recordsWithKey = this.currentSession.recordings.filter(
379
+ (r) => r.key === key
380
+ );
381
+ console.error(
382
+ `Request record not found for response: ${key} at ${req.method} ${host}${req.url}`
383
+ );
384
+ console.error(
385
+ ` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
386
+ );
387
+ console.error(
388
+ ` Records with key:`,
389
+ recordsWithKey.map((r) => ({
390
+ seq: r.sequence,
391
+ hasResponse: !!r.response
392
+ }))
393
+ );
394
+ return false;
395
+ }
396
+ record.response = {
397
+ statusCode: proxyRes.statusCode,
398
+ headers: proxyRes.headers,
399
+ body: body || null
400
+ };
401
+ await this.saveCurrentSession();
402
+ console.log(
403
+ `recordResponseData: Recorded response for ${req.method} ${req.url}`
404
+ );
405
+ return true;
406
+ }
326
407
  async handleReplayRequest(req, res) {
327
408
  const key = getReqID(req);
328
409
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
329
410
  try {
330
411
  const session = await loadRecordingSession(filePath);
331
- const currentSequence = this.replaySequenceMap.get(key) || 0;
332
- const record = session.recordings.find(
333
- (r) => r.key === key && r.sequence === currentSequence
412
+ const host = req.headers.host || "unknown";
413
+ const recordsWithKey = session.recordings.filter(
414
+ (r) => r.key === key && r.response
334
415
  );
335
- if (!record) {
416
+ if (recordsWithKey.length === 0) {
336
417
  throw new Error(
337
- `No recording found for ${key} with sequence ${currentSequence}`
418
+ `No recording found for ${key} at ${req.method} ${host}${req.url}`
338
419
  );
339
420
  }
421
+ const usageCount = this.replaySequenceMap.get(key) || 0;
422
+ const recordIndex = usageCount % recordsWithKey.length;
423
+ const record = recordsWithKey[recordIndex];
424
+ console.log(
425
+ `Replaying ${req.method} ${req.url} (usage: ${usageCount}, using recording ${recordIndex}/${recordsWithKey.length})`
426
+ );
427
+ this.replaySequenceMap.set(key, usageCount + 1);
340
428
  if (!record.response) {
341
- throw new Error("No response recorded for this request");
429
+ throw new Error(
430
+ `No response recorded for this request: ${req.method} ${host}${req.url}`
431
+ );
342
432
  }
343
- this.replaySequenceMap.set(key, currentSequence + 1);
344
433
  const { statusCode, headers, body } = record.response;
345
434
  const origin = req.headers.origin;
346
435
  const responseHeaders = {
347
436
  ...headers,
348
437
  "access-control-allow-origin": origin || "*",
349
- "access-control-allow-credentials": "true"
438
+ "access-control-allow-credentials": "true",
439
+ "access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
440
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
441
+ "access-control-expose-headers": "*"
350
442
  };
351
443
  res.writeHead(statusCode, responseHeaders);
352
444
  res.end(body);
353
- console.log(
354
- `Replayed: ${req.method} ${req.url} (sequence: ${currentSequence})`
355
- );
356
445
  } catch (error) {
357
- this.handleReplayError(res, error, key, filePath);
446
+ this.handleReplayError(req, res, error, key, filePath);
358
447
  }
359
448
  }
360
- handleReplayError(res, err, key, filePath) {
449
+ handleReplayError(req, res, err, key, filePath) {
361
450
  const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
362
451
  console.error("Replay error:", err);
363
- sendJsonResponse(res, HTTP_STATUS_NOT_FOUND, {
364
- error: isFileNotFound ? "Recording file not found" : "Recording not found",
365
- message: err instanceof Error ? err.message : "Unknown error",
366
- key,
367
- filePath
452
+ const origin = req.headers.origin;
453
+ res.writeHead(HTTP_STATUS_NOT_FOUND, {
454
+ "Content-Type": "application/json",
455
+ "Access-Control-Allow-Origin": origin || "*",
456
+ "Access-Control-Allow-Credentials": "true"
368
457
  });
458
+ res.end(
459
+ JSON.stringify({
460
+ error: isFileNotFound ? "Recording file not found" : "Recording not found",
461
+ message: err instanceof Error ? err.message : "Unknown error",
462
+ key,
463
+ filePath
464
+ })
465
+ );
369
466
  }
370
467
  async handleRequest(req, res) {
371
468
  if (req.method === "OPTIONS") {
@@ -395,6 +492,7 @@ var ProxyServer = class {
395
492
  const target = this.getTarget();
396
493
  console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
397
494
  if (this.mode === Modes.record) {
495
+ this.saveRequestRecordSync(req, null);
398
496
  await this.bufferAndProxyRequest(req, res, target);
399
497
  } else {
400
498
  this.proxy.web(req, res, { target });
@@ -405,11 +503,20 @@ var ProxyServer = class {
405
503
  req.on("data", (chunk) => {
406
504
  chunks.push(chunk);
407
505
  });
408
- await new Promise((resolve) => {
409
- req.on("end", () => resolve());
410
- });
506
+ try {
507
+ await new Promise((resolve, reject) => {
508
+ req.on("end", () => resolve());
509
+ req.on("error", (err) => reject(err));
510
+ setTimeout(
511
+ () => reject(new Error("Request buffering timeout")),
512
+ 3e4
513
+ );
514
+ });
515
+ } catch (error) {
516
+ console.error("Error buffering request:", error);
517
+ }
411
518
  const body = Buffer.concat(chunks).toString("utf8");
412
- await this.saveRequestRecord(req, body);
519
+ this.updateRequestBodySync(req, body);
413
520
  const targetUrl = new URL(target);
414
521
  const isHttps = targetUrl.protocol === "https:";
415
522
  const requestModule = isHttps ? https__default.default : http__default.default;
@@ -424,9 +531,38 @@ var ProxyServer = class {
424
531
  },
425
532
  (proxyRes) => {
426
533
  this.addCorsHeaders(proxyRes, req);
427
- this.recordResponse(req, proxyRes);
428
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
429
- proxyRes.pipe(res);
534
+ const responseChunks = [];
535
+ proxyRes.on("data", (chunk) => {
536
+ responseChunks.push(chunk);
537
+ });
538
+ proxyRes.on("end", async () => {
539
+ const responseBody = Buffer.concat(responseChunks);
540
+ const recorded = await this.recordResponseData(
541
+ req,
542
+ proxyRes,
543
+ responseBody.toString("utf8")
544
+ );
545
+ const origin = req.headers.origin;
546
+ const responseHeaders = {
547
+ ...proxyRes.headers,
548
+ "access-control-allow-origin": origin || "*",
549
+ "access-control-allow-credentials": "true",
550
+ "access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
551
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
552
+ "access-control-expose-headers": "*"
553
+ };
554
+ res.writeHead(proxyRes.statusCode || 200, responseHeaders);
555
+ res.end(responseBody);
556
+ if (recorded) {
557
+ console.log(`Recorded: ${req.method} ${req.url}`);
558
+ }
559
+ });
560
+ proxyRes.on("error", (err) => {
561
+ console.error("Proxy response error:", err);
562
+ if (!res.headersSent) {
563
+ this.handleProxyError(err, req, res);
564
+ }
565
+ });
430
566
  }
431
567
  );
432
568
  proxyReq.on("error", (err) => {
package/dist/index.d.cts CHANGED
@@ -30,8 +30,10 @@ declare class ProxyServer {
30
30
  private switchToReplayMode;
31
31
  private setupModeTimeout;
32
32
  private saveCurrentSession;
33
- private saveRequestRecord;
33
+ private saveRequestRecordSync;
34
+ private updateRequestBodySync;
34
35
  private recordResponse;
36
+ private recordResponseData;
35
37
  private handleReplayRequest;
36
38
  private handleReplayError;
37
39
  private handleRequest;
package/dist/index.d.ts CHANGED
@@ -30,8 +30,10 @@ declare class ProxyServer {
30
30
  private switchToReplayMode;
31
31
  private setupModeTimeout;
32
32
  private saveCurrentSession;
33
- private saveRequestRecord;
33
+ private saveRequestRecordSync;
34
+ private updateRequestBodySync;
34
35
  private recordResponse;
36
+ private recordResponseData;
35
37
  private handleReplayRequest;
36
38
  private handleReplayError;
37
39
  private handleRequest;
package/dist/index.mjs CHANGED
@@ -127,14 +127,17 @@ var ProxyServer = class {
127
127
  this.proxy.on("error", this.handleProxyError.bind(this));
128
128
  this.proxy.on("proxyRes", this.handleProxyResponse.bind(this));
129
129
  }
130
- handleProxyError(err, _req, res) {
130
+ handleProxyError(err, req, res) {
131
131
  console.error("Proxy error:", err);
132
132
  if (!(res instanceof http.ServerResponse)) {
133
133
  return;
134
134
  }
135
135
  if (!res.headersSent) {
136
+ const origin = req.headers.origin;
136
137
  res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
137
- "Content-Type": "application/json"
138
+ "Content-Type": "application/json",
139
+ "Access-Control-Allow-Origin": origin || "*",
140
+ "Access-Control-Allow-Credentials": "true"
138
141
  });
139
142
  }
140
143
  res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
@@ -190,7 +193,7 @@ var ProxyServer = class {
190
193
  async switchMode(mode, id) {
191
194
  if (this.currentSession) {
192
195
  console.log("Switching mode, saving current session first");
193
- await this.saveCurrentSession();
196
+ await this.saveCurrentSession(true);
194
197
  console.log("Session saved, continuing with mode switch");
195
198
  }
196
199
  switch (mode) {
@@ -245,13 +248,13 @@ var ProxyServer = class {
245
248
  if (timeout && timeout > 0) {
246
249
  this.modeTimeout = setTimeout(async () => {
247
250
  console.log("Timeout reached, switching back to transparent mode");
248
- await this.saveCurrentSession();
251
+ await this.saveCurrentSession(true);
249
252
  this.switchToTransparentMode();
250
253
  this.modeTimeout = null;
251
254
  }, timeout);
252
255
  }
253
256
  }
254
- async saveCurrentSession() {
257
+ async saveCurrentSession(filterIncomplete = false) {
255
258
  if (!this.currentSession) {
256
259
  console.log("No current session to save");
257
260
  return;
@@ -260,13 +263,27 @@ var ProxyServer = class {
260
263
  console.log("Session has no recordings, skipping save");
261
264
  return;
262
265
  }
266
+ if (filterIncomplete) {
267
+ const incompleteCount = this.currentSession.recordings.filter(
268
+ (r) => !r.response
269
+ ).length;
270
+ if (incompleteCount > 0) {
271
+ console.log(
272
+ `Removing ${incompleteCount} incomplete recording(s) without responses`
273
+ );
274
+ this.currentSession.recordings = this.currentSession.recordings.filter(
275
+ (r) => r.response
276
+ );
277
+ }
278
+ }
263
279
  console.log(
264
280
  `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
265
281
  );
266
282
  await saveRecordingSession(this.recordingsDir, this.currentSession);
267
283
  }
268
- async saveRequestRecord(req, body) {
284
+ saveRequestRecordSync(req, body) {
269
285
  if (!this.currentSession) {
286
+ console.log("saveRequestRecordSync: No current session");
270
287
  return;
271
288
  }
272
289
  const key = getReqID(req);
@@ -284,6 +301,30 @@ var ProxyServer = class {
284
301
  sequence: currentSequence
285
302
  };
286
303
  this.currentSession.recordings.push(record);
304
+ console.log(
305
+ // eslint-disable-next-line sonarjs/no-nested-template-literals
306
+ `saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${currentSequence}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
307
+ );
308
+ }
309
+ updateRequestBodySync(req, body) {
310
+ if (!this.currentSession) {
311
+ console.log("updateRequestBodySync: No current session");
312
+ return;
313
+ }
314
+ const key = getReqID(req);
315
+ const record = this.currentSession.recordings.findLast(
316
+ (r) => r.key === key && !r.response
317
+ );
318
+ if (!record) {
319
+ console.error(
320
+ `updateRequestBodySync: Could not find request record for ${req.method} ${req.url}`
321
+ );
322
+ return;
323
+ }
324
+ record.request.body = body || null;
325
+ console.log(
326
+ `updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
327
+ );
287
328
  }
288
329
  async recordResponse(req, proxyRes) {
289
330
  if (!this.currentSession) {
@@ -312,49 +353,105 @@ var ProxyServer = class {
312
353
  console.log(`Recorded: ${req.method} ${req.url}`);
313
354
  });
314
355
  }
356
+ async recordResponseData(req, proxyRes, body) {
357
+ if (!this.currentSession) {
358
+ console.log("recordResponseData: No current session");
359
+ return false;
360
+ }
361
+ const key = getReqID(req);
362
+ const record = this.currentSession.recordings.findLast(
363
+ (r) => r.key === key && !r.response
364
+ );
365
+ if (!record) {
366
+ const host = req.headers.host || "unknown";
367
+ const recordsWithKey = this.currentSession.recordings.filter(
368
+ (r) => r.key === key
369
+ );
370
+ console.error(
371
+ `Request record not found for response: ${key} at ${req.method} ${host}${req.url}`
372
+ );
373
+ console.error(
374
+ ` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
375
+ );
376
+ console.error(
377
+ ` Records with key:`,
378
+ recordsWithKey.map((r) => ({
379
+ seq: r.sequence,
380
+ hasResponse: !!r.response
381
+ }))
382
+ );
383
+ return false;
384
+ }
385
+ record.response = {
386
+ statusCode: proxyRes.statusCode,
387
+ headers: proxyRes.headers,
388
+ body: body || null
389
+ };
390
+ await this.saveCurrentSession();
391
+ console.log(
392
+ `recordResponseData: Recorded response for ${req.method} ${req.url}`
393
+ );
394
+ return true;
395
+ }
315
396
  async handleReplayRequest(req, res) {
316
397
  const key = getReqID(req);
317
398
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
318
399
  try {
319
400
  const session = await loadRecordingSession(filePath);
320
- const currentSequence = this.replaySequenceMap.get(key) || 0;
321
- const record = session.recordings.find(
322
- (r) => r.key === key && r.sequence === currentSequence
401
+ const host = req.headers.host || "unknown";
402
+ const recordsWithKey = session.recordings.filter(
403
+ (r) => r.key === key && r.response
323
404
  );
324
- if (!record) {
405
+ if (recordsWithKey.length === 0) {
325
406
  throw new Error(
326
- `No recording found for ${key} with sequence ${currentSequence}`
407
+ `No recording found for ${key} at ${req.method} ${host}${req.url}`
327
408
  );
328
409
  }
410
+ const usageCount = this.replaySequenceMap.get(key) || 0;
411
+ const recordIndex = usageCount % recordsWithKey.length;
412
+ const record = recordsWithKey[recordIndex];
413
+ console.log(
414
+ `Replaying ${req.method} ${req.url} (usage: ${usageCount}, using recording ${recordIndex}/${recordsWithKey.length})`
415
+ );
416
+ this.replaySequenceMap.set(key, usageCount + 1);
329
417
  if (!record.response) {
330
- throw new Error("No response recorded for this request");
418
+ throw new Error(
419
+ `No response recorded for this request: ${req.method} ${host}${req.url}`
420
+ );
331
421
  }
332
- this.replaySequenceMap.set(key, currentSequence + 1);
333
422
  const { statusCode, headers, body } = record.response;
334
423
  const origin = req.headers.origin;
335
424
  const responseHeaders = {
336
425
  ...headers,
337
426
  "access-control-allow-origin": origin || "*",
338
- "access-control-allow-credentials": "true"
427
+ "access-control-allow-credentials": "true",
428
+ "access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
429
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
430
+ "access-control-expose-headers": "*"
339
431
  };
340
432
  res.writeHead(statusCode, responseHeaders);
341
433
  res.end(body);
342
- console.log(
343
- `Replayed: ${req.method} ${req.url} (sequence: ${currentSequence})`
344
- );
345
434
  } catch (error) {
346
- this.handleReplayError(res, error, key, filePath);
435
+ this.handleReplayError(req, res, error, key, filePath);
347
436
  }
348
437
  }
349
- handleReplayError(res, err, key, filePath) {
438
+ handleReplayError(req, res, err, key, filePath) {
350
439
  const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
351
440
  console.error("Replay error:", err);
352
- sendJsonResponse(res, HTTP_STATUS_NOT_FOUND, {
353
- error: isFileNotFound ? "Recording file not found" : "Recording not found",
354
- message: err instanceof Error ? err.message : "Unknown error",
355
- key,
356
- filePath
441
+ const origin = req.headers.origin;
442
+ res.writeHead(HTTP_STATUS_NOT_FOUND, {
443
+ "Content-Type": "application/json",
444
+ "Access-Control-Allow-Origin": origin || "*",
445
+ "Access-Control-Allow-Credentials": "true"
357
446
  });
447
+ res.end(
448
+ JSON.stringify({
449
+ error: isFileNotFound ? "Recording file not found" : "Recording not found",
450
+ message: err instanceof Error ? err.message : "Unknown error",
451
+ key,
452
+ filePath
453
+ })
454
+ );
358
455
  }
359
456
  async handleRequest(req, res) {
360
457
  if (req.method === "OPTIONS") {
@@ -384,6 +481,7 @@ var ProxyServer = class {
384
481
  const target = this.getTarget();
385
482
  console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
386
483
  if (this.mode === Modes.record) {
484
+ this.saveRequestRecordSync(req, null);
387
485
  await this.bufferAndProxyRequest(req, res, target);
388
486
  } else {
389
487
  this.proxy.web(req, res, { target });
@@ -394,11 +492,20 @@ var ProxyServer = class {
394
492
  req.on("data", (chunk) => {
395
493
  chunks.push(chunk);
396
494
  });
397
- await new Promise((resolve) => {
398
- req.on("end", () => resolve());
399
- });
495
+ try {
496
+ await new Promise((resolve, reject) => {
497
+ req.on("end", () => resolve());
498
+ req.on("error", (err) => reject(err));
499
+ setTimeout(
500
+ () => reject(new Error("Request buffering timeout")),
501
+ 3e4
502
+ );
503
+ });
504
+ } catch (error) {
505
+ console.error("Error buffering request:", error);
506
+ }
400
507
  const body = Buffer.concat(chunks).toString("utf8");
401
- await this.saveRequestRecord(req, body);
508
+ this.updateRequestBodySync(req, body);
402
509
  const targetUrl = new URL(target);
403
510
  const isHttps = targetUrl.protocol === "https:";
404
511
  const requestModule = isHttps ? https : http;
@@ -413,9 +520,38 @@ var ProxyServer = class {
413
520
  },
414
521
  (proxyRes) => {
415
522
  this.addCorsHeaders(proxyRes, req);
416
- this.recordResponse(req, proxyRes);
417
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
418
- proxyRes.pipe(res);
523
+ const responseChunks = [];
524
+ proxyRes.on("data", (chunk) => {
525
+ responseChunks.push(chunk);
526
+ });
527
+ proxyRes.on("end", async () => {
528
+ const responseBody = Buffer.concat(responseChunks);
529
+ const recorded = await this.recordResponseData(
530
+ req,
531
+ proxyRes,
532
+ responseBody.toString("utf8")
533
+ );
534
+ const origin = req.headers.origin;
535
+ const responseHeaders = {
536
+ ...proxyRes.headers,
537
+ "access-control-allow-origin": origin || "*",
538
+ "access-control-allow-credentials": "true",
539
+ "access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
540
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
541
+ "access-control-expose-headers": "*"
542
+ };
543
+ res.writeHead(proxyRes.statusCode || 200, responseHeaders);
544
+ res.end(responseBody);
545
+ if (recorded) {
546
+ console.log(`Recorded: ${req.method} ${req.url}`);
547
+ }
548
+ });
549
+ proxyRes.on("error", (err) => {
550
+ console.error("Proxy response error:", err);
551
+ if (!res.headersSent) {
552
+ this.handleProxyError(err, req, res);
553
+ }
554
+ });
419
555
  }
420
556
  );
421
557
  proxyReq.on("error", (err) => {