test-proxy-recorder 0.3.3 → 0.3.5
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/README.md +180 -524
- package/dist/index.cjs +98 -84
- package/dist/index.d.cts +9 -6
- package/dist/index.d.ts +9 -6
- package/dist/index.mjs +98 -84
- package/dist/playwright/index.cjs +7 -8
- package/dist/playwright/index.mjs +7 -8
- package/dist/proxy.js +131 -105
- package/package.json +8 -2
package/dist/index.cjs
CHANGED
|
@@ -71,9 +71,9 @@ function processRecordings(recordings) {
|
|
|
71
71
|
const processedRecordings = [];
|
|
72
72
|
for (const [_key, keyRecordings] of recordingsByKey) {
|
|
73
73
|
keyRecordings.sort((a, b) => a.recordingId - b.recordingId);
|
|
74
|
-
keyRecordings.
|
|
74
|
+
for (const [index, recording] of keyRecordings.entries()) {
|
|
75
75
|
processedRecordings.push({ ...recording, sequence: index });
|
|
76
|
-
}
|
|
76
|
+
}
|
|
77
77
|
}
|
|
78
78
|
processedRecordings.sort((a, b) => a.recordingId - b.recordingId);
|
|
79
79
|
return processedRecordings;
|
|
@@ -131,8 +131,7 @@ function sendJsonResponse(res, statusCode, data) {
|
|
|
131
131
|
|
|
132
132
|
// src/ProxyServer.ts
|
|
133
133
|
var ProxyServer = class {
|
|
134
|
-
|
|
135
|
-
currentTargetIndex;
|
|
134
|
+
target;
|
|
136
135
|
mode;
|
|
137
136
|
recordingId;
|
|
138
137
|
replayId;
|
|
@@ -140,19 +139,22 @@ var ProxyServer = class {
|
|
|
140
139
|
proxy;
|
|
141
140
|
currentSession;
|
|
142
141
|
recordingsDir;
|
|
142
|
+
timeoutMs;
|
|
143
143
|
recordingIdCounter;
|
|
144
144
|
// Unique ID for each recording entry
|
|
145
145
|
sequenceCounterByKey;
|
|
146
146
|
// Sequence counter per key (endpoint)
|
|
147
147
|
replaySessions;
|
|
148
148
|
// Track multiple concurrent replay sessions by recording ID
|
|
149
|
+
sessionEvictionTimer;
|
|
150
|
+
// Periodic timer to evict idle replay sessions
|
|
149
151
|
recordingPromises;
|
|
150
152
|
// Stack of promises that resolve to completed recordings
|
|
151
153
|
flushPromise;
|
|
152
154
|
// Promise for in-progress flush operation
|
|
153
|
-
constructor(
|
|
154
|
-
this.
|
|
155
|
-
this.
|
|
155
|
+
constructor(target, recordingsDir, timeoutMs) {
|
|
156
|
+
this.target = target;
|
|
157
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
156
158
|
this.mode = Modes.transparent;
|
|
157
159
|
this.recordingId = null;
|
|
158
160
|
this.recordingIdCounter = 0;
|
|
@@ -162,6 +164,7 @@ var ProxyServer = class {
|
|
|
162
164
|
this.currentSession = null;
|
|
163
165
|
this.recordingsDir = recordingsDir;
|
|
164
166
|
this.replaySessions = /* @__PURE__ */ new Map();
|
|
167
|
+
this.sessionEvictionTimer = null;
|
|
165
168
|
this.recordingPromises = [];
|
|
166
169
|
this.flushPromise = null;
|
|
167
170
|
this.proxy = httpProxy__default.default.createProxyServer({
|
|
@@ -224,11 +227,6 @@ var ProxyServer = class {
|
|
|
224
227
|
const corsHeaders = this.getCorsHeaders(req);
|
|
225
228
|
Object.assign(proxyRes.headers, corsHeaders);
|
|
226
229
|
}
|
|
227
|
-
getTarget() {
|
|
228
|
-
const target = this.targets[this.currentTargetIndex];
|
|
229
|
-
this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
|
|
230
|
-
return target;
|
|
231
|
-
}
|
|
232
230
|
/**
|
|
233
231
|
* Extract recording ID from custom HTTP header
|
|
234
232
|
* Used for concurrent replay session routing, especially with Next.js
|
|
@@ -264,13 +262,7 @@ var ProxyServer = class {
|
|
|
264
262
|
getRecordingIdFromRequest(req) {
|
|
265
263
|
const fromHeader = this.getRecordingIdFromHeader(req);
|
|
266
264
|
const fromCookie = this.getRecordingIdFromCookie(req);
|
|
267
|
-
|
|
268
|
-
return fromHeader;
|
|
269
|
-
}
|
|
270
|
-
if (fromCookie) {
|
|
271
|
-
return fromCookie;
|
|
272
|
-
}
|
|
273
|
-
return null;
|
|
265
|
+
return fromHeader ?? fromCookie ?? null;
|
|
274
266
|
}
|
|
275
267
|
/**
|
|
276
268
|
* Get or create a replay session state for a given recording ID
|
|
@@ -290,6 +282,7 @@ var ProxyServer = class {
|
|
|
290
282
|
sortedRecordingsByKey: /* @__PURE__ */ new Map()
|
|
291
283
|
};
|
|
292
284
|
this.replaySessions.set(recordingId, session);
|
|
285
|
+
this.startSessionEvictionTimer();
|
|
293
286
|
console.log(
|
|
294
287
|
`[CONCURRENT REPLAY] Created new session for recording: ${recordingId}`
|
|
295
288
|
);
|
|
@@ -301,8 +294,9 @@ var ProxyServer = class {
|
|
|
301
294
|
* @param sessionId The session ID to clean up
|
|
302
295
|
*/
|
|
303
296
|
async cleanupSession(sessionId) {
|
|
304
|
-
|
|
305
|
-
|
|
297
|
+
this.replaySessions.delete(sessionId);
|
|
298
|
+
if (this.replaySessions.size === 0) {
|
|
299
|
+
this.stopSessionEvictionTimer();
|
|
306
300
|
}
|
|
307
301
|
if (this.recordingId === sessionId) {
|
|
308
302
|
await this.saveCurrentSession();
|
|
@@ -314,29 +308,44 @@ var ProxyServer = class {
|
|
|
314
308
|
}
|
|
315
309
|
console.log(`[CLEANUP] Session ${sessionId} cleaned up successfully`);
|
|
316
310
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const id = url.searchParams.get("id") || void 0;
|
|
321
|
-
const timeoutParam = url.searchParams.get("timeout");
|
|
322
|
-
const timeout = timeoutParam ? Number.parseInt(timeoutParam, 10) : void 0;
|
|
323
|
-
if (!mode) {
|
|
324
|
-
throw new Error("Mode parameter is required");
|
|
311
|
+
startSessionEvictionTimer() {
|
|
312
|
+
if (this.sessionEvictionTimer) {
|
|
313
|
+
return;
|
|
325
314
|
}
|
|
326
|
-
|
|
315
|
+
const CHECK_INTERVAL_MS = 3e4;
|
|
316
|
+
this.sessionEvictionTimer = setInterval(() => {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
for (const [id, session] of this.replaySessions) {
|
|
319
|
+
if (now - session.lastAccessTime >= this.timeoutMs) {
|
|
320
|
+
console.log(
|
|
321
|
+
`[EVICTION] Evicting idle replay session: ${id} (idle for ${Math.round((now - session.lastAccessTime) / 1e3)}s)`
|
|
322
|
+
);
|
|
323
|
+
this.replaySessions.delete(id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (this.replaySessions.size === 0) {
|
|
327
|
+
this.stopSessionEvictionTimer();
|
|
328
|
+
}
|
|
329
|
+
}, CHECK_INTERVAL_MS);
|
|
330
|
+
this.sessionEvictionTimer.unref();
|
|
327
331
|
}
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (req.method === "POST") {
|
|
333
|
-
const body = await readRequestBody(req);
|
|
334
|
-
console.log(`MODE CHANGE (${req.method})`, body);
|
|
335
|
-
return JSON.parse(body);
|
|
332
|
+
stopSessionEvictionTimer() {
|
|
333
|
+
if (this.sessionEvictionTimer) {
|
|
334
|
+
clearInterval(this.sessionEvictionTimer);
|
|
335
|
+
this.sessionEvictionTimer = null;
|
|
336
336
|
}
|
|
337
|
-
|
|
337
|
+
}
|
|
338
|
+
async parseControlBody(req) {
|
|
339
|
+
const body = await readRequestBody(req);
|
|
340
|
+
console.log(`MODE CHANGE (${req.method})`, body);
|
|
341
|
+
return JSON.parse(body);
|
|
338
342
|
}
|
|
339
343
|
async handleControlRequest(req, res) {
|
|
344
|
+
if (req.method === "HEAD") {
|
|
345
|
+
res.writeHead(HTTP_STATUS_OK);
|
|
346
|
+
res.end();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
340
349
|
if (req.method === "GET") {
|
|
341
350
|
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
342
351
|
recordingsDir: this.recordingsDir,
|
|
@@ -345,8 +354,11 @@ var ProxyServer = class {
|
|
|
345
354
|
});
|
|
346
355
|
return;
|
|
347
356
|
}
|
|
357
|
+
await this.handleControlPost(req, res);
|
|
358
|
+
}
|
|
359
|
+
async handleControlPost(req, res) {
|
|
348
360
|
try {
|
|
349
|
-
const data = await this.
|
|
361
|
+
const data = await this.parseControlBody(req);
|
|
350
362
|
const { mode, id, timeout: requestTimeout, cleanup } = data;
|
|
351
363
|
if (cleanup && id) {
|
|
352
364
|
await this.cleanupSession(id);
|
|
@@ -357,29 +369,7 @@ var ProxyServer = class {
|
|
|
357
369
|
});
|
|
358
370
|
return;
|
|
359
371
|
}
|
|
360
|
-
|
|
361
|
-
throw new Error(
|
|
362
|
-
"Mode parameter is required when cleanup is not specified"
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
366
|
-
this.clearModeTimeout();
|
|
367
|
-
await this.switchMode(mode, id);
|
|
368
|
-
this.setupModeTimeout(timeout);
|
|
369
|
-
if (mode === Modes.replay && id) {
|
|
370
|
-
res.setHeader(
|
|
371
|
-
"Set-Cookie",
|
|
372
|
-
`proxy-recording-id=${encodeURIComponent(id)}; HttpOnly; Path=/; SameSite=Lax`
|
|
373
|
-
);
|
|
374
|
-
console.log(`[CONCURRENT REPLAY] Set cookie for recording: ${id}`);
|
|
375
|
-
}
|
|
376
|
-
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
377
|
-
success: true,
|
|
378
|
-
mode: this.mode,
|
|
379
|
-
id: this.recordingId || this.replayId,
|
|
380
|
-
timeout,
|
|
381
|
-
recordingsDir: this.recordingsDir
|
|
382
|
-
});
|
|
372
|
+
await this.applyModeChange(res, mode, id, requestTimeout);
|
|
383
373
|
} catch (error) {
|
|
384
374
|
console.error("Control request error:", error);
|
|
385
375
|
sendJsonResponse(res, HTTP_STATUS_BAD_REQUEST, {
|
|
@@ -387,6 +377,31 @@ var ProxyServer = class {
|
|
|
387
377
|
});
|
|
388
378
|
}
|
|
389
379
|
}
|
|
380
|
+
async applyModeChange(res, mode, id, requestTimeout) {
|
|
381
|
+
if (!mode) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
"Mode parameter is required when cleanup is not specified"
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const timeout = requestTimeout ?? this.timeoutMs;
|
|
387
|
+
this.clearModeTimeout();
|
|
388
|
+
await this.switchMode(mode, id);
|
|
389
|
+
this.setupModeTimeout(timeout);
|
|
390
|
+
if (mode === Modes.replay && id) {
|
|
391
|
+
res.setHeader(
|
|
392
|
+
"Set-Cookie",
|
|
393
|
+
`proxy-recording-id=${encodeURIComponent(id)}; HttpOnly; Path=/; SameSite=Lax`
|
|
394
|
+
);
|
|
395
|
+
console.log(`[CONCURRENT REPLAY] Set cookie for recording: ${id}`);
|
|
396
|
+
}
|
|
397
|
+
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
398
|
+
success: true,
|
|
399
|
+
mode: this.mode,
|
|
400
|
+
id: this.recordingId || this.replayId,
|
|
401
|
+
timeout,
|
|
402
|
+
recordingsDir: this.recordingsDir
|
|
403
|
+
});
|
|
404
|
+
}
|
|
390
405
|
clearModeTimeout() {
|
|
391
406
|
clearTimeout(this.modeTimeout || 0);
|
|
392
407
|
this.modeTimeout = null;
|
|
@@ -426,7 +441,7 @@ var ProxyServer = class {
|
|
|
426
441
|
this.recordingId = null;
|
|
427
442
|
this.replayId = null;
|
|
428
443
|
this.currentSession = null;
|
|
429
|
-
|
|
444
|
+
this.clearModeTimeout();
|
|
430
445
|
console.log("Switched to transparent mode");
|
|
431
446
|
}
|
|
432
447
|
switchToRecordMode(id) {
|
|
@@ -457,7 +472,7 @@ var ProxyServer = class {
|
|
|
457
472
|
console.log(`Switched to replay mode with ID: ${id}`);
|
|
458
473
|
}
|
|
459
474
|
setupModeTimeout(timeout) {
|
|
460
|
-
|
|
475
|
+
this.clearModeTimeout();
|
|
461
476
|
this.modeTimeout = setTimeout(async () => {
|
|
462
477
|
console.log("Timeout reached, switching back to transparent mode");
|
|
463
478
|
await this.saveCurrentSession();
|
|
@@ -582,11 +597,10 @@ var ProxyServer = class {
|
|
|
582
597
|
try {
|
|
583
598
|
const sessionState = this.getOrCreateReplaySession(recordingId);
|
|
584
599
|
if (!sessionState.loadedSession) {
|
|
585
|
-
|
|
586
|
-
`Recording session file not found: ${filePath}`
|
|
600
|
+
throw Object.assign(
|
|
601
|
+
new Error(`Recording session file not found: ${filePath}`),
|
|
602
|
+
{ code: "ENOENT" }
|
|
587
603
|
);
|
|
588
|
-
error.code = "ENOENT";
|
|
589
|
-
throw error;
|
|
590
604
|
}
|
|
591
605
|
const servedForThisKey = this.getServedTracker(sessionState, key);
|
|
592
606
|
const host = req.headers.host || "unknown";
|
|
@@ -681,7 +695,7 @@ var ProxyServer = class {
|
|
|
681
695
|
res.end();
|
|
682
696
|
}
|
|
683
697
|
async handleProxyRequest(req, res) {
|
|
684
|
-
const target = this.
|
|
698
|
+
const target = this.target;
|
|
685
699
|
console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
|
|
686
700
|
if (this.mode === Modes.record) {
|
|
687
701
|
await this.recordAndProxyRequest(req, res, target);
|
|
@@ -807,7 +821,7 @@ var ProxyServer = class {
|
|
|
807
821
|
this.handleReplayWebSocket(req, socket);
|
|
808
822
|
return;
|
|
809
823
|
}
|
|
810
|
-
const target = this.
|
|
824
|
+
const target = this.target;
|
|
811
825
|
console.log(`[${this.mode}] WebSocket upgrade ${req.url} -> ${target}`);
|
|
812
826
|
if (this.mode === Modes.record) {
|
|
813
827
|
this.handleRecordWebSocket(req, socket, head, target);
|
|
@@ -880,11 +894,12 @@ var ProxyServer = class {
|
|
|
880
894
|
console.error("WebSocket server error:", err);
|
|
881
895
|
});
|
|
882
896
|
}
|
|
883
|
-
handleReplayWebSocket(req, socket) {
|
|
897
|
+
async handleReplayWebSocket(req, socket) {
|
|
884
898
|
const url = req.url || "/";
|
|
885
899
|
const key = `WS_${url.replaceAll("/", "_")}`;
|
|
886
900
|
const filePath = getRecordingPath(this.recordingsDir, this.replayId);
|
|
887
|
-
|
|
901
|
+
try {
|
|
902
|
+
const session = await loadRecordingSession(filePath);
|
|
888
903
|
const wsRecording = session.websocketRecordings.find(
|
|
889
904
|
(r) => r.key === key
|
|
890
905
|
);
|
|
@@ -950,21 +965,22 @@ var ProxyServer = class {
|
|
|
950
965
|
console.log("Replay WebSocket closed");
|
|
951
966
|
});
|
|
952
967
|
});
|
|
953
|
-
}
|
|
968
|
+
} catch (error) {
|
|
954
969
|
console.error("Replay error:", error);
|
|
955
970
|
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
956
971
|
socket.destroy();
|
|
957
|
-
}
|
|
972
|
+
}
|
|
958
973
|
}
|
|
959
974
|
logServerStartup(port) {
|
|
960
975
|
console.log(`Proxy server running on http://localhost:${port}`);
|
|
961
976
|
console.log(`Mode: ${this.mode}`);
|
|
962
|
-
console.log(`
|
|
977
|
+
console.log(`Target: ${this.target}`);
|
|
963
978
|
console.log(
|
|
964
979
|
`Control endpoint: http://localhost:${port}${CONTROL_ENDPOINT}`
|
|
965
980
|
);
|
|
966
981
|
}
|
|
967
982
|
};
|
|
983
|
+
var registeredContexts = /* @__PURE__ */ new WeakSet();
|
|
968
984
|
function getProxyPort() {
|
|
969
985
|
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
970
986
|
if (envPort) {
|
|
@@ -983,7 +999,7 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
983
999
|
id: sessionId,
|
|
984
1000
|
...timeout && { timeout }
|
|
985
1001
|
};
|
|
986
|
-
const response = await fetch(`http://
|
|
1002
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
987
1003
|
method: "POST",
|
|
988
1004
|
headers: { "Content-Type": "application/json" },
|
|
989
1005
|
body: JSON.stringify(body)
|
|
@@ -1006,7 +1022,7 @@ async function cleanupSession(sessionId) {
|
|
|
1006
1022
|
cleanup: true,
|
|
1007
1023
|
id: sessionId
|
|
1008
1024
|
};
|
|
1009
|
-
const response = await fetch(`http://
|
|
1025
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
1010
1026
|
method: "POST",
|
|
1011
1027
|
headers: { "Content-Type": "application/json" },
|
|
1012
1028
|
body: JSON.stringify(body)
|
|
@@ -1070,7 +1086,7 @@ async function getRecordingsDir() {
|
|
|
1070
1086
|
}
|
|
1071
1087
|
const proxyPort = getProxyPort();
|
|
1072
1088
|
try {
|
|
1073
|
-
const response = await fetch(`http://
|
|
1089
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`);
|
|
1074
1090
|
if (response.ok) {
|
|
1075
1091
|
const data = await response.json();
|
|
1076
1092
|
if (data.recordingsDir) {
|
|
@@ -1157,10 +1173,8 @@ var playwrightProxy = {
|
|
|
1157
1173
|
// Ensure the handler applies to all matching requests
|
|
1158
1174
|
);
|
|
1159
1175
|
const context = page.context();
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
if (!globalThis[handlerKey]) {
|
|
1163
|
-
globalThis[handlerKey] = true;
|
|
1176
|
+
if (!registeredContexts.has(context)) {
|
|
1177
|
+
registeredContexts.add(context);
|
|
1164
1178
|
context.on("close", async () => {
|
|
1165
1179
|
try {
|
|
1166
1180
|
await cleanupSession(sessionId);
|
|
@@ -1170,7 +1184,7 @@ var playwrightProxy = {
|
|
|
1170
1184
|
error
|
|
1171
1185
|
);
|
|
1172
1186
|
} finally {
|
|
1173
|
-
delete
|
|
1187
|
+
registeredContexts.delete(context);
|
|
1174
1188
|
}
|
|
1175
1189
|
});
|
|
1176
1190
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,8 +4,7 @@ export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording
|
|
|
4
4
|
import '@playwright/test';
|
|
5
5
|
|
|
6
6
|
declare class ProxyServer {
|
|
7
|
-
private
|
|
8
|
-
private currentTargetIndex;
|
|
7
|
+
private target;
|
|
9
8
|
private mode;
|
|
10
9
|
private recordingId;
|
|
11
10
|
private replayId;
|
|
@@ -13,12 +12,14 @@ declare class ProxyServer {
|
|
|
13
12
|
private proxy;
|
|
14
13
|
private currentSession;
|
|
15
14
|
private recordingsDir;
|
|
15
|
+
private timeoutMs;
|
|
16
16
|
private recordingIdCounter;
|
|
17
17
|
private sequenceCounterByKey;
|
|
18
18
|
private replaySessions;
|
|
19
|
+
private sessionEvictionTimer;
|
|
19
20
|
private recordingPromises;
|
|
20
21
|
private flushPromise;
|
|
21
|
-
constructor(
|
|
22
|
+
constructor(target: string, recordingsDir: string, timeoutMs?: number);
|
|
22
23
|
init(): Promise<void>;
|
|
23
24
|
listen(port: number): http.Server;
|
|
24
25
|
private setupProxyEventHandlers;
|
|
@@ -30,7 +31,6 @@ declare class ProxyServer {
|
|
|
30
31
|
*/
|
|
31
32
|
private getCorsHeaders;
|
|
32
33
|
private addCorsHeaders;
|
|
33
|
-
private getTarget;
|
|
34
34
|
/**
|
|
35
35
|
* Extract recording ID from custom HTTP header
|
|
36
36
|
* Used for concurrent replay session routing, especially with Next.js
|
|
@@ -62,9 +62,12 @@ declare class ProxyServer {
|
|
|
62
62
|
* @param sessionId The session ID to clean up
|
|
63
63
|
*/
|
|
64
64
|
private cleanupSession;
|
|
65
|
-
private
|
|
66
|
-
private
|
|
65
|
+
private startSessionEvictionTimer;
|
|
66
|
+
private stopSessionEvictionTimer;
|
|
67
|
+
private parseControlBody;
|
|
67
68
|
private handleControlRequest;
|
|
69
|
+
private handleControlPost;
|
|
70
|
+
private applyModeChange;
|
|
68
71
|
private clearModeTimeout;
|
|
69
72
|
private switchMode;
|
|
70
73
|
private switchToTransparentMode;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording
|
|
|
4
4
|
import '@playwright/test';
|
|
5
5
|
|
|
6
6
|
declare class ProxyServer {
|
|
7
|
-
private
|
|
8
|
-
private currentTargetIndex;
|
|
7
|
+
private target;
|
|
9
8
|
private mode;
|
|
10
9
|
private recordingId;
|
|
11
10
|
private replayId;
|
|
@@ -13,12 +12,14 @@ declare class ProxyServer {
|
|
|
13
12
|
private proxy;
|
|
14
13
|
private currentSession;
|
|
15
14
|
private recordingsDir;
|
|
15
|
+
private timeoutMs;
|
|
16
16
|
private recordingIdCounter;
|
|
17
17
|
private sequenceCounterByKey;
|
|
18
18
|
private replaySessions;
|
|
19
|
+
private sessionEvictionTimer;
|
|
19
20
|
private recordingPromises;
|
|
20
21
|
private flushPromise;
|
|
21
|
-
constructor(
|
|
22
|
+
constructor(target: string, recordingsDir: string, timeoutMs?: number);
|
|
22
23
|
init(): Promise<void>;
|
|
23
24
|
listen(port: number): http.Server;
|
|
24
25
|
private setupProxyEventHandlers;
|
|
@@ -30,7 +31,6 @@ declare class ProxyServer {
|
|
|
30
31
|
*/
|
|
31
32
|
private getCorsHeaders;
|
|
32
33
|
private addCorsHeaders;
|
|
33
|
-
private getTarget;
|
|
34
34
|
/**
|
|
35
35
|
* Extract recording ID from custom HTTP header
|
|
36
36
|
* Used for concurrent replay session routing, especially with Next.js
|
|
@@ -62,9 +62,12 @@ declare class ProxyServer {
|
|
|
62
62
|
* @param sessionId The session ID to clean up
|
|
63
63
|
*/
|
|
64
64
|
private cleanupSession;
|
|
65
|
-
private
|
|
66
|
-
private
|
|
65
|
+
private startSessionEvictionTimer;
|
|
66
|
+
private stopSessionEvictionTimer;
|
|
67
|
+
private parseControlBody;
|
|
67
68
|
private handleControlRequest;
|
|
69
|
+
private handleControlPost;
|
|
70
|
+
private applyModeChange;
|
|
68
71
|
private clearModeTimeout;
|
|
69
72
|
private switchMode;
|
|
70
73
|
private switchToTransparentMode;
|