diff --git a/packages/mobile/src/hooks/useActivityPing.ts b/packages/mobile/src/hooks/useActivityPing.ts index 00d472f20cd..374c4890276 100644 --- a/packages/mobile/src/hooks/useActivityPing.ts +++ b/packages/mobile/src/hooks/useActivityPing.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import { useCurrentUserId, useQueryContext } from '@audius/common/api' @@ -9,18 +9,24 @@ export const useActivityPing = () => { const { data: currentUserId } = useCurrentUserId() const { audiusSdk } = useQueryContext() - useEnterForeground( - useCallback(async () => { - if (!currentUserId) return - try { - const sdk = await audiusSdk() - await audiusBackendInstance.pingActivity({ - sdk, - userId: currentUserId - }) - } catch { - // Fire-and-forget - } - }, [currentUserId, audiusSdk]) - ) + const pingActivity = useCallback(async () => { + if (!currentUserId) return + try { + const sdk = await audiusSdk() + await audiusBackendInstance.pingActivity({ + sdk, + userId: currentUserId + }) + } catch { + // Fire-and-forget + } + }, [currentUserId, audiusSdk]) + + // Ping on initial app open (AppState starts as 'active' with no transition) + useEffect(() => { + pingActivity() + }, [pingActivity]) + + // Ping on subsequent foreground transitions + useEnterForeground(pingActivity) } diff --git a/packages/web/src/app/web-player/WebPlayer.tsx b/packages/web/src/app/web-player/WebPlayer.tsx index 911d37b0825..9d4413dd5ec 100644 --- a/packages/web/src/app/web-player/WebPlayer.tsx +++ b/packages/web/src/app/web-player/WebPlayer.tsx @@ -67,6 +67,7 @@ import PlayBarProvider from 'components/play-bar/PlayBarProvider' import { RewardClaimedToast } from 'components/reward-claimed-toast/RewardClaimedToast' import { USDCBalanceFetcher } from 'components/usdc-balance-fetcher/USDCBalanceFetcher' import { useEnvironment } from 'hooks/useEnvironment' +import { useActivityPing } from 'hooks/useActivityPing' import { usePlaybackPositionPersistence } from 'hooks/usePlaybackPositionPersistence' import { usePlaybackPositionPolling } from 'hooks/usePlaybackPositionPolling' import { usePlaybackRatePersistence } from 'hooks/usePlaybackRatePersistence' @@ -498,6 +499,7 @@ const WebPlayer = (props: WebPlayerProps) => { const frostedSurfaceIntensity = useSelector(getFrostedSurfaceIntensity) ?? FrostedSurfaceIntensity.DEFAULT + useActivityPing() usePlaybackRatePersistence() usePlaybackPositionPersistence() usePlaybackPositionPolling() diff --git a/packages/web/src/hooks/useActivityPing.ts b/packages/web/src/hooks/useActivityPing.ts new file mode 100644 index 00000000000..41f54174909 --- /dev/null +++ b/packages/web/src/hooks/useActivityPing.ts @@ -0,0 +1,47 @@ +import { useCallback, useEffect, useRef } from 'react' + +import { useCurrentUserId, useQueryContext } from '@audius/common/api' + +import { audiusBackendInstance } from 'services/audius-backend/audius-backend-instance' + +const PING_DEBOUNCE_MS = 5 * 60 * 1000 + +export const useActivityPing = () => { + const { data: currentUserId } = useCurrentUserId() + const { audiusSdk } = useQueryContext() + const lastPingRef = useRef(0) + + const pingActivity = useCallback(async () => { + if (!currentUserId) return + const now = Date.now() + if (now - lastPingRef.current < PING_DEBOUNCE_MS) return + lastPingRef.current = now + try { + const sdk = await audiusSdk() + await audiusBackendInstance.pingActivity({ + sdk, + userId: currentUserId + }) + } catch { + // Fire-and-forget + } + }, [currentUserId, audiusSdk]) + + // Ping on initial page load + useEffect(() => { + pingActivity() + }, [pingActivity]) + + // Ping when tab becomes visible again + useEffect(() => { + const handleVisibilityChange = () => { + if (document.visibilityState === 'visible') { + pingActivity() + } + } + document.addEventListener('visibilitychange', handleVisibilityChange) + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange) + } + }, [pingActivity]) +}