test-proxy-recorder 0.3.9 → 0.4.0

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.mjs CHANGED
@@ -7,6 +7,11 @@ import path2 from 'path';
7
7
  import filenamify2 from 'filenamify';
8
8
  import { WebSocket, WebSocketServer } from 'ws';
9
9
 
10
+ // src/config.ts
11
+ function defineConfig(config) {
12
+ return config;
13
+ }
14
+
10
15
  // src/constants.ts
11
16
  var DEFAULT_TIMEOUT_MS = 120 * 1e3;
12
17
  var HTTP_STATUS_BAD_GATEWAY = 502;
@@ -260,6 +265,139 @@ var Modes = {
260
265
  record: "record",
261
266
  replay: "replay"
262
267
  };
268
+
269
+ // src/utils/redact.ts
270
+ var DEFAULT_REDACTED_HEADERS = [
271
+ "authorization",
272
+ "cookie",
273
+ "set-cookie"
274
+ ];
275
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
276
+ var COOKIE_HEADERS = /* @__PURE__ */ new Set(["cookie", "set-cookie"]);
277
+ function resolveRedaction(config) {
278
+ const extra = (config?.headers ?? []).map((name) => name.toLowerCase());
279
+ const headerSet = /* @__PURE__ */ new Set([...DEFAULT_REDACTED_HEADERS, ...extra]);
280
+ for (const name of config?.allowHeaders ?? []) {
281
+ headerSet.delete(name.toLowerCase());
282
+ }
283
+ return {
284
+ headerSet,
285
+ allowCookies: new Set(
286
+ (config?.allowCookies ?? []).map((name) => name.toLowerCase())
287
+ ),
288
+ regexes: toGlobalRegexes(config?.bodyPatterns),
289
+ placeholder: config?.placeholder ?? REDACTED_PLACEHOLDER
290
+ };
291
+ }
292
+ function toGlobalRegexes(patterns) {
293
+ if (!patterns || patterns.length === 0) {
294
+ return [];
295
+ }
296
+ return patterns.map((pattern) => {
297
+ if (typeof pattern === "string") {
298
+ return new RegExp(pattern, "g");
299
+ }
300
+ return pattern.flags.includes("g") ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);
301
+ });
302
+ }
303
+ function redactCookieHeader(value, allowCookies, placeholder) {
304
+ return value.split(";").map((part) => part.trim()).filter(Boolean).map((pair) => {
305
+ const eq = pair.indexOf("=");
306
+ if (eq === -1) {
307
+ return pair;
308
+ }
309
+ const name = pair.slice(0, eq);
310
+ return allowCookies.has(name.toLowerCase()) ? pair : `${name}=${placeholder}`;
311
+ }).join("; ");
312
+ }
313
+ function redactSetCookieValue(value, allowCookies, placeholder) {
314
+ const semicolon = value.indexOf(";");
315
+ const firstPair = semicolon === -1 ? value : value.slice(0, semicolon);
316
+ const attributes = semicolon === -1 ? "" : value.slice(semicolon);
317
+ const eq = firstPair.indexOf("=");
318
+ if (eq === -1) {
319
+ return value;
320
+ }
321
+ const name = firstPair.slice(0, eq).trim();
322
+ if (allowCookies.has(name.toLowerCase())) {
323
+ return value;
324
+ }
325
+ return `${name}=${placeholder}${attributes}`;
326
+ }
327
+ function redactCookieAware(lower, value, resolved) {
328
+ const { allowCookies, placeholder } = resolved;
329
+ const redactOne = (cookie) => lower === "cookie" ? redactCookieHeader(cookie, allowCookies, placeholder) : redactSetCookieValue(cookie, allowCookies, placeholder);
330
+ return Array.isArray(value) ? value.map((v) => redactOne(v)) : redactOne(String(value));
331
+ }
332
+ function redactHeaderValue(name, value, resolved) {
333
+ const lower = name.toLowerCase();
334
+ if (resolved.allowCookies.size > 0 && COOKIE_HEADERS.has(lower)) {
335
+ return redactCookieAware(lower, value, resolved);
336
+ }
337
+ return Array.isArray(value) ? value.map(() => resolved.placeholder) : resolved.placeholder;
338
+ }
339
+ function redactHeaders(headers, resolved) {
340
+ const result = {};
341
+ for (const [name, value] of Object.entries(headers)) {
342
+ result[name] = resolved.headerSet.has(name.toLowerCase()) ? redactHeaderValue(name, value, resolved) : value;
343
+ }
344
+ return result;
345
+ }
346
+ function redactBody(body, regexes, placeholder) {
347
+ if (!body || regexes.length === 0) {
348
+ return body ?? null;
349
+ }
350
+ let result = body;
351
+ for (const regex of regexes) {
352
+ regex.lastIndex = 0;
353
+ result = result.replace(regex, placeholder);
354
+ }
355
+ return result;
356
+ }
357
+ function redactRecording(recording, resolved) {
358
+ const { regexes, placeholder } = resolved;
359
+ return {
360
+ ...recording,
361
+ request: {
362
+ ...recording.request,
363
+ headers: redactHeaders(recording.request.headers, resolved),
364
+ body: redactBody(recording.request.body, regexes, placeholder)
365
+ },
366
+ response: recording.response && {
367
+ ...recording.response,
368
+ headers: redactHeaders(recording.response.headers, resolved),
369
+ body: redactBody(recording.response.body, regexes, placeholder)
370
+ }
371
+ };
372
+ }
373
+ function redactWebSocketRecording(recording, resolved) {
374
+ const { regexes, placeholder } = resolved;
375
+ return {
376
+ ...recording,
377
+ headers: recording.headers ? redactHeaders(recording.headers, resolved) : recording.headers,
378
+ messages: recording.messages.map((message) => ({
379
+ ...message,
380
+ data: redactBody(message.data, regexes, placeholder) ?? message.data
381
+ }))
382
+ };
383
+ }
384
+ function redactSession(session, config) {
385
+ if (config?.enabled === false) {
386
+ return session;
387
+ }
388
+ const resolved = resolveRedaction(config);
389
+ return {
390
+ ...session,
391
+ recordings: session.recordings.map(
392
+ (recording) => redactRecording(recording, resolved)
393
+ ),
394
+ websocketRecordings: (session.websocketRecordings ?? []).map(
395
+ (recording) => redactWebSocketRecording(recording, resolved)
396
+ )
397
+ };
398
+ }
399
+
400
+ // src/utils/fileUtils.ts
263
401
  var JSON_INDENT_SPACES = 2;
