Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/sdk/electron/contract-tests/entity/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const capabilities = [
'client-per-context-summaries',
'track-hooks',
'tls:skip-verify-peer',
'secure-mode-hash',
'event-gzip',

/// ////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,17 @@ describe('given an event processor', () => {
const userObj = { key: 'user-key', kind: 'user', name: 'Example user', anonymous: true };
const context = Context.fromLDContext(userObj);

// Migration events are server-only; servers set redactAnonymousAllEvents: true so anonymous
// contexts are redacted.
const serverEventProcessor = new EventProcessor(
{ ...eventProcessorConfig, redactAnonymousAllEvents: true },
clientContext,
{},
new ContextDeduplicator(),
);

Date.now = jest.fn(() => 1000);
eventProcessor.sendEvent({
serverEventProcessor.sendEvent({
kind: 'migration_op',
operation: 'read',
creationDate: 1000,
Expand All @@ -488,7 +497,8 @@ describe('given an event processor', () => {
samplingRatio: 1,
});

await eventProcessor.flush();
await serverEventProcessor.flush();
serverEventProcessor.close();

const redactedContext = {
kind: 'user',
Expand Down Expand Up @@ -737,8 +747,46 @@ describe('given an event processor', () => {

await eventProcessor.flush();

// The custom event inlines the context, so an anonymous context's attributes are redacted.
// The index event is not subject to anonymous redaction and keeps the full context.
// By default (redactAnonymousAllEvents unset -> false, the client-side behavior) custom events
// do NOT redact an anonymous context's attributes; the full context is sent.
expect(mockSendEventData).toBeCalledWith(LDEventType.AnalyticsEvents, [
{
kind: 'index',
creationDate: 1000,
context: { ...anonUser, kind: 'user' },
},
{
kind: 'custom',
key: 'eventkey',
data: { thing: 'stuff' },
creationDate: 1000,
context: { ...anonUser, kind: 'user' },
},
]);
});

it('redacts anonymous context in custom event when redactAnonymousAllEvents is true', async () => {
// Server-side SDKs set redactAnonymousAllEvents: true so custom events redact an anonymous
// context's attributes, just like feature events.
const serverEventProcessor = new EventProcessor(
{ ...eventProcessorConfig, redactAnonymousAllEvents: true },
clientContext,
{},
new ContextDeduplicator(),
);

serverEventProcessor.sendEvent({
kind: 'custom',
creationDate: 1000,
context: Context.fromLDContext(anonUser),
key: 'eventkey',
data: { thing: 'stuff' },
samplingRatio: 1,
});

await serverEventProcessor.flush();
serverEventProcessor.close();

const redactedAnonUser = {
key: 'anon-user',
kind: 'user',
Expand Down
15 changes: 13 additions & 2 deletions packages/shared/common/src/internal/events/EventProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export interface EventProcessorOptions {
eventsCapacity: number;
flushInterval: number;
diagnosticRecordingInterval: number;
// Whether anonymous context attributes are redacted across all inlined events (custom and
// migration op events), in addition to feature events which always redact them. Defaults to
// false (redact only feature events, the client-side behavior); server-side SDKs set this to
// true so anonymous contexts are redacted on every event kind.
redactAnonymousAllEvents?: boolean;
}

function isMultiEventSummarizer(summarizer: unknown): summarizer is LDMultiEventSummarizer {
Expand Down Expand Up @@ -264,7 +269,10 @@ export default class EventProcessor implements LDEventProcessor {
const migrationEvent: MigrationOutputEvent = {
...inputEvent,
context: inputEvent.context
? this._contextFilter.filter(inputEvent.context, true)
? this._contextFilter.filter(
inputEvent.context,
this._config.redactAnonymousAllEvents ?? false,
)
: undefined,
};
if (migrationEvent.samplingRatio === 1) {
Expand Down Expand Up @@ -361,7 +369,10 @@ export default class EventProcessor implements LDEventProcessor {
kind: 'custom',
creationDate: event.creationDate,
key: event.key,
context: this._contextFilter.filter(event.context, true),
context: this._contextFilter.filter(
event.context,
this._config.redactAnonymousAllEvents ?? false,
),
};

if (event.samplingRatio !== 1) {
Expand Down
8 changes: 6 additions & 2 deletions packages/shared/sdk-server/src/LDClientImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ function constructFDv1(
eventProcessor = new NullEventProcessor();
} else {
eventProcessor = new internal.EventProcessor(
config,
// Server-side SDKs redact anonymous context attributes on all inlined events (feature,
// custom, migration op), not just feature events.
{ ...config, redactAnonymousAllEvents: true },
clientContext,
baseHeaders,
new ContextDeduplicator(config),
Expand Down Expand Up @@ -312,7 +314,9 @@ function constructFDv2(
eventProcessor = new NullEventProcessor();
} else {
eventProcessor = new internal.EventProcessor(
config,
// Server-side SDKs redact anonymous context attributes on all inlined events (feature,
// custom, migration op), not just feature events.
{ ...config, redactAnonymousAllEvents: true },
clientContext,
baseHeaders,
new ContextDeduplicator(config),
Expand Down
Loading