test-proxy-recorder 0.3.4 → 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 +95 -77
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.mjs +95 -77
- package/dist/playwright/index.cjs +7 -8
- package/dist/playwright/index.mjs +7 -8
- package/dist/proxy.js +114 -84
- package/package.json +8 -2
package/dist/index.mjs
CHANGED
|
@@ -59,9 +59,9 @@ function processRecordings(recordings) {
|
|
|
59
59
|
const processedRecordings = [];
|
|
60
60
|
for (const [_key, keyRecordings] of recordingsByKey) {
|
|
61
61
|
keyRecordings.sort((a, b) => a.recordingId - b.recordingId);
|
|
62
|
-
keyRecordings.
|
|
62
|
+
for (const [index, recording] of keyRecordings.entries()) {
|
|
63
63
|
processedRecordings.push({ ...recording, sequence: index });
|
|
64
|
-
}
|
|
64
|
+
}
|
|
65
65
|
}
|
|
66
66
|
processedRecordings.sort((a, b) => a.recordingId - b.recordingId);
|
|
67
67
|
return processedRecordings;
|
|
@@ -127,18 +127,22 @@ var ProxyServer = class {
|
|
|
127
127
|
proxy;
|
|
128
128
|
currentSession;
|
|
129
129
|
recordingsDir;
|
|
130
|
+
timeoutMs;
|
|
130
131
|
recordingIdCounter;
|
|
131
132
|
// Unique ID for each recording entry
|
|
132
133
|
sequenceCounterByKey;
|
|
133
134
|
// Sequence counter per key (endpoint)
|
|
134
135
|
replaySessions;
|
|
135
136
|
// Track multiple concurrent replay sessions by recording ID
|
|
137
|
+
sessionEvictionTimer;
|
|
138
|
+
// Periodic timer to evict idle replay sessions
|
|
136
139
|
recordingPromises;
|
|
137
140
|
// Stack of promises that resolve to completed recordings
|
|
138
141
|
flushPromise;
|
|
139
142
|
// Promise for in-progress flush operation
|
|
140
|
-
constructor(target, recordingsDir) {
|
|
143
|
+
constructor(target, recordingsDir, timeoutMs) {
|
|
141
144
|
this.target = target;
|
|
145
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
142
146
|
this.mode = Modes.transparent;
|
|
143
147
|
this.recordingId = null;
|
|
144
148
|
this.recordingIdCounter = 0;
|
|
@@ -148,6 +152,7 @@ var ProxyServer = class {
|
|
|
148
152
|
this.currentSession = null;
|
|
149
153
|
this.recordingsDir = recordingsDir;
|
|
150
154
|
this.replaySessions = /* @__PURE__ */ new Map();
|
|
155
|
+
this.sessionEvictionTimer = null;
|
|
151
156
|
this.recordingPromises = [];
|
|
152
157
|
this.flushPromise = null;
|
|
153
158
|
this.proxy = httpProxy.createProxyServer({
|
|
@@ -210,9 +215,6 @@ var ProxyServer = class {
|
|
|
210
215
|
const corsHeaders = this.getCorsHeaders(req);
|
|
211
216
|
Object.assign(proxyRes.headers, corsHeaders);
|
|
212
217
|
}
|
|
213
|
-
getTarget() {
|
|
214
|
-
return this.target;
|
|
215
|
-
}
|
|
216
218
|
/**
|
|
217
219
|
* Extract recording ID from custom HTTP header
|
|
218
220
|
* Used for concurrent replay session routing, especially with Next.js
|
|
@@ -248,13 +250,7 @@ var ProxyServer = class {
|
|
|
248
250
|
getRecordingIdFromRequest(req) {
|
|
249
251
|
const fromHeader = this.getRecordingIdFromHeader(req);
|
|
250
252
|
const fromCookie = this.getRecordingIdFromCookie(req);
|
|
251
|
-
|
|
252
|
-
return fromHeader;
|
|
253
|
-
}
|
|
254
|
-
if (fromCookie) {
|
|
255
|
-
return fromCookie;
|
|
256
|
-
}
|
|
257
|
-
return null;
|
|
253
|
+
return fromHeader ?? fromCookie ?? null;
|
|
258
254
|
}
|
|
259
255
|
/**
|
|
260
256
|
* Get or create a replay session state for a given recording ID
|
|
@@ -274,6 +270,7 @@ var ProxyServer = class {
|
|
|
274
270
|
sortedRecordingsByKey: /* @__PURE__ */ new Map()
|
|
275
271
|
};
|
|
276
272
|
this.replaySessions.set(recordingId, session);
|
|
273
|
+
this.startSessionEvictionTimer();
|
|
277
274
|
console.log(
|
|
278
275
|
`[CONCURRENT REPLAY] Created new session for recording: ${recordingId}`
|
|
279
276
|
);
|
|
@@ -285,8 +282,9 @@ var ProxyServer = class {
|
|
|
285
282
|
* @param sessionId The session ID to clean up
|
|
286
283
|
*/
|
|
287
284
|
async cleanupSession(sessionId) {
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
this.replaySessions.delete(sessionId);
|
|
286
|
+
if (this.replaySessions.size === 0) {
|
|
287
|
+
this.stopSessionEvictionTimer();
|
|
290
288
|
}
|
|
291
289
|
if (this.recordingId === sessionId) {
|
|
292
290
|
await this.saveCurrentSession();
|
|
@@ -298,29 +296,44 @@ var ProxyServer = class {
|
|
|
298
296
|
}
|
|
299
297
|
console.log(`[CLEANUP] Session ${sessionId} cleaned up successfully`);
|
|
300
298
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const id = url.searchParams.get("id") || void 0;
|
|
305
|
-
const timeoutParam = url.searchParams.get("timeout");
|
|
306
|
-
const timeout = timeoutParam ? Number.parseInt(timeoutParam, 10) : void 0;
|
|
307
|
-
if (!mode) {
|
|
308
|
-
throw new Error("Mode parameter is required");
|
|
299
|
+
startSessionEvictionTimer() {
|
|
300
|
+
if (this.sessionEvictionTimer) {
|
|
301
|
+
return;
|
|
309
302
|
}
|
|
310
|
-
|
|
303
|
+
const CHECK_INTERVAL_MS = 3e4;
|
|
304
|
+
this.sessionEvictionTimer = setInterval(() => {
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
for (const [id, session] of this.replaySessions) {
|
|
307
|
+
if (now - session.lastAccessTime >= this.timeoutMs) {
|
|
308
|
+
console.log(
|
|
309
|
+
`[EVICTION] Evicting idle replay session: ${id} (idle for ${Math.round((now - session.lastAccessTime) / 1e3)}s)`
|
|
310
|
+
);
|
|
311
|
+
this.replaySessions.delete(id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (this.replaySessions.size === 0) {
|
|
315
|
+
this.stopSessionEvictionTimer();
|
|
316
|
+
}
|
|
317
|
+
}, CHECK_INTERVAL_MS);
|
|
318
|
+
this.sessionEvictionTimer.unref();
|
|
311
319
|
}
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (req.method === "POST") {
|
|
317
|
-
const body = await readRequestBody(req);
|
|
318
|
-
console.log(`MODE CHANGE (${req.method})`, body);
|
|
319
|
-
return JSON.parse(body);
|
|
320
|
+
stopSessionEvictionTimer() {
|
|
321
|
+
if (this.sessionEvictionTimer) {
|
|
322
|
+
clearInterval(this.sessionEvictionTimer);
|
|
323
|
+
this.sessionEvictionTimer = null;
|
|
320
324
|
}
|
|
321
|
-
|
|
325
|
+
}
|
|
326
|
+
async parseControlBody(req) {
|
|
327
|
+
const body = await readRequestBody(req);
|
|
328
|
+
console.log(`MODE CHANGE (${req.method})`, body);
|
|
329
|
+
return JSON.parse(body);
|
|
322
330
|
}
|
|
323
331
|
async handleControlRequest(req, res) {
|
|
332
|
+
if (req.method === "HEAD") {
|
|
333
|
+
res.writeHead(HTTP_STATUS_OK);
|
|
334
|
+
res.end();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
324
337
|
if (req.method === "GET") {
|
|
325
338
|
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
326
339
|
recordingsDir: this.recordingsDir,
|
|
@@ -329,8 +342,11 @@ var ProxyServer = class {
|
|
|
329
342
|
});
|
|
330
343
|
return;
|
|
331
344
|
}
|
|
345
|
+
await this.handleControlPost(req, res);
|
|
346
|
+
}
|
|
347
|
+
async handleControlPost(req, res) {
|
|
332
348
|
try {
|
|
333
|
-
const data = await this.
|
|
349
|
+
const data = await this.parseControlBody(req);
|
|
334
350
|
const { mode, id, timeout: requestTimeout, cleanup } = data;
|
|
335
351
|
if (cleanup && id) {
|
|
336
352
|
await this.cleanupSession(id);
|
|
@@ -341,29 +357,7 @@ var ProxyServer = class {
|
|
|
341
357
|
});
|
|
342
358
|
return;
|
|
343
359
|
}
|
|
344
|
-
|
|
345
|
-
throw new Error(
|
|
346
|
-
"Mode parameter is required when cleanup is not specified"
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
350
|
-
this.clearModeTimeout();
|
|
351
|
-
await this.switchMode(mode, id);
|
|
352
|
-
this.setupModeTimeout(timeout);
|
|
353
|
-
if (mode === Modes.replay && id) {
|
|
354
|
-
res.setHeader(
|
|
355
|
-
"Set-Cookie",
|
|
356
|
-
`proxy-recording-id=${encodeURIComponent(id)}; HttpOnly; Path=/; SameSite=Lax`
|
|
357
|
-
);
|
|
358
|
-
console.log(`[CONCURRENT REPLAY] Set cookie for recording: ${id}`);
|
|
359
|
-
}
|
|
360
|
-
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
361
|
-
success: true,
|
|
362
|
-
mode: this.mode,
|
|
363
|
-
id: this.recordingId || this.replayId,
|
|
364
|
-
timeout,
|
|
365
|
-
recordingsDir: this.recordingsDir
|
|
366
|
-
});
|
|
360
|
+
await this.applyModeChange(res, mode, id, requestTimeout);
|
|
367
361
|
} catch (error) {
|
|
368
362
|
console.error("Control request error:", error);
|
|
369
363
|
sendJsonResponse(res, HTTP_STATUS_BAD_REQUEST, {
|
|
@@ -371,6 +365,31 @@ var ProxyServer = class {
|
|
|
371
365
|
});
|
|
372
366
|
}
|
|
373
367
|
}
|
|
368
|
+
async applyModeChange(res, mode, id, requestTimeout) {
|
|
369
|
+
if (!mode) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"Mode parameter is required when cleanup is not specified"
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const timeout = requestTimeout ?? this.timeoutMs;
|
|
375
|
+
this.clearModeTimeout();
|
|
376
|
+
await this.switchMode(mode, id);
|
|
377
|
+
this.setupModeTimeout(timeout);
|
|
378
|
+
if (mode === Modes.replay && id) {
|
|
379
|
+
res.setHeader(
|
|
380
|
+
"Set-Cookie",
|
|
381
|
+
`proxy-recording-id=${encodeURIComponent(id)}; HttpOnly; Path=/; SameSite=Lax`
|
|
382
|
+
);
|
|
383
|
+
console.log(`[CONCURRENT REPLAY] Set cookie for recording: ${id}`);
|
|
384
|
+
}
|
|
385
|
+
sendJsonResponse(res, HTTP_STATUS_OK, {
|
|
386
|
+
success: true,
|
|
387
|
+
mode: this.mode,
|
|
388
|
+
id: this.recordingId || this.replayId,
|
|
389
|
+
timeout,
|
|
390
|
+
recordingsDir: this.recordingsDir
|
|
391
|
+
});
|
|
392
|
+
}
|
|
374
393
|
clearModeTimeout() {
|
|
375
394
|
clearTimeout(this.modeTimeout || 0);
|
|
376
395
|
this.modeTimeout = null;
|
|
@@ -410,7 +429,7 @@ var ProxyServer = class {
|
|
|
410
429
|
this.recordingId = null;
|
|
411
430
|
this.replayId = null;
|
|
412
431
|
this.currentSession = null;
|
|
413
|
-
|
|
432
|
+
this.clearModeTimeout();
|
|
414
433
|
console.log("Switched to transparent mode");
|
|
415
434
|
}
|
|
416
435
|
switchToRecordMode(id) {
|
|
@@ -441,7 +460,7 @@ var ProxyServer = class {
|
|
|
441
460
|
console.log(`Switched to replay mode with ID: ${id}`);
|
|
442
461
|
}
|
|
443
462
|
setupModeTimeout(timeout) {
|
|
444
|
-
|
|
463
|
+
this.clearModeTimeout();
|
|
445
464
|
this.modeTimeout = setTimeout(async () => {
|
|
446
465
|
console.log("Timeout reached, switching back to transparent mode");
|
|
447
466
|
await this.saveCurrentSession();
|
|
@@ -566,11 +585,10 @@ var ProxyServer = class {
|
|
|
566
585
|
try {
|
|
567
586
|
const sessionState = this.getOrCreateReplaySession(recordingId);
|
|
568
587
|
if (!sessionState.loadedSession) {
|
|
569
|
-
|
|
570
|
-
`Recording session file not found: ${filePath}`
|
|
588
|
+
throw Object.assign(
|
|
589
|
+
new Error(`Recording session file not found: ${filePath}`),
|
|
590
|
+
{ code: "ENOENT" }
|
|
571
591
|
);
|
|
572
|
-
error.code = "ENOENT";
|
|
573
|
-
throw error;
|
|
574
592
|
}
|
|
575
593
|
const servedForThisKey = this.getServedTracker(sessionState, key);
|
|
576
594
|
const host = req.headers.host || "unknown";
|
|
@@ -665,7 +683,7 @@ var ProxyServer = class {
|
|
|
665
683
|
res.end();
|
|
666
684
|
}
|
|
667
685
|
async handleProxyRequest(req, res) {
|
|
668
|
-
const target = this.
|
|
686
|
+
const target = this.target;
|
|
669
687
|
console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
|
|
670
688
|
if (this.mode === Modes.record) {
|
|
671
689
|
await this.recordAndProxyRequest(req, res, target);
|
|
@@ -791,7 +809,7 @@ var ProxyServer = class {
|
|
|
791
809
|
this.handleReplayWebSocket(req, socket);
|
|
792
810
|
return;
|
|
793
811
|
}
|
|
794
|
-
const target = this.
|
|
812
|
+
const target = this.target;
|
|
795
813
|
console.log(`[${this.mode}] WebSocket upgrade ${req.url} -> ${target}`);
|
|
796
814
|
if (this.mode === Modes.record) {
|
|
797
815
|
this.handleRecordWebSocket(req, socket, head, target);
|
|
@@ -864,11 +882,12 @@ var ProxyServer = class {
|
|
|
864
882
|
console.error("WebSocket server error:", err);
|
|
865
883
|
});
|
|
866
884
|
}
|
|
867
|
-
handleReplayWebSocket(req, socket) {
|
|
885
|
+
async handleReplayWebSocket(req, socket) {
|
|
868
886
|
const url = req.url || "/";
|
|
869
887
|
const key = `WS_${url.replaceAll("/", "_")}`;
|
|
870
888
|
const filePath = getRecordingPath(this.recordingsDir, this.replayId);
|
|
871
|
-
|
|
889
|
+
try {
|
|
890
|
+
const session = await loadRecordingSession(filePath);
|
|
872
891
|
const wsRecording = session.websocketRecordings.find(
|
|
873
892
|
(r) => r.key === key
|
|
874
893
|
);
|
|
@@ -934,11 +953,11 @@ var ProxyServer = class {
|
|
|
934
953
|
console.log("Replay WebSocket closed");
|
|
935
954
|
});
|
|
936
955
|
});
|
|
937
|
-
}
|
|
956
|
+
} catch (error) {
|
|
938
957
|
console.error("Replay error:", error);
|
|
939
958
|
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
940
959
|
socket.destroy();
|
|
941
|
-
}
|
|
960
|
+
}
|
|
942
961
|
}
|
|
943
962
|
logServerStartup(port) {
|
|
944
963
|
console.log(`Proxy server running on http://localhost:${port}`);
|
|
@@ -949,6 +968,7 @@ var ProxyServer = class {
|
|
|
949
968
|
);
|
|
950
969
|
}
|
|
951
970
|
};
|
|
971
|
+
var registeredContexts = /* @__PURE__ */ new WeakSet();
|
|
952
972
|
function getProxyPort() {
|
|
953
973
|
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
954
974
|
if (envPort) {
|
|
@@ -967,7 +987,7 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
967
987
|
id: sessionId,
|
|
968
988
|
...timeout && { timeout }
|
|
969
989
|
};
|
|
970
|
-
const response = await fetch(`http://
|
|
990
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
971
991
|
method: "POST",
|
|
972
992
|
headers: { "Content-Type": "application/json" },
|
|
973
993
|
body: JSON.stringify(body)
|
|
@@ -990,7 +1010,7 @@ async function cleanupSession(sessionId) {
|
|
|
990
1010
|
cleanup: true,
|
|
991
1011
|
id: sessionId
|
|
992
1012
|
};
|
|
993
|
-
const response = await fetch(`http://
|
|
1013
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
994
1014
|
method: "POST",
|
|
995
1015
|
headers: { "Content-Type": "application/json" },
|
|
996
1016
|
body: JSON.stringify(body)
|
|
@@ -1054,7 +1074,7 @@ async function getRecordingsDir() {
|
|
|
1054
1074
|
}
|
|
1055
1075
|
const proxyPort = getProxyPort();
|
|
1056
1076
|
try {
|
|
1057
|
-
const response = await fetch(`http://
|
|
1077
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`);
|
|
1058
1078
|
if (response.ok) {
|
|
1059
1079
|
const data = await response.json();
|
|
1060
1080
|
if (data.recordingsDir) {
|
|
@@ -1141,10 +1161,8 @@ var playwrightProxy = {
|
|
|
1141
1161
|
// Ensure the handler applies to all matching requests
|
|
1142
1162
|
);
|
|
1143
1163
|
const context = page.context();
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
if (!globalThis[handlerKey]) {
|
|
1147
|
-
globalThis[handlerKey] = true;
|
|
1164
|
+
if (!registeredContexts.has(context)) {
|
|
1165
|
+
registeredContexts.add(context);
|
|
1148
1166
|
context.on("close", async () => {
|
|
1149
1167
|
try {
|
|
1150
1168
|
await cleanupSession(sessionId);
|
|
@@ -1154,7 +1172,7 @@ var playwrightProxy = {
|
|
|
1154
1172
|
error
|
|
1155
1173
|
);
|
|
1156
1174
|
} finally {
|
|
1157
|
-
delete
|
|
1175
|
+
registeredContexts.delete(context);
|
|
1158
1176
|
}
|
|
1159
1177
|
});
|
|
1160
1178
|
}
|
|
@@ -17,6 +17,7 @@ var Modes = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// src/playwright/index.ts
|
|
20
|
+
var registeredContexts = /* @__PURE__ */ new WeakSet();
|
|
20
21
|
function getProxyPort() {
|
|
21
22
|
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
22
23
|
if (envPort) {
|
|
@@ -35,7 +36,7 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
35
36
|
id: sessionId,
|
|
36
37
|
...timeout && { timeout }
|
|
37
38
|
};
|
|
38
|
-
const response = await fetch(`http://
|
|
39
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
39
40
|
method: "POST",
|
|
40
41
|
headers: { "Content-Type": "application/json" },
|
|
41
42
|
body: JSON.stringify(body)
|
|
@@ -58,7 +59,7 @@ async function cleanupSession(sessionId) {
|
|
|
58
59
|
cleanup: true,
|
|
59
60
|
id: sessionId
|
|
60
61
|
};
|
|
61
|
-
const response = await fetch(`http://
|
|
62
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
62
63
|
method: "POST",
|
|
63
64
|
headers: { "Content-Type": "application/json" },
|
|
64
65
|
body: JSON.stringify(body)
|
|
@@ -122,7 +123,7 @@ async function getRecordingsDir() {
|
|
|
122
123
|
}
|
|
123
124
|
const proxyPort = getProxyPort();
|
|
124
125
|
try {
|
|
125
|
-
const response = await fetch(`http://
|
|
126
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`);
|
|
126
127
|
if (response.ok) {
|
|
127
128
|
const data = await response.json();
|
|
128
129
|
if (data.recordingsDir) {
|
|
@@ -209,10 +210,8 @@ var playwrightProxy = {
|
|
|
209
210
|
// Ensure the handler applies to all matching requests
|
|
210
211
|
);
|
|
211
212
|
const context = page.context();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!globalThis[handlerKey]) {
|
|
215
|
-
globalThis[handlerKey] = true;
|
|
213
|
+
if (!registeredContexts.has(context)) {
|
|
214
|
+
registeredContexts.add(context);
|
|
216
215
|
context.on("close", async () => {
|
|
217
216
|
try {
|
|
218
217
|
await cleanupSession(sessionId);
|
|
@@ -222,7 +221,7 @@ var playwrightProxy = {
|
|
|
222
221
|
error
|
|
223
222
|
);
|
|
224
223
|
} finally {
|
|
225
|
-
delete
|
|
224
|
+
registeredContexts.delete(context);
|
|
226
225
|
}
|
|
227
226
|
});
|
|
228
227
|
}
|
|
@@ -11,6 +11,7 @@ var Modes = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/playwright/index.ts
|
|
14
|
+
var registeredContexts = /* @__PURE__ */ new WeakSet();
|
|
14
15
|
function getProxyPort() {
|
|
15
16
|
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
16
17
|
if (envPort) {
|
|
@@ -29,7 +30,7 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
29
30
|
id: sessionId,
|
|
30
31
|
...timeout && { timeout }
|
|
31
32
|
};
|
|
32
|
-
const response = await fetch(`http://
|
|
33
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
33
34
|
method: "POST",
|
|
34
35
|
headers: { "Content-Type": "application/json" },
|
|
35
36
|
body: JSON.stringify(body)
|
|
@@ -52,7 +53,7 @@ async function cleanupSession(sessionId) {
|
|
|
52
53
|
cleanup: true,
|
|
53
54
|
id: sessionId
|
|
54
55
|
};
|
|
55
|
-
const response = await fetch(`http://
|
|
56
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`, {
|
|
56
57
|
method: "POST",
|
|
57
58
|
headers: { "Content-Type": "application/json" },
|
|
58
59
|
body: JSON.stringify(body)
|
|
@@ -116,7 +117,7 @@ async function getRecordingsDir() {
|
|
|
116
117
|
}
|
|
117
118
|
const proxyPort = getProxyPort();
|
|
118
119
|
try {
|
|
119
|
-
const response = await fetch(`http://
|
|
120
|
+
const response = await fetch(`http://localhost:${proxyPort}/__control`);
|
|
120
121
|
if (response.ok) {
|
|
121
122
|
const data = await response.json();
|
|
122
123
|
if (data.recordingsDir) {
|
|
@@ -203,10 +204,8 @@ var playwrightProxy = {
|
|
|
203
204
|
// Ensure the handler applies to all matching requests
|
|
204
205
|
);
|
|
205
206
|
const context = page.context();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (!globalThis[handlerKey]) {
|
|
209
|
-
globalThis[handlerKey] = true;
|
|
207
|
+
if (!registeredContexts.has(context)) {
|
|
208
|
+
registeredContexts.add(context);
|
|
210
209
|
context.on("close", async () => {
|
|
211
210
|
try {
|
|
212
211
|
await cleanupSession(sessionId);
|
|
@@ -216,7 +215,7 @@ var playwrightProxy = {
|
|
|
216
215
|
error
|
|
217
216
|
);
|
|
218
217
|
} finally {
|
|
219
|
-
delete
|
|
218
|
+
registeredContexts.delete(context);
|
|
220
219
|
}
|
|
221
220
|
});
|
|
222
221
|
}
|