Skip to content

Issue #1327: apply resourceEventMapper to auto tracked XHR resources#1328

Open
teplymax wants to merge 1 commit into
DataDog:developfrom
teplymax:teplymax/apply-resourceEventMapper-to-auto-tracked-resources
Open

Issue #1327: apply resourceEventMapper to auto tracked XHR resources#1328
teplymax wants to merge 1 commit into
DataDog:developfrom
teplymax:teplymax/apply-resourceEventMapper-to-auto-tracked-resources

Conversation

@teplymax

@teplymax teplymax commented Jul 7, 2026

Copy link
Copy Markdown

Issue #1327

What does this PR do?

This PR applies resourceEventMapper to auto-tracked XHR resources before they are started
natively, so applications can sanitize resource URLs before they are reported to Datadog.

For example, a resource URL containing PII can be rewritten before reporting:

https://testurl.com?phone=12345

to:

https://testurl.com?phone=PHONE_PLACEHOLDER

With this change, the mapper can:

  • drop an auto-tracked XHR resource by returning null,
  • inspect and sanitize the resource URL through resourceContext.responseURL,
  • update resource context before native RUM tracking starts,
  • stay in sync when DdRum.registerResourceEventMapper or
    DdRum.unregisterResourceEventMapper is called after SDK initialization.

The main implementation changes are:

  • DdSdkReactNative.initialize passes the configured resourceEventMapper to
    DdRumResourceTracking.startTracking.
  • DdRum.startResource applies the mapper before calling native startResource; if the
    mapper returns null, no native resource is started.
  • DdRum.stopResource no longer sends the _dd.resource.drop_resource marker when the
    mapper returns null, because dropped resources are now skipped before native start.
  • DdRum.registerResourceEventMapper and DdRum.unregisterResourceEventMapper update the
    active XHR tracking proxy.
  • DdRumResourceTracking.updateResourceEventMapper forwards mapper changes to the request
    proxy when resource tracking is active.
  • XHRProxy.onTrackingUpdate supports partial updates, so tracing sample rate and mapper
    updates can be applied independently.
  • ResourceReporter converts Datadog's internal RUMResource into a
    mapper-compatible resource event before running mappers.
  • ResourceEvent is exported and the native resource mapper type now includes
    resourceContext.

Motivation

We need to sanitize resource URLs reported to Datadog to prevent leaking PII through query
parameters or other URL components.

The current automatic XHR resource tracking flow does not give resourceEventMapper enough
control at the point where the native resource is started.

Filtering decisions that depend on the request URL can happen too late: the native resource
has already been started, and dropping the event requires sending a native-side drop marker
later during stopResource.

This makes it difficult to use resourceEventMapper as the single JavaScript-level place for
resource filtering and sanitization. It also creates a gap between frontend and mobile
instrumentation: our frontend can already sanitize resource URLs before reporting them, and we
want mobile apps to support the same privacy guarantees and implementation model.

The main use cases are:

  • replacing PII values in resource URLs with placeholders before reporting,
  • dropping non-first-party resources,
  • dropping resources when an application-level condition is not met,
  • sanitizing the resource URL before it is passed to native RUM tracking,
  • keeping auto-tracked XHR resources aligned with manually tracked resource mapper behavior.

Additional Notes

The important behavior change is that the keep/drop decision now happens before native
startResource.

DdRum.startResource builds a mapper-compatible resource event with the original resource key,
default XHR metadata, context, timestamp, and the request URL exposed as
resourceContext.responseURL:

const mappedEvent = this.resourceEventMapper.applyEventMapper({
    key,
    statusCode: 0,
    kind: 'xhr',
    size: -1,
    context,
    timestampMs,
    resourceContext: { responseURL: url } as XMLHttpRequest
});

if (!mappedEvent) {
    return generateEmptyPromise();
}

const startUrl = mappedEvent.resourceContext?.responseURL ?? url;

For kept resources, native tracking starts with the original resource key for lifecycle
correlation and the mapped URL/context for reporting:

this.nativeRum.startResource(
    key,
    method,
    startUrl,
    encodeAttributes(mappedEvent.context),
    timestampMs
);

