test-proxy-recorder 0.1.6 → 0.1.8
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 +56 -61
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +56 -61
- package/dist/proxy.js +56 -61
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -144,11 +144,10 @@ var ProxyServer = class {
|
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
if (!res.headersSent) {
|
|
147
|
-
const
|
|
147
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
148
148
|
res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
|
|
149
149
|
"Content-Type": "application/json",
|
|
150
|
-
|
|
151
|
-
"Access-Control-Allow-Credentials": "true"
|
|
150
|
+
...corsHeaders
|
|
152
151
|
});
|
|
153
152
|
}
|
|
154
153
|
res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
|
|
@@ -159,24 +158,51 @@ var ProxyServer = class {
|
|
|
159
158
|
this.recordResponse(req, proxyRes);
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Get CORS headers for a given request
|
|
163
|
+
* @param req The incoming HTTP request
|
|
164
|
+
* @returns An object containing CORS headers
|
|
165
|
+
*/
|
|
166
|
+
getCorsHeaders(req) {
|
|
163
167
|
const origin = req.headers.origin;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
return {
|
|
169
|
+
"access-control-allow-origin": origin || "*",
|
|
170
|
+
"access-control-allow-credentials": "true",
|
|
171
|
+
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
172
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
173
|
+
"access-control-expose-headers": "*"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
addCorsHeaders(proxyRes, req) {
|
|
177
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
178
|
+
Object.assign(proxyRes.headers, corsHeaders);
|
|
169
179
|
}
|
|
170
180
|
getTarget() {
|
|
171
181
|
const target = this.targets[this.currentTargetIndex];
|
|
172
182
|
this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
|
|
173
183
|
return target;
|
|
174
184
|
}
|
|
185
|
+
parseGetParams(req) {
|
|
186
|
+
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
|
187
|
+
const mode = url.searchParams.get("mode");
|
|
188
|
+
const id = url.searchParams.get("id") || void 0;
|
|
189
|
+
const timeoutParam = url.searchParams.get("timeout");
|
|
190
|
+
const timeout = timeoutParam ? Number.parseInt(timeoutParam, 10) : void 0;
|
|
191
|
+
if (!mode) {
|
|
192
|
+
throw new Error("Mode parameter is required");
|
|
193
|
+
}
|
|
194
|
+
return { mode, id, timeout };
|
|
195
|
+
}
|
|
175
196
|
async handleControlRequest(req, res) {
|
|
176
197
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
let data;
|
|
199
|
+
if (req.method === "GET") {
|
|
200
|
+
data = this.parseGetParams(req);
|
|
201
|
+
} else {
|
|
202
|
+
const body = await readRequestBody(req);
|
|
203
|
+
console.log("MODE CHANGE (POST)", body);
|
|
204
|
+
data = JSON.parse(body);
|
|
205
|
+
}
|
|
180
206
|
const { mode, id, timeout: requestTimeout } = data;
|
|
181
207
|
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
182
208
|
this.clearModeTimeout();
|
|
@@ -267,11 +293,6 @@ var ProxyServer = class {
|
|
|
267
293
|
}
|
|
268
294
|
async saveCurrentSession(filterIncomplete = false) {
|
|
269
295
|
if (!this.currentSession) {
|
|
270
|
-
console.log("No current session to save");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (this.currentSession.recordings.length === 0 && this.currentSession.websocketRecordings.length === 0) {
|
|
274
|
-
console.log("Session has no recordings, skipping save");
|
|
275
296
|
return;
|
|
276
297
|
}
|
|
277
298
|
if (filterIncomplete) {
|
|
@@ -279,9 +300,6 @@ var ProxyServer = class {
|
|
|
279
300
|
(r) => !r.response
|
|
280
301
|
).length;
|
|
281
302
|
if (incompleteCount > 0) {
|
|
282
|
-
console.log(
|
|
283
|
-
`Removing ${incompleteCount} incomplete recording(s) without responses`
|
|
284
|
-
);
|
|
285
303
|
this.currentSession.recordings = this.currentSession.recordings.filter(
|
|
286
304
|
(r) => r.response
|
|
287
305
|
);
|
|
@@ -294,12 +312,9 @@ var ProxyServer = class {
|
|
|
294
312
|
}
|
|
295
313
|
saveRequestRecordSync(req, body) {
|
|
296
314
|
if (!this.currentSession) {
|
|
297
|
-
console.log("saveRequestRecordSync: No current session");
|
|
298
315
|
return;
|
|
299
316
|
}
|
|
300
317
|
const key = getReqID(req);
|
|
301
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
302
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
303
318
|
const record = {
|
|
304
319
|
request: {
|
|
305
320
|
method: req.method,
|
|
@@ -309,17 +324,17 @@ var ProxyServer = class {
|
|
|
309
324
|
},
|
|
310
325
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
311
326
|
key,
|
|
312
|
-
sequence:
|
|
327
|
+
sequence: -1
|
|
328
|
+
// Temporary, will be set when response arrives
|
|
313
329
|
};
|
|
314
330
|
this.currentSession.recordings.push(record);
|
|
315
331
|
console.log(
|
|
316
332
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
317
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key},
|
|
333
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
318
334
|
);
|
|
319
335
|
}
|
|
320
336
|
updateRequestBodySync(req, body) {
|
|
321
337
|
if (!this.currentSession) {
|
|
322
|
-
console.log("updateRequestBodySync: No current session");
|
|
323
338
|
return;
|
|
324
339
|
}
|
|
325
340
|
const key = getReqID(req);
|
|
@@ -360,13 +375,11 @@ var ProxyServer = class {
|
|
|
360
375
|
headers: proxyRes.headers,
|
|
361
376
|
body: body || null
|
|
362
377
|
};
|
|
363
|
-
await this.saveCurrentSession();
|
|
364
378
|
console.log(`Recorded: ${req.method} ${req.url}`);
|
|
365
379
|
});
|
|
366
380
|
}
|
|
367
381
|
async recordResponseData(req, proxyRes, body) {
|
|
368
382
|
if (!this.currentSession) {
|
|
369
|
-
console.log("recordResponseData: No current session");
|
|
370
383
|
return false;
|
|
371
384
|
}
|
|
372
385
|
const key = getReqID(req);
|
|
@@ -398,9 +411,12 @@ var ProxyServer = class {
|
|
|
398
411
|
headers: proxyRes.headers,
|
|
399
412
|
body: body || null
|
|
400
413
|
};
|
|
401
|
-
|
|
414
|
+
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
415
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
416
|
+
record.sequence = currentSequence;
|
|
417
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
402
418
|
console.log(
|
|
403
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url}`
|
|
419
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
404
420
|
);
|
|
405
421
|
return true;
|
|
406
422
|
}
|
|
@@ -410,9 +426,7 @@ var ProxyServer = class {
|
|
|
410
426
|
try {
|
|
411
427
|
const session = await loadRecordingSession(filePath);
|
|
412
428
|
const host = req.headers.host || "unknown";
|
|
413
|
-
const recordsWithKey = session.recordings.filter(
|
|
414
|
-
(r) => r.key === key && r.response
|
|
415
|
-
);
|
|
429
|
+
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
416
430
|
if (recordsWithKey.length === 0) {
|
|
417
431
|
throw new Error(
|
|
418
432
|
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
@@ -422,7 +436,7 @@ var ProxyServer = class {
|
|
|
422
436
|
const recordIndex = usageCount % recordsWithKey.length;
|
|
423
437
|
const record = recordsWithKey[recordIndex];
|
|
424
438
|
console.log(
|
|
425
|
-
`Replaying ${req.method} ${req.url} (usage: ${usageCount},
|
|
439
|
+
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
426
440
|
);
|
|
427
441
|
this.replaySequenceMap.set(key, usageCount + 1);
|
|
428
442
|
if (!record.response) {
|
|
@@ -431,14 +445,9 @@ var ProxyServer = class {
|
|
|
431
445
|
);
|
|
432
446
|
}
|
|
433
447
|
const { statusCode, headers, body } = record.response;
|
|
434
|
-
const origin = req.headers.origin;
|
|
435
448
|
const responseHeaders = {
|
|
436
449
|
...headers,
|
|
437
|
-
|
|
438
|
-
"access-control-allow-credentials": "true",
|
|
439
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
440
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
441
|
-
"access-control-expose-headers": "*"
|
|
450
|
+
...this.getCorsHeaders(req)
|
|
442
451
|
};
|
|
443
452
|
res.writeHead(statusCode, responseHeaders);
|
|
444
453
|
res.end(body);
|
|
@@ -449,11 +458,10 @@ var ProxyServer = class {
|
|
|
449
458
|
handleReplayError(req, res, err, key, filePath) {
|
|
450
459
|
const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
451
460
|
console.error("Replay error:", err);
|
|
452
|
-
const
|
|
461
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
453
462
|
res.writeHead(HTTP_STATUS_NOT_FOUND, {
|
|
454
463
|
"Content-Type": "application/json",
|
|
455
|
-
|
|
456
|
-
"Access-Control-Allow-Credentials": "true"
|
|
464
|
+
...corsHeaders
|
|
457
465
|
});
|
|
458
466
|
res.end(
|
|
459
467
|
JSON.stringify({
|
|
@@ -468,7 +476,8 @@ var ProxyServer = class {
|
|
|
468
476
|
if (req.method === "OPTIONS") {
|
|
469
477
|
return this.handleCorsPreflightRequest(req, res);
|
|
470
478
|
}
|
|
471
|
-
|
|
479
|
+
const urlPath = req.url?.split("?")[0] || "";
|
|
480
|
+
if (urlPath === CONTROL_ENDPOINT) {
|
|
472
481
|
return this.handleControlRequest(req, res);
|
|
473
482
|
}
|
|
474
483
|
if (this.mode === Modes.replay) {
|
|
@@ -477,12 +486,9 @@ var ProxyServer = class {
|
|
|
477
486
|
await this.handleProxyRequest(req, res);
|
|
478
487
|
}
|
|
479
488
|
handleCorsPreflightRequest(req, res) {
|
|
480
|
-
const
|
|
489
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
481
490
|
res.writeHead(HTTP_STATUS_OK, {
|
|
482
|
-
|
|
483
|
-
"Access-Control-Allow-Credentials": "true",
|
|
484
|
-
"Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
485
|
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
491
|
+
...corsHeaders,
|
|
486
492
|
"Access-Control-Max-Age": "86400"
|
|
487
493
|
// 24 hours
|
|
488
494
|
});
|
|
@@ -542,14 +548,9 @@ var ProxyServer = class {
|
|
|
542
548
|
proxyRes,
|
|
543
549
|
responseBody.toString("utf8")
|
|
544
550
|
);
|
|
545
|
-
const origin = req.headers.origin;
|
|
546
551
|
const responseHeaders = {
|
|
547
552
|
...proxyRes.headers,
|
|
548
|
-
|
|
549
|
-
"access-control-allow-credentials": "true",
|
|
550
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
551
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
552
|
-
"access-control-expose-headers": "*"
|
|
553
|
+
...this.getCorsHeaders(req)
|
|
553
554
|
};
|
|
554
555
|
res.writeHead(proxyRes.statusCode || 200, responseHeaders);
|
|
555
556
|
res.end(responseBody);
|
|
@@ -614,9 +615,6 @@ var ProxyServer = class {
|
|
|
614
615
|
if (backendWs.readyState === ws.WebSocket.OPEN) {
|
|
615
616
|
backendWs.send(message);
|
|
616
617
|
}
|
|
617
|
-
this.saveCurrentSession().catch((error) => {
|
|
618
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
619
|
-
});
|
|
620
618
|
});
|
|
621
619
|
backendWs.on("message", (data) => {
|
|
622
620
|
const message = data.toString();
|
|
@@ -628,9 +626,6 @@ var ProxyServer = class {
|
|
|
628
626
|
if (clientWs.readyState === ws.WebSocket.OPEN) {
|
|
629
627
|
clientWs.send(message);
|
|
630
628
|
}
|
|
631
|
-
this.saveCurrentSession().catch((error) => {
|
|
632
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
633
|
-
});
|
|
634
629
|
});
|
|
635
630
|
clientWs.on("error", (err) => {
|
|
636
631
|
console.error("Client WebSocket error:", err);
|
package/dist/index.d.cts
CHANGED
|
@@ -20,8 +20,15 @@ declare class ProxyServer {
|
|
|
20
20
|
private setupProxyEventHandlers;
|
|
21
21
|
private handleProxyError;
|
|
22
22
|
private handleProxyResponse;
|
|
23
|
+
/**
|
|
24
|
+
* Get CORS headers for a given request
|
|
25
|
+
* @param req The incoming HTTP request
|
|
26
|
+
* @returns An object containing CORS headers
|
|
27
|
+
*/
|
|
28
|
+
private getCorsHeaders;
|
|
23
29
|
private addCorsHeaders;
|
|
24
30
|
private getTarget;
|
|
31
|
+
private parseGetParams;
|
|
25
32
|
private handleControlRequest;
|
|
26
33
|
private clearModeTimeout;
|
|
27
34
|
private switchMode;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,8 +20,15 @@ declare class ProxyServer {
|
|
|
20
20
|
private setupProxyEventHandlers;
|
|
21
21
|
private handleProxyError;
|
|
22
22
|
private handleProxyResponse;
|
|
23
|
+
/**
|
|
24
|
+
* Get CORS headers for a given request
|
|
25
|
+
* @param req The incoming HTTP request
|
|
26
|
+
* @returns An object containing CORS headers
|
|
27
|
+
*/
|
|
28
|
+
private getCorsHeaders;
|
|
23
29
|
private addCorsHeaders;
|
|
24
30
|
private getTarget;
|
|
31
|
+
private parseGetParams;
|
|
25
32
|
private handleControlRequest;
|
|
26
33
|
private clearModeTimeout;
|
|
27
34
|
private switchMode;
|
package/dist/index.mjs
CHANGED
|
@@ -133,11 +133,10 @@ var ProxyServer = class {
|
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
if (!res.headersSent) {
|
|
136
|
-
const
|
|
136
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
137
137
|
res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
|
|
138
138
|
"Content-Type": "application/json",
|
|
139
|
-
|
|
140
|
-
"Access-Control-Allow-Credentials": "true"
|
|
139
|
+
...corsHeaders
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
142
|
res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
|
|
@@ -148,24 +147,51 @@ var ProxyServer = class {
|
|
|
148
147
|
this.recordResponse(req, proxyRes);
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Get CORS headers for a given request
|
|
152
|
+
* @param req The incoming HTTP request
|
|
153
|
+
* @returns An object containing CORS headers
|
|
154
|
+
*/
|
|
155
|
+
getCorsHeaders(req) {
|
|
152
156
|
const origin = req.headers.origin;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
return {
|
|
158
|
+
"access-control-allow-origin": origin || "*",
|
|
159
|
+
"access-control-allow-credentials": "true",
|
|
160
|
+
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
161
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
162
|
+
"access-control-expose-headers": "*"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
addCorsHeaders(proxyRes, req) {
|
|
166
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
167
|
+
Object.assign(proxyRes.headers, corsHeaders);
|
|
158
168
|
}
|
|
159
169
|
getTarget() {
|
|
160
170
|
const target = this.targets[this.currentTargetIndex];
|
|
161
171
|
this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
|
|
162
172
|
return target;
|
|
163
173
|
}
|
|
174
|
+
parseGetParams(req) {
|
|
175
|
+
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
|
176
|
+
const mode = url.searchParams.get("mode");
|
|
177
|
+
const id = url.searchParams.get("id") || void 0;
|
|
178
|
+
const timeoutParam = url.searchParams.get("timeout");
|
|
179
|
+
const timeout = timeoutParam ? Number.parseInt(timeoutParam, 10) : void 0;
|
|
180
|
+
if (!mode) {
|
|
181
|
+
throw new Error("Mode parameter is required");
|
|
182
|
+
}
|
|
183
|
+
return { mode, id, timeout };
|
|
184
|
+
}
|
|
164
185
|
async handleControlRequest(req, res) {
|
|
165
186
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
let data;
|
|
188
|
+
if (req.method === "GET") {
|
|
189
|
+
data = this.parseGetParams(req);
|
|
190
|
+
} else {
|
|
191
|
+
const body = await readRequestBody(req);
|
|
192
|
+
console.log("MODE CHANGE (POST)", body);
|
|
193
|
+
data = JSON.parse(body);
|
|
194
|
+
}
|
|
169
195
|
const { mode, id, timeout: requestTimeout } = data;
|
|
170
196
|
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
171
197
|
this.clearModeTimeout();
|
|
@@ -256,11 +282,6 @@ var ProxyServer = class {
|
|
|
256
282
|
}
|
|
257
283
|
async saveCurrentSession(filterIncomplete = false) {
|
|
258
284
|
if (!this.currentSession) {
|
|
259
|
-
console.log("No current session to save");
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
if (this.currentSession.recordings.length === 0 && this.currentSession.websocketRecordings.length === 0) {
|
|
263
|
-
console.log("Session has no recordings, skipping save");
|
|
264
285
|
return;
|
|
265
286
|
}
|
|
266
287
|
if (filterIncomplete) {
|
|
@@ -268,9 +289,6 @@ var ProxyServer = class {
|
|
|
268
289
|
(r) => !r.response
|
|
269
290
|
).length;
|
|
270
291
|
if (incompleteCount > 0) {
|
|
271
|
-
console.log(
|
|
272
|
-
`Removing ${incompleteCount} incomplete recording(s) without responses`
|
|
273
|
-
);
|
|
274
292
|
this.currentSession.recordings = this.currentSession.recordings.filter(
|
|
275
293
|
(r) => r.response
|
|
276
294
|
);
|
|
@@ -283,12 +301,9 @@ var ProxyServer = class {
|
|
|
283
301
|
}
|
|
284
302
|
saveRequestRecordSync(req, body) {
|
|
285
303
|
if (!this.currentSession) {
|
|
286
|
-
console.log("saveRequestRecordSync: No current session");
|
|
287
304
|
return;
|
|
288
305
|
}
|
|
289
306
|
const key = getReqID(req);
|
|
290
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
291
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
292
307
|
const record = {
|
|
293
308
|
request: {
|
|
294
309
|
method: req.method,
|
|
@@ -298,17 +313,17 @@ var ProxyServer = class {
|
|
|
298
313
|
},
|
|
299
314
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300
315
|
key,
|
|
301
|
-
sequence:
|
|
316
|
+
sequence: -1
|
|
317
|
+
// Temporary, will be set when response arrives
|
|
302
318
|
};
|
|
303
319
|
this.currentSession.recordings.push(record);
|
|
304
320
|
console.log(
|
|
305
321
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
306
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key},
|
|
322
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
307
323
|
);
|
|
308
324
|
}
|
|
309
325
|
updateRequestBodySync(req, body) {
|
|
310
326
|
if (!this.currentSession) {
|
|
311
|
-
console.log("updateRequestBodySync: No current session");
|
|
312
327
|
return;
|
|
313
328
|
}
|
|
314
329
|
const key = getReqID(req);
|
|
@@ -349,13 +364,11 @@ var ProxyServer = class {
|
|
|
349
364
|
headers: proxyRes.headers,
|
|
350
365
|
body: body || null
|
|
351
366
|
};
|
|
352
|
-
await this.saveCurrentSession();
|
|
353
367
|
console.log(`Recorded: ${req.method} ${req.url}`);
|
|
354
368
|
});
|
|
355
369
|
}
|
|
356
370
|
async recordResponseData(req, proxyRes, body) {
|
|
357
371
|
if (!this.currentSession) {
|
|
358
|
-
console.log("recordResponseData: No current session");
|
|
359
372
|
return false;
|
|
360
373
|
}
|
|
361
374
|
const key = getReqID(req);
|
|
@@ -387,9 +400,12 @@ var ProxyServer = class {
|
|
|
387
400
|
headers: proxyRes.headers,
|
|
388
401
|
body: body || null
|
|
389
402
|
};
|
|
390
|
-
|
|
403
|
+
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
404
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
405
|
+
record.sequence = currentSequence;
|
|
406
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
391
407
|
console.log(
|
|
392
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url}`
|
|
408
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
393
409
|
);
|
|
394
410
|
return true;
|
|
395
411
|
}
|
|
@@ -399,9 +415,7 @@ var ProxyServer = class {
|
|
|
399
415
|
try {
|
|
400
416
|
const session = await loadRecordingSession(filePath);
|
|
401
417
|
const host = req.headers.host || "unknown";
|
|
402
|
-
const recordsWithKey = session.recordings.filter(
|
|
403
|
-
(r) => r.key === key && r.response
|
|
404
|
-
);
|
|
418
|
+
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
405
419
|
if (recordsWithKey.length === 0) {
|
|
406
420
|
throw new Error(
|
|
407
421
|
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
@@ -411,7 +425,7 @@ var ProxyServer = class {
|
|
|
411
425
|
const recordIndex = usageCount % recordsWithKey.length;
|
|
412
426
|
const record = recordsWithKey[recordIndex];
|
|
413
427
|
console.log(
|
|
414
|
-
`Replaying ${req.method} ${req.url} (usage: ${usageCount},
|
|
428
|
+
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
415
429
|
);
|
|
416
430
|
this.replaySequenceMap.set(key, usageCount + 1);
|
|
417
431
|
if (!record.response) {
|
|
@@ -420,14 +434,9 @@ var ProxyServer = class {
|
|
|
420
434
|
);
|
|
421
435
|
}
|
|
422
436
|
const { statusCode, headers, body } = record.response;
|
|
423
|
-
const origin = req.headers.origin;
|
|
424
437
|
const responseHeaders = {
|
|
425
438
|
...headers,
|
|
426
|
-
|
|
427
|
-
"access-control-allow-credentials": "true",
|
|
428
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
429
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
430
|
-
"access-control-expose-headers": "*"
|
|
439
|
+
...this.getCorsHeaders(req)
|
|
431
440
|
};
|
|
432
441
|
res.writeHead(statusCode, responseHeaders);
|
|
433
442
|
res.end(body);
|
|
@@ -438,11 +447,10 @@ var ProxyServer = class {
|
|
|
438
447
|
handleReplayError(req, res, err, key, filePath) {
|
|
439
448
|
const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
440
449
|
console.error("Replay error:", err);
|
|
441
|
-
const
|
|
450
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
442
451
|
res.writeHead(HTTP_STATUS_NOT_FOUND, {
|
|
443
452
|
"Content-Type": "application/json",
|
|
444
|
-
|
|
445
|
-
"Access-Control-Allow-Credentials": "true"
|
|
453
|
+
...corsHeaders
|
|
446
454
|
});
|
|
447
455
|
res.end(
|
|
448
456
|
JSON.stringify({
|
|
@@ -457,7 +465,8 @@ var ProxyServer = class {
|
|
|
457
465
|
if (req.method === "OPTIONS") {
|
|
458
466
|
return this.handleCorsPreflightRequest(req, res);
|
|
459
467
|
}
|
|
460
|
-
|
|
468
|
+
const urlPath = req.url?.split("?")[0] || "";
|
|
469
|
+
if (urlPath === CONTROL_ENDPOINT) {
|
|
461
470
|
return this.handleControlRequest(req, res);
|
|
462
471
|
}
|
|
463
472
|
if (this.mode === Modes.replay) {
|
|
@@ -466,12 +475,9 @@ var ProxyServer = class {
|
|
|
466
475
|
await this.handleProxyRequest(req, res);
|
|
467
476
|
}
|
|
468
477
|
handleCorsPreflightRequest(req, res) {
|
|
469
|
-
const
|
|
478
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
470
479
|
res.writeHead(HTTP_STATUS_OK, {
|
|
471
|
-
|
|
472
|
-
"Access-Control-Allow-Credentials": "true",
|
|
473
|
-
"Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
474
|
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
480
|
+
...corsHeaders,
|
|
475
481
|
"Access-Control-Max-Age": "86400"
|
|
476
482
|
// 24 hours
|
|
477
483
|
});
|
|
@@ -531,14 +537,9 @@ var ProxyServer = class {
|
|
|
531
537
|
proxyRes,
|
|
532
538
|
responseBody.toString("utf8")
|
|
533
539
|
);
|
|
534
|
-
const origin = req.headers.origin;
|
|
535
540
|
const responseHeaders = {
|
|
536
541
|
...proxyRes.headers,
|
|
537
|
-
|
|
538
|
-
"access-control-allow-credentials": "true",
|
|
539
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
540
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
541
|
-
"access-control-expose-headers": "*"
|
|
542
|
+
...this.getCorsHeaders(req)
|
|
542
543
|
};
|
|
543
544
|
res.writeHead(proxyRes.statusCode || 200, responseHeaders);
|
|
544
545
|
res.end(responseBody);
|
|
@@ -603,9 +604,6 @@ var ProxyServer = class {
|
|
|
603
604
|
if (backendWs.readyState === WebSocket.OPEN) {
|
|
604
605
|
backendWs.send(message);
|
|
605
606
|
}
|
|
606
|
-
this.saveCurrentSession().catch((error) => {
|
|
607
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
608
|
-
});
|
|
609
607
|
});
|
|
610
608
|
backendWs.on("message", (data) => {
|
|
611
609
|
const message = data.toString();
|
|
@@ -617,9 +615,6 @@ var ProxyServer = class {
|
|
|
617
615
|
if (clientWs.readyState === WebSocket.OPEN) {
|
|
618
616
|
clientWs.send(message);
|
|
619
617
|
}
|
|
620
|
-
this.saveCurrentSession().catch((error) => {
|
|
621
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
622
|
-
});
|
|
623
618
|
});
|
|
624
619
|
clientWs.on("error", (err) => {
|
|
625
620
|
console.error("Client WebSocket error:", err);
|
package/dist/proxy.js
CHANGED
|
@@ -167,11 +167,10 @@ var ProxyServer = class {
|
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
169
|
if (!res.headersSent) {
|
|
170
|
-
const
|
|
170
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
171
171
|
res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
|
|
172
172
|
"Content-Type": "application/json",
|
|
173
|
-
|
|
174
|
-
"Access-Control-Allow-Credentials": "true"
|
|
173
|
+
...corsHeaders
|
|
175
174
|
});
|
|
176
175
|
}
|
|
177
176
|
res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
|
|
@@ -182,24 +181,51 @@ var ProxyServer = class {
|
|
|
182
181
|
this.recordResponse(req, proxyRes);
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Get CORS headers for a given request
|
|
186
|
+
* @param req The incoming HTTP request
|
|
187
|
+
* @returns An object containing CORS headers
|
|
188
|
+
*/
|
|
189
|
+
getCorsHeaders(req) {
|
|
186
190
|
const origin = req.headers.origin;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
return {
|
|
192
|
+
"access-control-allow-origin": origin || "*",
|
|
193
|
+
"access-control-allow-credentials": "true",
|
|
194
|
+
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
195
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
196
|
+
"access-control-expose-headers": "*"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
addCorsHeaders(proxyRes, req) {
|
|
200
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
201
|
+
Object.assign(proxyRes.headers, corsHeaders);
|
|
192
202
|
}
|
|
193
203
|
getTarget() {
|
|
194
204
|
const target = this.targets[this.currentTargetIndex];
|
|
195
205
|
this.currentTargetIndex = (this.currentTargetIndex + 1) % this.targets.length;
|
|
196
206
|
return target;
|
|
197
207
|
}
|
|
208
|
+
parseGetParams(req) {
|
|
209
|
+
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
|
210
|
+
const mode = url.searchParams.get("mode");
|
|
211
|
+
const id = url.searchParams.get("id") || void 0;
|
|
212
|
+
const timeoutParam = url.searchParams.get("timeout");
|
|
213
|
+
const timeout = timeoutParam ? Number.parseInt(timeoutParam, 10) : void 0;
|
|
214
|
+
if (!mode) {
|
|
215
|
+
throw new Error("Mode parameter is required");
|
|
216
|
+
}
|
|
217
|
+
return { mode, id, timeout };
|
|
218
|
+
}
|
|
198
219
|
async handleControlRequest(req, res) {
|
|
199
220
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
221
|
+
let data;
|
|
222
|
+
if (req.method === "GET") {
|
|
223
|
+
data = this.parseGetParams(req);
|
|
224
|
+
} else {
|
|
225
|
+
const body = await readRequestBody(req);
|
|
226
|
+
console.log("MODE CHANGE (POST)", body);
|
|
227
|
+
data = JSON.parse(body);
|
|
228
|
+
}
|
|
203
229
|
const { mode, id, timeout: requestTimeout } = data;
|
|
204
230
|
const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
205
231
|
this.clearModeTimeout();
|
|
@@ -290,11 +316,6 @@ var ProxyServer = class {
|
|
|
290
316
|
}
|
|
291
317
|
async saveCurrentSession(filterIncomplete = false) {
|
|
292
318
|
if (!this.currentSession) {
|
|
293
|
-
console.log("No current session to save");
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (this.currentSession.recordings.length === 0 && this.currentSession.websocketRecordings.length === 0) {
|
|
297
|
-
console.log("Session has no recordings, skipping save");
|
|
298
319
|
return;
|
|
299
320
|
}
|
|
300
321
|
if (filterIncomplete) {
|
|
@@ -302,9 +323,6 @@ var ProxyServer = class {
|
|
|
302
323
|
(r) => !r.response
|
|
303
324
|
).length;
|
|
304
325
|
if (incompleteCount > 0) {
|
|
305
|
-
console.log(
|
|
306
|
-
`Removing ${incompleteCount} incomplete recording(s) without responses`
|
|
307
|
-
);
|
|
308
326
|
this.currentSession.recordings = this.currentSession.recordings.filter(
|
|
309
327
|
(r) => r.response
|
|
310
328
|
);
|
|
@@ -317,12 +335,9 @@ var ProxyServer = class {
|
|
|
317
335
|
}
|
|
318
336
|
saveRequestRecordSync(req, body) {
|
|
319
337
|
if (!this.currentSession) {
|
|
320
|
-
console.log("saveRequestRecordSync: No current session");
|
|
321
338
|
return;
|
|
322
339
|
}
|
|
323
340
|
const key = getReqID(req);
|
|
324
|
-
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
325
|
-
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
326
341
|
const record = {
|
|
327
342
|
request: {
|
|
328
343
|
method: req.method,
|
|
@@ -332,17 +347,17 @@ var ProxyServer = class {
|
|
|
332
347
|
},
|
|
333
348
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
349
|
key,
|
|
335
|
-
sequence:
|
|
350
|
+
sequence: -1
|
|
351
|
+
// Temporary, will be set when response arrives
|
|
336
352
|
};
|
|
337
353
|
this.currentSession.recordings.push(record);
|
|
338
354
|
console.log(
|
|
339
355
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
340
|
-
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key},
|
|
356
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
341
357
|
);
|
|
342
358
|
}
|
|
343
359
|
updateRequestBodySync(req, body) {
|
|
344
360
|
if (!this.currentSession) {
|
|
345
|
-
console.log("updateRequestBodySync: No current session");
|
|
346
361
|
return;
|
|
347
362
|
}
|
|
348
363
|
const key = getReqID(req);
|
|
@@ -383,13 +398,11 @@ var ProxyServer = class {
|
|
|
383
398
|
headers: proxyRes.headers,
|
|
384
399
|
body: body || null
|
|
385
400
|
};
|
|
386
|
-
await this.saveCurrentSession();
|
|
387
401
|
console.log(`Recorded: ${req.method} ${req.url}`);
|
|
388
402
|
});
|
|
389
403
|
}
|
|
390
404
|
async recordResponseData(req, proxyRes, body) {
|
|
391
405
|
if (!this.currentSession) {
|
|
392
|
-
console.log("recordResponseData: No current session");
|
|
393
406
|
return false;
|
|
394
407
|
}
|
|
395
408
|
const key = getReqID(req);
|
|
@@ -421,9 +434,12 @@ var ProxyServer = class {
|
|
|
421
434
|
headers: proxyRes.headers,
|
|
422
435
|
body: body || null
|
|
423
436
|
};
|
|
424
|
-
|
|
437
|
+
record.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
438
|
+
const currentSequence = this.requestSequenceMap.get(key) || 0;
|
|
439
|
+
record.sequence = currentSequence;
|
|
440
|
+
this.requestSequenceMap.set(key, currentSequence + 1);
|
|
425
441
|
console.log(
|
|
426
|
-
`recordResponseData: Recorded response for ${req.method} ${req.url}`
|
|
442
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url} (seq: ${record.sequence})`
|
|
427
443
|
);
|
|
428
444
|
return true;
|
|
429
445
|
}
|
|
@@ -433,9 +449,7 @@ var ProxyServer = class {
|
|
|
433
449
|
try {
|
|
434
450
|
const session = await loadRecordingSession(filePath);
|
|
435
451
|
const host = req.headers.host || "unknown";
|
|
436
|
-
const recordsWithKey = session.recordings.filter(
|
|
437
|
-
(r) => r.key === key && r.response
|
|
438
|
-
);
|
|
452
|
+
const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => a.sequence - b.sequence);
|
|
439
453
|
if (recordsWithKey.length === 0) {
|
|
440
454
|
throw new Error(
|
|
441
455
|
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
@@ -445,7 +459,7 @@ var ProxyServer = class {
|
|
|
445
459
|
const recordIndex = usageCount % recordsWithKey.length;
|
|
446
460
|
const record = recordsWithKey[recordIndex];
|
|
447
461
|
console.log(
|
|
448
|
-
`Replaying ${req.method} ${req.url} (usage: ${usageCount},
|
|
462
|
+
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, sequence: ${record.sequence}, body_len: ${record.response?.body?.length || 0})`
|
|
449
463
|
);
|
|
450
464
|
this.replaySequenceMap.set(key, usageCount + 1);
|
|
451
465
|
if (!record.response) {
|
|
@@ -454,14 +468,9 @@ var ProxyServer = class {
|
|
|
454
468
|
);
|
|
455
469
|
}
|
|
456
470
|
const { statusCode, headers, body } = record.response;
|
|
457
|
-
const origin = req.headers.origin;
|
|
458
471
|
const responseHeaders = {
|
|
459
472
|
...headers,
|
|
460
|
-
|
|
461
|
-
"access-control-allow-credentials": "true",
|
|
462
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
463
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
464
|
-
"access-control-expose-headers": "*"
|
|
473
|
+
...this.getCorsHeaders(req)
|
|
465
474
|
};
|
|
466
475
|
res.writeHead(statusCode, responseHeaders);
|
|
467
476
|
res.end(body);
|
|
@@ -472,11 +481,10 @@ var ProxyServer = class {
|
|
|
472
481
|
handleReplayError(req, res, err, key, filePath) {
|
|
473
482
|
const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
474
483
|
console.error("Replay error:", err);
|
|
475
|
-
const
|
|
484
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
476
485
|
res.writeHead(HTTP_STATUS_NOT_FOUND, {
|
|
477
486
|
"Content-Type": "application/json",
|
|
478
|
-
|
|
479
|
-
"Access-Control-Allow-Credentials": "true"
|
|
487
|
+
...corsHeaders
|
|
480
488
|
});
|
|
481
489
|
res.end(
|
|
482
490
|
JSON.stringify({
|
|
@@ -491,7 +499,8 @@ var ProxyServer = class {
|
|
|
491
499
|
if (req.method === "OPTIONS") {
|
|
492
500
|
return this.handleCorsPreflightRequest(req, res);
|
|
493
501
|
}
|
|
494
|
-
|
|
502
|
+
const urlPath = req.url?.split("?")[0] || "";
|
|
503
|
+
if (urlPath === CONTROL_ENDPOINT) {
|
|
495
504
|
return this.handleControlRequest(req, res);
|
|
496
505
|
}
|
|
497
506
|
if (this.mode === Modes.replay) {
|
|
@@ -500,12 +509,9 @@ var ProxyServer = class {
|
|
|
500
509
|
await this.handleProxyRequest(req, res);
|
|
501
510
|
}
|
|
502
511
|
handleCorsPreflightRequest(req, res) {
|
|
503
|
-
const
|
|
512
|
+
const corsHeaders = this.getCorsHeaders(req);
|
|
504
513
|
res.writeHead(HTTP_STATUS_OK, {
|
|
505
|
-
|
|
506
|
-
"Access-Control-Allow-Credentials": "true",
|
|
507
|
-
"Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
508
|
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
514
|
+
...corsHeaders,
|
|
509
515
|
"Access-Control-Max-Age": "86400"
|
|
510
516
|
// 24 hours
|
|
511
517
|
});
|
|
@@ -565,14 +571,9 @@ var ProxyServer = class {
|
|
|
565
571
|
proxyRes,
|
|
566
572
|
responseBody.toString("utf8")
|
|
567
573
|
);
|
|
568
|
-
const origin = req.headers.origin;
|
|
569
574
|
const responseHeaders = {
|
|
570
575
|
...proxyRes.headers,
|
|
571
|
-
|
|
572
|
-
"access-control-allow-credentials": "true",
|
|
573
|
-
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
574
|
-
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
575
|
-
"access-control-expose-headers": "*"
|
|
576
|
+
...this.getCorsHeaders(req)
|
|
576
577
|
};
|
|
577
578
|
res.writeHead(proxyRes.statusCode || 200, responseHeaders);
|
|
578
579
|
res.end(responseBody);
|
|
@@ -637,9 +638,6 @@ var ProxyServer = class {
|
|
|
637
638
|
if (backendWs.readyState === WebSocket.OPEN) {
|
|
638
639
|
backendWs.send(message);
|
|
639
640
|
}
|
|
640
|
-
this.saveCurrentSession().catch((error) => {
|
|
641
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
642
|
-
});
|
|
643
641
|
});
|
|
644
642
|
backendWs.on("message", (data) => {
|
|
645
643
|
const message = data.toString();
|
|
@@ -651,9 +649,6 @@ var ProxyServer = class {
|
|
|
651
649
|
if (clientWs.readyState === WebSocket.OPEN) {
|
|
652
650
|
clientWs.send(message);
|
|
653
651
|
}
|
|
654
|
-
this.saveCurrentSession().catch((error) => {
|
|
655
|
-
console.error("Failed to save WebSocket recording:", error);
|
|
656
|
-
});
|
|
657
652
|
});
|
|
658
653
|
clientWs.on("error", (err) => {
|
|
659
654
|
console.error("Client WebSocket error:", err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "test-proxy-recorder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright testing framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|