@thaparoyal/replayapi 0.1.1 → 0.1.2
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.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +174 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* ReplayAPI SDK Types
|
|
3
5
|
*/
|
|
@@ -56,6 +58,42 @@ interface CaptureStats {
|
|
|
56
58
|
startedAt: Date;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* ReplayAPI SDK — Express/Connect middleware
|
|
63
|
+
*
|
|
64
|
+
* Captures incoming HTTP requests and responses,
|
|
65
|
+
* then sends them asynchronously to the ReplayAPI backend.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import express from 'express';
|
|
70
|
+
* import { replayApi } from '@thaparoyal/replayapi';
|
|
71
|
+
*
|
|
72
|
+
* const app = express();
|
|
73
|
+
* replayApi.init('rp_your_api_key');
|
|
74
|
+
*
|
|
75
|
+
* // Add middleware BEFORE your routes
|
|
76
|
+
* app.use(replayApi.middleware());
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create the Express/Connect middleware function.
|
|
82
|
+
*
|
|
83
|
+
* Options:
|
|
84
|
+
* - `exclude` — URL patterns to skip (e.g. health checks)
|
|
85
|
+
* - `maxBodySize` — max body size to capture in bytes (default: 1MB)
|
|
86
|
+
*/
|
|
87
|
+
interface MiddlewareOptions {
|
|
88
|
+
/** URL patterns to exclude from capture */
|
|
89
|
+
exclude?: Array<string | RegExp>;
|
|
90
|
+
/** Max body size to capture in bytes (default: 1MB) */
|
|
91
|
+
maxBodySize?: number;
|
|
92
|
+
}
|
|
93
|
+
declare function createMiddleware(options?: MiddlewareOptions): (req: IncomingMessage & {
|
|
94
|
+
body?: unknown;
|
|
95
|
+
}, res: ServerResponse, next: (err?: unknown) => void) => void;
|
|
96
|
+
|
|
59
97
|
/**
|
|
60
98
|
* @replayapi/node — ReplayAPI SDK for Node.js
|
|
61
99
|
*
|
|
@@ -113,6 +151,7 @@ declare const replayApi: {
|
|
|
113
151
|
stop: typeof stop;
|
|
114
152
|
isActive: typeof isActive;
|
|
115
153
|
getStats: typeof getStats;
|
|
154
|
+
middleware: typeof createMiddleware;
|
|
116
155
|
};
|
|
117
156
|
|
|
118
|
-
export { type CaptureStats, type ReplayApiConfig, type ReplayApiInstance, getStats, init, isActive, replayApi, stop };
|
|
157
|
+
export { type CaptureStats, type MiddlewareOptions, type ReplayApiConfig, type ReplayApiInstance, createMiddleware, getStats, init, isActive, replayApi, stop };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* ReplayAPI SDK Types
|
|
3
5
|
*/
|
|
@@ -56,6 +58,42 @@ interface CaptureStats {
|
|
|
56
58
|
startedAt: Date;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* ReplayAPI SDK — Express/Connect middleware
|
|
63
|
+
*
|
|
64
|
+
* Captures incoming HTTP requests and responses,
|
|
65
|
+
* then sends them asynchronously to the ReplayAPI backend.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import express from 'express';
|
|
70
|
+
* import { replayApi } from '@thaparoyal/replayapi';
|
|
71
|
+
*
|
|
72
|
+
* const app = express();
|
|
73
|
+
* replayApi.init('rp_your_api_key');
|
|
74
|
+
*
|
|
75
|
+
* // Add middleware BEFORE your routes
|
|
76
|
+
* app.use(replayApi.middleware());
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create the Express/Connect middleware function.
|
|
82
|
+
*
|
|
83
|
+
* Options:
|
|
84
|
+
* - `exclude` — URL patterns to skip (e.g. health checks)
|
|
85
|
+
* - `maxBodySize` — max body size to capture in bytes (default: 1MB)
|
|
86
|
+
*/
|
|
87
|
+
interface MiddlewareOptions {
|
|
88
|
+
/** URL patterns to exclude from capture */
|
|
89
|
+
exclude?: Array<string | RegExp>;
|
|
90
|
+
/** Max body size to capture in bytes (default: 1MB) */
|
|
91
|
+
maxBodySize?: number;
|
|
92
|
+
}
|
|
93
|
+
declare function createMiddleware(options?: MiddlewareOptions): (req: IncomingMessage & {
|
|
94
|
+
body?: unknown;
|
|
95
|
+
}, res: ServerResponse, next: (err?: unknown) => void) => void;
|
|
96
|
+
|
|
59
97
|
/**
|
|
60
98
|
* @replayapi/node — ReplayAPI SDK for Node.js
|
|
61
99
|
*
|
|
@@ -113,6 +151,7 @@ declare const replayApi: {
|
|
|
113
151
|
stop: typeof stop;
|
|
114
152
|
isActive: typeof isActive;
|
|
115
153
|
getStats: typeof getStats;
|
|
154
|
+
middleware: typeof createMiddleware;
|
|
116
155
|
};
|
|
117
156
|
|
|
118
|
-
export { type CaptureStats, type ReplayApiConfig, type ReplayApiInstance, getStats, init, isActive, replayApi, stop };
|
|
157
|
+
export { type CaptureStats, type MiddlewareOptions, type ReplayApiConfig, type ReplayApiInstance, createMiddleware, getStats, init, isActive, replayApi, stop };
|
package/dist/index.js
CHANGED
|
@@ -363,6 +363,176 @@ function getFetchStats() {
|
|
|
363
363
|
return { ...stats2 };
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
// src/middleware.ts
|
|
367
|
+
var API_URL = process.env.REPLAY_API_URL || "http://localhost:8000";
|
|
368
|
+
var apiKey = null;
|
|
369
|
+
var environment = "development";
|
|
370
|
+
var captureCount = 0;
|
|
371
|
+
var errorCount = 0;
|
|
372
|
+
function configureMiddleware(key, env) {
|
|
373
|
+
apiKey = key;
|
|
374
|
+
environment = env;
|
|
375
|
+
}
|
|
376
|
+
function mapEnvironment(env) {
|
|
377
|
+
const map = {
|
|
378
|
+
development: "dev",
|
|
379
|
+
dev: "dev",
|
|
380
|
+
local: "local",
|
|
381
|
+
staging: "staging",
|
|
382
|
+
production: "production",
|
|
383
|
+
prod: "production",
|
|
384
|
+
test: "test",
|
|
385
|
+
testing: "test"
|
|
386
|
+
};
|
|
387
|
+
return map[env.toLowerCase()] || "dev";
|
|
388
|
+
}
|
|
389
|
+
async function sendToBackend(event) {
|
|
390
|
+
try {
|
|
391
|
+
const url = `${API_URL}/api/ingest`;
|
|
392
|
+
const { default: http2 } = await import('http');
|
|
393
|
+
const { default: https2 } = await import('https');
|
|
394
|
+
const parsedUrl = new URL(url);
|
|
395
|
+
const transport = parsedUrl.protocol === "https:" ? https2 : http2;
|
|
396
|
+
const body = JSON.stringify(event);
|
|
397
|
+
const req = transport.request(
|
|
398
|
+
{
|
|
399
|
+
hostname: parsedUrl.hostname,
|
|
400
|
+
port: parsedUrl.port,
|
|
401
|
+
path: parsedUrl.pathname,
|
|
402
|
+
method: "POST",
|
|
403
|
+
headers: {
|
|
404
|
+
"Content-Type": "application/json",
|
|
405
|
+
"X-API-Key": apiKey || "",
|
|
406
|
+
"Content-Length": Buffer.byteLength(body)
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
(res) => {
|
|
410
|
+
res.resume();
|
|
411
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
412
|
+
debug(`ingest response: ${res.statusCode}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
req.on("error", (err) => {
|
|
417
|
+
errorCount++;
|
|
418
|
+
debug(`ingest error: ${err.message}`);
|
|
419
|
+
});
|
|
420
|
+
req.write(body);
|
|
421
|
+
req.end();
|
|
422
|
+
captureCount++;
|
|
423
|
+
} catch (err) {
|
|
424
|
+
errorCount++;
|
|
425
|
+
debug(`ingest send error: ${err.message}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function collectResponseBody(res, callback) {
|
|
429
|
+
const chunks = [];
|
|
430
|
+
const originalWrite = res.write.bind(res);
|
|
431
|
+
const originalEnd = res.end.bind(res);
|
|
432
|
+
res.write = function(chunk, ...args) {
|
|
433
|
+
if (chunk) {
|
|
434
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
435
|
+
}
|
|
436
|
+
return originalWrite(chunk, ...args);
|
|
437
|
+
};
|
|
438
|
+
res.end = function(chunk, ...args) {
|
|
439
|
+
if (chunk && typeof chunk !== "function") {
|
|
440
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
441
|
+
}
|
|
442
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
443
|
+
callback(body);
|
|
444
|
+
return originalEnd(chunk, ...args);
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function tryParseJson(str) {
|
|
448
|
+
try {
|
|
449
|
+
return JSON.parse(str);
|
|
450
|
+
} catch {
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function createMiddleware(options = {}) {
|
|
455
|
+
const { exclude = [], maxBodySize = 1024 * 1024 } = options;
|
|
456
|
+
return function replayMiddleware(req, res, next) {
|
|
457
|
+
if (!apiKey) {
|
|
458
|
+
return next();
|
|
459
|
+
}
|
|
460
|
+
const url = req.url || "/";
|
|
461
|
+
for (const pattern of exclude) {
|
|
462
|
+
if (typeof pattern === "string" && url.includes(pattern)) return next();
|
|
463
|
+
if (pattern instanceof RegExp && pattern.test(url)) return next();
|
|
464
|
+
}
|
|
465
|
+
const startTime = Date.now();
|
|
466
|
+
const startHrTime = process.hrtime.bigint();
|
|
467
|
+
const method = (req.method || "GET").toUpperCase();
|
|
468
|
+
const host = (req.headers.host || "unknown").split(":")[0];
|
|
469
|
+
const protocol = req.protocol === "https" ? "http" : "http";
|
|
470
|
+
let path = url;
|
|
471
|
+
let queryParams = {};
|
|
472
|
+
const qIndex = url.indexOf("?");
|
|
473
|
+
if (qIndex !== -1) {
|
|
474
|
+
path = url.substring(0, qIndex);
|
|
475
|
+
try {
|
|
476
|
+
const searchParams = new URLSearchParams(url.substring(qIndex + 1));
|
|
477
|
+
queryParams = Object.fromEntries(searchParams.entries());
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const requestHeaders = {};
|
|
482
|
+
for (const [key, val] of Object.entries(req.headers)) {
|
|
483
|
+
if (val) {
|
|
484
|
+
requestHeaders[key] = Array.isArray(val) ? val.join(", ") : val;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
let requestBody = void 0;
|
|
488
|
+
if (req.body !== void 0) {
|
|
489
|
+
const bodyStr = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
490
|
+
if (bodyStr.length <= maxBodySize) {
|
|
491
|
+
requestBody = typeof req.body === "string" ? tryParseJson(req.body) || req.body : req.body;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
collectResponseBody(res, (responseBodyStr) => {
|
|
495
|
+
const durationMs = Number(process.hrtime.bigint() - startHrTime) / 1e6;
|
|
496
|
+
const statusCode = res.statusCode;
|
|
497
|
+
const responseHeaders = {};
|
|
498
|
+
const rawHeaders = res.getHeaders();
|
|
499
|
+
for (const [key, val] of Object.entries(rawHeaders)) {
|
|
500
|
+
if (val) {
|
|
501
|
+
responseHeaders[key] = Array.isArray(val) ? val.join(", ") : String(val);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
let responseBody = void 0;
|
|
505
|
+
if (responseBodyStr.length <= maxBodySize) {
|
|
506
|
+
responseBody = tryParseJson(responseBodyStr) || responseBodyStr;
|
|
507
|
+
}
|
|
508
|
+
const event = {
|
|
509
|
+
method,
|
|
510
|
+
protocol,
|
|
511
|
+
host,
|
|
512
|
+
path,
|
|
513
|
+
queryParams,
|
|
514
|
+
requestHeaders,
|
|
515
|
+
requestBodyInline: requestBody,
|
|
516
|
+
requestSizeBytes: requestBody ? Buffer.byteLength(JSON.stringify(requestBody)) : 0,
|
|
517
|
+
statusCode,
|
|
518
|
+
responseHeaders,
|
|
519
|
+
responseBodyInline: responseBody,
|
|
520
|
+
responseSizeBytes: responseBodyStr.length,
|
|
521
|
+
startedAt: new Date(startTime).toISOString(),
|
|
522
|
+
durationMs: Math.round(durationMs * 100) / 100,
|
|
523
|
+
source: "sdk",
|
|
524
|
+
environment: mapEnvironment(environment),
|
|
525
|
+
clientIp: req.headers["x-forwarded-for"] ? String(req.headers["x-forwarded-for"]).split(",")[0].trim() : req.socket?.remoteAddress || null,
|
|
526
|
+
tags: [],
|
|
527
|
+
metadata: {}
|
|
528
|
+
};
|
|
529
|
+
debug(`captured: ${method} ${path} ${statusCode} (${event.durationMs}ms)`);
|
|
530
|
+
sendToBackend(event);
|
|
531
|
+
});
|
|
532
|
+
next();
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
366
536
|
// src/index.ts
|
|
367
537
|
var initialized = false;
|
|
368
538
|
function init(configOrApiKey) {
|
|
@@ -385,6 +555,7 @@ function init(configOrApiKey) {
|
|
|
385
555
|
}
|
|
386
556
|
config3.environment = config3.environment || process.env.REPLAY_ENVIRONMENT || "development";
|
|
387
557
|
info(`initialized \u2014 env: ${config3.environment}`);
|
|
558
|
+
configureMiddleware(config3.apiKey, config3.environment);
|
|
388
559
|
installHttpInterceptor(config3);
|
|
389
560
|
installFetchInterceptor(config3);
|
|
390
561
|
initialized = true;
|
|
@@ -414,9 +585,11 @@ var replayApi = {
|
|
|
414
585
|
init,
|
|
415
586
|
stop,
|
|
416
587
|
isActive,
|
|
417
|
-
getStats
|
|
588
|
+
getStats,
|
|
589
|
+
middleware: createMiddleware
|
|
418
590
|
};
|
|
419
591
|
|
|
592
|
+
exports.createMiddleware = createMiddleware;
|
|
420
593
|
exports.getStats = getStats;
|
|
421
594
|
exports.init = init;
|
|
422
595
|
exports.isActive = isActive;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/matcher.ts","../src/logger.ts","../src/interceptor-http.ts","../src/interceptor-fetch.ts","../src/index.ts"],"names":["URL","https","http","PROXY_URL","active","config","stats","init"],"mappings":";;;;;;;;;;;;;;AAQO,SAAS,eAAA,CACd,KACA,QAAA,EACS;AACT,EAAA,OAAO,QAAA,CAAS,IAAA,CAAK,CAAC,OAAA,KAAY;AAChC,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA,OAAO,GAAA,CAAI,SAAS,OAAO,CAAA;AAAA,IAC7B;AACA,IAAA,OAAO,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,EACzB,CAAC,CAAA;AACH;AAKO,SAAS,aAAA,CACd,GAAA,EACA,QAAA,EACA,OAAA,EACA,OAAA,EACS;AAET,EAAA,IAAI,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA,EAAG;AAC9B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACjC,IAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,EAAK,OAAO,CAAA,EAAG;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACjC,IAAA,IAAI,eAAA,CAAgB,GAAA,EAAK,OAAO,CAAA,EAAG;AACjC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;;;AClDA,IAAI,YAAA,GAAe,KAAA;AAEZ,SAAS,SAAS,OAAA,EAAwB;AAC/C,EAAA,YAAA,GAAe,OAAA;AACjB;AAEO,SAAS,SAAS,IAAA,EAAuB;AAC9C,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,OAAA,CAAQ,GAAA,CAAI,aAAA,EAAe,GAAG,IAAI,CAAA;AAAA,EACpC;AACF;AAEO,SAAS,QAAQ,IAAA,EAAuB;AAC7C,EAAA,OAAA,CAAQ,GAAA,CAAI,aAAA,EAAe,GAAG,IAAI,CAAA;AACpC;AAEO,SAAS,QAAQ,IAAA,EAAuB;AAC7C,EAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,GAAG,IAAI,CAAA;AACrC;AAEO,SAAS,SAAS,IAAA,EAAuB;AAC9C,EAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,EAAe,GAAG,IAAI,CAAA;AACtC;;;ACDA,IAAI,mBAAA,GAAkD,IAAA;AACtD,IAAI,eAAA,GAA0C,IAAA;AAC9C,IAAI,oBAAA,GAAoD,IAAA;AACxD,IAAI,gBAAA,GAA4C,IAAA;AAEhD,IAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,gBAAA,IAAoB,uBAAA;AAGlD,IAAI,MAAA,GAAS,KAAA;AACb,IAAI,MAAA;AAKJ,IAAM,KAAA,GAAsB;AAAA,EAC1B,aAAA,EAAe,CAAA;AAAA,EACf,YAAA,EAAc,CAAA;AAAA,EACd,WAAA,EAAa,CAAA;AAAA,EACb,SAAA,sBAAe,IAAA;AACjB,CAAA;AAQA,SAAS,iBACP,IAAA,EAKA;AACA,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,UAA+B,EAAC;AACpC,EAAA,IAAI,QAAA;AAEJ,EAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,EAAU;AAC/B,IAAA,SAAA,GAAY,KAAK,CAAC,CAAA;AAClB,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,MAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,MAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,QAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,IAAA,CAAK,CAAC,CAAA,YAAaA,OAAAA,EAAK;AACjC,IAAA,SAAA,GAAY,IAAA,CAAK,CAAC,CAAA,CAAE,QAAA,EAAS;AAC7B,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,MAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,MAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,QAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,IAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,IAAA,MAAM,QAAA,GAAY,QAAkC,QAAA,IAAY,OAAA;AAChE,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,QAAA,IAAY,OAAA,CAAQ,IAAA,IAAQ,WAAA;AACjD,IAAA,MAAM,OAAO,OAAA,CAAQ,IAAA,GAAO,CAAA,CAAA,EAAI,OAAA,CAAQ,IAAI,CAAA,CAAA,GAAK,EAAA;AACjD,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,GAAA;AAC7B,IAAA,SAAA,GAAY,GAAG,QAAQ,CAAA,EAAA,EAAK,IAAI,CAAA,EAAG,IAAI,GAAG,IAAI,CAAA,CAAA;AAC9C,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB;AAAA,EACF,CAAA,MAAO;AACL,IAAA,SAAA,GAAY,EAAA;AAAA,EACd;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,QAAA,EAAS;AACxC;AAKA,SAAS,oBAAA,CACP,YACA,eAAA,EACqB;AACrB,EAAA,OAAO,SAAS,kBACX,IAAA,EACiB;AACpB,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,QAAA,EAAS,GAAI,iBAAiB,IAAI,CAAA;AAE9D,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAI,MAAM,8CAA8C,CAAA;AACxD,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAWC,sBAAA,GAAQC,qBAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAGA,MAAA,IACE,CAAC,aAAA;AAAA,QACC,SAAA;AAAA,QACA,MAAA,CAAO,QAAA;AAAA,QACP,MAAA,CAAO,OAAA;AAAA,QACP,MAAA,CAAO;AAAA,OACT,EACA;AACA,QAAI,KAAA,CAAM,wBAAwB,SAAS,CAAA;AAC3C,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAWD,sBAAA,GAAQC,qBAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAGA,MAAA,MAAM,WAAA,GAAc,IAAIF,OAAAA,CAAI,MAAA,CAAO,QAAQ,CAAA;AAG3C,MAAA,IAAI,YAAA;AACJ,MAAA,IAAI;AACF,QAAA,YAAA,GAAe,IAAIA,QAAI,SAAS,CAAA;AAAA,MAClC,CAAA,CAAA,MAAQ;AACN,QAAI,KAAA,CAAM,iCAAiC,SAAS,CAAA;AACpD,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAWC,sBAAA,GAAQC,qBAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAEA,MAAA,MAAM,eAAe,CAAA,EAAG,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK,aAAa,IAAI,CAAA,CAAA;AAGnE,MAAA,MAAM,cAAA,GAAsC;AAAA,QAC1C,GAAG,OAAA;AAAA,QACH,UAAU,WAAA,CAAY,QAAA;AAAA,QACtB,UAAU,WAAA,CAAY,QAAA;AAAA,QACtB,MAAM,WAAA,CAAY,IAAA,KAAS,WAAA,CAAY,QAAA,KAAa,WAAW,GAAA,GAAM,EAAA,CAAA;AAAA,QACrE,IAAA,EAAM,YAAA,CAAa,QAAA,GAAW,YAAA,CAAa,MAAA;AAAA,QAC3C,OAAA,EAAS;AAAA,UACP,GAAG,OAAA,CAAQ,OAAA;AAAA,UACX,aAAa,MAAA,CAAO,MAAA;AAAA,UACpB,iBAAA,EAAmB,YAAA;AAAA,UACnB,gBAAgB,MAAA,CAAO,WAAA;AAAA;AAAA,UAEvB,MAAM,YAAA,CAAa;AAAA;AACrB,OACF;AAGA,MAAA,IAAI,OAAO,SAAA,EAAW;AACpB,QAAC,cAAA,CAAe,OAAA,CAAmC,kBAAkB,CAAA,GACnE,MAAA,CAAO,SAAA;AAAA,MACX;AAGA,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,cAAA,CAAe,UAAU,MAAA,CAAO,OAAA;AAAA,MAClC;AAEA,MAAI,KAAA,CAAM,cAAc,OAAA,CAAQ,MAAA,IAAU,OAAO,SAAA,EAAW,QAAA,EAAK,OAAO,QAAQ,CAAA;AAChF,MAAA,KAAA,CAAM,aAAA,EAAA;AAGN,MAAA,MAAM,QAAA,GAAW,IAAIF,OAAAA,CAAI,CAAA,EAAG,eAAe,QAAQ,CAAA,EAAA,EAAK,cAAA,CAAe,QAAQ,IAAI,cAAA,CAAe,IAAI,CAAA,EAAG,cAAA,CAAe,IAAI,CAAA,CAAE,CAAA;AAC9H,MAAA,OAAO,mBAAA,CAAqB,IAAA,CAAKE,qBAAA,EAAM,QAAA,EAAU,gBAAgB,QAAQ,CAAA;AAAA,IAC3E,SAAS,GAAA,EAAK;AACZ,MAAI,KAAA,CAAM,uCAAuC,GAAG,CAAA;AACpD,MAAA,KAAA,CAAM,WAAA,EAAA;AACN,MAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,QAC9B,eAAA,KAAoB,WAAWD,sBAAA,GAAQC,qBAAA;AAAA,QACvC;AAAA,OACF;AAAA,IACF;AAAA,EACF,CAAA;AACF;AAKA,SAAS,iBACP,cAAA,EACiB;AACjB,EAAA,OAAO,SAAS,cAAc,IAAA,EAAqC;AACjE,IAAA,MAAM,GAAA,GAAO,cAAA,CAA4B,GAAG,IAAI,CAAA;AAChD,IAAA,GAAA,CAAI,GAAA,EAAI;AACR,IAAA,OAAO,GAAA;AAAA,EACT,CAAA;AACF;AAKO,SAAS,uBAAuB,GAAA,EAA4B;AACjE,EAAA,IAAI,MAAA,EAAQ;AACV,IAAI,KAAK,kDAAkD,CAAA;AAC3D,IAAA;AAAA,EACF;AAEA,EAAA,MAAA,GAAS;AAAA,IACP,GAAG,GAAA;AAAA,IACH,QAAA,EAAU,SAAA;AAAA,IACV,WAAA,EAAa,IAAI,WAAA,IAAe;AAAA,GAClC;AAGA,EAAA,mBAAA,GAAsBA,qBAAA,CAAK,OAAA;AAC3B,EAAA,eAAA,GAAkBA,qBAAA,CAAK,GAAA;AACvB,EAAA,oBAAA,GAAuBD,sBAAA,CAAM,OAAA;AAC7B,EAAA,gBAAA,GAAmBA,sBAAA,CAAM,GAAA;AAGzB,EAAA,MAAM,kBAAA,GAAqB,oBAAA,CAAqB,mBAAA,EAAqB,OAAO,CAAA;AAC5E,EAAAC,qBAAA,CAAK,OAAA,GAAU,kBAAA;AACf,EAAAA,qBAAA,CAAK,GAAA,GAAM,iBAAiB,kBAAkB,CAAA;AAG9C,EAAA,MAAM,mBAAA,GAAsB,oBAAA;AAAA,IAC1B,oBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAAD,sBAAA,CAAM,OAAA,GAAU,mBAAA;AAChB,EAAAA,sBAAA,CAAM,GAAA,GAAM,iBAAiB,mBAAmB,CAAA;AAGhD,EAAA,KAAA,CAAM,aAAA,GAAgB,CAAA;AACtB,EAAA,KAAA,CAAM,YAAA,GAAe,CAAA;AACrB,EAAA,KAAA,CAAM,WAAA,GAAc,CAAA;AACpB,EAAA,KAAA,CAAM,SAAA,uBAAgB,IAAA,EAAK;AAE3B,EAAA,MAAA,GAAS,IAAA;AACT,EAAI,MAAM,kCAAkC,CAAA;AAC9C;AAKO,SAAS,wBAAA,GAAiC;AAC/C,EAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,EAAA,IAAI,mBAAA,wBAA0B,OAAA,GAAU,mBAAA;AACxC,EAAA,IAAI,eAAA,wBAAsB,GAAA,GAAM,eAAA;AAChC,EAAA,IAAI,oBAAA,yBAA4B,OAAA,GAAU,oBAAA;AAC1C,EAAA,IAAI,gBAAA,yBAAwB,GAAA,GAAM,gBAAA;AAElC,EAAA,mBAAA,GAAsB,IAAA;AACtB,EAAA,eAAA,GAAkB,IAAA;AAClB,EAAA,oBAAA,GAAuB,IAAA;AACvB,EAAA,gBAAA,GAAmB,IAAA;AAEnB,EAAA,MAAA,GAAS,KAAA;AACT,EAAI,MAAM,gCAAgC,CAAA;AAC5C;AAEO,SAAS,uBAAA,GAAmC;AACjD,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,YAAA,GAA6B;AAC3C,EAAA,OAAO,EAAE,GAAG,KAAA,EAAM;AACpB;;;AClRA,IAAME,UAAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,gBAAA,IAAoB,uBAAA;AAElD,IAAI,aAAA,GAAgD,IAAA;AACpD,IAAIC,OAAAA,GAAS,KAAA;AACb,IAAIC,OAAAA;AAKJ,IAAMC,MAAAA,GAAsB;AAAA,EAC1B,aAAA,EAAe,CAAA;AAAA,EACf,YAAA,EAAc,CAAA;AAAA,EACd,WAAA,EAAa,CAAA;AAAA,EACb,SAAA,sBAAe,IAAA;AACjB,CAAA;AAKO,SAAS,wBAAwB,GAAA,EAA4B;AAClE,EAAA,IAAIF,OAAAA,EAAQ;AACV,IAAI,KAAK,qCAAqC,CAAA;AAC9C,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AAC1C,IAAI,MAAM,4DAA4D,CAAA;AACtE,IAAA;AAAA,EACF;AAEA,EAAAC,OAAAA,GAAS;AAAA,IACP,GAAG,GAAA;AAAA,IACH,QAAA,EAAUF,UAAAA;AAAA,IACV,WAAA,EAAa,IAAI,WAAA,IAAe;AAAA,GAClC;AAEA,EAAA,aAAA,GAAgB,UAAA,CAAW,KAAA;AAE3B,EAAA,UAAA,CAAW,KAAA,GAAQ,eAAe,YAAA,CAChC,KAAA,EACAI,KAAAA,EACmB;AACnB,IAAA,IAAI;AAEF,MAAA,IAAI,SAAA;AACJ,MAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,QAAA,SAAA,GAAY,KAAA;AAAA,MACd,CAAA,MAAA,IAAW,iBAAiB,GAAA,EAAK;AAC/B,QAAA,SAAA,GAAY,MAAM,QAAA,EAAS;AAAA,MAC7B,CAAA,MAAA,IAAW,iBAAiB,OAAA,EAAS;AACnC,QAAA,SAAA,GAAY,KAAA,CAAM,GAAA;AAAA,MACpB,CAAA,MAAO;AACL,QAAA,SAAA,GAAY,OAAO,KAAK,CAAA;AAAA,MAC1B;AAGA,MAAA,IACE,CAAC,aAAA;AAAA,QACC,SAAA;AAAA,QACAF,OAAAA,CAAO,QAAA;AAAA,QACPA,OAAAA,CAAO,OAAA;AAAA,QACPA,OAAAA,CAAO;AAAA,OACT,EACA;AACA,QAAI,KAAA,CAAM,8BAA8B,SAAS,CAAA;AACjD,QAAAC,MAAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,MACnC;AAGA,MAAA,IAAI,YAAA;AACJ,MAAA,IAAI;AACF,QAAA,YAAA,GAAe,IAAI,IAAI,SAAS,CAAA;AAAA,MAClC,CAAA,CAAA,MAAQ;AACN,QAAI,KAAA,CAAM,wCAAwC,SAAS,CAAA;AAC3D,QAAAD,MAAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,MACnC;AAEA,MAAA,MAAM,eAAe,CAAA,EAAG,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK,aAAa,IAAI,CAAA,CAAA;AACnE,MAAA,MAAM,SAAA,GAAY,YAAA,CAAa,QAAA,GAAW,YAAA,CAAa,MAAA;AACvD,MAAA,MAAM,UAAA,GAAa,CAAA,EAAGF,OAAAA,CAAO,QAAQ,GAAG,SAAS,CAAA,CAAA;AAGjD,MAAA,MAAM,UAAU,IAAI,OAAA,CAAQE,KAAAA,EAAM,OAAA,IAAW,EAAE,CAAA;AAG/C,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AACpC,UAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG;AACrB,YAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,UACxB;AAAA,QACF,CAAC,CAAA;AAAA,MACH;AAGA,MAAA,OAAA,CAAQ,GAAA,CAAI,WAAA,EAAaF,OAAAA,CAAO,MAAM,CAAA;AACtC,MAAA,OAAA,CAAQ,GAAA,CAAI,mBAAmB,YAAY,CAAA;AAC3C,MAAA,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAgBA,OAAAA,CAAO,WAAW,CAAA;AAC9C,MAAA,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,YAAA,CAAa,IAAI,CAAA;AAErC,MAAA,IAAIA,QAAO,SAAA,EAAW;AACpB,QAAA,OAAA,CAAQ,GAAA,CAAI,kBAAA,EAAoBA,OAAAA,CAAO,SAAS,CAAA;AAAA,MAClD;AAGA,MAAA,MAAM,WAAA,GAA2B;AAAA,QAC/B,GAAGE,KAAAA;AAAA,QACH;AAAA,OACF;AAGA,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,WAAA,CAAY,MAAA,GAAS,WAAA,CAAY,MAAA,IAAU,KAAA,CAAM,MAAA;AACjD,QAAA,IAAI,CAAC,WAAA,CAAY,IAAA,IAAQ,KAAA,CAAM,IAAA,EAAM;AACnC,UAAA,WAAA,CAAY,OAAO,KAAA,CAAM,IAAA;AAAA,QAC3B;AAAA,MACF;AAEA,MAAI,KAAA;AAAA,QACF,kBAAA;AAAA,QACA,YAAY,MAAA,IAAU,KAAA;AAAA,QACtB,SAAA;AAAA,QACA,QAAA;AAAA,QACAF,OAAAA,CAAO;AAAA,OACT;AACA,MAAAC,MAAAA,CAAM,aAAA,EAAA;AAEN,MAAA,OAAO,aAAA,CAAe,YAAY,WAAW,CAAA;AAAA,IAC/C,SAAS,GAAA,EAAK;AACZ,MAAI,KAAA,CAAM,6CAA6C,GAAG,CAAA;AAC1D,MAAAA,MAAAA,CAAM,WAAA,EAAA;AACN,MAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,IACnC;AAAA,EACF,CAAA;AAGA,EAAAD,OAAM,aAAA,GAAgB,CAAA;AACtB,EAAAA,OAAM,YAAA,GAAe,CAAA;AACrB,EAAAA,OAAM,WAAA,GAAc,CAAA;AACpB,EAAAA,MAAAA,CAAM,SAAA,mBAAY,IAAI,IAAA,EAAK;AAE3B,EAAAF,OAAAA,GAAS,IAAA;AACT,EAAI,MAAM,6BAA6B,CAAA;AACzC;AAKO,SAAS,yBAAA,GAAkC;AAChD,EAAA,IAAI,CAACA,OAAAA,IAAU,CAAC,aAAA,EAAe;AAE/B,EAAA,UAAA,CAAW,KAAA,GAAQ,aAAA;AACnB,EAAA,aAAA,GAAgB,IAAA;AAChB,EAAAA,OAAAA,GAAS,KAAA;AACT,EAAI,MAAM,2BAA2B,CAAA;AACvC;AAEO,SAAS,wBAAA,GAAoC;AAClD,EAAA,OAAOA,OAAAA;AACT;AAEO,SAAS,aAAA,GAA8B;AAC5C,EAAA,OAAO,EAAE,GAAGE,MAAAA,EAAM;AACpB;;;AC5IA,IAAI,WAAA,GAAc,KAAA;AAkBlB,SAAS,KAAK,cAAA,EAA6D;AAEzE,EAAA,MAAMD,UACJ,OAAO,cAAA,KAAmB,WAAW,EAAE,MAAA,EAAQ,gBAAe,GAAI,cAAA;AAEpE,EAAA,IAAI,WAAA,EAAa;AACf,IAAI,KAAK,0EAAqE,CAAA;AAC9E,IAAA,IAAA,EAAK;AAAA,EACP;AAGA,EAAA,IAAI,CAACA,QAAO,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAIA,QAAO,QAAA,EAAU;AACnB,IAAI,KAAK,kDAAkD,CAAA;AAC3D,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AAAA,EACpC;AAGA,EAAA,IAAIA,QAAO,KAAA,EAAO;AAChB,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf;AAGA,EAAAA,QAAO,WAAA,GACLA,OAAAA,CAAO,WAAA,IAAe,OAAA,CAAQ,IAAI,kBAAA,IAAsB,aAAA;AAE1D,EAAI,IAAA,CAAK,CAAA,wBAAA,EAAsBA,OAAAA,CAAO,WAAW,CAAA,CAAE,CAAA;AAGnD,EAAA,sBAAA,CAAuBA,OAAM,CAAA;AAC7B,EAAA,uBAAA,CAAwBA,OAAM,CAAA;AAE9B,EAAA,WAAA,GAAc,IAAA;AAEd,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AACpC;AAKA,SAAS,IAAA,GAAa;AACpB,EAAA,IAAI,CAAC,WAAA,EAAa;AAElB,EAAA,wBAAA,EAAyB;AACzB,EAAA,yBAAA,EAA0B;AAE1B,EAAA,WAAA,GAAc,KAAA;AACd,EAAI,KAAK,yCAAoC,CAAA;AAC/C;AAKA,SAAS,QAAA,GAAoB;AAC3B,EAAA,OAAO,uBAAA,MAA6B,wBAAA,EAAyB;AAC/D;AAKA,SAAS,QAAA,GAAyB;AAChC,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,MAAM,aAAa,aAAA,EAAc;AAEjC,EAAA,OAAO;AAAA,IACL,aAAA,EAAe,SAAA,CAAU,aAAA,GAAgB,UAAA,CAAW,aAAA;AAAA,IACpD,YAAA,EAAc,SAAA,CAAU,YAAA,GAAe,UAAA,CAAW,YAAA;AAAA,IAClD,WAAA,EAAa,SAAA,CAAU,WAAA,GAAc,UAAA,CAAW,WAAA;AAAA,IAChD,WAAW,SAAA,CAAU,SAAA,GAAY,WAAW,SAAA,GACxC,SAAA,CAAU,YACV,UAAA,CAAW;AAAA,GACjB;AACF;AAKO,IAAM,SAAA,GAAY;AAAA,EACvB,IAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF","file":"index.js","sourcesContent":["/**\r\n * ReplayAPI SDK — URL matching utilities\r\n */\r\n\r\n/**\r\n * Check if a URL matches any of the given patterns.\r\n * Patterns can be strings (substring match) or RegExp.\r\n */\r\nexport function matchesPatterns(\r\n url: string,\r\n patterns: Array<string | RegExp>\r\n): boolean {\r\n return patterns.some((pattern) => {\r\n if (typeof pattern === \"string\") {\r\n return url.includes(pattern);\r\n }\r\n return pattern.test(url);\r\n });\r\n}\r\n\r\n/**\r\n * Determine if a request URL should be captured based on include/exclude filters.\r\n */\r\nexport function shouldCapture(\r\n url: string,\r\n proxyUrl: string,\r\n include?: Array<string | RegExp>,\r\n exclude?: Array<string | RegExp>\r\n): boolean {\r\n // Never capture requests to the proxy itself\r\n if (url.startsWith(proxyUrl)) {\r\n return false;\r\n }\r\n\r\n // Never capture requests to ReplayAPI internal paths\r\n if (url.includes(\"/__replay/\")) {\r\n return false;\r\n }\r\n\r\n // If include patterns are set, URL must match at least one\r\n if (include && include.length > 0) {\r\n if (!matchesPatterns(url, include)) {\r\n return false;\r\n }\r\n }\r\n\r\n // If exclude patterns are set, URL must not match any\r\n if (exclude && exclude.length > 0) {\r\n if (matchesPatterns(url, exclude)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n","/**\r\n * ReplayAPI SDK — Simple logger\r\n */\r\n\r\nlet debugEnabled = false;\r\n\r\nexport function setDebug(enabled: boolean): void {\r\n debugEnabled = enabled;\r\n}\r\n\r\nexport function debug(...args: unknown[]): void {\r\n if (debugEnabled) {\r\n console.log(\"[replayapi]\", ...args);\r\n }\r\n}\r\n\r\nexport function info(...args: unknown[]): void {\r\n console.log(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function warn(...args: unknown[]): void {\r\n console.warn(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function error(...args: unknown[]): void {\r\n console.error(\"[replayapi]\", ...args);\r\n}\r\n","/**\r\n * ReplayAPI SDK — HTTP/HTTPS interceptor\r\n *\r\n * Monkey-patches Node.js http.request and https.request to redirect\r\n * outgoing requests through the ReplayAPI proxy. The proxy captures\r\n * the traffic and forwards it to the original target.\r\n *\r\n * How it works:\r\n * 1. Original request: GET https://api.example.com/users\r\n * 2. SDK rewrites to: GET http://proxy:8080/users\r\n * with headers:\r\n * X-Api-Key: rp_...\r\n * X-Replay-Target: https://api.example.com\r\n * X-Replay-Env: staging\r\n * 3. Proxy captures, forwards to target, returns response transparently\r\n */\r\n\r\nimport http from \"node:http\";\r\nimport https from \"node:https\";\r\nimport { URL } from \"node:url\";\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\n// Store original implementations\r\nlet originalHttpRequest: typeof http.request | null = null;\r\nlet originalHttpGet: typeof http.get | null = null;\r\nlet originalHttpsRequest: typeof https.request | null = null;\r\nlet originalHttpsGet: typeof https.get | null = null;\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\n// Track state\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Parse the various argument formats that http.request accepts:\r\n * - (url: string, options?, callback?)\r\n * - (url: URL, options?, callback?)\r\n * - (options, callback?)\r\n */\r\nfunction parseRequestArgs(\r\n args: unknown[]\r\n): {\r\n targetUrl: string;\r\n options: http.RequestOptions;\r\n callback?: (res: http.IncomingMessage) => void;\r\n} {\r\n let targetUrl: string;\r\n let options: http.RequestOptions = {};\r\n let callback: ((res: http.IncomingMessage) => void) | undefined;\r\n\r\n if (typeof args[0] === \"string\") {\r\n targetUrl = args[0];\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (args[0] instanceof URL) {\r\n targetUrl = args[0].toString();\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (typeof args[0] === \"object\" && args[0] !== null) {\r\n options = args[0] as http.RequestOptions;\r\n const protocol = (options as { protocol?: string }).protocol || \"http:\";\r\n const host = options.hostname || options.host || \"localhost\";\r\n const port = options.port ? `:${options.port}` : \"\";\r\n const path = options.path || \"/\";\r\n targetUrl = `${protocol}//${host}${port}${path}`;\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n }\r\n } else {\r\n targetUrl = \"\";\r\n }\r\n\r\n return { targetUrl, options, callback };\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.request / https.request\r\n */\r\nfunction createProxiedRequest(\r\n originalFn: typeof http.request,\r\n defaultProtocol: \"http:\" | \"https:\"\r\n): typeof http.request {\r\n return function proxiedRequest(\r\n ...args: unknown[]\r\n ): http.ClientRequest {\r\n try {\r\n const { targetUrl, options, callback } = parseRequestArgs(args);\r\n\r\n if (!targetUrl) {\r\n log.debug(\"could not parse request URL, passing through\");\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Parse the proxy URL\r\n const proxyParsed = new URL(config.proxyUrl);\r\n\r\n // Parse the target URL to extract origin and path\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n\r\n // Build proxied options: send to proxy, with X-Replay-Target header\r\n const proxiedOptions: http.RequestOptions = {\r\n ...options,\r\n protocol: proxyParsed.protocol,\r\n hostname: proxyParsed.hostname,\r\n port: proxyParsed.port || (proxyParsed.protocol === \"https:\" ? 443 : 80),\r\n path: parsedTarget.pathname + parsedTarget.search,\r\n headers: {\r\n ...options.headers,\r\n \"X-Api-Key\": config.apiKey,\r\n \"X-Replay-Target\": targetOrigin,\r\n \"X-Replay-Env\": config.environment,\r\n // Preserve the original Host header\r\n Host: parsedTarget.host,\r\n },\r\n };\r\n\r\n // Add session ID if configured\r\n if (config.sessionId) {\r\n (proxiedOptions.headers as Record<string, string>)[\"X-Replay-Session\"] =\r\n config.sessionId;\r\n }\r\n\r\n // Set timeout if configured\r\n if (config.timeout) {\r\n proxiedOptions.timeout = config.timeout;\r\n }\r\n\r\n log.debug(\"capturing:\", options.method || \"GET\", targetUrl, \"→\", config.proxyUrl);\r\n stats.totalCaptured++;\r\n\r\n // Use http.request (not https) since we're talking to the proxy over HTTP\r\n const proxyUrl = new URL(`${proxiedOptions.protocol}//${proxiedOptions.hostname}:${proxiedOptions.port}${proxiedOptions.path}`);\r\n return originalHttpRequest!.call(http, proxyUrl, proxiedOptions, callback);\r\n } catch (err) {\r\n log.error(\"interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n } as typeof http.request;\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.get / https.get\r\n */\r\nfunction createProxiedGet(\r\n proxiedRequest: typeof http.request\r\n): typeof http.get {\r\n return function proxiedGet(...args: unknown[]): http.ClientRequest {\r\n const req = (proxiedRequest as Function)(...args) as http.ClientRequest;\r\n req.end();\r\n return req;\r\n } as typeof http.get;\r\n}\r\n\r\n/**\r\n * Install the HTTP/HTTPS interceptors\r\n */\r\nexport function installHttpInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"interceptor already installed, call stop() first\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n // Save originals\r\n originalHttpRequest = http.request;\r\n originalHttpGet = http.get;\r\n originalHttpsRequest = https.request;\r\n originalHttpsGet = https.get;\r\n\r\n // Patch http\r\n const proxiedHttpRequest = createProxiedRequest(originalHttpRequest, \"http:\");\r\n http.request = proxiedHttpRequest;\r\n http.get = createProxiedGet(proxiedHttpRequest);\r\n\r\n // Patch https\r\n const proxiedHttpsRequest = createProxiedRequest(\r\n originalHttpsRequest,\r\n \"https:\"\r\n );\r\n https.request = proxiedHttpsRequest;\r\n https.get = createProxiedGet(proxiedHttpsRequest);\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"http/https interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the HTTP/HTTPS interceptors\r\n */\r\nexport function uninstallHttpInterceptor(): void {\r\n if (!active) return;\r\n\r\n if (originalHttpRequest) http.request = originalHttpRequest;\r\n if (originalHttpGet) http.get = originalHttpGet;\r\n if (originalHttpsRequest) https.request = originalHttpsRequest;\r\n if (originalHttpsGet) https.get = originalHttpsGet;\r\n\r\n originalHttpRequest = null;\r\n originalHttpGet = null;\r\n originalHttpsRequest = null;\r\n originalHttpsGet = null;\r\n\r\n active = false;\r\n log.debug(\"http/https interceptor removed\");\r\n}\r\n\r\nexport function isHttpInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getHttpStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Global fetch() interceptor\r\n *\r\n * Patches globalThis.fetch to route requests through the ReplayAPI proxy.\r\n * Works with Node.js 18+ native fetch and any polyfills that set globalThis.fetch.\r\n */\r\n\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\nlet originalFetch: typeof globalThis.fetch | null = null;\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Install the fetch interceptor\r\n */\r\nexport function installFetchInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"fetch interceptor already installed\");\r\n return;\r\n }\r\n\r\n // Only patch if fetch exists (Node.js 18+)\r\n if (typeof globalThis.fetch !== \"function\") {\r\n log.debug(\"globalThis.fetch not available, skipping fetch interceptor\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n originalFetch = globalThis.fetch;\r\n\r\n globalThis.fetch = async function proxiedFetch(\r\n input: string | URL | Request,\r\n init?: RequestInit\r\n ): Promise<Response> {\r\n try {\r\n // Resolve the target URL\r\n let targetUrl: string;\r\n if (typeof input === \"string\") {\r\n targetUrl = input;\r\n } else if (input instanceof URL) {\r\n targetUrl = input.toString();\r\n } else if (input instanceof Request) {\r\n targetUrl = input.url;\r\n } else {\r\n targetUrl = String(input);\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"fetch skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n // Parse target URL\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"fetch: invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n const proxyPath = parsedTarget.pathname + parsedTarget.search;\r\n const proxiedUrl = `${config.proxyUrl}${proxyPath}`;\r\n\r\n // Build headers\r\n const headers = new Headers(init?.headers || {});\r\n\r\n // If input is a Request, merge its headers too\r\n if (input instanceof Request) {\r\n input.headers.forEach((value, key) => {\r\n if (!headers.has(key)) {\r\n headers.set(key, value);\r\n }\r\n });\r\n }\r\n\r\n // Add ReplayAPI headers\r\n headers.set(\"X-Api-Key\", config.apiKey);\r\n headers.set(\"X-Replay-Target\", targetOrigin);\r\n headers.set(\"X-Replay-Env\", config.environment);\r\n headers.set(\"Host\", parsedTarget.host);\r\n\r\n if (config.sessionId) {\r\n headers.set(\"X-Replay-Session\", config.sessionId);\r\n }\r\n\r\n // Build proxied init\r\n const proxiedInit: RequestInit = {\r\n ...init,\r\n headers,\r\n };\r\n\r\n // If input is a Request, preserve method and body\r\n if (input instanceof Request) {\r\n proxiedInit.method = proxiedInit.method || input.method;\r\n if (!proxiedInit.body && input.body) {\r\n proxiedInit.body = input.body;\r\n }\r\n }\r\n\r\n log.debug(\r\n \"fetch capturing:\",\r\n proxiedInit.method || \"GET\",\r\n targetUrl,\r\n \"→\",\r\n config.proxyUrl\r\n );\r\n stats.totalCaptured++;\r\n\r\n return originalFetch!(proxiedUrl, proxiedInit);\r\n } catch (err) {\r\n log.error(\"fetch interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return originalFetch!(input, init);\r\n }\r\n };\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"fetch interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the fetch interceptor\r\n */\r\nexport function uninstallFetchInterceptor(): void {\r\n if (!active || !originalFetch) return;\r\n\r\n globalThis.fetch = originalFetch;\r\n originalFetch = null;\r\n active = false;\r\n log.debug(\"fetch interceptor removed\");\r\n}\r\n\r\nexport function isFetchInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getFetchStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * @replayapi/node — ReplayAPI SDK for Node.js\r\n *\r\n * Automatically captures outgoing HTTP traffic and routes it through\r\n * the ReplayAPI proxy for recording, analysis, and replay testing.\r\n *\r\n * @example\r\n * ```ts\r\n * import { replayApi } from '@replayapi/node'\r\n *\r\n * // Simplest — just pass your API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: 'rp_your_api_key', environment: 'staging' })\r\n *\r\n * // All outgoing HTTP/fetch calls are now captured automatically\r\n * ```\r\n */\r\n\r\nimport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nimport {\r\n installHttpInterceptor,\r\n uninstallHttpInterceptor,\r\n isHttpInterceptorActive,\r\n getHttpStats,\r\n} from \"./interceptor-http.js\";\r\nimport {\r\n installFetchInterceptor,\r\n uninstallFetchInterceptor,\r\n isFetchInterceptorActive,\r\n getFetchStats,\r\n} from \"./interceptor-fetch.js\";\r\nimport { setDebug } from \"./logger.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nlet initialized = false;\r\n\r\n/**\r\n * Initialize the ReplayAPI SDK.\r\n *\r\n * Call this once at application startup, before any outgoing HTTP requests.\r\n * The SDK will automatically intercept `http.request`, `https.request`,\r\n * and `fetch` to route traffic through the ReplayAPI proxy.\r\n *\r\n * @example\r\n * ```ts\r\n * // Just an API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: process.env.REPLAY_API_KEY!, environment: 'staging' })\r\n * ```\r\n */\r\nfunction init(configOrApiKey: ReplayApiConfig | string): ReplayApiInstance {\r\n // Accept a plain string as shorthand for { apiKey: string }\r\n const config: ReplayApiConfig =\r\n typeof configOrApiKey === \"string\" ? { apiKey: configOrApiKey } : configOrApiKey;\r\n\r\n if (initialized) {\r\n log.warn(\"replayApi.init() called multiple times — stopping previous instance\");\r\n stop();\r\n }\r\n\r\n // Validate config\r\n if (!config.apiKey) {\r\n throw new Error(\r\n \"[@replayapi/node] apiKey is required. Get one from your ReplayAPI dashboard.\"\r\n );\r\n }\r\n\r\n if (config.disabled) {\r\n log.info(\"SDK disabled via config, skipping initialization\");\r\n return { stop, isActive, getStats };\r\n }\r\n\r\n // Enable debug logging if requested\r\n if (config.debug) {\r\n setDebug(true);\r\n }\r\n\r\n // Resolve environment with env var fallback\r\n config.environment =\r\n config.environment || process.env.REPLAY_ENVIRONMENT || \"development\";\r\n\r\n log.info(`initialized — env: ${config.environment}`);\r\n\r\n // Install interceptors\r\n installHttpInterceptor(config);\r\n installFetchInterceptor(config);\r\n\r\n initialized = true;\r\n\r\n return { stop, isActive, getStats };\r\n}\r\n\r\n/**\r\n * Stop the SDK and restore original HTTP/fetch behavior.\r\n */\r\nfunction stop(): void {\r\n if (!initialized) return;\r\n\r\n uninstallHttpInterceptor();\r\n uninstallFetchInterceptor();\r\n\r\n initialized = false;\r\n log.info(\"stopped — all interceptors removed\");\r\n}\r\n\r\n/**\r\n * Check if the SDK is currently intercepting traffic.\r\n */\r\nfunction isActive(): boolean {\r\n return isHttpInterceptorActive() || isFetchInterceptorActive();\r\n}\r\n\r\n/**\r\n * Get combined capture statistics from all interceptors.\r\n */\r\nfunction getStats(): CaptureStats {\r\n const httpStats = getHttpStats();\r\n const fetchStats = getFetchStats();\r\n\r\n return {\r\n totalCaptured: httpStats.totalCaptured + fetchStats.totalCaptured,\r\n totalSkipped: httpStats.totalSkipped + fetchStats.totalSkipped,\r\n totalErrors: httpStats.totalErrors + fetchStats.totalErrors,\r\n startedAt: httpStats.startedAt < fetchStats.startedAt\r\n ? httpStats.startedAt\r\n : fetchStats.startedAt,\r\n };\r\n}\r\n\r\n/**\r\n * The main ReplayAPI SDK instance.\r\n */\r\nexport const replayApi = {\r\n init,\r\n stop,\r\n isActive,\r\n getStats,\r\n};\r\n\r\n// Named exports for flexibility\r\nexport { init, stop, isActive, getStats };\r\n\r\n// Re-export types\r\nexport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/matcher.ts","../src/logger.ts","../src/interceptor-http.ts","../src/interceptor-fetch.ts","../src/middleware.ts","../src/index.ts"],"names":["URL","https","http","PROXY_URL","active","config","stats","init"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * ReplayAPI SDK — URL matching utilities\r\n */\r\n\r\n/**\r\n * Check if a URL matches any of the given patterns.\r\n * Patterns can be strings (substring match) or RegExp.\r\n */\r\nexport function matchesPatterns(\r\n url: string,\r\n patterns: Array<string | RegExp>\r\n): boolean {\r\n return patterns.some((pattern) => {\r\n if (typeof pattern === \"string\") {\r\n return url.includes(pattern);\r\n }\r\n return pattern.test(url);\r\n });\r\n}\r\n\r\n/**\r\n * Determine if a request URL should be captured based on include/exclude filters.\r\n */\r\nexport function shouldCapture(\r\n url: string,\r\n proxyUrl: string,\r\n include?: Array<string | RegExp>,\r\n exclude?: Array<string | RegExp>\r\n): boolean {\r\n // Never capture requests to the proxy itself\r\n if (url.startsWith(proxyUrl)) {\r\n return false;\r\n }\r\n\r\n // Never capture requests to ReplayAPI internal paths\r\n if (url.includes(\"/__replay/\")) {\r\n return false;\r\n }\r\n\r\n // If include patterns are set, URL must match at least one\r\n if (include && include.length > 0) {\r\n if (!matchesPatterns(url, include)) {\r\n return false;\r\n }\r\n }\r\n\r\n // If exclude patterns are set, URL must not match any\r\n if (exclude && exclude.length > 0) {\r\n if (matchesPatterns(url, exclude)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n","/**\r\n * ReplayAPI SDK — Simple logger\r\n */\r\n\r\nlet debugEnabled = false;\r\n\r\nexport function setDebug(enabled: boolean): void {\r\n debugEnabled = enabled;\r\n}\r\n\r\nexport function debug(...args: unknown[]): void {\r\n if (debugEnabled) {\r\n console.log(\"[replayapi]\", ...args);\r\n }\r\n}\r\n\r\nexport function info(...args: unknown[]): void {\r\n console.log(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function warn(...args: unknown[]): void {\r\n console.warn(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function error(...args: unknown[]): void {\r\n console.error(\"[replayapi]\", ...args);\r\n}\r\n","/**\r\n * ReplayAPI SDK — HTTP/HTTPS interceptor\r\n *\r\n * Monkey-patches Node.js http.request and https.request to redirect\r\n * outgoing requests through the ReplayAPI proxy. The proxy captures\r\n * the traffic and forwards it to the original target.\r\n *\r\n * How it works:\r\n * 1. Original request: GET https://api.example.com/users\r\n * 2. SDK rewrites to: GET http://proxy:8080/users\r\n * with headers:\r\n * X-Api-Key: rp_...\r\n * X-Replay-Target: https://api.example.com\r\n * X-Replay-Env: staging\r\n * 3. Proxy captures, forwards to target, returns response transparently\r\n */\r\n\r\nimport http from \"node:http\";\r\nimport https from \"node:https\";\r\nimport { URL } from \"node:url\";\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\n// Store original implementations\r\nlet originalHttpRequest: typeof http.request | null = null;\r\nlet originalHttpGet: typeof http.get | null = null;\r\nlet originalHttpsRequest: typeof https.request | null = null;\r\nlet originalHttpsGet: typeof https.get | null = null;\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\n// Track state\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Parse the various argument formats that http.request accepts:\r\n * - (url: string, options?, callback?)\r\n * - (url: URL, options?, callback?)\r\n * - (options, callback?)\r\n */\r\nfunction parseRequestArgs(\r\n args: unknown[]\r\n): {\r\n targetUrl: string;\r\n options: http.RequestOptions;\r\n callback?: (res: http.IncomingMessage) => void;\r\n} {\r\n let targetUrl: string;\r\n let options: http.RequestOptions = {};\r\n let callback: ((res: http.IncomingMessage) => void) | undefined;\r\n\r\n if (typeof args[0] === \"string\") {\r\n targetUrl = args[0];\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (args[0] instanceof URL) {\r\n targetUrl = args[0].toString();\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (typeof args[0] === \"object\" && args[0] !== null) {\r\n options = args[0] as http.RequestOptions;\r\n const protocol = (options as { protocol?: string }).protocol || \"http:\";\r\n const host = options.hostname || options.host || \"localhost\";\r\n const port = options.port ? `:${options.port}` : \"\";\r\n const path = options.path || \"/\";\r\n targetUrl = `${protocol}//${host}${port}${path}`;\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n }\r\n } else {\r\n targetUrl = \"\";\r\n }\r\n\r\n return { targetUrl, options, callback };\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.request / https.request\r\n */\r\nfunction createProxiedRequest(\r\n originalFn: typeof http.request,\r\n defaultProtocol: \"http:\" | \"https:\"\r\n): typeof http.request {\r\n return function proxiedRequest(\r\n ...args: unknown[]\r\n ): http.ClientRequest {\r\n try {\r\n const { targetUrl, options, callback } = parseRequestArgs(args);\r\n\r\n if (!targetUrl) {\r\n log.debug(\"could not parse request URL, passing through\");\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Parse the proxy URL\r\n const proxyParsed = new URL(config.proxyUrl);\r\n\r\n // Parse the target URL to extract origin and path\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n\r\n // Build proxied options: send to proxy, with X-Replay-Target header\r\n const proxiedOptions: http.RequestOptions = {\r\n ...options,\r\n protocol: proxyParsed.protocol,\r\n hostname: proxyParsed.hostname,\r\n port: proxyParsed.port || (proxyParsed.protocol === \"https:\" ? 443 : 80),\r\n path: parsedTarget.pathname + parsedTarget.search,\r\n headers: {\r\n ...options.headers,\r\n \"X-Api-Key\": config.apiKey,\r\n \"X-Replay-Target\": targetOrigin,\r\n \"X-Replay-Env\": config.environment,\r\n // Preserve the original Host header\r\n Host: parsedTarget.host,\r\n },\r\n };\r\n\r\n // Add session ID if configured\r\n if (config.sessionId) {\r\n (proxiedOptions.headers as Record<string, string>)[\"X-Replay-Session\"] =\r\n config.sessionId;\r\n }\r\n\r\n // Set timeout if configured\r\n if (config.timeout) {\r\n proxiedOptions.timeout = config.timeout;\r\n }\r\n\r\n log.debug(\"capturing:\", options.method || \"GET\", targetUrl, \"→\", config.proxyUrl);\r\n stats.totalCaptured++;\r\n\r\n // Use http.request (not https) since we're talking to the proxy over HTTP\r\n const proxyUrl = new URL(`${proxiedOptions.protocol}//${proxiedOptions.hostname}:${proxiedOptions.port}${proxiedOptions.path}`);\r\n return originalHttpRequest!.call(http, proxyUrl, proxiedOptions, callback);\r\n } catch (err) {\r\n log.error(\"interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n } as typeof http.request;\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.get / https.get\r\n */\r\nfunction createProxiedGet(\r\n proxiedRequest: typeof http.request\r\n): typeof http.get {\r\n return function proxiedGet(...args: unknown[]): http.ClientRequest {\r\n const req = (proxiedRequest as Function)(...args) as http.ClientRequest;\r\n req.end();\r\n return req;\r\n } as typeof http.get;\r\n}\r\n\r\n/**\r\n * Install the HTTP/HTTPS interceptors\r\n */\r\nexport function installHttpInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"interceptor already installed, call stop() first\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n // Save originals\r\n originalHttpRequest = http.request;\r\n originalHttpGet = http.get;\r\n originalHttpsRequest = https.request;\r\n originalHttpsGet = https.get;\r\n\r\n // Patch http\r\n const proxiedHttpRequest = createProxiedRequest(originalHttpRequest, \"http:\");\r\n http.request = proxiedHttpRequest;\r\n http.get = createProxiedGet(proxiedHttpRequest);\r\n\r\n // Patch https\r\n const proxiedHttpsRequest = createProxiedRequest(\r\n originalHttpsRequest,\r\n \"https:\"\r\n );\r\n https.request = proxiedHttpsRequest;\r\n https.get = createProxiedGet(proxiedHttpsRequest);\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"http/https interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the HTTP/HTTPS interceptors\r\n */\r\nexport function uninstallHttpInterceptor(): void {\r\n if (!active) return;\r\n\r\n if (originalHttpRequest) http.request = originalHttpRequest;\r\n if (originalHttpGet) http.get = originalHttpGet;\r\n if (originalHttpsRequest) https.request = originalHttpsRequest;\r\n if (originalHttpsGet) https.get = originalHttpsGet;\r\n\r\n originalHttpRequest = null;\r\n originalHttpGet = null;\r\n originalHttpsRequest = null;\r\n originalHttpsGet = null;\r\n\r\n active = false;\r\n log.debug(\"http/https interceptor removed\");\r\n}\r\n\r\nexport function isHttpInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getHttpStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Global fetch() interceptor\r\n *\r\n * Patches globalThis.fetch to route requests through the ReplayAPI proxy.\r\n * Works with Node.js 18+ native fetch and any polyfills that set globalThis.fetch.\r\n */\r\n\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\nlet originalFetch: typeof globalThis.fetch | null = null;\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Install the fetch interceptor\r\n */\r\nexport function installFetchInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"fetch interceptor already installed\");\r\n return;\r\n }\r\n\r\n // Only patch if fetch exists (Node.js 18+)\r\n if (typeof globalThis.fetch !== \"function\") {\r\n log.debug(\"globalThis.fetch not available, skipping fetch interceptor\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n originalFetch = globalThis.fetch;\r\n\r\n globalThis.fetch = async function proxiedFetch(\r\n input: string | URL | Request,\r\n init?: RequestInit\r\n ): Promise<Response> {\r\n try {\r\n // Resolve the target URL\r\n let targetUrl: string;\r\n if (typeof input === \"string\") {\r\n targetUrl = input;\r\n } else if (input instanceof URL) {\r\n targetUrl = input.toString();\r\n } else if (input instanceof Request) {\r\n targetUrl = input.url;\r\n } else {\r\n targetUrl = String(input);\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"fetch skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n // Parse target URL\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"fetch: invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n const proxyPath = parsedTarget.pathname + parsedTarget.search;\r\n const proxiedUrl = `${config.proxyUrl}${proxyPath}`;\r\n\r\n // Build headers\r\n const headers = new Headers(init?.headers || {});\r\n\r\n // If input is a Request, merge its headers too\r\n if (input instanceof Request) {\r\n input.headers.forEach((value, key) => {\r\n if (!headers.has(key)) {\r\n headers.set(key, value);\r\n }\r\n });\r\n }\r\n\r\n // Add ReplayAPI headers\r\n headers.set(\"X-Api-Key\", config.apiKey);\r\n headers.set(\"X-Replay-Target\", targetOrigin);\r\n headers.set(\"X-Replay-Env\", config.environment);\r\n headers.set(\"Host\", parsedTarget.host);\r\n\r\n if (config.sessionId) {\r\n headers.set(\"X-Replay-Session\", config.sessionId);\r\n }\r\n\r\n // Build proxied init\r\n const proxiedInit: RequestInit = {\r\n ...init,\r\n headers,\r\n };\r\n\r\n // If input is a Request, preserve method and body\r\n if (input instanceof Request) {\r\n proxiedInit.method = proxiedInit.method || input.method;\r\n if (!proxiedInit.body && input.body) {\r\n proxiedInit.body = input.body;\r\n }\r\n }\r\n\r\n log.debug(\r\n \"fetch capturing:\",\r\n proxiedInit.method || \"GET\",\r\n targetUrl,\r\n \"→\",\r\n config.proxyUrl\r\n );\r\n stats.totalCaptured++;\r\n\r\n return originalFetch!(proxiedUrl, proxiedInit);\r\n } catch (err) {\r\n log.error(\"fetch interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return originalFetch!(input, init);\r\n }\r\n };\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"fetch interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the fetch interceptor\r\n */\r\nexport function uninstallFetchInterceptor(): void {\r\n if (!active || !originalFetch) return;\r\n\r\n globalThis.fetch = originalFetch;\r\n originalFetch = null;\r\n active = false;\r\n log.debug(\"fetch interceptor removed\");\r\n}\r\n\r\nexport function isFetchInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getFetchStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Express/Connect middleware\r\n *\r\n * Captures incoming HTTP requests and responses,\r\n * then sends them asynchronously to the ReplayAPI backend.\r\n *\r\n * @example\r\n * ```ts\r\n * import express from 'express';\r\n * import { replayApi } from '@thaparoyal/replayapi';\r\n *\r\n * const app = express();\r\n * replayApi.init('rp_your_api_key');\r\n *\r\n * // Add middleware BEFORE your routes\r\n * app.use(replayApi.middleware());\r\n * ```\r\n */\r\n\r\nimport type { IncomingMessage, ServerResponse } from \"http\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst API_URL = process.env.REPLAY_API_URL || \"http://localhost:8000\";\r\n\r\nlet apiKey: string | null = null;\r\nlet environment = \"development\";\r\nlet captureCount = 0;\r\nlet errorCount = 0;\r\n\r\n/**\r\n * Configure the middleware (called from init)\r\n */\r\nexport function configureMiddleware(key: string, env: string): void {\r\n apiKey = key;\r\n environment = env;\r\n}\r\n\r\n/**\r\n * Map environment string to valid enum value\r\n */\r\nfunction mapEnvironment(env: string): string {\r\n const map: Record<string, string> = {\r\n development: \"dev\",\r\n dev: \"dev\",\r\n local: \"local\",\r\n staging: \"staging\",\r\n production: \"production\",\r\n prod: \"production\",\r\n test: \"test\",\r\n testing: \"test\",\r\n };\r\n return map[env.toLowerCase()] || \"dev\";\r\n}\r\n\r\n/**\r\n * Send captured traffic event to the ReplayAPI backend (fire-and-forget)\r\n */\r\nasync function sendToBackend(event: Record<string, unknown>): Promise<void> {\r\n try {\r\n const url = `${API_URL}/api/ingest`;\r\n // Use native http to avoid intercepting our own calls\r\n const { default: http } = await import(\"http\");\r\n const { default: https } = await import(\"https\");\r\n\r\n const parsedUrl = new URL(url);\r\n const transport = parsedUrl.protocol === \"https:\" ? https : http;\r\n const body = JSON.stringify(event);\r\n\r\n const req = transport.request(\r\n {\r\n hostname: parsedUrl.hostname,\r\n port: parsedUrl.port,\r\n path: parsedUrl.pathname,\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n \"X-API-Key\": apiKey || \"\",\r\n \"Content-Length\": Buffer.byteLength(body),\r\n },\r\n },\r\n (res) => {\r\n // Drain the response\r\n res.resume();\r\n if (res.statusCode && res.statusCode >= 400) {\r\n log.debug(`ingest response: ${res.statusCode}`);\r\n }\r\n }\r\n );\r\n\r\n req.on(\"error\", (err) => {\r\n errorCount++;\r\n log.debug(`ingest error: ${err.message}`);\r\n });\r\n\r\n req.write(body);\r\n req.end();\r\n\r\n captureCount++;\r\n } catch (err) {\r\n errorCount++;\r\n log.debug(`ingest send error: ${(err as Error).message}`);\r\n }\r\n}\r\n\r\n/**\r\n * Collect the response body by intercepting write/end\r\n */\r\nfunction collectResponseBody(\r\n res: ServerResponse,\r\n callback: (body: string) => void\r\n): void {\r\n const chunks: Buffer[] = [];\r\n const originalWrite = res.write.bind(res);\r\n const originalEnd = res.end.bind(res);\r\n\r\n res.write = function (\r\n chunk: unknown,\r\n ...args: unknown[]\r\n ): boolean {\r\n if (chunk) {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\r\n }\r\n return (originalWrite as Function)(chunk, ...args);\r\n };\r\n\r\n res.end = function (\r\n chunk?: unknown,\r\n ...args: unknown[]\r\n ): ServerResponse {\r\n if (chunk && typeof chunk !== \"function\") {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\r\n }\r\n\r\n const body = Buffer.concat(chunks).toString(\"utf-8\");\r\n callback(body);\r\n\r\n return (originalEnd as Function)(chunk, ...args);\r\n };\r\n}\r\n\r\n/**\r\n * Parse JSON body safely\r\n */\r\nfunction tryParseJson(str: string): unknown {\r\n try {\r\n return JSON.parse(str);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Create the Express/Connect middleware function.\r\n *\r\n * Options:\r\n * - `exclude` — URL patterns to skip (e.g. health checks)\r\n * - `maxBodySize` — max body size to capture in bytes (default: 1MB)\r\n */\r\nexport interface MiddlewareOptions {\r\n /** URL patterns to exclude from capture */\r\n exclude?: Array<string | RegExp>;\r\n /** Max body size to capture in bytes (default: 1MB) */\r\n maxBodySize?: number;\r\n}\r\n\r\nexport function createMiddleware(options: MiddlewareOptions = {}) {\r\n const { exclude = [], maxBodySize = 1024 * 1024 } = options;\r\n\r\n return function replayMiddleware(\r\n req: IncomingMessage & { body?: unknown },\r\n res: ServerResponse,\r\n next: (err?: unknown) => void\r\n ): void {\r\n if (!apiKey) {\r\n return next();\r\n }\r\n\r\n const url = req.url || \"/\";\r\n\r\n // Check exclude patterns\r\n for (const pattern of exclude) {\r\n if (typeof pattern === \"string\" && url.includes(pattern)) return next();\r\n if (pattern instanceof RegExp && pattern.test(url)) return next();\r\n }\r\n\r\n const startTime = Date.now();\r\n const startHrTime = process.hrtime.bigint();\r\n\r\n // Collect request info\r\n const method = (req.method || \"GET\").toUpperCase();\r\n const host = (req.headers.host || \"unknown\").split(\":\")[0];\r\n const protocol = (req as unknown as { protocol?: string }).protocol === \"https\" ? \"http\" : \"http\";\r\n\r\n // Parse path and query\r\n let path = url;\r\n let queryParams = {};\r\n const qIndex = url.indexOf(\"?\");\r\n if (qIndex !== -1) {\r\n path = url.substring(0, qIndex);\r\n try {\r\n const searchParams = new URLSearchParams(url.substring(qIndex + 1));\r\n queryParams = Object.fromEntries(searchParams.entries());\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n\r\n // Get request headers (sanitize sensitive ones)\r\n const requestHeaders: Record<string, string> = {};\r\n for (const [key, val] of Object.entries(req.headers)) {\r\n if (val) {\r\n requestHeaders[key] = Array.isArray(val) ? val.join(\", \") : val;\r\n }\r\n }\r\n\r\n // Request body (Express populates req.body after body parsers)\r\n let requestBody: unknown = undefined;\r\n if (req.body !== undefined) {\r\n const bodyStr = typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\r\n if (bodyStr.length <= maxBodySize) {\r\n requestBody = typeof req.body === \"string\" ? tryParseJson(req.body) || req.body : req.body;\r\n }\r\n }\r\n\r\n // Intercept response body\r\n collectResponseBody(res, (responseBodyStr) => {\r\n const durationMs = Number(process.hrtime.bigint() - startHrTime) / 1e6;\r\n const statusCode = res.statusCode;\r\n\r\n // Get response headers\r\n const responseHeaders: Record<string, string> = {};\r\n const rawHeaders = res.getHeaders();\r\n for (const [key, val] of Object.entries(rawHeaders)) {\r\n if (val) {\r\n responseHeaders[key] = Array.isArray(val) ? val.join(\", \") : String(val);\r\n }\r\n }\r\n\r\n // Parse response body\r\n let responseBody: unknown = undefined;\r\n if (responseBodyStr.length <= maxBodySize) {\r\n responseBody = tryParseJson(responseBodyStr) || responseBodyStr;\r\n }\r\n\r\n // Build traffic event\r\n const event = {\r\n method,\r\n protocol,\r\n host,\r\n path,\r\n queryParams,\r\n requestHeaders,\r\n requestBodyInline: requestBody,\r\n requestSizeBytes: requestBody ? Buffer.byteLength(JSON.stringify(requestBody)) : 0,\r\n statusCode,\r\n responseHeaders,\r\n responseBodyInline: responseBody,\r\n responseSizeBytes: responseBodyStr.length,\r\n startedAt: new Date(startTime).toISOString(),\r\n durationMs: Math.round(durationMs * 100) / 100,\r\n source: \"sdk\",\r\n environment: mapEnvironment(environment),\r\n clientIp: req.headers[\"x-forwarded-for\"]\r\n ? String(req.headers[\"x-forwarded-for\"]).split(\",\")[0].trim()\r\n : req.socket?.remoteAddress || null,\r\n tags: [],\r\n metadata: {},\r\n };\r\n\r\n log.debug(`captured: ${method} ${path} ${statusCode} (${event.durationMs}ms)`);\r\n\r\n // Send asynchronously — don't block the response\r\n sendToBackend(event);\r\n });\r\n\r\n next();\r\n };\r\n}\r\n\r\nexport function getMiddlewareStats() {\r\n return { captured: captureCount, errors: errorCount };\r\n}\r\n","/**\r\n * @replayapi/node — ReplayAPI SDK for Node.js\r\n *\r\n * Automatically captures outgoing HTTP traffic and routes it through\r\n * the ReplayAPI proxy for recording, analysis, and replay testing.\r\n *\r\n * @example\r\n * ```ts\r\n * import { replayApi } from '@replayapi/node'\r\n *\r\n * // Simplest — just pass your API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: 'rp_your_api_key', environment: 'staging' })\r\n *\r\n * // All outgoing HTTP/fetch calls are now captured automatically\r\n * ```\r\n */\r\n\r\nimport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nimport {\r\n installHttpInterceptor,\r\n uninstallHttpInterceptor,\r\n isHttpInterceptorActive,\r\n getHttpStats,\r\n} from \"./interceptor-http.js\";\r\nimport {\r\n installFetchInterceptor,\r\n uninstallFetchInterceptor,\r\n isFetchInterceptorActive,\r\n getFetchStats,\r\n} from \"./interceptor-fetch.js\";\r\nimport { configureMiddleware, createMiddleware, getMiddlewareStats } from \"./middleware.js\";\r\nimport type { MiddlewareOptions } from \"./middleware.js\";\r\nimport { setDebug } from \"./logger.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nlet initialized = false;\r\n\r\n/**\r\n * Initialize the ReplayAPI SDK.\r\n *\r\n * Call this once at application startup, before any outgoing HTTP requests.\r\n * The SDK will automatically intercept `http.request`, `https.request`,\r\n * and `fetch` to route traffic through the ReplayAPI proxy.\r\n *\r\n * @example\r\n * ```ts\r\n * // Just an API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: process.env.REPLAY_API_KEY!, environment: 'staging' })\r\n * ```\r\n */\r\nfunction init(configOrApiKey: ReplayApiConfig | string): ReplayApiInstance {\r\n // Accept a plain string as shorthand for { apiKey: string }\r\n const config: ReplayApiConfig =\r\n typeof configOrApiKey === \"string\" ? { apiKey: configOrApiKey } : configOrApiKey;\r\n\r\n if (initialized) {\r\n log.warn(\"replayApi.init() called multiple times — stopping previous instance\");\r\n stop();\r\n }\r\n\r\n // Validate config\r\n if (!config.apiKey) {\r\n throw new Error(\r\n \"[@replayapi/node] apiKey is required. Get one from your ReplayAPI dashboard.\"\r\n );\r\n }\r\n\r\n if (config.disabled) {\r\n log.info(\"SDK disabled via config, skipping initialization\");\r\n return { stop, isActive, getStats };\r\n }\r\n\r\n // Enable debug logging if requested\r\n if (config.debug) {\r\n setDebug(true);\r\n }\r\n\r\n // Resolve environment with env var fallback\r\n config.environment =\r\n config.environment || process.env.REPLAY_ENVIRONMENT || \"development\";\r\n\r\n log.info(`initialized — env: ${config.environment}`);\r\n\r\n // Configure middleware with credentials\r\n configureMiddleware(config.apiKey, config.environment);\r\n\r\n // Install interceptors for outgoing traffic\r\n installHttpInterceptor(config);\r\n installFetchInterceptor(config);\r\n\r\n initialized = true;\r\n\r\n return { stop, isActive, getStats };\r\n}\r\n\r\n/**\r\n * Stop the SDK and restore original HTTP/fetch behavior.\r\n */\r\nfunction stop(): void {\r\n if (!initialized) return;\r\n\r\n uninstallHttpInterceptor();\r\n uninstallFetchInterceptor();\r\n\r\n initialized = false;\r\n log.info(\"stopped — all interceptors removed\");\r\n}\r\n\r\n/**\r\n * Check if the SDK is currently intercepting traffic.\r\n */\r\nfunction isActive(): boolean {\r\n return isHttpInterceptorActive() || isFetchInterceptorActive();\r\n}\r\n\r\n/**\r\n * Get combined capture statistics from all interceptors.\r\n */\r\nfunction getStats(): CaptureStats {\r\n const httpStats = getHttpStats();\r\n const fetchStats = getFetchStats();\r\n\r\n return {\r\n totalCaptured: httpStats.totalCaptured + fetchStats.totalCaptured,\r\n totalSkipped: httpStats.totalSkipped + fetchStats.totalSkipped,\r\n totalErrors: httpStats.totalErrors + fetchStats.totalErrors,\r\n startedAt: httpStats.startedAt < fetchStats.startedAt\r\n ? httpStats.startedAt\r\n : fetchStats.startedAt,\r\n };\r\n}\r\n\r\n/**\r\n * The main ReplayAPI SDK instance.\r\n */\r\nexport const replayApi = {\r\n init,\r\n stop,\r\n isActive,\r\n getStats,\r\n middleware: createMiddleware,\r\n};\r\n\r\n// Named exports for flexibility\r\nexport { init, stop, isActive, getStats, createMiddleware };\r\n\r\n// Re-export types\r\nexport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nexport type { MiddlewareOptions } from \"./middleware.js\";\r\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -356,6 +356,176 @@ function getFetchStats() {
|
|
|
356
356
|
return { ...stats2 };
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
// src/middleware.ts
|
|
360
|
+
var API_URL = process.env.REPLAY_API_URL || "http://localhost:8000";
|
|
361
|
+
var apiKey = null;
|
|
362
|
+
var environment = "development";
|
|
363
|
+
var captureCount = 0;
|
|
364
|
+
var errorCount = 0;
|
|
365
|
+
function configureMiddleware(key, env) {
|
|
366
|
+
apiKey = key;
|
|
367
|
+
environment = env;
|
|
368
|
+
}
|
|
369
|
+
function mapEnvironment(env) {
|
|
370
|
+
const map = {
|
|
371
|
+
development: "dev",
|
|
372
|
+
dev: "dev",
|
|
373
|
+
local: "local",
|
|
374
|
+
staging: "staging",
|
|
375
|
+
production: "production",
|
|
376
|
+
prod: "production",
|
|
377
|
+
test: "test",
|
|
378
|
+
testing: "test"
|
|
379
|
+
};
|
|
380
|
+
return map[env.toLowerCase()] || "dev";
|
|
381
|
+
}
|
|
382
|
+
async function sendToBackend(event) {
|
|
383
|
+
try {
|
|
384
|
+
const url = `${API_URL}/api/ingest`;
|
|
385
|
+
const { default: http2 } = await import('http');
|
|
386
|
+
const { default: https2 } = await import('https');
|
|
387
|
+
const parsedUrl = new URL(url);
|
|
388
|
+
const transport = parsedUrl.protocol === "https:" ? https2 : http2;
|
|
389
|
+
const body = JSON.stringify(event);
|
|
390
|
+
const req = transport.request(
|
|
391
|
+
{
|
|
392
|
+
hostname: parsedUrl.hostname,
|
|
393
|
+
port: parsedUrl.port,
|
|
394
|
+
path: parsedUrl.pathname,
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: {
|
|
397
|
+
"Content-Type": "application/json",
|
|
398
|
+
"X-API-Key": apiKey || "",
|
|
399
|
+
"Content-Length": Buffer.byteLength(body)
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
(res) => {
|
|
403
|
+
res.resume();
|
|
404
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
405
|
+
debug(`ingest response: ${res.statusCode}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
req.on("error", (err) => {
|
|
410
|
+
errorCount++;
|
|
411
|
+
debug(`ingest error: ${err.message}`);
|
|
412
|
+
});
|
|
413
|
+
req.write(body);
|
|
414
|
+
req.end();
|
|
415
|
+
captureCount++;
|
|
416
|
+
} catch (err) {
|
|
417
|
+
errorCount++;
|
|
418
|
+
debug(`ingest send error: ${err.message}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function collectResponseBody(res, callback) {
|
|
422
|
+
const chunks = [];
|
|
423
|
+
const originalWrite = res.write.bind(res);
|
|
424
|
+
const originalEnd = res.end.bind(res);
|
|
425
|
+
res.write = function(chunk, ...args) {
|
|
426
|
+
if (chunk) {
|
|
427
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
428
|
+
}
|
|
429
|
+
return originalWrite(chunk, ...args);
|
|
430
|
+
};
|
|
431
|
+
res.end = function(chunk, ...args) {
|
|
432
|
+
if (chunk && typeof chunk !== "function") {
|
|
433
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
434
|
+
}
|
|
435
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
436
|
+
callback(body);
|
|
437
|
+
return originalEnd(chunk, ...args);
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function tryParseJson(str) {
|
|
441
|
+
try {
|
|
442
|
+
return JSON.parse(str);
|
|
443
|
+
} catch {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function createMiddleware(options = {}) {
|
|
448
|
+
const { exclude = [], maxBodySize = 1024 * 1024 } = options;
|
|
449
|
+
return function replayMiddleware(req, res, next) {
|
|
450
|
+
if (!apiKey) {
|
|
451
|
+
return next();
|
|
452
|
+
}
|
|
453
|
+
const url = req.url || "/";
|
|
454
|
+
for (const pattern of exclude) {
|
|
455
|
+
if (typeof pattern === "string" && url.includes(pattern)) return next();
|
|
456
|
+
if (pattern instanceof RegExp && pattern.test(url)) return next();
|
|
457
|
+
}
|
|
458
|
+
const startTime = Date.now();
|
|
459
|
+
const startHrTime = process.hrtime.bigint();
|
|
460
|
+
const method = (req.method || "GET").toUpperCase();
|
|
461
|
+
const host = (req.headers.host || "unknown").split(":")[0];
|
|
462
|
+
const protocol = req.protocol === "https" ? "http" : "http";
|
|
463
|
+
let path = url;
|
|
464
|
+
let queryParams = {};
|
|
465
|
+
const qIndex = url.indexOf("?");
|
|
466
|
+
if (qIndex !== -1) {
|
|
467
|
+
path = url.substring(0, qIndex);
|
|
468
|
+
try {
|
|
469
|
+
const searchParams = new URLSearchParams(url.substring(qIndex + 1));
|
|
470
|
+
queryParams = Object.fromEntries(searchParams.entries());
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const requestHeaders = {};
|
|
475
|
+
for (const [key, val] of Object.entries(req.headers)) {
|
|
476
|
+
if (val) {
|
|
477
|
+
requestHeaders[key] = Array.isArray(val) ? val.join(", ") : val;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
let requestBody = void 0;
|
|
481
|
+
if (req.body !== void 0) {
|
|
482
|
+
const bodyStr = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
|
483
|
+
if (bodyStr.length <= maxBodySize) {
|
|
484
|
+
requestBody = typeof req.body === "string" ? tryParseJson(req.body) || req.body : req.body;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
collectResponseBody(res, (responseBodyStr) => {
|
|
488
|
+
const durationMs = Number(process.hrtime.bigint() - startHrTime) / 1e6;
|
|
489
|
+
const statusCode = res.statusCode;
|
|
490
|
+
const responseHeaders = {};
|
|
491
|
+
const rawHeaders = res.getHeaders();
|
|
492
|
+
for (const [key, val] of Object.entries(rawHeaders)) {
|
|
493
|
+
if (val) {
|
|
494
|
+
responseHeaders[key] = Array.isArray(val) ? val.join(", ") : String(val);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
let responseBody = void 0;
|
|
498
|
+
if (responseBodyStr.length <= maxBodySize) {
|
|
499
|
+
responseBody = tryParseJson(responseBodyStr) || responseBodyStr;
|
|
500
|
+
}
|
|
501
|
+
const event = {
|
|
502
|
+
method,
|
|
503
|
+
protocol,
|
|
504
|
+
host,
|
|
505
|
+
path,
|
|
506
|
+
queryParams,
|
|
507
|
+
requestHeaders,
|
|
508
|
+
requestBodyInline: requestBody,
|
|
509
|
+
requestSizeBytes: requestBody ? Buffer.byteLength(JSON.stringify(requestBody)) : 0,
|
|
510
|
+
statusCode,
|
|
511
|
+
responseHeaders,
|
|
512
|
+
responseBodyInline: responseBody,
|
|
513
|
+
responseSizeBytes: responseBodyStr.length,
|
|
514
|
+
startedAt: new Date(startTime).toISOString(),
|
|
515
|
+
durationMs: Math.round(durationMs * 100) / 100,
|
|
516
|
+
source: "sdk",
|
|
517
|
+
environment: mapEnvironment(environment),
|
|
518
|
+
clientIp: req.headers["x-forwarded-for"] ? String(req.headers["x-forwarded-for"]).split(",")[0].trim() : req.socket?.remoteAddress || null,
|
|
519
|
+
tags: [],
|
|
520
|
+
metadata: {}
|
|
521
|
+
};
|
|
522
|
+
debug(`captured: ${method} ${path} ${statusCode} (${event.durationMs}ms)`);
|
|
523
|
+
sendToBackend(event);
|
|
524
|
+
});
|
|
525
|
+
next();
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
359
529
|
// src/index.ts
|
|
360
530
|
var initialized = false;
|
|
361
531
|
function init(configOrApiKey) {
|
|
@@ -378,6 +548,7 @@ function init(configOrApiKey) {
|
|
|
378
548
|
}
|
|
379
549
|
config3.environment = config3.environment || process.env.REPLAY_ENVIRONMENT || "development";
|
|
380
550
|
info(`initialized \u2014 env: ${config3.environment}`);
|
|
551
|
+
configureMiddleware(config3.apiKey, config3.environment);
|
|
381
552
|
installHttpInterceptor(config3);
|
|
382
553
|
installFetchInterceptor(config3);
|
|
383
554
|
initialized = true;
|
|
@@ -407,9 +578,10 @@ var replayApi = {
|
|
|
407
578
|
init,
|
|
408
579
|
stop,
|
|
409
580
|
isActive,
|
|
410
|
-
getStats
|
|
581
|
+
getStats,
|
|
582
|
+
middleware: createMiddleware
|
|
411
583
|
};
|
|
412
584
|
|
|
413
|
-
export { getStats, init, isActive, replayApi, stop };
|
|
585
|
+
export { createMiddleware, getStats, init, isActive, replayApi, stop };
|
|
414
586
|
//# sourceMappingURL=index.mjs.map
|
|
415
587
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/matcher.ts","../src/logger.ts","../src/interceptor-http.ts","../src/interceptor-fetch.ts","../src/index.ts"],"names":["URL","PROXY_URL","active","config","stats","init"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * ReplayAPI SDK — URL matching utilities\r\n */\r\n\r\n/**\r\n * Check if a URL matches any of the given patterns.\r\n * Patterns can be strings (substring match) or RegExp.\r\n */\r\nexport function matchesPatterns(\r\n url: string,\r\n patterns: Array<string | RegExp>\r\n): boolean {\r\n return patterns.some((pattern) => {\r\n if (typeof pattern === \"string\") {\r\n return url.includes(pattern);\r\n }\r\n return pattern.test(url);\r\n });\r\n}\r\n\r\n/**\r\n * Determine if a request URL should be captured based on include/exclude filters.\r\n */\r\nexport function shouldCapture(\r\n url: string,\r\n proxyUrl: string,\r\n include?: Array<string | RegExp>,\r\n exclude?: Array<string | RegExp>\r\n): boolean {\r\n // Never capture requests to the proxy itself\r\n if (url.startsWith(proxyUrl)) {\r\n return false;\r\n }\r\n\r\n // Never capture requests to ReplayAPI internal paths\r\n if (url.includes(\"/__replay/\")) {\r\n return false;\r\n }\r\n\r\n // If include patterns are set, URL must match at least one\r\n if (include && include.length > 0) {\r\n if (!matchesPatterns(url, include)) {\r\n return false;\r\n }\r\n }\r\n\r\n // If exclude patterns are set, URL must not match any\r\n if (exclude && exclude.length > 0) {\r\n if (matchesPatterns(url, exclude)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n","/**\r\n * ReplayAPI SDK — Simple logger\r\n */\r\n\r\nlet debugEnabled = false;\r\n\r\nexport function setDebug(enabled: boolean): void {\r\n debugEnabled = enabled;\r\n}\r\n\r\nexport function debug(...args: unknown[]): void {\r\n if (debugEnabled) {\r\n console.log(\"[replayapi]\", ...args);\r\n }\r\n}\r\n\r\nexport function info(...args: unknown[]): void {\r\n console.log(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function warn(...args: unknown[]): void {\r\n console.warn(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function error(...args: unknown[]): void {\r\n console.error(\"[replayapi]\", ...args);\r\n}\r\n","/**\r\n * ReplayAPI SDK — HTTP/HTTPS interceptor\r\n *\r\n * Monkey-patches Node.js http.request and https.request to redirect\r\n * outgoing requests through the ReplayAPI proxy. The proxy captures\r\n * the traffic and forwards it to the original target.\r\n *\r\n * How it works:\r\n * 1. Original request: GET https://api.example.com/users\r\n * 2. SDK rewrites to: GET http://proxy:8080/users\r\n * with headers:\r\n * X-Api-Key: rp_...\r\n * X-Replay-Target: https://api.example.com\r\n * X-Replay-Env: staging\r\n * 3. Proxy captures, forwards to target, returns response transparently\r\n */\r\n\r\nimport http from \"node:http\";\r\nimport https from \"node:https\";\r\nimport { URL } from \"node:url\";\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\n// Store original implementations\r\nlet originalHttpRequest: typeof http.request | null = null;\r\nlet originalHttpGet: typeof http.get | null = null;\r\nlet originalHttpsRequest: typeof https.request | null = null;\r\nlet originalHttpsGet: typeof https.get | null = null;\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\n// Track state\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Parse the various argument formats that http.request accepts:\r\n * - (url: string, options?, callback?)\r\n * - (url: URL, options?, callback?)\r\n * - (options, callback?)\r\n */\r\nfunction parseRequestArgs(\r\n args: unknown[]\r\n): {\r\n targetUrl: string;\r\n options: http.RequestOptions;\r\n callback?: (res: http.IncomingMessage) => void;\r\n} {\r\n let targetUrl: string;\r\n let options: http.RequestOptions = {};\r\n let callback: ((res: http.IncomingMessage) => void) | undefined;\r\n\r\n if (typeof args[0] === \"string\") {\r\n targetUrl = args[0];\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (args[0] instanceof URL) {\r\n targetUrl = args[0].toString();\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (typeof args[0] === \"object\" && args[0] !== null) {\r\n options = args[0] as http.RequestOptions;\r\n const protocol = (options as { protocol?: string }).protocol || \"http:\";\r\n const host = options.hostname || options.host || \"localhost\";\r\n const port = options.port ? `:${options.port}` : \"\";\r\n const path = options.path || \"/\";\r\n targetUrl = `${protocol}//${host}${port}${path}`;\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n }\r\n } else {\r\n targetUrl = \"\";\r\n }\r\n\r\n return { targetUrl, options, callback };\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.request / https.request\r\n */\r\nfunction createProxiedRequest(\r\n originalFn: typeof http.request,\r\n defaultProtocol: \"http:\" | \"https:\"\r\n): typeof http.request {\r\n return function proxiedRequest(\r\n ...args: unknown[]\r\n ): http.ClientRequest {\r\n try {\r\n const { targetUrl, options, callback } = parseRequestArgs(args);\r\n\r\n if (!targetUrl) {\r\n log.debug(\"could not parse request URL, passing through\");\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Parse the proxy URL\r\n const proxyParsed = new URL(config.proxyUrl);\r\n\r\n // Parse the target URL to extract origin and path\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n\r\n // Build proxied options: send to proxy, with X-Replay-Target header\r\n const proxiedOptions: http.RequestOptions = {\r\n ...options,\r\n protocol: proxyParsed.protocol,\r\n hostname: proxyParsed.hostname,\r\n port: proxyParsed.port || (proxyParsed.protocol === \"https:\" ? 443 : 80),\r\n path: parsedTarget.pathname + parsedTarget.search,\r\n headers: {\r\n ...options.headers,\r\n \"X-Api-Key\": config.apiKey,\r\n \"X-Replay-Target\": targetOrigin,\r\n \"X-Replay-Env\": config.environment,\r\n // Preserve the original Host header\r\n Host: parsedTarget.host,\r\n },\r\n };\r\n\r\n // Add session ID if configured\r\n if (config.sessionId) {\r\n (proxiedOptions.headers as Record<string, string>)[\"X-Replay-Session\"] =\r\n config.sessionId;\r\n }\r\n\r\n // Set timeout if configured\r\n if (config.timeout) {\r\n proxiedOptions.timeout = config.timeout;\r\n }\r\n\r\n log.debug(\"capturing:\", options.method || \"GET\", targetUrl, \"→\", config.proxyUrl);\r\n stats.totalCaptured++;\r\n\r\n // Use http.request (not https) since we're talking to the proxy over HTTP\r\n const proxyUrl = new URL(`${proxiedOptions.protocol}//${proxiedOptions.hostname}:${proxiedOptions.port}${proxiedOptions.path}`);\r\n return originalHttpRequest!.call(http, proxyUrl, proxiedOptions, callback);\r\n } catch (err) {\r\n log.error(\"interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n } as typeof http.request;\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.get / https.get\r\n */\r\nfunction createProxiedGet(\r\n proxiedRequest: typeof http.request\r\n): typeof http.get {\r\n return function proxiedGet(...args: unknown[]): http.ClientRequest {\r\n const req = (proxiedRequest as Function)(...args) as http.ClientRequest;\r\n req.end();\r\n return req;\r\n } as typeof http.get;\r\n}\r\n\r\n/**\r\n * Install the HTTP/HTTPS interceptors\r\n */\r\nexport function installHttpInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"interceptor already installed, call stop() first\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n // Save originals\r\n originalHttpRequest = http.request;\r\n originalHttpGet = http.get;\r\n originalHttpsRequest = https.request;\r\n originalHttpsGet = https.get;\r\n\r\n // Patch http\r\n const proxiedHttpRequest = createProxiedRequest(originalHttpRequest, \"http:\");\r\n http.request = proxiedHttpRequest;\r\n http.get = createProxiedGet(proxiedHttpRequest);\r\n\r\n // Patch https\r\n const proxiedHttpsRequest = createProxiedRequest(\r\n originalHttpsRequest,\r\n \"https:\"\r\n );\r\n https.request = proxiedHttpsRequest;\r\n https.get = createProxiedGet(proxiedHttpsRequest);\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"http/https interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the HTTP/HTTPS interceptors\r\n */\r\nexport function uninstallHttpInterceptor(): void {\r\n if (!active) return;\r\n\r\n if (originalHttpRequest) http.request = originalHttpRequest;\r\n if (originalHttpGet) http.get = originalHttpGet;\r\n if (originalHttpsRequest) https.request = originalHttpsRequest;\r\n if (originalHttpsGet) https.get = originalHttpsGet;\r\n\r\n originalHttpRequest = null;\r\n originalHttpGet = null;\r\n originalHttpsRequest = null;\r\n originalHttpsGet = null;\r\n\r\n active = false;\r\n log.debug(\"http/https interceptor removed\");\r\n}\r\n\r\nexport function isHttpInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getHttpStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Global fetch() interceptor\r\n *\r\n * Patches globalThis.fetch to route requests through the ReplayAPI proxy.\r\n * Works with Node.js 18+ native fetch and any polyfills that set globalThis.fetch.\r\n */\r\n\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\nlet originalFetch: typeof globalThis.fetch | null = null;\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Install the fetch interceptor\r\n */\r\nexport function installFetchInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"fetch interceptor already installed\");\r\n return;\r\n }\r\n\r\n // Only patch if fetch exists (Node.js 18+)\r\n if (typeof globalThis.fetch !== \"function\") {\r\n log.debug(\"globalThis.fetch not available, skipping fetch interceptor\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n originalFetch = globalThis.fetch;\r\n\r\n globalThis.fetch = async function proxiedFetch(\r\n input: string | URL | Request,\r\n init?: RequestInit\r\n ): Promise<Response> {\r\n try {\r\n // Resolve the target URL\r\n let targetUrl: string;\r\n if (typeof input === \"string\") {\r\n targetUrl = input;\r\n } else if (input instanceof URL) {\r\n targetUrl = input.toString();\r\n } else if (input instanceof Request) {\r\n targetUrl = input.url;\r\n } else {\r\n targetUrl = String(input);\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"fetch skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n // Parse target URL\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"fetch: invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n const proxyPath = parsedTarget.pathname + parsedTarget.search;\r\n const proxiedUrl = `${config.proxyUrl}${proxyPath}`;\r\n\r\n // Build headers\r\n const headers = new Headers(init?.headers || {});\r\n\r\n // If input is a Request, merge its headers too\r\n if (input instanceof Request) {\r\n input.headers.forEach((value, key) => {\r\n if (!headers.has(key)) {\r\n headers.set(key, value);\r\n }\r\n });\r\n }\r\n\r\n // Add ReplayAPI headers\r\n headers.set(\"X-Api-Key\", config.apiKey);\r\n headers.set(\"X-Replay-Target\", targetOrigin);\r\n headers.set(\"X-Replay-Env\", config.environment);\r\n headers.set(\"Host\", parsedTarget.host);\r\n\r\n if (config.sessionId) {\r\n headers.set(\"X-Replay-Session\", config.sessionId);\r\n }\r\n\r\n // Build proxied init\r\n const proxiedInit: RequestInit = {\r\n ...init,\r\n headers,\r\n };\r\n\r\n // If input is a Request, preserve method and body\r\n if (input instanceof Request) {\r\n proxiedInit.method = proxiedInit.method || input.method;\r\n if (!proxiedInit.body && input.body) {\r\n proxiedInit.body = input.body;\r\n }\r\n }\r\n\r\n log.debug(\r\n \"fetch capturing:\",\r\n proxiedInit.method || \"GET\",\r\n targetUrl,\r\n \"→\",\r\n config.proxyUrl\r\n );\r\n stats.totalCaptured++;\r\n\r\n return originalFetch!(proxiedUrl, proxiedInit);\r\n } catch (err) {\r\n log.error(\"fetch interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return originalFetch!(input, init);\r\n }\r\n };\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"fetch interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the fetch interceptor\r\n */\r\nexport function uninstallFetchInterceptor(): void {\r\n if (!active || !originalFetch) return;\r\n\r\n globalThis.fetch = originalFetch;\r\n originalFetch = null;\r\n active = false;\r\n log.debug(\"fetch interceptor removed\");\r\n}\r\n\r\nexport function isFetchInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getFetchStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * @replayapi/node — ReplayAPI SDK for Node.js\r\n *\r\n * Automatically captures outgoing HTTP traffic and routes it through\r\n * the ReplayAPI proxy for recording, analysis, and replay testing.\r\n *\r\n * @example\r\n * ```ts\r\n * import { replayApi } from '@replayapi/node'\r\n *\r\n * // Simplest — just pass your API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: 'rp_your_api_key', environment: 'staging' })\r\n *\r\n * // All outgoing HTTP/fetch calls are now captured automatically\r\n * ```\r\n */\r\n\r\nimport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nimport {\r\n installHttpInterceptor,\r\n uninstallHttpInterceptor,\r\n isHttpInterceptorActive,\r\n getHttpStats,\r\n} from \"./interceptor-http.js\";\r\nimport {\r\n installFetchInterceptor,\r\n uninstallFetchInterceptor,\r\n isFetchInterceptorActive,\r\n getFetchStats,\r\n} from \"./interceptor-fetch.js\";\r\nimport { setDebug } from \"./logger.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nlet initialized = false;\r\n\r\n/**\r\n * Initialize the ReplayAPI SDK.\r\n *\r\n * Call this once at application startup, before any outgoing HTTP requests.\r\n * The SDK will automatically intercept `http.request`, `https.request`,\r\n * and `fetch` to route traffic through the ReplayAPI proxy.\r\n *\r\n * @example\r\n * ```ts\r\n * // Just an API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: process.env.REPLAY_API_KEY!, environment: 'staging' })\r\n * ```\r\n */\r\nfunction init(configOrApiKey: ReplayApiConfig | string): ReplayApiInstance {\r\n // Accept a plain string as shorthand for { apiKey: string }\r\n const config: ReplayApiConfig =\r\n typeof configOrApiKey === \"string\" ? { apiKey: configOrApiKey } : configOrApiKey;\r\n\r\n if (initialized) {\r\n log.warn(\"replayApi.init() called multiple times — stopping previous instance\");\r\n stop();\r\n }\r\n\r\n // Validate config\r\n if (!config.apiKey) {\r\n throw new Error(\r\n \"[@replayapi/node] apiKey is required. Get one from your ReplayAPI dashboard.\"\r\n );\r\n }\r\n\r\n if (config.disabled) {\r\n log.info(\"SDK disabled via config, skipping initialization\");\r\n return { stop, isActive, getStats };\r\n }\r\n\r\n // Enable debug logging if requested\r\n if (config.debug) {\r\n setDebug(true);\r\n }\r\n\r\n // Resolve environment with env var fallback\r\n config.environment =\r\n config.environment || process.env.REPLAY_ENVIRONMENT || \"development\";\r\n\r\n log.info(`initialized — env: ${config.environment}`);\r\n\r\n // Install interceptors\r\n installHttpInterceptor(config);\r\n installFetchInterceptor(config);\r\n\r\n initialized = true;\r\n\r\n return { stop, isActive, getStats };\r\n}\r\n\r\n/**\r\n * Stop the SDK and restore original HTTP/fetch behavior.\r\n */\r\nfunction stop(): void {\r\n if (!initialized) return;\r\n\r\n uninstallHttpInterceptor();\r\n uninstallFetchInterceptor();\r\n\r\n initialized = false;\r\n log.info(\"stopped — all interceptors removed\");\r\n}\r\n\r\n/**\r\n * Check if the SDK is currently intercepting traffic.\r\n */\r\nfunction isActive(): boolean {\r\n return isHttpInterceptorActive() || isFetchInterceptorActive();\r\n}\r\n\r\n/**\r\n * Get combined capture statistics from all interceptors.\r\n */\r\nfunction getStats(): CaptureStats {\r\n const httpStats = getHttpStats();\r\n const fetchStats = getFetchStats();\r\n\r\n return {\r\n totalCaptured: httpStats.totalCaptured + fetchStats.totalCaptured,\r\n totalSkipped: httpStats.totalSkipped + fetchStats.totalSkipped,\r\n totalErrors: httpStats.totalErrors + fetchStats.totalErrors,\r\n startedAt: httpStats.startedAt < fetchStats.startedAt\r\n ? httpStats.startedAt\r\n : fetchStats.startedAt,\r\n };\r\n}\r\n\r\n/**\r\n * The main ReplayAPI SDK instance.\r\n */\r\nexport const replayApi = {\r\n init,\r\n stop,\r\n isActive,\r\n getStats,\r\n};\r\n\r\n// Named exports for flexibility\r\nexport { init, stop, isActive, getStats };\r\n\r\n// Re-export types\r\nexport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/matcher.ts","../src/logger.ts","../src/interceptor-http.ts","../src/interceptor-fetch.ts","../src/middleware.ts","../src/index.ts"],"names":["URL","PROXY_URL","active","config","stats","init","http","https"],"mappings":";;;;;;;AAQO,SAAS,eAAA,CACd,KACA,QAAA,EACS;AACT,EAAA,OAAO,QAAA,CAAS,IAAA,CAAK,CAAC,OAAA,KAAY;AAChC,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA,OAAO,GAAA,CAAI,SAAS,OAAO,CAAA;AAAA,IAC7B;AACA,IAAA,OAAO,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,EACzB,CAAC,CAAA;AACH;AAKO,SAAS,aAAA,CACd,GAAA,EACA,QAAA,EACA,OAAA,EACA,OAAA,EACS;AAET,EAAA,IAAI,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA,EAAG;AAC9B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACjC,IAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,EAAK,OAAO,CAAA,EAAG;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACjC,IAAA,IAAI,eAAA,CAAgB,GAAA,EAAK,OAAO,CAAA,EAAG;AACjC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;;;AClDA,IAAI,YAAA,GAAe,KAAA;AAEZ,SAAS,SAAS,OAAA,EAAwB;AAC/C,EAAA,YAAA,GAAe,OAAA;AACjB;AAEO,SAAS,SAAS,IAAA,EAAuB;AAC9C,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,OAAA,CAAQ,GAAA,CAAI,aAAA,EAAe,GAAG,IAAI,CAAA;AAAA,EACpC;AACF;AAEO,SAAS,QAAQ,IAAA,EAAuB;AAC7C,EAAA,OAAA,CAAQ,GAAA,CAAI,aAAA,EAAe,GAAG,IAAI,CAAA;AACpC;AAEO,SAAS,QAAQ,IAAA,EAAuB;AAC7C,EAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,GAAG,IAAI,CAAA;AACrC;AAEO,SAAS,SAAS,IAAA,EAAuB;AAC9C,EAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,EAAe,GAAG,IAAI,CAAA;AACtC;;;ACDA,IAAI,mBAAA,GAAkD,IAAA;AACtD,IAAI,eAAA,GAA0C,IAAA;AAC9C,IAAI,oBAAA,GAAoD,IAAA;AACxD,IAAI,gBAAA,GAA4C,IAAA;AAEhD,IAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,gBAAA,IAAoB,uBAAA;AAGlD,IAAI,MAAA,GAAS,KAAA;AACb,IAAI,MAAA;AAKJ,IAAM,KAAA,GAAsB;AAAA,EAC1B,aAAA,EAAe,CAAA;AAAA,EACf,YAAA,EAAc,CAAA;AAAA,EACd,WAAA,EAAa,CAAA;AAAA,EACb,SAAA,sBAAe,IAAA;AACjB,CAAA;AAQA,SAAS,iBACP,IAAA,EAKA;AACA,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,UAA+B,EAAC;AACpC,EAAA,IAAI,QAAA;AAEJ,EAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,EAAU;AAC/B,IAAA,SAAA,GAAY,KAAK,CAAC,CAAA;AAClB,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,MAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,MAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,QAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,IAAA,CAAK,CAAC,CAAA,YAAaA,KAAAA,EAAK;AACjC,IAAA,SAAA,GAAY,IAAA,CAAK,CAAC,CAAA,CAAE,QAAA,EAAS;AAC7B,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,MAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,MAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,QAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,OAAO,IAAA,CAAK,CAAC,MAAM,QAAA,IAAY,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AAC1D,IAAA,OAAA,GAAU,KAAK,CAAC,CAAA;AAChB,IAAA,MAAM,QAAA,GAAY,QAAkC,QAAA,IAAY,OAAA;AAChE,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,QAAA,IAAY,OAAA,CAAQ,IAAA,IAAQ,WAAA;AACjD,IAAA,MAAM,OAAO,OAAA,CAAQ,IAAA,GAAO,CAAA,CAAA,EAAI,OAAA,CAAQ,IAAI,CAAA,CAAA,GAAK,EAAA;AACjD,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,GAAA;AAC7B,IAAA,SAAA,GAAY,GAAG,QAAQ,CAAA,EAAA,EAAK,IAAI,CAAA,EAAG,IAAI,GAAG,IAAI,CAAA,CAAA;AAC9C,IAAA,IAAI,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,UAAA,EAAY;AACjC,MAAA,QAAA,GAAW,KAAK,CAAC,CAAA;AAAA,IACnB;AAAA,EACF,CAAA,MAAO;AACL,IAAA,SAAA,GAAY,EAAA;AAAA,EACd;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,QAAA,EAAS;AACxC;AAKA,SAAS,oBAAA,CACP,YACA,eAAA,EACqB;AACrB,EAAA,OAAO,SAAS,kBACX,IAAA,EACiB;AACpB,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,QAAA,EAAS,GAAI,iBAAiB,IAAI,CAAA;AAE9D,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAI,MAAM,8CAA8C,CAAA;AACxD,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAW,KAAA,GAAQ,IAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAGA,MAAA,IACE,CAAC,aAAA;AAAA,QACC,SAAA;AAAA,QACA,MAAA,CAAO,QAAA;AAAA,QACP,MAAA,CAAO,OAAA;AAAA,QACP,MAAA,CAAO;AAAA,OACT,EACA;AACA,QAAI,KAAA,CAAM,wBAAwB,SAAS,CAAA;AAC3C,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAW,KAAA,GAAQ,IAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAGA,MAAA,MAAM,WAAA,GAAc,IAAIA,KAAAA,CAAI,MAAA,CAAO,QAAQ,CAAA;AAG3C,MAAA,IAAI,YAAA;AACJ,MAAA,IAAI;AACF,QAAA,YAAA,GAAe,IAAIA,MAAI,SAAS,CAAA;AAAA,MAClC,CAAA,CAAA,MAAQ;AACN,QAAI,KAAA,CAAM,iCAAiC,SAAS,CAAA;AACpD,QAAA,KAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,UAC9B,eAAA,KAAoB,WAAW,KAAA,GAAQ,IAAA;AAAA,UACvC;AAAA,SACF;AAAA,MACF;AAEA,MAAA,MAAM,eAAe,CAAA,EAAG,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK,aAAa,IAAI,CAAA,CAAA;AAGnE,MAAA,MAAM,cAAA,GAAsC;AAAA,QAC1C,GAAG,OAAA;AAAA,QACH,UAAU,WAAA,CAAY,QAAA;AAAA,QACtB,UAAU,WAAA,CAAY,QAAA;AAAA,QACtB,MAAM,WAAA,CAAY,IAAA,KAAS,WAAA,CAAY,QAAA,KAAa,WAAW,GAAA,GAAM,EAAA,CAAA;AAAA,QACrE,IAAA,EAAM,YAAA,CAAa,QAAA,GAAW,YAAA,CAAa,MAAA;AAAA,QAC3C,OAAA,EAAS;AAAA,UACP,GAAG,OAAA,CAAQ,OAAA;AAAA,UACX,aAAa,MAAA,CAAO,MAAA;AAAA,UACpB,iBAAA,EAAmB,YAAA;AAAA,UACnB,gBAAgB,MAAA,CAAO,WAAA;AAAA;AAAA,UAEvB,MAAM,YAAA,CAAa;AAAA;AACrB,OACF;AAGA,MAAA,IAAI,OAAO,SAAA,EAAW;AACpB,QAAC,cAAA,CAAe,OAAA,CAAmC,kBAAkB,CAAA,GACnE,MAAA,CAAO,SAAA;AAAA,MACX;AAGA,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,cAAA,CAAe,UAAU,MAAA,CAAO,OAAA;AAAA,MAClC;AAEA,MAAI,KAAA,CAAM,cAAc,OAAA,CAAQ,MAAA,IAAU,OAAO,SAAA,EAAW,QAAA,EAAK,OAAO,QAAQ,CAAA;AAChF,MAAA,KAAA,CAAM,aAAA,EAAA;AAGN,MAAA,MAAM,QAAA,GAAW,IAAIA,KAAAA,CAAI,CAAA,EAAG,eAAe,QAAQ,CAAA,EAAA,EAAK,cAAA,CAAe,QAAQ,IAAI,cAAA,CAAe,IAAI,CAAA,EAAG,cAAA,CAAe,IAAI,CAAA,CAAE,CAAA;AAC9H,MAAA,OAAO,mBAAA,CAAqB,IAAA,CAAK,IAAA,EAAM,QAAA,EAAU,gBAAgB,QAAQ,CAAA;AAAA,IAC3E,SAAS,GAAA,EAAK;AACZ,MAAI,KAAA,CAAM,uCAAuC,GAAG,CAAA;AACpD,MAAA,KAAA,CAAM,WAAA,EAAA;AACN,MAAA,OAAQ,UAAA,CAAwB,KAAA;AAAA,QAC9B,eAAA,KAAoB,WAAW,KAAA,GAAQ,IAAA;AAAA,QACvC;AAAA,OACF;AAAA,IACF;AAAA,EACF,CAAA;AACF;AAKA,SAAS,iBACP,cAAA,EACiB;AACjB,EAAA,OAAO,SAAS,cAAc,IAAA,EAAqC;AACjE,IAAA,MAAM,GAAA,GAAO,cAAA,CAA4B,GAAG,IAAI,CAAA;AAChD,IAAA,GAAA,CAAI,GAAA,EAAI;AACR,IAAA,OAAO,GAAA;AAAA,EACT,CAAA;AACF;AAKO,SAAS,uBAAuB,GAAA,EAA4B;AACjE,EAAA,IAAI,MAAA,EAAQ;AACV,IAAI,KAAK,kDAAkD,CAAA;AAC3D,IAAA;AAAA,EACF;AAEA,EAAA,MAAA,GAAS;AAAA,IACP,GAAG,GAAA;AAAA,IACH,QAAA,EAAU,SAAA;AAAA,IACV,WAAA,EAAa,IAAI,WAAA,IAAe;AAAA,GAClC;AAGA,EAAA,mBAAA,GAAsB,IAAA,CAAK,OAAA;AAC3B,EAAA,eAAA,GAAkB,IAAA,CAAK,GAAA;AACvB,EAAA,oBAAA,GAAuB,KAAA,CAAM,OAAA;AAC7B,EAAA,gBAAA,GAAmB,KAAA,CAAM,GAAA;AAGzB,EAAA,MAAM,kBAAA,GAAqB,oBAAA,CAAqB,mBAAA,EAAqB,OAAO,CAAA;AAC5E,EAAA,IAAA,CAAK,OAAA,GAAU,kBAAA;AACf,EAAA,IAAA,CAAK,GAAA,GAAM,iBAAiB,kBAAkB,CAAA;AAG9C,EAAA,MAAM,mBAAA,GAAsB,oBAAA;AAAA,IAC1B,oBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,KAAA,CAAM,OAAA,GAAU,mBAAA;AAChB,EAAA,KAAA,CAAM,GAAA,GAAM,iBAAiB,mBAAmB,CAAA;AAGhD,EAAA,KAAA,CAAM,aAAA,GAAgB,CAAA;AACtB,EAAA,KAAA,CAAM,YAAA,GAAe,CAAA;AACrB,EAAA,KAAA,CAAM,WAAA,GAAc,CAAA;AACpB,EAAA,KAAA,CAAM,SAAA,uBAAgB,IAAA,EAAK;AAE3B,EAAA,MAAA,GAAS,IAAA;AACT,EAAI,MAAM,kCAAkC,CAAA;AAC9C;AAKO,SAAS,wBAAA,GAAiC;AAC/C,EAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,EAAA,IAAI,mBAAA,OAA0B,OAAA,GAAU,mBAAA;AACxC,EAAA,IAAI,eAAA,OAAsB,GAAA,GAAM,eAAA;AAChC,EAAA,IAAI,oBAAA,QAA4B,OAAA,GAAU,oBAAA;AAC1C,EAAA,IAAI,gBAAA,QAAwB,GAAA,GAAM,gBAAA;AAElC,EAAA,mBAAA,GAAsB,IAAA;AACtB,EAAA,eAAA,GAAkB,IAAA;AAClB,EAAA,oBAAA,GAAuB,IAAA;AACvB,EAAA,gBAAA,GAAmB,IAAA;AAEnB,EAAA,MAAA,GAAS,KAAA;AACT,EAAI,MAAM,gCAAgC,CAAA;AAC5C;AAEO,SAAS,uBAAA,GAAmC;AACjD,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,YAAA,GAA6B;AAC3C,EAAA,OAAO,EAAE,GAAG,KAAA,EAAM;AACpB;;;AClRA,IAAMC,UAAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,gBAAA,IAAoB,uBAAA;AAElD,IAAI,aAAA,GAAgD,IAAA;AACpD,IAAIC,OAAAA,GAAS,KAAA;AACb,IAAIC,OAAAA;AAKJ,IAAMC,MAAAA,GAAsB;AAAA,EAC1B,aAAA,EAAe,CAAA;AAAA,EACf,YAAA,EAAc,CAAA;AAAA,EACd,WAAA,EAAa,CAAA;AAAA,EACb,SAAA,sBAAe,IAAA;AACjB,CAAA;AAKO,SAAS,wBAAwB,GAAA,EAA4B;AAClE,EAAA,IAAIF,OAAAA,EAAQ;AACV,IAAI,KAAK,qCAAqC,CAAA;AAC9C,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AAC1C,IAAI,MAAM,4DAA4D,CAAA;AACtE,IAAA;AAAA,EACF;AAEA,EAAAC,OAAAA,GAAS;AAAA,IACP,GAAG,GAAA;AAAA,IACH,QAAA,EAAUF,UAAAA;AAAA,IACV,WAAA,EAAa,IAAI,WAAA,IAAe;AAAA,GAClC;AAEA,EAAA,aAAA,GAAgB,UAAA,CAAW,KAAA;AAE3B,EAAA,UAAA,CAAW,KAAA,GAAQ,eAAe,YAAA,CAChC,KAAA,EACAI,KAAAA,EACmB;AACnB,IAAA,IAAI;AAEF,MAAA,IAAI,SAAA;AACJ,MAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,QAAA,SAAA,GAAY,KAAA;AAAA,MACd,CAAA,MAAA,IAAW,iBAAiB,GAAA,EAAK;AAC/B,QAAA,SAAA,GAAY,MAAM,QAAA,EAAS;AAAA,MAC7B,CAAA,MAAA,IAAW,iBAAiB,OAAA,EAAS;AACnC,QAAA,SAAA,GAAY,KAAA,CAAM,GAAA;AAAA,MACpB,CAAA,MAAO;AACL,QAAA,SAAA,GAAY,OAAO,KAAK,CAAA;AAAA,MAC1B;AAGA,MAAA,IACE,CAAC,aAAA;AAAA,QACC,SAAA;AAAA,QACAF,OAAAA,CAAO,QAAA;AAAA,QACPA,OAAAA,CAAO,OAAA;AAAA,QACPA,OAAAA,CAAO;AAAA,OACT,EACA;AACA,QAAI,KAAA,CAAM,8BAA8B,SAAS,CAAA;AACjD,QAAAC,MAAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,MACnC;AAGA,MAAA,IAAI,YAAA;AACJ,MAAA,IAAI;AACF,QAAA,YAAA,GAAe,IAAI,IAAI,SAAS,CAAA;AAAA,MAClC,CAAA,CAAA,MAAQ;AACN,QAAI,KAAA,CAAM,wCAAwC,SAAS,CAAA;AAC3D,QAAAD,MAAAA,CAAM,YAAA,EAAA;AACN,QAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,MACnC;AAEA,MAAA,MAAM,eAAe,CAAA,EAAG,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK,aAAa,IAAI,CAAA,CAAA;AACnE,MAAA,MAAM,SAAA,GAAY,YAAA,CAAa,QAAA,GAAW,YAAA,CAAa,MAAA;AACvD,MAAA,MAAM,UAAA,GAAa,CAAA,EAAGF,OAAAA,CAAO,QAAQ,GAAG,SAAS,CAAA,CAAA;AAGjD,MAAA,MAAM,UAAU,IAAI,OAAA,CAAQE,KAAAA,EAAM,OAAA,IAAW,EAAE,CAAA;AAG/C,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AACpC,UAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG;AACrB,YAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,UACxB;AAAA,QACF,CAAC,CAAA;AAAA,MACH;AAGA,MAAA,OAAA,CAAQ,GAAA,CAAI,WAAA,EAAaF,OAAAA,CAAO,MAAM,CAAA;AACtC,MAAA,OAAA,CAAQ,GAAA,CAAI,mBAAmB,YAAY,CAAA;AAC3C,MAAA,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAgBA,OAAAA,CAAO,WAAW,CAAA;AAC9C,MAAA,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,YAAA,CAAa,IAAI,CAAA;AAErC,MAAA,IAAIA,QAAO,SAAA,EAAW;AACpB,QAAA,OAAA,CAAQ,GAAA,CAAI,kBAAA,EAAoBA,OAAAA,CAAO,SAAS,CAAA;AAAA,MAClD;AAGA,MAAA,MAAM,WAAA,GAA2B;AAAA,QAC/B,GAAGE,KAAAA;AAAA,QACH;AAAA,OACF;AAGA,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,WAAA,CAAY,MAAA,GAAS,WAAA,CAAY,MAAA,IAAU,KAAA,CAAM,MAAA;AACjD,QAAA,IAAI,CAAC,WAAA,CAAY,IAAA,IAAQ,KAAA,CAAM,IAAA,EAAM;AACnC,UAAA,WAAA,CAAY,OAAO,KAAA,CAAM,IAAA;AAAA,QAC3B;AAAA,MACF;AAEA,MAAI,KAAA;AAAA,QACF,kBAAA;AAAA,QACA,YAAY,MAAA,IAAU,KAAA;AAAA,QACtB,SAAA;AAAA,QACA,QAAA;AAAA,QACAF,OAAAA,CAAO;AAAA,OACT;AACA,MAAAC,MAAAA,CAAM,aAAA,EAAA;AAEN,MAAA,OAAO,aAAA,CAAe,YAAY,WAAW,CAAA;AAAA,IAC/C,SAAS,GAAA,EAAK;AACZ,MAAI,KAAA,CAAM,6CAA6C,GAAG,CAAA;AAC1D,MAAAA,MAAAA,CAAM,WAAA,EAAA;AACN,MAAA,OAAO,aAAA,CAAe,OAAOC,KAAI,CAAA;AAAA,IACnC;AAAA,EACF,CAAA;AAGA,EAAAD,OAAM,aAAA,GAAgB,CAAA;AACtB,EAAAA,OAAM,YAAA,GAAe,CAAA;AACrB,EAAAA,OAAM,WAAA,GAAc,CAAA;AACpB,EAAAA,MAAAA,CAAM,SAAA,mBAAY,IAAI,IAAA,EAAK;AAE3B,EAAAF,OAAAA,GAAS,IAAA;AACT,EAAI,MAAM,6BAA6B,CAAA;AACzC;AAKO,SAAS,yBAAA,GAAkC;AAChD,EAAA,IAAI,CAACA,OAAAA,IAAU,CAAC,aAAA,EAAe;AAE/B,EAAA,UAAA,CAAW,KAAA,GAAQ,aAAA;AACnB,EAAA,aAAA,GAAgB,IAAA;AAChB,EAAAA,OAAAA,GAAS,KAAA;AACT,EAAI,MAAM,2BAA2B,CAAA;AACvC;AAEO,SAAS,wBAAA,GAAoC;AAClD,EAAA,OAAOA,OAAAA;AACT;AAEO,SAAS,aAAA,GAA8B;AAC5C,EAAA,OAAO,EAAE,GAAGE,MAAAA,EAAM;AACpB;;;AC1JA,IAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,cAAA,IAAkB,uBAAA;AAE9C,IAAI,MAAA,GAAwB,IAAA;AAC5B,IAAI,WAAA,GAAc,aAAA;AAClB,IAAI,YAAA,GAAe,CAAA;AACnB,IAAI,UAAA,GAAa,CAAA;AAKV,SAAS,mBAAA,CAAoB,KAAa,GAAA,EAAmB;AAClE,EAAA,MAAA,GAAS,GAAA;AACT,EAAA,WAAA,GAAc,GAAA;AAChB;AAKA,SAAS,eAAe,GAAA,EAAqB;AAC3C,EAAA,MAAM,GAAA,GAA8B;AAAA,IAClC,WAAA,EAAa,KAAA;AAAA,IACb,GAAA,EAAK,KAAA;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,OAAA,EAAS,SAAA;AAAA,IACT,UAAA,EAAY,YAAA;AAAA,IACZ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM,MAAA;AAAA,IACN,OAAA,EAAS;AAAA,GACX;AACA,EAAA,OAAO,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,CAAA,IAAK,KAAA;AACnC;AAKA,eAAe,cAAc,KAAA,EAA+C;AAC1E,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,GAAG,OAAO,CAAA,WAAA,CAAA;AAEtB,IAAA,MAAM,EAAE,OAAA,EAASE,KAAAA,EAAK,GAAI,MAAM,OAAO,MAAM,CAAA;AAC7C,IAAA,MAAM,EAAE,OAAA,EAASC,MAAAA,EAAM,GAAI,MAAM,OAAO,OAAO,CAAA;AAE/C,IAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,MAAM,SAAA,GAAY,SAAA,CAAU,QAAA,KAAa,QAAA,GAAWA,MAAAA,GAAQD,KAAAA;AAC5D,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAEjC,IAAA,MAAM,MAAM,SAAA,CAAU,OAAA;AAAA,MACpB;AAAA,QACE,UAAU,SAAA,CAAU,QAAA;AAAA,QACpB,MAAM,SAAA,CAAU,IAAA;AAAA,QAChB,MAAM,SAAA,CAAU,QAAA;AAAA,QAChB,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB,kBAAA;AAAA,UAChB,aAAa,MAAA,IAAU,EAAA;AAAA,UACvB,gBAAA,EAAkB,MAAA,CAAO,UAAA,CAAW,IAAI;AAAA;AAC1C,OACF;AAAA,MACA,CAAC,GAAA,KAAQ;AAEP,QAAA,GAAA,CAAI,MAAA,EAAO;AACX,QAAA,IAAI,GAAA,CAAI,UAAA,IAAc,GAAA,CAAI,UAAA,IAAc,GAAA,EAAK;AAC3C,UAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,UAAU,CAAA,CAAE,CAAA;AAAA,QAChD;AAAA,MACF;AAAA,KACF;AAEA,IAAA,GAAA,CAAI,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAQ;AACvB,MAAA,UAAA,EAAA;AACA,MAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA;AAAA,IAC1C,CAAC,CAAA;AAED,IAAA,GAAA,CAAI,MAAM,IAAI,CAAA;AACd,IAAA,GAAA,CAAI,GAAA,EAAI;AAER,IAAA,YAAA,EAAA;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,UAAA,EAAA;AACA,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAuB,GAAA,CAAc,OAAO,CAAA,CAAE,CAAA;AAAA,EAC1D;AACF;AAKA,SAAS,mBAAA,CACP,KACA,QAAA,EACM;AACN,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,MAAM,aAAA,GAAgB,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA;AACxC,EAAA,MAAM,WAAA,GAAc,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,GAAG,CAAA;AAEpC,EAAA,GAAA,CAAI,KAAA,GAAQ,SACV,KAAA,EAAA,GACG,IAAA,EACM;AACT,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,IAAA,CAAK,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,KAAe,CAAC,CAAA;AAAA,IAC3E;AACA,IAAA,OAAQ,aAAA,CAA2B,KAAA,EAAO,GAAG,IAAI,CAAA;AAAA,EACnD,CAAA;AAEA,EAAA,GAAA,CAAI,GAAA,GAAM,SACR,KAAA,EAAA,GACG,IAAA,EACa;AAChB,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,UAAA,EAAY;AACxC,MAAA,MAAA,CAAO,IAAA,CAAK,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,KAAe,CAAC,CAAA;AAAA,IAC3E;AAEA,IAAA,MAAM,OAAO,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,CAAE,SAAS,OAAO,CAAA;AACnD,IAAA,QAAA,CAAS,IAAI,CAAA;AAEb,IAAA,OAAQ,WAAA,CAAyB,KAAA,EAAO,GAAG,IAAI,CAAA;AAAA,EACjD,CAAA;AACF;AAKA,SAAS,aAAa,GAAA,EAAsB;AAC1C,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAgBO,SAAS,gBAAA,CAAiB,OAAA,GAA6B,EAAC,EAAG;AAChE,EAAA,MAAM,EAAE,OAAA,GAAU,IAAI,WAAA,GAAc,IAAA,GAAO,MAAK,GAAI,OAAA;AAEpD,EAAA,OAAO,SAAS,gBAAA,CACd,GAAA,EACA,GAAA,EACA,IAAA,EACM;AACN,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,IAAA,EAAK;AAAA,IACd;AAEA,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,IAAO,GAAA;AAGvB,IAAA,KAAA,MAAW,WAAW,OAAA,EAAS;AAC7B,MAAA,IAAI,OAAO,YAAY,QAAA,IAAY,GAAA,CAAI,SAAS,OAAO,CAAA,SAAU,IAAA,EAAK;AACtE,MAAA,IAAI,mBAAmB,MAAA,IAAU,OAAA,CAAQ,KAAK,GAAG,CAAA,SAAU,IAAA,EAAK;AAAA,IAClE;AAEA,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,MAAA,CAAO,MAAA,EAAO;AAG1C,IAAA,MAAM,MAAA,GAAA,CAAU,GAAA,CAAI,MAAA,IAAU,KAAA,EAAO,WAAA,EAAY;AACjD,IAAA,MAAM,IAAA,GAAA,CAAQ,IAAI,OAAA,CAAQ,IAAA,IAAQ,WAAW,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AACzD,IAAA,MAAM,QAAA,GAAY,GAAA,CAAyC,QAAA,KAAa,OAAA,GAAU,MAAA,GAAS,MAAA;AAG3F,IAAA,IAAI,IAAA,GAAO,GAAA;AACX,IAAA,IAAI,cAAc,EAAC;AACnB,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC9B,IAAA,IAAI,WAAW,EAAA,EAAI;AACjB,MAAA,IAAA,GAAO,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,MAAM,CAAA;AAC9B,MAAA,IAAI;AACF,QAAA,MAAM,eAAe,IAAI,eAAA,CAAgB,IAAI,SAAA,CAAU,MAAA,GAAS,CAAC,CAAC,CAAA;AAClE,QAAA,WAAA,GAAc,MAAA,CAAO,WAAA,CAAY,YAAA,CAAa,OAAA,EAAS,CAAA;AAAA,MACzD,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAGA,IAAA,MAAM,iBAAyC,EAAC;AAChD,IAAA,KAAA,MAAW,CAAC,KAAK,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AACpD,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,cAAA,CAAe,GAAG,IAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,GAAA;AAAA,MAC9D;AAAA,IACF;AAGA,IAAA,IAAI,WAAA,GAAuB,MAAA;AAC3B,IAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAW;AAC1B,MAAA,MAAM,OAAA,GAAU,OAAO,GAAA,CAAI,IAAA,KAAS,QAAA,GAAW,IAAI,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AACjF,MAAA,IAAI,OAAA,CAAQ,UAAU,WAAA,EAAa;AACjC,QAAA,WAAA,GAAc,OAAO,GAAA,CAAI,IAAA,KAAS,QAAA,GAAW,YAAA,CAAa,IAAI,IAAI,CAAA,IAAK,GAAA,CAAI,IAAA,GAAO,GAAA,CAAI,IAAA;AAAA,MACxF;AAAA,IACF;AAGA,IAAA,mBAAA,CAAoB,GAAA,EAAK,CAAC,eAAA,KAAoB;AAC5C,MAAA,MAAM,aAAa,MAAA,CAAO,OAAA,CAAQ,OAAO,MAAA,EAAO,GAAI,WAAW,CAAA,GAAI,GAAA;AACnE,MAAA,MAAM,aAAa,GAAA,CAAI,UAAA;AAGvB,MAAA,MAAM,kBAA0C,EAAC;AACjD,MAAA,MAAM,UAAA,GAAa,IAAI,UAAA,EAAW;AAClC,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,GAAG,KAAK,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,EAAG;AACnD,QAAA,IAAI,GAAA,EAAK;AACP,UAAA,eAAA,CAAgB,GAAG,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,GAAG,CAAA;AAAA,QACzE;AAAA,MACF;AAGA,MAAA,IAAI,YAAA,GAAwB,MAAA;AAC5B,MAAA,IAAI,eAAA,CAAgB,UAAU,WAAA,EAAa;AACzC,QAAA,YAAA,GAAe,YAAA,CAAa,eAAe,CAAA,IAAK,eAAA;AAAA,MAClD;AAGA,MAAA,MAAM,KAAA,GAAQ;AAAA,QACZ,MAAA;AAAA,QACA,QAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QACA,WAAA;AAAA,QACA,cAAA;AAAA,QACA,iBAAA,EAAmB,WAAA;AAAA,QACnB,gBAAA,EAAkB,cAAc,MAAA,CAAO,UAAA,CAAW,KAAK,SAAA,CAAU,WAAW,CAAC,CAAA,GAAI,CAAA;AAAA,QACjF,UAAA;AAAA,QACA,eAAA;AAAA,QACA,kBAAA,EAAoB,YAAA;AAAA,QACpB,mBAAmB,eAAA,CAAgB,MAAA;AAAA,QACnC,SAAA,EAAW,IAAI,IAAA,CAAK,SAAS,EAAE,WAAA,EAAY;AAAA,QAC3C,UAAA,EAAY,IAAA,CAAK,KAAA,CAAM,UAAA,GAAa,GAAG,CAAA,GAAI,GAAA;AAAA,QAC3C,MAAA,EAAQ,KAAA;AAAA,QACR,WAAA,EAAa,eAAe,WAAW,CAAA;AAAA,QACvC,QAAA,EAAU,IAAI,OAAA,CAAQ,iBAAiB,IACnC,MAAA,CAAO,GAAA,CAAI,QAAQ,iBAAiB,CAAC,EAAE,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA,CAAE,MAAK,GAC1D,GAAA,CAAI,QAAQ,aAAA,IAAiB,IAAA;AAAA,QACjC,MAAM,EAAC;AAAA,QACP,UAAU;AAAC,OACb;AAEA,MAAI,KAAA,CAAM,CAAA,UAAA,EAAa,MAAM,CAAA,CAAA,EAAI,IAAI,IAAI,UAAU,CAAA,EAAA,EAAK,KAAA,CAAM,UAAU,CAAA,GAAA,CAAK,CAAA;AAG7E,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,IACrB,CAAC,CAAA;AAED,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF;;;AC/OA,IAAI,WAAA,GAAc,KAAA;AAkBlB,SAAS,KAAK,cAAA,EAA6D;AAEzE,EAAA,MAAMH,UACJ,OAAO,cAAA,KAAmB,WAAW,EAAE,MAAA,EAAQ,gBAAe,GAAI,cAAA;AAEpE,EAAA,IAAI,WAAA,EAAa;AACf,IAAI,KAAK,0EAAqE,CAAA;AAC9E,IAAA,IAAA,EAAK;AAAA,EACP;AAGA,EAAA,IAAI,CAACA,QAAO,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAIA,QAAO,QAAA,EAAU;AACnB,IAAI,KAAK,kDAAkD,CAAA;AAC3D,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AAAA,EACpC;AAGA,EAAA,IAAIA,QAAO,KAAA,EAAO;AAChB,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf;AAGA,EAAAA,QAAO,WAAA,GACLA,OAAAA,CAAO,WAAA,IAAe,OAAA,CAAQ,IAAI,kBAAA,IAAsB,aAAA;AAE1D,EAAI,IAAA,CAAK,CAAA,wBAAA,EAAsBA,OAAAA,CAAO,WAAW,CAAA,CAAE,CAAA;AAGnD,EAAA,mBAAA,CAAoBA,OAAAA,CAAO,MAAA,EAAQA,OAAAA,CAAO,WAAW,CAAA;AAGrD,EAAA,sBAAA,CAAuBA,OAAM,CAAA;AAC7B,EAAA,uBAAA,CAAwBA,OAAM,CAAA;AAE9B,EAAA,WAAA,GAAc,IAAA;AAEd,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AACpC;AAKA,SAAS,IAAA,GAAa;AACpB,EAAA,IAAI,CAAC,WAAA,EAAa;AAElB,EAAA,wBAAA,EAAyB;AACzB,EAAA,yBAAA,EAA0B;AAE1B,EAAA,WAAA,GAAc,KAAA;AACd,EAAI,KAAK,yCAAoC,CAAA;AAC/C;AAKA,SAAS,QAAA,GAAoB;AAC3B,EAAA,OAAO,uBAAA,MAA6B,wBAAA,EAAyB;AAC/D;AAKA,SAAS,QAAA,GAAyB;AAChC,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,MAAM,aAAa,aAAA,EAAc;AAEjC,EAAA,OAAO;AAAA,IACL,aAAA,EAAe,SAAA,CAAU,aAAA,GAAgB,UAAA,CAAW,aAAA;AAAA,IACpD,YAAA,EAAc,SAAA,CAAU,YAAA,GAAe,UAAA,CAAW,YAAA;AAAA,IAClD,WAAA,EAAa,SAAA,CAAU,WAAA,GAAc,UAAA,CAAW,WAAA;AAAA,IAChD,WAAW,SAAA,CAAU,SAAA,GAAY,WAAW,SAAA,GACxC,SAAA,CAAU,YACV,UAAA,CAAW;AAAA,GACjB;AACF;AAKO,IAAM,SAAA,GAAY;AAAA,EACvB,IAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA,EAAY;AACd","file":"index.mjs","sourcesContent":["/**\r\n * ReplayAPI SDK — URL matching utilities\r\n */\r\n\r\n/**\r\n * Check if a URL matches any of the given patterns.\r\n * Patterns can be strings (substring match) or RegExp.\r\n */\r\nexport function matchesPatterns(\r\n url: string,\r\n patterns: Array<string | RegExp>\r\n): boolean {\r\n return patterns.some((pattern) => {\r\n if (typeof pattern === \"string\") {\r\n return url.includes(pattern);\r\n }\r\n return pattern.test(url);\r\n });\r\n}\r\n\r\n/**\r\n * Determine if a request URL should be captured based on include/exclude filters.\r\n */\r\nexport function shouldCapture(\r\n url: string,\r\n proxyUrl: string,\r\n include?: Array<string | RegExp>,\r\n exclude?: Array<string | RegExp>\r\n): boolean {\r\n // Never capture requests to the proxy itself\r\n if (url.startsWith(proxyUrl)) {\r\n return false;\r\n }\r\n\r\n // Never capture requests to ReplayAPI internal paths\r\n if (url.includes(\"/__replay/\")) {\r\n return false;\r\n }\r\n\r\n // If include patterns are set, URL must match at least one\r\n if (include && include.length > 0) {\r\n if (!matchesPatterns(url, include)) {\r\n return false;\r\n }\r\n }\r\n\r\n // If exclude patterns are set, URL must not match any\r\n if (exclude && exclude.length > 0) {\r\n if (matchesPatterns(url, exclude)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n","/**\r\n * ReplayAPI SDK — Simple logger\r\n */\r\n\r\nlet debugEnabled = false;\r\n\r\nexport function setDebug(enabled: boolean): void {\r\n debugEnabled = enabled;\r\n}\r\n\r\nexport function debug(...args: unknown[]): void {\r\n if (debugEnabled) {\r\n console.log(\"[replayapi]\", ...args);\r\n }\r\n}\r\n\r\nexport function info(...args: unknown[]): void {\r\n console.log(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function warn(...args: unknown[]): void {\r\n console.warn(\"[replayapi]\", ...args);\r\n}\r\n\r\nexport function error(...args: unknown[]): void {\r\n console.error(\"[replayapi]\", ...args);\r\n}\r\n","/**\r\n * ReplayAPI SDK — HTTP/HTTPS interceptor\r\n *\r\n * Monkey-patches Node.js http.request and https.request to redirect\r\n * outgoing requests through the ReplayAPI proxy. The proxy captures\r\n * the traffic and forwards it to the original target.\r\n *\r\n * How it works:\r\n * 1. Original request: GET https://api.example.com/users\r\n * 2. SDK rewrites to: GET http://proxy:8080/users\r\n * with headers:\r\n * X-Api-Key: rp_...\r\n * X-Replay-Target: https://api.example.com\r\n * X-Replay-Env: staging\r\n * 3. Proxy captures, forwards to target, returns response transparently\r\n */\r\n\r\nimport http from \"node:http\";\r\nimport https from \"node:https\";\r\nimport { URL } from \"node:url\";\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\n// Store original implementations\r\nlet originalHttpRequest: typeof http.request | null = null;\r\nlet originalHttpGet: typeof http.get | null = null;\r\nlet originalHttpsRequest: typeof https.request | null = null;\r\nlet originalHttpsGet: typeof https.get | null = null;\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\n// Track state\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Parse the various argument formats that http.request accepts:\r\n * - (url: string, options?, callback?)\r\n * - (url: URL, options?, callback?)\r\n * - (options, callback?)\r\n */\r\nfunction parseRequestArgs(\r\n args: unknown[]\r\n): {\r\n targetUrl: string;\r\n options: http.RequestOptions;\r\n callback?: (res: http.IncomingMessage) => void;\r\n} {\r\n let targetUrl: string;\r\n let options: http.RequestOptions = {};\r\n let callback: ((res: http.IncomingMessage) => void) | undefined;\r\n\r\n if (typeof args[0] === \"string\") {\r\n targetUrl = args[0];\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (args[0] instanceof URL) {\r\n targetUrl = args[0].toString();\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n } else if (typeof args[1] === \"object\" && args[1] !== null) {\r\n options = args[1] as http.RequestOptions;\r\n if (typeof args[2] === \"function\") {\r\n callback = args[2] as (res: http.IncomingMessage) => void;\r\n }\r\n }\r\n } else if (typeof args[0] === \"object\" && args[0] !== null) {\r\n options = args[0] as http.RequestOptions;\r\n const protocol = (options as { protocol?: string }).protocol || \"http:\";\r\n const host = options.hostname || options.host || \"localhost\";\r\n const port = options.port ? `:${options.port}` : \"\";\r\n const path = options.path || \"/\";\r\n targetUrl = `${protocol}//${host}${port}${path}`;\r\n if (typeof args[1] === \"function\") {\r\n callback = args[1] as (res: http.IncomingMessage) => void;\r\n }\r\n } else {\r\n targetUrl = \"\";\r\n }\r\n\r\n return { targetUrl, options, callback };\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.request / https.request\r\n */\r\nfunction createProxiedRequest(\r\n originalFn: typeof http.request,\r\n defaultProtocol: \"http:\" | \"https:\"\r\n): typeof http.request {\r\n return function proxiedRequest(\r\n ...args: unknown[]\r\n ): http.ClientRequest {\r\n try {\r\n const { targetUrl, options, callback } = parseRequestArgs(args);\r\n\r\n if (!targetUrl) {\r\n log.debug(\"could not parse request URL, passing through\");\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n // Parse the proxy URL\r\n const proxyParsed = new URL(config.proxyUrl);\r\n\r\n // Parse the target URL to extract origin and path\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n\r\n // Build proxied options: send to proxy, with X-Replay-Target header\r\n const proxiedOptions: http.RequestOptions = {\r\n ...options,\r\n protocol: proxyParsed.protocol,\r\n hostname: proxyParsed.hostname,\r\n port: proxyParsed.port || (proxyParsed.protocol === \"https:\" ? 443 : 80),\r\n path: parsedTarget.pathname + parsedTarget.search,\r\n headers: {\r\n ...options.headers,\r\n \"X-Api-Key\": config.apiKey,\r\n \"X-Replay-Target\": targetOrigin,\r\n \"X-Replay-Env\": config.environment,\r\n // Preserve the original Host header\r\n Host: parsedTarget.host,\r\n },\r\n };\r\n\r\n // Add session ID if configured\r\n if (config.sessionId) {\r\n (proxiedOptions.headers as Record<string, string>)[\"X-Replay-Session\"] =\r\n config.sessionId;\r\n }\r\n\r\n // Set timeout if configured\r\n if (config.timeout) {\r\n proxiedOptions.timeout = config.timeout;\r\n }\r\n\r\n log.debug(\"capturing:\", options.method || \"GET\", targetUrl, \"→\", config.proxyUrl);\r\n stats.totalCaptured++;\r\n\r\n // Use http.request (not https) since we're talking to the proxy over HTTP\r\n const proxyUrl = new URL(`${proxiedOptions.protocol}//${proxiedOptions.hostname}:${proxiedOptions.port}${proxiedOptions.path}`);\r\n return originalHttpRequest!.call(http, proxyUrl, proxiedOptions, callback);\r\n } catch (err) {\r\n log.error(\"interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return (originalFn as Function).apply(\r\n defaultProtocol === \"https:\" ? https : http,\r\n args\r\n ) as http.ClientRequest;\r\n }\r\n } as typeof http.request;\r\n}\r\n\r\n/**\r\n * Create a proxied version of http.get / https.get\r\n */\r\nfunction createProxiedGet(\r\n proxiedRequest: typeof http.request\r\n): typeof http.get {\r\n return function proxiedGet(...args: unknown[]): http.ClientRequest {\r\n const req = (proxiedRequest as Function)(...args) as http.ClientRequest;\r\n req.end();\r\n return req;\r\n } as typeof http.get;\r\n}\r\n\r\n/**\r\n * Install the HTTP/HTTPS interceptors\r\n */\r\nexport function installHttpInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"interceptor already installed, call stop() first\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n // Save originals\r\n originalHttpRequest = http.request;\r\n originalHttpGet = http.get;\r\n originalHttpsRequest = https.request;\r\n originalHttpsGet = https.get;\r\n\r\n // Patch http\r\n const proxiedHttpRequest = createProxiedRequest(originalHttpRequest, \"http:\");\r\n http.request = proxiedHttpRequest;\r\n http.get = createProxiedGet(proxiedHttpRequest);\r\n\r\n // Patch https\r\n const proxiedHttpsRequest = createProxiedRequest(\r\n originalHttpsRequest,\r\n \"https:\"\r\n );\r\n https.request = proxiedHttpsRequest;\r\n https.get = createProxiedGet(proxiedHttpsRequest);\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"http/https interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the HTTP/HTTPS interceptors\r\n */\r\nexport function uninstallHttpInterceptor(): void {\r\n if (!active) return;\r\n\r\n if (originalHttpRequest) http.request = originalHttpRequest;\r\n if (originalHttpGet) http.get = originalHttpGet;\r\n if (originalHttpsRequest) https.request = originalHttpsRequest;\r\n if (originalHttpsGet) https.get = originalHttpsGet;\r\n\r\n originalHttpRequest = null;\r\n originalHttpGet = null;\r\n originalHttpsRequest = null;\r\n originalHttpsGet = null;\r\n\r\n active = false;\r\n log.debug(\"http/https interceptor removed\");\r\n}\r\n\r\nexport function isHttpInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getHttpStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Global fetch() interceptor\r\n *\r\n * Patches globalThis.fetch to route requests through the ReplayAPI proxy.\r\n * Works with Node.js 18+ native fetch and any polyfills that set globalThis.fetch.\r\n */\r\n\r\nimport type { ReplayApiConfig, CaptureStats } from \"./types.js\";\r\nimport { shouldCapture } from \"./matcher.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst PROXY_URL = process.env.REPLAY_PROXY_URL || \"http://localhost:8080\";\r\n\r\nlet originalFetch: typeof globalThis.fetch | null = null;\r\nlet active = false;\r\nlet config: Required<\r\n Pick<ReplayApiConfig, \"apiKey\" | \"environment\">\r\n> &\r\n ReplayApiConfig & { proxyUrl: string };\r\n\r\nconst stats: CaptureStats = {\r\n totalCaptured: 0,\r\n totalSkipped: 0,\r\n totalErrors: 0,\r\n startedAt: new Date(),\r\n};\r\n\r\n/**\r\n * Install the fetch interceptor\r\n */\r\nexport function installFetchInterceptor(cfg: ReplayApiConfig): void {\r\n if (active) {\r\n log.warn(\"fetch interceptor already installed\");\r\n return;\r\n }\r\n\r\n // Only patch if fetch exists (Node.js 18+)\r\n if (typeof globalThis.fetch !== \"function\") {\r\n log.debug(\"globalThis.fetch not available, skipping fetch interceptor\");\r\n return;\r\n }\r\n\r\n config = {\r\n ...cfg,\r\n proxyUrl: PROXY_URL,\r\n environment: cfg.environment || \"development\",\r\n };\r\n\r\n originalFetch = globalThis.fetch;\r\n\r\n globalThis.fetch = async function proxiedFetch(\r\n input: string | URL | Request,\r\n init?: RequestInit\r\n ): Promise<Response> {\r\n try {\r\n // Resolve the target URL\r\n let targetUrl: string;\r\n if (typeof input === \"string\") {\r\n targetUrl = input;\r\n } else if (input instanceof URL) {\r\n targetUrl = input.toString();\r\n } else if (input instanceof Request) {\r\n targetUrl = input.url;\r\n } else {\r\n targetUrl = String(input);\r\n }\r\n\r\n // Check if this URL should be captured\r\n if (\r\n !shouldCapture(\r\n targetUrl,\r\n config.proxyUrl,\r\n config.include,\r\n config.exclude\r\n )\r\n ) {\r\n log.debug(\"fetch skipping (filtered):\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n // Parse target URL\r\n let parsedTarget: URL;\r\n try {\r\n parsedTarget = new URL(targetUrl);\r\n } catch {\r\n log.debug(\"fetch: invalid URL, passing through:\", targetUrl);\r\n stats.totalSkipped++;\r\n return originalFetch!(input, init);\r\n }\r\n\r\n const targetOrigin = `${parsedTarget.protocol}//${parsedTarget.host}`;\r\n const proxyPath = parsedTarget.pathname + parsedTarget.search;\r\n const proxiedUrl = `${config.proxyUrl}${proxyPath}`;\r\n\r\n // Build headers\r\n const headers = new Headers(init?.headers || {});\r\n\r\n // If input is a Request, merge its headers too\r\n if (input instanceof Request) {\r\n input.headers.forEach((value, key) => {\r\n if (!headers.has(key)) {\r\n headers.set(key, value);\r\n }\r\n });\r\n }\r\n\r\n // Add ReplayAPI headers\r\n headers.set(\"X-Api-Key\", config.apiKey);\r\n headers.set(\"X-Replay-Target\", targetOrigin);\r\n headers.set(\"X-Replay-Env\", config.environment);\r\n headers.set(\"Host\", parsedTarget.host);\r\n\r\n if (config.sessionId) {\r\n headers.set(\"X-Replay-Session\", config.sessionId);\r\n }\r\n\r\n // Build proxied init\r\n const proxiedInit: RequestInit = {\r\n ...init,\r\n headers,\r\n };\r\n\r\n // If input is a Request, preserve method and body\r\n if (input instanceof Request) {\r\n proxiedInit.method = proxiedInit.method || input.method;\r\n if (!proxiedInit.body && input.body) {\r\n proxiedInit.body = input.body;\r\n }\r\n }\r\n\r\n log.debug(\r\n \"fetch capturing:\",\r\n proxiedInit.method || \"GET\",\r\n targetUrl,\r\n \"→\",\r\n config.proxyUrl\r\n );\r\n stats.totalCaptured++;\r\n\r\n return originalFetch!(proxiedUrl, proxiedInit);\r\n } catch (err) {\r\n log.error(\"fetch interceptor error, passing through:\", err);\r\n stats.totalErrors++;\r\n return originalFetch!(input, init);\r\n }\r\n };\r\n\r\n // Reset stats\r\n stats.totalCaptured = 0;\r\n stats.totalSkipped = 0;\r\n stats.totalErrors = 0;\r\n stats.startedAt = new Date();\r\n\r\n active = true;\r\n log.debug(\"fetch interceptor installed\");\r\n}\r\n\r\n/**\r\n * Uninstall the fetch interceptor\r\n */\r\nexport function uninstallFetchInterceptor(): void {\r\n if (!active || !originalFetch) return;\r\n\r\n globalThis.fetch = originalFetch;\r\n originalFetch = null;\r\n active = false;\r\n log.debug(\"fetch interceptor removed\");\r\n}\r\n\r\nexport function isFetchInterceptorActive(): boolean {\r\n return active;\r\n}\r\n\r\nexport function getFetchStats(): CaptureStats {\r\n return { ...stats };\r\n}\r\n","/**\r\n * ReplayAPI SDK — Express/Connect middleware\r\n *\r\n * Captures incoming HTTP requests and responses,\r\n * then sends them asynchronously to the ReplayAPI backend.\r\n *\r\n * @example\r\n * ```ts\r\n * import express from 'express';\r\n * import { replayApi } from '@thaparoyal/replayapi';\r\n *\r\n * const app = express();\r\n * replayApi.init('rp_your_api_key');\r\n *\r\n * // Add middleware BEFORE your routes\r\n * app.use(replayApi.middleware());\r\n * ```\r\n */\r\n\r\nimport type { IncomingMessage, ServerResponse } from \"http\";\r\nimport * as log from \"./logger.js\";\r\n\r\nconst API_URL = process.env.REPLAY_API_URL || \"http://localhost:8000\";\r\n\r\nlet apiKey: string | null = null;\r\nlet environment = \"development\";\r\nlet captureCount = 0;\r\nlet errorCount = 0;\r\n\r\n/**\r\n * Configure the middleware (called from init)\r\n */\r\nexport function configureMiddleware(key: string, env: string): void {\r\n apiKey = key;\r\n environment = env;\r\n}\r\n\r\n/**\r\n * Map environment string to valid enum value\r\n */\r\nfunction mapEnvironment(env: string): string {\r\n const map: Record<string, string> = {\r\n development: \"dev\",\r\n dev: \"dev\",\r\n local: \"local\",\r\n staging: \"staging\",\r\n production: \"production\",\r\n prod: \"production\",\r\n test: \"test\",\r\n testing: \"test\",\r\n };\r\n return map[env.toLowerCase()] || \"dev\";\r\n}\r\n\r\n/**\r\n * Send captured traffic event to the ReplayAPI backend (fire-and-forget)\r\n */\r\nasync function sendToBackend(event: Record<string, unknown>): Promise<void> {\r\n try {\r\n const url = `${API_URL}/api/ingest`;\r\n // Use native http to avoid intercepting our own calls\r\n const { default: http } = await import(\"http\");\r\n const { default: https } = await import(\"https\");\r\n\r\n const parsedUrl = new URL(url);\r\n const transport = parsedUrl.protocol === \"https:\" ? https : http;\r\n const body = JSON.stringify(event);\r\n\r\n const req = transport.request(\r\n {\r\n hostname: parsedUrl.hostname,\r\n port: parsedUrl.port,\r\n path: parsedUrl.pathname,\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n \"X-API-Key\": apiKey || \"\",\r\n \"Content-Length\": Buffer.byteLength(body),\r\n },\r\n },\r\n (res) => {\r\n // Drain the response\r\n res.resume();\r\n if (res.statusCode && res.statusCode >= 400) {\r\n log.debug(`ingest response: ${res.statusCode}`);\r\n }\r\n }\r\n );\r\n\r\n req.on(\"error\", (err) => {\r\n errorCount++;\r\n log.debug(`ingest error: ${err.message}`);\r\n });\r\n\r\n req.write(body);\r\n req.end();\r\n\r\n captureCount++;\r\n } catch (err) {\r\n errorCount++;\r\n log.debug(`ingest send error: ${(err as Error).message}`);\r\n }\r\n}\r\n\r\n/**\r\n * Collect the response body by intercepting write/end\r\n */\r\nfunction collectResponseBody(\r\n res: ServerResponse,\r\n callback: (body: string) => void\r\n): void {\r\n const chunks: Buffer[] = [];\r\n const originalWrite = res.write.bind(res);\r\n const originalEnd = res.end.bind(res);\r\n\r\n res.write = function (\r\n chunk: unknown,\r\n ...args: unknown[]\r\n ): boolean {\r\n if (chunk) {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\r\n }\r\n return (originalWrite as Function)(chunk, ...args);\r\n };\r\n\r\n res.end = function (\r\n chunk?: unknown,\r\n ...args: unknown[]\r\n ): ServerResponse {\r\n if (chunk && typeof chunk !== \"function\") {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\r\n }\r\n\r\n const body = Buffer.concat(chunks).toString(\"utf-8\");\r\n callback(body);\r\n\r\n return (originalEnd as Function)(chunk, ...args);\r\n };\r\n}\r\n\r\n/**\r\n * Parse JSON body safely\r\n */\r\nfunction tryParseJson(str: string): unknown {\r\n try {\r\n return JSON.parse(str);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Create the Express/Connect middleware function.\r\n *\r\n * Options:\r\n * - `exclude` — URL patterns to skip (e.g. health checks)\r\n * - `maxBodySize` — max body size to capture in bytes (default: 1MB)\r\n */\r\nexport interface MiddlewareOptions {\r\n /** URL patterns to exclude from capture */\r\n exclude?: Array<string | RegExp>;\r\n /** Max body size to capture in bytes (default: 1MB) */\r\n maxBodySize?: number;\r\n}\r\n\r\nexport function createMiddleware(options: MiddlewareOptions = {}) {\r\n const { exclude = [], maxBodySize = 1024 * 1024 } = options;\r\n\r\n return function replayMiddleware(\r\n req: IncomingMessage & { body?: unknown },\r\n res: ServerResponse,\r\n next: (err?: unknown) => void\r\n ): void {\r\n if (!apiKey) {\r\n return next();\r\n }\r\n\r\n const url = req.url || \"/\";\r\n\r\n // Check exclude patterns\r\n for (const pattern of exclude) {\r\n if (typeof pattern === \"string\" && url.includes(pattern)) return next();\r\n if (pattern instanceof RegExp && pattern.test(url)) return next();\r\n }\r\n\r\n const startTime = Date.now();\r\n const startHrTime = process.hrtime.bigint();\r\n\r\n // Collect request info\r\n const method = (req.method || \"GET\").toUpperCase();\r\n const host = (req.headers.host || \"unknown\").split(\":\")[0];\r\n const protocol = (req as unknown as { protocol?: string }).protocol === \"https\" ? \"http\" : \"http\";\r\n\r\n // Parse path and query\r\n let path = url;\r\n let queryParams = {};\r\n const qIndex = url.indexOf(\"?\");\r\n if (qIndex !== -1) {\r\n path = url.substring(0, qIndex);\r\n try {\r\n const searchParams = new URLSearchParams(url.substring(qIndex + 1));\r\n queryParams = Object.fromEntries(searchParams.entries());\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n\r\n // Get request headers (sanitize sensitive ones)\r\n const requestHeaders: Record<string, string> = {};\r\n for (const [key, val] of Object.entries(req.headers)) {\r\n if (val) {\r\n requestHeaders[key] = Array.isArray(val) ? val.join(\", \") : val;\r\n }\r\n }\r\n\r\n // Request body (Express populates req.body after body parsers)\r\n let requestBody: unknown = undefined;\r\n if (req.body !== undefined) {\r\n const bodyStr = typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\r\n if (bodyStr.length <= maxBodySize) {\r\n requestBody = typeof req.body === \"string\" ? tryParseJson(req.body) || req.body : req.body;\r\n }\r\n }\r\n\r\n // Intercept response body\r\n collectResponseBody(res, (responseBodyStr) => {\r\n const durationMs = Number(process.hrtime.bigint() - startHrTime) / 1e6;\r\n const statusCode = res.statusCode;\r\n\r\n // Get response headers\r\n const responseHeaders: Record<string, string> = {};\r\n const rawHeaders = res.getHeaders();\r\n for (const [key, val] of Object.entries(rawHeaders)) {\r\n if (val) {\r\n responseHeaders[key] = Array.isArray(val) ? val.join(\", \") : String(val);\r\n }\r\n }\r\n\r\n // Parse response body\r\n let responseBody: unknown = undefined;\r\n if (responseBodyStr.length <= maxBodySize) {\r\n responseBody = tryParseJson(responseBodyStr) || responseBodyStr;\r\n }\r\n\r\n // Build traffic event\r\n const event = {\r\n method,\r\n protocol,\r\n host,\r\n path,\r\n queryParams,\r\n requestHeaders,\r\n requestBodyInline: requestBody,\r\n requestSizeBytes: requestBody ? Buffer.byteLength(JSON.stringify(requestBody)) : 0,\r\n statusCode,\r\n responseHeaders,\r\n responseBodyInline: responseBody,\r\n responseSizeBytes: responseBodyStr.length,\r\n startedAt: new Date(startTime).toISOString(),\r\n durationMs: Math.round(durationMs * 100) / 100,\r\n source: \"sdk\",\r\n environment: mapEnvironment(environment),\r\n clientIp: req.headers[\"x-forwarded-for\"]\r\n ? String(req.headers[\"x-forwarded-for\"]).split(\",\")[0].trim()\r\n : req.socket?.remoteAddress || null,\r\n tags: [],\r\n metadata: {},\r\n };\r\n\r\n log.debug(`captured: ${method} ${path} ${statusCode} (${event.durationMs}ms)`);\r\n\r\n // Send asynchronously — don't block the response\r\n sendToBackend(event);\r\n });\r\n\r\n next();\r\n };\r\n}\r\n\r\nexport function getMiddlewareStats() {\r\n return { captured: captureCount, errors: errorCount };\r\n}\r\n","/**\r\n * @replayapi/node — ReplayAPI SDK for Node.js\r\n *\r\n * Automatically captures outgoing HTTP traffic and routes it through\r\n * the ReplayAPI proxy for recording, analysis, and replay testing.\r\n *\r\n * @example\r\n * ```ts\r\n * import { replayApi } from '@replayapi/node'\r\n *\r\n * // Simplest — just pass your API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: 'rp_your_api_key', environment: 'staging' })\r\n *\r\n * // All outgoing HTTP/fetch calls are now captured automatically\r\n * ```\r\n */\r\n\r\nimport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nimport {\r\n installHttpInterceptor,\r\n uninstallHttpInterceptor,\r\n isHttpInterceptorActive,\r\n getHttpStats,\r\n} from \"./interceptor-http.js\";\r\nimport {\r\n installFetchInterceptor,\r\n uninstallFetchInterceptor,\r\n isFetchInterceptorActive,\r\n getFetchStats,\r\n} from \"./interceptor-fetch.js\";\r\nimport { configureMiddleware, createMiddleware, getMiddlewareStats } from \"./middleware.js\";\r\nimport type { MiddlewareOptions } from \"./middleware.js\";\r\nimport { setDebug } from \"./logger.js\";\r\nimport * as log from \"./logger.js\";\r\n\r\nlet initialized = false;\r\n\r\n/**\r\n * Initialize the ReplayAPI SDK.\r\n *\r\n * Call this once at application startup, before any outgoing HTTP requests.\r\n * The SDK will automatically intercept `http.request`, `https.request`,\r\n * and `fetch` to route traffic through the ReplayAPI proxy.\r\n *\r\n * @example\r\n * ```ts\r\n * // Just an API key\r\n * replayApi.init('rp_your_api_key')\r\n *\r\n * // Or with options\r\n * replayApi.init({ apiKey: process.env.REPLAY_API_KEY!, environment: 'staging' })\r\n * ```\r\n */\r\nfunction init(configOrApiKey: ReplayApiConfig | string): ReplayApiInstance {\r\n // Accept a plain string as shorthand for { apiKey: string }\r\n const config: ReplayApiConfig =\r\n typeof configOrApiKey === \"string\" ? { apiKey: configOrApiKey } : configOrApiKey;\r\n\r\n if (initialized) {\r\n log.warn(\"replayApi.init() called multiple times — stopping previous instance\");\r\n stop();\r\n }\r\n\r\n // Validate config\r\n if (!config.apiKey) {\r\n throw new Error(\r\n \"[@replayapi/node] apiKey is required. Get one from your ReplayAPI dashboard.\"\r\n );\r\n }\r\n\r\n if (config.disabled) {\r\n log.info(\"SDK disabled via config, skipping initialization\");\r\n return { stop, isActive, getStats };\r\n }\r\n\r\n // Enable debug logging if requested\r\n if (config.debug) {\r\n setDebug(true);\r\n }\r\n\r\n // Resolve environment with env var fallback\r\n config.environment =\r\n config.environment || process.env.REPLAY_ENVIRONMENT || \"development\";\r\n\r\n log.info(`initialized — env: ${config.environment}`);\r\n\r\n // Configure middleware with credentials\r\n configureMiddleware(config.apiKey, config.environment);\r\n\r\n // Install interceptors for outgoing traffic\r\n installHttpInterceptor(config);\r\n installFetchInterceptor(config);\r\n\r\n initialized = true;\r\n\r\n return { stop, isActive, getStats };\r\n}\r\n\r\n/**\r\n * Stop the SDK and restore original HTTP/fetch behavior.\r\n */\r\nfunction stop(): void {\r\n if (!initialized) return;\r\n\r\n uninstallHttpInterceptor();\r\n uninstallFetchInterceptor();\r\n\r\n initialized = false;\r\n log.info(\"stopped — all interceptors removed\");\r\n}\r\n\r\n/**\r\n * Check if the SDK is currently intercepting traffic.\r\n */\r\nfunction isActive(): boolean {\r\n return isHttpInterceptorActive() || isFetchInterceptorActive();\r\n}\r\n\r\n/**\r\n * Get combined capture statistics from all interceptors.\r\n */\r\nfunction getStats(): CaptureStats {\r\n const httpStats = getHttpStats();\r\n const fetchStats = getFetchStats();\r\n\r\n return {\r\n totalCaptured: httpStats.totalCaptured + fetchStats.totalCaptured,\r\n totalSkipped: httpStats.totalSkipped + fetchStats.totalSkipped,\r\n totalErrors: httpStats.totalErrors + fetchStats.totalErrors,\r\n startedAt: httpStats.startedAt < fetchStats.startedAt\r\n ? httpStats.startedAt\r\n : fetchStats.startedAt,\r\n };\r\n}\r\n\r\n/**\r\n * The main ReplayAPI SDK instance.\r\n */\r\nexport const replayApi = {\r\n init,\r\n stop,\r\n isActive,\r\n getStats,\r\n middleware: createMiddleware,\r\n};\r\n\r\n// Named exports for flexibility\r\nexport { init, stop, isActive, getStats, createMiddleware };\r\n\r\n// Re-export types\r\nexport type { ReplayApiConfig, ReplayApiInstance, CaptureStats } from \"./types.js\";\r\nexport type { MiddlewareOptions } from \"./middleware.js\";\r\n"]}
|