@envive-ai/react-hooks 0.3.20 → 0.3.22

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.
Files changed (82) hide show
  1. package/dist/application/models/guards/api/isApiPDPEventAttributes.cjs +2 -2
  2. package/dist/application/models/guards/api/isApiPDPEventAttributes.js +2 -2
  3. package/dist/atoms/app/index.d.cts +7 -7
  4. package/dist/atoms/app/index.d.ts +6 -6
  5. package/dist/atoms/app/variant.d.cts +6 -6
  6. package/dist/atoms/chat/chatState.d.cts +18 -18
  7. package/dist/atoms/chat/chatState.d.ts +18 -18
  8. package/dist/atoms/chat/form.d.cts +3 -3
  9. package/dist/atoms/chat/form.d.ts +2 -2
  10. package/dist/atoms/chat/index.d.cts +1 -1
  11. package/dist/atoms/chat/index.d.ts +2 -2
  12. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  13. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  14. package/dist/atoms/chat/messageQueue.d.cts +7 -7
  15. package/dist/atoms/chat/messageQueue.d.ts +6 -6
  16. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  17. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  18. package/dist/atoms/chat/renderedWidgetRefs.d.cts +3 -3
  19. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  20. package/dist/atoms/chat/replies.d.cts +3 -3
  21. package/dist/atoms/chat/suggestions.d.cts +3 -3
  22. package/dist/atoms/chat/suggestions.d.ts +2 -2
  23. package/dist/atoms/envive/enviveConfig.d.cts +13 -13
  24. package/dist/atoms/envive/enviveConfig.d.ts +13 -13
  25. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  26. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  27. package/dist/atoms/org/customerService.d.cts +6 -6
  28. package/dist/atoms/org/customerService.d.ts +6 -6
  29. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  30. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  31. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  32. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  33. package/dist/atoms/org/orgAnalyticsConfig.d.cts +5 -5
  34. package/dist/atoms/org/orgAnalyticsConfig.d.ts +5 -5
  35. package/dist/atoms/search/chatSearch.d.cts +17 -17
  36. package/dist/atoms/search/chatSearch.d.ts +17 -17
  37. package/dist/atoms/search/searchAPI.d.cts +13 -13
  38. package/dist/atoms/search/searchAPI.d.ts +13 -13
  39. package/dist/atoms/search/utils.d.ts +1 -1
  40. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  41. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  42. package/dist/contexts/amplitudeContext/amplitudeContext.cjs +9 -3
  43. package/dist/contexts/amplitudeContext/amplitudeContext.d.cts +2 -1
  44. package/dist/contexts/amplitudeContext/amplitudeContext.d.ts +2 -1
  45. package/dist/contexts/amplitudeContext/amplitudeContext.js +9 -3
  46. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  47. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
  48. package/dist/contexts/types.d.cts +1 -1
  49. package/dist/contexts/types.d.ts +1 -1
  50. package/dist/contexts/typesV3.d.cts +1 -1
  51. package/dist/contexts/typesV3.d.ts +1 -1
  52. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  53. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
  54. package/dist/hooks/Search/useSearch.cjs +12 -4
  55. package/dist/hooks/Search/useSearch.js +12 -4
  56. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +20 -27
  57. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +8 -8
  58. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +8 -8
  59. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +21 -28
  60. package/dist/hooks/WidgetInteraction/types.cjs +25 -1
  61. package/dist/hooks/WidgetInteraction/types.d.cts +11 -2
  62. package/dist/hooks/WidgetInteraction/types.d.ts +11 -2
  63. package/dist/hooks/WidgetInteraction/types.js +24 -2
  64. package/dist/hooks/WidgetInteraction/useWidgetInteraction.cjs +6 -2
  65. package/dist/hooks/WidgetInteraction/useWidgetInteraction.js +6 -2
  66. package/dist/hooks/utils.d.ts +1 -1
  67. package/dist/services/amplitudeService/amplitudeService.cjs +9 -1
  68. package/dist/services/amplitudeService/amplitudeService.d.cts +2 -1
  69. package/dist/services/amplitudeService/amplitudeService.d.ts +2 -1
  70. package/dist/services/amplitudeService/amplitudeService.js +9 -1
  71. package/package.json +1 -1
  72. package/src/application/models/guards/api/isApiPDPEventAttributes.ts +1 -1
  73. package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +31 -27
  74. package/src/contexts/amplitudeContext/amplitudeContext.tsx +5 -2
  75. package/src/contexts/pageContext/__tests__/pageContext.test.tsx +10 -0
  76. package/src/hooks/Search/__tests__/useSearch.test.tsx +0 -4
  77. package/src/hooks/Search/useSearch.tsx +14 -8
  78. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +27 -34
  79. package/src/hooks/WidgetInteraction/types.ts +25 -1
  80. package/src/hooks/WidgetInteraction/useWidgetInteraction.ts +3 -1
  81. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +69 -6
  82. package/src/services/amplitudeService/amplitudeService.ts +13 -0
