Skip to content

Commit 29876df

Browse files
fix(sdk): Set {{auto}} if user.ip_address is undefined and sendDefaultPii: true (#4466)
1 parent 4fabd63 commit 29876df

File tree

3 files changed

+249
-4
lines changed

3 files changed

+249
-4
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Version 7 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or hi
4040

4141
### Major Changes
4242

43-
- `ip addresses` is only collected when `sendDefaultPii`: `true`
43+
- Set `{{auto}}` if `user.ip_address` is `undefined` and `sendDefaultPii: true` ([#4466](https://github.com/getsentry/sentry-react-native/pull/4466))
4444
- Exceptions from `captureConsoleIntegration` are now marked as handled: true by default
4545
- `shutdownTimeout` moved from `core` to `@sentry/react-native`
4646
- `hasTracingEnabled` was renamed to `hasSpansEnabled`

packages/core/src/js/client.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ import type {
1010
TransportMakeRequestResponse,
1111
UserFeedback,
1212
} from '@sentry/core';
13-
import { BaseClient, dateTimestampInSeconds, logger, SentryError } from '@sentry/core';
13+
import {
14+
addAutoIpAddressToSession,
15+
addAutoIpAddressToUser,
16+
BaseClient,
17+
dateTimestampInSeconds,
18+
logger,
19+
SentryError,
20+
} from '@sentry/core';
1421
import { Alert } from 'react-native';
1522

1623
import { getDevServer } from './integrations/debugsymbolicatorutils';
@@ -48,6 +55,11 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
4855
super(options);
4956

5057
this._outcomesBuffer = [];
58+
59+
if (options.sendDefaultPii === true) {
60+
this.on('postprocessEvent', addAutoIpAddressToUser);
61+
this.on('beforeSendSession', addAutoIpAddressToSession);
62+
}
5163
}
5264

5365
/**

packages/core/test/client.test.ts

+235-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@ import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative'
22
jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative);
33

44
import { defaultStackParser } from '@sentry/browser';
5-
import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/core';
6-
import { rejectedSyncPromise, SentryError } from '@sentry/core';
5+
import type {
6+
Envelope,
7+
Event,
8+
Outcome,
9+
SessionAggregates,
10+
Transport,
11+
TransportMakeRequestResponse,
12+
} from '@sentry/core';
13+
import {
14+
addAutoIpAddressToSession,
15+
addAutoIpAddressToUser,
16+
makeSession,
17+
rejectedSyncPromise,
18+
SentryError,
19+
} from '@sentry/core';
720
import * as RN from 'react-native';
821

922
import { ReactNativeClient } from '../src/js/client';
@@ -625,6 +638,206 @@ describe('Tests ReactNativeClient', () => {
625638
client.recordDroppedEvent('before_send', 'error');
626639
}
627640
});
641+
642+
describe('ipAddress', () => {
643+
let mockTransportSend: jest.Mock;
644+
let client: ReactNativeClient;
645+
646+
beforeEach(() => {
647+
mockTransportSend = jest.fn(() => Promise.resolve());
648+
client = new ReactNativeClient({
649+
...DEFAULT_OPTIONS,
650+
dsn: EXAMPLE_DSN,
651+
transport: () => ({
652+
send: mockTransportSend,
653+
flush: jest.fn(),
654+
}),
655+
sendDefaultPii: true,
656+
});
657+
});
658+
659+
test('preserves ip_address null', () => {
660+
client.captureEvent({
661+
user: {
662+
ip_address: null,
663+
},
664+
});
665+
666+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user).toEqual(
667+
expect.objectContaining({ ip_address: null }),
668+
);
669+
});
670+
671+
test('preserves ip_address value if set', () => {
672+
client.captureEvent({
673+
user: {
674+
ip_address: '203.45.167.89',
675+
},
676+
});
677+
678+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user).toEqual(
679+
expect.objectContaining({ ip_address: '203.45.167.89' }),
680+
);
681+
});
682+
683+
test('adds ip_address {{auto}} to user if set to undefined', () => {
684+
client.captureEvent({
685+
user: {
686+
ip_address: undefined,
687+
},
688+
});
689+
690+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user).toEqual(
691+
expect.objectContaining({ ip_address: '{{auto}}' }),
692+
);
693+
});
694+
695+
test('adds ip_address {{auto}} to user if not set', () => {
696+
client.captureEvent({
697+
user: {},
698+
});
699+
700+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user).toEqual(
701+
expect.objectContaining({ ip_address: '{{auto}}' }),
702+
);
703+
});
704+
705+
test('adds ip_address {{auto}} to undefined user', () => {
706+
client.captureEvent({});
707+
708+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user).toEqual(
709+
expect.objectContaining({ ip_address: '{{auto}}' }),
710+
);
711+
});
712+
713+
test('does not add ip_address {{auto}} to undefined user if sendDefaultPii is false', () => {
714+
const { client, onSpy } = createClientWithSpy({
715+
transport: () => ({
716+
send: mockTransportSend,
717+
flush: jest.fn(),
718+
}),
719+
sendDefaultPii: false,
720+
});
721+
722+
client.captureEvent({});
723+
724+
expect(onSpy).not.toHaveBeenCalledWith('postprocessEvent', addAutoIpAddressToUser);
725+
expect(
726+
mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].user?.ip_address,
727+
).toBeUndefined();
728+
});
729+
730+
test('uses ip address hooks if sendDefaultPii is true', () => {
731+
const { onSpy } = createClientWithSpy({
732+
sendDefaultPii: true,
733+
});
734+
735+
expect(onSpy).toHaveBeenCalledWith('postprocessEvent', addAutoIpAddressToUser);
736+
expect(onSpy).toHaveBeenCalledWith('beforeSendSession', addAutoIpAddressToSession);
737+
});
738+
739+
test('does not add ip_address {{auto}} to session if sendDefaultPii is false', () => {
740+
const { client, onSpy } = createClientWithSpy({
741+
release: 'test', // required for sessions to be sent
742+
transport: () => ({
743+
send: mockTransportSend,
744+
flush: jest.fn(),
745+
}),
746+
sendDefaultPii: false,
747+
});
748+
749+
const session = makeSession();
750+
session.ipAddress = undefined;
751+
client.captureSession(session);
752+
753+
expect(onSpy).not.toHaveBeenCalledWith('beforeSendSession', addAutoIpAddressToSession);
754+
expect(
755+
mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].attrs.ip_address,
756+
).toBeUndefined();
757+
});
758+
759+
test('does not add ip_address {{auto}} to session aggregate if sendDefaultPii is false', () => {
760+
const { client, onSpy } = createClientWithSpy({
761+
release: 'test', // required for sessions to be sent
762+
transport: () => ({
763+
send: mockTransportSend,
764+
flush: jest.fn(),
765+
}),
766+
sendDefaultPii: false,
767+
});
768+
769+
const session: SessionAggregates = {
770+
aggregates: [],
771+
};
772+
client.sendSession(session);
773+
774+
expect(onSpy).not.toHaveBeenCalledWith('beforeSendSession', addAutoIpAddressToSession);
775+
expect(
776+
mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].attrs.ip_address,
777+
).toBeUndefined();
778+
});
779+
780+
test('does not overwrite session aggregate ip_address if already set', () => {
781+
const { client } = createClientWithSpy({
782+
release: 'test', // required for sessions to be sent
783+
transport: () => ({
784+
send: mockTransportSend,
785+
flush: jest.fn(),
786+
}),
787+
sendDefaultPii: true,
788+
});
789+
790+
const session: SessionAggregates = {
791+
aggregates: [],
792+
attrs: {
793+
ip_address: '123.45.67.89',
794+
},
795+
};
796+
client.sendSession(session);
797+
798+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].attrs.ip_address).toBe(
799+
'123.45.67.89',
800+
);
801+
});
802+
803+
test('does add ip_address {{auto}} to session if sendDefaultPii is true', () => {
804+
const { client } = createClientWithSpy({
805+
release: 'test', // required for sessions to be sent
806+
transport: () => ({
807+
send: mockTransportSend,
808+
flush: jest.fn(),
809+
}),
810+
sendDefaultPii: true,
811+
});
812+
813+
const session = makeSession();
814+
session.ipAddress = undefined;
815+
client.captureSession(session);
816+
817+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].attrs.ip_address).toBe(
818+
'{{auto}}',
819+
);
820+
});
821+
822+
test('does not overwrite session ip_address if already set', () => {
823+
const { client } = createClientWithSpy({
824+
release: 'test', // required for sessions to be sent
825+
transport: () => ({
826+
send: mockTransportSend,
827+
flush: jest.fn(),
828+
}),
829+
sendDefaultPii: true,
830+
});
831+
832+
const session = makeSession();
833+
session.ipAddress = '123.45.67.89';
834+
client.captureSession(session);
835+
836+
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload].attrs.ip_address).toBe(
837+
'123.45.67.89',
838+
);
839+
});
840+
});
628841
});
629842

630843
function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeClientOptions {
@@ -638,3 +851,23 @@ function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeC
638851
...options,
639852
};
640853
}
854+
855+
function createClientWithSpy(options: Partial<ReactNativeClientOptions>) {
856+
const onSpy = jest.fn();
857+
class SpyClient extends ReactNativeClient {
858+
public on(hook: string, callback: unknown): () => void {
859+
onSpy(hook, callback);
860+
// @ts-expect-error - the public interface doesn't allow string and unknown
861+
return super.on(hook, callback);
862+
}
863+
}
864+
865+
return {
866+
client: new SpyClient({
867+
...DEFAULT_OPTIONS,
868+
dsn: EXAMPLE_DSN,
869+
...options,
870+
}),
871+
onSpy,
872+
};
873+
}

0 commit comments

Comments
 (0)