import { IUpdateSessionRequest } from "@/entities/session/types"; import { useCallback, useMemo, useState, useEffect } from "react"; import { setSessionIdToCookie } from "@/entities/session/serverActions"; import { metricService } from "@/services/analytics/metricService"; import { SessionCollector, FingerprintCollector, FacebookCollector, parseUtmParams, getOrCreateAnonymousId, type CreateSessionResponse, type UpdateSessionData, type SessionFingerprintData, type SessionFacebookData, } from "@wit-lab-llc/frontend-shared"; import { parseQueryParams } from "@/shared/utils/url"; // TODO: Get locale from context or i18n const locale = "en"; // Debug logging helper - only logs in development const isDev = process.env.NODE_ENV === "development"; const debugLog = (message: string, ...args: unknown[]) => { if (isDev) { console.log(`🔍 [useSession] ${message}`, ...args); } }; // Singleton instances let sessionCollectorInstance: SessionCollector | null = null; let fingerprintCollectorInstance: FingerprintCollector | null = null; let facebookCollectorInstance: FacebookCollector | null = null; // Module-level flag to track if data collection has been done (persists across remounts) let dataCollectionDone = false; function getSessionCollector(): SessionCollector { if (!sessionCollectorInstance) { sessionCollectorInstance = new SessionCollector(); } return sessionCollectorInstance; } function getFingerprintCollector(): FingerprintCollector { if (!fingerprintCollectorInstance) { fingerprintCollectorInstance = new FingerprintCollector(); } return fingerprintCollectorInstance; } function getFacebookCollector(): FacebookCollector { if (!facebookCollectorInstance) { facebookCollectorInstance = new FacebookCollector(); } return facebookCollectorInstance; } interface IUseSessionProps { funnelId: string; googleAnalyticsId?: string; } export const useSession = ({ funnelId, googleAnalyticsId }: IUseSessionProps) => { const [collector] = useState(() => getSessionCollector()); const [fingerprintCollector] = useState(() => getFingerprintCollector()); const [facebookCollector] = useState(() => getFacebookCollector()); const [isError, setIsError] = useState(false); // Get current session ID from collector const sessionId = collector.getSessionId(); // Collect fingerprint and facebook data on mount (only once per app lifecycle) useEffect(() => { if (typeof window === "undefined" || dataCollectionDone) return; dataCollectionDone = true; // Collect data in background debugLog("Starting fingerprint and Facebook data collection..."); Promise.all([ fingerprintCollector.collect().catch((e) => { debugLog("Fingerprint collection failed:", e); return null; }), Promise.resolve(facebookCollector.collect()), ]).then(([fpResult, fbResult]) => { debugLog("✅ Data collection completed", { fingerprint: fpResult ? { visitorId: fpResult.visitorId, confidence: fpResult.confidence } : null, facebook: fbResult ? { fbp: fbResult.fbp, fbc: fbResult.fbc, fbclid: fbResult.fbclid } : null, }); }); }, [fingerprintCollector, facebookCollector]); const createSession = useCallback(async (): Promise => { if (typeof window === "undefined") { return { sessionId: "", status: "error" }; } try { const query = parseQueryParams(); const utm = parseUtmParams(); const anonymousId = getOrCreateAnonymousId(); const lastActivityAt = new Date().toISOString(); // Get fingerprint data for session let fingerprint: SessionFingerprintData | undefined; try { debugLog("Getting fingerprint data for session..."); const fpData = await fingerprintCollector.getOrCollect(); if (fpData) { const payload = fingerprintCollector.toServerPayload(); fingerprint = { visitorId: fpData.visitorId, confidence: fpData.confidence, collectedAt: fpData.collectedAt, ...payload, } as SessionFingerprintData; debugLog("✅ Fingerprint data ready", { visitorId: fingerprint.visitorId, confidence: fingerprint.confidence, }); } } catch (e) { console.warn("[useSession] Failed to collect fingerprint:", e); } // Get facebook data for session let facebookData: SessionFacebookData | undefined; try { debugLog("Getting Facebook data for session..."); const fbData = facebookCollector.getData() || facebookCollector.collect(); if (fbData) { facebookData = { fbp: fbData.fbp ?? undefined, fbc: fbData.fbc ?? undefined, fbclid: fbData.fbclid ?? undefined, externalId: fingerprint?.visitorId, // Use visitorId as external_id landingPage: fbData.landingPage ?? undefined, referrer: fbData.referrer, clientUserAgent: fbData.userAgent, eventSourceUrl: fbData.currentUrl, browserLanguage: fbData.browserLanguage, screenResolution: fbData.screenResolution, viewportSize: fbData.viewportSize, colorDepth: fbData.colorDepth, devicePixelRatio: fbData.devicePixelRatio, touchSupport: fbData.touchSupport, cookiesEnabled: fbData.cookiesEnabled, doNotTrack: fbData.doNotTrack, collectedAt: fbData.collectedAt, }; debugLog("✅ Facebook data ready", { fbp: facebookData.fbp, fbc: facebookData.fbc, fbclid: facebookData.fbclid, externalId: facebookData.externalId, landingPage: facebookData.landingPage, }); } } catch (e) { console.warn("[useSession] Failed to collect Facebook data:", e); } debugLog("Creating session with data:", { source: funnelId, hasFingerprint: !!fingerprint, hasFacebookData: !!facebookData, utm, }); const response = await collector.create({ source: funnelId, feature: "stripe", locale, sign: false, utm, anonymousId, query, landingQuery: query, lastActivityAt, fingerprint, facebookData, }); debugLog("Session creation response:", response); if (response.sessionId && response.status !== "error") { // Set cookie for server-side access await setSessionIdToCookie("activeSessionId", response.sessionId); // For old sessions, update with current feature and data if (response.status === "old") { debugLog("Updating old session with current feature and data..."); await collector.update({ feature: "stripe", anonymousId, query, lastActivityAt: new Date().toISOString(), }); } // Send analytics only for new sessions if (response.status === "success") { metricService.userParams({ sessionId: response.sessionId, }); metricService.sendVisitContext({ sessionId: response.sessionId, funnelId, gaId: googleAnalyticsId, }); } return response; } setIsError(true); return { status: "error", sessionId: "" }; } catch (error) { debugLog("❌ Session creation failed:", error); console.error("Session creation failed:", error); setIsError(true); return { status: "error", sessionId: "" }; } }, [collector, funnelId, googleAnalyticsId, fingerprintCollector, facebookCollector]); const updateSession = useCallback( async (data: IUpdateSessionRequest["data"]) => { try { const query = parseQueryParams(); const anonymousId = getOrCreateAnonymousId(); // Convert to library's UpdateSessionData format const updateData: UpdateSessionData = { feature: "stripe", anonymousId, query, lastActivityAt: new Date().toISOString(), ...data, }; const result = await collector.update(updateData, { source: funnelId, feature: "stripe", locale, sign: false, utm: parseUtmParams(), anonymousId, query, landingQuery: query, lastActivityAt: new Date().toISOString(), }); return result; } catch (error) { debugLog("❌ Session update failed:", error); console.error("Session update failed:", error); return null; } }, [collector, funnelId] ); const deleteSession = useCallback(() => { collector.delete(funnelId); }, [collector, funnelId]); return useMemo( () => ({ session: sessionId, isError, createSession, updateSession, deleteSession, }), [sessionId, isError, createSession, deleteSession, updateSession] ); };