@@ -23,6 +23,7 @@ export { EnviveMetricsEventName, SpiffyMetricsEventName };
23
23
  interface AmplitudeContextType {
24
24
  trackEvent: (params: TrackEventParams) => Promise<void>;
25
25
  isReady: boolean;
26
+ isMockMode: boolean;
26
27
  setSupplementalDefaultProps: (props: Record<string, unknown>) => void;
27
28
  }
28
29
 
@@ -55,6 +56,7 @@ export const AmplitudeProvider: React.FC<{
55
56
  const [service, setService] = useState<AmplitudeService | null>(null);
56
57
 
57
58
  const isReady = Boolean(userId && service && service.isReady);
59
+ const isMockMode = Boolean(service?.isMockApiKey);
58
60
 
59
61
  // Create service instance when dependencies are ready
60
62
  useEffect(() => {
@@ -109,16 +111,17 @@ export const AmplitudeProvider: React.FC<{
109
111
  }
110
112
  },
111
113
  isReady,
114
+ isMockMode,
112
115
  setSupplementalDefaultProps: (props: Record<string, unknown>) => {
113
116
  if (service) {
114
117
  service.setSupplementalDefaultProps(props);
115
118
  }
116
119
  },
117
120
  }),
118
- [service, isReady],
121
+ [service, isReady, isMockMode],
119
122
  );
120
123
 
