test-proxy-recorder 0.1.2 → 0.1.4

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.
@@ -0,0 +1,96 @@
1
+ import { TestInfo } from '@playwright/test';
2
+ import http from 'node:http';
3
+
4
+ declare const Modes: {
5
+ readonly transparent: "transparent";
6
+ readonly record: "record";
7
+ readonly replay: "replay";
8
+ };
9
+ type Mode = (typeof Modes)[keyof typeof Modes];
10
+ interface ControlRequest {
11
+ mode: Mode;
12
+ id?: string;
13
+ timeout?: number;
14
+ }
15
+ interface RecordedRequest {
16
+ method: string;
17
+ url: string;
18
+ headers: http.IncomingHttpHeaders;
19
+ body: string | null;
20
+ }
21
+ interface RecordedResponse {
22
+ statusCode: number;
23
+ headers: http.IncomingHttpHeaders;
24
+ body: string | null;
25
+ }
26
+ interface Recording {
27
+ request: RecordedRequest;
28
+ response?: RecordedResponse;
29
+ timestamp: string;
30
+ key: string;
31
+ sequence: number;
32
+ }
33
+ interface WebSocketMessage {
34
+ direction: 'client-to-server' | 'server-to-client';
35
+ data: string;
36
+ timestamp: string;
37
+ }
38
+ interface WebSocketRecording {
39
+ url: string;
40
+ messages: WebSocketMessage[];
41
+ timestamp: string;
42
+ key: string;
43
+ }
44
+ interface RecordingSession {
45
+ id: string;
46
+ recordings: Recording[];
47
+ websocketRecordings: WebSocketRecording[];
48
+ }
49
+
50
+ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
51
+ /**
52
+ * Set the proxy mode for a given session
53
+ * @param mode - The proxy mode to set (recording, replay, transparent)
54
+ * @param sessionId - Unique identifier for the session
55
+ * @param timeout - Optional timeout in milliseconds
56
+ */
57
+ declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
58
+ /**
59
+ * Generate a session ID from test info
60
+ * @param testInfo - Playwright test info object
61
+ */
62
+ declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
63
+ /**
64
+ * Start recording for a test
65
+ * @param testInfo - Playwright test info object
66
+ */
67
+ declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
68
+ /**
69
+ * Start replay for a test
70
+ * @param testInfo - Playwright test info object
71
+ */
72
+ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
73
+ /**
74
+ * Stop recording/replay and return to transparent mode
75
+ * @param testInfo - Playwright test info object
76
+ */
77
+ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
78
+ /**
79
+ * Playwright test fixture helper for managing proxy mode
80
+ * Use this in beforeEach/afterEach hooks
81
+ */
82
+ declare const playwrightProxy: {
83
+ /**
84
+ * Setup before test - sets the proxy mode
85
+ * @param testInfo - Playwright test info object
86
+ * @param mode - The proxy mode to use for this test
87
+ */
88
+ before(testInfo: PlaywrightTestInfo, mode: Mode): Promise<void>;
89
+ /**
90
+ * Cleanup after test - returns to transparent mode
91
+ * @param testInfo - Playwright test info object
92
+ */
93
+ after(testInfo: PlaywrightTestInfo): Promise<void>;
94
+ };
95
+
96
+ export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
@@ -0,0 +1,96 @@
1
+ import { TestInfo } from '@playwright/test';
2
+ import http from 'node:http';
3
+
4
+ declare const Modes: {
5
+ readonly transparent: "transparent";
6
+ readonly record: "record";
7
+ readonly replay: "replay";
8
+ };
9
+ type Mode = (typeof Modes)[keyof typeof Modes];
10
+ interface ControlRequest {
11
+ mode: Mode;
12
+ id?: string;
13
+ timeout?: number;
14
+ }
15
+ interface RecordedRequest {
16
+ method: string;
17
+ url: string;
18
+ headers: http.IncomingHttpHeaders;
19
+ body: string | null;
20
+ }
21
+ interface RecordedResponse {
22
+ statusCode: number;
23
+ headers: http.IncomingHttpHeaders;
24
+ body: string | null;
25
+ }
26
+ interface Recording {
27
+ request: RecordedRequest;
28
+ response?: RecordedResponse;
29
+ timestamp: string;
30
+ key: string;
31
+ sequence: number;
32
+ }
33
+ interface WebSocketMessage {
34
+ direction: 'client-to-server' | 'server-to-client';
35
+ data: string;
36
+ timestamp: string;
37
+ }
38
+ interface WebSocketRecording {
39
+ url: string;
40
+ messages: WebSocketMessage[];
41
+ timestamp: string;
42
+ key: string;
43
+ }
44
+ interface RecordingSession {
45
+ id: string;
46
+ recordings: Recording[];
47
+ websocketRecordings: WebSocketRecording[];
48
+ }
49
+
50
+ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
51
+ /**
52
+ * Set the proxy mode for a given session
53
+ * @param mode - The proxy mode to set (recording, replay, transparent)
54
+ * @param sessionId - Unique identifier for the session
55
+ * @param timeout - Optional timeout in milliseconds
56
+ */
57
+ declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
58
+ /**
59
+ * Generate a session ID from test info
60
+ * @param testInfo - Playwright test info object
61
+ */
62
+ declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
63
+ /**
64
+ * Start recording for a test
65
+ * @param testInfo - Playwright test info object
66
+ */
67
+ declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
68
+ /**
69
+ * Start replay for a test
70
+ * @param testInfo - Playwright test info object
71
+ */
72
+ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
73
+ /**
74
+ * Stop recording/replay and return to transparent mode
75
+ * @param testInfo - Playwright test info object
76
+ */
77
+ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
78
+ /**
79
+ * Playwright test fixture helper for managing proxy mode
80
+ * Use this in beforeEach/afterEach hooks
81
+ */
82
+ declare const playwrightProxy: {
83
+ /**
84
+ * Setup before test - sets the proxy mode
85
+ * @param testInfo - Playwright test info object
86
+ * @param mode - The proxy mode to use for this test
87
+ */
88
+ before(testInfo: PlaywrightTestInfo, mode: Mode): Promise<void>;
89
+ /**
90
+ * Cleanup after test - returns to transparent mode
91
+ * @param testInfo - Playwright test info object
92
+ */
93
+ after(testInfo: PlaywrightTestInfo): Promise<void>;
94
+ };
95
+
96
+ export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
package/dist/index.cjs CHANGED
@@ -2,16 +2,20 @@
2
2
 
