@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,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview PostHog bootstrap utilities for Next.js server-side rendering
|
|
3
|
+
* PostHog bootstrap utilities for Next.js server-side rendering
|
|
4
|
+
* Enables consistent feature flag delivery from server to client
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BootstrapData, PostHogCookie } from '../types/posthog-types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a unique distinct ID (compatible with PostHog format)
|
|
11
|
+
*/
|
|
12
|
+
export function generateDistinctId(): string {
|
|
13
|
+
// Use a similar format to PostHog's default distinct ID generation
|
|
14
|
+
const timestamp = Date.now().toString(36);
|
|
15
|
+
const randomStr = Math.random().toString(36).slice(2, 15);
|
|
16
|
+
return `${timestamp}-${randomStr}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse PostHog cookie to extract distinct ID
|
|
21
|
+
*/
|
|
22
|
+
export function parsePostHogCookie(
|
|
23
|
+
cookieValue: string,
|
|
24
|
+
_projectApiKey: string,
|
|
25
|
+
): PostHogCookie | null {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(cookieValue);
|
|
28
|
+
|
|
29
|
+
// Validate the cookie structure
|
|
30
|
+
if (parsed && typeof parsed.distinct_id === 'string') {
|
|
31
|
+
return parsed as PostHogCookie;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
} catch {
|
|
36
|
+
// Silently fail - cookies may be malformed
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get PostHog cookie name for a project
|
|
43
|
+
*/
|
|
44
|
+
export function getPostHogCookieName(projectApiKey: string): string {
|
|
45
|
+
return `ph_${projectApiKey}_posthog`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract distinct ID from cookies (Next.js compatible)
|
|
50
|
+
*/
|
|
51
|
+
export function getDistinctIdFromCookies(
|
|
52
|
+
cookies: any, // Next.js cookies object or cookie string
|
|
53
|
+
projectApiKey: string,
|
|
54
|
+
): string | null {
|
|
55
|
+
try {
|
|
56
|
+
const cookieName = getPostHogCookieName(projectApiKey);
|
|
57
|
+
|
|
58
|
+
// Handle Next.js cookies() object
|
|
59
|
+
if (cookies && typeof cookies.get === 'function') {
|
|
60
|
+
const cookie = cookies.get(cookieName);
|
|
61
|
+
if (cookie?.value) {
|
|
62
|
+
const parsed = parsePostHogCookie(cookie.value, projectApiKey);
|
|
63
|
+
return parsed?.distinct_id ?? null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle raw cookie string
|
|
68
|
+
if (typeof cookies === 'string') {
|
|
69
|
+
const cookieMatch = cookies.match(new RegExp(`${cookieName}=([^;]+)`));
|
|
70
|
+
if (cookieMatch?.[1]) {
|
|
71
|
+
const parsed = parsePostHogCookie(decodeURIComponent(cookieMatch[1]), projectApiKey);
|
|
72
|
+
return parsed?.distinct_id ?? null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
77
|
+
} catch {
|
|
78
|
+
// Silently fail - cookies may be malformed or missing
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create bootstrap data for PostHog client initialization
|
|
85
|
+
*/
|
|
86
|
+
export function createBootstrapData(distinctId: string): BootstrapData {
|
|
87
|
+
return {
|
|
88
|
+
distinctID: distinctId,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Cached bootstrap data fetcher (React cache compatible)
|
|
94
|
+
*/
|
|
95
|
+
const bootstrapCache = new Map<string, { data: BootstrapData; timestamp: number }>();
|
|
96
|
+
const CACHE_TTL = 30000; // 30 seconds
|
|
97
|
+
|
|
98
|
+
export function getCachedBootstrapData(distinctId: string): BootstrapData | null {
|
|
99
|
+
const cached = bootstrapCache.get(distinctId);
|
|
100
|
+
|
|
101
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
102
|
+
return cached.data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Clean up expired entries
|
|
106
|
+
for (const [key, value] of bootstrapCache.entries()) {
|
|
107
|
+
if (Date.now() - value.timestamp >= CACHE_TTL) {
|
|
108
|
+
bootstrapCache.delete(key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function setCachedBootstrapData(distinctId: string, data: BootstrapData): void {
|
|
116
|
+
bootstrapCache.set(distinctId, {
|
|
117
|
+
data,
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validate bootstrap data structure
|
|
124
|
+
*/
|
|
125
|
+
export function validateBootstrapData(data: any): data is BootstrapData {
|
|
126
|
+
return data && typeof data === 'object' && typeof data.distinctID === 'string';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create minimal bootstrap data when full data is unavailable
|
|
131
|
+
*/
|
|
132
|
+
export function createMinimalBootstrapData(distinctId?: string): BootstrapData {
|
|
133
|
+
return {
|
|
134
|
+
distinctID: distinctId ?? generateDistinctId(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Client-safe PostHog utilities for Next.js
|
|
3
|
+
* Client-safe PostHog utilities for Next.js
|
|
4
|
+
* These utilities can be safely imported in client-side code
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type BootstrapData } from '../types/posthog-types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PostHog provider configuration helper for Next.js
|
|
11
|
+
* This is safe to use in client-side code
|
|
12
|
+
*/
|
|
13
|
+
function createPostHogConfig(
|
|
14
|
+
apiKey: string,
|
|
15
|
+
options?: {
|
|
16
|
+
autocapture?: boolean;
|
|
17
|
+
bootstrap?: BootstrapData;
|
|
18
|
+
capture_pageview?: boolean;
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
host?: string;
|
|
21
|
+
session_recording?: boolean;
|
|
22
|
+
},
|
|
23
|
+
) {
|
|
24
|
+
return {
|
|
25
|
+
apiKey,
|
|
26
|
+
options: {
|
|
27
|
+
api_host: options?.host ?? 'https://app.posthog.com',
|
|
28
|
+
autocapture: options?.autocapture ?? true,
|
|
29
|
+
bootstrap: options?.bootstrap,
|
|
30
|
+
capture_pageview: options?.capture_pageview ?? false, // We handle manually
|
|
31
|
+
disable_session_recording: !(options?.session_recording ?? true),
|
|
32
|
+
loaded: options?.debug
|
|
33
|
+
? (posthog: any) => {
|
|
34
|
+
// Only enable debug in development
|
|
35
|
+
if (options.debug) {
|
|
36
|
+
posthog.debug();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
: undefined,
|
|
40
|
+
...options,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Re-export client-safe utilities from posthog-bootstrap
|
|
47
|
+
*/
|
|
48
|
+
export { createPostHogConfig };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js specific utilities for PostHog integration
|
|
3
|
+
* Next.js specific utilities for PostHog integration
|
|
4
|
+
* Provides seamless server-side rendering and client hydration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createBootstrapData,
|
|
9
|
+
createMinimalBootstrapData,
|
|
10
|
+
generateDistinctId,
|
|
11
|
+
getCachedBootstrapData,
|
|
12
|
+
getDistinctIdFromCookies,
|
|
13
|
+
setCachedBootstrapData,
|
|
14
|
+
} from './posthog-bootstrap';
|
|
15
|
+
|
|
16
|
+
// Dynamic import of React cache for Next.js compatibility
|
|
17
|
+
import type { BootstrapData } from '../types/posthog-types';
|
|
18
|
+
|
|
19
|
+
type CacheFunction = <T extends (...args: any[]) => any>(fn: T) => T;
|
|
20
|
+
|
|
21
|
+
let cache: CacheFunction;
|
|
22
|
+
try {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, prefer-destructuring
|
|
24
|
+
cache = require('react').cache;
|
|
25
|
+
} catch {
|
|
26
|
+
// Fallback for non-React environments
|
|
27
|
+
cache = <T extends (...args: any[]) => any>(fn: T): T => fn;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a PostHog client for server-side operations
|
|
32
|
+
*/
|
|
33
|
+
async function createPostHogServerClient(
|
|
34
|
+
apiKey: string,
|
|
35
|
+
options?: {
|
|
36
|
+
host?: string | undefined;
|
|
37
|
+
timeout?: number | undefined;
|
|
38
|
+
flushAt?: number | undefined;
|
|
39
|
+
flushInterval?: number | undefined;
|
|
40
|
+
},
|
|
41
|
+
) {
|
|
42
|
+
if (typeof window !== 'undefined') {
|
|
43
|
+
throw new TypeError('createPostHogServerClient should only be called on the server');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const { PostHog } = await import('posthog-node');
|
|
48
|
+
|
|
49
|
+
return new PostHog(apiKey, {
|
|
50
|
+
flushAt: options?.flushAt ?? 1,
|
|
51
|
+
flushInterval: options?.flushInterval ?? 0,
|
|
52
|
+
host: options?.host ?? 'https://app.posthog.com',
|
|
53
|
+
...(options?.timeout !== undefined && { timeout: options.timeout }),
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error('PostHog Node.js SDK not available. Install with: npm install posthog-node');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get or generate distinct ID for server-side operations
|
|
62
|
+
* Uses React cache to ensure consistent ID across server renders
|
|
63
|
+
*/
|
|
64
|
+
const getOrGenerateDistinctId = cache(
|
|
65
|
+
async (
|
|
66
|
+
cookies: { get: (name: string) => { value: string } | undefined },
|
|
67
|
+
apiKey: string,
|
|
68
|
+
): Promise<string> => {
|
|
69
|
+
// Try to get from cookies first
|
|
70
|
+
const existingId = getDistinctIdFromCookies(cookies, apiKey);
|
|
71
|
+
if (existingId) {
|
|
72
|
+
return existingId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Generate new ID
|
|
76
|
+
return generateDistinctId();
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Fetch PostHog bootstrap data on the server
|
|
82
|
+
* Uses React cache to prevent duplicate requests
|
|
83
|
+
*/
|
|
84
|
+
const getPostHogBootstrapData = cache(
|
|
85
|
+
async (
|
|
86
|
+
apiKey: string,
|
|
87
|
+
distinctId: string,
|
|
88
|
+
options?: {
|
|
89
|
+
host?: string | undefined;
|
|
90
|
+
timeout?: number | undefined;
|
|
91
|
+
},
|
|
92
|
+
): Promise<BootstrapData> => {
|
|
93
|
+
// Check cache first
|
|
94
|
+
const cached = getCachedBootstrapData(distinctId);
|
|
95
|
+
if (cached) {
|
|
96
|
+
return cached;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const client = await createPostHogServerClient(apiKey, {
|
|
101
|
+
host: options?.host,
|
|
102
|
+
// Add timeout for server-side requests
|
|
103
|
+
timeout: options?.timeout ?? 5000,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Cleanup
|
|
107
|
+
await client.shutdown();
|
|
108
|
+
|
|
109
|
+
const bootstrapData = createBootstrapData(distinctId);
|
|
110
|
+
|
|
111
|
+
// Cache the result
|
|
112
|
+
setCachedBootstrapData(distinctId, bootstrapData);
|
|
113
|
+
|
|
114
|
+
return bootstrapData;
|
|
115
|
+
} catch {
|
|
116
|
+
// Return minimal data on error
|
|
117
|
+
const minimalData = createMinimalBootstrapData(distinctId);
|
|
118
|
+
setCachedBootstrapData(distinctId, minimalData);
|
|
119
|
+
return minimalData;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Complete bootstrap data fetcher for Next.js apps
|
|
126
|
+
* Handles cookies, distinct ID generation, and flag fetching
|
|
127
|
+
*/
|
|
128
|
+
const getCompleteBootstrapData = cache(
|
|
129
|
+
async (
|
|
130
|
+
cookies: { get: (name: string) => { value: string } | undefined },
|
|
131
|
+
apiKey: string,
|
|
132
|
+
options?: {
|
|
133
|
+
host?: string | undefined;
|
|
134
|
+
timeout?: number | undefined;
|
|
135
|
+
fallbackToGenerated?: boolean | undefined;
|
|
136
|
+
},
|
|
137
|
+
): Promise<BootstrapData> => {
|
|
138
|
+
try {
|
|
139
|
+
// Get or generate distinct ID
|
|
140
|
+
const distinctId = await getOrGenerateDistinctId(cookies, apiKey);
|
|
141
|
+
|
|
142
|
+
// Fetch bootstrap data
|
|
143
|
+
return await getPostHogBootstrapData(apiKey, distinctId, options);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (options?.fallbackToGenerated !== false) {
|
|
146
|
+
// Fallback to generated ID with empty flags
|
|
147
|
+
const fallbackId = generateDistinctId();
|
|
148
|
+
return createMinimalBootstrapData(fallbackId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* React Suspense-compatible PostHog bootstrap fetcher
|
|
158
|
+
*/
|
|
159
|
+
function createPostHogSuspenseData(
|
|
160
|
+
cookies: { get: (name: string) => { value: string } | undefined },
|
|
161
|
+
apiKey: string,
|
|
162
|
+
options?: {
|
|
163
|
+
host?: string | undefined;
|
|
164
|
+
timeout?: number | undefined;
|
|
165
|
+
},
|
|
166
|
+
) {
|
|
167
|
+
let status = 'pending';
|
|
168
|
+
let result: BootstrapData;
|
|
169
|
+
let error: unknown;
|
|
170
|
+
|
|
171
|
+
const suspender = (async () => {
|
|
172
|
+
try {
|
|
173
|
+
const data = await getCompleteBootstrapData(cookies, apiKey, options);
|
|
174
|
+
status = 'fulfilled';
|
|
175
|
+
result = data;
|
|
176
|
+
return data;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
status = 'rejected';
|
|
179
|
+
error = err;
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
})();
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
read() {
|
|
186
|
+
if (status === 'pending') {
|
|
187
|
+
throw suspender;
|
|
188
|
+
} else if (status === 'rejected') {
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* PostHog provider configuration helper for Next.js
|
|
198
|
+
*/
|
|
199
|
+
function createPostHogConfig(
|
|
200
|
+
apiKey: string,
|
|
201
|
+
options?: {
|
|
202
|
+
host?: string | undefined;
|
|
203
|
+
autocapture?: boolean | undefined;
|
|
204
|
+
capture_pageview?: boolean | undefined;
|
|
205
|
+
session_recording?: boolean | undefined;
|
|
206
|
+
bootstrap?: BootstrapData | undefined;
|
|
207
|
+
debug?: boolean | undefined;
|
|
208
|
+
},
|
|
209
|
+
) {
|
|
210
|
+
return {
|
|
211
|
+
apiKey,
|
|
212
|
+
options: {
|
|
213
|
+
api_host: options?.host ?? 'https://app.posthog.com',
|
|
214
|
+
autocapture: options?.autocapture ?? true,
|
|
215
|
+
bootstrap: options?.bootstrap,
|
|
216
|
+
capture_pageview: options?.capture_pageview ?? false, // We handle manually
|
|
217
|
+
disable_session_recording: !(options?.session_recording ?? true),
|
|
218
|
+
loaded: options?.debug
|
|
219
|
+
? (posthog: { debug: () => void }) => {
|
|
220
|
+
// Only enable debug in development
|
|
221
|
+
if (options.debug) {
|
|
222
|
+
posthog.debug();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
: undefined,
|
|
226
|
+
...options,
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Middleware helper for PostHog tracking
|
|
233
|
+
*/
|
|
234
|
+
function _createPostHogMiddleware(apiKey: string) {
|
|
235
|
+
return async (
|
|
236
|
+
request: {
|
|
237
|
+
headers?: {
|
|
238
|
+
get?: (name: string) => string | null;
|
|
239
|
+
[key: string]: string | undefined | ((name: string) => string | null);
|
|
240
|
+
};
|
|
241
|
+
ip?: string;
|
|
242
|
+
cookies?: { get: (name: string) => { value: string } | undefined };
|
|
243
|
+
},
|
|
244
|
+
_response?: unknown,
|
|
245
|
+
) => {
|
|
246
|
+
try {
|
|
247
|
+
// Extract user info from request
|
|
248
|
+
const userAgent = request.headers?.get?.('user-agent') ?? request.headers?.['user-agent'];
|
|
249
|
+
const referer = request.headers?.get?.('referer') ?? request.headers?.['referer'];
|
|
250
|
+
const ip = request.ip ?? request.headers?.get?.('x-forwarded-for') ?? 'unknown';
|
|
251
|
+
|
|
252
|
+
// Get distinct ID from cookies
|
|
253
|
+
const { cookies } = request;
|
|
254
|
+
const distinctId = getDistinctIdFromCookies(cookies, apiKey) ?? generateDistinctId();
|
|
255
|
+
|
|
256
|
+
// Create tracking context
|
|
257
|
+
return {
|
|
258
|
+
distinctId,
|
|
259
|
+
ip,
|
|
260
|
+
referer,
|
|
261
|
+
timestamp: new Date().toISOString(),
|
|
262
|
+
userAgent,
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Export commonly used utilities
|
|
272
|
+
*/
|
|
273
|
+
export {
|
|
274
|
+
createPostHogConfig,
|
|
275
|
+
// eslint-disable-next-line no-used-underscore-vars/no-used-underscore-vars
|
|
276
|
+
_createPostHogMiddleware as createPostHogMiddleware,
|
|
277
|
+
createPostHogServerClient,
|
|
278
|
+
createPostHogSuspenseData,
|
|
279
|
+
getCompleteBootstrapData,
|
|
280
|
+
getOrGenerateDistinctId,
|
|
281
|
+
getPostHogBootstrapData,
|
|
282
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Server-only PostHog utilities for Next.js
|
|
3
|
+
* Server-only PostHog utilities for Next.js
|
|
4
|
+
* These utilities should only be imported in server-side code
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Dynamic import of React cache for Next.js compatibility
|
|
8
|
+
import { type BootstrapData } from '../types/posthog-types';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createBootstrapData,
|
|
12
|
+
createMinimalBootstrapData,
|
|
13
|
+
generateDistinctId,
|
|
14
|
+
getCachedBootstrapData,
|
|
15
|
+
getDistinctIdFromCookies,
|
|
16
|
+
setCachedBootstrapData,
|
|
17
|
+
} from './posthog-bootstrap';
|
|
18
|
+
|
|
19
|
+
let cache: any;
|
|
20
|
+
try {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, prefer-destructuring
|
|
22
|
+
cache = require('react').cache;
|
|
23
|
+
} catch {
|
|
24
|
+
// Fallback for non-React environments
|
|
25
|
+
cache = <T extends (...args: any[]) => any>(fn: T): T => fn;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a PostHog client for server-side operations
|
|
30
|
+
*/
|
|
31
|
+
async function createPostHogServerClient(apiKey: string, options?: any) {
|
|
32
|
+
try {
|
|
33
|
+
const { PostHog } = await import('posthog-node');
|
|
34
|
+
|
|
35
|
+
return new PostHog(apiKey, {
|
|
36
|
+
flushAt: 1,
|
|
37
|
+
flushInterval: 0,
|
|
38
|
+
host: 'https://app.posthog.com',
|
|
39
|
+
...options,
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error('PostHog Node.js SDK not available. Install with: npm install posthog-node');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get or generate distinct ID for server-side operations
|
|
48
|
+
* Uses React cache to ensure consistent ID across server renders
|
|
49
|
+
*/
|
|
50
|
+
const getOrGenerateDistinctId = cache(async (cookies: any, apiKey: string): Promise<string> => {
|
|
51
|
+
// Try to get from cookies first
|
|
52
|
+
const existingId = getDistinctIdFromCookies(cookies, apiKey);
|
|
53
|
+
if (existingId) {
|
|
54
|
+
return existingId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate new ID
|
|
58
|
+
return generateDistinctId();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fetch PostHog bootstrap data on the server
|
|
63
|
+
* Uses React cache to prevent duplicate requests
|
|
64
|
+
*/
|
|
65
|
+
const getPostHogBootstrapData = cache(
|
|
66
|
+
async (
|
|
67
|
+
apiKey: string,
|
|
68
|
+
distinctId: string,
|
|
69
|
+
options?: {
|
|
70
|
+
host?: string;
|
|
71
|
+
timeout?: number;
|
|
72
|
+
},
|
|
73
|
+
): Promise<BootstrapData> => {
|
|
74
|
+
// Check cache first
|
|
75
|
+
const cached = getCachedBootstrapData(distinctId);
|
|
76
|
+
if (cached) {
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const client = await createPostHogServerClient(apiKey, {
|
|
82
|
+
host: options?.host,
|
|
83
|
+
// Add timeout for server-side requests
|
|
84
|
+
timeout: options?.timeout ?? 5000,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Cleanup
|
|
88
|
+
await client.shutdown();
|
|
89
|
+
|
|
90
|
+
const bootstrapData = createBootstrapData(distinctId);
|
|
91
|
+
|
|
92
|
+
// Cache the result
|
|
93
|
+
setCachedBootstrapData(distinctId, bootstrapData);
|
|
94
|
+
|
|
95
|
+
return bootstrapData;
|
|
96
|
+
} catch {
|
|
97
|
+
// Return minimal data on error
|
|
98
|
+
const minimalData = createMinimalBootstrapData(distinctId);
|
|
99
|
+
setCachedBootstrapData(distinctId, minimalData);
|
|
100
|
+
return minimalData;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Complete bootstrap data fetcher for Next.js apps
|
|
107
|
+
* Handles cookies, distinct ID generation, and flag fetching
|
|
108
|
+
*/
|
|
109
|
+
const getCompleteBootstrapData = cache(
|
|
110
|
+
async (
|
|
111
|
+
cookies: any,
|
|
112
|
+
apiKey: string,
|
|
113
|
+
options?: {
|
|
114
|
+
fallbackToGenerated?: boolean;
|
|
115
|
+
host?: string;
|
|
116
|
+
timeout?: number;
|
|
117
|
+
},
|
|
118
|
+
): Promise<BootstrapData> => {
|
|
119
|
+
try {
|
|
120
|
+
// Get or generate distinct ID
|
|
121
|
+
const distinctId = await getOrGenerateDistinctId(cookies, apiKey);
|
|
122
|
+
|
|
123
|
+
// Fetch bootstrap data
|
|
124
|
+
return await getPostHogBootstrapData(apiKey, distinctId, options);
|
|
125
|
+
} catch (error: any) {
|
|
126
|
+
if (options?.fallbackToGenerated !== false) {
|
|
127
|
+
// Fallback to generated ID with empty flags
|
|
128
|
+
const fallbackId = generateDistinctId();
|
|
129
|
+
return createMinimalBootstrapData(fallbackId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Commented out as currently unused but kept for potential future use
|
|
138
|
+
// /**
|
|
139
|
+
// * Middleware helper for PostHog tracking
|
|
140
|
+
// */
|
|
141
|
+
// function _createPostHogMiddleware(apiKey: string) {
|
|
142
|
+
// return async (request: any, _response?: any) => {
|
|
143
|
+
// try {
|
|
144
|
+
// // Extract user info from request
|
|
145
|
+
// const userAgent = request.headers?.get?.('user-agent') ?? request.headers?.['user-agent'];
|
|
146
|
+
// const referer = request.headers?.get?.('referer') ?? request.headers?.['referer'];
|
|
147
|
+
// const ip = request.ip ?? request.headers?.get?.('x-forwarded-for') ?? 'unknown';
|
|
148
|
+
|
|
149
|
+
// // Get distinct ID from cookies
|
|
150
|
+
// const { cookies } = request;
|
|
151
|
+
// const distinctId = getDistinctIdFromCookies(cookies, apiKey) ?? generateDistinctId();
|
|
152
|
+
|
|
153
|
+
// // Create tracking context
|
|
154
|
+
// return {
|
|
155
|
+
// distinctId,
|
|
156
|
+
// ip,
|
|
157
|
+
// referer,
|
|
158
|
+
// timestamp: new Date().toISOString(),
|
|
159
|
+
// userAgent,
|
|
160
|
+
// };
|
|
161
|
+
// } catch {
|
|
162
|
+
// return null;
|
|
163
|
+
// }
|
|
164
|
+
// };
|
|
165
|
+
// }
|
|
166
|
+
|
|
167
|
+
// /**
|
|
168
|
+
// * React Suspense-compatible PostHog bootstrap fetcher
|
|
169
|
+
// */
|
|
170
|
+
// function _createPostHogSuspenseData(
|
|
171
|
+
// cookies: any,
|
|
172
|
+
// apiKey: string,
|
|
173
|
+
// options?: {
|
|
174
|
+
// host?: string;
|
|
175
|
+
// timeout?: number;
|
|
176
|
+
// },
|
|
177
|
+
// ) {
|
|
178
|
+
// let status = 'pending';
|
|
179
|
+
// let result: BootstrapData;
|
|
180
|
+
// let error: any;
|
|
181
|
+
|
|
182
|
+
// // eslint-disable-next-line promise/prefer-await-to-then
|
|
183
|
+
// const suspender = getCompleteBootstrapData(cookies, apiKey, options).then(
|
|
184
|
+
// // eslint-disable-next-line promise/always-return
|
|
185
|
+
// (data: any) => {
|
|
186
|
+
// status = 'fulfilled';
|
|
187
|
+
// result = data;
|
|
188
|
+
// },
|
|
189
|
+
// (err: any) => {
|
|
190
|
+
// status = 'rejected';
|
|
191
|
+
// error = err;
|
|
192
|
+
// },
|
|
193
|
+
// );
|
|
194
|
+
|
|
195
|
+
// return {
|
|
196
|
+
// read() {
|
|
197
|
+
// if (status === 'pending') {
|
|
198
|
+
// throw suspender;
|
|
199
|
+
// } else if (status === 'rejected') {
|
|
200
|
+
// throw error;
|
|
201
|
+
// }
|
|
202
|
+
// return result;
|
|
203
|
+
// },
|
|
204
|
+
// };
|
|
205
|
+
// }
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Export commonly used utilities (server-safe only)
|
|
209
|
+
*/
|
|
210
|
+
export { getCompleteBootstrapData };
|