test-proxy-recorder 0.1.8 → 0.1.9
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-CBjvm5rb.d.cts → index-CG-XcFDa.d.cts} +1 -0
- package/dist/{index-CBjvm5rb.d.ts → index-CG-XcFDa.d.ts} +1 -0
- package/dist/index.cjs +72 -42
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +71 -42
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/proxy.js +71 -42
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var https = require('https');
|
|
|
6
6
|
var httpProxy = require('http-proxy');
|
|
7
7
|
var ws = require('ws');
|
|
8
8
|
var path = require('path');
|
|
9
|
+
var crypto = require('crypto');
|
|
9
10
|
var filenamify = require('filenamify');
|
|
10
11
|
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -15,6 +16,7 @@ var http__default = /*#__PURE__*/_interopDefault(http);
|
|
|
15
16
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
16
17
|
var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
|
|
17
18
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
19
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
18
20
|
var filenamify__default = /*#__PURE__*/_interopDefault(filenamify);
|
|
19
21
|
|
|
20
22
|
// src/ProxyServer.ts
|
|
@@ -53,7 +55,6 @@ async function saveRecordingSession(recordingsDir, session) {
|
|
|
53
55
|
`Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
|
|
54
56
|
);
|
|
55
57
|
}
|
|
56
|
-
var QUERY_HASH_LENGTH = 8;
|
|
57
58
|
function getReqID(req) {
|
|
58
59
|
const urlParts = req.url.split("?");
|
|
59
60
|
const pathname = urlParts[0];
|
|
@@ -68,7 +69,7 @@ function generateQueryHash(query) {
|
|
|
68
69
|
if (!query) {
|
|
69
70
|
return "";
|
|
70
71
|
}
|
|
71
|
-
const hash =
|
|
72
|
+
const hash = crypto__default.default.createHash("md5").update(query).digest("hex").slice(0, 16);
|
|
72
73
|
return `_${hash}`;
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -101,11 +102,14 @@ var ProxyServer = class {
|
|
|
101
102
|
// Track sequence per request key
|
|
102
103
|
replaySequenceMap;
|
|
103
104
|
// Track replay position per request key
|
|
105
|
+
recordingIdCounter;
|
|
106
|
+
// Unique ID for each recording entry
|
|
104
107
|
constructor(targets, recordingsDir) {
|
|
105
108
|
this.targets = targets;
|
|
106
109
|
this.currentTargetIndex = 0;
|
|
107
110
|
this.mode = Modes.transparent;
|
|
108
111
|
this.recordingId = null;
|
|
112
|
+
this.recordingIdCounter = 0;
|
|
109
113
|
this.replayId = null;
|
|
110
114
|
this.modeTimeout = null;
|
|
111
115
|
this.currentSession = null;
|
|
@@ -315,6 +319,11 @@ var ProxyServer = class {
|
|
|
315
319
|
return;
|
|
316
320
|
}
|
|
317
321
|
const key = getReqID(req);
|
|
322
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
323
|
+
const sequence = currentSequence;
|
|
324
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
325
|
+
const recordingId = this.recordingIdCounter++;
|
|
326
|
+
req.__recordingId = recordingId;
|
|
318
327
|
const record = {
|
|
319
328
|
request: {
|
|
320
329
|
method: req.method,
|
|
@@ -324,44 +333,58 @@ var ProxyServer = class {
|
|
|
324
333
|
},
|
|
325
334
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
326
335
|
key,
|
|
327
|
-
sequence
|
|
328
|
-
|
|
336
|
+
sequence,
|
|
337
|
+
recordingId
|
|
329
338
|
};
|
|
330
339
|
this.currentSession.recordings.push(record);
|
|
331
340
|
console.log(
|
|
332
341
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
333
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
342
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${sequence}, recordingId: ${recordingId}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
334
343
|
);
|
|
335
344
|
}
|
|
336
345
|
updateRequestBodySync(req, body) {
|
|
337
346
|
if (!this.currentSession) {
|
|
338
347
|
return;
|
|
339
348
|
}
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
(
|
|
349
|
+
const recordingId = req.__recordingId;
|
|
350
|
+
if (recordingId === void 0) {
|
|
351
|
+
console.error(
|
|
352
|
+
`updateRequestBodySync: No recording ID found on request ${req.method} ${req.url}`
|
|
353
|
+
);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const record = this.currentSession.recordings.find(
|
|
357
|
+
(r) => r.recordingId === recordingId
|
|
343
358
|
);
|
|
344
359
|
if (!record) {
|
|
345
360
|
console.error(
|
|
346
|
-
`updateRequestBodySync: Could not find
|
|
361
|
+
`updateRequestBodySync: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
347
362
|
);
|
|
348
363
|
return;
|
|
349
364
|
}
|
|
350
365
|
record.request.body = body || null;
|
|
351
366
|
console.log(
|
|
352
|
-
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
|
|
367
|
+
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars, recordingId: ${recordingId})`
|
|
353
368
|
);
|
|
354
369
|
}
|
|
355
370
|
async recordResponse(req, proxyRes) {
|
|
356
371
|
if (!this.currentSession) {
|
|
357
372
|
return;
|
|
358
373
|
}
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
(
|
|
374
|
+
const recordingId = req.__recordingId;
|
|
375
|
+
if (recordingId === void 0) {
|
|
376
|
+
console.error(
|
|
377
|
+
`recordResponse: No recording ID found on request ${req.method} ${req.url}`
|
|
378
|
+
);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const record = this.currentSession.recordings.find(
|
|
382
|
+
(r) => r.recordingId === recordingId
|
|
362
383
|
);
|
|
363
384
|
if (!record) {
|
|
364
|
-
console.error(
|
|
385
|
+
console.error(
|
|
386
|
+
`recordResponse: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
387
|
+
);
|
|
365
388
|
return;
|
|
366
389
|
}
|
|
367
390
|
const chunks = [];
|
|
@@ -375,34 +398,28 @@ var ProxyServer = class {
|
|
|
375
398
|
headers: proxyRes.headers,
|
|
376
399
|
body: body || null
|
|
377
400
|
};
|
|
378
|
-
console.log(
|
|
401
|
+
console.log(
|
|
402
|
+
`Recorded: ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
403
|
+
);
|
|
379
404
|
});
|
|
380
405
|
}
|
|
381
406
|
async recordResponseData(req, proxyRes, body) {
|
|
382
407
|
if (!this.currentSession) {
|
|
383
408
|
return false;
|
|
384
409
|
}
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
(r) => r.key === key && !r.response
|
|
388
|
-
);
|
|
389
|
-
if (!record) {
|
|
390
|
-
const host = req.headers.host || "unknown";
|
|
391
|
-
const recordsWithKey = this.currentSession.recordings.filter(
|
|
392
|
-
(r) => r.key === key
|
|
393
|
-
);
|
|
410
|
+
const recordingId = req.__recordingId;
|
|
411
|
+
if (recordingId === void 0) {
|
|
394
412
|
console.error(
|
|
395
|
-
`
|
|
396
|
-
);
|
|
397
|
-
console.error(
|
|
398
|
-
` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
|
|
413
|
+
`recordResponseData: No recording ID found on request ${req.method} ${req.url}`
|
|
399
414
|
);
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
const record = this.currentSession.recordings.find(
|
|
418
|
+
(r) => r.recordingId === recordingId
|
|
419
|
+
);
|
|
420
|
+
if (!record) {
|
|
400
421
|
console.error(
|
|
401
|
-
`
|
|
402
|
-
recordsWithKey.map((r) => ({
|
|
403
|
-
seq: r.sequence,
|
|
404
|
-
hasResponse: !!r.response
|
|
405
|
-
}))
|
|
422
|
+
`recordResponseData: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
406
423
|
);
|
|
407
424
|
return false;
|
|
408
425
|
}
|
|
@@ -411,12 +428,8 @@ var ProxyServer = class {
|
|
|
411
428
|
headers: proxyRes.headers,
|
|
412
429
|
body: body || null
|
|
413
430
|
};
|
|
414
|
-
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
415
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
416
|
-
record.sequence = currentSequence;
|
|
417
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
418
431
|
console.log(
|
|
419
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
432
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
420
433
|
);
|
|
421
434
|
return true;
|
|
422
435
|
}
|
|
@@ -428,13 +441,30 @@ var ProxyServer = class {
|
|
|
428
441
|
const host = req.headers.host || "unknown";
|
|
429
442
|
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
430
443
|
if (recordsWithKey.length === 0) {
|
|
431
|
-
|
|
432
|
-
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
444
|
+
console.warn(
|
|
445
|
+
`No recording found for ${key} at ${req.method} ${host}${req.url}, returning default response`
|
|
433
446
|
);
|
|
447
|
+
const defaultResponse = req.method === "GET" ? {
|
|
448
|
+
data: [],
|
|
449
|
+
items: [],
|
|
450
|
+
results: [],
|
|
451
|
+
updated_at: "0001-01-01T00:00:00Z"
|
|
452
|
+
} : { success: true };
|
|
453
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
454
|
+
res.writeHead(HTTP_STATUS_OK, {
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
...corsHeaders
|
|
457
|
+
});
|
|
458
|
+
res.end(JSON.stringify(defaultResponse));
|
|
459
|
+
return;
|
|
434
460
|
}
|
|
435
461
|
const usageCount = this.replaySequenceMap.get(key) || 0;
|
|
436
|
-
|
|
437
|
-
|
|
462
|
+
let record;
|
|
463
|
+
if (usageCount < recordsWithKey.length) {
|
|
464
|
+
record = recordsWithKey[usageCount];
|
|
465
|
+
} else {
|
|
466
|
+
record = recordsWithKey[recordsWithKey.length - 1];
|
|
467
|
+
}
|
|
438
468
|
console.log(
|
|
439
469
|
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
440
470
|
);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-
|
|
2
|
+
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-CG-XcFDa.cjs';
|
|
3
3
|
import '@playwright/test';
|
|
4
4
|
|
|
5
5
|
declare class ProxyServer {
|
|
@@ -14,6 +14,7 @@ declare class ProxyServer {
|
|
|
14
14
|
private recordingsDir;
|
|
15
15
|
private requestSequenceMap;
|
|
16
16
|
private replaySequenceMap;
|
|
17
|
+
private recordingIdCounter;
|
|
17
18
|
constructor(targets: string[], recordingsDir: string);
|
|
18
19
|
init(): Promise<void>;
|
|
19
20
|
listen(port: number): http.Server;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-
|
|
2
|
+
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-CG-XcFDa.js';
|
|
3
3
|
import '@playwright/test';
|
|
4
4
|
|
|
5
5
|
declare class ProxyServer {
|
|
@@ -14,6 +14,7 @@ declare class ProxyServer {
|
|
|
14
14
|
private recordingsDir;
|
|
15
15
|
private requestSequenceMap;
|
|
16
16
|
private replaySequenceMap;
|
|
17
|
+
private recordingIdCounter;
|
|
17
18
|
constructor(targets: string[], recordingsDir: string);
|
|
18
19
|
init(): Promise<void>;
|
|
19
20
|
listen(port: number): http.Server;
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import https from 'https';
|
|
|
4
4
|
import httpProxy from 'http-proxy';
|
|
5
5
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import crypto from 'crypto';
|
|
7
8
|
import filenamify from 'filenamify';
|
|
8
9
|
|
|
9
10
|
// src/ProxyServer.ts
|
|
@@ -42,7 +43,6 @@ async function saveRecordingSession(recordingsDir, session) {
|
|
|
42
43
|
`Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
|
|
43
44
|
);
|
|
44
45
|
}
|
|
45
|
-
var QUERY_HASH_LENGTH = 8;
|
|
46
46
|
function getReqID(req) {
|
|
47
47
|
const urlParts = req.url.split("?");
|
|
48
48
|
const pathname = urlParts[0];
|
|
@@ -57,7 +57,7 @@ function generateQueryHash(query) {
|
|
|
57
57
|
if (!query) {
|
|
58
58
|
return "";
|
|
59
59
|
}
|
|
60
|
-
const hash =
|
|
60
|
+
const hash = crypto.createHash("md5").update(query).digest("hex").slice(0, 16);
|
|
61
61
|
return `_${hash}`;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -90,11 +90,14 @@ var ProxyServer = class {
|
|
|
90
90
|
// Track sequence per request key
|
|
91
91
|
replaySequenceMap;
|
|
92
92
|
// Track replay position per request key
|
|
93
|
+
recordingIdCounter;
|
|
94
|
+
// Unique ID for each recording entry
|
|
93
95
|
constructor(targets, recordingsDir) {
|
|
94
96
|
this.targets = targets;
|
|
95
97
|
this.currentTargetIndex = 0;
|
|
96
98
|
this.mode = Modes.transparent;
|
|
97
99
|
this.recordingId = null;
|
|
100
|
+
this.recordingIdCounter = 0;
|
|
98
101
|
this.replayId = null;
|
|
99
102
|
this.modeTimeout = null;
|
|
100
103
|
this.currentSession = null;
|
|
@@ -304,6 +307,11 @@ var ProxyServer = class {
|
|
|
304
307
|
return;
|
|
305
308
|
}
|
|
306
309
|
const key = getReqID(req);
|
|
310
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
311
|
+
const sequence = currentSequence;
|
|
312
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
313
|
+
const recordingId = this.recordingIdCounter++;
|
|
314
|
+
req.__recordingId = recordingId;
|
|
307
315
|
const record = {
|
|
308
316
|
request: {
|
|
309
317
|
method: req.method,
|
|
@@ -313,44 +321,58 @@ var ProxyServer = class {
|
|
|
313
321
|
},
|
|
314
322
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
315
323
|
key,
|
|
316
|
-
sequence
|
|
317
|
-
|
|
324
|
+
sequence,
|
|
325
|
+
recordingId
|
|
318
326
|
};
|
|
319
327
|
this.currentSession.recordings.push(record);
|
|
320
328
|
console.log(
|
|
321
329
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
322
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
330
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${sequence}, recordingId: ${recordingId}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
323
331
|
);
|
|
324
332
|
}
|
|
325
333
|
updateRequestBodySync(req, body) {
|
|
326
334
|
if (!this.currentSession) {
|
|
327
335
|
return;
|
|
328
336
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
(
|
|
337
|
+
const recordingId = req.__recordingId;
|
|
338
|
+
if (recordingId === void 0) {
|
|
339
|
+
console.error(
|
|
340
|
+
`updateRequestBodySync: No recording ID found on request ${req.method} ${req.url}`
|
|
341
|
+
);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const record = this.currentSession.recordings.find(
|
|
345
|
+
(r) => r.recordingId === recordingId
|
|
332
346
|
);
|
|
333
347
|
if (!record) {
|
|
334
348
|
console.error(
|
|
335
|
-
`updateRequestBodySync: Could not find
|
|
349
|
+
`updateRequestBodySync: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
336
350
|
);
|
|
337
351
|
return;
|
|
338
352
|
}
|
|
339
353
|
record.request.body = body || null;
|
|
340
354
|
console.log(
|
|
341
|
-
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
|
|
355
|
+
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars, recordingId: ${recordingId})`
|
|
342
356
|
);
|
|
343
357
|
}
|
|
344
358
|
async recordResponse(req, proxyRes) {
|
|
345
359
|
if (!this.currentSession) {
|
|
346
360
|
return;
|
|
347
361
|
}
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
(
|
|
362
|
+
const recordingId = req.__recordingId;
|
|
363
|
+
if (recordingId === void 0) {
|
|
364
|
+
console.error(
|
|
365
|
+
`recordResponse: No recording ID found on request ${req.method} ${req.url}`
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const record = this.currentSession.recordings.find(
|
|
370
|
+
(r) => r.recordingId === recordingId
|
|
351
371
|
);
|
|
352
372
|
if (!record) {
|
|
353
|
-
console.error(
|
|
373
|
+
console.error(
|
|
374
|
+
`recordResponse: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
375
|
+
);
|
|
354
376
|
return;
|
|
355
377
|
}
|
|
356
378
|
const chunks = [];
|
|
@@ -364,34 +386,28 @@ var ProxyServer = class {
|
|
|
364
386
|
headers: proxyRes.headers,
|
|
365
387
|
body: body || null
|
|
366
388
|
};
|
|
367
|
-
console.log(
|
|
389
|
+
console.log(
|
|
390
|
+
`Recorded: ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
391
|
+
);
|
|
368
392
|
});
|
|
369
393
|
}
|
|
370
394
|
async recordResponseData(req, proxyRes, body) {
|
|
371
395
|
if (!this.currentSession) {
|
|
372
396
|
return false;
|
|
373
397
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
(r) => r.key === key && !r.response
|
|
377
|
-
);
|
|
378
|
-
if (!record) {
|
|
379
|
-
const host = req.headers.host || "unknown";
|
|
380
|
-
const recordsWithKey = this.currentSession.recordings.filter(
|
|
381
|
-
(r) => r.key === key
|
|
382
|
-
);
|
|
398
|
+
const recordingId = req.__recordingId;
|
|
399
|
+
if (recordingId === void 0) {
|
|
383
400
|
console.error(
|
|
384
|
-
`
|
|
385
|
-
);
|
|
386
|
-
console.error(
|
|
387
|
-
` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
|
|
401
|
+
`recordResponseData: No recording ID found on request ${req.method} ${req.url}`
|
|
388
402
|
);
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
const record = this.currentSession.recordings.find(
|
|
406
|
+
(r) => r.recordingId === recordingId
|
|
407
|
+
);
|
|
408
|
+
if (!record) {
|
|
389
409
|
console.error(
|
|
390
|
-
`
|
|
391
|
-
recordsWithKey.map((r) => ({
|
|
392
|
-
seq: r.sequence,
|
|
393
|
-
hasResponse: !!r.response
|
|
394
|
-
}))
|
|
410
|
+
`recordResponseData: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
395
411
|
);
|
|
396
412
|
return false;
|
|
397
413
|
}
|
|
@@ -400,12 +416,8 @@ var ProxyServer = class {
|
|
|
400
416
|
headers: proxyRes.headers,
|
|
401
417
|
body: body || null
|
|
402
418
|
};
|
|
403
|
-
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
404
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
405
|
-
record.sequence = currentSequence;
|
|
406
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
407
419
|
console.log(
|
|
408
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
420
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
409
421
|
);
|
|
410
422
|
return true;
|
|
411
423
|
}
|
|
@@ -417,13 +429,30 @@ var ProxyServer = class {
|
|
|
417
429
|
const host = req.headers.host || "unknown";
|
|
418
430
|
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
419
431
|
if (recordsWithKey.length === 0) {
|
|
420
|
-
|
|
421
|
-
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
432
|
+
console.warn(
|
|
433
|
+
`No recording found for ${key} at ${req.method} ${host}${req.url}, returning default response`
|
|
422
434
|
);
|
|
435
|
+
const defaultResponse = req.method === "GET" ? {
|
|
436
|
+
data: [],
|
|
437
|
+
items: [],
|
|
438
|
+
results: [],
|
|
439
|
+
updated_at: "0001-01-01T00:00:00Z"
|
|
440
|
+
} : { success: true };
|
|
441
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
442
|
+
res.writeHead(HTTP_STATUS_OK, {
|
|
443
|
+
"Content-Type": "application/json",
|
|
444
|
+
...corsHeaders
|
|
445
|
+
});
|
|
446
|
+
res.end(JSON.stringify(defaultResponse));
|
|
447
|
+
return;
|
|
423
448
|
}
|
|
424
449
|
const usageCount = this.replaySequenceMap.get(key) || 0;
|
|
425
|
-
|
|
426
|
-
|
|
450
|
+
let record;
|
|
451
|
+
if (usageCount < recordsWithKey.length) {
|
|
452
|
+
record = recordsWithKey[usageCount];
|
|
453
|
+
} else {
|
|
454
|
+
record = recordsWithKey[recordsWithKey.length - 1];
|
|
455
|
+
}
|
|
427
456
|
console.log(
|
|
428
457
|
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
429
458
|
);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import '@playwright/test';
|
|
2
|
-
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-
|
|
2
|
+
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CG-XcFDa.cjs';
|
|
3
3
|
import 'node:http';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import '@playwright/test';
|
|
2
|
-
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-
|
|
2
|
+
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CG-XcFDa.js';
|
|
3
3
|
import 'node:http';
|
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
|
|
|
@@ -124,11 +124,14 @@ var ProxyServer = class {
|
|
|
124
124
|
// Track sequence per request key
|
|
125
125
|
replaySequenceMap;
|
|
126
126
|
// Track replay position per request key
|
|
127
|
+
recordingIdCounter;
|
|
128
|
+
// Unique ID for each recording entry
|
|
127
129
|
constructor(targets2, recordingsDir2) {
|
|
128
130
|
this.targets = targets2;
|
|
129
131
|
this.currentTargetIndex = 0;
|
|
130
132
|
this.mode = Modes.transparent;
|
|
131
133
|
this.recordingId = null;
|
|
134
|
+
this.recordingIdCounter = 0;
|
|
132
135
|
this.replayId = null;
|
|
133
136
|
this.modeTimeout = null;
|
|
134
137
|
this.currentSession = null;
|
|
@@ -338,6 +341,11 @@ var ProxyServer = class {
|
|
|
338
341
|
return;
|
|
339
342
|
}
|
|
340
343
|
const key = getReqID(req);
|
|
344
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
345
|
+
const sequence = currentSequence;
|
|
346
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
347
|
+
const recordingId = this.recordingIdCounter++;
|
|
348
|
+
req.__recordingId = recordingId;
|
|
341
349
|
const record = {
|
|
342
350
|
request: {
|
|
343
351
|
method: req.method,
|
|
@@ -347,44 +355,58 @@ var ProxyServer = class {
|
|
|
347
355
|
},
|
|
348
356
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
349
357
|
key,
|
|
350
|
-
sequence
|
|
351
|
-
|
|
358
|
+
sequence,
|
|
359
|
+
recordingId
|
|
352
360
|
};
|
|
353
361
|
this.currentSession.recordings.push(record);
|
|
354
362
|
console.log(
|
|
355
363
|
// 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})`
|
|
364
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${sequence}, recordingId: ${recordingId}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
357
365
|
);
|
|
358
366
|
}
|
|
359
367
|
updateRequestBodySync(req, body) {
|
|
360
368
|
if (!this.currentSession) {
|
|
361
369
|
return;
|
|
362
370
|
}
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
(
|
|
371
|
+
const recordingId = req.__recordingId;
|
|
372
|
+
if (recordingId === void 0) {
|
|
373
|
+
console.error(
|
|
374
|
+
`updateRequestBodySync: No recording ID found on request ${req.method} ${req.url}`
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const record = this.currentSession.recordings.find(
|
|
379
|
+
(r) => r.recordingId === recordingId
|
|
366
380
|
);
|
|
367
381
|
if (!record) {
|
|
368
382
|
console.error(
|
|
369
|
-
`updateRequestBodySync: Could not find
|
|
383
|
+
`updateRequestBodySync: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
370
384
|
);
|
|
371
385
|
return;
|
|
372
386
|
}
|
|
373
387
|
record.request.body = body || null;
|
|
374
388
|
console.log(
|
|
375
|
-
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
|
|
389
|
+
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars, recordingId: ${recordingId})`
|
|
376
390
|
);
|
|
377
391
|
}
|
|
378
392
|
async recordResponse(req, proxyRes) {
|
|
379
393
|
if (!this.currentSession) {
|
|
380
394
|
return;
|
|
381
395
|
}
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
(
|
|
396
|
+
const recordingId = req.__recordingId;
|
|
397
|
+
if (recordingId === void 0) {
|
|
398
|
+
console.error(
|
|
399
|
+
`recordResponse: No recording ID found on request ${req.method} ${req.url}`
|
|
400
|
+
);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const record = this.currentSession.recordings.find(
|
|
404
|
+
(r) => r.recordingId === recordingId
|
|
385
405
|
);
|
|
386
406
|
if (!record) {
|
|
387
|
-
console.error(
|
|
407
|
+
console.error(
|
|
408
|
+
`recordResponse: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
409
|
+
);
|
|
388
410
|
return;
|
|
389
411
|
}
|
|
390
412
|
const chunks = [];
|
|
@@ -398,34 +420,28 @@ var ProxyServer = class {
|
|
|
398
420
|
headers: proxyRes.headers,
|
|
399
421
|
body: body || null
|
|
400
422
|
};
|
|
401
|
-
console.log(
|
|
423
|
+
console.log(
|
|
424
|
+
`Recorded: ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
425
|
+
);
|
|
402
426
|
});
|
|
403
427
|
}
|
|
404
428
|
async recordResponseData(req, proxyRes, body) {
|
|
405
429
|
if (!this.currentSession) {
|
|
406
430
|
return false;
|
|
407
431
|
}
|
|
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
|
-
);
|
|
432
|
+
const recordingId = req.__recordingId;
|
|
433
|
+
if (recordingId === void 0) {
|
|
417
434
|
console.error(
|
|
418
|
-
`
|
|
419
|
-
);
|
|
420
|
-
console.error(
|
|
421
|
-
` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
|
|
435
|
+
`recordResponseData: No recording ID found on request ${req.method} ${req.url}`
|
|
422
436
|
);
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const record = this.currentSession.recordings.find(
|
|
440
|
+
(r) => r.recordingId === recordingId
|
|
441
|
+
);
|
|
442
|
+
if (!record) {
|
|
423
443
|
console.error(
|
|
424
|
-
`
|
|
425
|
-
recordsWithKey.map((r) => ({
|
|
426
|
-
seq: r.sequence,
|
|
427
|
-
hasResponse: !!r.response
|
|
428
|
-
}))
|
|
444
|
+
`recordResponseData: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
|
|
429
445
|
);
|
|
430
446
|
return false;
|
|
431
447
|
}
|
|
@@ -434,12 +450,8 @@ var ProxyServer = class {
|
|
|
434
450
|
headers: proxyRes.headers,
|
|
435
451
|
body: body || null
|
|
436
452
|
};
|
|
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
453
|
console.log(
|
|
442
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
454
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence}, recordingId: ${recordingId})`
|
|
443
455
|
);
|
|
444
456
|
return true;
|
|
445
457
|
}
|
|
@@ -451,13 +463,30 @@ var ProxyServer = class {
|
|
|
451
463
|
const host = req.headers.host || "unknown";
|
|
452
464
|
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
453
465
|
if (recordsWithKey.length === 0) {
|
|
454
|
-
|
|
455
|
-
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
466
|
+
console.warn(
|
|
467
|
+
`No recording found for ${key} at ${req.method} ${host}${req.url}, returning default response`
|
|
456
468
|
);
|
|
469
|
+
const defaultResponse = req.method === "GET" ? {
|
|
470
|
+
data: [],
|
|
471
|
+
items: [],
|
|
472
|
+
results: [],
|
|
473
|
+
updated_at: "0001-01-01T00:00:00Z"
|
|
474
|
+
} : { success: true };
|
|
475
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
476
|
+
res.writeHead(HTTP_STATUS_OK, {
|
|
477
|
+
"Content-Type": "application/json",
|
|
478
|
+
...corsHeaders
|
|
479
|
+
});
|
|
480
|
+
res.end(JSON.stringify(defaultResponse));
|
|
481
|
+
return;
|
|
457
482
|
}
|
|
458
483
|
const usageCount = this.replaySequenceMap.get(key) || 0;
|
|
459
|
-
|
|
460
|
-
|
|
484
|
+
let record;
|
|
485
|
+
if (usageCount < recordsWithKey.length) {
|
|
486
|
+
record = recordsWithKey[usageCount];
|
|
487
|
+
} else {
|
|
488
|
+
record = recordsWithKey[recordsWithKey.length - 1];
|
|
489
|
+
}
|
|
461
490
|
console.log(
|
|
462
491
|
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
463
492
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "test-proxy-recorder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|