3
3
  var fs = require('fs/promises');
4
4
  var http = require('http');
5
+ var https = require('https');
5
6
  var httpProxy = require('http-proxy');
6
7
  var ws = require('ws');
7
8
  var path = require('path');
9
+ var filenamify = require('filenamify');
8
10
 
9
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
12
 
11
13
  var fs__default = /*#__PURE__*/_interopDefault(fs);
12
14
  var http__default = /*#__PURE__*/_interopDefault(http);
15
+ var https__default = /*#__PURE__*/_interopDefault(https);
13
16
  var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
14
17
  var path__default = /*#__PURE__*/_interopDefault(path);
18
+ var filenamify__default = /*#__PURE__*/_interopDefault(filenamify);
15
19
 
16
20
  // src/ProxyServer.ts
17
21
 
@@ -47,6 +51,24 @@ async function saveRecordingSession(recordingsDir, session) {
47
51
  `Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
48
52
  );
49
53
  }
54
+ var QUERY_HASH_LENGTH = 8;
55
+ function getReqID(req) {
56
+ const urlParts = req.url.split("?");
57
+ const pathname = urlParts[0];
58
+ const query = urlParts[1] || "";
59
+ const pathPart = pathname === "/" ? "root" : pathname.slice(1);
60
+ const normalizedPath = filenamify__default.default(pathPart, { replacement: "_" });
61
+ const queryHash = generateQueryHash(query);
62
+ const filename = `${req.method}_${normalizedPath}${queryHash}.json`;
63
+ return filenamify__default.default(filename, { replacement: "_" });
64
+ }
65
+ function generateQueryHash(query) {
66
+ if (!query) {
67
+ return "";
68
+ }
69
+ const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
70
+ return `_${hash}`;
71
+ }
50
72
 
51
73
  // src/utils/httpHelpers.ts
52
74
  var CONTENT_TYPE_JSON = "application/json";
@@ -62,28 +84,6 @@ function sendJsonResponse(res, statusCode, data) {
62
84
  res.end(JSON.stringify(data));
63
85
  }
64
86
 
65
- // src/utils/requestKeyGenerator.ts
66
- var QUERY_HASH_LENGTH = 8;
67
- function generateRequestKey(req) {
68
- const urlParts = req.url.split("?");
69
- const pathname = urlParts[0];
70
- const query = urlParts[1] || "";
71
- const normalizedPath = normalizePathname(pathname);
72
- const queryHash = generateQueryHash(query);
73
- return `${req.method}_${normalizedPath}${queryHash}.json`;
74
- }
75
- function normalizePathname(pathname) {
76
- const normalized = pathname.replaceAll("/", "_").replace(/^_/, "");
77
- return normalized || "root";
78
- }
79
- function generateQueryHash(query) {
80
- if (!query) {
81
- return "";
82
- }
83
- const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
84
- return `_${hash}`;
85
- }
86
-
87
87
  // src/ProxyServer.ts
88
88
  var ProxyServer = class {
89
89
  targets;
@@ -95,6 +95,10 @@ var ProxyServer = class {
95
95
  proxy;
96
96
  currentSession;
97
97
  recordingsDir;
98
+ requestSequenceMap;
99
+ // Track sequence per request key
100
+ replaySequenceMap;
101
+ // Track replay position per request key
98
102
  constructor(targets, recordingsDir) {
99
103
  this.targets = targets;
100
104
  this.currentTargetIndex = 0;
@@ -104,6 +108,8 @@ var ProxyServer = class {
104
108
  this.modeTimeout = null;
105
109
  this.currentSession = null;
106
110
  this.recordingsDir = recordingsDir;
111
+ this.requestSequenceMap = /* @__PURE__ */ new Map();
112
+ this.replaySequenceMap = /* @__PURE__ */ new Map();
107
113
  this.proxy = httpProxy__default.default.createProxyServer({
108
114
  secure: false,
109
115
  changeOrigin: true
@@ -142,10 +148,19 @@ var ProxyServer = class {
142
148
  res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
143
149
  }
144
150
  handleProxyResponse(proxyRes, req) {
151
+ this.addCorsHeaders(proxyRes, req);
145
152
  if (this.mode === Modes.record && this.recordingId) {
146
153
  this.recordResponse(req, proxyRes);
147
154
  }
148
155
  }
156
+ addCorsHeaders(proxyRes, req) {
157
+ const origin = req.headers.origin;
158
+ proxyRes.headers["access-control-allow-origin"] = origin || "*";
159
+ proxyRes.headers["access-control-allow-credentials"] = "true";
160
+ proxyRes.headers["access-control-allow-headers"] = req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization";
161
+ proxyRes.headers["access-control-allow-methods"] = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
162
+ proxyRes.headers["access-control-expose-headers"] = "*";
163
+ }
149
164
  getTarget() {
150
165
  const target = this.targets[this.currentTargetIndex];
151
166
  this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
@@ -209,6 +224,7 @@ var ProxyServer = class {
209
224
  this.recordingId = null;
210
225
  this.replayId = null;
211
226
  this.currentSession = null;
227
+ clearTimeout(this.modeTimeout || 0);
212
228
  console.log("Switched to transparent mode");
213
229
  }
214
230
  switchToRecordMode(id) {
@@ -219,6 +235,7 @@ var ProxyServer = class {
219
235
  this.recordingId = id;
220
236
  this.replayId = null;
221
237
  this.currentSession = { id, recordings: [], websocketRecordings: [] };
238
+ this.requestSequenceMap.clear();
222
239
  console.log(`Switched to record mode with ID: ${id}`);
223
240
  }
224
241
  switchToReplayMode(id) {
@@ -229,6 +246,7 @@ var ProxyServer = class {
229
246
  this.replayId = id;
230
247
  this.recordingId = null;
231
248
  this.currentSession = null;
249
+ this.replaySequenceMap.clear();
232
250
  console.log(`Switched to replay mode with ID: ${id}`);
233
251
  }
234
252
  setupModeTimeout(timeout) {
@@ -259,7 +277,9 @@ var ProxyServer = class {
259
277
  if (!this.currentSession) {
260
278
  return;
261
279
  }
262
- const key = generateRequestKey(req);
280
+ const key = getReqID(req);
281
+ const currentSequence = this.requestSequenceMap.get(key) || 0;
282
+ this.requestSequenceMap.set(key, currentSequence + 1);
263
283
  const record = {
264
284
  request: {
265
285
  method: req.method,
@@ -268,7 +288,8 @@ var ProxyServer = class {
268
288
  body: body || null
269
289
  },
270
290
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
271
- key
291
+ key,
292
+ sequence: currentSequence
272
293
  };
273
294
  this.currentSession.recordings.push(record);
274
295
  }
@@ -276,8 +297,10 @@ var ProxyServer = class {
276
297
  if (!this.currentSession) {
277
298
  return;
278
299
  }
279
- const key = generateRequestKey(req);
280
- const record = this.currentSession.recordings.find((r) => r.key === key);
300
+ const key = getReqID(req);
301
+ const record = this.currentSession.recordings.findLast(
302
+ (r) => r.key === key && !r.response
303
+ );
281
304
  if (!record) {
282
305
  console.error("Request record not found for response:", key);
283
306
  return;
@@ -298,21 +321,35 @@ var ProxyServer = class {
298
321
  });
299
322
  }
300
323
  async handleReplayRequest(req, res) {
301
- const key = generateRequestKey(req);
324
+ const key = getReqID(req);
302
325
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
303
326
  try {
304
327
  const session = await loadRecordingSession(filePath);
305
- const record = session.recordings.find((r) => r.key === key);
328
+ const currentSequence = this.replaySequenceMap.get(key) || 0;
329
+ const record = session.recordings.find(
330
+ (r) => r.key === key && r.sequence === currentSequence
331
+ );
306
332
  if (!record) {
307
- throw new Error(`No recording found for ${key}`);
333
+ throw new Error(
334
+ `No recording found for ${key} with sequence ${currentSequence}`
335
+ );
308
336
  }
309
337
  if (!record.response) {
310
338
  throw new Error("No response recorded for this request");
311
339
  }
340
+ this.replaySequenceMap.set(key, currentSequence + 1);
312
341
  const { statusCode, headers, body } = record.response;
313
- res.writeHead(statusCode, headers);
342
+ const origin = req.headers.origin;
343
+ const responseHeaders = {
344
+ ...headers,
345
+ "access-control-allow-origin": origin || "*",
346
+ "access-control-allow-credentials": "true"
347
+ };
348
+ res.writeHead(statusCode, responseHeaders);
314
349
  res.end(body);
315
- console.log(`Replayed: ${req.method} ${req.url}`);
350
+ console.log(
351
+ `Replayed: ${req.method} ${req.url} (sequence: ${currentSequence})`
352
+ );
316
353
  } catch (error) {
317
354
  this.handleReplayError(res, error, key, filePath);
318
355
  }
@@ -328,6 +365,9 @@ var ProxyServer = class {
328
365
  });
329
366
  }
330
367
  async handleRequest(req, res) {
368
+ if (req.method === "OPTIONS") {
369
+ return this.handleCorsPreflightRequest(req, res);
370
+ }
331
371
  if (req.url === CONTROL_ENDPOINT) {
332
372
  return this.handleControlRequest(req, res);
333
373
  }
@@ -336,23 +376,63 @@ var ProxyServer = class {
336
376
  }
337
377
  await this.handleProxyRequest(req, res);
338
378
  }
379
+ handleCorsPreflightRequest(req, res) {
380
+ const origin = req.headers.origin;
381
+ res.writeHead(HTTP_STATUS_OK, {
382
+ "Access-Control-Allow-Origin": origin || "*",
383
+ "Access-Control-Allow-Credentials": "true",
384
+ "Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
385
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
386
+ "Access-Control-Max-Age": "86400"
387
+ // 24 hours
388
+ });
389
+ res.end();
390
+ }
339
391
  async handleProxyRequest(req, res) {
340
392
  const target = this.getTarget();
341
393
  console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
342
394
  if (this.mode === Modes.record) {
343
- await this.bufferRequestForRecord(req);
395
+ await this.bufferAndProxyRequest(req, res, target);
396
+ } else {
397
+ this.proxy.web(req, res, { target });
344
398
  }
345
- this.proxy.web(req, res, { target });
346
399
  }
347
- async bufferRequestForRecord(req) {
400
+ async bufferAndProxyRequest(req, res, target) {
348
401
  const chunks = [];
349
402
  req.on("data", (chunk) => {
350
403
  chunks.push(chunk);
351
404
  });
352
- req.on("end", async () => {
353
- const body = Buffer.concat(chunks).toString("utf8");
354
- await this.saveRequestRecord(req, body);
405
+ await new Promise((resolve) => {
406
+ req.on("end", () => resolve());
355
407
  });
408
+ const body = Buffer.concat(chunks).toString("utf8");
409
+ await this.saveRequestRecord(req, body);
410
+ const targetUrl = new URL(target);
411
+ const isHttps = targetUrl.protocol === "https:";
412
+ const requestModule = isHttps ? https__default.default : http__default.default;
413
+ const defaultPort = isHttps ? 443 : 80;
414
+ const proxyReq = requestModule.request(
415
+ {
416
+ hostname: targetUrl.hostname,
417
+ port: targetUrl.port || defaultPort,
418
+ path: req.url,
419
+ method: req.method,
420
+ headers: req.headers
421
+ },
422
+ (proxyRes) => {
423
+ this.addCorsHeaders(proxyRes, req);
424
+ this.recordResponse(req, proxyRes);
425
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
426
+ proxyRes.pipe(res);
427
+ }
428
+ );
429
+ proxyReq.on("error", (err) => {
430
+ this.handleProxyError(err, req, res);
431
+ });
432
+ if (chunks.length > 0) {
433
+ proxyReq.write(Buffer.concat(chunks));
434
+ }
435
+ proxyReq.end();
356
436
  }
357
437
  handleUpgrade(req, socket, head) {
358
438
  if (this.mode === Modes.replay) {
@@ -555,15 +635,15 @@ function generateSessionId(testInfo) {
555
635
  }
556
636
  async function startRecording(testInfo) {
557
637
  const sessionId = generateSessionId(testInfo);
558
- await setProxyMode("recording", sessionId);
638
+ await setProxyMode(Modes.record, sessionId);
559
639
  }
560
640
  async function startReplay(testInfo) {
561
641
  const sessionId = generateSessionId(testInfo);
562
- await setProxyMode("replay", sessionId);
642
+ await setProxyMode(Modes.replay, sessionId);
563
643
  }
564
644
  async function stopProxy(testInfo) {
565
645
  const sessionId = generateSessionId(testInfo);
566
- await setProxyMode("transparent", sessionId);
646
+ await setProxyMode(Modes.transparent, sessionId);
567
647
  }
568
648
  var playwrightProxy = {
569
649
  /**
@@ -582,7 +662,7 @@ var playwrightProxy = {
582
662
  */
583
663
  async after(testInfo) {
584
664
  const sessionId = generateSessionId(testInfo);
585
- await setProxyMode("transparent", sessionId);
665
+ await setProxyMode(Modes.transparent, sessionId);
586
666
  }
587
667
  };
588
668
 
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from 'node:http';
2
- export { PlaywrightTestInfo, ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy } from './playwright/index.cjs';
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-De4mgziH.cjs';
3
3
  import '@playwright/test';
4
4
 
5
5
  declare class ProxyServer {
@@ -12,12 +12,15 @@ declare class ProxyServer {
12
12
  private proxy;
13
13
  private currentSession;
14
14
  private recordingsDir;
15
+ private requestSequenceMap;
16
+ private replaySequenceMap;
15
17
  constructor(targets: string[], recordingsDir: string);
16
18
  init(): Promise<void>;
17
19
  listen(port: number): http.Server;
18
20
  private setupProxyEventHandlers;
19
21
  private handleProxyError;
20
22
  private handleProxyResponse;
23
+ private addCorsHeaders;
21
24
  private getTarget;
22
25
  private handleControlRequest;
23
26
  private clearModeTimeout;
@@ -32,57 +35,13 @@ declare class ProxyServer {
32
35
  private handleReplayRequest;
33
36
  private handleReplayError;
34
37
  private handleRequest;
38
+ private handleCorsPreflightRequest;
35
39
  private handleProxyRequest;
36
- private bufferRequestForRecord;
40
+ private bufferAndProxyRequest;
37
41
  private handleUpgrade;
38
42
  private handleRecordWebSocket;
39
43
  private handleReplayWebSocket;
40
44
  private logServerStartup;
41
45
  }
42
46
 
43
- declare const Modes: {
44
- readonly transparent: "transparent";
45
- readonly record: "record";
46
- readonly replay: "replay";
47
- };
48
- type Mode = (typeof Modes)[keyof typeof Modes];
49
- interface ControlRequest {
50
- mode: Mode;
51
- id?: string;
52
- timeout?: number;
53
- }
54
- interface RecordedRequest {
55
- method: string;
56
- url: string;
57
- headers: http.IncomingHttpHeaders;
58
- body: string | null;
59
- }
60
- interface RecordedResponse {
61
- statusCode: number;
62
- headers: http.IncomingHttpHeaders;
63
- body: string | null;
64
- }
65
- interface Recording {
66
- request: RecordedRequest;
67
- response?: RecordedResponse;
68
- timestamp: string;
69
- key: string;
70
- }
71
- interface WebSocketMessage {
72
- direction: 'client-to-server' | 'server-to-client';
73
- data: string;
74
- timestamp: string;
75
- }
76
- interface WebSocketRecording {
77
- url: string;
78
- messages: WebSocketMessage[];
79
- timestamp: string;
80
- key: string;
81
- }
82
- interface RecordingSession {
83
- id: string;
84
- recordings: Recording[];
85
- websocketRecordings: WebSocketRecording[];
86
- }
87
-
88
- export { type ControlRequest, type Mode, ProxyServer, type Recording, type RecordingSession, type WebSocketRecording };
47
+ export { ProxyServer };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from 'node:http';
2
- export { PlaywrightTestInfo, ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy } from './playwright/index.js';
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-De4mgziH.js';
3
3
  import '@playwright/test';
4
4
 
5
5
  declare class ProxyServer {
@@ -12,12 +12,15 @@ declare class ProxyServer {
12
12
  private proxy;
13
13
  private currentSession;
14
14
  private recordingsDir;
15
+ private requestSequenceMap;
16
+ private replaySequenceMap;
15
17
  constructor(targets: string[], recordingsDir: string);
16
18
  init(): Promise<void>;
17
19
  listen(port: number): http.Server;
18
20
  private setupProxyEventHandlers;
19
21
  private handleProxyError;
20
22
  private handleProxyResponse;
23
+ private addCorsHeaders;
21
24
  private getTarget;
22
25
  private handleControlRequest;
23
26
  private clearModeTimeout;
@@ -32,57 +35,13 @@ declare class ProxyServer {
32
35
  private handleReplayRequest;
33
36
  private handleReplayError;
34
37
  private handleRequest;
38
+ private handleCorsPreflightRequest;
35
39
  private handleProxyRequest;
36
- private bufferRequestForRecord;
40
+ private bufferAndProxyRequest;
37
41
  private handleUpgrade;
38
42
  private handleRecordWebSocket;
39
43
  private handleReplayWebSocket;
40
44
  private logServerStartup;
41
45
  }
42
46
 
43
- declare const Modes: {
44
- readonly transparent: "transparent";
45
- readonly record: "record";
46
- readonly replay: "replay";
47
- };
48
- type Mode = (typeof Modes)[keyof typeof Modes];
49
- interface ControlRequest {
50
- mode: Mode;
51
- id?: string;
52
- timeout?: number;
53
- }
54
- interface RecordedRequest {
55
- method: string;
56
- url: string;
57
- headers: http.IncomingHttpHeaders;
58
- body: string | null;
59
- }
60
- interface RecordedResponse {
61
- statusCode: number;
62
- headers: http.IncomingHttpHeaders;
63
- body: string | null;
64
- }
65
- interface Recording {
66
- request: RecordedRequest;
67
- response?: RecordedResponse;
68
- timestamp: string;
69
- key: string;
70
- }
71
- interface WebSocketMessage {
72
- direction: 'client-to-server' | 'server-to-client';
73
- data: string;
74
- timestamp: string;
75
- }
76
- interface WebSocketRecording {
77
- url: string;
78
- messages: WebSocketMessage[];
79
- timestamp: string;
80
- key: string;
81
- }
82
- interface RecordingSession {
83
- id: string;
84
- recordings: Recording[];
85
- websocketRecordings: WebSocketRecording[];
86
- }
87
-
88
- export { type ControlRequest, type Mode, ProxyServer, type Recording, type RecordingSession, type WebSocketRecording };
47
+ export { ProxyServer };