From a6e038d55a2a0147e5eb1aaa47f9f83d5300a799 Mon Sep 17 00:00:00 2001 From: unknownproperty Date: Thu, 19 Feb 2026 22:27:21 +0300 Subject: [PATCH] feat(umami): add session track for boards and vks --- apps/xi.web/src/pages/(app)/_layout.tsx | 3 ++ packages/modules.calls/index.ts | 1 + packages/modules.calls/src/hooks/index.ts | 1 + .../src/hooks/useUmamiActivityHeartbeat.ts | 54 +++++++++++++++++++ packages/modules.calls/src/index.ts | 1 + 5 files changed, 60 insertions(+) create mode 100644 packages/modules.calls/src/hooks/useUmamiActivityHeartbeat.ts diff --git a/apps/xi.web/src/pages/(app)/_layout.tsx b/apps/xi.web/src/pages/(app)/_layout.tsx index f6f70e8a..3e87c147 100644 --- a/apps/xi.web/src/pages/(app)/_layout.tsx +++ b/apps/xi.web/src/pages/(app)/_layout.tsx @@ -10,6 +10,7 @@ import { RoomProvider, useCallStore, ModeSyncProvider, + useUmamiActivityHeartbeat, } from 'modules.calls'; import { useCurrentUser, useUpdateProfile, useMarkNotificationAsRead } from 'common.services'; import { OnboardingStageT } from 'common.api'; @@ -25,6 +26,8 @@ function LayoutComponent() { const router = useRouter(); const updateStore = useCallStore((state) => state.updateStore); + useUmamiActivityHeartbeat(); + useEffect(() => { const pathname = router.state.location.pathname; const search = router.state.location.search; diff --git a/packages/modules.calls/index.ts b/packages/modules.calls/index.ts index c34492d9..93ea958a 100644 --- a/packages/modules.calls/index.ts +++ b/packages/modules.calls/index.ts @@ -3,3 +3,4 @@ export { LiveKitProvider } from './src/providers/LiveKitProvider'; export { RoomProvider } from './src/providers/RoomProvider'; export { useCallStore } from './src/store/callStore'; export { useStartCall } from './src/hooks'; +export { useUmamiActivityHeartbeat } from './src'; diff --git a/packages/modules.calls/src/hooks/index.ts b/packages/modules.calls/src/hooks/index.ts index eed14e94..acc0ce1a 100644 --- a/packages/modules.calls/src/hooks/index.ts +++ b/packages/modules.calls/src/hooks/index.ts @@ -16,3 +16,4 @@ export { useVideoSecurity } from './useVideoSecurity'; export { useScreenShareCleanup } from './useScreenShareCleanup'; export { useVideoBlur } from './useVideoBlur'; export { useParticipantJoinSync } from './useParticipantJoinSync'; +export { useUmamiActivityHeartbeat } from './useUmamiActivityHeartbeat'; diff --git a/packages/modules.calls/src/hooks/useUmamiActivityHeartbeat.ts b/packages/modules.calls/src/hooks/useUmamiActivityHeartbeat.ts new file mode 100644 index 00000000..27ad8f0c --- /dev/null +++ b/packages/modules.calls/src/hooks/useUmamiActivityHeartbeat.ts @@ -0,0 +1,54 @@ +import { useLocation } from '@tanstack/react-router'; +import { useEffect, useRef } from 'react'; +import { useCallStore } from '../store/callStore'; + +const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000; // 5 минут + +/** Проверяет, находится ли пользователь на странице доски по pathname */ +function isBoardPath(pathname: string): boolean { + return ( + /^\/board\/[^/]+$/.test(pathname) || + /\/classrooms\/[^/]+\/boards\/[^/]+/.test(pathname) || + /\/materials\/[^/]+\/board\/?$/.test(pathname) + ); +} + +/** + * Раз в 5 минут вызывает umami.track(), если пользователь + * в активной ВКС или на странице доски. + */ +export function useUmamiActivityHeartbeat() { + const pathname = useLocation().pathname; + const isStarted = useCallStore((s) => s.isStarted); + const intervalRef = useRef | null>(null); + + const isOnBoard = isBoardPath(pathname); + const shouldTrack = Boolean(isStarted || isOnBoard); + + useEffect(() => { + if (!shouldTrack) { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + return; + } + + const track = () => { + const win = window as Window & { umami?: { track: (name?: string) => void } }; + if (typeof window !== 'undefined' && win.umami) { + win.umami.track(); + } + }; + + intervalRef.current = setInterval(track, HEARTBEAT_INTERVAL_MS); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + }, [shouldTrack]); + + return null; +} diff --git a/packages/modules.calls/src/index.ts b/packages/modules.calls/src/index.ts index d8149be2..23e6915d 100644 --- a/packages/modules.calls/src/index.ts +++ b/packages/modules.calls/src/index.ts @@ -10,5 +10,6 @@ export { useRaisedHands, useHandFocus, useSpeakingParticipant, + useUmamiActivityHeartbeat, } from './hooks'; export { useCallStore } from './store';