121
- if (!isReady) {
124
+ if (!isReady && !isMockMode) {
122
125
  return null;
123
126
  }
124
127
 
@@ -19,6 +19,16 @@ vi.spyOn(Logger.prototype, 'logInfo').mockImplementation(() => {});
19
19
  vi.spyOn(Logger.prototype, 'logWarn').mockImplementation(() => {});
20
20
  vi.spyOn(Logger.prototype, 'logError').mockImplementation(() => {});
21
21
 
22
+ vi.mock('src/contexts/amplitudeContext', () => ({
23
+ useAmplitude: () => ({
24
+ trackEvent: vi.fn(),
25
+ isReady: true,
26
+ }),
27
+ EnviveMetricsEventName: {
28
+ PageViewed: 'Page Viewed',
29
+ },
30
+ }));
31
+
22
32
  // Mock CommerceApiClient
23
33
  const mockResolveUrl = vi.fn();
24
34
  vi.mock('src/application/commerce-api', () => ({
@@ -29,10 +29,6 @@ import { UserIdentityService } from 'src/services/userIdentityService';
29
29
  import { useSearch } from '../useSearch';
30
30
 
31
31
  // Mock dependencies
32
- vi.mock('src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent', () => ({
33
- useTrackComponentVisibleEvent: vi.fn(),
34
- }));
35
-
36
32
  vi.mock('src/hooks/Intersection/useIntersection', () => ({
37
33
  useIntersection: vi.fn(() => false),
38
34
  }));
@@ -27,8 +27,8 @@ import { SearchResponseProduct } from '@spiffy-ai/commerce-api-client';
27
27
  import { SearchFilterDatum, SelectFilterItem } from 'src/types/search-filter-types';
28
28
  import { orgShortNameAtom } from 'src/atoms/envive/enviveConfig';
29
29
  import { FrontendConfig, SearchResponseProductAttributes } from 'src/application/models';
30
+ import { useIntersection } from 'src/hooks/Intersection/useIntersection';
30
31
  import { SearchResultsState, getSearchResultsState } from '../utils';
31
- import { useTrackComponentVisibleEvent } from '../TrackComponentVisibleEvent';
32
32
  import { useSearchInput } from './useSearchInput';
33
33
  import { useNewOrgConfig } from '../NewOrgConfig';
34
34
  import { useRecommendedProducts } from './useRecommendedProducts';
@@ -292,13 +292,19 @@ export const useSearch = ({
292
292
  scrollToTop();
293
293
  }, [setProductSorting, clearFilters, scrollToTop]);
294
294
 
295
- // Side Effects
296
- useTrackComponentVisibleEvent(
297
- SpiffyWidgets.SearchResults,
298
- searchResultsRef as RefObject<HTMLElement>,
299
- {},
300
- SpiffyMetricsEventName.SearchComponentVisible,
301
- );
295
+ const searchResultsVisible = useIntersection(searchResultsRef as RefObject<HTMLElement>, '0px');
296
+ const hasTrackedSearchComponentVisible = useRef(false);
297
+
298
+ useEffect(() => {
299
+ if (!searchResultsVisible || hasTrackedSearchComponentVisible.current) {
300
+ return;
301
+ }
302
+ hasTrackedSearchComponentVisible.current = true;
303
+ trackEvent({
304
+ eventName: SpiffyMetricsEventName.SearchComponentVisible,
305
+ eventProps: {},
306
+ });
307
+ }, [searchResultsVisible, trackEvent]);
302
308
 
303
309
  useEffect(() => {
304
310
  if (
@@ -1,54 +1,47 @@
1
1
  import { RefObject, useEffect, useRef } from 'react';
2
- import { SpiffyWidgets } from 'src/application/models/spiffyWidgets';
3
2
  import { useIntersection } from 'src/hooks/Intersection/useIntersection';
4
3
  import { useAmplitude } from 'src/contexts/amplitudeContext/amplitudeContext';
5
- import { SpiffyMetricsEventName } from 'src/services/amplitudeService/amplitudeService';
4
+ import {
5
+ EnviveMetricsEventName,
6
+ SpiffyMetricsEventName,
7
+ } from 'src/services/amplitudeService/amplitudeService';
6
8
 
7
9
  /**
8
- * Tracks a component and logs an event to Amplitude when the component is visible.
10
+ * Tracks a component and logs `SpiffyMetricsEventName.ChatComponentVisible` when visible.
9
11
  *
10
- * @param component - The component to track.
11
12
  * @param element - The element to track visibility of.
12
- * @param eventProps - Additional properties to include with the event.
13
- * @param eventName - The Amplitude event name to track (defaults to ChatComponentVisible).
13
+ * @param eventProps - Properties to include with the event.
14
+ * @param rootMargin - Root margin for the intersection observer (defaults to 0px).
15
+ * @param enabled - Whether tracking is enabled (defaults to true).
14
16
  */
15
17
  export const useTrackComponentVisibleEvent = (
16
- component: SpiffyWidgets,
17
18
  element: RefObject<HTMLElement>,
18
19
  eventProps?: Record<string, unknown>,
19
- eventName: SpiffyMetricsEventName = SpiffyMetricsEventName.ChatComponentVisible,
20
- ) => {
21
- const isVisible = useIntersection(element, '0px');
20
+ rootMargin: string = '0px',
21
+ enabled: boolean = true,
22
+ ): { isVisible: boolean } => {
23
+ const isVisible = useIntersection(element, rootMargin);
22
24
  const hasTrackedEvent = useRef(false);
23
25
  const { trackEvent } = useAmplitude();
24
26
 
25
- const componentProps = (() => {
26
- if (eventName === SpiffyMetricsEventName.ChatComponentVisible) {
27
- return {
28
- chat_component: component,
29
- ...eventProps,
30
- };
31
- }
32
- if (eventName === SpiffyMetricsEventName.SearchComponentVisible) {
33
- return {
34
- search_component: component,
35
- ...eventProps,
36
- };
37
- }
38
- // Default case for other event types
39
- return {
40
- component,
41
- ...eventProps,
42
- };
43
- })();
44
-
45
27
  useEffect(() => {
46
- if (isVisible && !hasTrackedEvent.current) {
28
+ if (!enabled || hasTrackedEvent.current) {
29
+ return;
30
+ }
31
+ if (isVisible) {
32
+ trackEvent({
33
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
34
+ eventProps,
35
+ });
47
36
  trackEvent({
48
- eventName,
49
- eventProps: componentProps,
37
+ eventName: EnviveMetricsEventName.WidgetRendered,
38
+ eventProps: {
39
+ ...eventProps,
40
+ 'trigger.widget': eventProps?.widget_type,
41
+ },
50
42
  });
51
43
  hasTrackedEvent.current = true;
52
44
  }
53
- }, [isVisible, component, eventProps, eventName, componentProps, trackEvent]);
45
+ }, [enabled, isVisible, eventProps, trackEvent]);
46
+ return { isVisible };
54
47
  };
@@ -5,11 +5,12 @@ export interface WidgetInteractionContext {
5
5
  page_id: string;
6
6
  }
7
7
 
8
- interface WidgetInteractionTrigger {
8
+ export interface WidgetInteractionTrigger {
9
9
  interaction_id?: string;
10
10
  widget: WidgetInteractionComponent;
11
11
  widget_interaction: WidgetInteractionType;
12
12
  widget_interaction_data?: WidgetInteractionData | null;
13
+ interaction_class?: InteractionClass;
13
14
  }
14
15
 
15
16
  export interface InternalWidgetInteraction {
@@ -63,6 +64,29 @@ export enum WidgetInteractionType {
63
64
  MANUAL_SCROLL_TO_BOTTOM = 'manual_scroll_to_bottom',
64
65
  }
65
66
 
67
+ export enum InteractionClass {
68
+ PASSIVE = 'passive', // No user commitment — hover, scroll, visibility triggers
69
+ NAVIGATIONAL = 'navigational', // Opens/closes something — often a side effect of intent
70
+ INTENTIONAL = 'intentional', // Deliberate user action — clicks, submits, typed queries
71
+ }
72
+
73
+ export const INTERACTION_TYPE_CLASS: Record<WidgetInteractionType, InteractionClass> = {
74
+ [WidgetInteractionType.QUERY_INPUT_CLICKED]: InteractionClass.INTENTIONAL,
75
+ [WidgetInteractionType.SUGGESTION_CLICKED]: InteractionClass.INTENTIONAL,
76
+ [WidgetInteractionType.WIDGET_CLICKED]: InteractionClass.INTENTIONAL,
77
+ [WidgetInteractionType.WIDGET_HOVERED]: InteractionClass.PASSIVE,
78
+ [WidgetInteractionType.WIDGET_EXPANDED]: InteractionClass.NAVIGATIONAL,
79
+ [WidgetInteractionType.WIDGET_COLLAPSED]: InteractionClass.NAVIGATIONAL,
80
+ [WidgetInteractionType.SUGGESTION_SCROLLED]: InteractionClass.PASSIVE,
81
+ [WidgetInteractionType.LINK_CLICKED]: InteractionClass.INTENTIONAL,
82
+ [WidgetInteractionType.PRODUCT_CARD_CLICKED]: InteractionClass.INTENTIONAL,
83
+ [WidgetInteractionType.TEXT_LINK_CLICKED]: InteractionClass.INTENTIONAL,
84
+ [WidgetInteractionType.ARTICLE_LINK_CLICKED]: InteractionClass.INTENTIONAL,
85
+ [WidgetInteractionType.REVIEW_CARD_CLICKED]: InteractionClass.INTENTIONAL,
86
+ [WidgetInteractionType.MESSAGE_SUBMITTED]: InteractionClass.INTENTIONAL,
87
+ [WidgetInteractionType.MANUAL_SCROLL_TO_BOTTOM]: InteractionClass.PASSIVE,
88
+ };
89
+
66
90
  export type URL = {
67
91
  url: string;
68
92
  };
@@ -3,7 +3,7 @@ import { pageVariantInfoAtom } from 'src/atoms/app';
3
3
  import { useAmplitude } from 'src/contexts/amplitudeContext';
4
4
  import { PageVariantInfo } from 'src/contexts/pageContext/types';
5
5
  import { v4 as uuid } from 'uuid';
6
- import { InternalWidgetInteraction, WidgetInteraction } from './types';
6
+ import { INTERACTION_TYPE_CLASS, InternalWidgetInteraction, WidgetInteraction } from './types';
7
7
  import { extractPageContext } from './utils';
8
8
 
9
9
  const getInteractionId = () => uuid();
@@ -16,6 +16,8 @@ export const useWidgetInteraction = () => {
16
16
  if (props.trigger) {
17
17
  // eslint-disable-next-line no-param-reassign
18
18
  props.trigger.interaction_id = props.trigger.interaction_id || getInteractionId();
19
+ props.trigger.interaction_class =
20
+ props.trigger.interaction_class || INTERACTION_TYPE_CLASS[props.trigger.widget_interaction];
19
21
  }
20
22
 
21
23
  const eventProps = {
@@ -85,10 +85,12 @@ describe('AmplitudeService', () => {
85
85
  });
86
86
  });
87
87
 
88
+ const validApiKey = 'abcdef1234567890abcdef1234567890';
89
+
88
90
  const createService = (overrides?: Partial<AmplitudeServiceConfig>) => {
89
91
  return new AmplitudeService({
90
92
  userId: 'test-user-id',
91
- amplitudeApiKey: 'test-api-key',
93
+ amplitudeApiKey: validApiKey,
92
94
  dataResidency: 'US',
93
95
  env: 'test',
94
96
  orgId: 'test-org-id',
@@ -107,7 +109,7 @@ describe('AmplitudeService', () => {
107
109
  createService();
108
110
 
109
111
  expect(mockInit).toHaveBeenCalledWith(
110
- 'test-api-key',
112
+ validApiKey,
111
113
  'test-user-id',
112
114
  expect.objectContaining({
113
115
  serverZone: 'US',
@@ -121,7 +123,7 @@ describe('AmplitudeService', () => {
121
123
  it('should not be ready if userId is missing', () => {
122
124
  const service = new AmplitudeService({
123
125
  userId: '',
124
- amplitudeApiKey: 'test-api-key',
126
+ amplitudeApiKey: validApiKey,
125
127
  dataResidency: 'US',
126
128
  env: 'test',
127
129
  orgId: 'test-org-id',
@@ -157,7 +159,7 @@ describe('AmplitudeService', () => {
157
159
  it('should not be ready if featureFlagService is missing', () => {
158
160
  const service = new AmplitudeService({
159
161
  userId: 'test-user-id',
160
- amplitudeApiKey: 'test-api-key',
162
+ amplitudeApiKey: validApiKey,
161
163
  dataResidency: 'US',
162
164
  env: 'test',
163
165
  orgId: 'test-org-id',
@@ -269,7 +271,7 @@ describe('AmplitudeService', () => {
269
271
  it('should not track if client is not initialized', async () => {
270
272
  const service = new AmplitudeService({
271
273
  userId: '',
272
- amplitudeApiKey: 'test-api-key',
274
+ amplitudeApiKey: validApiKey,
273
275
  dataResidency: 'US',
274
276
  env: 'test',
275
277
  orgId: 'test-org-id',
@@ -419,7 +421,7 @@ describe('AmplitudeService', () => {
419
421
  it('should fall back to window.localStorage when getLocalStorageItem is not provided', async () => {
420
422
  const service = new AmplitudeService({
421
423
  userId: 'test-user-id',
422
- amplitudeApiKey: 'test-api-key',
424
+ amplitudeApiKey: validApiKey,
423
425
  dataResidency: 'US',
424
426
  env: 'test',
425
427
  orgId: 'test-org-id',
@@ -597,6 +599,67 @@ describe('AmplitudeService', () => {
597
599
  });
598
600
  });
599
601
 
602
+ describe('isMockApiKey', () => {
603
+ it('should return true when amplitudeApiKey is "mock-amplitude-key"', () => {
604
+ const service = createService({ amplitudeApiKey: 'mock-amplitude-key' });
605
+
606
+ expect(service.isMockApiKey).toBe(true);
607
+ });
608
+
609
+ it('should return false for a real API key', () => {
610
+ const service = createService();
611
+
612
+ expect(service.isMockApiKey).toBe(false);
613
+ });
614
+ });
615
+
616
+ describe('Mock API key behavior', () => {
617
+ it('should not initialize the Amplitude client when using mock API key', () => {
618
+ createService({ amplitudeApiKey: 'mock-amplitude-key' });
619
+
620
+ expect(mockInit).not.toHaveBeenCalled();
621
+ expect(mockAdd).not.toHaveBeenCalled();
622
+ });
623
+
624
+ it('should log a warning when mock API key is detected during initialization', () => {
625
+ const logWarnSpy = vi.spyOn(Logger.prototype, 'logWarn');
626
+
627
+ createService({ amplitudeApiKey: 'mock-amplitude-key' });
628
+
629
+ expect(logWarnSpy).toHaveBeenCalledWith(
630
+ 'Mock API key detected — running in mock mode, no events will be sent to Amplitude.',
631
+ 'mock-amplitude-key',
632
+ );
633
+ });
634
+
635
+ it('should report isReady as false when using mock API key', () => {
636
+ const service = createService({ amplitudeApiKey: 'mock-amplitude-key' });
637
+
638
+ expect(service.isReady).toBe(false);
639
+ });
640
+
641
+ it('should not track events when using mock API key', async () => {
642
+ const service = createService({ amplitudeApiKey: 'mock-amplitude-key' });
643
+
644
+ await service.trackEvent({
645
+ eventName: SpiffyMetricsEventName.ChatUserMessageInput,
646
+ eventProps: { message: 'test' },
647
+ });
648
+
649
+ expect(mockTrack).not.toHaveBeenCalled();
650
+ });
651
+
652
+ it('should not call crypto.subtle.digest when using mock API key', async () => {
653
+ const service = createService({ amplitudeApiKey: 'mock-amplitude-key' });
654
+
655
+ await service.trackEvent({
656
+ eventName: SpiffyMetricsEventName.ChatUserMessageInput,
657
+ });
658
+
659
+ expect(mockDigest).not.toHaveBeenCalled();
660
+ });
661
+ });
662
+
600
663
  describe('Session replay initialization', () => {
601
664
  it('should disable session replay by default', () => {
602
665
  createService();
@@ -56,6 +56,10 @@ export class AmplitudeService {
56
56
  this.initialize();
57
57
  }
58
58
 
59
+ get isMockApiKey(): boolean {
60
+ return this.config.amplitudeApiKey === 'mock-amplitude-key';
61
+ }
62
+
59
63
  get isReady(): boolean {
60
64
  return Boolean(
61
65
  this.config.userId &&
@@ -228,6 +232,14 @@ export class AmplitudeService {
228
232
  return;
229
233
  }
230
234
 
235
+ if (this.isMockApiKey) {
236
+ logger.logWarn(
237
+ 'Mock API key detected — running in mock mode, no events will be sent to Amplitude.',
238
+ this.config.amplitudeApiKey,
239
+ );
240
+ return;
241
+ }
242
+
231
243
  if (!this.amplitudeClient) {
232
244
  const currentAmplitudeInstance: BrowserClient = createInstance();
233
245
 
@@ -353,6 +365,7 @@ export class AmplitudeService {
353
365
  eventGroups,
354
366
  alsoSendToGoogleAnalytics = false,
355
367
  }: TrackEventParams): Promise<void> {
368
+ if (this.isMockApiKey) return;
356
369
  logger.logDebug('Submitting event', eventName);
357
370
  try {
358
371
  const decoratedEventName = AmplitudeService.decorateEventName(eventName);