@od-oneapp/analytics 2026.1.1301
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/README.md +509 -0
- package/dist/ai-YMnynb-t.mjs +3347 -0
- package/dist/ai-YMnynb-t.mjs.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client-CTzJVFU5.mjs +9 -0
- package/dist/client-CTzJVFU5.mjs.map +1 -0
- package/dist/client-CcFTauAh.mjs +54 -0
- package/dist/client-CcFTauAh.mjs.map +1 -0
- package/dist/client-CeOLjbac.mjs +281 -0
- package/dist/client-CeOLjbac.mjs.map +1 -0
- package/dist/client-D339NFJS.mjs +267 -0
- package/dist/client-D339NFJS.mjs.map +1 -0
- package/dist/client-next.d.mts +62 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +525 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +30 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +186 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-DPS6bSYo.d.mts +34 -0
- package/dist/config-DPS6bSYo.d.mts.map +1 -0
- package/dist/config-P6P5adJg.mjs +287 -0
- package/dist/config-P6P5adJg.mjs.map +1 -0
- package/dist/console-8bND3mMU.mjs +128 -0
- package/dist/console-8bND3mMU.mjs.map +1 -0
- package/dist/ecommerce-Cgu4wlux.mjs +993 -0
- package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
- package/dist/emitters-6-nKo8i-.mjs +208 -0
- package/dist/emitters-6-nKo8i-.mjs.map +1 -0
- package/dist/emitters-DldkVSPp.d.mts +12 -0
- package/dist/emitters-DldkVSPp.d.mts.map +1 -0
- package/dist/index-BfNWgfa5.d.mts +1494 -0
- package/dist/index-BfNWgfa5.d.mts.map +1 -0
- package/dist/index-BkIWe--N.d.mts +953 -0
- package/dist/index-BkIWe--N.d.mts.map +1 -0
- package/dist/index-jPzXRn52.d.mts +184 -0
- package/dist/index-jPzXRn52.d.mts.map +1 -0
- package/dist/manager-DvRRjza6.d.mts +76 -0
- package/dist/manager-DvRRjza6.d.mts.map +1 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
- package/dist/providers-http-client.d.mts +37 -0
- package/dist/providers-http-client.d.mts.map +1 -0
- package/dist/providers-http-client.mjs +320 -0
- package/dist/providers-http-client.mjs.map +1 -0
- package/dist/providers-http-server.d.mts +31 -0
- package/dist/providers-http-server.d.mts.map +1 -0
- package/dist/providers-http-server.mjs +297 -0
- package/dist/providers-http-server.mjs.map +1 -0
- package/dist/providers-http.d.mts +46 -0
- package/dist/providers-http.d.mts.map +1 -0
- package/dist/providers-http.mjs +4 -0
- package/dist/server-edge.d.mts +9 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +373 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +67 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +193 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +7 -0
- package/dist/service-cYtBBL8x.mjs +945 -0
- package/dist/service-cYtBBL8x.mjs.map +1 -0
- package/dist/shared.d.mts +16 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +93 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types-BxBnNQ0V.d.mts +354 -0
- package/dist/types-BxBnNQ0V.d.mts.map +1 -0
- package/dist/types-CBvxUEaF.d.mts +216 -0
- package/dist/types-CBvxUEaF.d.mts.map +1 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +0 -0
- package/dist/vercel-types-lwakUfoI.d.mts +102 -0
- package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
- package/package.json +129 -0
- package/src/client/index.ts +164 -0
- package/src/client/manager.ts +71 -0
- package/src/client/next/components.tsx +270 -0
- package/src/client/next/hooks.ts +217 -0
- package/src/client/next/manager.ts +141 -0
- package/src/client/next.ts +144 -0
- package/src/client-next.ts +101 -0
- package/src/client.ts +89 -0
- package/src/examples/ai-sdk-patterns.ts +583 -0
- package/src/examples/emitter-patterns.ts +476 -0
- package/src/examples/nextjs-emitter-patterns.tsx +403 -0
- package/src/next/app-router.tsx +564 -0
- package/src/next/client.ts +419 -0
- package/src/next/index.ts +84 -0
- package/src/next/middleware.ts +429 -0
- package/src/next/rsc.tsx +300 -0
- package/src/next/server.ts +253 -0
- package/src/next/types.d.ts +220 -0
- package/src/providers/base-provider.ts +419 -0
- package/src/providers/console/client.ts +10 -0
- package/src/providers/console/index.ts +152 -0
- package/src/providers/console/server.ts +6 -0
- package/src/providers/console/types.ts +15 -0
- package/src/providers/http/client.ts +464 -0
- package/src/providers/http/index.ts +30 -0
- package/src/providers/http/server.ts +396 -0
- package/src/providers/http/types.ts +135 -0
- package/src/providers/posthog/client.ts +518 -0
- package/src/providers/posthog/index.ts +11 -0
- package/src/providers/posthog/server.ts +329 -0
- package/src/providers/posthog/types.ts +104 -0
- package/src/providers/segment/client.ts +113 -0
- package/src/providers/segment/index.ts +11 -0
- package/src/providers/segment/server.ts +115 -0
- package/src/providers/segment/types.ts +51 -0
- package/src/providers/vercel/client.ts +102 -0
- package/src/providers/vercel/index.ts +11 -0
- package/src/providers/vercel/server.ts +89 -0
- package/src/providers/vercel/types.ts +27 -0
- package/src/server/index.ts +103 -0
- package/src/server/manager.ts +62 -0
- package/src/server/next.ts +210 -0
- package/src/server-edge.ts +442 -0
- package/src/server-next.ts +39 -0
- package/src/server.ts +106 -0
- package/src/shared/emitters/ai/README.md +981 -0
- package/src/shared/emitters/ai/events/agent.ts +130 -0
- package/src/shared/emitters/ai/events/artifacts.ts +167 -0
- package/src/shared/emitters/ai/events/chat.ts +126 -0
- package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
- package/src/shared/emitters/ai/events/completion.ts +103 -0
- package/src/shared/emitters/ai/events/content-generation.ts +347 -0
- package/src/shared/emitters/ai/events/conversation.ts +332 -0
- package/src/shared/emitters/ai/events/product-features.ts +1402 -0
- package/src/shared/emitters/ai/events/streaming.ts +114 -0
- package/src/shared/emitters/ai/events/tool.ts +93 -0
- package/src/shared/emitters/ai/index.ts +69 -0
- package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
- package/src/shared/emitters/ai/track-ai.ts +50 -0
- package/src/shared/emitters/ai/types.ts +1041 -0
- package/src/shared/emitters/ai/utils.ts +468 -0
- package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
- package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
- package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
- package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
- package/src/shared/emitters/ecommerce/events/order.ts +199 -0
- package/src/shared/emitters/ecommerce/events/product.ts +205 -0
- package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
- package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
- package/src/shared/emitters/ecommerce/index.ts +46 -0
- package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
- package/src/shared/emitters/ecommerce/types.ts +314 -0
- package/src/shared/emitters/ecommerce/utils.ts +216 -0
- package/src/shared/emitters/emitter-types.ts +974 -0
- package/src/shared/emitters/emitters.ts +292 -0
- package/src/shared/emitters/helpers.ts +419 -0
- package/src/shared/emitters/index.ts +66 -0
- package/src/shared/index.ts +142 -0
- package/src/shared/ingestion/index.ts +66 -0
- package/src/shared/ingestion/schemas.ts +386 -0
- package/src/shared/ingestion/service.ts +628 -0
- package/src/shared/node22-features.ts +848 -0
- package/src/shared/providers/console-provider.ts +160 -0
- package/src/shared/types/base-types.ts +54 -0
- package/src/shared/types/console-types.ts +19 -0
- package/src/shared/types/posthog-types.ts +131 -0
- package/src/shared/types/segment-types.ts +15 -0
- package/src/shared/types/types.ts +397 -0
- package/src/shared/types/vercel-types.ts +19 -0
- package/src/shared/utils/config-client.ts +19 -0
- package/src/shared/utils/config.ts +250 -0
- package/src/shared/utils/emitter-adapter.ts +212 -0
- package/src/shared/utils/manager.test.ts +36 -0
- package/src/shared/utils/manager.ts +1322 -0
- package/src/shared/utils/posthog-bootstrap.ts +136 -0
- package/src/shared/utils/posthog-client-utils.ts +48 -0
- package/src/shared/utils/posthog-next-utils.ts +282 -0
- package/src/shared/utils/posthog-server-utils.ts +210 -0
- package/src/shared/utils/rate-limit.ts +289 -0
- package/src/shared/utils/security.ts +545 -0
- package/src/shared/utils/validation-client.ts +161 -0
- package/src/shared/utils/validation.ts +399 -0
- package/src/shared.ts +155 -0
- package/src/types/index.ts +62 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Client Analytics Manager
|
|
3
|
+
*
|
|
4
|
+
* Client-side analytics manager with static provider registry. Provides
|
|
5
|
+
* factory functions for creating client analytics instances with support
|
|
6
|
+
* for multiple providers (Console, HTTP, Segment, Vercel).
|
|
7
|
+
*
|
|
8
|
+
* **Providers Supported**:
|
|
9
|
+
* - `console`: Console logging provider (development/debugging)
|
|
10
|
+
* - `http`: HTTP provider for remote ingestion (oneapp-api)
|
|
11
|
+
* - `segment`: Segment.io analytics provider
|
|
12
|
+
* - `vercel`: Vercel Analytics provider
|
|
13
|
+
*
|
|
14
|
+
* @module @od-oneapp/analytics/client/manager
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { SegmentClientProvider } from '@integrations/segment/analytics-provider/client';
|
|
18
|
+
import { VercelClientProvider } from '@integrations/vercel/analytics-provider/client';
|
|
19
|
+
|
|
20
|
+
import { ConsoleProvider } from '../providers/console/client';
|
|
21
|
+
import { HttpClientProvider } from '../providers/http/client';
|
|
22
|
+
import { createAnalyticsManager } from '../shared/utils/manager';
|
|
23
|
+
|
|
24
|
+
import type { AnalyticsConfig, AnalyticsManager, ProviderRegistry } from '../shared/types/types';
|
|
25
|
+
|
|
26
|
+
// Static provider registry for client environments
|
|
27
|
+
const CLIENT_PROVIDERS: ProviderRegistry = {
|
|
28
|
+
console: config => new ConsoleProvider(config),
|
|
29
|
+
http: config => new HttpClientProvider(config),
|
|
30
|
+
segment: config => new SegmentClientProvider(config),
|
|
31
|
+
vercel: config => new VercelClientProvider(config),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create and initialize a client analytics instance
|
|
36
|
+
* This is the primary way to create analytics for client-side applications
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const analytics = await createClientAnalytics({
|
|
41
|
+
* providers: {
|
|
42
|
+
* http: { endpoint: '/api/v1/ingest' },
|
|
43
|
+
* },
|
|
44
|
+
* });
|
|
45
|
+
* await analytics.track('Button Clicked', { variant: 'primary' });
|
|
46
|
+
* ```
|
|
47
|
+
* @param config - Analytics configuration including providers and settings
|
|
48
|
+
* @returns Promise resolving to initialized analytics manager
|
|
49
|
+
*/
|
|
50
|
+
export async function createClientAnalytics(config: AnalyticsConfig): Promise<AnalyticsManager> {
|
|
51
|
+
const manager = createAnalyticsManager(config, CLIENT_PROVIDERS);
|
|
52
|
+
await manager.initialize();
|
|
53
|
+
return manager;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a client analytics instance without initializing
|
|
58
|
+
* Useful when you need to control initialization timing
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const analytics = createClientAnalyticsUninitialized(config);
|
|
63
|
+
* // later inside a lazy effect
|
|
64
|
+
* await analytics.initialize();
|
|
65
|
+
* ```
|
|
66
|
+
* @param config - Analytics configuration including providers and settings
|
|
67
|
+
* @returns Uninitialized analytics manager instance
|
|
68
|
+
*/
|
|
69
|
+
export function createClientAnalyticsUninitialized(config: AnalyticsConfig): AnalyticsManager {
|
|
70
|
+
return createAnalyticsManager(config, CLIENT_PROVIDERS);
|
|
71
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js Client Components for Analytics
|
|
3
|
+
*
|
|
4
|
+
* React components for integrating analytics into Next.js applications.
|
|
5
|
+
* Provides provider components, tracked UI elements, and higher-order
|
|
6
|
+
* components for automatic event tracking.
|
|
7
|
+
*
|
|
8
|
+
* **Components**:
|
|
9
|
+
* - `AnalyticsProvider`: Provider component for App Router
|
|
10
|
+
* - `TrackedButton`: Button component with automatic click tracking
|
|
11
|
+
* - `TrackedLink`: Link component with automatic click tracking
|
|
12
|
+
* - `withViewTracking`: HOC for tracking component views
|
|
13
|
+
*
|
|
14
|
+
* **Note**: This file uses Next.js hooks. For framework-agnostic components,
|
|
15
|
+
* use the base components or accept navigation functions as parameters.
|
|
16
|
+
*
|
|
17
|
+
* @module @od-oneapp/analytics/client/next/components
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use client';
|
|
21
|
+
|
|
22
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
23
|
+
|
|
24
|
+
import type { Route } from 'next';
|
|
25
|
+
|
|
26
|
+
import { createClientObservability } from '@od-oneapp/observability/client/next';
|
|
27
|
+
import { useRouter } from 'next/navigation';
|
|
28
|
+
|
|
29
|
+
import { usePageTracking, useTrackEvent } from './hooks';
|
|
30
|
+
import { createNextJSClientAnalytics } from './manager';
|
|
31
|
+
|
|
32
|
+
import type { AnalyticsConfig, AnalyticsManager, Properties } from '../../shared/types/types';
|
|
33
|
+
import type { ObservabilityClient } from '@od-oneapp/observability/client/next';
|
|
34
|
+
|
|
35
|
+
// Global analytics instance
|
|
36
|
+
let globalAnalytics: AnalyticsManager | null = null;
|
|
37
|
+
|
|
38
|
+
// Global logger instance (from @od-oneapp/observability)
|
|
39
|
+
// Using minimal interface for optional fallback error logging
|
|
40
|
+
|
|
41
|
+
let logger: ObservabilityClient | null = null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Analytics provider component for App Router.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* Wraps your application and provides analytics context to all child components.
|
|
48
|
+
* Automatically initializes analytics and optionally tracks page views.
|
|
49
|
+
*
|
|
50
|
+
* @param props - Component props
|
|
51
|
+
* @param props.children - React children to wrap
|
|
52
|
+
* @param props.config - Analytics configuration
|
|
53
|
+
* @param props.autoPageTracking - Whether to automatically track page views (default: true)
|
|
54
|
+
* @param props.pageTrackingOptions - Options for page tracking
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* export default function RootLayout({ children }) {
|
|
59
|
+
* return (
|
|
60
|
+
* <AnalyticsProvider
|
|
61
|
+
* config={{
|
|
62
|
+
* providers: {
|
|
63
|
+
* http: { options: { endpoint: process.env.NEXT_PUBLIC_ANALYTICS_ENDPOINT! } }
|
|
64
|
+
* }
|
|
65
|
+
* }}
|
|
66
|
+
* autoPageTracking={true}
|
|
67
|
+
* >
|
|
68
|
+
* {children}
|
|
69
|
+
* </AnalyticsProvider>
|
|
70
|
+
* );
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function AnalyticsProvider({
|
|
75
|
+
autoPageTracking = true,
|
|
76
|
+
children,
|
|
77
|
+
config,
|
|
78
|
+
pageTrackingOptions,
|
|
79
|
+
}: {
|
|
80
|
+
children: React.ReactNode;
|
|
81
|
+
config: AnalyticsConfig;
|
|
82
|
+
autoPageTracking?: boolean;
|
|
83
|
+
pageTrackingOptions?: Parameters<typeof usePageTracking>[0];
|
|
84
|
+
}) {
|
|
85
|
+
// Initialize analytics
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!globalAnalytics) {
|
|
88
|
+
const initAnalytics = async () => {
|
|
89
|
+
try {
|
|
90
|
+
// Initialize logger if not already initialized
|
|
91
|
+
logger ??= await createClientObservability();
|
|
92
|
+
|
|
93
|
+
const instance = await createNextJSClientAnalytics(config);
|
|
94
|
+
globalAnalytics = instance;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (logger) {
|
|
97
|
+
logger.captureException(error, {
|
|
98
|
+
message: 'Failed to initialize analytics',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
void initAnalytics();
|
|
104
|
+
}
|
|
105
|
+
}, [config]);
|
|
106
|
+
|
|
107
|
+
// Auto page tracking
|
|
108
|
+
usePageTracking(autoPageTracking ? pageTrackingOptions : { skip: true });
|
|
109
|
+
|
|
110
|
+
return children as React.ReactElement;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Button component with automatic click tracking.
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* Wraps a standard button element and automatically tracks click events
|
|
118
|
+
* when the button is clicked.
|
|
119
|
+
*
|
|
120
|
+
* @param props - Button props plus tracking props
|
|
121
|
+
* @param props.eventName - Name of the event to track
|
|
122
|
+
* @param props.properties - Additional properties to include in the event
|
|
123
|
+
* @param props.onClick - Optional click handler (tracking happens before this)
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```tsx
|
|
127
|
+
* <TrackedButton
|
|
128
|
+
* eventName="Sign Up Clicked"
|
|
129
|
+
* properties={{ location: 'header', variant: 'primary' }}
|
|
130
|
+
* onClick={() => router.push('/signup')}
|
|
131
|
+
* >
|
|
132
|
+
* Sign Up
|
|
133
|
+
* </TrackedButton>
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function TrackedButton({
|
|
137
|
+
children,
|
|
138
|
+
eventName,
|
|
139
|
+
onClick,
|
|
140
|
+
properties,
|
|
141
|
+
...props
|
|
142
|
+
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
143
|
+
eventName: string;
|
|
144
|
+
properties?: Properties;
|
|
145
|
+
}) {
|
|
146
|
+
const track = useTrackEvent();
|
|
147
|
+
|
|
148
|
+
const handleClick = useCallback(
|
|
149
|
+
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
150
|
+
track(eventName, properties);
|
|
151
|
+
onClick?.(e);
|
|
152
|
+
},
|
|
153
|
+
[track, eventName, properties, onClick],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<button type="button" {...props} onClick={handleClick}>
|
|
158
|
+
{children}
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Link component with automatic click tracking.
|
|
165
|
+
*
|
|
166
|
+
* @remarks
|
|
167
|
+
* Wraps a standard anchor element and automatically tracks click events
|
|
168
|
+
* when the link is clicked. Also handles internal navigation for Next.js.
|
|
169
|
+
*
|
|
170
|
+
* @param props - Anchor props plus tracking props
|
|
171
|
+
* @param props.eventName - Name of the event to track
|
|
172
|
+
* @param props.properties - Additional properties to include in the event
|
|
173
|
+
* @param props.href - Link URL
|
|
174
|
+
* @param props.onClick - Optional click handler (tracking happens before this)
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* <TrackedLink
|
|
179
|
+
* href="/products"
|
|
180
|
+
* eventName="Product Link Clicked"
|
|
181
|
+
* properties={{ linkText: 'View Products' }}
|
|
182
|
+
* >
|
|
183
|
+
* View Products
|
|
184
|
+
* </TrackedLink>
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export function TrackedLink({
|
|
188
|
+
children,
|
|
189
|
+
eventName,
|
|
190
|
+
href,
|
|
191
|
+
onClick,
|
|
192
|
+
properties,
|
|
193
|
+
...props
|
|
194
|
+
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
195
|
+
eventName: string;
|
|
196
|
+
properties?: Properties;
|
|
197
|
+
}) {
|
|
198
|
+
const track = useTrackEvent();
|
|
199
|
+
const router = useRouter();
|
|
200
|
+
|
|
201
|
+
const handleClick = useCallback(
|
|
202
|
+
(e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
203
|
+
// Track the event
|
|
204
|
+
const enhancedProperties = {
|
|
205
|
+
...properties,
|
|
206
|
+
href,
|
|
207
|
+
link_text: typeof children === 'string' ? children : undefined,
|
|
208
|
+
};
|
|
209
|
+
track(eventName, enhancedProperties);
|
|
210
|
+
|
|
211
|
+
// Handle navigation
|
|
212
|
+
if (onClick) {
|
|
213
|
+
onClick(e);
|
|
214
|
+
} else if (href && !props.target && href.startsWith('/')) {
|
|
215
|
+
// Internal navigation
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
router.push(href as Route);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[track, eventName, properties, href, onClick, router, children, props.target],
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<a {...props} href={href} onClick={handleClick}>
|
|
225
|
+
{children}
|
|
226
|
+
</a>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Higher-order component for tracking component views.
|
|
232
|
+
*
|
|
233
|
+
* @remarks
|
|
234
|
+
* Wraps a component and automatically tracks a view event when the component
|
|
235
|
+
* is first rendered. Useful for tracking page sections or feature usage.
|
|
236
|
+
*
|
|
237
|
+
* @param Component - The component to wrap
|
|
238
|
+
* @param eventName - Name of the event to track
|
|
239
|
+
* @param getProperties - Optional function to extract properties from component props
|
|
240
|
+
* @returns Wrapped component with automatic view tracking
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```tsx
|
|
244
|
+
* const TrackedDashboard = withViewTracking(
|
|
245
|
+
* Dashboard,
|
|
246
|
+
* 'Dashboard Viewed',
|
|
247
|
+
* (props) => ({ userId: props.userId, plan: props.plan })
|
|
248
|
+
* );
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function withViewTracking<P extends object>(
|
|
252
|
+
Component: React.ComponentType<P>,
|
|
253
|
+
eventName: string,
|
|
254
|
+
getProperties?: (props: P) => Properties,
|
|
255
|
+
) {
|
|
256
|
+
return function TrackedComponent(props: P) {
|
|
257
|
+
const track = useTrackEvent();
|
|
258
|
+
const tracked = useRef(false);
|
|
259
|
+
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if (!tracked.current) {
|
|
262
|
+
tracked.current = true;
|
|
263
|
+
const properties = getProperties ? getProperties(props) : {};
|
|
264
|
+
track(eventName, properties);
|
|
265
|
+
}
|
|
266
|
+
}, [track, props]);
|
|
267
|
+
|
|
268
|
+
return <Component {...props} />;
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js Client Hooks for Analytics
|
|
3
|
+
*
|
|
4
|
+
* React hooks for integrating analytics into Next.js applications.
|
|
5
|
+
* Provides hooks for accessing analytics instances, tracking events,
|
|
6
|
+
* and automatic page view tracking.
|
|
7
|
+
*
|
|
8
|
+
* **Hooks**:
|
|
9
|
+
* - `useAnalytics()`: Get or create analytics instance
|
|
10
|
+
* - `usePageTracking()`: Automatic page view tracking in App Router
|
|
11
|
+
* - `useTrackEvent()`: Hook for tracking events
|
|
12
|
+
* - `useIdentifyUser()`: Hook for user identification
|
|
13
|
+
* - `resetAnalytics()`: Utility to reset analytics (useful for testing)
|
|
14
|
+
*
|
|
15
|
+
* **Note**: These hooks use Next.js navigation hooks. For framework-agnostic hooks,
|
|
16
|
+
* use the base hooks from './hooks' or './manager'.
|
|
17
|
+
*
|
|
18
|
+
* @module @od-oneapp/analytics/client/next/hooks
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use client';
|
|
22
|
+
|
|
23
|
+
// Note: These are Next.js hooks required for App Router integration
|
|
24
|
+
// Client components are already Next.js-specific, so this is acceptable
|
|
25
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
26
|
+
|
|
27
|
+
import { useParams, usePathname, useSearchParams } from 'next/navigation';
|
|
28
|
+
// Type-only imports for Next.js types (compile-time only, no runtime dependency)
|
|
29
|
+
|
|
30
|
+
import { createNextJSClientAnalytics } from './manager';
|
|
31
|
+
|
|
32
|
+
import type { AnalyticsConfig, AnalyticsManager, TrackingOptions } from '../../shared/types/types';
|
|
33
|
+
|
|
34
|
+
// Global analytics instance
|
|
35
|
+
let globalAnalytics: AnalyticsManager | null = null;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hook to get or create analytics instance.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Returns the global analytics instance, creating it if it doesn't exist
|
|
42
|
+
* and config is provided. Returns null if analytics hasn't been initialized.
|
|
43
|
+
*
|
|
44
|
+
* @param config - Optional analytics configuration (only used on first call)
|
|
45
|
+
* @returns Analytics manager instance or null if not initialized
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* const analytics = useAnalytics(config);
|
|
50
|
+
* useEffect(() => {
|
|
51
|
+
* if (analytics) {
|
|
52
|
+
* analytics.track('ClientReady');
|
|
53
|
+
* }
|
|
54
|
+
* }, [analytics]);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function useAnalytics(config?: AnalyticsConfig): AnalyticsManager | null {
|
|
58
|
+
const [analytics, setAnalytics] = useState<AnalyticsManager | null>(null);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!globalAnalytics && config) {
|
|
62
|
+
const initAnalytics = async () => {
|
|
63
|
+
try {
|
|
64
|
+
// Note: Client-side observability handled by manager
|
|
65
|
+
|
|
66
|
+
const instance = await createNextJSClientAnalytics(config);
|
|
67
|
+
globalAnalytics = instance;
|
|
68
|
+
setAnalytics(instance);
|
|
69
|
+
} catch {
|
|
70
|
+
// Failed to initialize analytics - silently continue
|
|
71
|
+
// TODO: Add proper error logging via observability package
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
void initAnalytics();
|
|
75
|
+
} else if (globalAnalytics && analytics !== globalAnalytics) {
|
|
76
|
+
// Use setTimeout to avoid synchronous setState in effect
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
setAnalytics(globalAnalytics);
|
|
79
|
+
}, 0);
|
|
80
|
+
}
|
|
81
|
+
}, [config]);
|
|
82
|
+
|
|
83
|
+
return analytics;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hook for automatic page view tracking in App Router.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* Automatically tracks page views when the pathname, search params, or route
|
|
91
|
+
* params change. Prevents duplicate tracking for the same page.
|
|
92
|
+
*
|
|
93
|
+
* @param options - Page tracking options
|
|
94
|
+
* @param options.trackSearch - Whether to include search params in tracking (default: false)
|
|
95
|
+
* @param options.trackParams - Whether to include route params in tracking (default: false)
|
|
96
|
+
* @param options.properties - Additional properties to include in page events
|
|
97
|
+
* @param options.skip - Whether to skip tracking (default: false)
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* usePageTracking({ trackSearch: true, trackParams: true });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function usePageTracking(options?: {
|
|
105
|
+
trackSearch?: boolean;
|
|
106
|
+
trackParams?: boolean;
|
|
107
|
+
properties?: Record<string, any>;
|
|
108
|
+
skip?: boolean;
|
|
109
|
+
}) {
|
|
110
|
+
const pathname = usePathname();
|
|
111
|
+
const searchParams = useSearchParams();
|
|
112
|
+
const params = useParams();
|
|
113
|
+
const tracked = useRef<string>('');
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (options?.skip || !globalAnalytics) return;
|
|
117
|
+
|
|
118
|
+
// Create unique key for this page view
|
|
119
|
+
const searchString = options?.trackSearch ? searchParams.toString() : '';
|
|
120
|
+
const pageKey = `${pathname}${searchString}`;
|
|
121
|
+
|
|
122
|
+
// Avoid duplicate tracking
|
|
123
|
+
if (tracked.current === pageKey) return;
|
|
124
|
+
tracked.current = pageKey;
|
|
125
|
+
|
|
126
|
+
// Build properties
|
|
127
|
+
const properties: Record<string, any> = {
|
|
128
|
+
...options?.properties,
|
|
129
|
+
url: window.location.href,
|
|
130
|
+
path: pathname,
|
|
131
|
+
referrer: document.referrer,
|
|
132
|
+
title: document.title,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (options?.trackSearch) {
|
|
136
|
+
properties.search = searchString;
|
|
137
|
+
properties.search_params = Object.fromEntries(searchParams.entries());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (options?.trackParams && params) {
|
|
141
|
+
properties.route_params = params;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Track page view
|
|
145
|
+
void globalAnalytics.page(pathname, properties);
|
|
146
|
+
}, [
|
|
147
|
+
pathname,
|
|
148
|
+
searchParams,
|
|
149
|
+
params,
|
|
150
|
+
options?.skip,
|
|
151
|
+
options?.trackSearch,
|
|
152
|
+
options?.trackParams,
|
|
153
|
+
options?.properties,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Hook for tracking events.
|
|
159
|
+
*
|
|
160
|
+
* @remarks
|
|
161
|
+
* Returns a callback function for tracking events. The callback is stable
|
|
162
|
+
* across renders and can be safely used in event handlers.
|
|
163
|
+
*
|
|
164
|
+
* @returns Callback function for tracking events
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```tsx
|
|
168
|
+
* const track = useTrackEvent();
|
|
169
|
+
* track('CTA Clicked', { label: 'Request Demo' });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export function useTrackEvent() {
|
|
173
|
+
return useCallback((event: string, properties?: any, options?: TrackingOptions) => {
|
|
174
|
+
if (!globalAnalytics) return;
|
|
175
|
+
void globalAnalytics.track(event, properties, options);
|
|
176
|
+
}, []);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hook for user identification.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* Returns a callback function for identifying users. The callback is stable
|
|
184
|
+
* across renders and can be safely used in event handlers.
|
|
185
|
+
*
|
|
186
|
+
* @returns Callback function for identifying users
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* const identify = useIdentifyUser();
|
|
191
|
+
* identify(user.id, { plan: user.plan });
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export function useIdentifyUser() {
|
|
195
|
+
return useCallback((userId: string, traits?: any, options?: TrackingOptions) => {
|
|
196
|
+
if (!globalAnalytics) return;
|
|
197
|
+
void globalAnalytics.identify(userId, traits, options);
|
|
198
|
+
}, []);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Utility to reset analytics (useful for testing).
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* Clears the global analytics instance. Useful in test environments to
|
|
206
|
+
* ensure clean state between tests.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* beforeEach(() => {
|
|
211
|
+
* resetAnalytics();
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export function resetAnalytics() {
|
|
216
|
+
globalAnalytics = null;
|
|
217
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js Client Analytics Manager
|
|
3
|
+
*
|
|
4
|
+
* Next.js client analytics manager with truly dynamic provider loading.
|
|
5
|
+
* Only imports providers that are actually configured to avoid webpack
|
|
6
|
+
* bundling unused dependencies, reducing bundle size.
|
|
7
|
+
*
|
|
8
|
+
* **Features**:
|
|
9
|
+
* - Dynamic provider loading (only configured providers are bundled)
|
|
10
|
+
* - Code splitting via webpack chunks
|
|
11
|
+
* - Lazy initialization support
|
|
12
|
+
* - Next.js App Router optimized
|
|
13
|
+
*
|
|
14
|
+
* @module @od-oneapp/analytics/client/next/manager
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Simple console fallback - observability integration can be added later
|
|
18
|
+
import { createAnalyticsManager } from '../../shared/utils/manager';
|
|
19
|
+
|
|
20
|
+
import type { AnalyticsConfig, AnalyticsManager, ProviderRegistry } from '../../shared/types/types';
|
|
21
|
+
const logWarn = (_message: string, _context?: any) => {
|
|
22
|
+
// No-op to avoid console warnings in production
|
|
23
|
+
// TODO: Add proper error logging via observability package
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a Next.js client analytics instance with runtime provider loading
|
|
28
|
+
* Only imports providers that are actually configured to avoid webpack bundling unused dependencies
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const analytics = await createNextJSClientAnalytics({
|
|
33
|
+
* providers: { posthog: { apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY! } },
|
|
34
|
+
* });
|
|
35
|
+
* await analytics.track('Dashboard Viewed');
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export async function createNextJSClientAnalytics(
|
|
39
|
+
config: AnalyticsConfig,
|
|
40
|
+
): Promise<AnalyticsManager> {
|
|
41
|
+
const CLIENT_PROVIDERS: ProviderRegistry = {};
|
|
42
|
+
|
|
43
|
+
// Get configured providers
|
|
44
|
+
const configuredProviders = Object.keys(config.providers);
|
|
45
|
+
|
|
46
|
+
// Only import configured providers using truly dynamic imports
|
|
47
|
+
const providerPromises = configuredProviders.map(async providerName => {
|
|
48
|
+
switch (providerName) {
|
|
49
|
+
case 'console': {
|
|
50
|
+
const module = await import(
|
|
51
|
+
/* webpackChunkName: "analytics-console" */ '../../providers/console/client'
|
|
52
|
+
);
|
|
53
|
+
CLIENT_PROVIDERS.console = config => new module.ConsoleProvider(config);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'posthog': {
|
|
57
|
+
const module = await import(
|
|
58
|
+
/* webpackChunkName: "analytics-posthog" */ '../../providers/posthog/client'
|
|
59
|
+
);
|
|
60
|
+
CLIENT_PROVIDERS.posthog = config => new module.PostHogClientProvider(config);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'segment': {
|
|
64
|
+
const module = await import(
|
|
65
|
+
/* webpackChunkName: "analytics-segment" */ '../../providers/segment/client'
|
|
66
|
+
);
|
|
67
|
+
CLIENT_PROVIDERS.segment = config => new module.SegmentClientProvider(config);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'vercel': {
|
|
71
|
+
const module = await import(
|
|
72
|
+
/* webpackChunkName: "analytics-vercel" */ '../../providers/vercel/client'
|
|
73
|
+
);
|
|
74
|
+
CLIENT_PROVIDERS.vercel = config => new module.VercelClientProvider(config);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
// Skip unknown providers
|
|
79
|
+
logWarn('Unknown analytics provider:', { providerName });
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Wait for all configured providers to load
|
|
85
|
+
await Promise.all(providerPromises);
|
|
86
|
+
|
|
87
|
+
const manager = createAnalyticsManager(config, CLIENT_PROVIDERS);
|
|
88
|
+
await manager.initialize();
|
|
89
|
+
return manager;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a Next.js client analytics instance without initializing
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const analytics = await createNextJSClientAnalyticsUninitialized(config);
|
|
98
|
+
* // run setup before first event
|
|
99
|
+
* await analytics.initialize();
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export async function createNextJSClientAnalyticsUninitialized(
|
|
103
|
+
config: AnalyticsConfig,
|
|
104
|
+
): Promise<AnalyticsManager> {
|
|
105
|
+
const CLIENT_PROVIDERS: ProviderRegistry = {};
|
|
106
|
+
|
|
107
|
+
// Get configured providers
|
|
108
|
+
const configuredProviders = Object.keys(config.providers);
|
|
109
|
+
|
|
110
|
+
// Dynamically import only configured providers
|
|
111
|
+
for (const providerName of configuredProviders) {
|
|
112
|
+
switch (providerName) {
|
|
113
|
+
case 'console': {
|
|
114
|
+
const { ConsoleProvider } = await import('../../providers/console/client');
|
|
115
|
+
CLIENT_PROVIDERS.console = config => new ConsoleProvider(config);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'posthog': {
|
|
119
|
+
const { PostHogClientProvider } = await import('../../providers/posthog/client');
|
|
120
|
+
CLIENT_PROVIDERS.posthog = config => new PostHogClientProvider(config);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'segment': {
|
|
124
|
+
const { SegmentClientProvider } = await import('../../providers/segment/client');
|
|
125
|
+
CLIENT_PROVIDERS.segment = config => new SegmentClientProvider(config);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'vercel': {
|
|
129
|
+
const { VercelClientProvider } = await import('../../providers/vercel/client');
|
|
130
|
+
CLIENT_PROVIDERS.vercel = config => new VercelClientProvider(config);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
// Skip unknown providers
|
|
135
|
+
logWarn('Unknown analytics provider:', { providerName });
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return createAnalyticsManager(config, CLIENT_PROVIDERS);
|
|
141
|
+
}
|