264
402
  var EXTENSION = ".mock.json";
265
403
  var MAX_FILENAME_LENGTH = 255 - EXTENSION.length;
@@ -304,14 +442,17 @@ function processRecordings(recordings) {
304
442
  processedRecordings.sort((a, b) => a.recordingId - b.recordingId);
305
443
  return processedRecordings;
306
444
  }
307
- async function saveRecordingSession(recordingsDir, session) {
445
+ async function saveRecordingSession(recordingsDir, session, redaction) {
308
446
  const filePath = getRecordingPath(recordingsDir, session.id);
309
447
  await fs.mkdir(recordingsDir, { recursive: true });
310
448
  const processedRecordings = processRecordings(session.recordings);
311
- const processedSession = {
312
- ...session,
313
- recordings: processedRecordings
314
- };
449
+ const processedSession = redactSession(
450
+ {
451
+ ...session,
452
+ recordings: processedRecordings
453
+ },
454
+ redaction
455
+ );
315
456
  await fs.writeFile(
316
457
  filePath,
317
458
  JSON.stringify(processedSession, null, JSON_INDENT_SPACES)
@@ -542,17 +683,20 @@ var ProxyServer = class {
542
683
  timeoutMs;
543
684
  // Unique ID for each recording entry
544
685
  recordingIdCounter;
545
- sequenceCounterByKey;
546
686
  // Sequence counter per key (endpoint)
687
+ sequenceCounterByKey;
547
688
  replaySessions;
548
689
  // Track multiple concurrent replay sessions by recording ID
549
690
  recordingPromises;
550
691
  // Stack of promises that resolve to completed recordings
551
692
  flushPromise;
552
693
  // Promise for in-progress flush operation
553
- constructor(target, recordingsDir, timeoutMs) {
694
+ redaction;
695
+ // Secret-redaction config applied before saving
696
+ constructor(target, recordingsDir, timeoutMs, redaction) {
554
697
  this.target = target;
555
698
  this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
699
+ this.redaction = redaction;
556
700
  this.mode = Modes.transparent;
557
701
  this.recordingId = null;
558
702
  this.recordingIdCounter = 0;
@@ -802,7 +946,11 @@ var ProxyServer = class {
802
946
  console.log(
803
947
  `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
804
948
  );
805
- await saveRecordingSession(this.recordingsDir, this.currentSession);
949
+ await saveRecordingSession(
950
+ this.recordingsDir,
951
+ this.currentSession,
952
+ this.redaction
953
+ );
806
954
  }
807
955
  getRecordingIdOrError(req, res) {
808
956
  const recordingIdFromRequest = getRecordingIdFromRequest(req);
@@ -1306,6 +1454,6 @@ function createHeadersWithRecordingId(requestHeaders, additionalHeaders = {}) {
1306
1454
  };
1307
1455
  }
1308
1456
 
1309
- export { ProxyServer, RECORDING_ID_HEADER, createHeadersWithRecordingId, generateSessionId, getRecordingId, playwrightProxy, setNextProxyHeaders, setProxyMode, startRecording, startReplay, stopProxy };
1457
+ export { DEFAULT_REDACTED_HEADERS, ProxyServer, RECORDING_ID_HEADER, REDACTED_PLACEHOLDER, createHeadersWithRecordingId, defineConfig, generateSessionId, getRecordingId, playwrightProxy, redactSession, setNextProxyHeaders, setProxyMode, startRecording, startReplay, stopProxy };
1310
1458
  //# sourceMappingURL=index.mjs.map
1311
1459
  //# sourceMappingURL=index.mjs.map
@@ -1,3 +1,3 @@
1
1
  import '@playwright/test';
2
- export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BloXCw69.cjs';
2
+ export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BnkejxM_.cjs';
3
3
  import 'node:http';
@@ -1,3 +1,3 @@
1
1
  import '@playwright/test';
2
- export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BloXCw69.js';
2
+ export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BnkejxM_.js';
3
3
  import 'node:http';