@reproapp/node-sdk 0.0.29 → 0.0.30
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.js +137 -20
- package/package.json +2 -2
- package/src/index.ts +130 -15
- package/test/init-auto-http-server.test.js +170 -0
package/dist/index.js
CHANGED
|
@@ -295,6 +295,7 @@ const REQUEST_START_HEADER = 'x-bug-request-start';
|
|
|
295
295
|
const REPRO_QUERY_SESSION_PARAM = '__repro_sid';
|
|
296
296
|
const REPRO_QUERY_ACTION_PARAM = '__repro_aid';
|
|
297
297
|
const REPRO_QUERY_START_PARAM = '__repro_start';
|
|
298
|
+
const REPRO_REQUEST_MIDDLEWARE_RAN = Symbol.for('__repro_request_middleware_ran');
|
|
298
299
|
const KAFKA_HEADER_TRACE_ID = 'x-repro-trace-id';
|
|
299
300
|
const KAFKA_HEADER_SPAN_ID = 'x-repro-span-id';
|
|
300
301
|
const KAFKA_HEADER_PARENT_SPAN_ID = 'x-repro-parent-span-id';
|
|
@@ -4027,6 +4028,22 @@ function reproMiddleware(cfg) {
|
|
|
4027
4028
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
4028
4029
|
patchMongoDb(cfg);
|
|
4029
4030
|
return async function (req, res, next) {
|
|
4031
|
+
const reqAny = req;
|
|
4032
|
+
if (reqAny?.[REPRO_REQUEST_MIDDLEWARE_RAN]) {
|
|
4033
|
+
return next();
|
|
4034
|
+
}
|
|
4035
|
+
try {
|
|
4036
|
+
Object.defineProperty(reqAny, REPRO_REQUEST_MIDDLEWARE_RAN, {
|
|
4037
|
+
value: true,
|
|
4038
|
+
configurable: true,
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
catch {
|
|
4042
|
+
try {
|
|
4043
|
+
reqAny[REPRO_REQUEST_MIDDLEWARE_RAN] = true;
|
|
4044
|
+
}
|
|
4045
|
+
catch { }
|
|
4046
|
+
}
|
|
4030
4047
|
const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
|
|
4031
4048
|
const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
|
|
4032
4049
|
const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
|
|
@@ -4063,30 +4080,50 @@ function reproMiddleware(cfg) {
|
|
|
4063
4080
|
const maskReq = { method: String(req.method || 'GET').toUpperCase(), path: urlPathOnly, key };
|
|
4064
4081
|
const requestHeaders = sanitizeHeaders(req.headers, cfg.captureHeaders);
|
|
4065
4082
|
beginSessionRequest(sid);
|
|
4066
|
-
// ---- response body capture
|
|
4083
|
+
// ---- response body capture ----
|
|
4067
4084
|
let capturedBody = undefined;
|
|
4068
|
-
const
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4085
|
+
const originalJson = res.json;
|
|
4086
|
+
if (typeof originalJson === 'function') {
|
|
4087
|
+
const origJson = originalJson.bind(res);
|
|
4088
|
+
res.json = (body) => { capturedBody = body; return origJson(body); };
|
|
4089
|
+
}
|
|
4090
|
+
const originalSend = res.send;
|
|
4091
|
+
if (typeof originalSend === 'function') {
|
|
4092
|
+
const origSend = originalSend.bind(res);
|
|
4093
|
+
res.send = (body) => {
|
|
4094
|
+
if (capturedBody === undefined) {
|
|
4095
|
+
capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
|
|
4096
|
+
}
|
|
4097
|
+
return origSend(body);
|
|
4098
|
+
};
|
|
4099
|
+
}
|
|
4100
|
+
const origWrite = typeof res.write === 'function'
|
|
4101
|
+
? res.write.bind(res)
|
|
4102
|
+
: null;
|
|
4103
|
+
const origEnd = typeof res.end === 'function'
|
|
4104
|
+
? res.end.bind(res)
|
|
4105
|
+
: null;
|
|
4079
4106
|
const chunks = [];
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4107
|
+
if (origWrite) {
|
|
4108
|
+
res.write = (chunk, ...args) => {
|
|
4109
|
+
try {
|
|
4110
|
+
if (chunk != null)
|
|
4111
|
+
chunks.push(chunk);
|
|
4112
|
+
}
|
|
4113
|
+
catch { }
|
|
4114
|
+
return origWrite(chunk, ...args);
|
|
4115
|
+
};
|
|
4083
4116
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4117
|
+
if (origEnd) {
|
|
4118
|
+
res.end = (chunk, ...args) => {
|
|
4119
|
+
try {
|
|
4120
|
+
if (chunk != null)
|
|
4121
|
+
chunks.push(chunk);
|
|
4122
|
+
}
|
|
4123
|
+
catch { }
|
|
4124
|
+
return origEnd(chunk, ...args);
|
|
4125
|
+
};
|
|
4088
4126
|
}
|
|
4089
|
-
catch { } return origEnd(chunk, ...args); };
|
|
4090
4127
|
// ---- our ALS (unchanged) ----
|
|
4091
4128
|
const tracerApiWithTrace = __TRACER__;
|
|
4092
4129
|
const runInTrace = (fn) => {
|
|
@@ -8092,9 +8129,13 @@ const REPRO_AUTO_MIDDLEWARE_INSTALLED = Symbol.for('__repro_auto_middleware_inst
|
|
|
8092
8129
|
const REPRO_AUTO_NEST_FACTORY_PATCHED = Symbol.for('__repro_auto_nest_factory_patched');
|
|
8093
8130
|
const REPRO_AUTO_EXPRESS_LISTEN_PATCHED = Symbol.for('__repro_auto_express_listen_patched');
|
|
8094
8131
|
const REPRO_AUTO_NEST_LISTEN_PATCHED = Symbol.for('__repro_auto_nest_listen_patched');
|
|
8132
|
+
const REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED = Symbol.for('__repro_auto_http_create_server_patched');
|
|
8133
|
+
const REPRO_AUTO_HTTP_SERVER_PATCHED = Symbol.for('__repro_auto_http_server_patched');
|
|
8134
|
+
const REPRO_AUTO_HTTP_LISTENER_WRAPPED = Symbol.for('__repro_auto_http_listener_wrapped');
|
|
8095
8135
|
const REPRO_AUTO_MONGOOSE_PLUGIN_PATCHED = Symbol.for('__repro_auto_mongoose_plugin_patched');
|
|
8096
8136
|
let reproAutoMiddlewareConfig = null;
|
|
8097
8137
|
const mongoosePluginConfigs = [];
|
|
8138
|
+
const httpRequestListenerWrappers = new WeakMap();
|
|
8098
8139
|
let frameworkRequireHookInstalled = false;
|
|
8099
8140
|
let mongooseRequireHookInstalled = false;
|
|
8100
8141
|
function hasMarker(target, marker) {
|
|
@@ -8199,6 +8240,81 @@ function patchExpressFromModuleExports(expressModule) {
|
|
|
8199
8240
|
setMarker(wrappedListen, REPRO_AUTO_EXPRESS_LISTEN_PATCHED);
|
|
8200
8241
|
application.listen = wrappedListen;
|
|
8201
8242
|
}
|
|
8243
|
+
function wrapHttpRequestListener(listener, cfg) {
|
|
8244
|
+
if (typeof listener !== 'function')
|
|
8245
|
+
return listener;
|
|
8246
|
+
if (hasMarker(listener, REPRO_AUTO_HTTP_LISTENER_WRAPPED))
|
|
8247
|
+
return listener;
|
|
8248
|
+
const existing = httpRequestListenerWrappers.get(listener);
|
|
8249
|
+
if (existing)
|
|
8250
|
+
return existing;
|
|
8251
|
+
const middleware = reproMiddleware(cfg);
|
|
8252
|
+
const wrapped = function reproPatchedHttpRequestListener(req, res) {
|
|
8253
|
+
return middleware(req, res, (err) => {
|
|
8254
|
+
if (err) {
|
|
8255
|
+
try {
|
|
8256
|
+
if (!res.headersSent) {
|
|
8257
|
+
res.statusCode = 500;
|
|
8258
|
+
res.end('Internal Server Error');
|
|
8259
|
+
return undefined;
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
catch { }
|
|
8263
|
+
throw err;
|
|
8264
|
+
}
|
|
8265
|
+
return listener.call(this, req, res);
|
|
8266
|
+
});
|
|
8267
|
+
};
|
|
8268
|
+
setMarker(wrapped, REPRO_AUTO_HTTP_LISTENER_WRAPPED);
|
|
8269
|
+
httpRequestListenerWrappers.set(listener, wrapped);
|
|
8270
|
+
return wrapped;
|
|
8271
|
+
}
|
|
8272
|
+
function patchHttpServerRequestListenerMethods(server) {
|
|
8273
|
+
const cfg = reproAutoMiddlewareConfig;
|
|
8274
|
+
if (!cfg || !server || hasMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED))
|
|
8275
|
+
return;
|
|
8276
|
+
for (const methodName of ['on', 'addListener', 'prependListener', 'once']) {
|
|
8277
|
+
const original = server[methodName];
|
|
8278
|
+
if (typeof original !== 'function')
|
|
8279
|
+
continue;
|
|
8280
|
+
server[methodName] = function reproPatchedHttpServerListenerMethod(...args) {
|
|
8281
|
+
if (args[0] === 'request' && typeof args[1] === 'function' && reproAutoMiddlewareConfig) {
|
|
8282
|
+
args[1] = wrapHttpRequestListener(args[1], reproAutoMiddlewareConfig);
|
|
8283
|
+
}
|
|
8284
|
+
return original.apply(this, args);
|
|
8285
|
+
};
|
|
8286
|
+
}
|
|
8287
|
+
setMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED);
|
|
8288
|
+
}
|
|
8289
|
+
function patchHttpCreateServerFromModuleExports(httpModule) {
|
|
8290
|
+
const original = httpModule?.createServer;
|
|
8291
|
+
if (!httpModule || typeof original !== 'function')
|
|
8292
|
+
return;
|
|
8293
|
+
if (hasMarker(original, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED))
|
|
8294
|
+
return;
|
|
8295
|
+
const wrappedCreateServer = function reproPatchedHttpCreateServer(...args) {
|
|
8296
|
+
const cfg = reproAutoMiddlewareConfig;
|
|
8297
|
+
if (cfg) {
|
|
8298
|
+
const listenerIndex = typeof args[0] === 'function'
|
|
8299
|
+
? 0
|
|
8300
|
+
: typeof args[1] === 'function'
|
|
8301
|
+
? 1
|
|
8302
|
+
: -1;
|
|
8303
|
+
if (listenerIndex >= 0) {
|
|
8304
|
+
args[listenerIndex] = wrapHttpRequestListener(args[listenerIndex], cfg);
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
const server = original.apply(this, args);
|
|
8308
|
+
patchHttpServerRequestListenerMethods(server);
|
|
8309
|
+
return server;
|
|
8310
|
+
};
|
|
8311
|
+
setMarker(wrappedCreateServer, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED);
|
|
8312
|
+
httpModule.createServer = wrappedCreateServer;
|
|
8313
|
+
}
|
|
8314
|
+
function patchHttpServerAutoAttach() {
|
|
8315
|
+
patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:http') ?? requireOptionalHostModule('http'));
|
|
8316
|
+
patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:https') ?? requireOptionalHostModule('https'));
|
|
8317
|
+
}
|
|
8202
8318
|
function patchKnownFrameworkModules() {
|
|
8203
8319
|
const nestModules = new Set();
|
|
8204
8320
|
const resolvedNest = requireOptionalHostModule('@nestjs/core');
|
|
@@ -8502,6 +8618,7 @@ async function initRepro(cfg) {
|
|
|
8502
8618
|
};
|
|
8503
8619
|
shareRuntimePrivacyState(reproAutoMiddlewareConfig, integrationCfg);
|
|
8504
8620
|
installFrameworkRequireHook();
|
|
8621
|
+
patchHttpServerAutoAttach();
|
|
8505
8622
|
patchKnownFrameworkModules();
|
|
8506
8623
|
patchHttpClients({
|
|
8507
8624
|
resolveContext: cfg.resolveContext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Repro Nest SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
13
|
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
|
-
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js",
|
|
15
|
+
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js",
|
|
16
16
|
"test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -313,6 +313,7 @@ const REQUEST_START_HEADER = 'x-bug-request-start';
|
|
|
313
313
|
const REPRO_QUERY_SESSION_PARAM = '__repro_sid';
|
|
314
314
|
const REPRO_QUERY_ACTION_PARAM = '__repro_aid';
|
|
315
315
|
const REPRO_QUERY_START_PARAM = '__repro_start';
|
|
316
|
+
const REPRO_REQUEST_MIDDLEWARE_RAN = Symbol.for('__repro_request_middleware_ran');
|
|
316
317
|
const KAFKA_HEADER_TRACE_ID = 'x-repro-trace-id';
|
|
317
318
|
const KAFKA_HEADER_SPAN_ID = 'x-repro-span-id';
|
|
318
319
|
const KAFKA_HEADER_PARENT_SPAN_ID = 'x-repro-parent-span-id';
|
|
@@ -4916,6 +4917,19 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4916
4917
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
4917
4918
|
patchMongoDb(cfg);
|
|
4918
4919
|
return async function (req: Request, res: Response, next: NextFunction) {
|
|
4920
|
+
const reqAny = req as any;
|
|
4921
|
+
if (reqAny?.[REPRO_REQUEST_MIDDLEWARE_RAN]) {
|
|
4922
|
+
return next();
|
|
4923
|
+
}
|
|
4924
|
+
try {
|
|
4925
|
+
Object.defineProperty(reqAny, REPRO_REQUEST_MIDDLEWARE_RAN, {
|
|
4926
|
+
value: true,
|
|
4927
|
+
configurable: true,
|
|
4928
|
+
});
|
|
4929
|
+
} catch {
|
|
4930
|
+
try { reqAny[REPRO_REQUEST_MIDDLEWARE_RAN] = true; } catch {}
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4919
4933
|
const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
|
|
4920
4934
|
const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
|
|
4921
4935
|
const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
|
|
@@ -4955,24 +4969,44 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4955
4969
|
const requestHeaders = sanitizeHeaders(req.headers, cfg.captureHeaders);
|
|
4956
4970
|
beginSessionRequest(sid);
|
|
4957
4971
|
|
|
4958
|
-
// ---- response body capture
|
|
4972
|
+
// ---- response body capture ----
|
|
4959
4973
|
let capturedBody: any = undefined;
|
|
4960
|
-
const
|
|
4961
|
-
(
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4974
|
+
const originalJson = (res as any).json;
|
|
4975
|
+
if (typeof originalJson === 'function') {
|
|
4976
|
+
const origJson = originalJson.bind(res as any);
|
|
4977
|
+
(res as any).json = (body: any) => { capturedBody = body; return origJson(body); };
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
const originalSend = (res as any).send;
|
|
4981
|
+
if (typeof originalSend === 'function') {
|
|
4982
|
+
const origSend = originalSend.bind(res as any);
|
|
4983
|
+
(res as any).send = (body: any) => {
|
|
4984
|
+
if (capturedBody === undefined) {
|
|
4985
|
+
capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
|
|
4986
|
+
}
|
|
4987
|
+
return origSend(body);
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4970
4990
|
|
|
4971
|
-
const origWrite = (res as any).write
|
|
4972
|
-
|
|
4991
|
+
const origWrite = typeof (res as any).write === 'function'
|
|
4992
|
+
? (res as any).write.bind(res as any)
|
|
4993
|
+
: null;
|
|
4994
|
+
const origEnd = typeof (res as any).end === 'function'
|
|
4995
|
+
? (res as any).end.bind(res as any)
|
|
4996
|
+
: null;
|
|
4973
4997
|
const chunks: Array<Buffer | string> = [];
|
|
4974
|
-
|
|
4975
|
-
|
|
4998
|
+
if (origWrite) {
|
|
4999
|
+
(res as any).write = (chunk: any, ...args: any[]) => {
|
|
5000
|
+
try { if (chunk != null) chunks.push(chunk); } catch {}
|
|
5001
|
+
return origWrite(chunk, ...args);
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
if (origEnd) {
|
|
5005
|
+
(res as any).end = (chunk?: any, ...args: any[]) => {
|
|
5006
|
+
try { if (chunk != null) chunks.push(chunk); } catch {}
|
|
5007
|
+
return origEnd(chunk, ...args);
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
4976
5010
|
|
|
4977
5011
|
// ---- our ALS (unchanged) ----
|
|
4978
5012
|
const tracerApiWithTrace = __TRACER__ as (TracerApi & { withTrace?: (id: string, fn: () => void) => void }) | null;
|
|
@@ -9331,10 +9365,14 @@ const REPRO_AUTO_MIDDLEWARE_INSTALLED = Symbol.for('__repro_auto_middleware_inst
|
|
|
9331
9365
|
const REPRO_AUTO_NEST_FACTORY_PATCHED = Symbol.for('__repro_auto_nest_factory_patched');
|
|
9332
9366
|
const REPRO_AUTO_EXPRESS_LISTEN_PATCHED = Symbol.for('__repro_auto_express_listen_patched');
|
|
9333
9367
|
const REPRO_AUTO_NEST_LISTEN_PATCHED = Symbol.for('__repro_auto_nest_listen_patched');
|
|
9368
|
+
const REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED = Symbol.for('__repro_auto_http_create_server_patched');
|
|
9369
|
+
const REPRO_AUTO_HTTP_SERVER_PATCHED = Symbol.for('__repro_auto_http_server_patched');
|
|
9370
|
+
const REPRO_AUTO_HTTP_LISTENER_WRAPPED = Symbol.for('__repro_auto_http_listener_wrapped');
|
|
9334
9371
|
const REPRO_AUTO_MONGOOSE_PLUGIN_PATCHED = Symbol.for('__repro_auto_mongoose_plugin_patched');
|
|
9335
9372
|
|
|
9336
9373
|
let reproAutoMiddlewareConfig: ReproMiddlewareConfig | null = null;
|
|
9337
9374
|
const mongoosePluginConfigs: ReproMiddlewareConfig[] = [];
|
|
9375
|
+
const httpRequestListenerWrappers = new WeakMap<Function, Function>();
|
|
9338
9376
|
let frameworkRequireHookInstalled = false;
|
|
9339
9377
|
let mongooseRequireHookInstalled = false;
|
|
9340
9378
|
|
|
@@ -9435,6 +9473,82 @@ function patchExpressFromModuleExports(expressModule: any): void {
|
|
|
9435
9473
|
application.listen = wrappedListen;
|
|
9436
9474
|
}
|
|
9437
9475
|
|
|
9476
|
+
function wrapHttpRequestListener(listener: any, cfg: ReproMiddlewareConfig): any {
|
|
9477
|
+
if (typeof listener !== 'function') return listener;
|
|
9478
|
+
if (hasMarker(listener, REPRO_AUTO_HTTP_LISTENER_WRAPPED)) return listener;
|
|
9479
|
+
|
|
9480
|
+
const existing = httpRequestListenerWrappers.get(listener);
|
|
9481
|
+
if (existing) return existing;
|
|
9482
|
+
|
|
9483
|
+
const middleware = reproMiddleware(cfg);
|
|
9484
|
+
const wrapped = function reproPatchedHttpRequestListener(this: any, req: any, res: any) {
|
|
9485
|
+
return middleware(req, res, (err?: any) => {
|
|
9486
|
+
if (err) {
|
|
9487
|
+
try {
|
|
9488
|
+
if (!res.headersSent) {
|
|
9489
|
+
res.statusCode = 500;
|
|
9490
|
+
res.end('Internal Server Error');
|
|
9491
|
+
return undefined;
|
|
9492
|
+
}
|
|
9493
|
+
} catch {}
|
|
9494
|
+
throw err;
|
|
9495
|
+
}
|
|
9496
|
+
return listener.call(this, req, res);
|
|
9497
|
+
});
|
|
9498
|
+
};
|
|
9499
|
+
setMarker(wrapped, REPRO_AUTO_HTTP_LISTENER_WRAPPED);
|
|
9500
|
+
httpRequestListenerWrappers.set(listener, wrapped);
|
|
9501
|
+
return wrapped;
|
|
9502
|
+
}
|
|
9503
|
+
|
|
9504
|
+
function patchHttpServerRequestListenerMethods(server: any): void {
|
|
9505
|
+
const cfg = reproAutoMiddlewareConfig;
|
|
9506
|
+
if (!cfg || !server || hasMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED)) return;
|
|
9507
|
+
|
|
9508
|
+
for (const methodName of ['on', 'addListener', 'prependListener', 'once']) {
|
|
9509
|
+
const original = server[methodName];
|
|
9510
|
+
if (typeof original !== 'function') continue;
|
|
9511
|
+
server[methodName] = function reproPatchedHttpServerListenerMethod(this: any, ...args: any[]) {
|
|
9512
|
+
if (args[0] === 'request' && typeof args[1] === 'function' && reproAutoMiddlewareConfig) {
|
|
9513
|
+
args[1] = wrapHttpRequestListener(args[1], reproAutoMiddlewareConfig);
|
|
9514
|
+
}
|
|
9515
|
+
return original.apply(this, args);
|
|
9516
|
+
};
|
|
9517
|
+
}
|
|
9518
|
+
setMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED);
|
|
9519
|
+
}
|
|
9520
|
+
|
|
9521
|
+
function patchHttpCreateServerFromModuleExports(httpModule: any): void {
|
|
9522
|
+
const original = httpModule?.createServer;
|
|
9523
|
+
if (!httpModule || typeof original !== 'function') return;
|
|
9524
|
+
if (hasMarker(original, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED)) return;
|
|
9525
|
+
|
|
9526
|
+
const wrappedCreateServer = function reproPatchedHttpCreateServer(this: any, ...args: any[]) {
|
|
9527
|
+
const cfg = reproAutoMiddlewareConfig;
|
|
9528
|
+
if (cfg) {
|
|
9529
|
+
const listenerIndex =
|
|
9530
|
+
typeof args[0] === 'function'
|
|
9531
|
+
? 0
|
|
9532
|
+
: typeof args[1] === 'function'
|
|
9533
|
+
? 1
|
|
9534
|
+
: -1;
|
|
9535
|
+
if (listenerIndex >= 0) {
|
|
9536
|
+
args[listenerIndex] = wrapHttpRequestListener(args[listenerIndex], cfg);
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9539
|
+
const server = original.apply(this, args);
|
|
9540
|
+
patchHttpServerRequestListenerMethods(server);
|
|
9541
|
+
return server;
|
|
9542
|
+
};
|
|
9543
|
+
setMarker(wrappedCreateServer, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED);
|
|
9544
|
+
httpModule.createServer = wrappedCreateServer;
|
|
9545
|
+
}
|
|
9546
|
+
|
|
9547
|
+
function patchHttpServerAutoAttach(): void {
|
|
9548
|
+
patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:http') ?? requireOptionalHostModule('http'));
|
|
9549
|
+
patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:https') ?? requireOptionalHostModule('https'));
|
|
9550
|
+
}
|
|
9551
|
+
|
|
9438
9552
|
function patchKnownFrameworkModules(): void {
|
|
9439
9553
|
const nestModules = new Set<any>();
|
|
9440
9554
|
const resolvedNest = requireOptionalHostModule('@nestjs/core');
|
|
@@ -9742,6 +9856,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
9742
9856
|
shareRuntimePrivacyState(reproAutoMiddlewareConfig, integrationCfg);
|
|
9743
9857
|
|
|
9744
9858
|
installFrameworkRequireHook();
|
|
9859
|
+
patchHttpServerAutoAttach();
|
|
9745
9860
|
patchKnownFrameworkModules();
|
|
9746
9861
|
|
|
9747
9862
|
patchHttpClients({
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
|
|
2
|
+
process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
|
|
3
|
+
process.env.TRACE_IDLE_FLUSH_MS = '10';
|
|
4
|
+
process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
|
|
5
|
+
|
|
6
|
+
const assert = require('node:assert/strict');
|
|
7
|
+
const http = require('node:http');
|
|
8
|
+
const mongodb = require('mongodb');
|
|
9
|
+
|
|
10
|
+
const originalNativeMongoInsertOne = mongodb.Collection.prototype.insertOne;
|
|
11
|
+
mongodb.Collection.prototype.insertOne = async function reproFakeNativeMongoInsertOne(doc, options) {
|
|
12
|
+
assert.equal(options.session.id, 'auto-http-native-session-1');
|
|
13
|
+
doc._id = doc._id ?? 'subject-auto-http-1';
|
|
14
|
+
return { acknowledged: true, insertedId: doc._id };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const { initRepro, reproMiddleware } = require('../dist');
|
|
18
|
+
const { flushIngestQueue } = require('../dist/ingest/client');
|
|
19
|
+
|
|
20
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
|
|
22
|
+
async function waitFor(predicate, timeoutMs = 3000) {
|
|
23
|
+
const deadline = Date.now() + timeoutMs;
|
|
24
|
+
while (Date.now() < deadline) {
|
|
25
|
+
if (predicate()) return;
|
|
26
|
+
await sleep(25);
|
|
27
|
+
}
|
|
28
|
+
assert(predicate(), 'timed out waiting for expected condition');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flattenEvents(posts) {
|
|
32
|
+
return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function backendRequests(posts, sessionId) {
|
|
36
|
+
return flattenEvents(posts).filter((event) => {
|
|
37
|
+
return event?.event_type === 'backend_request' && event?.session_id === sessionId;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function dbChanges(posts, sessionId) {
|
|
42
|
+
return flattenEvents(posts)
|
|
43
|
+
.filter((event) => event?.event_type === 'db_change' && event?.session_id === sessionId)
|
|
44
|
+
.flatMap((event) => Array.isArray(event?.payload?.db) ? event.payload.db : []);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function listen(server) {
|
|
48
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
49
|
+
return server.address().port;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function close(server) {
|
|
53
|
+
await new Promise((resolve, reject) => {
|
|
54
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function request(port, sessionId, actionId) {
|
|
59
|
+
await new Promise((resolve, reject) => {
|
|
60
|
+
const req = http.request({
|
|
61
|
+
host: '127.0.0.1',
|
|
62
|
+
port,
|
|
63
|
+
path: '/subjects',
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'content-type': 'application/json',
|
|
67
|
+
'x-bug-session-id': sessionId,
|
|
68
|
+
'x-bug-action-id': actionId,
|
|
69
|
+
'x-bug-request-start': String(Date.now()),
|
|
70
|
+
},
|
|
71
|
+
}, (res) => {
|
|
72
|
+
res.resume();
|
|
73
|
+
res.on('end', resolve);
|
|
74
|
+
});
|
|
75
|
+
req.on('error', reject);
|
|
76
|
+
req.end(JSON.stringify({ ok: true }));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const capturedBodies = [];
|
|
82
|
+
const originalFetch = global.fetch;
|
|
83
|
+
global.fetch = async (_url, init = {}) => {
|
|
84
|
+
capturedBodies.push(JSON.parse(String(init.body || '{}')));
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
status: 202,
|
|
88
|
+
json: async () => ({ ok: true }),
|
|
89
|
+
text: async () => '{"ok":true}',
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const cfg = {
|
|
94
|
+
tenantId: 'TENANT_test',
|
|
95
|
+
appId: 'APP_test',
|
|
96
|
+
appSecret: 'secret',
|
|
97
|
+
appName: 'auto-http-test',
|
|
98
|
+
serviceName: 'Auto HTTP Test',
|
|
99
|
+
captureHeaders: false,
|
|
100
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
101
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let autoServer;
|
|
105
|
+
let doubleServer;
|
|
106
|
+
try {
|
|
107
|
+
await initRepro(cfg);
|
|
108
|
+
|
|
109
|
+
const subjectsCollection = new mongodb.MongoClient('mongodb://127.0.0.1:27017/clinical')
|
|
110
|
+
.db('clinical')
|
|
111
|
+
.collection('subjects');
|
|
112
|
+
|
|
113
|
+
autoServer = http.createServer(async (_req, res) => {
|
|
114
|
+
await subjectsCollection.insertOne(
|
|
115
|
+
{ subjectNumber: '10013038', status: 'screened' },
|
|
116
|
+
{
|
|
117
|
+
session: {
|
|
118
|
+
id: 'auto-http-native-session-1',
|
|
119
|
+
hasEnded: false,
|
|
120
|
+
inTransaction: () => true,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
res.setHeader('content-type', 'application/json');
|
|
125
|
+
res.end(JSON.stringify({ ok: true, mode: 'auto' }));
|
|
126
|
+
});
|
|
127
|
+
const autoPort = await listen(autoServer);
|
|
128
|
+
await request(autoPort, 'S_auto_http_only', 'A_auto_http_only');
|
|
129
|
+
await waitFor(() => backendRequests(capturedBodies, 'S_auto_http_only').length === 1);
|
|
130
|
+
await waitFor(() => dbChanges(capturedBodies, 'S_auto_http_only').length === 1);
|
|
131
|
+
assert.equal(backendRequests(capturedBodies, 'S_auto_http_only').length, 1);
|
|
132
|
+
const [nativeMongoChange] = dbChanges(capturedBodies, 'S_auto_http_only');
|
|
133
|
+
assert.equal(nativeMongoChange.type, 'mongo');
|
|
134
|
+
assert.equal(nativeMongoChange.collection, 'subjects');
|
|
135
|
+
assert.equal(nativeMongoChange.op, 'insertOne');
|
|
136
|
+
assert.equal(nativeMongoChange.pk._id, 'subject-auto-http-1');
|
|
137
|
+
assert.equal(nativeMongoChange.query.driver, 'mongodb');
|
|
138
|
+
assert.equal(nativeMongoChange.query.db, 'clinical');
|
|
139
|
+
assert.equal(nativeMongoChange.query.doc.subjectNumber, '10013038');
|
|
140
|
+
assert.equal(nativeMongoChange.query.options.session.__kind, 'mongodb_client_session');
|
|
141
|
+
assert.equal(nativeMongoChange.query.options.session.id, 'auto-http-native-session-1');
|
|
142
|
+
assert.equal(nativeMongoChange.after.subjectNumber, '10013038');
|
|
143
|
+
assert.equal(nativeMongoChange.resultMeta.insertedId, 'subject-auto-http-1');
|
|
144
|
+
|
|
145
|
+
const manual = reproMiddleware(cfg);
|
|
146
|
+
doubleServer = http.createServer((req, res) => {
|
|
147
|
+
manual(req, res, () => {
|
|
148
|
+
res.setHeader('content-type', 'application/json');
|
|
149
|
+
res.end(JSON.stringify({ ok: true, mode: 'manual-too' }));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
const doublePort = await listen(doubleServer);
|
|
153
|
+
await request(doublePort, 'S_auto_http_no_duplicate', 'A_auto_http_no_duplicate');
|
|
154
|
+
await waitFor(() => backendRequests(capturedBodies, 'S_auto_http_no_duplicate').length === 1);
|
|
155
|
+
assert.equal(backendRequests(capturedBodies, 'S_auto_http_no_duplicate').length, 1);
|
|
156
|
+
|
|
157
|
+
console.log('initRepro auto HTTP server middleware OK');
|
|
158
|
+
} finally {
|
|
159
|
+
if (autoServer?.listening) await close(autoServer);
|
|
160
|
+
if (doubleServer?.listening) await close(doubleServer);
|
|
161
|
+
await flushIngestQueue();
|
|
162
|
+
mongodb.Collection.prototype.insertOne = originalNativeMongoInsertOne;
|
|
163
|
+
global.fetch = originalFetch;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch((error) => {
|
|
168
|
+
console.error(error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|