If the mapper returns null later during stopResource, JavaScript returns without calling
native code. A resource dropped at start time has no native resource entry to stop:

if (!mappedEvent) {
    return generateEmptyPromise();
}

For auto-tracked XHR resources, ResourceReporter converts the internal RUMResource into the
shape expected by ResourceEventMapper before running mappers:

const toReportedResourceEvent = (resource: RUMResource): ReportedResourceEvent => ({
    ...resource,
    statusCode: resource.response.statusCode,
    kind: resource.request.kind,
    size: resource.response.size,
    context: {},
    timestampMs: resource.timings.stopTime,
    resourceContext: resource.resourceContext,
    attributes: {}
});

Mappers run sequentially against the latest mapped resource. Returning null drops the
resource; returning an object merges the mapper output back into the resource:

for (const mapper of this.getMappers()) {
    const mappedResource = mapper(modifiedResource);
    if (mappedResource === null) {
        return;
    }
    modifiedResource = {
        ...modifiedResource,
        ...mappedResource
    };
}

If the mapper sanitizes resourceContext.responseURL, that sanitized URL is used when
reporting the resource:

reportResource({
    ...modifiedResource,
    request: {
        ...modifiedResource.request,
        url:
            modifiedResource.resourceContext?.responseURL ??
            modifiedResource.request.url
    }
});

Runtime mapper registration is forwarded to active resource tracking:

registerResourceEventMapper(resourceEventMapper: ResourceEventMapper) {
    this.resourceEventMapper = generateResourceEventMapper(resourceEventMapper);
    DdRumResourceTracking.updateResourceEventMapper(resourceEventMapper);
}

unregisterResourceEventMapper() {
    this.resourceEventMapper = generateResourceEventMapper(undefined);
    DdRumResourceTracking.updateResourceEventMapper(undefined);
}

The chages were tested with appropriate patch being applied to production app:

@datadog+mobile-react-native+3.5.1.patch

Everything works as expected.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)
  • If this PR is auto-generated, please make sure also to manually update the code related to the change

Copilot AI review requested due to automatic review settings July 7, 2026 08:54
@teplymax teplymax requested a review from a team as a code owner July 7, 2026 08:54
@teplymax teplymax force-pushed the teplymax/apply-resourceEventMapper-to-auto-tracked-resources branch from 686202b to 440d774 Compare July 7, 2026 08:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates React Native RUM resource instrumentation so resourceEventMapper can sanitize or drop auto-tracked XHR resources before native RUM startResource is invoked, aligning auto-tracked behavior with manually tracked resources and improving privacy controls (e.g., URL PII redaction).

Changes:

  • Apply resourceEventMapper earlier in the resource lifecycle (notably in DdRum.startResource) and propagate mapper updates to active XHR tracking.
  • Extend resource event shapes/types to include resourceContext and export ResourceEvent for mapper compatibility.
  • Update XHR auto-tracking pipeline (XHRProxy/ResourceReporter) and add/adjust unit tests for the new behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts Pass mapper into ResourceReporter and support partial tracking updates (sample rate vs mapper).
packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/ResourceReporter.ts Convert internal RUMResource to mapper-compatible shape; run mappers and allow URL override via resourceContext.responseURL.
packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/internalDevResourceBlocklist.ts Update mapper typing to match the new ResourceMapper signature.
packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/tests/ResourceReporter.test.ts Add coverage for mapper-driven URL sanitization and runtime mapper updates.
packages/core/src/rum/instrumentation/resourceTracking/requestProxy/interfaces/RequestProxy.ts Allow partial updates in onTrackingUpdate (optional sample rate + optional mapper).
packages/core/src/rum/instrumentation/resourceTracking/DdRumResourceTracking.tsx Accept mapper at tracking start and add updateResourceEventMapper forwarding to the proxy.
packages/core/src/rum/instrumentation/resourceTracking/tests/DdRumResourceTracking.test.ts Add coverage for registering/unregistering resource mapper after tracking starts.
packages/core/src/rum/eventMappers/resourceEventMapper.ts Export ResourceEvent and include resourceContext in the native mapper type path.
packages/core/src/rum/DdRum.ts Apply mapper at startResource (using resourceContext.responseURL) and forward mapper updates to resource tracking.
packages/core/src/rum/tests/DdRum.test.ts Update expectations for drop behavior (no native start/stop when mapper returns null).
packages/core/src/DdSdkReactNative.tsx Pass configured resourceEventMapper into DdRumResourceTracking.startTracking.
packages/core/src/tests/DdSdkReactNative.test.tsx Adjust expectations for the updated resource tracking initialization and start behavior.
Comments suppressed due to low confidence (1)

