test-proxy-recorder 0.1.0 → 0.1.2

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.
Files changed (41) hide show
  1. package/README.md +79 -172
  2. package/dist/index.cjs +597 -0
  3. package/dist/index.d.cts +88 -0
  4. package/dist/index.d.ts +88 -5
  5. package/dist/index.mjs +582 -0
  6. package/dist/playwright/index.cjs +72 -0
  7. package/dist/playwright/index.d.cts +50 -0
  8. package/dist/playwright/index.d.ts +12 -10
  9. package/dist/playwright/index.mjs +65 -0
  10. package/dist/proxy.js +555 -4
  11. package/package.json +12 -5
  12. package/dist/ProxyServer.d.ts +0 -39
  13. package/dist/ProxyServer.d.ts.map +0 -1
  14. package/dist/ProxyServer.js +0 -464
  15. package/dist/cli.d.ts +0 -8
  16. package/dist/cli.d.ts.map +0 -1
  17. package/dist/cli.js +0 -32
  18. package/dist/constants.d.ts +0 -7
  19. package/dist/constants.d.ts.map +0 -1
  20. package/dist/constants.js +0 -7
  21. package/dist/index.d.ts.map +0 -1
  22. package/dist/index.js +0 -3
  23. package/dist/playwright/index.d.ts.map +0 -1
  24. package/dist/playwright/index.js +0 -92
  25. package/dist/proxy.d.ts +0 -2
  26. package/dist/proxy.d.ts.map +0 -1
  27. package/dist/types.d.ts +0 -46
  28. package/dist/types.d.ts.map +0 -1
  29. package/dist/types.js +0 -6
  30. package/dist/utils/fileUtils.d.ts +0 -5
  31. package/dist/utils/fileUtils.d.ts.map +0 -1
  32. package/dist/utils/fileUtils.js +0 -16
  33. package/dist/utils/httpHelpers.d.ts +0 -4
  34. package/dist/utils/httpHelpers.d.ts.map +0 -1
  35. package/dist/utils/httpHelpers.js +0 -13
  36. package/dist/utils/index.d.ts +0 -4
  37. package/dist/utils/index.d.ts.map +0 -1
  38. package/dist/utils/index.js +0 -4
  39. package/dist/utils/requestKeyGenerator.d.ts +0 -3
  40. package/dist/utils/requestKeyGenerator.d.ts.map +0 -1
  41. package/dist/utils/requestKeyGenerator.js +0 -24
