test-proxy-recorder 0.3.1 → 0.3.3

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
@@ -6,7 +6,7 @@ var https = require('https');
6
6
  var httpProxy = require('http-proxy');
7
7
  var ws = require('ws');
8
8
  var crypto = require('crypto');
9
- var path = require('path');
9
+ var path2 = require('path');
10
10
  var filenamify2 = require('filenamify');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -16,7 +16,7 @@ var http__default = /*#__PURE__*/_interopDefault(http);
16
16
  var https__default = /*#__PURE__*/_interopDefault(https);
17
17
  var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
18
18
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
19
- var path__default = /*#__PURE__*/_interopDefault(path);
19
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
20
20
  var filenamify2__default = /*#__PURE__*/_interopDefault(filenamify2);
21
21
 
22
22
  // src/constants.ts
@@ -53,7 +53,7 @@ function getRecordingPath(recordingsDir, id) {
53
53
  maxLength: 255
54
54
  // Set explicit max to prevent filenamify's default truncation
55
55
  });
56
- return path__default.default.join(recordingsDir, `${sanitizedId}${EXTENSION}`);
56
+ return path2__default.default.join(recordingsDir, `${sanitizedId}${EXTENSION}`);
57
57
  }
58
58
  async function loadRecordingSession(filePath) {
59
59
  const fileContent = await fs__default.default.readFile(filePath, "utf8");
@@ -94,16 +94,19 @@ async function saveRecordingSession(recordingsDir, session) {
94
94
  `Saved ${processedRecordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
95
95
  );
96
96
  }
97
- function getReqID(req) {
98
- const urlParts = req.url.split("?");
99
- const pathname = urlParts[0];
100
- const query = urlParts[1] || "";
97
+ function generateRecordingKey(pathname, query, method) {
101
98
  const pathPart = pathname === "/" ? "root" : pathname.slice(1);
102
99
  const normalizedPath = filenamify2__default.default(pathPart, { replacement: "_" });
103
100
  const queryHash = generateQueryHash(query);
104
- const filename = `${req.method}_${normalizedPath}${queryHash}.json`;
101
+ const filename = `${method}_${normalizedPath}${queryHash}.json`;
105
102
  return filenamify2__default.default(filename, { replacement: "_" });
106
103
  }
104
+ function getReqID(req) {
105
+ const urlParts = req.url.split("?");
106
+ const pathname = urlParts[0];
107
+ const query = urlParts[1] || "";
108
+ return generateRecordingKey(pathname, query, req.method);
109
+ }
107
110
  function generateQueryHash(query) {
108
111
  if (!query) {
109
112
  return "";
@@ -145,6 +148,8 @@ var ProxyServer = class {
145
148
  // Track multiple concurrent replay sessions by recording ID
146
149
  recordingPromises;
147
150
  // Stack of promises that resolve to completed recordings
151
+ flushPromise;
152
+ // Promise for in-progress flush operation
148
153
  constructor(targets, recordingsDir) {
149
154
  this.targets = targets;
150
155
  this.currentTargetIndex = 0;
@@ -158,6 +163,7 @@ var ProxyServer = class {
158
163
  this.recordingsDir = recordingsDir;
159
164
  this.replaySessions = /* @__PURE__ */ new Map();
160
165
  this.recordingPromises = [];
166
+ this.flushPromise = null;
161
167
  this.proxy = httpProxy__default.default.createProxyServer({
162
168
  secure: false,
163
169
  changeOrigin: true,
@@ -256,7 +262,15 @@ var ProxyServer = class {
256
262
  * @returns The recording ID, or null if not found
257
263
  */
258
264
  getRecordingIdFromRequest(req) {
259
- return this.getRecordingIdFromHeader(req) || this.getRecordingIdFromCookie(req);
265
+ const fromHeader = this.getRecordingIdFromHeader(req);
266
+ const fromCookie = this.getRecordingIdFromCookie(req);
267
+ if (fromHeader) {
268
+ return fromHeader;
269
+ }
270
+ if (fromCookie) {
271
+ return fromCookie;
272
+ }
273
+ return null;
260
274
  }
261
275
  /**
262
276
  * Get or create a replay session state for a given recording ID
@@ -272,7 +286,8 @@ var ProxyServer = class {
272
286
  recordingId,
273
287
  servedRecordingIdsByKey: /* @__PURE__ */ new Map(),
274
288
  loadedSession: null,
275
- lastAccessTime: Date.now()
289
+ lastAccessTime: Date.now(),
290
+ sortedRecordingsByKey: /* @__PURE__ */ new Map()
276
291
  };
277
292
  this.replaySessions.set(recordingId, session);
278
293
  console.log(
@@ -281,6 +296,24 @@ var ProxyServer = class {
281
296
  }
282
297
  return session;
283
298
  }
299
+ /**
300
+ * Clean up a session - removes it from memory and resets counters
301
+ * @param sessionId The session ID to clean up
302
+ */
303
+ async cleanupSession(sessionId) {
304
+ if (this.replaySessions.has(sessionId)) {
305
+ this.replaySessions.delete(sessionId);
306
+ }
307
+ if (this.recordingId === sessionId) {
308
+ await this.saveCurrentSession();
309
+ this.currentSession = null;
310
+ this.recordingId = null;
311
+ }
312
+ if (this.replayId === sessionId) {
313
+ this.replayId = null;
314
+ }
315
+ console.log(`[CLEANUP] Session ${sessionId} cleaned up successfully`);
316
+ }
284
317
  parseGetParams(req) {
285
318
  const url = new URL(req.url || "", `http://${req.headers.host}`);
286
319
  const mode = url.searchParams.get("mode");
@@ -304,9 +337,31 @@ var ProxyServer = class {
304
337
  throw new Error("Unsupported control method");
305
338
  }
306
339
  async handleControlRequest(req, res) {
340
+ if (req.method === "GET") {
341
+ sendJsonResponse(res, HTTP_STATUS_OK, {
342
+ recordingsDir: this.recordingsDir,
343
+ mode: this.mode,
344
+ id: this.recordingId || this.replayId
345
+ });
346
+ return;
347
+ }
307
348
  try {
308
349
  const data = await this.parseControlRequest(req);
309
- const { mode, id, timeout: requestTimeout } = data;
350
+ const { mode, id, timeout: requestTimeout, cleanup } = data;
351
+ if (cleanup && id) {
352
+ await this.cleanupSession(id);
353
+ sendJsonResponse(res, HTTP_STATUS_OK, {
354
+ success: true,
355
+ message: `Session ${id} cleaned up`,
356
+ mode: this.mode
357
+ });
358
+ return;
359
+ }
360
+ if (!mode) {
361
+ throw new Error(
362
+ "Mode parameter is required when cleanup is not specified"
363
+ );
364
+ }
310
365
  const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
311
366
  this.clearModeTimeout();
312
367
  await this.switchMode(mode, id);
@@ -322,7 +377,8 @@ var ProxyServer = class {
322
377
  success: true,
323
378
  mode: this.mode,
324
379
  id: this.recordingId || this.replayId,
325
- timeout
380
+ timeout,
381
+ recordingsDir: this.recordingsDir
326
382
  });
327
383
  } catch (error) {
328
384
  console.error("Control request error:", error);
@@ -387,16 +443,21 @@ var ProxyServer = class {
387
443
  this.replayId = id;
388
444
  this.recordingId = null;
389
445
  this.currentSession = null;
390
- const session = this.replaySessions.get(id);
391
- if (session) {
392
- session.servedRecordingIdsByKey.clear();
393
- console.log(`Reset served recordings tracker for session: ${id}`);
394
- } else {
395
- this.getOrCreateReplaySession(id);
446
+ const sessionState = this.getOrCreateReplaySession(id);
447
+ sessionState.servedRecordingIdsByKey.clear();
448
+ sessionState.sortedRecordingsByKey.clear();
449
+ const filePath = getRecordingPath(this.recordingsDir, id);
450
+ try {
451
+ sessionState.loadedSession = await loadRecordingSession(filePath);
452
+ console.log(`[REPLAY] Loaded recording session: ${id}`);
453
+ } catch (error) {
454
+ console.error(`[REPLAY ERROR] Failed to load session ${id}:`, error);
455
+ sessionState.loadedSession = null;
396
456
  }
397
457
  console.log(`Switched to replay mode with ID: ${id}`);
398
458
  }
399
459
  setupModeTimeout(timeout) {
460
+ clearTimeout(this.modeTimeout || 0);
400
461
  this.modeTimeout = setTimeout(async () => {
401
462
  console.log("Timeout reached, switching back to transparent mode");
402
463
  await this.saveCurrentSession();
@@ -405,21 +466,32 @@ var ProxyServer = class {
405
466
  }, timeout);
406
467
  }
407
468
  async flushPendingRecordings() {
469
+ if (this.flushPromise) {
470
+ await this.flushPromise;
471
+ return;
472
+ }
408
473
  if (this.recordingPromises.length === 0) {
409
474
  return;
410
475
  }
411
- const results = await Promise.allSettled(this.recordingPromises);
412
- if (this.currentSession) {
413
- for (const result of results) {
414
- if (result.status === "fulfilled" && result.value) {
415
- this.currentSession.recordings.push(result.value);
476
+ this.flushPromise = (async () => {
477
+ try {
478
+ const results = await Promise.allSettled(this.recordingPromises);
479
+ if (this.currentSession) {
480
+ for (const result of results) {
481
+ if (result.status === "fulfilled" && result.value) {
482
+ this.currentSession.recordings.push(result.value);
483
+ }
484
+ }
485
+ console.log(
486
+ `Flushed ${results.length} recordings to session (total: ${this.currentSession.recordings.length})`
487
+ );
416
488
  }
489
+ this.recordingPromises = [];
490
+ } finally {
491
+ this.flushPromise = null;
417
492
  }
418
- console.log(
419
- `Flushed ${results.length} recordings to session (total: ${this.currentSession.recordings.length})`
420
- );
421
- }
422
- this.recordingPromises = [];
493
+ })();
494
+ await this.flushPromise;
423
495
  }
424
496
  async saveCurrentSession() {
425
497
  if (!this.currentSession) {
@@ -432,7 +504,29 @@ var ProxyServer = class {
432
504
  await saveRecordingSession(this.recordingsDir, this.currentSession);
433
505
  }
434
506
  getRecordingIdOrError(req, res) {
435
- const recordingId = this.getRecordingIdFromRequest(req) || this.replayId;
507
+ const recordingIdFromRequest = this.getRecordingIdFromRequest(req);
508
+ if (recordingIdFromRequest) {
509
+ return recordingIdFromRequest;
510
+ }
511
+ if (this.replaySessions.size > 1) {
512
+ console.warn(
513
+ `[CONCURRENT REPLAY WARNING] Request to ${req.method} ${req.url} is missing ${RECORDING_ID_HEADER} header/cookie. Active sessions: ${[...this.replaySessions.keys()].join(", ")}. this.replayId fallback would be: ${this.replayId} (NOT USING - could be wrong session)`
514
+ );
515
+ const corsHeaders = this.getCorsHeaders(req);
516
+ res.writeHead(HTTP_STATUS_BAD_REQUEST, {
517
+ "Content-Type": "application/json",
518
+ ...corsHeaders
519
+ });
520
+ res.end(
521
+ JSON.stringify({
522
+ error: "Missing recording ID in concurrent replay mode. Ensure x-test-rcrd-id header is set.",
523
+ activeSessions: [...this.replaySessions.keys()],
524
+ hint: "This usually means page.setExtraHTTPHeaders() did not apply to this request type"
525
+ })
526
+ );
527
+ return null;
528
+ }
529
+ const recordingId = this.replayId;
436
530
  if (!recordingId) {
437
531
  const corsHeaders = this.getCorsHeaders(req);
438
532
  res.writeHead(HTTP_STATUS_BAD_REQUEST, {
@@ -442,22 +536,30 @@ var ProxyServer = class {
442
536
  res.end(JSON.stringify({ error: "No replay session active" }));
443
537
  return null;
444
538
  }
539
+ console.log(
540
+ `[FALLBACK] Using replayId fallback for ${req.method} ${req.url} -> session: ${recordingId} (single session mode)`
541
+ );
445
542
  return recordingId;
446
543
  }
447
- async ensureSessionLoaded(recordingId, filePath) {
448
- const sessionState = this.getOrCreateReplaySession(recordingId);
449
- if (!sessionState.loadedSession) {
450
- sessionState.loadedSession = await loadRecordingSession(filePath);
451
- console.log(`[REPLAY] Loaded recording session: ${recordingId}`);
452
- }
453
- return sessionState;
454
- }
455
544
  getServedTracker(sessionState, key) {
456
545
  if (!sessionState.servedRecordingIdsByKey.has(key)) {
457
546
  sessionState.servedRecordingIdsByKey.set(key, /* @__PURE__ */ new Set());
458
547
  }
459
548
  return sessionState.servedRecordingIdsByKey.get(key);
460
549
  }
550
+ getSortedRecordings(sessionState, key) {
551
+ if (sessionState.sortedRecordingsByKey.has(key)) {
552
+ return sessionState.sortedRecordingsByKey.get(key);
553
+ }
554
+ const session = sessionState.loadedSession;
555
+ const sortedRecords = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => {
556
+ const aSeq = a.sequence !== void 0 ? a.sequence : a.recordingId;
557
+ const bSeq = b.sequence !== void 0 ? b.sequence : b.recordingId;
558
+ return aSeq - bSeq;
559
+ });
560
+ sessionState.sortedRecordingsByKey.set(key, sortedRecords);
561
+ return sortedRecords;
562
+ }
461
563
  selectReplayRecord(recordsWithKey, servedForThisKey, key, recordingId) {
462
564
  for (const rec of recordsWithKey) {
463
565
  if (!servedForThisKey.has(rec.recordingId)) {
@@ -478,18 +580,17 @@ var ProxyServer = class {
478
580
  const key = getReqID(req);
479
581
  const filePath = getRecordingPath(this.recordingsDir, recordingId);
480
582
  try {
481
- const sessionState = await this.ensureSessionLoaded(
482
- recordingId,
483
- filePath
484
- );
485
- const session = sessionState.loadedSession;
583
+ const sessionState = this.getOrCreateReplaySession(recordingId);
584
+ if (!sessionState.loadedSession) {
585
+ const error = new Error(
586
+ `Recording session file not found: ${filePath}`
587
+ );
588
+ error.code = "ENOENT";
589
+ throw error;
590
+ }
486
591
  const servedForThisKey = this.getServedTracker(sessionState, key);
487
592
  const host = req.headers.host || "unknown";
488
- const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => {
489
- const aSeq = a.sequence !== void 0 ? a.sequence : a.recordingId;
490
- const bSeq = b.sequence !== void 0 ? b.sequence : b.recordingId;
491
- return aSeq - bSeq;
492
- });
593
+ const recordsWithKey = this.getSortedRecordings(sessionState, key);
493
594
  if (recordsWithKey.length === 0) {
494
595
  const errorMsg = `No recording found for ${key} at ${req.method} ${host}${req.url}`;
495
596
  console.error(`[REPLAY ERROR] ${errorMsg} (session: ${recordingId})`);
@@ -700,7 +801,6 @@ var ProxyServer = class {
700
801
  })();
701
802
  });
702
803
  this.recordingPromises.push(recordingPromise);
703
- await recordingPromise;
704
804
  }
705
805
  handleUpgrade(req, socket, head) {
706
806
  if (this.mode === Modes.replay) {
@@ -865,8 +965,6 @@ var ProxyServer = class {
865
965
  );
866
966
  }
867
967
  };
868
-
869
- // src/playwright/index.ts
870
968
  function getProxyPort() {
871
969
  const envPort = process.env.TEST_PROXY_RECORDER_PORT;
872
970
  if (envPort) {
@@ -896,12 +994,34 @@ async function setProxyMode(mode, sessionId, timeout) {
896
994
  throw new Error(`Failed to set proxy mode: ${text}`);
897
995
  }
898
996
  await response.json();
899
- console.log(`Proxy mode set to: ${mode} (session: ${sessionId})`);
900
997
  } catch (error) {
901
998
  console.error(`Error setting proxy mode:`, error);
902
999
  throw error;
903
1000
  }
904
1001
  }
1002
+ async function cleanupSession(sessionId) {
1003
+ const proxyPort = getProxyPort();
1004
+ try {
1005
+ const body = {
1006
+ cleanup: true,
1007
+ id: sessionId
1008
+ };
1009
+ const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
1010
+ method: "POST",
1011
+ headers: { "Content-Type": "application/json" },
1012
+ body: JSON.stringify(body)
1013
+ });
1014
+ if (!response.ok) {
1015
+ const text = await response.text();
1016
+ console.error(`Failed to cleanup session ${sessionId}:`, text);
1017
+ throw new Error(`Failed to cleanup session: ${text}`);
1018
+ }
1019
+ await response.json();
1020
+ } catch (error) {
1021
+ console.error(`Error cleaning up session: ${sessionId}`, error);
1022
+ throw error;
1023
+ }
1024
+ }
905
1025
  function parseSpecFilePath(specPath) {
906
1026
  const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
907
1027
  if (folderMatch) {
@@ -943,6 +1063,50 @@ async function stopProxy(testInfo) {
943
1063
  const sessionId = generateSessionId(testInfo);
944
1064
  await setProxyMode(Modes.transparent, sessionId);
945
1065
  }
1066
+ var cachedRecordingsDir = null;
1067
+ async function getRecordingsDir() {
1068
+ if (cachedRecordingsDir) {
1069
+ return cachedRecordingsDir;
1070
+ }
1071
+ const proxyPort = getProxyPort();
1072
+ try {
1073
+ const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`);
1074
+ if (response.ok) {
1075
+ const data = await response.json();
1076
+ if (data.recordingsDir) {
1077
+ cachedRecordingsDir = data.recordingsDir;
1078
+ return cachedRecordingsDir;
1079
+ }
1080
+ }
1081
+ } catch (error) {
1082
+ console.warn(
1083
+ "Failed to get recordings directory from proxy, using default:",
1084
+ error
1085
+ );
1086
+ }
1087
+ cachedRecordingsDir = path2__default.default.join(process.cwd(), "e2e", "recordings");
1088
+ return cachedRecordingsDir;
1089
+ }
1090
+ async function setupClientSideRecording(page, sessionId, mode, url) {
1091
+ const harFileName = sessionId.replaceAll("/", "__");
1092
+ const recordingsDir = await getRecordingsDir();
1093
+ const harPath = path2__default.default.join(recordingsDir, `${harFileName}.har`);
1094
+ try {
1095
+ await page.routeFromHAR(harPath, {
1096
+ url,
1097
+ update: mode === Modes.record,
1098
+ updateContent: "embed"
1099
+ });
1100
+ } catch (error) {
1101
+ if (mode === Modes.replay) {
1102
+ console.error(
1103
+ `[Client-Side Replay] Failed to load HAR file. Run tests in record mode first.`,
1104
+ error
1105
+ );
1106
+ throw error;
1107
+ }
1108
+ }
1109
+ }
946
1110
  var playwrightProxy = {
947
1111
  /**
948
1112
  * Setup before test - sets the proxy mode and configures page with custom header
@@ -950,24 +1114,66 @@ var playwrightProxy = {
950
1114
  * @param page - Playwright page object
951
1115
  * @param testInfo - Playwright test info object
952
1116
  * @param mode - The proxy mode to use for this test
953
- * @param timeout - Optional timeout in milliseconds
1117
+ * @param options - Optional configuration including timeout and client-side recording patterns
954
1118
  */
955
- async before(page, testInfo, mode, timeout) {
1119
+ async before(page, testInfo, mode, options) {
1120
+ const timeout = typeof options === "number" ? options : options?.timeout;
1121
+ const clientSideOptions = typeof options === "object" && options !== null ? options : void 0;
956
1122
  const sessionId = generateSessionId(testInfo);
957
1123
  await page.setExtraHTTPHeaders({
958
1124
  [RECORDING_ID_HEADER]: sessionId
959
1125
  });
960
1126
  await setProxyMode(mode, sessionId, timeout);
961
- page.on("close", async () => {
962
- try {
963
- await setProxyMode(Modes.replay, sessionId);
964
- console.log(
965
- `[Cleanup] Switched to replay mode for session: ${sessionId}`
966
- );
967
- } catch (error) {
968
- console.error("[Cleanup] Error during page close cleanup:", error);
969
- }
970
- });
1127
+ if (clientSideOptions?.url) {
1128
+ await setupClientSideRecording(
1129
+ page,
1130
+ sessionId,
1131
+ mode,
1132
+ clientSideOptions.url
1133
+ );
1134
+ }
1135
+ const proxyPort = process.env.TEST_PROXY_RECORDER_PORT || "8100";
1136
+ const proxyUrl = `localhost:${proxyPort}`;
1137
+ await page.route(
1138
+ (url) => {
1139
+ const urlStr = url.toString();
1140
+ const matches = urlStr.includes(proxyUrl);
1141
+ return matches;
1142
+ },
1143
+ async (route) => {
1144
+ try {
1145
+ const headers = route.request().headers();
1146
+ headers[RECORDING_ID_HEADER] = sessionId;
1147
+ await route.continue({ headers });
1148
+ } catch (error) {
1149
+ console.error(
1150
+ `[Route Handler Error] Failed to add ${RECORDING_ID_HEADER} header:`,
1151
+ error
1152
+ );
1153
+ await route.fallback();
1154
+ }
1155
+ },
1156
+ { times: Infinity }
1157
+ // Ensure the handler applies to all matching requests
1158
+ );
1159
+ const context = page.context();
1160
+ const contextId = context._guid || "default";
1161
+ const handlerKey = `cleanup_${contextId}`;
1162
+ if (!globalThis[handlerKey]) {
1163
+ globalThis[handlerKey] = true;
1164
+ context.on("close", async () => {
1165
+ try {
1166
+ await cleanupSession(sessionId);
1167
+ } catch (error) {
1168
+ console.warn(
1169
+ `[Cleanup] Failed to cleanup session ${sessionId}:`,
1170
+ error
1171
+ );
1172
+ } finally {
1173
+ delete globalThis[handlerKey];
1174
+ }
1175
+ });
1176
+ }
971
1177
  },
972
1178
  /**
973
1179
  * Global teardown - switches proxy to transparent mode
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { RECORDING_ID_HEADER, createHeadersWithRecordingId, getRecordingId, setNextProxyHeaders } from './nextjs/index.cjs';
2
2
  import http from 'node:http';
3
- 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-CVuiglPk.cjs';
3
+ 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-BlBWqSE4.cjs';
4
4
  import '@playwright/test';
5
5
 
6
6
  declare class ProxyServer {
@@ -17,6 +17,7 @@ declare class ProxyServer {
17
17
  private sequenceCounterByKey;
18
18
  private replaySessions;
19
19
  private recordingPromises;
20
+ private flushPromise;
20
21
  constructor(targets: string[], recordingsDir: string);
21
22
  init(): Promise<void>;
22
23
  listen(port: number): http.Server;
@@ -56,6 +57,11 @@ declare class ProxyServer {
56
57
  * @returns The replay session state
57
58
  */
58
59
  private getOrCreateReplaySession;
60
+ /**
61
+ * Clean up a session - removes it from memory and resets counters
62
+ * @param sessionId The session ID to clean up
63
+ */
64
+ private cleanupSession;
59
65
  private parseGetParams;
60
66
  private parseControlRequest;
61
67
  private handleControlRequest;
@@ -68,8 +74,8 @@ declare class ProxyServer {
68
74
  private flushPendingRecordings;
69
75
  private saveCurrentSession;
70
76
  private getRecordingIdOrError;
71
- private ensureSessionLoaded;
72
77
  private getServedTracker;
78
+ private getSortedRecordings;
73
79
  private selectReplayRecord;
74
80
  private handleReplayRequest;
75
81
  private handleReplayError;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { RECORDING_ID_HEADER, createHeadersWithRecordingId, getRecordingId, setNextProxyHeaders } from './nextjs/index.js';
2
2
  import http from 'node:http';
3
- 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-CVuiglPk.js';
3
+ 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-BlBWqSE4.js';
4
4
  import '@playwright/test';
5
5
 
6
6
  declare class ProxyServer {
@@ -17,6 +17,7 @@ declare class ProxyServer {
17
17
  private sequenceCounterByKey;
18
18
  private replaySessions;
19
19
  private recordingPromises;
20
+ private flushPromise;
20
21
  constructor(targets: string[], recordingsDir: string);
21
22
  init(): Promise<void>;
22
23
  listen(port: number): http.Server;
@@ -56,6 +57,11 @@ declare class ProxyServer {
56
57
  * @returns The replay session state
57
58
  */
58
59
  private getOrCreateReplaySession;
60
+ /**
61
+ * Clean up a session - removes it from memory and resets counters
62
+ * @param sessionId The session ID to clean up
63
+ */
64
+ private cleanupSession;
59
65
  private parseGetParams;
60
66
  private parseControlRequest;
61
67
  private handleControlRequest;
@@ -68,8 +74,8 @@ declare class ProxyServer {
68
74
  private flushPendingRecordings;
69
75
  private saveCurrentSession;
70
76
  private getRecordingIdOrError;
71
- private ensureSessionLoaded;
72
77
  private getServedTracker;
78
+ private getSortedRecordings;
73
79
  private selectReplayRecord;
74
80
  private handleReplayRequest;
75
81
  private handleReplayError;