Skip to content

Commit c2dea94

Browse files
feat: Ability to programmatically disable tracking (#1067)
* feat: Ability to programmatically disable tracking * refactor: deep nested conditions
1 parent 3835ce1 commit c2dea94

File tree

5 files changed

+84
-1
lines changed

5 files changed

+84
-1
lines changed

packages/core/src/analytics.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ export class SegmentClient {
115115
* Observable to know when the client is fully initialized and ready to send events to destination
116116
*/
117117
readonly isReady = new Observable<boolean>(false);
118+
/**
119+
* Access or subscribe to client enabled
120+
*/
121+
readonly enabled: Watchable<boolean> & Settable<boolean>;
118122
/**
119123
* Access or subscribe to client context
120124
*/
@@ -247,6 +251,12 @@ export class SegmentClient {
247251
onChange: this.store.deepLinkData.onChange,
248252
};
249253

254+
this.enabled = {
255+
get: this.store.enabled.get,
256+
set: this.store.enabled.set,
257+
onChange: this.store.enabled.onChange,
258+
};
259+
250260
// add segment destination plugin unless
251261
// asked not to via configuration.
252262
if (this.config.autoAddSegmentDestination === true) {
@@ -476,7 +486,9 @@ export class SegmentClient {
476486
async process(incomingEvent: SegmentEvent, enrichment?: EnrichmentClosure) {
477487
const event = this.applyRawEventData(incomingEvent);
478488
event.enrichment = enrichment;
479-
489+
if (this.enabled.get() === false) {
490+
return;
491+
}
480492
if (this.isReady.value) {
481493
return this.startTimelineProcessing(event);
482494
} else {

packages/core/src/storage/sovranStorage.ts

+47
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Data = {
4040
userInfo: UserInfoState;
4141
filters: DestinationFilters;
4242
pendingEvents: SegmentEvent[];
43+
enabled: boolean;
4344
};
4445

4546
const INITIAL_VALUES: Data = {
@@ -54,6 +55,7 @@ const INITIAL_VALUES: Data = {
5455
traits: undefined,
5556
},
5657
pendingEvents: [],
58+
enabled: true,
5759
};
5860

5961
const isEverythingReady = (state: ReadinessStore) =>
@@ -185,6 +187,9 @@ export class SovranStorage implements Storage {
185187
Settable<SegmentEvent[]> &
186188
Queue<SegmentEvent, SegmentEvent[]>;
187189

190+
readonly enabledStore: Store<{ enabled: boolean }>;
191+
readonly enabled: Watchable<boolean> & Settable<boolean>;
192+
188193
constructor(config: StorageConfig) {
189194
this.storeId = config.storeId;
190195
this.storePersistor = config.storePersistor;
@@ -195,6 +200,7 @@ export class SovranStorage implements Storage {
195200
hasRestoredUserInfo: false,
196201
hasRestoredFilters: false,
197202
hasRestoredPendingEvents: false,
203+
hasRestoredEnabled: false,
198204
});
199205

200206
const markAsReadyGenerator = (key: keyof ReadinessStore) => () => {
@@ -490,6 +496,47 @@ export class SovranStorage implements Storage {
490496
this.deepLinkStore.subscribe(callback),
491497
};
492498

499+
this.enabledStore = createStore(
500+
{ enabled: INITIAL_VALUES.enabled },
501+
{
502+
persist: {
503+
storeId: `${this.storeId}-enabled`,
504+
persistor: this.storePersistor,
505+
saveDelay: this.storePersistorSaveDelay,
506+
onInitialized: markAsReadyGenerator('hasRestoredEnabled'),
507+
},
508+
}
509+
);
510+
// Accessor object for enabled
511+
this.enabled = {
512+
get: createGetter(
513+
() => {
514+
const state = this.enabledStore.getState();
515+
return state.enabled;
516+
},
517+
async () => {
518+
const value = await this.enabledStore.getState(true);
519+
return value.enabled;
520+
}
521+
),
522+
523+
onChange: (callback: (value: boolean) => void) => {
524+
return this.enabledStore.subscribe((store) => {
525+
callback(store.enabled);
526+
});
527+
},
528+
529+
set: async (value: boolean | ((prev: boolean) => boolean)) => {
530+
const { enabled } = await this.enabledStore.dispatch((state) => {
531+
const newEnabled =
532+
value instanceof Function ? value(state.enabled) : value;
533+
return { enabled: newEnabled };
534+
});
535+
return enabled;
536+
},
537+
};
538+
539+
493540
this.fixAnonymousId();
494541
}
495542

packages/core/src/storage/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface ReadinessStore {
5959
hasRestoredUserInfo: boolean;
6060
hasRestoredFilters: boolean;
6161
hasRestoredPendingEvents: boolean;
62+
hasRestoredEnabled: boolean;
6263
}
6364

6465
/**
@@ -91,6 +92,8 @@ export interface Storage {
9192
readonly pendingEvents: Watchable<SegmentEvent[]> &
9293
Settable<SegmentEvent[]> &
9394
Queue<SegmentEvent, SegmentEvent[]>;
95+
96+
readonly enabled: Watchable<boolean> & Settable<boolean>;
9497
}
9598
export type DeepLinkData = {
9699
referring_application: string;

packages/core/src/test-helpers/mockSegmentStore.ts

+20
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { createGetter } from '../storage/helpers';
2424

2525
export type StoreData = {
2626
isReady: boolean;
27+
enabled: boolean;
2728
context?: DeepPartial<Context>;
2829
settings: SegmentAPIIntegrations;
2930
consentSettings?: SegmentAPIConsentSettings;
@@ -36,6 +37,7 @@ export type StoreData = {
3637

3738
const INITIAL_VALUES: StoreData = {
3839
isReady: true,
40+
enabled: true,
3941
context: undefined,
4042
settings: {
4143
[SEGMENT_DESTINATION_KEY]: {},
@@ -90,6 +92,7 @@ export class MockSegmentStore implements Storage {
9092
userInfo: createCallbackManager<UserInfoState>(),
9193
deepLinkData: createCallbackManager<DeepLinkData>(),
9294
pendingEvents: createCallbackManager<SegmentEvent[]>(),
95+
enabled: createCallbackManager<boolean>(),
9396
};
9497

9598
readonly isReady = {
@@ -103,6 +106,23 @@ export class MockSegmentStore implements Storage {
103106
},
104107
};
105108

109+
readonly enabled = {
110+
get: createMockStoreGetter(() => {
111+
return this.data.enabled;
112+
}),
113+
onChange: (_callback: (value: boolean) => void) => {
114+
return () => {
115+
return;
116+
};
117+
},
118+
set: async (value: boolean | ((prev: boolean) => boolean)) => {
119+
this.data.enabled =
120+
value instanceof Function ? value(this.data.enabled ?? true) : value;
121+
this.callbacks.enabled.run(this.data.enabled);
122+
return this.data.enabled;
123+
},
124+
};
125+
106126
readonly context: Watchable<DeepPartial<Context> | undefined> &
107127
Settable<DeepPartial<Context>> = {
108128
get: createMockStoreGetter(() => ({ ...this.data.context })),

packages/core/src/test-helpers/setupSegmentClient.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const createTestClient = (
1414
) => {
1515
const store = new MockSegmentStore({
1616
isReady: true,
17+
enabled: true,
1718
...storeData,
1819
});
1920

0 commit comments

Comments
 (0)