package/dist/index.cjs ADDED
@@ -0,0 +1,597 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs/promises');
4
+ var http = require('http');
5
+ var httpProxy = require('http-proxy');
6
+ var ws = require('ws');
7
+ var path = require('path');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
12
+ var http__default = /*#__PURE__*/_interopDefault(http);
13
+ var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
14
+ var path__default = /*#__PURE__*/_interopDefault(path);
15
+
16
+ // src/ProxyServer.ts
17
+
18
+ // src/constants.ts
19
+ var DEFAULT_TIMEOUT_MS = 120 * 1e3;
20
+ var HTTP_STATUS_BAD_GATEWAY = 502;
21
+ var HTTP_STATUS_OK = 200;
22
+ var HTTP_STATUS_BAD_REQUEST = 400;
23
+ var HTTP_STATUS_NOT_FOUND = 404;
24
+ var CONTROL_ENDPOINT = "/__control";
25
+
26
+ // src/types.ts
27
+ var Modes = {
28
+ transparent: "transparent",
29
+ record: "record",
30
+ replay: "replay"
31
+ };
32
+ var JSON_INDENT_SPACES = 2;
33
+ function getRecordingPath(recordingsDir, id) {
34
+ return path__default.default.join(recordingsDir, `${id}.json`);
35
+ }
36
+ async function loadRecordingSession(filePath) {
37
+ const fileContent = await fs__default.default.readFile(filePath, "utf8");
38
+ return JSON.parse(fileContent);
39
+ }
40
+ async function saveRecordingSession(recordingsDir, session) {
41
+ const filePath = getRecordingPath(recordingsDir, session.id);
42
+ await fs__default.default.writeFile(
43
+ filePath,
44
+ JSON.stringify(session, null, JSON_INDENT_SPACES)
45
+ );
46
+ console.log(
47
+ `Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
48
+ );
49
+ }
50
+
51
+ // src/utils/httpHelpers.ts
52
+ var CONTENT_TYPE_JSON = "application/json";
53
+ async function readRequestBody(req) {
54
+ let body = "";
55
+ for await (const chunk of req) {
56
+ body += chunk.toString();
57
+ }
58
+ return body;
59
+ }
60
+ function sendJsonResponse(res, statusCode, data) {
61
+ res.writeHead(statusCode, { "Content-Type": CONTENT_TYPE_JSON });
62
+ res.end(JSON.stringify(data));
63
+ }
64
+
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
+ // src/ProxyServer.ts
88
+ var ProxyServer = class {
89
+ targets;
90
+ currentTargetIndex;
91
+ mode;
92
+ recordingId;
93
+ replayId;
94
+ modeTimeout;
95
+ proxy;
96
+ currentSession;
97
+ recordingsDir;
98
+ constructor(targets, recordingsDir) {
99
+ this.targets = targets;
100
+ this.currentTargetIndex = 0;
101
+ this.mode = Modes.transparent;
102
+ this.recordingId = null;
103
+ this.replayId = null;
104
+ this.modeTimeout = null;
105
+ this.currentSession = null;
106
+ this.recordingsDir = recordingsDir;
107
+ this.proxy = httpProxy__default.default.createProxyServer({
108
+ secure: false,
109
+ changeOrigin: true
110
+ });
111
+ this.setupProxyEventHandlers();
112
+ }
113
+ async init() {
114
+ await fs__default.default.mkdir(this.recordingsDir, { recursive: true });
115
+ }
116
+ listen(port) {
117
+ const server = http__default.default.createServer((req, res) => {
118
+ this.handleRequest(req, res);
119
+ });
120
+ server.on("upgrade", (req, socket, head) => {
121
+ this.handleUpgrade(req, socket, head);
122
+ });
123
+ server.listen(port, () => {
124
+ this.logServerStartup(port);
125
+ });
126
+ return server;
127
+ }
128
+ setupProxyEventHandlers() {
129
+ this.proxy.on("error", this.handleProxyError.bind(this));
130
+ this.proxy.on("proxyRes", this.handleProxyResponse.bind(this));
131
+ }
132
+ handleProxyError(err, _req, res) {
133
+ console.error("Proxy error:", err);
134
+ if (!(res instanceof http__default.default.ServerResponse)) {
135
+ return;
136
+ }
137
+ if (!res.headersSent) {
138
+ res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
139
+ "Content-Type": "application/json"
140
+ });
141
+ }
142
+ res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
143
+ }
144
+ handleProxyResponse(proxyRes, req) {
145
+ if (this.mode === Modes.record && this.recordingId) {
146
+ this.recordResponse(req, proxyRes);
147
+ }
148
+ }
149
+ getTarget() {
150
+ const target = this.targets[this.currentTargetIndex];
151
+ this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
152
+ return target;
153
+ }
154
+ async handleControlRequest(req, res) {
155
+ try {
156
+ const body = await readRequestBody(req);
157
+ console.log("MODE CHANGE", body);
158
+ const data = JSON.parse(body);
159
+ const { mode, id, timeout: requestTimeout } = data;
160
+ const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
161
+ this.clearModeTimeout();
162
+ await this.switchMode(mode, id);
163
+ this.setupModeTimeout(timeout);
164
+ sendJsonResponse(res, HTTP_STATUS_OK, {
165
+ success: true,
166
+ mode: this.mode,
167
+ id: this.recordingId || this.replayId,
168
+ timeout
169
+ });
170
+ } catch (error) {
171
+ console.error("Control request error:", error);
172
+ sendJsonResponse(res, HTTP_STATUS_BAD_REQUEST, {
173
+ error: error instanceof Error ? error.message : "Unknown error"
174
+ });
175
+ }
176
+ }
177
+ clearModeTimeout() {
178
+ if (this.modeTimeout) {
179
+ clearTimeout(this.modeTimeout);
180
+ this.modeTimeout = null;
181
+ }
182
+ }
183
+ async switchMode(mode, id) {
184
+ if (this.currentSession) {
185
+ console.log("Switching mode, saving current session first");
186
+ await this.saveCurrentSession();
187
+ console.log("Session saved, continuing with mode switch");
188
+ }
189
+ switch (mode) {
190
+ case Modes.transparent: {
191
+ this.switchToTransparentMode();
192
+ break;
193
+ }
194
+ case Modes.record: {
195
+ this.switchToRecordMode(id);
196
+ break;
197
+ }
198
+ case Modes.replay: {
199
+ this.switchToReplayMode(id);
200
+ break;
201
+ }
202
+ default: {
203
+ throw new Error("Invalid mode. Use: transparent, record, or replay");
204
+ }
205
+ }
206
+ }
207
+ switchToTransparentMode() {
208
+ this.mode = Modes.transparent;
209
+ this.recordingId = null;
210
+ this.replayId = null;
211
+ this.currentSession = null;
212
+ console.log("Switched to transparent mode");
213
+ }
214
+ switchToRecordMode(id) {
215
+ if (!id) {
216
+ throw new Error("Record ID is required");
217
+ }
218
+ this.mode = Modes.record;
219
+ this.recordingId = id;
220
+ this.replayId = null;
221
+ this.currentSession = { id, recordings: [], websocketRecordings: [] };
222
+ console.log(`Switched to record mode with ID: ${id}`);
223
+ }
224
+ switchToReplayMode(id) {
225
+ if (!id) {
226
+ throw new Error("Replay ID is required");
227
+ }
228
+ this.mode = Modes.replay;
229
+ this.replayId = id;
230
+ this.recordingId = null;
231
+ this.currentSession = null;
232
+ console.log(`Switched to replay mode with ID: ${id}`);
233
+ }
234
+ setupModeTimeout(timeout) {
235
+ if (timeout && timeout > 0) {
236
+ this.modeTimeout = setTimeout(async () => {
237
+ console.log("Timeout reached, switching back to transparent mode");
238
+ await this.saveCurrentSession();
239
+ this.switchToTransparentMode();
240
+ this.modeTimeout = null;
241
+ }, timeout);
242
+ }
243
+ }
244
+ async saveCurrentSession() {
245
+ if (!this.currentSession) {
246
+ console.log("No current session to save");
247
+ return;
248
+ }
249
+ if (this.currentSession.recordings.length === 0 && this.currentSession.websocketRecordings.length === 0) {
250
+ console.log("Session has no recordings, skipping save");
251
+ return;
252
+ }
253
+ console.log(
254
+ `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
255
+ );
256
+ await saveRecordingSession(this.recordingsDir, this.currentSession);
257
+ }
258
+ async saveRequestRecord(req, body) {
259
+ if (!this.currentSession) {
260
+ return;
261
+ }
262
+ const key = generateRequestKey(req);
263
+ const record = {
264
+ request: {
265
+ method: req.method,
266
+ url: req.url,
267
+ headers: req.headers,
268
+ body: body || null
269
+ },
270
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
271
+ key
272
+ };
273
+ this.currentSession.recordings.push(record);
274
+ }
275
+ async recordResponse(req, proxyRes) {
276
+ if (!this.currentSession) {
277
+ return;
278
+ }
279
+ const key = generateRequestKey(req);
280
+ const record = this.currentSession.recordings.find((r) => r.key === key);
281
+ if (!record) {
282
+ console.error("Request record not found for response:", key);
283
+ return;
284
+ }
285
+ const chunks = [];
286
+ proxyRes.on("data", (chunk) => {
287
+ chunks.push(chunk);
288
+ });
289
+ proxyRes.on("end", async () => {
290
+ const body = Buffer.concat(chunks).toString("utf8");
291
+ record.response = {
292
+ statusCode: proxyRes.statusCode,
293
+ headers: proxyRes.headers,
294
+ body: body || null
295
+ };
296
+ await this.saveCurrentSession();
297
+ console.log(`Recorded: ${req.method} ${req.url}`);
298
+ });
299
+ }
300
+ async handleReplayRequest(req, res) {
301
+ const key = generateRequestKey(req);
302
+ const filePath = getRecordingPath(this.recordingsDir, this.replayId);
303
+ try {
304
+ const session = await loadRecordingSession(filePath);
305
+ const record = session.recordings.find((r) => r.key === key);
306
+ if (!record) {
307
+ throw new Error(`No recording found for ${key}`);
308
+ }
309
+ if (!record.response) {
310
+ throw new Error("No response recorded for this request");
311
+ }
312
+ const { statusCode, headers, body } = record.response;
313
+ res.writeHead(statusCode, headers);
314
+ res.end(body);
315
+ console.log(`Replayed: ${req.method} ${req.url}`);
316
+ } catch (error) {
317
+ this.handleReplayError(res, error, key, filePath);
318
+ }
319
+ }
320
+ handleReplayError(res, err, key, filePath) {
321
+ const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
322
+ console.error("Replay error:", err);
323
+ sendJsonResponse(res, HTTP_STATUS_NOT_FOUND, {
324
+ error: isFileNotFound ? "Recording file not found" : "Recording not found",
325
+ message: err instanceof Error ? err.message : "Unknown error",
326
+ key,
327
+ filePath
328
+ });
329
+ }
330
+ async handleRequest(req, res) {
331
+ if (req.url === CONTROL_ENDPOINT) {
332
+ return this.handleControlRequest(req, res);
333
+ }
334
+ if (this.mode === Modes.replay) {
335
+ return this.handleReplayRequest(req, res);
336
+ }
337
+ await this.handleProxyRequest(req, res);
338
+ }
339
+ async handleProxyRequest(req, res) {
340
+ const target = this.getTarget();
341
+ console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
342
+ if (this.mode === Modes.record) {
343
+ await this.bufferRequestForRecord(req);
344
+ }
345
+ this.proxy.web(req, res, { target });
346
+ }
347
+ async bufferRequestForRecord(req) {
348
+ const chunks = [];
349
+ req.on("data", (chunk) => {
350
+ chunks.push(chunk);
351
+ });
352
+ req.on("end", async () => {
353
+ const body = Buffer.concat(chunks).toString("utf8");
354
+ await this.saveRequestRecord(req, body);
355
+ });
356
+ }
357
+ handleUpgrade(req, socket, head) {
358
+ if (this.mode === Modes.replay) {
359
+ this.handleReplayWebSocket(req, socket);
360
+ return;
361
+ }
362
+ const target = this.getTarget();
363
+ console.log(`[${this.mode}] WebSocket upgrade ${req.url} -> ${target}`);
364
+ if (this.mode === Modes.record) {
365
+ this.handleRecordWebSocket(req, socket, head, target);
366
+ } else {
367
+ this.proxy.ws(req, socket, head, { target });
368
+ }
369
+ }
370
+ handleRecordWebSocket(req, clientSocket, head, target) {
371
+ const url = req.url || "/";
372
+ const key = `WS_${url.replaceAll("/", "_")}`;
373
+ const wsRecording = {
374
+ url,
375
+ messages: [],
376
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
377
+ key
378
+ };
379
+ if (this.currentSession) {
380
+ this.currentSession.websocketRecordings.push(wsRecording);
381
+ }
382
+ const backendWsUrl = `${target.replace("http", "ws")}${url}`;
383
+ const backendWs = new ws.WebSocket(backendWsUrl);
384
+ const wss = new ws.WebSocketServer({ noServer: true });
385
+ backendWs.on("open", () => {
386
+ console.log(`WebSocket recording: connected to backend ${backendWsUrl}`);
387
+ wss.handleUpgrade(req, clientSocket, head, (clientWs) => {
388
+ clientWs.on("message", (data) => {
389
+ const message = data.toString();
390
+ wsRecording.messages.push({
391
+ direction: "client-to-server",
392
+ data: message,
393
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
394
+ });
395
+ if (backendWs.readyState === ws.WebSocket.OPEN) {
396
+ backendWs.send(message);
397
+ }
398
+ this.saveCurrentSession().catch((error) => {
399
+ console.error("Failed to save WebSocket recording:", error);
400
+ });
401
+ });
402
+ backendWs.on("message", (data) => {
403
+ const message = data.toString();
404
+ wsRecording.messages.push({
405
+ direction: "server-to-client",
406
+ data: message,
407
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
408
+ });
409
+ if (clientWs.readyState === ws.WebSocket.OPEN) {
410
+ clientWs.send(message);
411
+ }
412
+ this.saveCurrentSession().catch((error) => {
413
+ console.error("Failed to save WebSocket recording:", error);
414
+ });
415
+ });
416
+ clientWs.on("error", (err) => {
417
+ console.error("Client WebSocket error:", err);
418
+ });
419
+ backendWs.on("error", (err) => {
420
+ console.error("Backend WebSocket error:", err);
421
+ });
422
+ clientWs.on("close", () => {
423
+ backendWs.close();
424
+ console.log("Client WebSocket closed");
425
+ });
426
+ backendWs.on("close", () => {
427
+ clientWs.close();
428
+ console.log("Backend WebSocket closed");
429
+ });
430
+ });
431
+ });
432
+ backendWs.on("error", (err) => {
433
+ console.error("Backend WebSocket connection error:", err);
434
+ clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
435
+ clientSocket.destroy();
436
+ });
437
+ wss.on("error", (err) => {
438
+ console.error("WebSocket server error:", err);
439
+ });
440
+ }
441
+ handleReplayWebSocket(req, socket) {
442
+ const url = req.url || "/";
443
+ const key = `WS_${url.replaceAll("/", "_")}`;
444
+ const filePath = getRecordingPath(this.recordingsDir, this.replayId);
445
+ loadRecordingSession(filePath).then((session) => {
446
+ const wsRecording = session.websocketRecordings.find(
447
+ (r) => r.key === key
448
+ );
449
+ if (!wsRecording) {
450
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
451
+ socket.destroy();
452
+ console.log(`No WebSocket recording found for ${key}`);
453
+ return;
454
+ }
455
+ const wss = new ws.WebSocketServer({ noServer: true });
456
+ const fakeReq = Object.assign(req, {
457
+ headers: {
458
+ ...req.headers,
459
+ "sec-websocket-key": req.headers["sec-websocket-key"] || "replay-key",
460
+ "sec-websocket-version": "13"
461
+ }
462
+ });
463
+ wss.handleUpgrade(fakeReq, socket, Buffer.alloc(0), (ws$1) => {
464
+ console.log(`Replaying WebSocket: ${url}`);
465
+ const serverMessages = wsRecording.messages.filter(
466
+ (m) => m.direction === "server-to-client"
467
+ );
468
+ let messageIndex = 0;
469
+ ws$1.on("message", (data) => {
470
+ const clientMessage = data.toString();
471
+ console.log(`Replay: Client sent: ${clientMessage}`);
472
+ if (messageIndex < serverMessages.length) {
473
+ setTimeout(() => {
474
+ if (ws$1.readyState === ws.WebSocket.OPEN) {
475
+ ws$1.send(serverMessages[messageIndex].data);
476
+ console.log(`Replay: Sent server message ${messageIndex}`);
477
+ messageIndex++;
478
+ }
479
+ }, 10);
480
+ }
481
+ });
482
+ let initialMessagesSent = 0;
483
+ for (let i = 0; i < wsRecording.messages.length; i++) {
484
+ const msg = wsRecording.messages[i];
485
+ if (msg.direction === "client-to-server") {
486
+ break;
487
+ }
488
+ if (msg.direction === "server-to-client") {
489
+ setTimeout(
490
+ () => {
491
+ if (ws$1.readyState === ws.WebSocket.OPEN) {
492
+ ws$1.send(msg.data);
493
+ console.log(
494
+ `Replay: Sent initial server message: ${msg.data}`
495
+ );
496
+ messageIndex++;
497
+ initialMessagesSent++;
498
+ }
499
+ },
500
+ 10 * (initialMessagesSent + 1)
501
+ );
502
+ }
503
+ }
504
+ ws$1.on("error", (err) => {
505
+ console.error("Replay WebSocket error:", err);
506
+ });
507
+ ws$1.on("close", () => {
508
+ console.log("Replay WebSocket closed");
509
+ });
510
+ });
511
+ }).catch((error) => {
512
+ console.error("Replay error:", error);
513
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
514
+ socket.destroy();
515
+ });
516
+ }
517
+ logServerStartup(port) {
518
+ console.log(`Proxy server running on http://localhost:${port}`);
519
+ console.log(`Mode: ${this.mode}`);
520
+ console.log(`Targets: ${this.targets.join(", ")}`);
521
+ console.log(
522
+ `Control endpoint: http://localhost:${port}${CONTROL_ENDPOINT}`
523
+ );
524
+ }
525
+ };
526
+
527
+ // src/playwright/index.ts
528
+ var INTERNAL_API_URL = process.env.INTERNAL_API_URL || "http://localhost:8100";
529
+ async function setProxyMode(mode, sessionId, timeout) {
530
+ try {
531
+ const body = {
532
+ mode,
533
+ id: sessionId,
534
+ ...timeout && { timeout }
535
+ };
536
+ const response = await fetch(`${INTERNAL_API_URL}/__control`, {
537
+ method: "POST",
538
+ headers: { "Content-Type": "application/json" },
539
+ body: JSON.stringify(body)
540
+ });
541
+ if (!response.ok) {
542
+ const text = await response.text();
543
+ console.error(`Failed to set proxy mode to ${mode}:`, text);
544
+ throw new Error(`Failed to set proxy mode: ${text}`);
545
+ }
546
+ await response.json();
547
+ console.log(`Proxy mode set to: ${mode} (session: ${sessionId})`);
548
+ } catch (error) {
549
+ console.error(`Error setting proxy mode:`, error);
550
+ throw error;
551
+ }
552
+ }
553
+ function generateSessionId(testInfo) {
554
+ return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
555
+ }
556
+ async function startRecording(testInfo) {
557
+ const sessionId = generateSessionId(testInfo);
558
+ await setProxyMode("recording", sessionId);
559
+ }
560
+ async function startReplay(testInfo) {
561
+ const sessionId = generateSessionId(testInfo);
562
+ await setProxyMode("replay", sessionId);
563
+ }
564
+ async function stopProxy(testInfo) {
565
+ const sessionId = generateSessionId(testInfo);
566
+ await setProxyMode("transparent", sessionId);
567
+ }
568
+ var playwrightProxy = {
569
+ /**
570
+ * Setup before test - sets the proxy mode
571
+ * @param testInfo - Playwright test info object
572
+ * @param mode - The proxy mode to use for this test
573
+ */
574
+ async before(testInfo, mode) {
575
+ const sessionId = generateSessionId(testInfo);
576
+ console.log("Proxy setup:", { mode, sessionId });
577
+ await setProxyMode(mode, sessionId);
578
+ },
579
+ /**
580
+ * Cleanup after test - returns to transparent mode
581
+ * @param testInfo - Playwright test info object
582
+ */
583
+ async after(testInfo) {
584
+ const sessionId = generateSessionId(testInfo);
585
+ await setProxyMode("transparent", sessionId);
586
+ }
587
+ };
588
+
589
+ exports.ProxyServer = ProxyServer;
590
+ exports.generateSessionId = generateSessionId;
591
+ exports.playwrightProxy = playwrightProxy;
592
+ exports.setProxyMode = setProxyMode;
593
+ exports.startRecording = startRecording;
594
+ exports.startReplay = startReplay;
595
+ exports.stopProxy = stopProxy;
596
+ //# sourceMappingURL=index.cjs.map
597
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,88 @@
1
+ import http from 'node:http';
2
+ export { PlaywrightTestInfo, ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy } from './playwright/index.cjs';
3
+ import '@playwright/test';
4
+
5
+ declare class ProxyServer {
6
+ private targets;
7
+ private currentTargetIndex;
8
+ private mode;
9
+ private recordingId;
10
+ private replayId;
11
+ private modeTimeout;
12
+ private proxy;
13
+ private currentSession;
14
+ private recordingsDir;
15
+ constructor(targets: string[], recordingsDir: string);
16
+ init(): Promise<void>;
17
+ listen(port: number): http.Server;
18
+ private setupProxyEventHandlers;
19
+ private handleProxyError;
20
+ private handleProxyResponse;
21
+ private getTarget;
22
+ private handleControlRequest;
23
+ private clearModeTimeout;
24
+ private switchMode;
25
+ private switchToTransparentMode;
26
+ private switchToRecordMode;
27
+ private switchToReplayMode;
28
+ private setupModeTimeout;
29
+ private saveCurrentSession;
30
+ private saveRequestRecord;
31
+ private recordResponse;
32
+ private handleReplayRequest;
33
+ private handleReplayError;
34
+ private handleRequest;
35
+ private handleProxyRequest;
36
+ private bufferRequestForRecord;
37
+ private handleUpgrade;
38
+ private handleRecordWebSocket;
39
+ private handleReplayWebSocket;
40
+ private logServerStartup;
41
+ }
42
+
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 };