From 5b89cfd57316f5c825b5635bf9b5e206a3511126 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Wed, 24 Dec 2025 02:06:31 +0300 Subject: [PATCH] push --- .npmrc | 5 +- package-lock.json | 54 ++++++++++ package.json | 5 +- src/api/resources/Session.ts | 13 ++- src/hooks/session/useSession.ts | 181 ++++++++++++++++++++++++++++++-- src/init.tsx | 7 ++ 6 files changed, 251 insertions(+), 14 deletions(-) diff --git a/.npmrc b/.npmrc index ca9f540..67a64db 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,4 @@ -node-options=--experimental-vm-modules --no-warnings \ No newline at end of file +node-options=--experimental-vm-modules --no-warnings + +@wit-lab-llc:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3518329..5b6d3cd 100755 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@reduxjs/toolkit": "^1.9.5", "@smakss/react-scroll-direction": "^4.0.4", "@unleash/proxy-client-react": "^4.5.2", + "@wit-lab-llc/frontend-shared": "^1.0.4", "apng-js": "^1.1.1", "core-js": "^3.37.1", "framer-motion": "^11.0.8", @@ -66,6 +67,28 @@ "vite-plugin-svgr": "^4.2.0" } }, + "../wit-frontend-shared": { + "name": "@wit-lab-llc/frontend-shared", + "version": "1.0.0", + "extraneous": true, + "license": "UNLICENSED", + "dependencies": { + "@fingerprintjs/fingerprintjs": "^5.0.1" + }, + "devDependencies": { + "@types/node": "^25.0.2", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "react": ">=18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -1028,6 +1051,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fingerprintjs/fingerprintjs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-5.0.1.tgz", + "integrity": "sha512-KbaeE/rk2WL8MfpRP6jTI4lSr42SJPjvkyrjP3QU6uUDkOMWWYC2Ts1sNSYcegHC8avzOoYTHBj+2fTqvZWQBA==", + "license": "MIT" + }, "node_modules/@floating-ui/core": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", @@ -2127,6 +2156,18 @@ "vite": "^4.2.0" } }, + "node_modules/@wit-lab-llc/frontend-shared": { + "version": "1.0.4", + "resolved": "https://npm.pkg.github.com/download/@wit-lab-llc/frontend-shared/1.0.4/c342b071bc0716511d84bc3f3c9685aa7649ea5e", + "integrity": "sha512-9EZEpjWCdz+IP4RsgkVTPiUEKhm5LqnbgD/S/GJ07eG0Y10ulj3qXjDVXc7N9gURyZULshriDt350w6nhN7Z3w==", + "license": "UNLICENSED", + "dependencies": { + "@fingerprintjs/fingerprintjs": "^5.0.1" + }, + "peerDependencies": { + "react": ">=18.0.0" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -6238,6 +6279,11 @@ "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", "dev": true }, + "@fingerprintjs/fingerprintjs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-5.0.1.tgz", + "integrity": "sha512-KbaeE/rk2WL8MfpRP6jTI4lSr42SJPjvkyrjP3QU6uUDkOMWWYC2Ts1sNSYcegHC8avzOoYTHBj+2fTqvZWQBA==" + }, "@floating-ui/core": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", @@ -6903,6 +6949,14 @@ "react-refresh": "^0.14.0" } }, + "@wit-lab-llc/frontend-shared": { + "version": "1.0.4", + "resolved": "https://npm.pkg.github.com/download/@wit-lab-llc/frontend-shared/1.0.4/c342b071bc0716511d84bc3f3c9685aa7649ea5e", + "integrity": "sha512-9EZEpjWCdz+IP4RsgkVTPiUEKhm5LqnbgD/S/GJ07eG0Y10ulj3qXjDVXc7N9gURyZULshriDt350w6nhN7Z3w==", + "requires": { + "@fingerprintjs/fingerprintjs": "^5.0.1" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", diff --git a/package.json b/package.json index c0d444e..a5ef465 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "start:local": "vite --host --mode localhost", "start:prod": "vite --host --mode production", "build:dev": "tsc && vite build --mode develop", - "build:prod": "tsc && vite build --mode production" + "build:prod": "tsc && vite build --mode production", + "link:shared": "npm link @wit-lab-llc/frontend-shared", + "unlink:shared": "npm unlink @wit-lab-llc/frontend-shared && npm install" }, "dependencies": { "@emotion/react": "^11.11.4", @@ -24,6 +26,7 @@ "@reduxjs/toolkit": "^1.9.5", "@smakss/react-scroll-direction": "^4.0.4", "@unleash/proxy-client-react": "^4.5.2", + "@wit-lab-llc/frontend-shared": "^1.0.4", "apng-js": "^1.1.1", "core-js": "^3.37.1", "framer-motion": "^11.0.8", diff --git a/src/api/resources/Session.ts b/src/api/resources/Session.ts index fa34043..f38c309 100644 --- a/src/api/resources/Session.ts +++ b/src/api/resources/Session.ts @@ -5,6 +5,7 @@ import { ICreateAuthorizeUser } from "./User"; import { ELocalesPlacement } from "@/locales"; import { Currency } from "@/components/PaymentTable/Price"; import { PeriodType } from "@/hooks/translations"; +import type { SessionFingerprintData, SessionFacebookData } from "@wit-lab-llc/frontend-shared"; export interface PayloadCreate { feature: string, // Type: string @@ -14,7 +15,13 @@ export interface PayloadCreate { sign: boolean, // Type: boolean signDate: string | undefined, // Type: string, ISO Date utm: IUTM, // Type: { [key: string]: string } - Optional - domain: string // Type: string + anonymousId?: string, + query?: Record, + landingQuery?: Record, + lastActivityAt?: string, + domain: string, // Type: string + fingerprint?: SessionFingerprintData, // Fingerprint data from library + facebookData?: SessionFacebookData // Facebook data for Conversions API } export interface PayloadUpdate { @@ -22,6 +29,10 @@ export interface PayloadUpdate { data: { feature: string, // Type: string + anonymousId?: string, + query?: Record, + lastActivityAt?: string, + profile?: Partial; partner?: Partial>; answers?: Partial; diff --git a/src/hooks/session/useSession.ts b/src/hooks/session/useSession.ts index ee2fb33..8ecb65b 100644 --- a/src/hooks/session/useSession.ts +++ b/src/hooks/session/useSession.ts @@ -3,9 +3,33 @@ 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, useMemo, useState } from "react" +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(); @@ -19,16 +43,119 @@ export const useSession = () => { 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 => { - if (session[source]?.length) { - localStorage.setItem(`${source}_sessionId`, session[source]); - return { - sessionId: session[source], - status: "old" - } + 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, @@ -37,7 +164,13 @@ export const useSession = () => { sign: checked, signDate: dateOfCheck.length ? dateOfCheck : undefined, utm, - domain: window.location.hostname + 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); @@ -48,6 +181,15 @@ export const useSession = () => { 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); @@ -64,7 +206,7 @@ export const useSession = () => { sessionId: "" } } - }, [api, checked, dateOfCheck, dispatch, feature, session, timezone, utm]) + }, [api, checked, dateOfCheck, dispatch, feature, session, timezone, utm, fingerprintCollector, facebookCollector, readVisit, visitTtlMs, writeVisit]) const updateSession = useCallback(async (data: Omit, source: ESourceAuthorization, sessionId?: string) => { try { @@ -73,22 +215,39 @@ export const useSession = () => { 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]) + }, [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]) + }, [dispatch, getVisitStorageKey]) return useMemo(() => ({ session, diff --git a/src/init.tsx b/src/init.tsx index de81375..4b0a4b4 100755 --- a/src/init.tsx +++ b/src/init.tsx @@ -26,12 +26,19 @@ import { InitializationProvider } from "./initialization"; import { getSourceByPathname } from "./utils/source.utils"; import { parseQueryParams } from "./services/url"; import { actions } from "./store"; +import { initWitLib } from "@wit-lab-llc/frontend-shared"; pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.js`; const environments = import.meta.env; const init = async () => { + // Initialize WIT shared library for fingerprint and facebook data collection + initWitLib({ + baseUrl: environments.AURA_API_URL || '', + debug: environments.MODE !== 'production', + }); + // Parse UTM parameters from URL at initialization const utm = parseQueryParams(); console.log('UTM parameters parsed at init:', utm);