@metamask-previews/analytics-controller 3.0.0-preview-3e86f66f7 → 3.0.0-preview-db287901b

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.
@@ -21,20 +21,6 @@ export const controllerName = 'AnalyticsController';
21
21
  * `properties` or `sensitiveProperties` in storage indefinitely.
22
22
  */
23
23
  export const EVENT_FRAGMENT_MAX_AGE = 24 * 60 * 60 * 1000;
24
- /**
25
- * Purposes for which an analytics payload can be used.
26
- */
27
- export const AnalyticsPurpose = {
28
- Product: 'product',
29
- Marketing: 'marketing',
30
- };
31
- /**
32
- * Persisted queues on {@link AnalyticsControllerState}.
33
- */
34
- const AnalyticsQueue = {
35
- EventQueue: 'eventQueue',
36
- PreConsentEventQueue: 'preConsentEventQueue',
37
- };
38
24
  /**
39
25
  * Returns default values for AnalyticsController state.
40
26
  *
@@ -47,8 +33,6 @@ export function getDefaultAnalyticsControllerState() {
47
33
  return {
48
34
  optedIn: false,
49
35
  consentDecisionMade: false,
50
- optedInToMarketing: false,
51
- marketingConsentDecisionMade: false,
52
36
  };
53
37
  }
54
38
  /**
@@ -64,24 +48,6 @@ const analyticsControllerMetadata = {
64
48
  includeInDebugSnapshot: true,
65
49
  usedInUi: true,
66
50
  },
67
- optedInToMarketing: {
68
- includeInStateLogs: true,
69
- persist: true,
70
- includeInDebugSnapshot: true,
71
- usedInUi: true,
72
- },
73
- marketingConsentDecisionMade: {
74
- includeInStateLogs: true,
75
- persist: true,
76
- includeInDebugSnapshot: true,
77
- usedInUi: true,
78
- },
79
- eventsConfig: {
80
- includeInStateLogs: true,
81
- persist: true,
82
- includeInDebugSnapshot: true,
83
- usedInUi: false,
84
- },
85
51
  analyticsId: {
86
52
  includeInStateLogs: true,
87
53
  persist: true,
@@ -121,9 +87,6 @@ const MESSENGER_EXPOSED_METHODS = [
121
87
  'optIn',
122
88
  'optOut',
123
89
  'resetConsentDecision',
124
- 'optInToMarketing',
125
- 'optOutOfMarketing',
126
- 'resetMarketingConsentDecision',
127
90
  'createEventFragment',
128
91
  'upsertEventFragment',
129
92
  'updateEventFragment',
@@ -140,22 +103,6 @@ const MESSENGER_EXPOSED_METHODS = [
140
103
  function isRecord(value) {
141
104
  return value !== null && typeof value === 'object' && !Array.isArray(value);
142
105
  }
143
- function isAnalyticsPurpose(value) {
144
- return (value === AnalyticsPurpose.Product || value === AnalyticsPurpose.Marketing);
145
- }
146
- function isEventPurposesRecord(value) {
147
- return (isRecord(value) &&
148
- Object.values(value).every((purposes) => Array.isArray(purposes) &&
149
- purposes.length > 0 &&
150
- purposes.every(isAnalyticsPurpose)));
151
- }
152
- function isAnalyticsEventsConfig(value) {
153
- return (isRecord(value) &&
154
- typeof value.schemaVersion === 'string' &&
155
- typeof value.version === 'string' &&
156
- typeof value.timestamp === 'number' &&
157
- isEventPurposesRecord(value.events));
158
- }
159
106
  /**
160
107
  * Returns whether a JSON value is a non-array object.
161
108
  *
@@ -197,12 +144,7 @@ function isAnalyticsQueuedEvent(value) {
197
144
  return false;
198
145
  }
199
146
  if (typeof value.messageId !== 'string' ||
200
- typeof value.timestamp !== 'string' ||
201
- (value.eventPurposes !== undefined &&
202
- (!Array.isArray(value.eventPurposes) ||
203
- !value.eventPurposes.every(isAnalyticsPurpose))) ||
204
- (value.eventsConfigVersion !== undefined &&
205
- typeof value.eventsConfigVersion !== 'string')) {
147
+ typeof value.timestamp !== 'string') {
206
148
  return false;
207
149
  }
208
150
  if (value.type === 'track') {
@@ -243,10 +185,6 @@ function isAnalyticsEventFragment(value) {
243
185
  typeof value.successEvent === 'string') &&
244
186
  (value.failureEvent === undefined ||
245
187
  typeof value.failureEvent === 'string') &&
246
- (value.eventPurposes === undefined ||
247
- isEventPurposesRecord(value.eventPurposes)) &&
248
- (value.eventsConfigVersion === undefined ||
249
- typeof value.eventsConfigVersion === 'string') &&
250
188
  (value.context === undefined || isRecord(value.context)) &&
251
189
  (value.persist === undefined || typeof value.persist === 'boolean'));
252
190
  }
@@ -276,17 +214,18 @@ function mergeEventFragment(fragment, payload) {
276
214
  };
277
215
  }
278
216
  /**
279
- * Merges two optional analytics contexts.
217
+ * Merges two optional analytics contexts, preserving `undefined` when neither
218
+ * side has one so an empty context is never sent.
280
219
  *
281
220
  * @param base - The context to merge into.
282
- * @param override - The context whose fields win. When omitted, `base` is kept.
221
+ * @param override - The context whose fields win.
283
222
  * @returns The merged context, or `undefined` when both sides are unset.
284
223
  */
