import { useApi } from "@/api"; import { PayloadUpdate, ResponseCreate } from "@/api/resources/Session"; import { ESourceAuthorization } from "@/api/resources/User"; import { getClientTimezone, language } from "@/locales"; import { actions, selectors } from "@/store"; import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useDispatch, useSelector } from "react-redux"; import { FingerprintCollector, FacebookCollector, type SessionFingerprintData, type SessionFacebookData, getOrCreateAnonymousId, } from "@wit-lab-llc/frontend-shared"; import { parseQueryParams } from "@/services/url"; let fingerprintCollectorInstance: FingerprintCollector | null = null; let facebookCollectorInstance: FacebookCollector | null = null; function getFingerprintCollector(): FingerprintCollector { if (!fingerprintCollectorInstance) { fingerprintCollectorInstance = new FingerprintCollector(); } return fingerprintCollectorInstance; } function getFacebookCollector(): FacebookCollector { if (!facebookCollectorInstance) { facebookCollectorInstance = new FacebookCollector(); } return facebookCollectorInstance; } export const useSession = () => { const dispatch = useDispatch(); const api = useApi(); const session = useSelector(selectors.selectSession); const feature = useSelector(selectors.selectFeature) const { checked, dateOfCheck } = useSelector(selectors.selectPrivacyPolicy); const utm = useSelector(selectors.selectUTM); const timezone = getClientTimezone(); const [isError, setIsError] = useState(false); const [fingerprintCollector] = useState(() => getFingerprintCollector()); const [facebookCollector] = useState(() => getFacebookCollector()); const dataCollectedRef = useRef(false); const visitTtlMs = 30 * 60 * 1000; const getVisitStorageKey = useCallback((source: ESourceAuthorization) => { return `${source}_visit`; }, []); const readVisit = useCallback((source: ESourceAuthorization) => { try { const raw = localStorage.getItem(getVisitStorageKey(source)); if (!raw) return null; return JSON.parse(raw) as { sessionId: string; startedAt: number; lastActivityAt: number; anonymousId?: string; landingQuery?: Record; lastQuery?: Record; }; } catch { return null; } }, [getVisitStorageKey]); const writeVisit = useCallback((source: ESourceAuthorization, visit: { sessionId: string; startedAt: number; lastActivityAt: number; anonymousId?: string; landingQuery?: Record; lastQuery?: Record; }) => { localStorage.setItem(getVisitStorageKey(source), JSON.stringify(visit)); localStorage.setItem(`${source}_sessionId`, visit.sessionId); }, [getVisitStorageKey]); useEffect(() => { if (typeof window === "undefined" || dataCollectedRef.current) return; dataCollectedRef.current = true; Promise.all([ fingerprintCollector.collect().catch(() => null), Promise.resolve(facebookCollector.collect()), ]).then(() => { console.log("[useSession] Fingerprint and Facebook data collected"); }); }, [fingerprintCollector, facebookCollector]); const createSession = useCallback(async (source: ESourceAuthorization): Promise => { const nowMs = Date.now(); const existingVisit = readVisit(source); if (existingVisit && nowMs - existingVisit.lastActivityAt <= visitTtlMs) { const nextQuery = parseQueryParams(); const nextAnonymousId = existingVisit.anonymousId ?? getOrCreateAnonymousId(); writeVisit(source, { ...existingVisit, lastActivityAt: nowMs, lastQuery: nextQuery, anonymousId: nextAnonymousId, }); dispatch(actions.session.update({ session: existingVisit.sessionId, source })); localStorage.setItem(`${source}_sessionId`, existingVisit.sessionId); return { sessionId: existingVisit.sessionId, status: "old" }; } try { let fingerprint: SessionFingerprintData | undefined; try { const fpData = await fingerprintCollector.getOrCollect(); if (fpData) { const payload = fingerprintCollector.toServerPayload(); fingerprint = { visitorId: fpData.visitorId, confidence: fpData.confidence, collectedAt: fpData.collectedAt, ...payload, } as SessionFingerprintData; } } catch (e) { console.warn("[useSession] Failed to collect fingerprint:", e); } let facebookData: SessionFacebookData | undefined; try { const fbData = facebookCollector.getData() || facebookCollector.collect(); if (fbData) { facebookData = { fbp: fbData.fbp ?? undefined, fbc: fbData.fbc ?? undefined, fbclid: fbData.fbclid ?? undefined, externalId: fingerprint?.visitorId, 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, }; } } catch (e) { console.warn("[useSession] Failed to collect Facebook data:", e); } const sessionParams = { feature, locale: language, timezone, source, sign: checked, signDate: dateOfCheck.length ? dateOfCheck : undefined, utm, anonymousId: getOrCreateAnonymousId(), query: parseQueryParams(), landingQuery: parseQueryParams(), lastActivityAt: new Date().toISOString(), domain: window.location.hostname, fingerprint, facebookData, }; console.log('Creating session with parameters:', sessionParams); const sessionFromServer = await api.createSession(sessionParams); console.log('Session creation response:', sessionFromServer); if (sessionFromServer?.sessionId?.length && sessionFromServer?.status === "success") { dispatch(actions.session.update({ session: sessionFromServer.sessionId, source })); localStorage.setItem(`${source}_sessionId`, sessionFromServer.sessionId); const q = parseQueryParams(); writeVisit(source, { sessionId: sessionFromServer.sessionId, startedAt: Date.now(), lastActivityAt: Date.now(), anonymousId: sessionParams.anonymousId, landingQuery: q, lastQuery: q, }); return sessionFromServer } console.error('Session creation failed - invalid response:', sessionFromServer); setIsError(true); return { status: "error", sessionId: "" } } catch (error) { console.error('Session creation failed with error:', error); setIsError(true); return { status: "error", sessionId: "" } } }, [api, checked, dateOfCheck, dispatch, feature, session, timezone, utm, fingerprintCollector, facebookCollector, readVisit, visitTtlMs, writeVisit]) const updateSession = useCallback(async (data: Omit, source: ESourceAuthorization, sessionId?: string) => { try { const _sessionId = sessionId || session[source]; const result = await api.updateSession({ sessionId: _sessionId, data: { feature, anonymousId: getOrCreateAnonymousId(), query: parseQueryParams(), lastActivityAt: new Date().toISOString(), ...data } }); if (_sessionId) { const nowMs = Date.now(); const existingVisit = readVisit(source); if (existingVisit && existingVisit.sessionId === _sessionId) { writeVisit(source, { ...existingVisit, lastActivityAt: nowMs, lastQuery: parseQueryParams(), anonymousId: getOrCreateAnonymousId(), }); } } return result; } catch (error) { console.log(error) } }, [api, feature, session, readVisit, writeVisit]) const deleteSession = useCallback(async (source: ESourceAuthorization) => { localStorage.removeItem(`${source}_sessionId`); localStorage.removeItem(getVisitStorageKey(source)); dispatch(actions.session.update({ session: "", source })) }, [dispatch, getVisitStorageKey]) return useMemo(() => ({ session, isError, createSession, updateSession, deleteSession }), [ session, isError, createSession, deleteSession, updateSession ]) }