packages/core/src/rum/tests/DdRum.test.ts:1680

  • Current tests only cover the case where the resource mapper returns null for both start and stop. Add a test for the case where the mapper keeps the event at startResource() but returns null at stopResource() (e.g., based on statusCode), to ensure the native resource is still properly stopped/dropped and doesn't leak.
        it('does not start or stop the resource if the mapper returns null', async () => {
            const resourceEventMapper: ResourceEventMapper = resource => {
                return null;
            };


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

getTracingContext,
getTracingContextForPropagators
} from './instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders';
import { DdRumResourceTracking } from './instrumentation/resourceTracking/DdRumResourceTracking';
Comment on lines 519 to +523
registerResourceEventMapper(resourceEventMapper: ResourceEventMapper) {
this.resourceEventMapper = generateResourceEventMapper(
resourceEventMapper
);
DdRumResourceTracking.updateResourceEventMapper(resourceEventMapper);
Comment on lines 285 to +289
return bufferVoidNativeCall(() =>
this.nativeRum.startResource(
key,
method,
url,
encodeAttributes(context),
startUrl,
Comment on lines 319 to 321
if (!mappedEvent) {
/**
* To drop the resource we call `stopResource` and pass the `_dd.drop_resource` attribute in the context.
* It will be picked up by the resource mappers we implement on the native side that will drop the resource.
* This ensures we don't have any "started" resource left in memory on the native side.
*/
return bufferVoidNativeCall(() =>
this.nativeRum.stopResource(
key,
statusCode,
kind,
size,
{
'_dd.resource.drop_resource': true
},
timestampMs
)
);
return generateEmptyPromise();
}
Comment on lines +39 to 48
for (const mapper of this.getMappers()) {
const mappedResource = mapper(modifiedResource);
if (mappedResource === null) {
return;
}
modifiedResource = {
...modifiedResource,
...mappedResource
};
}
Copilot AI review requested due to automatic review settings July 7, 2026 09:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Comment on lines +314 to 321
/**
* We never drop at `stopResource`: the keep/drop decision was already
* made at `startResource`. A resource dropped there was never started,
* so this stop is a native no-op.
*/
if (!mappedEvent) {
/**
* To drop the resource we call `stopResource` and pass the `_dd.drop_resource` attribute in the context.
* It will be picked up by the resource mappers we implement on the native side that will drop the resource.
* This ensures we don't have any "started" resource left in memory on the native side.
*/
return bufferVoidNativeCall(() =>
this.nativeRum.stopResource(
key,
statusCode,
kind,
size,
{
'_dd.resource.drop_resource': true
},
timestampMs
)
);
return generateEmptyPromise();
}
Comment on lines +39 to 48
for (const mapper of this.getMappers()) {
const mappedResource = mapper(modifiedResource);
if (mappedResource === null) {
return;
}
modifiedResource = {
...modifiedResource,
...mappedResource
};
}
Comment on lines 34 to 37
/**
* Filters RN symbolicate calls and Expo logs calls that happen only in dev.
* @param resource RUMResource
*/
Comment on lines 250 to +265
startResource = (
key: string,
method: string,
url: string,
context: object = {},
timestampMs: number = this.timeProvider.now()
): Promise<void> => {
const mappedEvent = this.resourceEventMapper.applyEventMapper({
key,
statusCode: 0,
kind: 'xhr',
size: -1,
context,
timestampMs,
resourceContext: { responseURL: url } as XMLHttpRequest
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants