@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.
- package/dist/application/models/guards/api/isApiPDPEventAttributes.cjs +2 -2
- package/dist/application/models/guards/api/isApiPDPEventAttributes.js +2 -2
- package/dist/atoms/app/index.d.cts +7 -7
- package/dist/atoms/app/index.d.ts +6 -6
- package/dist/atoms/app/variant.d.cts +6 -6
- package/dist/atoms/chat/chatState.d.cts +18 -18
- package/dist/atoms/chat/chatState.d.ts +18 -18
- package/dist/atoms/chat/form.d.cts +3 -3
- package/dist/atoms/chat/form.d.ts +2 -2
- package/dist/atoms/chat/index.d.cts +1 -1
- package/dist/atoms/chat/index.d.ts +2 -2
- package/dist/atoms/chat/lastMessage.d.cts +2 -2
- package/dist/atoms/chat/lastMessage.d.ts +2 -2
- package/dist/atoms/chat/messageQueue.d.cts +7 -7
- package/dist/atoms/chat/messageQueue.d.ts +6 -6
- package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
- package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
- package/dist/atoms/chat/renderedWidgetRefs.d.cts +3 -3
- package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
- package/dist/atoms/chat/replies.d.cts +3 -3
- package/dist/atoms/chat/suggestions.d.cts +3 -3
- package/dist/atoms/chat/suggestions.d.ts +2 -2
- package/dist/atoms/envive/enviveConfig.d.cts +13 -13
- package/dist/atoms/envive/enviveConfig.d.ts +13 -13
- package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
- package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
- package/dist/atoms/org/customerService.d.cts +6 -6
- package/dist/atoms/org/customerService.d.ts +6 -6
- package/dist/atoms/org/graphqlConfig.d.cts +4 -4
- package/dist/atoms/org/graphqlConfig.d.ts +4 -4
- package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
- package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
- package/dist/atoms/org/orgAnalyticsConfig.d.cts +5 -5
- package/dist/atoms/org/orgAnalyticsConfig.d.ts +5 -5
- package/dist/atoms/search/chatSearch.d.cts +17 -17
- package/dist/atoms/search/chatSearch.d.ts +17 -17
- package/dist/atoms/search/searchAPI.d.cts +13 -13
- package/dist/atoms/search/searchAPI.d.ts +13 -13
- package/dist/atoms/search/utils.d.ts +1 -1
- package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
- package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
- package/dist/contexts/amplitudeContext/amplitudeContext.cjs +9 -3
- package/dist/contexts/amplitudeContext/amplitudeContext.d.cts +2 -1
- package/dist/contexts/amplitudeContext/amplitudeContext.d.ts +2 -1
- package/dist/contexts/amplitudeContext/amplitudeContext.js +9 -3
- package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
- package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
- package/dist/contexts/types.d.cts +1 -1
- package/dist/contexts/types.d.ts +1 -1
- package/dist/contexts/typesV3.d.cts +1 -1
- package/dist/contexts/typesV3.d.ts +1 -1
- package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
- package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
- package/dist/hooks/Search/useSearch.cjs +12 -4
- package/dist/hooks/Search/useSearch.js +12 -4
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +20 -27
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +8 -8
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +8 -8
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +21 -28
- package/dist/hooks/WidgetInteraction/types.cjs +25 -1
- package/dist/hooks/WidgetInteraction/types.d.cts +11 -2
- package/dist/hooks/WidgetInteraction/types.d.ts +11 -2
- package/dist/hooks/WidgetInteraction/types.js +24 -2
- package/dist/hooks/WidgetInteraction/useWidgetInteraction.cjs +6 -2
- package/dist/hooks/WidgetInteraction/useWidgetInteraction.js +6 -2
- package/dist/hooks/utils.d.ts +1 -1
- package/dist/services/amplitudeService/amplitudeService.cjs +9 -1
- package/dist/services/amplitudeService/amplitudeService.d.cts +2 -1
- package/dist/services/amplitudeService/amplitudeService.d.ts +2 -1
- package/dist/services/amplitudeService/amplitudeService.js +9 -1
- package/package.json +1 -1
- package/src/application/models/guards/api/isApiPDPEventAttributes.ts +1 -1
- package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +31 -27
- package/src/contexts/amplitudeContext/amplitudeContext.tsx +5 -2
- package/src/contexts/pageContext/__tests__/pageContext.test.tsx +10 -0
- package/src/hooks/Search/__tests__/useSearch.test.tsx +0 -4
- package/src/hooks/Search/useSearch.tsx +14 -8
- package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +27 -34
- package/src/hooks/WidgetInteraction/types.ts +25 -1
- package/src/hooks/WidgetInteraction/useWidgetInteraction.ts +3 -1
- package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +69 -6
- 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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
|
|
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 {
|
|
4
|
+
import {
|
|
5
|
+
EnviveMetricsEventName,
|
|
6
|
+
SpiffyMetricsEventName,
|
|
7
|
+
} from 'src/services/amplitudeService/amplitudeService';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* Tracks a component and logs
|
|
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 -
|
|
13
|
-
* @param
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 (
|
|
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:
|
|
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
|
-
}, [
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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);
|