285
224
  function mergeEventFragmentContext(base, override) {
286
- if (override === undefined) {
287
- return base;
225
+ if (base === undefined && override === undefined) {
226
+ return undefined;
288
227
  }
289
- return { ...(base ?? {}), ...override };
228
+ return { ...(base ?? {}), ...(override ?? {}) };
290
229
  }
291
230
  /**
292
231
  * The AnalyticsController manages analytics tracking across platforms (Mobile/Extension).
@@ -308,11 +247,6 @@ export class AnalyticsController extends BaseController {
308
247
  #isPreConsentQueueEnabled;
309
248
  #isGeolocationEnabled;
310
249
  #isEventFragmentsEnabled;
311
- /**
312
- * In-memory event-purpose lookup from persisted state.
313
- */
314
- #eventPurposes;
315
- #eventsConfigVersion;
316
250
  /**
317
251
  * The in-flight (or settled) initialization promise. Set on the first
318
252
  * {@link init} call and returned by subsequent calls so overlapping callers
@@ -346,15 +280,6 @@ export class AnalyticsController extends BaseController {
346
280
  ...getDefaultAnalyticsControllerState(),
347
281
  ...state,
348
282
  };
349
- const eventsConfig = isAnalyticsEventsConfig(initialState.eventsConfig)
350
- ? initialState.eventsConfig
351
- : undefined;
352
- if (eventsConfig === undefined) {
353
- delete initialState.eventsConfig;
354
- }
355
- else {
356
- initialState.eventsConfig = eventsConfig;
357
- }
358
283
  validateAnalyticsControllerState(initialState, platformAdapter.skipUUIDv4Check === true);
359
284
  super({
360
285
  name: controllerName,
@@ -370,14 +295,10 @@ export class AnalyticsController extends BaseController {
370
295
  this.#platformAdapter = platformAdapter;
371
296
  this.#initPromise = undefined;
372
297
  this.#locationResolvePromise = undefined;
373
- this.#eventPurposes = new Map(Object.entries(eventsConfig?.events ?? {}));
374
- this.#eventsConfigVersion = eventsConfig?.version;
375
298
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
376
299
  log('AnalyticsController initialized and ready', {
377
300
  enabled: analyticsControllerSelectors.selectEnabled(this.state),
378
301
  optedIn: this.state.optedIn,
379
- optedInToMarketing: this.state.optedInToMarketing === true,
380
- marketingConsentDecisionMade: this.state.marketingConsentDecisionMade === true,
381
302
  consentDecisionMade: this.state.consentDecisionMade,
382
303
  analyticsId: this.state.analyticsId,
383
304
  eventQueuePersistenceEnabled: this.#isEventQueuePersistenceEnabled,
@@ -392,11 +313,10 @@ export class AnalyticsController extends BaseController {
392
313
  * method must be called after construction to complete the setup process.
393
314
  *
394
315
  * When geolocation enrichment is enabled (`isGeolocationEnabled`), geolocation
395
- * is resolved only for a user who is already opted in to product or marketing
396
- * analytics. For users undecided or opted out of both purposes, it is
397
- * deferred until they opt in to either one (see {@link optIn} and
398
- * {@link optInToMarketing}), so a user's location is never requested before
399
- * they consent to analytics. In either case the `GeolocationController` and its
316
+ * is resolved only for a user who is already opted in; for undecided or
317
+ * opted-out users it is deferred until they opt in (see {@link optIn}), so a
318
+ * user's location is never requested before they consent to analytics. In
319
+ * either case the `GeolocationController` and its
400
320
  * `GeolocationController:getGeolocationData` action must be registered before
401
321
  * resolution occurs, or enrichment is skipped for the session (a message is
402
322
  * logged, see {@link #resolveLocationContext}).
@@ -430,11 +350,9 @@ export class AnalyticsController extends BaseController {
430
350
  initEventFragmentSnapshot.set(id, fragment.createdAt);
431
351
  }
432
352
  }
433
- await this.#fetchEventsConfig();
434
- // Resolve geolocation only when the user is already opted in to product or
435
- // marketing analytics. For undecided or opted-out users it is deferred to
436
- // {@link optIn} / {@link optInToMarketing}. Awaited so that an already-opted-in
437
- // session has location available before events replay.
353
+ // Resolve geolocation only when the user is already opted in; for undecided
354
+ // or opted-out users it is deferred to {@link optIn}. Awaited so that an
355
+ // already-opted-in session has location available before events replay.
438
356
  await this.#maybeResolveLocation();
439
357
  // Call onSetupCompleted lifecycle hook after initialization
440
358
  // State is already validated, so analyticsId is guaranteed to be a valid UUIDv4
@@ -446,20 +364,20 @@ export class AnalyticsController extends BaseController {
446
364
  log('Error calling platformAdapter.onSetupCompleted', error);
447
365
  }
448
366
  this.#replayQueuedEvents();
449
- this.#replayPreConsentEvents();
367
+ this.#reconcilePreConsentEvents();
450
368
  this.#reconcileEventFragments(initEventFragmentSnapshot);
451
369
  }
452
370
  /**
453
371
  * Start resolving the geolocation context if warranted, and return the
454
372
  * in-flight (or settled) resolution so callers can await it. No-op unless
455
- * enrichment is enabled, the user is opted in to product or marketing
456
- * analytics, and a resolution has not already been started. Deferring
457
- * resolution until consent ensures a user's location is never requested before
458
- * they consent to analytics (for example, during onboarding).
373
+ * enrichment is enabled, the user is opted in, and a resolution has not
374
+ * already been started. Deferring resolution until opt-in ensures a user's
375
+ * location is never requested before they consent to analytics (for example,
376
+ * during onboarding).
459
377
  *
460
378
  * Resolution runs at most once per controller session: the settled promise
461
- * is retained, so the outcome, including a failure (see
462
- * {@link #resolveLocationContext}) is not retried, and events are delivered
379
+ * is retained, so the outcome — including a failure (see
380
+ * {@link #resolveLocationContext}) — is not retried, and events are delivered
463
381
  * without location for the rest of the session.
464
382
  *
465
383
  * @returns The geolocation resolution promise, or `undefined` when no
@@ -468,7 +386,7 @@ export class AnalyticsController extends BaseController {
468
386
  #maybeResolveLocation() {
469
387
  if (this.#isGeolocationEnabled &&
470
388
  this.#locationResolvePromise === undefined &&
471
- (this.state.optedIn || this.state.optedInToMarketing === true)) {
389
+ analyticsControllerSelectors.selectEnabled(this.state)) {
472
390
  this.#locationResolvePromise = this.#resolveLocationContext();
473
391
  }
474
392
  return this.#locationResolvePromise;
@@ -509,131 +427,18 @@ export class AnalyticsController extends BaseController {
509
427
  },
510
428
  };
511
429
  }
512
- /**
513
- * Stamp the purposes currently allowed for a payload using Segment's consent
514
- * context shape.
515
- *
516
- * @param purposes - Purposes for which the payload is eligible.
517
- * @param context - Optional caller-provided context.
518
- * @param version - Config version captured with the payload.
519
- * @returns Context with allowed purpose preferences.
520
- */
521
- #withConsentContext(purposes, context, version) {
522
- const preferences = this.#allowedPurposePreferences(purposes);
523
- const { consent: existingConsent, eventsConfigVersion: _ignoredVersion, ...unmanagedContext } = context ?? {};
524
- const consent = isJsonRecord(existingConsent)
525
- ? existingConsent
526
- : {};
527
- const existingCategoryPreferences = isJsonRecord(consent.categoryPreferences)
528
- ? consent.categoryPreferences
529
- : {};
530
- return {
531
- ...unmanagedContext,
532
- consent: {
533
- ...consent,
534
- categoryPreferences: {
535
- ...existingCategoryPreferences,
536
- ...preferences,
537
- },
538
- },
539
- ...(version === undefined ? {} : { eventsConfigVersion: version }),
540
- };
541
- }
542
- /**
543
- * Load event-purpose configuration.
544
- *
545
- * Phase 1 stub. Persisted configuration remains authoritative until a remote
546
- * source is wired up.
547
- */
548
- async #fetchEventsConfig() {
549
- // Intentionally empty until an events-config source is wired up.
550
- }
551
- #purposesFromName(name) {
552
- return [...(this.#eventPurposes.get(name) ?? [AnalyticsPurpose.Product])];
553
- }
554
- #purposesFromQueuedEvent(queuedEvent) {
555
- if (queuedEvent.type === 'identify') {
556
- return [AnalyticsPurpose.Product];
557
- }
558
- if (queuedEvent.eventPurposes !== undefined) {
559
- return [...queuedEvent.eventPurposes];
560
- }
561
- return this.#purposesFromName(queuedEvent.type === 'view' ? queuedEvent.name : queuedEvent.eventName);
562
- }
563
- #eventNamesFromFragment(fragment) {
564
- return [
565
- fragment.initialEvent,
566
- fragment.successEvent,
567
- fragment.failureEvent,
568
- ].filter((name) => typeof name === 'string');
569
- }
570
- #purposesFromFragmentEvent(fragment, name) {
571
- return [
572
- ...(fragment.eventPurposes?.[name] ?? this.#purposesFromName(name)),
573
- ];
574
- }
575
- #purposesFromFragment(fragment) {
576
- const names = this.#eventNamesFromFragment(fragment);
577
- if (names.length === 0) {
578
- // Nameless property bags have no event to classify. Treat them as
579
- // eligible for any purpose so they can accumulate when either consent
580
- // allows capture.
581
- return Object.values(AnalyticsPurpose);
582
- }
583
- return [
584
- ...new Set(names.flatMap((name) => this.#purposesFromFragmentEvent(fragment, name))),
585
- ];
586
- }
587
- #consent(purpose) {
588
- return purpose === AnalyticsPurpose.Marketing
589
- ? {
590
- optedIn: this.state.optedInToMarketing === true,
591
- decisionMade: this.state.marketingConsentDecisionMade === true,
592
- }
593
- : {
594
- optedIn: this.state.optedIn,
595
- decisionMade: this.state.consentDecisionMade === true,
596
- };
597
- }
598
- #allowedPurposePreferences(purposes) {
599
- return {
600
- product: purposes.includes(AnalyticsPurpose.Product) &&
601
- this.#consent(AnalyticsPurpose.Product).optedIn,
602
- marketing: purposes.includes(AnalyticsPurpose.Marketing) &&
603
- this.#consent(AnalyticsPurpose.Marketing).optedIn,
604
- };
605
- }
606
- #hasAllowedPurpose(purposes) {
607
- const { product, marketing } = this.#allowedPurposePreferences(purposes);
608
- return product || marketing;
609
- }
610
- #hasUndecidedPurpose(purposes) {
611
- return purposes.some((purpose) => !this.#consent(purpose).decisionMade);
612
- }
613
- #isCaptureAllowed(purposes) {
614
- return (this.#hasAllowedPurpose(purposes) ||
615
- (this.#isPreConsentQueueEnabled && this.#hasUndecidedPurpose(purposes)));
616
- }
617
- #replaceQueue(field, nextQueue) {
618
- this.update((state) => {
619
- state[field] = nextQueue;
620
- });
621
- }
622
430
  /**
623
431
  * Send final track payload through the platform adapter or queue it if persistence is enabled.
624
432
  *
625
433
  * @param eventName - The name of the event.
626
434
  * @param properties - Optional event properties.
627
435
  * @param context - Optional platform-specific context.
628
- * @param purposes - Capture-time purposes for the event.
629
- * @param version - Capture-time events config version.
630
436
  */
