Skip to content

Commit

Permalink
fix: analytics at boot time (#3759)
Browse files Browse the repository at this point in the history
* refactor: BootProvider and log events

* refactor: revert getCachedBootOrNull

* fix: ssr shift due to firstLoad

* fix: test with wrong context set
  • Loading branch information
ilasw authored Dec 23, 2024
1 parent f06c56c commit 5b55e3b
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 161 deletions.
10 changes: 4 additions & 6 deletions packages/extension/src/companion/Companion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,11 @@ export default function Companion({
}),
});
const [assetsLoadedDebounce] = useDebounceFn(() => setAssetsLoaded(true), 10);
const routeChangedCallbackRef = useLogPageView();
const routeChangedCallback = useLogPageView();

useEffect(() => {
if (routeChangedCallbackRef.current) {
routeChangedCallbackRef.current();
}
}, [routeChangedCallbackRef]);
routeChangedCallback?.();
}, [routeChangedCallback]);

const [checkAssets, clearCheckAssets] = useDebounceFn(() => {
if (containerRef?.current?.offsetLeft === 0) {
Expand All @@ -133,7 +131,7 @@ export default function Companion({
}

checkAssets();
routeChangedCallbackRef.current();
routeChangedCallback?.();
// @NOTE see https://dailydotdev.atlassian.net/l/cp/dK9h1zoM
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [containerRef]);
Expand Down
8 changes: 4 additions & 4 deletions packages/extension/src/newtab/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function InternalApp(): ReactElement {
const { contentScriptGranted } = useContentScriptStatus();
const { hostGranted, isFetching: isCheckingHostPermissions } =
useHostStatus();
const routeChangedCallbackRef = useLogPageView();
const routeChangedCallback = useLogPageView();
useConsoleLogo();
const { user, isAuthReady } = useAuthContext();
const { growthbook } = useGrowthBookContext();
Expand All @@ -92,10 +92,10 @@ function InternalApp(): ReactElement {
const isFirefoxExtension = process.env.TARGET_BROWSER === 'firefox';

useEffect(() => {
if (routeChangedCallbackRef.current && isPageReady) {
routeChangedCallbackRef.current();
if (isPageReady && currentPage) {
routeChangedCallback?.();
}
}, [isPageReady, routeChangedCallbackRef, currentPage]);
}, [isPageReady, routeChangedCallback, currentPage]);

const { dismissToast } = useToastNotification();

Expand Down
11 changes: 6 additions & 5 deletions packages/shared/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { AccessToken, Boot, Visit } from '../lib/boot';
import { isCompanionActivated } from '../lib/element';
import { AuthTriggers, AuthTriggersType } from '../lib/auth';
import { Squad } from '../graphql/sources';
import { checkIsExtension, isNullOrUndefined } from '../lib/func';
import { checkIsExtension } from '../lib/func';

export interface LoginState {
trigger: AuthTriggersType;
Expand Down Expand Up @@ -65,6 +65,7 @@ export interface AuthContextData {
isAuthReady?: boolean;
geo?: Boot['geo'];
}

const isExtension = checkIsExtension();
const AuthContext = React.createContext<AuthContextData>(null);
export const useAuthContext = (): AuthContextData => useContext(AuthContext);
Expand Down Expand Up @@ -104,7 +105,7 @@ export type AuthContextProviderProps = {
isFetched?: boolean;
isLegacyLogout?: boolean;
children?: ReactNode;
firstLoad?: boolean;
isAuthReady?: boolean;
} & Pick<
AuthContextData,
| 'getRedirectUri'
Expand Down Expand Up @@ -133,15 +134,15 @@ export const AuthContextProvider = ({
isLegacyLogout,
accessToken,
squads,
firstLoad,
isAuthReady,
geo,
}: AuthContextProviderProps): ReactElement => {
const [loginState, setLoginState] = useState<LoginState | null>(null);
const endUser = user && 'providers' in user ? user : null;
const referral = user?.referralId || user?.referrer;
const referralOrigin = user?.referralOrigin;

if (firstLoad === true && endUser && !endUser?.infoConfirmed) {
if (!!isAuthReady && endUser && !endUser?.infoConfirmed) {
logout(LogoutReason.IncomleteOnboarding);
}

Expand All @@ -152,7 +153,7 @@ export const AuthContextProvider = ({
return (
<AuthContext.Provider
value={{
isAuthReady: !isNullOrUndefined(firstLoad),
isAuthReady,
user: endUser,
isLoggedIn: !!endUser?.id,
referral: loginState?.referral ?? referral,
Expand Down
90 changes: 42 additions & 48 deletions packages/shared/src/contexts/BootProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import React, {
ReactElement,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
Expand Down Expand Up @@ -87,7 +86,7 @@ const updateLocalBootData = (
return result;
};

const getCachedOrNull = () => {
const getCachedBootOrNull = () => {
try {
return JSON.parse(storage.getItem(BOOT_LOCAL_KEY));
} catch (err) {
Expand All @@ -112,6 +111,8 @@ export const BootDataProvider = ({
getRedirectUri,
getPage,
}: BootDataProviderProps): ReactElement => {
const { hostGranted } = useHostStatus();
const isExtension = checkIsExtension();
const queryClient = useQueryClient();
const preloadFeedsRef = useRef<PreloadFeeds>();
preloadFeedsRef.current = ({ feeds, user }) => {
Expand All @@ -130,22 +131,15 @@ export const BootDataProvider = ({
);
};

const [initialLoad, setInitialLoad] = useState<boolean>(null);
const [cachedBootData, setCachedBootData] = useState<Partial<Boot>>();

useEffect(() => {
const initialData = useMemo(() => {
if (localBootData) {
setCachedBootData(localBootData);

return;
return localBootData;
}

const boot = getLocalBootData();

if (!boot) {
setCachedBootData(null);

return;
return null;
}

if (boot?.settings?.theme) {
Expand All @@ -154,17 +148,15 @@ export const BootDataProvider = ({

preloadFeedsRef.current({ feeds: boot.feeds, user: boot.user });

setCachedBootData(boot);
return boot;
}, [localBootData]);

const { hostGranted } = useHostStatus();
const isExtension = checkIsExtension();
const logged = cachedBootData?.user as LoggedUser;
const logged = initialData?.user as LoggedUser;
const shouldRefetch = !!logged?.providers && !!logged?.id;
const lastAppliedChangeRef = useRef<Partial<BootCacheData>>();

const {
data: remoteData,
data: bootData,
error,
refetch,
isFetched,
Expand All @@ -175,24 +167,25 @@ export const BootDataProvider = ({
queryFn: async () => {
const result = await getBootData(app);
preloadFeedsRef.current({ feeds: result.feeds, user: result.user });
updateLocalBootData(bootData || {}, result);

return result;
},
refetchOnWindowFocus: shouldRefetch,
staleTime: STALE_TIME,
enabled: !isExtension || !!hostGranted,
placeholderData: initialData,
});

const isBootReady = isFetched && !isError;
const loadedFromCache = !!cachedBootData;
const { user, settings, alerts, notifications, squads, geo } =
cachedBootData || {};
const isBootReady = isFetched && !isError && !!bootData;
const loadedFromCache = !!bootData;
const { user, settings, alerts, notifications, squads, geo } = bootData || {};

useRefreshToken(remoteData?.accessToken, refetch);
useRefreshToken(bootData?.accessToken, refetch);
const updatedAtActive = user ? dataUpdatedAt : null;
const updateBootData = useCallback(
const updateQueryCache = useCallback(
(updatedBootData: Partial<BootCacheData>, update = true) => {
const cachedData = getCachedOrNull() || {};
const cachedData = getCachedBootOrNull() ?? {};
const lastAppliedChange = lastAppliedChangeRef.current;
let updatedData = { ...updatedBootData };
if (update) {
Expand All @@ -208,51 +201,52 @@ export const BootDataProvider = ({
}

const updated = updateLocalBootData(cachedData, updatedData);
setCachedBootData(updated);

queryClient.setQueryData<Partial<Boot>>(BOOT_QUERY_KEY, (previous) => {
if (!previous) {
return updated;
}

return { ...previous, ...updated };
});
},
[],
[queryClient],
);

const updateUser = useCallback(
async (newUser: LoggedUser | AnonymousUser) => {
updateBootData({ user: newUser });
updateQueryCache({ user: newUser });
await queryClient.invalidateQueries({
queryKey: generateQueryKey(RequestKey.Profile, newUser),
});
},
[updateBootData, queryClient],
[updateQueryCache, queryClient],
);

const updateSettings = useCallback(
(updatedSettings) => updateBootData({ settings: updatedSettings }),
[updateBootData],
(updatedSettings: Boot['settings']) =>
updateQueryCache({ settings: updatedSettings }),
[updateQueryCache],
);

const updateAlerts = useCallback(
(updatedAlerts) => updateBootData({ alerts: updatedAlerts }),
[updateBootData],
(updatedAlerts: Boot['alerts']) =>
updateQueryCache({ alerts: updatedAlerts }),
[updateQueryCache],
);

const updateExperimentation = useCallback(
(exp: BootCacheData['exp']) => {
updateLocalBootData(cachedBootData, { exp });
updateLocalBootData(bootData, { exp });
},
[cachedBootData],
[bootData],
);

gqlClient.setHeader(
'content-language',
(user as Partial<LoggedUser>)?.language || ContentLanguage.English,
);

useEffect(() => {
if (remoteData) {
setInitialLoad(initialLoad === null);
updateBootData(remoteData);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [remoteData]);

if (error) {
return (
<div className="mx-2 flex h-screen items-center justify-center">
Expand All @@ -266,7 +260,7 @@ export const BootDataProvider = ({
app={app}
user={user}
deviceId={deviceId}
experimentation={cachedBootData?.exp}
experimentation={bootData?.exp}
updateExperimentation={updateExperimentation}
>
<AuthContextProvider
Expand All @@ -276,13 +270,13 @@ export const BootDataProvider = ({
getRedirectUri={getRedirectUri}
loadingUser={!dataUpdatedAt || !user}
loadedUserFromCache={loadedFromCache}
visit={remoteData?.visit}
visit={bootData?.visit}
refetchBoot={refetch}
isFetched={isBootReady}
isLegacyLogout={remoteData?.isLegacyLogout}
accessToken={remoteData?.accessToken}
isLegacyLogout={bootData?.isLegacyLogout}
accessToken={bootData?.accessToken}
squads={squads}
firstLoad={initialLoad}
isAuthReady={isBootReady}
geo={geo}
>
<SettingsContextProvider
Expand Down
65 changes: 37 additions & 28 deletions packages/shared/src/hooks/log/useLogContextData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MutableRefObject, useMemo } from 'react';
import { MutableRefObject, useCallback } from 'react';
import { LogEvent, PushToQueueFunc } from './useLogQueue';
import { getCurrentLifecycleState } from '../../lib/lifecycle';
import { Origin } from '../../lib/log';
Expand Down Expand Up @@ -51,33 +51,42 @@ export default function useLogContextData(
durationEventsQueue: MutableRefObject<Map<string, LogEvent>>,
sendBeacon: () => void,
): LogContextData {
return useMemo<LogContextData>(
() => ({
logEvent(event: LogEvent) {
pushToQueue([generateEvent(event, sharedPropsRef, getPage())]);
},
logEventStart(id, event) {
if (!durationEventsQueue.current.has(id)) {
durationEventsQueue.current.set(
id,
generateEvent(event, sharedPropsRef, getPage()),
);
}
},
logEventEnd(id, now = new Date()) {
const event = durationEventsQueue.current.get(id);
if (event) {
durationEventsQueue.current.delete(id);
event.event_duration =
now.getTime() - event.event_timestamp.getTime();
if (window.scrollY > 0 && event.event_name !== 'page inactive') {
event.page_state = 'active';
}
pushToQueue([event]);
const logEvent = useCallback(
(event: LogEvent) => {
pushToQueue([generateEvent(event, sharedPropsRef, getPage())]);
},
[getPage, pushToQueue, sharedPropsRef],
);
const logEventStart = useCallback(
(id, event) => {
if (!durationEventsQueue.current.has(id)) {
durationEventsQueue.current.set(
id,
generateEvent(event, sharedPropsRef, getPage()),
);
}
},
[durationEventsQueue, getPage, sharedPropsRef],
);
const logEventEnd = useCallback(
(id, now = new Date()) => {
const event = durationEventsQueue.current.get(id);
if (event) {
durationEventsQueue.current.delete(id);
event.event_duration = now.getTime() - event.event_timestamp.getTime();
if (window.scrollY > 0 && event.event_name !== 'page inactive') {
event.page_state = 'active';
}
},
sendBeacon,
}),
[sharedPropsRef, getPage, pushToQueue, durationEventsQueue, sendBeacon],
pushToQueue([event]);
}
},
[durationEventsQueue, pushToQueue],
);

return {
logEvent,
logEventStart,
logEventEnd,
sendBeacon,
};
}
Loading

0 comments on commit 5b55e3b

Please sign in to comment.