@reproapp/node-sdk 0.0.24 → 0.0.26

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/src/index.ts CHANGED
@@ -1158,7 +1158,7 @@ type Ctx = {
1158
1158
 
1159
1159
  type RequestCaptureScopeState = {
1160
1160
  requestRid: string;
1161
- transport: 'http' | 'kafka' | 'background';
1161
+ transport: 'http' | 'kafka' | 'amqp' | 'background';
1162
1162
  backendRequestCount: number;
1163
1163
  traceBatchCount: number;
1164
1164
  dbChangeCount: number;
@@ -6419,7 +6419,7 @@ function cloneKafkaTelemetryValue(value: any): any {
6419
6419
  }
6420
6420
 
6421
6421
  async function materializeKafkaRequestBody(
6422
- cfg: KafkaJsPatchConfig,
6422
+ cfg: ReproMiddlewareConfig,
6423
6423
  maskReq: MaskRequestContext,
6424
6424
  body: Record<string, any>,
6425
6425
  ): Promise<InlinePrivacyMaterializationResult> {
@@ -7380,6 +7380,890 @@ export function patchKafkaJs(cfg: KafkaJsPatchConfig) {
7380
7380
  }
7381
7381
  }
7382
7382
 
7383
+ type AmqpHeaderMap = Record<string, any>;
7384
+ type AmqpContextResolver = () => { sid?: string; aid?: string } | undefined;
7385
+ type AmqpPropagationContext = {
7386
+ sid: string | null;
7387
+ aid: string | null;
7388
+ traceId: string | null;
7389
+ parentSpanId: string | null;
7390
+ requestRid: string | null;
7391
+ };
7392
+ const AMQP_INTERNAL_PUBLISH_DEPTH = Symbol.for('repro.amqplib.internalPublishDepth');
7393
+
7394
+ export type AmqplibPatchConfig = ReproMiddlewareConfig & {
7395
+ resolveContext?: AmqpContextResolver;
7396
+ };
7397
+
7398
+ function decodeAmqpHeaderValue(value: any): string | null {
7399
+ if (value === null || value === undefined) return null;
7400
+ if (typeof value === 'string') {
7401
+ const trimmed = value.trim();
7402
+ return trimmed.length ? trimmed : null;
7403
+ }
7404
+ if (Buffer.isBuffer(value)) {
7405
+ const decoded = value.toString('utf8').trim();
7406
+ return decoded.length ? decoded : null;
7407
+ }
7408
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
7409
+ return String(value);
7410
+ }
7411
+ return null;
7412
+ }
7413
+
7414
+ function readAmqpHeader(headers: any, key: string): string | null {
7415
+ if (!headers || typeof headers !== 'object') return null;
7416
+ const direct = decodeAmqpHeaderValue((headers as Record<string, any>)[key]);
7417
+ if (direct) return direct;
7418
+ const normalized = key.toLowerCase();
7419
+ for (const [entryKey, entryValue] of Object.entries(headers as Record<string, any>)) {
7420
+ if (entryKey.toLowerCase() !== normalized) continue;
7421
+ return decodeAmqpHeaderValue(entryValue);
7422
+ }
7423
+ return null;
7424
+ }
7425
+
7426
+ function cloneAmqpHeaders(headers: any): AmqpHeaderMap {
7427
+ if (!headers || typeof headers !== 'object' || Buffer.isBuffer(headers)) return {};
7428
+ const out: AmqpHeaderMap = {};
7429
+ for (const [key, value] of Object.entries(headers as Record<string, any>)) {
7430
+ out[key] = value;
7431
+ }
7432
+ return out;
7433
+ }
7434
+
7435
+ function withAmqpPropagationHeaders(
7436
+ headers: any,
7437
+ values: Record<string, string | null | undefined>,
7438
+ ): AmqpHeaderMap {
7439
+ const out = cloneAmqpHeaders(headers);
7440
+ for (const [key, value] of Object.entries(values)) {
7441
+ if (value === null || value === undefined) continue;
7442
+ const str = String(value).trim();
7443
+ if (!str.length) continue;
7444
+ out[key] = str;
7445
+ }
7446
+ return out;
7447
+ }
7448
+
7449
+ function normalizeAmqpHeadersForPayload(headers: any): Record<string, any> {
7450
+ const out: Record<string, any> = {};
7451
+ if (!headers || typeof headers !== 'object' || Buffer.isBuffer(headers)) return out;
7452
+ for (const [key, value] of Object.entries(headers as Record<string, any>)) {
7453
+ const decoded = decodeAmqpHeaderValue(value);
7454
+ if (decoded !== null) {
7455
+ out[key] = decoded;
7456
+ } else {
7457
+ const sanitized = sanitizeResultForMeta(value);
7458
+ if (sanitized !== undefined) out[key] = sanitized;
7459
+ }
7460
+ }
7461
+ return out;
7462
+ }
7463
+
7464
+ function hasAmqpPropagationValues(values: Record<string, string | null | undefined>): boolean {
7465
+ return Object.values(values).some((value) => {
7466
+ if (value === null || value === undefined) return false;
7467
+ return String(value).trim().length > 0;
7468
+ });
7469
+ }
7470
+
7471
+ function isAmqpOptionsObject(value: any): boolean {
7472
+ return Boolean(value && typeof value === 'object' && !Buffer.isBuffer(value) && typeof value !== 'function');
7473
+ }
7474
+
7475
+ function withAmqpPropagationOptions(
7476
+ options: any,
7477
+ values: Record<string, string | null | undefined>,
7478
+ ): Record<string, any> {
7479
+ const base = isAmqpOptionsObject(options) ? { ...options } : {};
7480
+ return {
7481
+ ...base,
7482
+ headers: withAmqpPropagationHeaders(base.headers, values),
7483
+ };
7484
+ }
7485
+
7486
+ function amqpContentSize(content: any): number | null {
7487
+ if (content === null || content === undefined) return null;
7488
+ if (typeof content === 'string') return Buffer.byteLength(content, 'utf8');
7489
+ if (Buffer.isBuffer(content)) return content.length;
7490
+ if (content instanceof Uint8Array) return content.byteLength;
7491
+ return null;
7492
+ }
7493
+
7494
+ function decodeAmqpContent(content: any): any {
7495
+ if (content === null || content === undefined) return null;
7496
+ if (typeof content === 'string') return content;
7497
+ if (Buffer.isBuffer(content)) return content.toString('utf8');
7498
+ if (content instanceof Uint8Array) return Buffer.from(content).toString('utf8');
7499
+ if (typeof content === 'number' || typeof content === 'boolean') return content;
7500
+ return sanitizeResultForMeta(content);
7501
+ }
7502
+
7503
+ function contentTypeLooksJson(contentType: any): boolean {
7504
+ return typeof contentType === 'string' && /\bjson\b/i.test(contentType);
7505
+ }
7506
+
7507
+ function parseAmqpJsonString(
7508
+ value: string,
7509
+ contentType?: any,
7510
+ ): { parsed: true; value: any } | { parsed: false; value: string } {
7511
+ const trimmed = value.trim();
7512
+ if (!trimmed) return { parsed: false, value };
7513
+ if (!contentTypeLooksJson(contentType) && !'{['.includes(trimmed[0]!)) {
7514
+ return { parsed: false, value };
7515
+ }
7516
+ try {
7517
+ return { parsed: true, value: JSON.parse(value) };
7518
+ } catch {
7519
+ return { parsed: false, value };
7520
+ }
7521
+ }
7522
+
7523
+ function decodeAmqpMessageContent(content: any, propertiesOrOptions?: any): {
7524
+ value: any;
7525
+ rawValue?: string;
7526
+ valueEncoding: string | null;
7527
+ } {
7528
+ const decoded = decodeAmqpContent(content);
7529
+ if (typeof decoded !== 'string') {
7530
+ return {
7531
+ value: decoded,
7532
+ valueEncoding: decoded === null ? null : typeof decoded,
7533
+ };
7534
+ }
7535
+ const parsed = parseAmqpJsonString(decoded, propertiesOrOptions?.contentType);
7536
+ if (parsed.parsed) {
7537
+ return {
7538
+ value: parsed.value,
7539
+ rawValue: decoded,
7540
+ valueEncoding: 'json',
7541
+ };
7542
+ }
7543
+ return {
7544
+ value: decoded,
7545
+ valueEncoding: Buffer.isBuffer(content) || content instanceof Uint8Array ? 'utf8' : 'string',
7546
+ };
7547
+ }
7548
+
7549
+ function buildAmqpMessagePayload(content: any, propertiesOrOptions?: any): Record<string, any> {
7550
+ const decoded = decodeAmqpMessageContent(content, propertiesOrOptions);
7551
+ const payload: Record<string, any> = {
7552
+ value: decoded.value,
7553
+ valueEncoding: decoded.valueEncoding,
7554
+ valueBytes: amqpContentSize(content),
7555
+ headers: normalizeAmqpHeadersForPayload(propertiesOrOptions?.headers),
7556
+ };
7557
+ if (decoded.rawValue !== undefined) {
7558
+ payload.rawValue = decoded.rawValue;
7559
+ }
7560
+ if (propertiesOrOptions?.contentType !== undefined) {
7561
+ payload.contentType = propertiesOrOptions.contentType;
7562
+ }
7563
+ if (propertiesOrOptions?.contentEncoding !== undefined) {
7564
+ payload.contentEncoding = propertiesOrOptions.contentEncoding;
7565
+ }
7566
+ if (propertiesOrOptions?.messageId !== undefined) {
7567
+ payload.messageId = propertiesOrOptions.messageId;
7568
+ }
7569
+ if (propertiesOrOptions?.correlationId !== undefined) {
7570
+ payload.correlationId = propertiesOrOptions.correlationId;
7571
+ }
7572
+ if (propertiesOrOptions?.replyTo !== undefined) {
7573
+ payload.replyTo = propertiesOrOptions.replyTo;
7574
+ }
7575
+ if (propertiesOrOptions?.type !== undefined) {
7576
+ payload.type = propertiesOrOptions.type;
7577
+ }
7578
+ return payload;
7579
+ }
7580
+
7581
+ function buildAmqpMessagePropertiesPayload(properties: any): Record<string, any> {
7582
+ if (!properties || typeof properties !== 'object') return {};
7583
+ const payload: Record<string, any> = {};
7584
+ const copyKeys = [
7585
+ 'contentType',
7586
+ 'contentEncoding',
7587
+ 'deliveryMode',
7588
+ 'priority',
7589
+ 'correlationId',
7590
+ 'replyTo',
7591
+ 'expiration',
7592
+ 'messageId',
7593
+ 'timestamp',
7594
+ 'type',
7595
+ 'userId',
7596
+ 'appId',
7597
+ 'clusterId',
7598
+ ];
7599
+ for (const key of copyKeys) {
7600
+ if (properties[key] !== undefined) payload[key] = sanitizeResultForMeta(properties[key]);
7601
+ }
7602
+ payload.headers = normalizeAmqpHeadersForPayload(properties.headers);
7603
+ return payload;
7604
+ }
7605
+
7606
+ function sanitizeAmqpQueue(queue: any): string {
7607
+ const value = typeof queue === 'string' ? queue.trim() : '';
7608
+ return value.length ? value : 'unknown-queue';
7609
+ }
7610
+
7611
+ function sanitizeAmqpExchange(exchange: any): string {
7612
+ if (exchange === undefined || exchange === null) return '';
7613
+ const value = typeof exchange === 'string' ? exchange.trim() : String(exchange).trim();
7614
+ return value;
7615
+ }
7616
+
7617
+ function sanitizeAmqpRoutingKey(routingKey: any): string {
7618
+ const value = typeof routingKey === 'string' ? routingKey.trim() : '';
7619
+ return value.length ? value : 'unknown-routing-key';
7620
+ }
7621
+
7622
+ function summarizeAmqpError(error: any): Record<string, unknown> {
7623
+ const message =
7624
+ typeof error?.message === 'string'
7625
+ ? error.message
7626
+ : error === null || error === undefined
7627
+ ? 'Unknown AMQP error'
7628
+ : String(error);
7629
+ return {
7630
+ name: typeof error?.name === 'string' ? error.name : undefined,
7631
+ message,
7632
+ code: error?.code ?? undefined,
7633
+ };
7634
+ }
7635
+
7636
+ function resolveAmqpContext(cfg: AmqplibPatchConfig, headers: any): AmqpPropagationContext {
7637
+ const ctx = getCtx();
7638
+ const fallback = cfg.resolveContext?.() ?? {};
7639
+ const tracerSpan = captureSpanContextFromTracer();
7640
+
7641
+ const sid = readAmqpHeader(headers, KAFKA_HEADER_SESSION_ID) ?? ctx.sid ?? fallback.sid ?? null;
7642
+ const aid = readAmqpHeader(headers, KAFKA_HEADER_ACTION_ID) ?? ctx.aid ?? fallback.aid ?? null;
7643
+ const traceId =
7644
+ readAmqpHeader(headers, KAFKA_HEADER_TRACE_ID) ??
7645
+ tracerSpan?.traceId ??
7646
+ __TRACER__?.getCurrentTraceId?.() ??
7647
+ null;
7648
+ const parentSpanId =
7649
+ readAmqpHeader(headers, KAFKA_HEADER_SPAN_ID) ??
7650
+ readAmqpHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID) ??
7651
+ null;
7652
+ const requestRid = readAmqpHeader(headers, KAFKA_HEADER_REQUEST_ID);
7653
+
7654
+ return { sid, aid, traceId, parentSpanId, requestRid };
7655
+ }
7656
+
7657
+ function withOptionalTraceContextSync<T>(traceId: string | null, fn: () => T): T {
7658
+ const tracerWithTrace = (__TRACER__ as (TracerApi & { withTrace?: <R>(id: string, runner: () => R) => R }) | null)?.withTrace;
7659
+ if (traceId && typeof tracerWithTrace === 'function') {
7660
+ let invoked = false;
7661
+ try {
7662
+ return tracerWithTrace(traceId, () => {
7663
+ invoked = true;
7664
+ return fn();
7665
+ });
7666
+ } catch (error) {
7667
+ if (invoked) throw error;
7668
+ return fn();
7669
+ }
7670
+ }
7671
+ return fn();
7672
+ }
7673
+
7674
+ function buildAmqpTraceEntries(
7675
+ actionId: string | null | undefined,
7676
+ requestRid: string,
7677
+ events: TraceEventRecord[],
7678
+ ): LegacyEntry[] {
7679
+ return buildKafkaTraceEntries(actionId, requestRid, events);
7680
+ }
7681
+
7682
+ function scheduleAmqpTraceFlush(
7683
+ cfg: AmqplibPatchConfig,
7684
+ amqpCtx: Ctx,
7685
+ actionId: string | null,
7686
+ requestRid: string,
7687
+ rawTraceEvents: any[],
7688
+ maskReq: MaskRequestContext,
7689
+ captureScope: RequestCaptureScopeState,
7690
+ ): void {
7691
+ const sid = amqpCtx.sid;
7692
+ if (!sid) return;
7693
+ const rawEvents = rawTraceEvents.slice();
7694
+
7695
+ scheduleSdkBackgroundWork(async () => {
7696
+ await als.run(amqpCtx, async () => {
7697
+ await drainPrivacyTasks(amqpCtx);
7698
+ const pendingEvents: PendingTraceEventRecord[] = [];
7699
+ for (const rawTraceEvent of rawEvents) {
7700
+ const evt = buildPendingKafkaTraceEvent(rawTraceEvent);
7701
+ if (evt) pendingEvents.push(evt);
7702
+ }
7703
+
7704
+ let entries: LegacyEntry[] = [];
7705
+ if (pendingEvents.length) {
7706
+ const masking = normalizeMaskingConfig(cfg.masking);
7707
+ const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
7708
+ const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
7709
+ cfg,
7710
+ maskReq,
7711
+ masking,
7712
+ resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
7713
+ });
7714
+ entries = buildAmqpTraceEntries(actionId, requestRid, materializedEvents);
7715
+ captureScope.traceBatchCount = entries.length;
7716
+ if (entries.length) {
7717
+ post(cfg, sid, { entries });
7718
+ }
7719
+ } else {
7720
+ captureScope.traceBatchCount = 0;
7721
+ }
7722
+
7723
+ post(cfg, sid, {
7724
+ entries: [{
7725
+ actionId: actionId ?? null,
7726
+ requestRid,
7727
+ requestCaptureManifest: buildRequestCaptureManifestPayload(
7728
+ captureScope,
7729
+ entries.length,
7730
+ ),
7731
+ t: alignedNow(),
7732
+ }],
7733
+ });
7734
+ });
7735
+ }, { priority: 'high' });
7736
+ }
7737
+
7738
+ function setAmqpPublishOptionsArg(
7739
+ args: any[],
7740
+ optionsIndex: number,
7741
+ nextOptions: Record<string, any>,
7742
+ callbackIndex: number,
7743
+ wrappedCallback?: (...callbackArgs: any[]) => any,
7744
+ ): any[] {
7745
+ const nextArgs = args.slice();
7746
+ while (nextArgs.length <= optionsIndex) {
7747
+ nextArgs.push(undefined);
7748
+ }
7749
+ nextArgs[optionsIndex] = nextOptions;
7750
+ if (callbackIndex >= 0 && wrappedCallback) {
7751
+ if (callbackIndex === optionsIndex) {
7752
+ nextArgs[optionsIndex + 1] = wrappedCallback;
7753
+ } else {
7754
+ nextArgs[callbackIndex] = wrappedCallback;
7755
+ }
7756
+ }
7757
+ return nextArgs;
7758
+ }
7759
+
7760
+ function scheduleAmqpPublishCapture(params: {
7761
+ cfg: AmqplibPatchConfig;
7762
+ sid: string | null;
7763
+ aid: string | null;
7764
+ publishRid: string;
7765
+ captureScope: RequestCaptureScopeState;
7766
+ method: 'AMQP_PUBLISH' | 'AMQP_SEND_TO_QUEUE';
7767
+ path: string;
7768
+ key: string;
7769
+ exchange?: string;
7770
+ routingKey?: string;
7771
+ queue?: string;
7772
+ content: any;
7773
+ options: any;
7774
+ span: SpanContext | null;
7775
+ t0: number;
7776
+ status: number;
7777
+ error?: any;
7778
+ result?: any;
7779
+ }): void {
7780
+ if (!params.sid) return;
7781
+ const maskReq: MaskRequestContext = {
7782
+ method: params.method,
7783
+ path: params.path,
7784
+ key: params.key,
7785
+ };
7786
+ const messagePayload = buildAmqpMessagePayload(params.content, params.options);
7787
+ const body = {
7788
+ transport: 'amqp',
7789
+ exchange: params.exchange,
7790
+ routingKey: params.routingKey,
7791
+ queue: params.queue,
7792
+ messageBytes: amqpContentSize(params.content),
7793
+ message: messagePayload,
7794
+ value: cloneKafkaTelemetryValue(messagePayload.value),
7795
+ rawValue: messagePayload.rawValue,
7796
+ valueEncoding: messagePayload.valueEncoding,
7797
+ valueBytes: messagePayload.valueBytes,
7798
+ properties: buildAmqpMessagePropertiesPayload(params.options),
7799
+ traceId: params.span?.traceId ?? null,
7800
+ spanId: params.span?.spanId ?? null,
7801
+ parentSpanId: params.span?.parentSpanId ?? null,
7802
+ };
7803
+
7804
+ scheduleSdkBackgroundWork(async () => {
7805
+ const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body);
7806
+ const requestPayload: Record<string, any> = {
7807
+ rid: params.publishRid,
7808
+ method: params.method,
7809
+ url: params.path,
7810
+ path: params.path,
7811
+ status: params.status,
7812
+ durMs: Date.now() - params.t0,
7813
+ key: params.key,
7814
+ headers: {
7815
+ transport: 'amqp',
7816
+ exchange: params.exchange,
7817
+ routingKey: params.routingKey,
7818
+ queue: params.queue,
7819
+ messageBytes: amqpContentSize(params.content),
7820
+ backpressure: params.result === false ? true : undefined,
7821
+ },
7822
+ body: bodyMaterialization.value,
7823
+ bodyMaterialization: bodyMaterialization.skipped,
7824
+ respBody: params.status >= 400
7825
+ ? { error: summarizeAmqpError(params.error) }
7826
+ : { accepted: params.result !== false, backpressure: params.result === false ? true : undefined },
7827
+ };
7828
+
7829
+ post(params.cfg, params.sid!, {
7830
+ entries: [{
7831
+ actionId: params.aid ?? null,
7832
+ request: requestPayload,
7833
+ t: alignedNow(),
7834
+ }],
7835
+ });
7836
+ params.captureScope.backendRequestCount += 1;
7837
+ post(params.cfg, params.sid!, {
7838
+ entries: [{
7839
+ actionId: params.aid ?? null,
7840
+ requestRid: params.publishRid,
7841
+ requestCaptureManifest: buildRequestCaptureManifestPayload(
7842
+ params.captureScope,
7843
+ 0,
7844
+ ),
7845
+ t: alignedNow(),
7846
+ }],
7847
+ });
7848
+ }, { priority: 'high' });
7849
+ }
7850
+
7851
+ function patchAmqplibPublishMethod(
7852
+ channel: any,
7853
+ cfg: AmqplibPatchConfig,
7854
+ methodName: 'publish' | 'sendToQueue',
7855
+ ) {
7856
+ const orig = channel?.[methodName];
7857
+ if (typeof orig !== 'function' || orig.__repro_amqplib_patched) return;
7858
+
7859
+ const optionsIndex = methodName === 'publish' ? 3 : 2;
7860
+ const method = methodName === 'publish' ? 'AMQP_PUBLISH' : 'AMQP_SEND_TO_QUEUE';
7861
+
7862
+ const wrapped = function reproPatchedAmqplibPublish(this: any, ...args: any[]) {
7863
+ if (methodName === 'publish' && Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) > 0) {
7864
+ return orig.apply(this, args);
7865
+ }
7866
+
7867
+ const ctx = getCtx();
7868
+ const fallback = cfg.resolveContext?.() ?? {};
7869
+ const sid = ctx.sid ?? fallback.sid ?? null;
7870
+ const aid = ctx.aid ?? fallback.aid ?? null;
7871
+ const span = captureSpanContextFromTracer();
7872
+ const publishRid = nextRequestId(alignedNow());
7873
+ const propagationValues = {
7874
+ [KAFKA_HEADER_TRACE_ID]: span?.traceId ?? null,
7875
+ [KAFKA_HEADER_SPAN_ID]: span?.spanId === null || span?.spanId === undefined ? null : String(span.spanId),
7876
+ [KAFKA_HEADER_PARENT_SPAN_ID]:
7877
+ span?.parentSpanId === null || span?.parentSpanId === undefined ? null : String(span.parentSpanId),
7878
+ [KAFKA_HEADER_REQUEST_ID]: publishRid,
7879
+ [KAFKA_HEADER_SESSION_ID]: sid,
7880
+ [KAFKA_HEADER_ACTION_ID]: aid,
7881
+ };
7882
+ const shouldPropagate = hasAmqpPropagationValues(propagationValues);
7883
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
7884
+ ? args.length - 1
7885
+ : -1;
7886
+ const originalOptions = callbackIndex === optionsIndex ? undefined : args[optionsIndex];
7887
+ const nextOptions = shouldPropagate
7888
+ ? withAmqpPropagationOptions(originalOptions, propagationValues)
7889
+ : originalOptions;
7890
+ const exchange = methodName === 'publish' ? sanitizeAmqpExchange(args[0]) : '';
7891
+ const routingKey = methodName === 'publish' ? sanitizeAmqpRoutingKey(args[1]) : undefined;
7892
+ const queue = methodName === 'sendToQueue' ? sanitizeAmqpQueue(args[0]) : undefined;
7893
+ const path = methodName === 'publish'
7894
+ ? `amqp://${exchange || 'default'}/${routingKey}`
7895
+ : `amqp://queue/${queue}`;
7896
+ const key = `${method} ${methodName === 'publish' ? `${exchange || 'default'}/${routingKey}` : queue}`;
7897
+ const content = methodName === 'publish' ? args[2] : args[1];
7898
+ const captureScope = createRequestCaptureScope(publishRid, 'amqp');
7899
+ const t0 = Date.now();
7900
+ let finished = false;
7901
+
7902
+ const finish = (status: number, error?: any, result?: any) => {
7903
+ if (finished) return;
7904
+ finished = true;
7905
+ scheduleAmqpPublishCapture({
7906
+ cfg,
7907
+ sid,
7908
+ aid,
7909
+ publishRid,
7910
+ captureScope,
7911
+ method,
7912
+ path,
7913
+ key,
7914
+ exchange,
7915
+ routingKey,
7916
+ queue,
7917
+ content,
7918
+ options: nextOptions,
7919
+ span,
7920
+ t0,
7921
+ status,
7922
+ error,
7923
+ result,
7924
+ });
7925
+ };
7926
+
7927
+ const originalCallback = callbackIndex >= 0 ? args[callbackIndex] : null;
7928
+ const wrappedCallback = typeof originalCallback === 'function'
7929
+ ? function reproAmqplibPublishCallback(this: any, ...callbackArgs: any[]) {
7930
+ const error = callbackArgs[0];
7931
+ finish(error ? 500 : 200, error, callbackArgs[1]);
7932
+ return originalCallback.apply(this, callbackArgs);
7933
+ }
7934
+ : undefined;
7935
+
7936
+ const nextArgs = shouldPropagate
7937
+ ? setAmqpPublishOptionsArg(args, optionsIndex, nextOptions, callbackIndex, wrappedCallback)
7938
+ : (wrappedCallback
7939
+ ? setAmqpPublishOptionsArg(args, optionsIndex, nextOptions, callbackIndex, wrappedCallback)
7940
+ : args);
7941
+ let bumpedInternalPublishDepth = false;
7942
+
7943
+ try {
7944
+ if (methodName === 'sendToQueue' && this && typeof this === 'object') {
7945
+ this[AMQP_INTERNAL_PUBLISH_DEPTH] = Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) + 1;
7946
+ bumpedInternalPublishDepth = true;
7947
+ }
7948
+ const result = orig.apply(this, nextArgs);
7949
+ if (!wrappedCallback) {
7950
+ finish(200, undefined, result);
7951
+ }
7952
+ return result;
7953
+ } catch (error) {
7954
+ finish(500, error);
7955
+ throw error;
7956
+ } finally {
7957
+ if (bumpedInternalPublishDepth) {
7958
+ const nextDepth = Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) - 1;
7959
+ if (nextDepth > 0) {
7960
+ this[AMQP_INTERNAL_PUBLISH_DEPTH] = nextDepth;
7961
+ } else if (this && typeof this === 'object') {
7962
+ delete this[AMQP_INTERNAL_PUBLISH_DEPTH];
7963
+ }
7964
+ }
7965
+ }
7966
+ };
7967
+
7968
+ (wrapped as any).__repro_amqplib_patched = true;
7969
+ channel[methodName] = wrapped;
7970
+ }
7971
+
7972
+ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queue: any) {
7973
+ return function reproAmqplibConsumeMessage(this: any, message: any) {
7974
+ if (message === null || message === undefined) {
7975
+ return onMessage.call(this, message);
7976
+ }
7977
+
7978
+ const queueName = sanitizeAmqpQueue(queue);
7979
+ const properties = message?.properties ?? {};
7980
+ const headers = properties?.headers;
7981
+ const runtime = resolveAmqpContext(cfg, headers);
7982
+ const sid = runtime.sid;
7983
+ const aid = runtime.aid;
7984
+ const deliveryTag = message?.fields?.deliveryTag ?? alignedNow();
7985
+ const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
7986
+ const traceId = consumeRid;
7987
+ const captureScope = createRequestCaptureScope(consumeRid, 'amqp');
7988
+ const rawTraceEvents: any[] = [];
7989
+ const maskReq: MaskRequestContext = {
7990
+ method: 'AMQP_CONSUME',
7991
+ path: `amqp://queue/${queueName}`,
7992
+ key: `AMQP_CONSUME ${queueName}`,
7993
+ };
7994
+ const amqpCtx: Ctx = {
7995
+ sid: sid ?? undefined,
7996
+ aid: aid ?? undefined,
7997
+ rid: consumeRid,
7998
+ clockSkewMs: 0,
7999
+ excludedSpanIds: new Set<string>(),
8000
+ maskReq,
8001
+ privacyTasks: new Set<Promise<void>>(),
8002
+ captureScope,
8003
+ };
8004
+ let unsubscribe: undefined | (() => void);
8005
+
8006
+ const subscribeTrace = () => {
8007
+ if (!__TRACER__?.tracer?.on) return;
8008
+ try {
8009
+ unsubscribe = __TRACER__.tracer.on((event: any) => {
8010
+ if (!event || event.traceId !== traceId) return;
8011
+ rawTraceEvents.push(event);
8012
+ });
8013
+ } catch {}
8014
+ };
8015
+ const unsubscribeTrace = () => {
8016
+ try { unsubscribe && unsubscribe(); } catch {}
8017
+ unsubscribe = undefined;
8018
+ };
8019
+
8020
+ const invoke = () => {
8021
+ subscribeTrace();
8022
+ try {
8023
+ const result = onMessage.call(this, message);
8024
+ if (result && typeof result.then === 'function') {
8025
+ return result.finally(() => {
8026
+ unsubscribeTrace();
8027
+ });
8028
+ }
8029
+ unsubscribeTrace();
8030
+ return result;
8031
+ } catch (error) {
8032
+ unsubscribeTrace();
8033
+ throw error;
8034
+ }
8035
+ };
8036
+
8037
+ const runInAls = () => {
8038
+ if (!sid && !aid) return invoke();
8039
+ return als.run(amqpCtx, () => invoke());
8040
+ };
8041
+
8042
+ const t0 = Date.now();
8043
+ let finished = false;
8044
+ const finish = (threw: boolean, error?: any) => {
8045
+ if (finished) return;
8046
+ finished = true;
8047
+ if (!sid) return;
8048
+ const fields = message?.fields ?? {};
8049
+ const messagePayload = buildAmqpMessagePayload(message?.content, properties);
8050
+ const body = {
8051
+ transport: 'amqp',
8052
+ queue: queueName,
8053
+ exchange: sanitizeAmqpExchange(fields.exchange),
8054
+ routingKey: sanitizeAmqpRoutingKey(fields.routingKey),
8055
+ deliveryTag: fields.deliveryTag ?? null,
8056
+ redelivered: fields.redelivered ?? null,
8057
+ consumerTag: fields.consumerTag ?? null,
8058
+ parentRequestRid: runtime.requestRid,
8059
+ parentTraceId: runtime.traceId,
8060
+ parentSpanId: runtime.parentSpanId,
8061
+ messageBytes: amqpContentSize(message?.content),
8062
+ message: messagePayload,
8063
+ value: cloneKafkaTelemetryValue(messagePayload.value),
8064
+ rawValue: messagePayload.rawValue,
8065
+ valueEncoding: messagePayload.valueEncoding,
8066
+ valueBytes: messagePayload.valueBytes,
8067
+ properties: buildAmqpMessagePropertiesPayload(properties),
8068
+ };
8069
+
8070
+ scheduleSdkBackgroundWork(async () => {
8071
+ const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body);
8072
+ const requestPayload: Record<string, any> = {
8073
+ rid: consumeRid,
8074
+ method: 'AMQP_CONSUME',
8075
+ url: `amqp://queue/${queueName}`,
8076
+ path: `amqp://queue/${queueName}`,
8077
+ status: threw ? 500 : 200,
8078
+ durMs: Date.now() - t0,
8079
+ key: `AMQP_CONSUME ${queueName}`,
8080
+ headers: normalizeAmqpHeadersForPayload(headers),
8081
+ body: bodyMaterialization.value,
8082
+ bodyMaterialization: bodyMaterialization.skipped,
8083
+ respBody: threw ? { error: summarizeAmqpError(error) } : undefined,
8084
+ };
8085
+
8086
+ post(cfg, sid, {
8087
+ entries: [{
8088
+ actionId: aid ?? null,
8089
+ request: requestPayload,
8090
+ t: alignedNow(),
8091
+ }],
8092
+ });
8093
+ captureScope.backendRequestCount += 1;
8094
+ scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
8095
+ }, { priority: 'high' });
8096
+ };
8097
+
8098
+ try {
8099
+ const result = withOptionalTraceContextSync(traceId, runInAls);
8100
+ if (result && typeof (result as any).then === 'function') {
8101
+ return (result as Promise<any>).then(
8102
+ (value) => {
8103
+ finish(false);
8104
+ return value;
8105
+ },
8106
+ (error) => {
8107
+ finish(true, error);
8108
+ throw error;
8109
+ },
8110
+ );
8111
+ }
8112
+ finish(false);
8113
+ return result;
8114
+ } catch (error) {
8115
+ finish(true, error);
8116
+ throw error;
8117
+ }
8118
+ };
8119
+ }
8120
+
8121
+ function patchAmqplibChannel(channel: any, cfg: AmqplibPatchConfig) {
8122
+ if (!channel || channel.__repro_amqplib_channel_patched) return;
8123
+ channel.__repro_amqplib_channel_patched = true;
8124
+
8125
+ patchAmqplibPublishMethod(channel, cfg, 'publish');
8126
+ patchAmqplibPublishMethod(channel, cfg, 'sendToQueue');
8127
+
8128
+ const origConsume = channel.consume;
8129
+ if (typeof origConsume === 'function' && !origConsume.__repro_amqplib_patched) {
8130
+ const wrapped = function reproPatchedAmqplibConsume(this: any, queue: any, onMessage: any, ...rest: any[]) {
8131
+ if (typeof onMessage !== 'function') {
8132
+ return origConsume.call(this, queue, onMessage, ...rest);
8133
+ }
8134
+ return origConsume.call(this, queue, wrapAmqplibConsumeHandler(onMessage, cfg, queue), ...rest);
8135
+ };
8136
+ (wrapped as any).__repro_amqplib_patched = true;
8137
+ channel.consume = wrapped;
8138
+ }
8139
+ }
8140
+
8141
+ function wrapAmqplibChannelFactory(
8142
+ connection: any,
8143
+ cfg: AmqplibPatchConfig,
8144
+ methodName: 'createChannel' | 'createConfirmChannel',
8145
+ ) {
8146
+ const orig = connection?.[methodName];
8147
+ if (typeof orig !== 'function' || orig.__repro_amqplib_patched) return;
8148
+
8149
+ const wrapped = function reproPatchedAmqplibCreateChannel(this: any, ...args: any[]) {
8150
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
8151
+ ? args.length - 1
8152
+ : -1;
8153
+ const nextArgs = args.slice();
8154
+ if (callbackIndex >= 0) {
8155
+ const originalCallback = args[callbackIndex];
8156
+ nextArgs[callbackIndex] = function reproAmqplibCreateChannelCallback(this: any, error: any, channel: any, ...rest: any[]) {
8157
+ if (channel) patchAmqplibChannel(channel, cfg);
8158
+ return originalCallback.call(this, error, channel, ...rest);
8159
+ };
8160
+ }
8161
+
8162
+ const result = orig.apply(this, nextArgs);
8163
+ if (result && typeof result.then === 'function') {
8164
+ return result.then((channel: any) => {
8165
+ patchAmqplibChannel(channel, cfg);
8166
+ return channel;
8167
+ });
8168
+ }
8169
+ if (result) patchAmqplibChannel(result, cfg);
8170
+ return result;
8171
+ };
8172
+
8173
+ (wrapped as any).__repro_amqplib_patched = true;
8174
+ connection[methodName] = wrapped;
8175
+ }
8176
+
8177
+ function patchAmqplibConnection(connection: any, cfg: AmqplibPatchConfig) {
8178
+ if (!connection || connection.__repro_amqplib_connection_patched) return;
8179
+ connection.__repro_amqplib_connection_patched = true;
8180
+ wrapAmqplibChannelFactory(connection, cfg, 'createChannel');
8181
+ wrapAmqplibChannelFactory(connection, cfg, 'createConfirmChannel');
8182
+ }
8183
+
8184
+ function patchAmqplibModule(amqpModule: any, cfg: AmqplibPatchConfig) {
8185
+ if (!amqpModule || typeof amqpModule !== 'object') return;
8186
+ const targets = new Set<any>([amqpModule]);
8187
+ if (amqpModule.default && typeof amqpModule.default === 'object') {
8188
+ targets.add(amqpModule.default);
8189
+ }
8190
+
8191
+ for (const target of targets) {
8192
+ if (!target || target.__repro_amqplib_module_patched) continue;
8193
+ const origConnect = target.connect;
8194
+ if (typeof origConnect !== 'function') continue;
8195
+ target.__repro_amqplib_module_patched = true;
8196
+ target.connect = function reproPatchedAmqplibConnect(this: any, ...args: any[]) {
8197
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
8198
+ ? args.length - 1
8199
+ : -1;
8200
+ const nextArgs = args.slice();
8201
+ if (callbackIndex >= 0) {
8202
+ const originalCallback = args[callbackIndex];
8203
+ nextArgs[callbackIndex] = function reproAmqplibConnectCallback(this: any, error: any, connection: any, ...rest: any[]) {
8204
+ if (connection) patchAmqplibConnection(connection, cfg);
8205
+ return originalCallback.call(this, error, connection, ...rest);
8206
+ };
8207
+ }
8208
+
8209
+ const result = origConnect.apply(this, nextArgs);
8210
+ if (result && typeof result.then === 'function') {
8211
+ return result.then((connection: any) => {
8212
+ patchAmqplibConnection(connection, cfg);
8213
+ return connection;
8214
+ });
8215
+ }
8216
+ if (result) patchAmqplibConnection(result, cfg);
8217
+ return result;
8218
+ };
8219
+ }
8220
+ }
8221
+
8222
+ const amqplibPatchConfigs: AmqplibPatchConfig[] = [];
8223
+ let amqplibRequireHookInstalled = false;
8224
+
8225
+ function installAmqplibRequireHook() {
8226
+ if (amqplibRequireHookInstalled) return;
8227
+ amqplibRequireHookInstalled = true;
8228
+
8229
+ try {
8230
+ const rim = requireOptionalHostModule('require-in-the-middle');
8231
+ const Hook = rim?.Hook ?? rim;
8232
+ if (typeof Hook !== 'function') return;
8233
+
8234
+ // eslint-disable-next-line no-new
8235
+ new Hook(['amqplib', 'amqplib/callback_api'], (exports: any) => {
8236
+ for (const cfg of amqplibPatchConfigs) {
8237
+ patchAmqplibModule(exports, cfg);
8238
+ }
8239
+ return exports;
8240
+ });
8241
+ } catch {}
8242
+ }
8243
+
8244
+ export function patchAmqplib(cfg: AmqplibPatchConfig) {
8245
+ amqplibPatchConfigs.push(cfg);
8246
+ installAmqplibRequireHook();
8247
+
8248
+ const amqpModules = new Set<any>();
8249
+ const resolvedAmqpModule = requireOptionalHostModule('amqplib');
8250
+ if (resolvedAmqpModule) {
8251
+ amqpModules.add(resolvedAmqpModule);
8252
+ }
8253
+ const resolvedAmqpCallbackModule = requireOptionalHostModule('amqplib/callback_api');
8254
+ if (resolvedAmqpCallbackModule) {
8255
+ amqpModules.add(resolvedAmqpCallbackModule);
8256
+ }
8257
+ for (const loadedAmqpModule of getLoadedModuleExportsByPackage('amqplib')) {
8258
+ amqpModules.add(loadedAmqpModule);
8259
+ }
8260
+ if (!amqpModules.size) return;
8261
+
8262
+ for (const amqpModule of amqpModules) {
8263
+ patchAmqplibModule(amqpModule, cfg);
8264
+ }
8265
+ }
8266
+
7383
8267
  export type RedisPatchConfig = ReproMiddlewareConfig & {
7384
8268
  resolveContext?: () => { sid?: string; aid?: string } | undefined;
7385
8269
  };
@@ -8129,6 +9013,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
8129
9013
  skipUrls: [resolveIngestUrl(ingestCfg)],
8130
9014
  });
8131
9015
  patchKafkaJs(integrationCfg);
9016
+ patchAmqplib(integrationCfg);
8132
9017
  patchRedis(integrationCfg);
8133
9018
  patchSendgridMail(integrationCfg);
8134
9019
  patchMongooseIntegration(reproAutoMiddlewareConfig);
@@ -8177,6 +9062,10 @@ export const __reproTestHooks = {
8177
9062
  materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
8178
9063
  patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
8179
9064
  wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
9065
+ patchAmqplibModuleForTest: patchAmqplibModule,
9066
+ patchAmqplibConnectionForTest: patchAmqplibConnection,
9067
+ patchAmqplibChannelForTest: patchAmqplibChannel,
9068
+ wrapAmqplibConsumeHandlerForTest: wrapAmqplibConsumeHandler,
8180
9069
  chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
8181
9070
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
8182
9071
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,