631
- #sendOrQueueTrackEvent(eventName, properties, context, purposes, version) {
632
- const isAllowed = this.#hasAllowedPurpose(purposes);
633
- const contextWithConsent = this.#withConsentContext(purposes, context, version);
437
+ #sendOrQueueTrackEvent(eventName, properties, context) {
634
438
  // Direct delivery: enabled and not persisting.
635
- if (isAllowed && !this.#isEventQueuePersistenceEnabled) {
636
- this.#platformAdapter.track(eventName, properties, contextWithConsent);
439
+ if (analyticsControllerSelectors.selectEnabled(this.state) &&
440
+ !this.#isEventQueuePersistenceEnabled) {
441
+ this.#platformAdapter.track(eventName, properties, context);
637
442
  return;
638
443
  }
639
444
  const queuedEvent = {
@@ -642,11 +447,11 @@ export class AnalyticsController extends BaseController {
642
447
  messageId: uuid(),
643
448
  timestamp: new Date().toISOString(),
644
449
  ...(properties === undefined ? {} : { properties }),
645
- context: contextWithConsent,
646
- eventPurposes: purposes,
647
- ...(version === undefined ? {} : { eventsConfigVersion: version }),
450
+ ...(context === undefined ? {} : { context }),
648
451
  };
649
- if (!isAllowed) {
452
+ // Not yet enabled (reached only while undecided with the pre-consent queue
453
+ // enabled): hold the event until the user opts in.
454
+ if (!analyticsControllerSelectors.selectEnabled(this.state)) {
650
455
  this.#enqueuePreConsentEvent(queuedEvent);
651
456
  return;
652
457
  }
@@ -660,10 +465,8 @@ export class AnalyticsController extends BaseController {
660
465
  * @param context - Optional platform-specific context.
661
466
  */
662
467
  #sendOrQueueIdentifyEvent(userId, traits, context) {
663
- const purposes = [AnalyticsPurpose.Product];
664
- const contextWithConsent = this.#withConsentContext(purposes, context);
665
468
  if (!this.#isEventQueuePersistenceEnabled) {
666
- this.#platformAdapter.identify(userId, traits, contextWithConsent);
469
+ this.#platformAdapter.identify(userId, traits, context);
667
470
  return;
668
471
  }
669
472
  const queuedEvent = {
@@ -672,8 +475,7 @@ export class AnalyticsController extends BaseController {
672
475
  messageId: uuid(),
673
476
  timestamp: new Date().toISOString(),
674
477
  ...(traits === undefined ? {} : { traits }),
675
- context: contextWithConsent,
676
- eventPurposes: purposes,
478
+ ...(context === undefined ? {} : { context }),
677
479
  };
678
480
  this.#enqueueEvent(queuedEvent);
679
481
  }
@@ -683,14 +485,10 @@ export class AnalyticsController extends BaseController {
683
485
  * @param name - The view name.
684
486
  * @param properties - Optional view properties.
685
487
  * @param context - Optional platform-specific context.
686
- * @param purposes - Capture-time purposes for the view.
687
- * @param version - Capture-time events config version.
688
488
  */
689
- #sendOrQueueViewEvent(name, properties, context, purposes, version) {
690
- const isAllowed = this.#hasAllowedPurpose(purposes);
691
- const contextWithConsent = this.#withConsentContext(purposes, context, version);
692
- if (isAllowed && !this.#isEventQueuePersistenceEnabled) {
693
- this.#platformAdapter.view(name, properties, contextWithConsent);
489
+ #sendOrQueueViewEvent(name, properties, context) {
490
+ if (!this.#isEventQueuePersistenceEnabled) {
491
+ this.#platformAdapter.view(name, properties, context);
694
492
  return;
695
493
  }
696
494
  const queuedEvent = {
@@ -699,14 +497,8 @@ export class AnalyticsController extends BaseController {
699
497
  messageId: uuid(),
700
498
  timestamp: new Date().toISOString(),
701
499
  ...(properties === undefined ? {} : { properties }),
702
- context: contextWithConsent,
703
- eventPurposes: purposes,
704
- ...(version === undefined ? {} : { eventsConfigVersion: version }),
500
+ ...(context === undefined ? {} : { context }),
705
501
  };
706
- if (!isAllowed) {
707
- this.#enqueuePreConsentEvent(queuedEvent);
708
- return;
709
- }
710
502
  this.#enqueueEvent(queuedEvent);
711
503
  }
712
504
  /**
@@ -776,23 +568,17 @@ export class AnalyticsController extends BaseController {
776
568
  if (!this.#isEventQueuePersistenceEnabled || !this.state.eventQueue) {
777
569
  return;
778
570
  }
779
- const remainingQueue = {};
780
- const eventsToSend = [];
571
+ if (!analyticsControllerSelectors.selectEnabled(this.state)) {
572
+ this.#clearQueuedEvents();
573
+ return;
574
+ }
781
575
  for (const [messageId, queuedEvent] of Object.entries(this.state.eventQueue)) {
782
576
  if (!isAnalyticsQueuedEvent(queuedEvent) ||
783
577
  queuedEvent.messageId !== messageId) {
784
578
  log('Dropping invalid queued analytics event', { messageId });
579
+ this.#removeQueuedEvent(messageId);
785
580
  continue;
786
581
  }
787
- const purposes = this.#purposesFromQueuedEvent(queuedEvent);
788
- if (this.#hasAllowedPurpose(purposes)) {
789
- const refreshedEvent = this.#refreshQueuedEventConsent(queuedEvent);
790
- remainingQueue[messageId] = refreshedEvent;
791
- eventsToSend.push(refreshedEvent);
792
- }
793
- }
794
- this.#replaceQueue(AnalyticsQueue.EventQueue, remainingQueue);
795
- for (const queuedEvent of eventsToSend) {
796
582
  this.#sendQueuedEvent(queuedEvent);
797
583
  }
798
584
  }
@@ -812,59 +598,17 @@ export class AnalyticsController extends BaseController {
812
598
  state.eventQueue = eventQueue;
813
599
  });
814
600
  }
815
- #refreshQueuedEventConsent(queuedEvent) {
816
- // Refresh only the consent stamp. Capture-time `eventPurposes` and
817
- // `eventsConfigVersion` stay as a pair and are never rewritten here.
818
- const purposes = this.#purposesFromQueuedEvent(queuedEvent);
819
- const preferences = this.#allowedPurposePreferences(purposes);
820
- const existingPreferences = isJsonRecord(queuedEvent.context?.consent)
821
- ? queuedEvent.context.consent.categoryPreferences
822
- : undefined;
823
- if (isJsonRecord(existingPreferences) &&
824
- existingPreferences.product === preferences.product &&
825
- existingPreferences.marketing === preferences.marketing) {
826
- return queuedEvent;
827
- }
828
- return {
829
- ...queuedEvent,
830
- context: this.#withConsentContext(purposes, queuedEvent.context, queuedEvent.eventsConfigVersion),
831
- };
832
- }
833
601
  /**
834
- * Prune a queue after a consent change.
835
- *
836
- * For {@link AnalyticsQueue.EventQueue}, entries are kept only while at least
837
- * one capture-time purpose is opted in, and their consent stamp is refreshed.
838
- * For {@link AnalyticsQueue.PreConsentEventQueue}, entries are kept while at
839
- * least one purpose is still allowed or undecided.
840
- *
841
- * @param field - The queue to prune.
602
+ * Clear all queued analytics events.
842
603
  */
843
- #pruneQueueForConsent(field) {
844
- const queue = this.state[field];
845
- if (!queue) {
604
+ #clearQueuedEvents() {
605
+ if (!this.state.eventQueue ||
606
+ Object.keys(this.state.eventQueue).length === 0) {
846
607
  return;
847
608
  }
848
- const nextQueue = {};
849
- for (const [messageId, queuedEvent] of Object.entries(queue)) {
850
- if (!isAnalyticsQueuedEvent(queuedEvent) ||
851
- queuedEvent.messageId !== messageId) {
852
- continue;
853
- }
854
- const purposes = this.#purposesFromQueuedEvent(queuedEvent);
855
- const isAllowed = this.#hasAllowedPurpose(purposes);
856
- if (field === AnalyticsQueue.EventQueue) {
857
- if (isAllowed) {
858
- nextQueue[messageId] = this.#refreshQueuedEventConsent(queuedEvent);
859
- }
860
- }
861
- if (field === AnalyticsQueue.PreConsentEventQueue) {
862
- if (isAllowed || this.#hasUndecidedPurpose(purposes)) {
863
- nextQueue[messageId] = queuedEvent;
864
- }
865
- }
866
- }
867
- this.#replaceQueue(field, nextQueue);
609
+ this.update((state) => {
610
+ state.eventQueue = {};
611
+ });
868
612
  }
869
613
  /**
870
614
  * Add an event to the pre-consent queue without delivering it.
@@ -880,6 +624,34 @@ export class AnalyticsController extends BaseController {
880
624
  state.preConsentEventQueue = preConsentEventQueue;
881
625
  });
882
626
  }
627
+ /**
628
+ * Replay queued pre-consent events through the delivery path.
629
+ *
630
+ * Only called by {@link #reconcilePreConsentEvents}, which guarantees the
631
+ * pre-consent queue is enabled and that the user is opted in. The queue is
632
+ * cleared before replaying so events cannot be re-queued or replayed twice.
633
+ *
634
+ * @param queue - The pre-consent event queue to replay.
635
+ */
636
+ #replayPreConsentEvents(queue) {
637
+ this.#clearPreConsentEvents();
638
+ for (const [messageId, queuedEvent] of Object.entries(queue)) {
639
+ if (!isAnalyticsQueuedEvent(queuedEvent) ||
640
+ queuedEvent.messageId !== messageId) {
641
+ log('Dropping invalid queued pre-consent analytics event', {
642
+ messageId,
643
+ });
644
+ continue;
645
+ }
646
+ const eventToReplay = this.#enrichPreConsentEvent(queuedEvent);
647
+ if (this.#isEventQueuePersistenceEnabled) {
648
+ this.#enqueueEvent(eventToReplay);
649
+ }
650
+ else {
651
+ this.#sendQueuedEvent(eventToReplay);
652
+ }
653
+ }
654
+ }
883
655
  /**
884
656
  * Enrich a pre-consent event with the geolocation resolved on opt-in.
885
657
  *
@@ -903,52 +675,42 @@ export class AnalyticsController extends BaseController {
903
675
  };
904
676
  }
905
677
  /**
906
- * Replay eligible pre-consent events against current consent.
678
+ * Clear all queued pre-consent events.
679
+ */
680
+ #clearPreConsentEvents() {
681
+ if (!this.state.preConsentEventQueue) {
682
+ return;
683
+ }
684
+ this.update((state) => {
685
+ state.preConsentEventQueue = {};
686
+ });
687
+ }
688
+ /**
689
+ * Reconcile the pre-consent queue on initialization.
907
690
  *
908
- * Allowed entries are replayed, undecided entries are kept for later, and
909
- * entries with no remaining allowed or undecided purpose are dropped. The
910
- * keep set is written before replay so events cannot be re-queued or
911
- * replayed twice.
691
+ * The queue should normally be empty unless the user is still undecided. This
692
+ * handles the rare cases where a consent decision was persisted but the queue
693
+ * was not flushed/cleared (e.g. an interrupted shutdown): replay it if the
694
+ * user is opted in, or clear it if they opted out.
912
695
  *
913
696
  * If the pre-consent queue is disabled, any stale persisted entries (e.g. from
914
697
  * a previous session where it was enabled) are dropped so they can never be
915
698
  * replayed.
916
699
  */
917
- #replayPreConsentEvents() {
700
+ #reconcilePreConsentEvents() {
918
701
  const queue = this.state.preConsentEventQueue;
919
702
  if (!queue) {
920
703
  return;
921
704
  }
922
705
  if (!this.#isPreConsentQueueEnabled) {
923
- this.update((state) => {
924
- state.preConsentEventQueue = {};
925
- });
706
+ this.#clearPreConsentEvents();
926
707
  return;
927
708
  }
928
- const keep = {};
929
- const replay = [];
930
- for (const [messageId, queuedEvent] of Object.entries(queue)) {
931
- if (!isAnalyticsQueuedEvent(queuedEvent) ||
932
- queuedEvent.messageId !== messageId) {
933
- continue;
934
- }
935
- const purposes = this.#purposesFromQueuedEvent(queuedEvent);
936
- if (this.#hasAllowedPurpose(purposes)) {
937
- replay.push(queuedEvent);
938
- }
939
- else if (this.#hasUndecidedPurpose(purposes)) {
940
- keep[messageId] = queuedEvent;
941
- }
709
+ if (this.state.optedIn) {
710
+ this.#replayPreConsentEvents(queue);
942
711
  }
943
- this.#replaceQueue(AnalyticsQueue.PreConsentEventQueue, keep);
944
- for (const queuedEvent of replay) {
945
- const eventToReplay = this.#refreshQueuedEventConsent(this.#enrichPreConsentEvent(queuedEvent));
946
- if (this.#isEventQueuePersistenceEnabled) {
947
- this.#enqueueEvent(eventToReplay);
948
- }
949
- else {
950
- this.#sendQueuedEvent(eventToReplay);
951
- }
712
+ else if (this.state.consentDecisionMade) {
713
+ this.#clearPreConsentEvents();
952
714
  }
953
715
  }
954
716
  /**
@@ -961,8 +723,9 @@ export class AnalyticsController extends BaseController {
961
723
  * finalization is not a failure, just an unfinished one.
962
724
  *
963
725
  * If the feature is disabled (e.g. a previous session had it enabled), or the
964
- * consent state no longer allows capture for any of a fragment's purposes,
965
- * those fragments are dropped so none of them can linger.
726
+ * consent state no longer allows capture (e.g. the fragments were written
727
+ * before the user opted out), every persisted fragment is dropped so none of
728
+ * them can linger.
966
729
  *
967
730
  * Non-persistent fragments are dropped only when their ID and `createdAt`
968
731
  * match a fragment present at the start of {@link init}. Fragments created
@@ -977,13 +740,28 @@ export class AnalyticsController extends BaseController {
977
740
  if (!fragments) {
978
741
  return;
979
742
  }
980
- if (!this.#isEventFragmentsEnabled) {
743
+ if (!this.#isEventFragmentsEnabled || !this.#isAnalyticsCaptureAllowed()) {
981
744
  this.#clearEventFragments();
982
745
  return;
983
746
  }
747
+ this.#purgeStaleEventFragments(fragments, initEventFragmentSnapshot);
748
+ }
749
+ /**
750
+ * Drop every persisted fragment that is invalid, expired, did not opt into
751
+ * `persist`, or was already present with the same `createdAt` when
752
+ * {@link init} began.
753
+ *
754
+ * Only called by {@link #reconcileEventFragments}, which guarantees the
755
+ * fragments exist and that the event fragments feature is enabled.
756
+ *
757
+ * @param currentEventFragments - The persisted fragments to filter.
758
+ * @param initEventFragmentSnapshot - Fragment IDs and `createdAt` values
759
+ * present when {@link init} began.
760
+ */
761
+ #purgeStaleEventFragments(currentEventFragments, initEventFragmentSnapshot) {
984
762
  const eventFragments = {};
985
763
  const now = Date.now();
986
- for (const [id, fragment] of Object.entries(fragments)) {
764
+ for (const [id, fragment] of Object.entries(currentEventFragments)) {
987
765
  if (!isAnalyticsEventFragment(fragment) || fragment.id !== id) {
988
766
  log('Dropping invalid persisted event fragment', { id });
989
767
  continue;
@@ -992,9 +770,6 @@ export class AnalyticsController extends BaseController {
992
770
  log('Dropping expired persisted event fragment', { id });
993
771
  continue;
994
772
  }
995
- if (!this.#isCaptureAllowed(this.#purposesFromFragment(fragment))) {
996
- continue;
997
- }
998
773
  const snapshotCreatedAt = initEventFragmentSnapshot.get(id);
999
774
  if (fragment.persist === true ||
1000
775
  snapshotCreatedAt === undefined ||
@@ -1002,11 +777,8 @@ export class AnalyticsController extends BaseController {
1002
777
  eventFragments[id] = fragment;
1003
778
  }
1004
779
  }
1005
- if (Object.keys(eventFragments).length === 0) {
1006
- this.#clearEventFragments();
1007
- return;
1008
- }
1009
- if (Object.keys(eventFragments).length === Object.keys(fragments).length) {
780
+ if (Object.keys(eventFragments).length ===
781
+ Object.keys(currentEventFragments).length) {
1010
782
  return;
1011
783
  }
1012
784
  this.update((state) => {
@@ -1026,31 +798,15 @@ export class AnalyticsController extends BaseController {
1026
798
  * Write an event fragment to state, replacing any fragment with the same ID.
1027
799
  *
1028
800
  * @param fragment - The fragment to store.
1029
- * @returns The stored fragment with capture-time purpose metadata.
1030
801
  */
1031
802
  #setEventFragment(fragment) {
1032
- // Snapshot classification only. Consent is stamped at emit time by
1033
- // {@link #trackEvent}, so fragment.context stays caller metadata.
1034
- let fragmentWithPurposeSnapshot = fragment;
1035
- if (fragment.eventPurposes === undefined) {
1036
- const names = this.#eventNamesFromFragment(fragment);
1037
- const eventPurposes = Object.fromEntries(names.map((name) => [name, this.#purposesFromName(name)]));
1038
- fragmentWithPurposeSnapshot = {
1039
- ...fragment,
1040
- ...(names.length === 0 ? {} : { eventPurposes }),
1041
- ...(this.#eventsConfigVersion === undefined
1042
- ? {}
1043
- : { eventsConfigVersion: this.#eventsConfigVersion }),
1044
- };
1045
- }
1046
803
  const eventFragments = {
1047
804
  ...this.state.eventFragments,
1048
- [fragmentWithPurposeSnapshot.id]: fragmentWithPurposeSnapshot,
805
+ [fragment.id]: fragment,
1049
806
  };
1050
807
  this.update((state) => {
1051
808
  state.eventFragments = eventFragments;
1052
809
  });
1053
- return fragmentWithPurposeSnapshot;
1054
810
  }
1055
811
  /**
1056
812
  * Remove an event fragment from state.
@@ -1068,35 +824,6 @@ export class AnalyticsController extends BaseController {
1068
824
  state.eventFragments = eventFragments;
1069
825
  });
1070
826
  }
1071
- /**
1072
- * Drop event fragments that the current consent state no longer allows to
1073
- * accumulate.
1074
- */
1075
- #pruneEventFragmentsForConsent() {
1076
- const fragments = this.state.eventFragments;
1077
- if (!fragments || Object.keys(fragments).length === 0) {
1078
- return;
1079
- }
1080
- const eventFragments = {};
1081
- for (const [id, fragment] of Object.entries(fragments)) {
1082
- if (isAnalyticsEventFragment(fragment) &&
1083
- this.#isCaptureAllowed(this.#purposesFromFragment(fragment))) {
1084
- eventFragments[id] = fragment;
1085
- }
1086
- }
1087
- this.update((state) => {
1088
- state.eventFragments = eventFragments;
1089
- });
1090
- }
1091
- /**
1092
- * Drop queued events and fragments that the current consent state no longer
1093
- * allows to keep.
1094
- */
1095
- #pruneAllForConsent() {
1096
- this.#pruneQueueForConsent(AnalyticsQueue.EventQueue);
1097
- this.#pruneQueueForConsent(AnalyticsQueue.PreConsentEventQueue);
1098
- this.#pruneEventFragmentsForConsent();
1099
- }
1100
827
  /**
1101
828
  * Clear all event fragments.
1102
829
  */
@@ -1119,18 +846,14 @@ export class AnalyticsController extends BaseController {
1119
846
  * fragment never accumulates data for an event that could not be delivered.
1120
847
  *
1121
848
  * @param method - The name of the method that was called.
1122
- * @param fragment - The fragment being read or written, when one is known.
1123
849
  * @returns True when the call should be ignored.
1124
850
  */
1125
- #shouldIgnoreEventFragmentCall(method, fragment) {
851
+ #shouldIgnoreEventFragmentCall(method) {
1126
852
  if (!this.#isEventFragmentsEnabled) {
1127
853
  log('Ignoring event fragment call because the event fragments feature is disabled', { method });
1128
854
  return true;
1129
855
  }
1130
- const captureAllowed = fragment
1131
- ? this.#isCaptureAllowed(this.#purposesFromFragment(fragment))
1132
- : this.#isCaptureAllowed(Object.values(AnalyticsPurpose));
1133
- if (!captureAllowed) {
856
+ if (!this.#isAnalyticsCaptureAllowed()) {
1134
857
  log('Ignoring event fragment call because the consent state does not allow capturing analytics', { method });
1135
858
  return true;
1136
859
  }
@@ -1150,14 +873,32 @@ export class AnalyticsController extends BaseController {
1150
873
  #emitEventFragment(fragment, name, context) {
1151
874
  const properties = { ...fragment.properties };
1152
875
  const sensitiveProperties = { ...fragment.sensitiveProperties };
1153
- this.#trackEvent({
876
+ this.trackEvent({
1154
877
  name,
1155
878
  properties,
1156
879
  sensitiveProperties,
1157
880
  saveDataRecording: false,
1158
881
  hasProperties: Object.keys(properties).length > 0 ||
1159
882
  Object.keys(sensitiveProperties).length > 0,
1160
- }, context, this.#purposesFromFragmentEvent(fragment, name), fragment.eventsConfigVersion);
883
+ }, context);
884
+ }
885
+ /**
886
+ * Returns whether the current consent state allows analytics data to be
887
+ * captured, either for immediate delivery or to be held until the user
888
+ * decides.
889
+ *
890
+ * Capture is allowed once the user has opted in, and also while they are
891
+ * undecided if the pre-consent queue is enabled: what is captured then is
892
+ * replayed when they opt in (see {@link optIn}) and discarded if they opt out
893
+ * (see {@link optOut}). An explicit opt-out never allows capture.
894
+ *
895
+ * @returns True when analytics data may be captured.
896
+ */
897
+ #isAnalyticsCaptureAllowed() {
898
+ if (analyticsControllerSelectors.selectEnabled(this.state)) {
899
+ return true;
900
+ }
901
+ return this.#isPreConsentQueueEnabled && !this.state.consentDecisionMade;
1161
902
  }
1162
903
  /**
1163
904
  * Track an analytics event.
@@ -1168,19 +909,16 @@ export class AnalyticsController extends BaseController {
1168
909
  * @param context - Optional platform-specific context forwarded to the platform adapter.
1169
910
  */
1170
911
  trackEvent(event, context) {
1171
- this.#trackEvent(event, context, this.#purposesFromName(event.name), this.#eventsConfigVersion);
1172
- }
1173
- #trackEvent(event, context, purposes, version) {
1174
912
  // An event captured while the user is still undecided is held in the
1175
913
  // pre-consent queue (see #sendOrQueueTrackEvent) instead of being
1176
914
  // delivered, and replayed if they later opt in.
1177
- if (!this.#isCaptureAllowed(purposes)) {
915
+ if (!this.#isAnalyticsCaptureAllowed()) {
1178
916
  return;
1179
917
  }
1180
918
  // if event does not have properties, send event without properties
1181
919
  // and return to prevent any additional processing
1182
920
  if (!event.hasProperties) {
1183
- this.#sendOrQueueTrackEvent(event.name, undefined, this.#withLocationContext(context), purposes, version);
921
+ this.#sendOrQueueTrackEvent(event.name, undefined, this.#withLocationContext(context));
1184
922
  return;
1185
923
  }
1186
924
  // Track regular properties first if anonymous events feature is enabled
@@ -1189,7 +927,7 @@ export class AnalyticsController extends BaseController {
1189
927
  // an event with user ID is tracked.
1190
928
  this.#sendOrQueueTrackEvent(event.name, {
1191
929
  ...event.properties,
1192
- }, this.#withLocationContext(context), purposes, version);
930
+ }, this.#withLocationContext(context));
1193
931
  }
1194
932
  const hasSensitiveProperties = Object.keys(event.sensitiveProperties).length > 0;
1195
933
  if (!this.#isAnonymousEventsFeatureEnabled || hasSensitiveProperties) {
@@ -1203,7 +941,7 @@ export class AnalyticsController extends BaseController {
1203
941
  // disabled, this is the single identified payload, so it is enriched.
1204
942
  this.#isAnonymousEventsFeatureEnabled
1205
943
  ? context
1206
- : this.#withLocationContext(context), purposes, version);
944
+ : this.#withLocationContext(context));
1207
945
  }
1208
946
  }
1209
947
  /**
@@ -1216,6 +954,7 @@ export class AnalyticsController extends BaseController {
1216
954
  if (!analyticsControllerSelectors.selectEnabled(this.state)) {
1217
955
  return;
1218
956
  }
957
+ // Delegate to platform adapter using the current analytics ID
1219
958
  this.#sendOrQueueIdentifyEvent(this.state.analyticsId, traits, this.#withLocationContext(context));
1220
959
  }
1221
960
  /**
@@ -1226,13 +965,11 @@ export class AnalyticsController extends BaseController {
1226
965
  * @param context - Optional platform-specific context forwarded to the platform adapter.
1227
966
  */
1228
967
  trackView(name, properties, context) {
1229
- const purposes = this.#purposesFromName(name);
1230
- const version = this.#eventsConfigVersion;
1231
- if (!this.#isCaptureAllowed(purposes)) {
968
+ if (!analyticsControllerSelectors.selectEnabled(this.state)) {
1232
969
  return;
1233
970
  }
1234
971
  // Delegate to platform adapter
1235
- this.#sendOrQueueViewEvent(name, properties, this.#withLocationContext(context), purposes, version);
972
+ this.#sendOrQueueViewEvent(name, properties, this.#withLocationContext(context));
1236
973
  }
1237
974
  /**
1238
975
  * Create an event fragment.
@@ -1259,17 +996,11 @@ export class AnalyticsController extends BaseController {
1259
996
  * Use {@link updateEventFragment} or {@link upsertEventFragment} to write.
1260
997
  */
1261
998
  createEventFragment(options = {}) {
1262
- // Classify create from event names only. Caller consent context must not
1263
- // affect the event-purpose snapshot.
1264
- if (this.#shouldIgnoreEventFragmentCall('createEventFragment', {
1265
- initialEvent: options.initialEvent,
1266
- successEvent: options.successEvent,
1267
- failureEvent: options.failureEvent,
1268
- })) {
999
+ if (this.#shouldIgnoreEventFragmentCall('createEventFragment')) {
1269
1000
  return undefined;
1270
1001
  }
1271
1002
  const now = Date.now();
1272
- const fragment = this.#setEventFragment({
1003
+ const fragment = {
1273
1004
  id: options.id ?? uuid(),
1274
1005
  properties: { ...(options.properties ?? {}) },
1275
1006
  sensitiveProperties: { ...(options.sensitiveProperties ?? {}) },
@@ -1288,7 +1019,8 @@ export class AnalyticsController extends BaseController {
1288
1019
  ? {}
1289
1020
  : { context: { ...options.context } }),
1290
1021
  ...(options.persist === undefined ? {} : { persist: options.persist }),
1291
- });
1022
+ };
1023
+ this.#setEventFragment(fragment);
1292
1024
  if (fragment.initialEvent) {
1293
1025
  this.#emitEventFragment(fragment, fragment.initialEvent, fragment.context);
1294
1026
  }
@@ -1305,10 +1037,10 @@ export class AnalyticsController extends BaseController {
1305
1037
  * @param payload - The properties and context to merge in.
1306
1038
  */
1307
1039
  upsertEventFragment(id, payload = {}) {
1308
- const fragment = this.#getEventFragment(id);
1309
- if (this.#shouldIgnoreEventFragmentCall('upsertEventFragment', fragment ?? {})) {
1040
+ if (this.#shouldIgnoreEventFragmentCall('upsertEventFragment')) {
1310
1041
  return;
1311
1042
  }
1043
+ const fragment = this.#getEventFragment(id);
1312
1044
  if (!fragment) {
1313
1045
  this.createEventFragment({ id, ...payload });
1314
1046
  return;
@@ -1326,10 +1058,10 @@ export class AnalyticsController extends BaseController {
1326
1058
  * allow capture, the call is a logged no-op and does not throw.
1327
1059
  */
1328
1060
  updateEventFragment(id, payload = {}) {
1329
- const fragment = this.#getEventFragment(id);
1330
- if (this.#shouldIgnoreEventFragmentCall('updateEventFragment', fragment)) {
1061
+ if (this.#shouldIgnoreEventFragmentCall('updateEventFragment')) {
1331
1062
  return;
1332
1063
  }
1064
+ const fragment = this.#getEventFragment(id);
1333
1065
  if (!fragment) {
1334
1066
  throw new Error(`Event fragment with id ${id} does not exist.`);
1335
1067
  }
@@ -1346,10 +1078,10 @@ export class AnalyticsController extends BaseController {
1346
1078
  * {@link upsertEventFragment} to write.
1347
1079
  */
1348
1080
  getEventFragmentById(id) {
1349
- const fragment = this.#getEventFragment(id);
1350
- if (this.#shouldIgnoreEventFragmentCall('getEventFragmentById', fragment)) {
1081
+ if (this.#shouldIgnoreEventFragmentCall('getEventFragmentById')) {
1351
1082
  return undefined;
1352
1083
  }
1084
+ const fragment = this.#getEventFragment(id);
1353
1085
  return fragment === undefined ? undefined : cloneDeep(fragment);
1354
1086
  }
1355
1087
  /**
@@ -1358,8 +1090,7 @@ export class AnalyticsController extends BaseController {
1358
1090
  * @param id - The fragment ID.
1359
1091
  */
1360
1092
  deleteEventFragment(id) {
1361
- const fragment = this.#getEventFragment(id);
1362
- if (this.#shouldIgnoreEventFragmentCall('deleteEventFragment', fragment)) {
1093
+ if (this.#shouldIgnoreEventFragmentCall('deleteEventFragment')) {
1363
1094
  return;
1364
1095
  }
1365
1096
  this.#removeEventFragment(id);
@@ -1381,10 +1112,10 @@ export class AnalyticsController extends BaseController {
1381
1112
  * allow capture, the call is a logged no-op and does not throw.
1382
1113
  */
1383
1114
  finalizeEventFragment(id, { abandoned = false, context } = {}) {
1384
- const fragment = this.#getEventFragment(id);
1385
- if (this.#shouldIgnoreEventFragmentCall('finalizeEventFragment', fragment)) {
1115
+ if (this.#shouldIgnoreEventFragmentCall('finalizeEventFragment')) {
1386
1116
  return;
1387
1117
  }
1118
+ const fragment = this.#getEventFragment(id);
1388
1119
  if (!fragment) {
1389
1120
  throw new Error(`Event fragment with id ${id} does not exist.`);
1390
1121
  }
@@ -1418,7 +1149,7 @@ export class AnalyticsController extends BaseController {
1418
1149
  // consent decision may have changed while geolocation was resolving (e.g.
1419
1150
  // resetConsentDecision ran during the await), and preserved pre-consent
1420
1151
  // events must not be delivered once the user is no longer opted in.
1421
- this.#replayPreConsentEvents();
1152
+ this.#reconcilePreConsentEvents();
1422
1153
  }
1423
1154
  /**
1424
1155
  * Opt out of analytics.
@@ -1432,7 +1163,9 @@ export class AnalyticsController extends BaseController {
1432
1163
  state.optedIn = false;
1433
1164
  state.consentDecisionMade = true;
1434
1165
  });
1435
- this.#pruneAllForConsent();
1166
+ this.#clearQueuedEvents();
1167
+ this.#clearPreConsentEvents();
1168
+ this.#clearEventFragments();
1436
1169
  }
1437
1170
  /**
1438
1171
  * Reset the consent decision back to undecided.
@@ -1451,47 +1184,10 @@ export class AnalyticsController extends BaseController {
1451
1184
  state.optedIn = false;
1452
1185
  state.consentDecisionMade = false;
1453
1186
  });
1454
- this.#pruneAllForConsent();
1455
- }
1456
- /**
1457
- * Opt in to marketing analytics.
1458
- *
1459
- * Independent of {@link optIn}. Replays queued marketing events.
1460
- *
1461
- * @returns A promise that resolves once opt-in processing has completed.
1462
- */
1463
- async optInToMarketing() {
1464
- this.update((state) => {
1465
- state.optedInToMarketing = true;
1466
- state.marketingConsentDecisionMade = true;
1467
- });
1468
- await this.#maybeResolveLocation();
1469
- this.#replayPreConsentEvents();
1470
- }
1471
- /**
1472
- * Opt out of marketing analytics.
1473
- *
1474
- * Independent of {@link optOut}. Discards queued marketing events and
1475
- * marketing event fragments.
1476
- */
1477
- optOutOfMarketing() {
1478
- this.update((state) => {
1479
- state.optedInToMarketing = false;
1480
- state.marketingConsentDecisionMade = true;
1481
- });
1482
- this.#pruneAllForConsent();
1483
- }
1484
- /**
1485
- * Reset the marketing consent decision back to undecided.
1486
- *
1487
- * Independent of {@link resetConsentDecision}.
1488
- */
1489
- resetMarketingConsentDecision() {
1490
- this.update((state) => {
1491
- state.optedInToMarketing = false;
1492
- state.marketingConsentDecisionMade = false;
1493
- });
1494
- this.#pruneAllForConsent();
1187
+ this.#clearQueuedEvents();
1188
+ if (!this.#isAnalyticsCaptureAllowed()) {
1189
+ this.#clearEventFragments();
1190
+ }
1495
1191
  }
1496
1192
  }
1497
1193
  //# sourceMappingURL=AnalyticsController.js.map