Skip to content

Commit f270ec5

Browse files
arthur791004claude
andcommitted
Dashboard: only record sites the user belongs to as recent/omnibar site
Validate site membership before persisting a site as the most recent site or setting it as the omnibar site, so a crafted or inaccessible `origin_site_id` can no longer be recorded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e4dd988 commit f270ec5

2 files changed

Lines changed: 48 additions & 13 deletions

File tree

client/dashboard/app/interim-omnibar/interim-omnibar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function InterimOmnibar( {
5353
onToggleNotifications,
5454
}: Props ) {
5555
const user = userProp ?? emptyUser;
56-
const siteId = user.primary_blog ?? null;
56+
const siteId = site?.ID ?? null;
5757
const siteSlug = site?.slug ?? null;
5858
const siteAdminUrl = site?.options?.admin_url ?? null;
5959
const isUnlaunchedSite = !! site && site.launch_status === 'unlaunched' && ! site.is_a4a_dev_site;

client/dashboard/app/omnibar/site.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
omnibarSiteIdQuery,
33
queryClient,
4+
siteByIdQuery,
45
userPreferenceQuery,
56
userPreferenceOptimisticMutation,
67
} from '@automattic/api-queries';
@@ -11,14 +12,22 @@ import { useEffect } from 'react';
1112
import { AUTH_QUERY_KEY } from '../auth';
1213
import type { Site, User } from '@automattic/api-core';
1314

15+
function isMemberOfSite( site: Site ) {
16+
// If the user is a member of the site, the capabilities property will exist
17+
return !! site.capabilities;
18+
}
19+
1420
/**
1521
* Initializes the current site for the omnibar, which is extracted from the URL,
1622
* or the most recent sites, or the user's primary blog, in that priority.
1723
*/
1824
export function useInitializeOmnibarSite() {
1925
const user = queryClient.getQueryData< User >( AUTH_QUERY_KEY );
2026

21-
const { data: recentSiteIds } = useQuery( userPreferenceQuery( 'recentSites' ), queryClient );
27+
const { data: recentSiteIds, isLoading: isLoadingRecentSiteIds } = useQuery(
28+
userPreferenceQuery( 'recentSites' ),
29+
queryClient
30+
);
2231
const { mutate: updateRecentSites } = useMutation(
2332
userPreferenceOptimisticMutation( 'recentSites' ),
2433
queryClient
@@ -39,24 +48,44 @@ export function useInitializeOmnibarSite() {
3948
( location.search as Record< string, string | undefined > ).origin_site_id
4049
);
4150
const originSiteId = originSiteIdParam > 0 ? originSiteIdParam : undefined;
51+
const fallbackSiteId =
52+
queryClient.getQueryData< number | null >( omnibarSiteIdQuery().queryKey ) ||
53+
recentSiteIds?.[ 0 ] ||
54+
user?.primary_blog;
4255

43-
const selectedSiteId = routeSite?.ID || originSiteId;
44-
const fallbackSiteId = recentSiteIds?.[ 0 ] || user?.primary_blog;
56+
const { data: originSite, isLoading: isLoadingOriginSite } = useQuery( {
57+
...siteByIdQuery( originSiteId ?? 0 ),
58+
enabled: !! originSiteId,
59+
} );
60+
61+
const { data: fallbackSite, isLoading: isLoadingFallbackSite } = useQuery( {
62+
...siteByIdQuery( fallbackSiteId ?? 0 ),
63+
enabled: !! fallbackSiteId,
64+
} );
4565

4666
useEffect( () => {
47-
// Wait until the route and recent sites are fully loaded, to avoid flicker.
48-
if ( ! isRouteLoaded || ! recentSiteIds ) {
67+
// Wait until the required data are fully loaded, to avoid flicker. Gate on the loading
68+
// state (not `! site`) so an inaccessible id that errors out still settles and doesn't
69+
// block initialization forever.
70+
if (
71+
! isRouteLoaded ||
72+
isLoadingRecentSiteIds ||
73+
isLoadingOriginSite ||
74+
isLoadingFallbackSite
75+
) {
4976
return;
5077
}
5178

5279
// `omnibarSiteIdQuery` is used as cross-tree shared state — its placeholder
5380
// queryFn resolves to `null`. If it's still in flight when we write here,
5481
// the resolution will overwrite our value and the omnibar loses the site.
5582
queryClient.cancelQueries( { queryKey: omnibarSiteIdQuery().queryKey } );
56-
queryClient.setQueryData(
57-
omnibarSiteIdQuery().queryKey,
58-
( currentSiteId ) => selectedSiteId || currentSiteId || fallbackSiteId
83+
84+
const omnibarSite = [ routeSite, originSite, fallbackSite ].find(
85+
( site ) => site && isMemberOfSite( site )
5986
);
87+
const omnibarSiteId = omnibarSite?.ID;
88+
queryClient.setQueryData( omnibarSiteIdQuery().queryKey, () => omnibarSiteId );
6089

6190
// Remove the `origin_site_id` query param from the URL.
6291
if ( originSiteId ) {
@@ -67,16 +96,22 @@ export function useInitializeOmnibarSite() {
6796
);
6897
}
6998

70-
// Push the selected site id as the most recent site.
71-
if ( selectedSiteId && selectedSiteId !== recentSiteIds[ 0 ] ) {
72-
updateRecentSites( [ ...new Set( [ selectedSiteId, ...recentSiteIds ] ) ].slice( 0, 5 ) );
99+
if ( omnibarSiteId && omnibarSiteId !== recentSiteIds?.[ 0 ] ) {
100+
updateRecentSites(
101+
[ ...new Set( [ omnibarSiteId, ...( recentSiteIds || [] ) ] ) ].slice( 0, 5 )
102+
);
73103
}
74104
}, [
75105
isRouteLoaded,
106+
routeSite,
76107
originSiteId,
77-
selectedSiteId,
108+
originSite,
109+
isLoadingOriginSite,
78110
fallbackSiteId,
111+
fallbackSite,
112+
isLoadingFallbackSite,
79113
recentSiteIds,
114+
isLoadingRecentSiteIds,
80115
updateRecentSites,
81116
] );
82117
}

0 commit comments

Comments
 (0)