personal fork of bluesky app bsky.kelinci.net
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: extract session state to external store and direct module exports

+595 -706
+2 -2
src/components/Composer/Autocomplete/useAutocomplete/index.ts
··· 16 16 import { useModerationOpts } from '#/state/preferences/moderation-opts'; 17 17 import { STALE } from '#/state/queries'; 18 18 import { DEFAULT_LOGGED_OUT_PREFERENCES } from '#/state/queries/preferences'; 19 - import { useClients } from '#/state/session'; 19 + import { getClients } from '#/state/session'; 20 20 21 21 import type { 22 22 AutocompleteApi, ··· 43 43 limit?: number; 44 44 showSearchFallback?: boolean; 45 45 }): AutocompleteApi { 46 - const { appview } = useClients(); 46 + const { appview } = getClients(); 47 47 const moderationOpts = useModerationOpts(); 48 48 const emojiSearch = useEmojiSearch(); 49 49
+2 -2
src/components/activity-notifications/SubscribeProfileDialog.tsx
··· 14 14 15 15 import { updateProfileShadow } from '#/state/cache/profile-shadow'; 16 16 import { RQKEY_getActivitySubscriptions } from '#/state/queries/activity-subscriptions'; 17 - import { useClients } from '#/state/session'; 17 + import { getClients } from '#/state/session'; 18 18 19 19 import { logger } from '#/logger'; 20 20 ··· 70 70 moderationOpts: ModerationOptions; 71 71 includeProfile?: boolean; 72 72 }) { 73 - const { appview } = useClients(); 73 + const { appview } = getClients(); 74 74 const queryClient = useQueryClient(); 75 75 const initialState = parseActivitySubscription(profile.viewer?.activitySubscription); 76 76 const [state, setState] = useState(initialState);
+2 -2
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 25 25 useSetThreadgateAllowMutation, 26 26 useThreadgateViewQuery, 27 27 } from '#/state/queries/threadgate'; 28 - import { useClients, useSession } from '#/state/session'; 28 + import { getClients, useSession } from '#/state/session'; 29 29 30 30 import { logger } from '#/logger'; 31 31 ··· 520 520 rootPostUri: ResourceUri; 521 521 }) { 522 522 const queryClient = useQueryClient(); 523 - const { appview, pds } = useClients(); 523 + const { appview, pds } = getClients(); 524 524 const getPost = useGetPost(); 525 525 526 526 return async () => {
+2 -4
src/components/dialogs/Signin.tsx
··· 4 4 5 5 import { errorMessage } from '#/lib/strings/errors'; 6 6 7 - import { type SessionAccount, useSession, useSessionApi } from '#/state/session'; 7 + import { login, type SessionAccount, switchAccount, useSession } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 78 78 onSelectOther: () => void; 79 79 }) { 80 80 const { currentAccount } = useSession(); 81 - const { login, switchAccount } = useSessionApi(); 82 81 const [pendingDid, setPendingDid] = useState<string | null>(null); 83 82 84 83 const onSelectAccount = useCallback( ··· 103 102 setPendingDid(null); 104 103 } 105 104 }, 106 - [close, currentAccount?.did, login, pendingDid, switchAccount], 105 + [close, currentAccount?.did, pendingDid], 107 106 ); 108 107 109 108 return ( ··· 132 131 } 133 132 134 133 function NewAccountScreen({ initialHandle, onBack }: { initialHandle: string; onBack?: () => void }) { 135 - const { login } = useSessionApi(); 136 134 const [identifier, setIdentifier] = useState(initialHandle); 137 135 const [isSubmitting, setIsSubmitting] = useState(false); 138 136 const [error, setError] = useState('');
+2 -2
src/components/dialogs/lists/CreateOrEditListDialog.tsx
··· 11 11 12 12 import type { ImageMeta } from '#/state/gallery'; 13 13 import { useListCreateMutation, useListMetadataMutation } from '#/state/queries/list'; 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 import { logger } from '#/logger'; 17 17 ··· 123 123 }) { 124 124 const activePurpose = list?.purpose || purpose || 'app.bsky.graph.defs#curatelist'; 125 125 const isCurateList = activePurpose === 'app.bsky.graph.defs#curatelist'; 126 - const { appview } = useClients(); 126 + const { appview } = getClients(); 127 127 const { 128 128 mutateAsync: createListMutation, 129 129 error: createListError,
+2 -2
src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
··· 17 17 useListMembershipRemoveMutation, 18 18 } from '#/state/queries/list-memberships'; 19 19 import { useProfileFollowsQuery } from '#/state/queries/profile-follows'; 20 - import { useClients, useSession } from '#/state/session'; 20 + import { getClients, useSession } from '#/state/session'; 21 21 22 22 import * as Dialog from '#/components/Dialog'; 23 23 import * as css from '#/components/dialogs/lists/ListAddRemoveUsersDialog.css'; ··· 60 60 onChange?: (type: 'add' | 'remove', profile: AnyProfileView) => void; 61 61 }) { 62 62 const { currentAccount } = useSession(); 63 - const { appview } = useClients(); 63 + const { appview } = getClients(); 64 64 const moderationOpts = useModerationOpts(); 65 65 const [searchText, setSearchText] = useState(''); 66 66 // dids whose add/remove mutation is in flight, so the row shows a spinner in place of the checkmark until
+2 -2
src/components/hooks/useRichText.ts
··· 4 4 5 5 import { detectFacets, type Richtext } from '#/lib/strings/rich-text-facets'; 6 6 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 export function useRichText(text: string): [Richtext, boolean] { 10 - const { appview } = useClients(); 10 + const { appview } = getClients(); 11 11 const [prevText, setPrevText] = useState(text); 12 12 const [rawRT, setRawRT] = useState<Richtext>(() => ({ text, facets: [] })); 13 13 const [resolvedRT, setResolvedRT] = useState<Richtext | null>(null);
+2 -2
src/components/moderation/AppealForm.tsx
··· 11 11 import { useLabelInfo } from '#/lib/moderation/useLabelInfo'; 12 12 import { makeProfileLink } from '#/lib/routes/links'; 13 13 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 import { logger } from '#/logger'; 17 17 ··· 42 42 const { labeler, strings } = useLabelInfo(label); 43 43 const [details, setDetails] = useState(''); 44 44 const { subject } = useLabelSubject({ label }); 45 - const { pds } = useClients(); 45 + const { pds } = getClients(); 46 46 const sourceName = labeler ? `@${labeler.creator.handle}` : label.src; 47 47 const [error, setError] = useState<string | null>(null); 48 48
+2 -2
src/components/moderation/ReportDialog/action.ts
··· 6 6 7 7 import { useMutation } from '@tanstack/react-query'; 8 8 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 import { logger } from '#/logger'; 12 12 ··· 16 16 import type { ParsedReportSubject } from './types'; 17 17 18 18 export function useSubmitReportMutation() { 19 - const { pds } = useClients(); 19 + const { pds } = getClients(); 20 20 21 21 return useMutation({ 22 22 async mutationFn({
+2 -2
src/features/liveNow/components/GoLiveDisabledDialog.tsx
··· 7 7 8 8 import { OzoneReason } from '#/lib/moderation/report-reasons'; 9 9 10 - import { useClients } from '#/state/session'; 10 + import { getClients } from '#/state/session'; 11 11 12 12 import { logger } from '#/logger'; 13 13 ··· 45 45 handle: Dialog.DialogHandle; 46 46 status: AppBskyActorDefs.StatusView; 47 47 }) { 48 - const { pds } = useClients(); 48 + const { pds } = getClients(); 49 49 const [details, setDetails] = useState(''); 50 50 51 51 const { mutate, isPending } = useMutation({
+3 -3
src/features/liveNow/index.tsx
··· 11 11 import { getLinkMeta, type LinkMeta } from '#/lib/link-meta/link-meta'; 12 12 13 13 import { updateProfileShadow } from '#/state/cache/profile-shadow'; 14 - import { useClients, useSession } from '#/state/session'; 14 + import { getClients, useSession } from '#/state/session'; 15 15 16 16 import { logger } from '#/logger'; 17 17 ··· 50 50 createdAt?: string, 51 51 ) { 52 52 const { currentAccount } = useSession(); 53 - const { pds } = useClients(); 53 + const { pds } = getClients(); 54 54 const queryClient = useQueryClient(); 55 55 return useMutation({ 56 56 mutationFn: async () => { ··· 168 168 169 169 export function useRemoveLiveStatusMutation(handle: DialogHandle) { 170 170 const { currentAccount } = useSession(); 171 - const { pds } = useClients(); 171 + const { pds } = getClients(); 172 172 const queryClient = useQueryClient(); 173 173 return useMutation({ 174 174 mutationFn: async () => {
+2 -3
src/lib/hooks/useAccountSwitcher.ts
··· 1 1 import { useCallback, useState } from 'react'; 2 2 3 - import { type SessionAccount, useSessionApi } from '#/state/session'; 3 + import { switchAccount, type SessionAccount } from '#/state/session'; 4 4 5 5 import { logger } from '#/logger'; 6 6 ··· 11 11 12 12 export function useAccountSwitcher() { 13 13 const [pendingDid, setPendingDid] = useState<string | null>(null); 14 - const { switchAccount } = useSessionApi(); 15 14 const { signinDialogHandle } = useGlobalDialogsHandleContext(); 16 15 17 16 const onPressSwitchAccount = useCallback( ··· 35 34 setPendingDid(null); 36 35 } 37 36 }, 38 - [switchAccount, signinDialogHandle, pendingDid], 37 + [pendingDid, signinDialogHandle], 39 38 ); 40 39 41 40 return { onPressSwitchAccount, pendingDid };
+2 -2
src/screens/Messages/components/ChatDisabled.tsx
··· 7 7 8 8 import { OzoneReason } from '#/lib/moderation/report-reasons'; 9 9 10 - import { useClients, useSession } from '#/state/session'; 10 + import { getClients, useSession } from '#/state/session'; 11 11 12 12 import { logger } from '#/logger'; 13 13 ··· 78 78 79 79 function DialogInner({ handle }: { handle: Dialog.DialogHandle }) { 80 80 const [details, setDetails] = useState(''); 81 - const { pds } = useClients(); 81 + const { pds } = getClients(); 82 82 const { currentAccount } = useSession(); 83 83 84 84 const { mutate, isPending } = useMutation({
+2 -2
src/screens/Messages/components/MessagesList.tsx
··· 20 20 import { useGetJoinLinkPreview } from '#/state/queries/join-links'; 21 21 import { useGetPost } from '#/state/queries/post'; 22 22 import { createEmbedViewRecordFromPost } from '#/state/queries/postgate/util'; 23 - import { useClients, useSession } from '#/state/session'; 23 + import { getClients, useSession } from '#/state/session'; 24 24 25 25 import { logger } from '#/logger'; 26 26 ··· 113 113 hasAcceptOverride?: boolean; 114 114 }) { 115 115 const convoState = useConvoActive(); 116 - const { appview } = useClients(); 116 + const { appview } = getClients(); 117 117 const { hasSession } = useSession(); 118 118 const getPost = useGetPost(); 119 119 const getJoinLinkPreview = useGetJoinLinkPreview();
+2 -2
src/screens/Profile/components/GermSelfDialog.tsx
··· 9 9 import { errorMessage, isNetworkError } from '#/lib/strings/errors'; 10 10 11 11 import { RQKEY } from '#/state/queries/profile'; 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 37 37 } 38 38 39 39 function DialogInner({ did, handle }: { did: Did; handle: Dialog.DialogHandle }) { 40 - const { appview, pds } = useClients(); 40 + const { appview, pds } = getClients(); 41 41 const queryClient = useQueryClient(); 42 42 43 43 const { mutate: deleteDeclaration, isPending } = useMutation({
+2 -2
src/screens/Settings/InterestsSettings.tsx
··· 16 16 import { createGetSuggestedUsersForExploreQueryKey } from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery'; 17 17 import { createGetSuggestedUsersForSeeMoreQueryKey } from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'; 18 18 import { createSuggestedStarterPacksQueryKey } from '#/state/queries/useSuggestedStarterPacksQuery'; 19 - import { useClients } from '#/state/session'; 19 + import { getClients } from '#/state/session'; 20 20 21 21 import { Spinner } from '#/components/Spinner'; 22 22 import { Text } from '#/components/Text'; ··· 72 72 preferences: UsePreferencesQueryResponse; 73 73 setIsSaving: (isSaving: boolean) => void; 74 74 }) { 75 - const { pds } = useClients(); 75 + const { pds } = getClients(); 76 76 const qc = useQueryClient(); 77 77 const interestsDisplayNames = useInterestsDisplayNames(); 78 78 const preselectedInterests = useMemo(() => preferences.interests.tags || [], [preferences.interests.tags]);
+1 -2
src/screens/Settings/Settings.tsx
··· 4 4 5 5 import { useTitle } from '#/lib/hooks/useTitle'; 6 6 7 - import { useSessionApi } from '#/state/session'; 7 + import { logoutEveryAccount } from '#/state/session'; 8 8 9 9 import { Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon } from '#/components/icons/Accessibility'; 10 10 import { Bell_Stroke2_Corner0_Rounded as NotificationIcon } from '#/components/icons/Bell'; ··· 31 31 export function SettingsScreen() { 32 32 useTitle(m['common.nav.settings']()); 33 33 34 - const { logoutEveryAccount } = useSessionApi(); 35 34 const signOutPromptHandle = Prompt.usePromptHandle(); 36 35 37 36 return (
+1 -2
src/screens/Settings/components/AccountsSection.tsx
··· 17 17 import { useProfileShadow } from '#/state/cache/profile-shadow'; 18 18 import { useModerationOpts } from '#/state/preferences/moderation-opts'; 19 19 import { useProfileQuery, useProfilesQuery } from '#/state/queries/profile'; 20 - import { type SessionAccount, useSession, useSessionApi } from '#/state/session'; 20 + import { removeAccount, type SessionAccount, useSession } from '#/state/session'; 21 21 22 22 import { AvatarStack } from '#/components/AvatarStack'; 23 23 import { useGlobalDialogsHandleContext } from '#/components/dialogs/Context'; ··· 213 213 profile?: AppBskyActorDefs.ProfileViewDetailed; 214 214 }) { 215 215 const removePromptHandle = Prompt.usePromptHandle(); 216 - const { removeAccount } = useSessionApi(); 217 216 const { isActive: live } = useActorStatus(profile); 218 217 219 218 return (
+2 -2
src/screens/Settings/components/ExportCarDialog.tsx
··· 4 4 5 5 import { saveBytesToDisk } from '#/lib/media/manip'; 6 6 7 - import { useClients, useSession } from '#/state/session'; 7 + import { getClients, useSession } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 34 34 } 35 35 36 36 function DialogInner() { 37 - const { chat, pds } = useClients(); 37 + const { chat, pds } = getClients(); 38 38 const { currentAccount } = useSession(); 39 39 const [loading, setLoading] = useState<'chat' | 'repo' | false>(false); 40 40
+2 -2
src/screens/StarterPack/StarterPackScreen.tsx
··· 24 24 import { useResolveDidQuery } from '#/state/queries/resolve-uri'; 25 25 import { useShortenLink } from '#/state/queries/shorten-link'; 26 26 import { useDeleteStarterPackMutation, useStarterPackQuery } from '#/state/queries/starter-packs'; 27 - import { useClients, useSession } from '#/state/session'; 27 + import { getClients, useSession } from '#/state/session'; 28 28 29 29 import { logger } from '#/logger'; 30 30 ··· 203 203 onOpenShareDialog: () => void; 204 204 }) { 205 205 const { currentAccount, hasSession } = useSession(); 206 - const { appview, pds } = useClients(); 206 + const { appview, pds } = getClients(); 207 207 const queryClient = useQueryClient(); 208 208 const { signinDialogHandle } = useGlobalDialogsHandleContext(); 209 209
+2 -2
src/state/feed-feedback.tsx
··· 16 16 17 17 import * as PostFeed from '#/view/com/posts/PostFeed'; 18 18 19 - import { useClients } from './session'; 19 + import { getClients } from './session'; 20 20 21 21 export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]; 22 22 ··· 55 55 // create once per mount: Logger.create returns a fresh instance each call, so a render-time call would 56 56 // both allocate per render and make any callback depending on it change identity every render. 57 57 const logger = useConstant(() => Logger.create(Logger.Context.FeedFeedback)); 58 - const { appview } = useClients(); 58 + const { appview } = getClients(); 59 59 60 60 const feed = !!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo) ? feedSourceInfo : undefined; 61 61
+2 -2
src/state/messages/convo/index.tsx
··· 16 16 import { RQKEY as getConvoKey, useMarkAsReadMutation } from '#/state/queries/messages/conversation'; 17 17 import { RQKEY_ROOT as ListConvosQueryKeyRoot } from '#/state/queries/messages/list-conversations'; 18 18 import { RQKEY as createProfileQueryKey } from '#/state/queries/profile'; 19 - import { useClients, useSession } from '#/state/session'; 19 + import { getClients, useSession } from '#/state/session'; 20 20 21 21 import { useFocusEffect } from '#/routes'; 22 22 ··· 60 60 children, 61 61 convoId, 62 62 }: Pick<ConvoParams, 'convoId'> & { children: React.ReactNode }) { 63 - const { chat } = useClients(); 63 + const { chat } = getClients(); 64 64 const { currentAccount } = useSession(); 65 65 if (!chat || !currentAccount) { 66 66 throw new Error('ConvoProvider must be rendered while signed in');
+2 -2
src/state/messages/events/index.tsx
··· 6 6 import { onVisibilityChange } from '#/lib/visibility'; 7 7 8 8 import { MessagesEventBus } from '#/state/messages/events/agent'; 9 - import { useClients, useSession } from '#/state/session'; 9 + import { getClients, useSession } from '#/state/session'; 10 10 11 11 const MessagesEventBusContext = createContext<MessagesEventBus | null>(null); 12 12 MessagesEventBusContext.displayName = 'MessagesEventBusContext'; ··· 21 21 22 22 export function MessagesEventBusProvider({ children }: { children: React.ReactNode }) { 23 23 const { currentAccount } = useSession(); 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 if (!currentAccount || !chat) { 27 27 return <MessagesEventBusContext.Provider value={null}>{children}</MessagesEventBusContext.Provider>;
+3 -3
src/state/queries/activity-subscriptions.ts
··· 15 15 import { getRecord, putRecord } from '#/lib/api/records'; 16 16 17 17 import { registerShadowFinders } from '#/state/cache/registry'; 18 - import { useClients, useSession } from '#/state/session'; 18 + import { getClients, useSession } from '#/state/session'; 19 19 20 20 import * as Toast from '#/components/Toast'; 21 21 ··· 25 25 export const RQKEY_getNotificationDeclaration = ['notification-declaration']; 26 26 27 27 export function useNotificationDeclarationQuery() { 28 - const { pds } = useClients(); 28 + const { pds } = getClients(); 29 29 const { currentAccount } = useSession(); 30 30 return useQuery({ 31 31 queryKey: RQKEY_getNotificationDeclaration, ··· 53 53 } 54 54 55 55 export function useNotificationDeclarationMutation() { 56 - const { pds } = useClients(); 56 + const { pds } = getClients(); 57 57 const { currentAccount } = useSession(); 58 58 const queryClient = useQueryClient(); 59 59 return useMutation({
+3 -3
src/state/queries/actor-autocomplete.ts
··· 15 15 import { toModerationPreferences } from '#/lib/moderation/prefs'; 16 16 17 17 import { STALE } from '#/state/queries'; 18 - import { useClients } from '#/state/session'; 18 + import { getClients } from '#/state/session'; 19 19 20 20 import { useModerationOpts } from '../preferences/moderation-opts'; 21 21 import { DEFAULT_LOGGED_OUT_PREFERENCES } from './preferences'; ··· 30 30 31 31 export function useActorAutocompleteQuery(prefix: string, maintainData?: boolean, limit?: number) { 32 32 const moderationOpts = useModerationOpts(); 33 - const { appview } = useClients(); 33 + const { appview } = getClients(); 34 34 35 35 let normalizedPrefix = prefix.toLowerCase().trim(); 36 36 if (normalizedPrefix.endsWith('.')) { ··· 84 84 self: AnyProfileView | undefined; 85 85 }) { 86 86 const moderationOpts = useModerationOpts(); 87 - const { appview } = useClients(); 87 + const { appview } = getClients(); 88 88 89 89 let normalizedPrefix = query.toLowerCase().trim(); 90 90 if (normalizedPrefix.endsWith('.')) {
+2 -2
src/state/queries/actor-search.ts
··· 11 11 12 12 import { registerShadowFinders } from '#/state/cache/registry'; 13 13 import { STALE } from '#/state/queries'; 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 export const RQKEY_ROOT = 'actor-search'; 17 17 export const RQKEY = (query: string, limit?: number) => [RQKEY_ROOT, query, limit]; ··· 27 27 maintainData?: boolean; 28 28 limit?: number; 29 29 }) { 30 - const { appview } = useClients(); 30 + const { appview } = getClients(); 31 31 return useInfiniteQuery< 32 32 AppBskyActorSearchActors.$output, 33 33 Error,
+3 -3
src/state/queries/actor-starter-packs.ts
··· 3 3 4 4 import { type QueryClient, useInfiniteQuery } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 export const RQKEY_ROOT = 'actor-starter-packs'; 9 9 export const RQKEY_WITH_MEMBERSHIP_ROOT = 'actor-starter-packs-with-membership'; ··· 11 11 export const RQKEY_WITH_MEMBERSHIP = (did?: string) => [RQKEY_WITH_MEMBERSHIP_ROOT, did]; 12 12 13 13 export function useActorStarterPacksQuery({ did, enabled = true }: { did?: Did; enabled?: boolean }) { 14 - const { appview } = useClients(); 14 + const { appview } = getClients(); 15 15 16 16 return useInfiniteQuery({ 17 17 queryKey: RQKEY(did), ··· 34 34 did?: Did; 35 35 enabled?: boolean; 36 36 }) { 37 - const { appview } = useClients(); 37 + const { appview } = getClients(); 38 38 39 39 return useInfiniteQuery({ 40 40 queryKey: RQKEY_WITH_MEMBERSHIP(did),
+2 -2
src/state/queries/bookmarks/useBookmarkMutation.ts
··· 11 11 optimisticallyDeleteBookmark, 12 12 optimisticallySaveBookmark, 13 13 } from '#/state/queries/bookmarks/useBookmarksQuery'; 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 import { logger } from '#/logger'; 17 17 ··· 25 25 26 26 export function useBookmarkMutation() { 27 27 const qc = useQueryClient(); 28 - const { appview } = useClients(); 28 + const { appview } = getClients(); 29 29 30 30 return useMutation({ 31 31 async mutationFn(args: MutationArgs) {
+2 -2
src/state/queries/bookmarks/useBookmarksQuery.ts
··· 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 8 import { didOrHandleUriMatches, embedViewRecordToPostView, getEmbeddedPost } from '#/state/queries/util'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 export const bookmarksQueryKeyRoot = 'bookmarks'; 12 12 export const createBookmarksQueryKey = () => [bookmarksQueryKeyRoot]; 13 13 14 14 export function useBookmarksQuery() { 15 - const { appview } = useClients(); 15 + const { appview } = getClients(); 16 16 17 17 return useInfiniteQuery< 18 18 AppBskyBookmarkGetBookmarks.$output,
+2 -2
src/state/queries/explore-feed-previews.tsx
··· 22 22 import type { FeedPostSlice, FeedPostSliceItem } from '#/state/queries/post-feed'; 23 23 import { usePreferencesQuery } from '#/state/queries/preferences'; 24 24 import { didOrHandleUriMatches, embedViewRecordToPostView, getEmbeddedPost } from '#/state/queries/util'; 25 - import { useClients } from '#/state/session'; 25 + import { getClients } from '#/state/session'; 26 26 27 27 import { m } from '#/paraglide/messages'; 28 28 ··· 113 113 ); 114 114 115 115 const uris = feeds.map((feed) => feed.uri); 116 - const { appview } = useClients(); 116 + const { appview } = getClients(); 117 117 const { data: preferences } = usePreferencesQuery(); 118 118 const userInterests = aggregateUserInterests(preferences); 119 119 const moderationOpts = useModerationOpts();
+7 -7
src/state/queries/feed.ts
··· 29 29 import { RQKEY as listQueryKey } from '#/state/queries/list'; 30 30 import { usePreferencesQuery } from '#/state/queries/preferences'; 31 31 import { createQueryKey } from '#/state/queries/util'; 32 - import { useClients, useSession } from '#/state/session'; 32 + import { getClients, useSession } from '#/state/session'; 33 33 34 34 import { m } from '#/paraglide/messages'; 35 35 ··· 166 166 167 167 export function useFeedSourceInfoQuery({ uri }: { uri: string }) { 168 168 const type = getFeedTypeFromUri(uri); 169 - const { appview } = useClients(); 169 + const { appview } = getClients(); 170 170 171 171 return useQuery({ 172 172 staleTime: STALE.INFINITY, ··· 221 221 222 222 export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { 223 223 const { hasSession } = useSession(); 224 - const { appview } = useClients(); 224 + const { appview } = getClients(); 225 225 const limit = options?.limit || 10; 226 226 const { data: preferences } = usePreferencesQuery(); 227 227 const queryClient = useQueryClient(); ··· 314 314 } 315 315 316 316 export function useSearchPopularFeedsMutation() { 317 - const { appview } = useClients(); 317 + const { appview } = getClients(); 318 318 const moderationOpts = useModerationOpts(); 319 319 320 320 return useMutation({ ··· 341 341 export const createPopularFeedsSearchQueryKey = (query: string) => [popularFeedsSearchQueryKeyRoot, query]; 342 342 343 343 export function usePopularFeedsSearch({ query, enabled }: { query: string; enabled?: boolean }) { 344 - const { appview } = useClients(); 344 + const { appview } = getClients(); 345 345 const moderationOpts = useModerationOpts(); 346 346 const enabledInner = enabled ?? !!moderationOpts; 347 347 ··· 412 412 413 413 export function usePinnedFeedsInfos() { 414 414 const { hasSession } = useSession(); 415 - const { appview } = useClients(); 415 + const { appview } = getClients(); 416 416 const { data: preferences, isLoading: isLoadingPrefs } = usePreferencesQuery(); 417 417 const pinnedItems = preferences?.savedFeeds.filter((feed) => feed.pinned) ?? []; 418 418 ··· 520 520 }; 521 521 522 522 export function useSavedFeeds() { 523 - const { appview } = useClients(); 523 + const { appview } = getClients(); 524 524 const { data: preferences, isLoading: isLoadingPrefs } = usePreferencesQuery(); 525 525 const savedItems = preferences?.savedFeeds ?? []; 526 526 const queryClient = useQueryClient();
+2 -2
src/state/queries/handle.ts
··· 4 4 import { useQueryClient } from '@tanstack/react-query'; 5 5 6 6 import { STALE } from '#/state/queries'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const handleQueryKeyRoot = 'handle'; 10 10 const fetchHandleQueryKey = (handleOrDid: string) => [handleQueryKeyRoot, handleOrDid]; 11 11 12 12 export function useFetchHandle() { 13 13 const queryClient = useQueryClient(); 14 - const { appview } = useClients(); 14 + const { appview } = getClients(); 15 15 16 16 return async (handleOrDid: string) => { 17 17 if (isDid(handleOrDid)) {
+3 -3
src/state/queries/join-links.ts
··· 4 4 import { type QueryClient, type QueryKey, useQuery, useQueryClient } from '@tanstack/react-query'; 5 5 6 6 import { createQueryKey } from '#/state/queries/util'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 91 91 */ 92 92 initialData?: ChatBskyGroupGetJoinLinkPreviews.$output; 93 93 }) { 94 - const { chat } = useClients(); 94 + const { chat } = getClients(); 95 95 96 96 return useQuery({ 97 97 queryKey: createJoinLinkPreviewQueryKey({ codes: codes ?? [], hasSession }), ··· 119 119 * @returns the preview, or undefined if it cannot be resolved 120 120 */ 121 121 export function useGetJoinLinkPreview() { 122 - const { chat } = useClients(); 122 + const { chat } = getClients(); 123 123 const queryClient = useQueryClient(); 124 124 125 125 return async ({
+2 -2
src/state/queries/known-followers.ts
··· 5 5 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 - import { useClients } from '#/state/session'; 8 + import { getClients } from '#/state/session'; 9 9 10 10 const PAGE_SIZE = 50; 11 11 type RQPageParam = string | undefined; ··· 14 14 export const RQKEY = (did: string) => [RQKEY_ROOT, did]; 15 15 16 16 export function useProfileKnownFollowersQuery(did: Did | undefined) { 17 - const { appview } = useClients(); 17 + const { appview } = getClients(); 18 18 return useInfiniteQuery< 19 19 AppBskyGraphGetKnownFollowers.$output, 20 20 Error,
+5 -5
src/state/queries/labeler.ts
··· 10 10 import { preferencesQueryKey, usePreferencesQuery } from '#/state/queries/preferences'; 11 11 import { addLabeler, removeLabeler } from '#/state/queries/preferences/agent'; 12 12 import { createQueryKey } from '#/state/queries/util'; 13 - import { useClients } from '#/state/session'; 13 + import { getClients } from '#/state/session'; 14 14 15 15 const labelerInfoQueryKeyRoot = 'labeler-info'; 16 16 export const labelerInfoQueryKey = (did: string) => [labelerInfoQueryKeyRoot, did]; ··· 19 19 createQueryKey('labelers-detailed-info', { dids }, { persistedVersion: 1 }); 20 20 21 21 export function useLabelerInfoQuery({ did, enabled }: { did?: Did; enabled?: boolean }) { 22 - const { appview } = useClients(); 22 + const { appview } = getClients(); 23 23 return useQuery({ 24 24 enabled: !!did && enabled !== false, 25 25 queryKey: labelerInfoQueryKey(did ?? ''), ··· 36 36 } 37 37 38 38 export function useLabelersDetailedInfoQuery({ dids }: { dids: Did[] }) { 39 - const { appview } = useClients(); 39 + const { appview } = getClients(); 40 40 return useQuery({ 41 41 enabled: !!dids.length, 42 42 queryKey: createLabelersDetailedInfoQueryKey(dids), ··· 56 56 57 57 export function useRemoveLabelersMutation() { 58 58 const queryClient = useQueryClient(); 59 - const { pds } = useClients(); 59 + const { pds } = getClients(); 60 60 61 61 return useMutation({ 62 62 async mutationFn({ dids }: { dids: Did[] }) { ··· 72 72 73 73 export function useLabelerSubscriptionMutation() { 74 74 const queryClient = useQueryClient(); 75 - const { appview, pds } = useClients(); 75 + const { appview, pds } = getClients(); 76 76 const preferences = usePreferencesQuery(); 77 77 78 78 return useMutation({
+3 -3
src/state/queries/like.ts
··· 5 5 6 6 import { createRecord, deleteRecord } from '#/lib/api/records'; 7 7 8 - import { useClients, useSession } from '#/state/session'; 8 + import { getClients, useSession } from '#/state/session'; 9 9 10 10 export function useLikeMutation() { 11 - const { pds } = useClients(); 11 + const { pds } = getClients(); 12 12 const { currentAccount } = useSession(); 13 13 return useMutation({ 14 14 mutationFn: async ({ uri, cid }: { uri: string; cid: string }) => { ··· 28 28 } 29 29 30 30 export function useUnlikeMutation() { 31 - const { pds } = useClients(); 31 + const { pds } = getClients(); 32 32 const { currentAccount } = useSession(); 33 33 return useMutation({ 34 34 mutationFn: async ({ uri }: { uri: string }) => {
+3 -3
src/state/queries/list-members.ts
··· 12 12 13 13 import { registerShadowFinders } from '#/state/cache/registry'; 14 14 import { STALE } from '#/state/queries'; 15 - import { useClients } from '#/state/session'; 15 + import { getClients } from '#/state/session'; 16 16 17 17 const PAGE_SIZE = 30; 18 18 type RQPageParam = string | undefined; ··· 23 23 export const RQKEY_ALL = (uri: string) => [RQKEY_ROOT_ALL, uri]; 24 24 25 25 export function useListMembersQuery(uri?: ResourceUri, limit: number = PAGE_SIZE) { 26 - const { appview } = useClients(); 26 + const { appview } = getClients(); 27 27 return useInfiniteQuery< 28 28 AppBskyGraphGetList.$output, 29 29 Error, ··· 50 50 } 51 51 52 52 export function useAllListMembersQuery(uri?: ResourceUri) { 53 - const { appview } = useClients(); 53 + const { appview } = getClients(); 54 54 return useQuery({ 55 55 staleTime: STALE.MINUTES.ONE, 56 56 queryKey: RQKEY_ALL(uri ?? ''),
+4 -4
src/state/queries/list-memberships.ts
··· 21 21 22 22 import { STALE } from '#/state/queries'; 23 23 import { RQKEY as LIST_MEMBERS_RQKEY } from '#/state/queries/list-members'; 24 - import { useClients, useSession } from '#/state/session'; 24 + import { getClients, useSession } from '#/state/session'; 25 25 26 26 import { RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY } from './actor-starter-packs'; 27 27 ··· 56 56 } 57 57 58 58 export function useListsWithMembershipQuery(params: { actor?: Did; enabled?: boolean }) { 59 - const { appview } = useClients(); 59 + const { appview } = getClients(); 60 60 return useQuery(listsWithMembershipQueryOptions({ ...params, appview })); 61 61 } 62 62 ··· 68 68 onError?: (error: Error) => void; 69 69 } = {}) { 70 70 const { currentAccount } = useSession(); 71 - const { pds } = useClients(); 71 + const { pds } = getClients(); 72 72 const queryClient = useQueryClient(); 73 73 // `subject` (the added profile) drives the optimistic membership cache updates in onSuccess below. 74 74 return useMutation< ··· 190 190 onError?: (error: Error) => void; 191 191 } = {}) { 192 192 const { currentAccount } = useSession(); 193 - const { pds } = useClients(); 193 + const { pds } = getClients(); 194 194 const queryClient = useQueryClient(); 195 195 return useMutation<void, Error, { listUri: ResourceUri; actorDid: Did; membershipUri: ResourceUri }>({ 196 196 mutationFn: async ({ membershipUri }) => {
+7 -7
src/state/queries/list.ts
··· 14 14 15 15 import type { ImageMeta } from '#/state/gallery'; 16 16 import { STALE } from '#/state/queries'; 17 - import { useClients, useSession } from '#/state/session'; 17 + import { getClients, useSession } from '#/state/session'; 18 18 19 19 import { FEED_INFO_RQKEY_ROOT } from './feed'; 20 20 import { invalidate as invalidateMyLists } from './my-lists'; ··· 33 33 }; 34 34 35 35 export function useListQuery(uri?: ResourceUri) { 36 - const { appview } = useClients(); 36 + const { appview } = getClients(); 37 37 return useQuery<AppBskyGraphDefs.ListView>({ 38 38 staleTime: STALE.MINUTES.ONE, 39 39 queryKey: RQKEY(uri || ''), ··· 61 61 } 62 62 export function useListCreateMutation() { 63 63 const { currentAccount } = useSession(); 64 - const { appview, pds } = useClients(); 64 + const { appview, pds } = getClients(); 65 65 const queryClient = useQueryClient(); 66 66 return useMutation<{ uri: ResourceUri; cid: string }, Error, ListCreateMutateParams>({ 67 67 async mutationFn({ purpose, name, description, descriptionFacets, avatar }) { ··· 113 113 } 114 114 export function useListMetadataMutation() { 115 115 const { currentAccount } = useSession(); 116 - const { appview, pds } = useClients(); 116 + const { appview, pds } = getClients(); 117 117 const queryClient = useQueryClient(); 118 118 return useMutation<{ uri: ResourceUri; cid: string }, Error, ListMetadataMutateParams>({ 119 119 async mutationFn({ uri, name, description, descriptionFacets, avatar }) { ··· 171 171 172 172 export function useListDeleteMutation() { 173 173 const { currentAccount } = useSession(); 174 - const { appview, pds } = useClients(); 174 + const { appview, pds } = getClients(); 175 175 const queryClient = useQueryClient(); 176 176 return useMutation<void, Error, { uri: ResourceUri }>({ 177 177 mutationFn: async ({ uri }) => { ··· 229 229 230 230 export function useListMuteMutation() { 231 231 const queryClient = useQueryClient(); 232 - const { appview } = useClients(); 232 + const { appview } = getClients(); 233 233 return useMutation<void, Error, { uri: ResourceUri; mute: boolean }>({ 234 234 mutationFn: async ({ uri, mute }) => { 235 235 await ok( ··· 253 253 254 254 export function useListBlockMutation() { 255 255 const { currentAccount } = useSession(); 256 - const { appview, pds } = useClients(); 256 + const { appview, pds } = getClients(); 257 257 const queryClient = useQueryClient(); 258 258 return useMutation<void, Error, { uri: ResourceUri; block: boolean }>({ 259 259 mutationFn: async ({ uri, block }) => {
+2 -2
src/state/queries/messages/accept-conversation.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 35 35 }, 36 36 ) { 37 37 const queryClient = useQueryClient(); 38 - const { chat } = useClients(); 38 + const { chat } = getClients(); 39 39 40 40 return useMutation({ 41 41 mutationFn: async () => {
+2 -2
src/state/queries/messages/actor-declaration.ts
··· 4 4 5 5 import { putRecord } from '#/lib/api/records'; 6 6 7 - import { useClients, useSession } from '#/state/session'; 7 + import { getClients, useSession } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 21 21 }) { 22 22 const queryClient = useQueryClient(); 23 23 const { currentAccount } = useSession(); 24 - const { pds } = useClients(); 24 + const { pds } = getClients(); 25 25 26 26 return useMutation({ 27 27 mutationFn: async (update: {
+2 -2
src/state/queries/messages/add-group-members.ts
··· 11 11 import { type InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'; 12 12 13 13 import { useProfileQuery } from '#/state/queries/profile'; 14 - import { useClients, useSession } from '#/state/session'; 14 + import { getClients, useSession } from '#/state/session'; 15 15 16 16 import { logger } from '#/logger'; 17 17 ··· 30 30 }, 31 31 ) { 32 32 const queryClient = useQueryClient(); 33 - const { chat } = useClients(); 33 + const { chat } = getClients(); 34 34 const { currentAccount } = useSession(); 35 35 const { data: myProfile } = useProfileQuery({ did: currentAccount?.did }); 36 36
+3 -3
src/state/queries/messages/conversation.ts
··· 11 11 import { registerShadowFinders } from '#/state/cache/registry'; 12 12 import { STALE } from '#/state/queries'; 13 13 import { useOnMarkAsRead } from '#/state/queries/messages/list-conversations'; 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 import { 17 17 RQKEY_PARTIAL as UNREAD_COUNTS_PARTIAL_KEY, ··· 28 28 export const RQKEY = (convoId: string) => [RQKEY_ROOT, convoId]; 29 29 30 30 export function useConvoQuery({ convoId }: { convoId: string }) { 31 - const { chat } = useClients(); 31 + const { chat } = getClients(); 32 32 33 33 return useQuery({ 34 34 queryKey: RQKEY(convoId), ··· 50 50 export function useMarkAsReadMutation() { 51 51 const optimisticUpdate = useOnMarkAsRead(); 52 52 const queryClient = useQueryClient(); 53 - const { chat } = useClients(); 53 + const { chat } = getClients(); 54 54 55 55 return useMutation({ 56 56 mutationFn: async ({ convoId, messageId }: { convoId?: string; messageId?: string }) => {
+2 -2
src/state/queries/messages/create-group-chat.ts
··· 4 4 5 5 import { useMutation, useQueryClient } from '@tanstack/react-query'; 6 6 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 18 18 onError?: (error: Error) => void; 19 19 }) { 20 20 const queryClient = useQueryClient(); 21 - const { chat } = useClients(); 21 + const { chat } = getClients(); 22 22 23 23 return useMutation({ 24 24 mutationFn: async ({ name, members }: { name: string; members: Did[] }) => {
+2 -2
src/state/queries/messages/create-join-link.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 20 20 }, 21 21 ) { 22 22 const queryClient = useQueryClient(); 23 - const { chat } = useClients(); 23 + const { chat } = getClients(); 24 24 25 25 return useMutation({ 26 26 mutationFn: async ({
+2 -2
src/state/queries/messages/disable-join-link.ts
··· 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 6 import { invalidateJoinLinkPreviewsForCode } from '#/state/queries/join-links'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 21 21 }, 22 22 ) { 23 23 const queryClient = useQueryClient(); 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 return useMutation({ 27 27 mutationFn: async () => {
+2 -2
src/state/queries/messages/edit-group-chat-name.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 20 20 }, 21 21 ) { 22 22 const queryClient = useQueryClient(); 23 - const { chat } = useClients(); 23 + const { chat } = getClients(); 24 24 25 25 return useMutation({ 26 26 mutationFn: async ({ name: groupName }: { name: string }) => {
+2 -2
src/state/queries/messages/edit-join-link.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 20 20 }, 21 21 ) { 22 22 const queryClient = useQueryClient(); 23 - const { chat } = useClients(); 23 + const { chat } = getClients(); 24 24 25 25 return useMutation({ 26 26 mutationFn: async ({
+2 -2
src/state/queries/messages/enable-join-link.ts
··· 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 6 import { invalidateJoinLinkPreviewsForCode } from '#/state/queries/join-links'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 21 21 }, 22 22 ) { 23 23 const queryClient = useQueryClient(); 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 return useMutation({ 27 27 mutationFn: async () => {
+2 -2
src/state/queries/messages/get-convo-availability.ts
··· 3 3 4 4 import { useQuery } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { STALE } from '..'; 9 9 ··· 11 11 export const RQKEY = (did: string) => [RQKEY_ROOT, did]; 12 12 13 13 export function useGetConvoAvailabilityQuery(did: Did, { enabled = true }: { enabled?: boolean } = {}) { 14 - const { chat } = useClients(); 14 + const { chat } = getClients(); 15 15 16 16 return useQuery({ 17 17 queryKey: RQKEY(did),
+2 -2
src/state/queries/messages/get-convo-for-members.ts
··· 4 4 5 5 import { useMutation, useQueryClient } from '@tanstack/react-query'; 6 6 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 18 18 onError?: (error: Error) => void; 19 19 }) { 20 20 const queryClient = useQueryClient(); 21 - const { chat } = useClients(); 21 + const { chat } = getClients(); 22 22 23 23 return useMutation({ 24 24 mutationFn: async (members: Did[]) => {
+2 -2
src/state/queries/messages/get-status.ts
··· 4 4 5 5 import { STALE } from '#/state/queries'; 6 6 import { createQueryKey } from '#/state/queries/util'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const chatActorStatusQueryKey = () => createQueryKey('chat-actor-status', {}, { persistedVersion: 1 }); 10 10 11 11 export function useChatActorStatusQuery() { 12 - const { chat } = useClients(); 12 + const { chat } = getClients(); 13 13 14 14 return useQuery({ 15 15 gcTime: STALE.INFINITY,
+2 -2
src/state/queries/messages/get-unread-counts.ts
··· 2 2 3 3 import { useQuery } from '@tanstack/react-query'; 4 4 5 - import { useClients, useSession } from '#/state/session'; 5 + import { getClients, useSession } from '#/state/session'; 6 6 7 7 import { STALE } from '..'; 8 8 ··· 19 19 20 20 export function useUnreadCountsQuery() { 21 21 const { hasSession } = useSession(); 22 - const { chat } = useClients(); 22 + const { chat } = getClients(); 23 23 // no age assurance in this fork, so group chats are always counted 24 24 const includeGroupChats = true; 25 25
+2 -2
src/state/queries/messages/join-requests.ts
··· 9 9 10 10 import { type InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'; 11 11 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 34 34 }, 35 35 ) { 36 36 const queryClient = useQueryClient(); 37 - const { chat } = useClients(); 37 + const { chat } = getClients(); 38 38 39 39 return useMutation({ 40 40 mutationFn: async ({ member }: { member: Did }) => {
+2 -2
src/state/queries/messages/leave-conversation.ts
··· 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 6 import { invalidateJoinLinkPreviewsForConvo } from '#/state/queries/join-links'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 import { logger } from '#/logger'; 10 10 ··· 33 33 }, 34 34 ) { 35 35 const queryClient = useQueryClient(); 36 - const { chat } = useClients(); 36 + const { chat } = getClients(); 37 37 38 38 return useMutation({ 39 39 mutationKey: RQKEY(convoId),
+2 -2
src/state/queries/messages/list-conversation-requests.tsx
··· 4 4 import { type InfiniteData, type QueryClient, useInfiniteQuery } from '@tanstack/react-query'; 5 5 6 6 import { registerShadowFinders } from '#/state/cache/registry'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const DEFAULT_LIMIT = 10; 10 10 ··· 15 15 enabled = true, 16 16 limit = DEFAULT_LIMIT, 17 17 }: { enabled?: boolean; limit?: number } = {}) { 18 - const { chat } = useClients(); 18 + const { chat } = getClients(); 19 19 20 20 return useInfiniteQuery({ 21 21 enabled,
+2 -2
src/state/queries/messages/list-conversations.tsx
··· 18 18 import { useCurrentConvoId } from '#/state/messages/current-convo-id'; 19 19 import { useMessagesEventBus } from '#/state/messages/events'; 20 20 import { invalidateJoinLinkPreviewsForConvo } from '#/state/queries/join-links'; 21 - import { useClients, useSession } from '#/state/session'; 21 + import { getClients, useSession } from '#/state/session'; 22 22 23 23 import { RQKEY as CONVO_KEY } from './conversation'; 24 24 import { ··· 119 119 limit?: number; 120 120 lockStatus?: 'unlocked' | 'locked' | 'locked-permanently'; 121 121 } = {}) { 122 - const { chat } = useClients(); 122 + const { chat } = getClients(); 123 123 124 124 return useInfiniteQuery({ 125 125 enabled,
+2 -2
src/state/queries/messages/list-convo-members.ts
··· 6 6 import { registerShadowFinders } from '#/state/cache/registry'; 7 7 import { STALE } from '#/state/queries'; 8 8 import { createQueryKey } from '#/state/queries/util'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 const RQKEY_ROOT = 'listConvoMembers'; 12 12 export const listConvoMembersQueryKey = (convoId: string) => createQueryKey(RQKEY_ROOT, { convoId }); ··· 21 21 convoId: string; 22 22 placeholderData?: ChatBskyActorDefs.ProfileViewBasic[]; 23 23 }) { 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 return useQuery({ 27 27 queryKey: listConvoMembersQueryKey(convoId),
+2 -2
src/state/queries/messages/list-join-requests.ts
··· 6 6 7 7 import { useMessagesEventBus } from '#/state/messages/events'; 8 8 import { createQueryKey } from '#/state/queries/util'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 import { STALE } from '..'; 12 12 ··· 24 24 convoId: string | undefined; 25 25 enabled?: boolean; 26 26 }) { 27 - const { chat } = useClients(); 27 + const { chat } = getClients(); 28 28 const queryClient = useQueryClient(); 29 29 const messagesBus = useMessagesEventBus(); 30 30 const isEnabled = enabled !== false && !!convoId;
+2 -2
src/state/queries/messages/list-mutual-groups.ts
··· 4 4 import { useInfiniteQuery } from '@tanstack/react-query'; 5 5 6 6 import { createQueryKey } from '#/state/queries/util'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const listMutualGroupsQueryKeyRoot = 'list-mutual-groups'; 10 10 ··· 20 20 enabled?: boolean; 21 21 limit?: number; 22 22 }) { 23 - const { chat } = useClients(); 23 + const { chat } = getClients(); 24 24 const isEnabled = enabled !== false && !!subject; 25 25 26 26 return useInfiniteQuery({
+2 -2
src/state/queries/messages/lock-conversation.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { rollbackConvoOptimistic, updateConvoOptimistic } from './utils/convo-cache'; 9 9 ··· 21 21 }, 22 22 ) { 23 23 const queryClient = useQueryClient(); 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 return useMutation({ 27 27 mutationFn: async ({ lock }: { lock: boolean; silent?: boolean }) => {
+2 -2
src/state/queries/messages/mark-join-request-read.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 12 12 13 13 export function useMarkJoinRequestsRead(convoId: string | undefined) { 14 14 const queryClient = useQueryClient(); 15 - const { chat } = useClients(); 15 + const { chat } = getClients(); 16 16 17 17 return useMutation({ 18 18 mutationFn: async () => {
+2 -2
src/state/queries/messages/mute-conversation.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { rollbackConvoOptimistic, updateConvoOptimistic } from './utils/convo-cache'; 9 9 ··· 18 18 }, 19 19 ) { 20 20 const queryClient = useQueryClient(); 21 - const { chat } = useClients(); 21 + const { chat } = getClients(); 22 22 23 23 return useMutation({ 24 24 mutationFn: async ({ mute }: { mute: boolean }) => {
+2 -2
src/state/queries/messages/remove-from-group.ts
··· 9 9 10 10 import { type InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'; 11 11 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 28 28 }, 29 29 ) { 30 30 const queryClient = useQueryClient(); 31 - const { chat } = useClients(); 31 + const { chat } = getClients(); 32 32 33 33 return useMutation({ 34 34 mutationFn: async ({ members }: { members: Did[] }) => {
+2 -2
src/state/queries/messages/request-join-group-chat.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 17 17 onError?: (error: Error) => void; 18 18 } = {}) { 19 19 const queryClient = useQueryClient(); 20 - const { chat } = useClients(); 20 + const { chat } = getClients(); 21 21 22 22 return useMutation({ 23 23 mutationFn: async ({ code }: { code: string }) => {
+2 -2
src/state/queries/messages/update-all-read.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 32 32 }, 33 33 ) { 34 34 const queryClient = useQueryClient(); 35 - const { chat } = useClients(); 35 + const { chat } = getClients(); 36 36 37 37 return useMutation({ 38 38 mutationFn: async () => {
+2 -2
src/state/queries/messages/withdraw-join-group-chat.ts
··· 3 3 4 4 import { useMutation, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 21 21 onError?: (error: Error) => void; 22 22 } = {}) { 23 23 const queryClient = useQueryClient(); 24 - const { chat } = useClients(); 24 + const { chat } = getClients(); 25 25 26 26 return useMutation({ 27 27 mutationFn: async ({ convoId }: { convoId: string }) => {
+2 -2
src/state/queries/my-blocked-accounts.ts
··· 4 4 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 5 5 6 6 import { registerShadowFinders } from '#/state/cache/registry'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const RQKEY_ROOT = 'my-blocked-accounts'; 10 10 export const RQKEY = () => [RQKEY_ROOT]; 11 11 type RQPageParam = string | undefined; 12 12 13 13 export function useMyBlockedAccountsQuery() { 14 - const { appview } = useClients(); 14 + const { appview } = getClients(); 15 15 return useInfiniteQuery< 16 16 AppBskyGraphGetBlocks.$output, 17 17 Error,
+2 -2
src/state/queries/my-lists.ts
··· 6 6 import { accumulate } from '#/lib/async/accumulate'; 7 7 8 8 import { STALE } from '#/state/queries'; 9 - import { useClients, useSession } from '#/state/session'; 9 + import { getClients, useSession } from '#/state/session'; 10 10 11 11 export type MyListsFilter = 'all' | 'curate' | 'mod' | 'all-including-subscribed'; 12 12 ··· 15 15 16 16 export function useMyListsQuery(filter: MyListsFilter) { 17 17 const { currentAccount } = useSession(); 18 - const { appview } = useClients(); 18 + const { appview } = getClients(); 19 19 return useQuery<AppBskyGraphDefs.ListView[]>({ 20 20 staleTime: STALE.MINUTES.ONE, 21 21 queryKey: RQKEY(filter),
+2 -2
src/state/queries/my-muted-accounts.ts
··· 4 4 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 5 5 6 6 import { registerShadowFinders } from '#/state/cache/registry'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 const RQKEY_ROOT = 'my-muted-accounts'; 10 10 export const RQKEY = () => [RQKEY_ROOT]; 11 11 type RQPageParam = string | undefined; 12 12 13 13 export function useMyMutedAccountsQuery() { 14 - const { appview } = useClients(); 14 + const { appview } = getClients(); 15 15 return useInfiniteQuery< 16 16 AppBskyGraphGetMutes.$output, 17 17 Error,
+2 -2
src/state/queries/notifications/feed.ts
··· 24 24 import { registerShadowFinders } from '#/state/cache/registry'; 25 25 import { useModerationOpts } from '#/state/preferences/moderation-opts'; 26 26 import { STALE } from '#/state/queries'; 27 - import { useClients } from '#/state/session'; 27 + import { getClients } from '#/state/session'; 28 28 import { useThreadgateHiddenReplyUris } from '#/state/threadgate-hidden-replies'; 29 29 30 30 import { didOrHandleUriMatches, embedViewRecordToPostView, getEmbeddedPost } from '../util'; ··· 44 44 } 45 45 46 46 export function useNotificationFeedQuery(opts: { enabled?: boolean; filter: 'all' | 'mentions' }) { 47 - const { appview } = useClients(); 47 + const { appview } = getClients(); 48 48 const queryClient = useQueryClient(); 49 49 const moderationOpts = useModerationOpts(); 50 50 const unreads = useUnreadNotificationsApi();
+4 -4
src/state/queries/notifications/settings.ts
··· 3 3 4 4 import { type QueryClient, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 5 5 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 import { logger } from '#/logger'; 9 9 ··· 52 52 type ChatNotificationSettingsUpdate = Partial<ChatNotificationSettingsPreferences>; 53 53 54 54 export function useNotificationSettingsQuery({ enabled }: { enabled?: boolean } = {}) { 55 - const { appview } = useClients(); 55 + const { appview } = getClients(); 56 56 57 57 return useQuery({ 58 58 queryKey: RQKEY_APP, ··· 65 65 } 66 66 67 67 export function useChatNotificationSettingsQuery({ enabled }: { enabled?: boolean } = {}) { 68 - const { chat } = useClients(); 68 + const { chat } = getClients(); 69 69 70 70 return useQuery({ 71 71 queryKey: RQKEY_CHAT, ··· 81 81 } 82 82 83 83 export function useNotificationSettingsUpdateMutation() { 84 - const { appview, chat } = useClients(); 84 + const { appview, chat } = getClients(); 85 85 const queryClient = useQueryClient(); 86 86 87 87 return useMutation({
+2 -2
src/state/queries/notifications/unread.tsx
··· 11 11 12 12 import { useModerationOpts } from '#/state/preferences/moderation-opts'; 13 13 import { truncateAndInvalidate } from '#/state/queries/util'; 14 - import { useClients, useSession } from '#/state/session'; 14 + import { getClients, useSession } from '#/state/session'; 15 15 16 16 import { RQKEY as RQKEY_NOTIFS } from './feed'; 17 17 import type { CachedFeedPage, FeedPage } from './types'; ··· 41 41 42 42 export function Provider({ children }: React.PropsWithChildren<{}>) { 43 43 const { hasSession } = useSession(); 44 - const { appview } = useClients(); 44 + const { appview } = getClients(); 45 45 const queryClient = useQueryClient(); 46 46 const moderationOpts = useModerationOpts(); 47 47
+2 -2
src/state/queries/pinned-post.ts
··· 12 12 import { m } from '#/paraglide/messages'; 13 13 14 14 import { updatePostShadow } from '../cache/post-shadow'; 15 - import { useClients, useSession } from '../session'; 15 + import { getClients, useSession } from '../session'; 16 16 import { useProfileUpdateMutation } from './profile'; 17 17 18 18 export function usePinnedPostMutation() { 19 19 const { currentAccount } = useSession(); 20 - const { appview } = useClients(); 20 + const { appview } = getClients(); 21 21 const queryClient = useQueryClient(); 22 22 const { mutateAsync: profileUpdateMutate } = useProfileUpdateMutation(); 23 23
+2 -2
src/state/queries/post-feed.ts
··· 42 42 import { registerShadowFinders } from '#/state/cache/registry'; 43 43 import { STALE } from '#/state/queries'; 44 44 import { DEFAULT_LOGGED_OUT_PREFERENCES } from '#/state/queries/preferences/const'; 45 - import { useClients, useSession } from '#/state/session'; 45 + import { getClients, useSession } from '#/state/session'; 46 46 import * as userActionHistory from '#/state/userActionHistory'; 47 47 48 48 import { logger } from '#/logger'; ··· 130 130 /** awaits Active Assistant (AA) state to prevent flash of unstyled content (FOUC). */ 131 131 const enabled = opts?.enabled !== false && !!moderationOpts && !!preferences; 132 132 const userInterests = aggregateUserInterests(preferences); 133 - const { appview } = useClients(); 133 + const { appview } = getClients(); 134 134 const { hasSession } = useSession(); 135 135 const lastRun = useRef<{ 136 136 data: InfiniteData<FeedPageUnselected>;
+2 -2
src/state/queries/post-interaction-settings.ts
··· 4 4 5 5 import { preferencesQueryKey } from '#/state/queries/preferences'; 6 6 import { setPostInteractionSettings } from '#/state/queries/preferences/agent'; 7 - import { useClients } from '#/state/session'; 7 + import { getClients } from '#/state/session'; 8 8 9 9 export function usePostInteractionSettingsMutation({ 10 10 onError, ··· 14 14 onSettled?: () => void; 15 15 } = {}) { 16 16 const qc = useQueryClient(); 17 - const { pds } = useClients(); 17 + const { pds } = getClients(); 18 18 return useMutation({ 19 19 async mutationFn(props: AppBskyActorDefs.PostInteractionSettingsPref) { 20 20 await setPostInteractionSettings(pds!, props);
+2 -2
src/state/queries/post-liked-by.ts
··· 5 5 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 - import { useClients } from '#/state/session'; 8 + import { getClients } from '#/state/session'; 9 9 10 10 const PAGE_SIZE = 30; 11 11 type RQPageParam = string | undefined; ··· 15 15 export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]; 16 16 17 17 export function useLikedByQuery(resolvedUri: ResourceUri | undefined) { 18 - const { appview } = useClients(); 18 + const { appview } = getClients(); 19 19 return useInfiniteQuery< 20 20 AppBskyFeedGetLikes.$output, 21 21 Error,
+2 -2
src/state/queries/post-quotes.ts
··· 11 11 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 12 12 13 13 import { registerShadowFinders } from '#/state/cache/registry'; 14 - import { useClients } from '#/state/session'; 14 + import { getClients } from '#/state/session'; 15 15 16 16 import { didOrHandleUriMatches, embedViewRecordToPostView, getEmbeddedPost } from './util'; 17 17 ··· 22 22 export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]; 23 23 24 24 export function usePostQuotesQuery(resolvedUri: ResourceUri | undefined) { 25 - const { appview } = useClients(); 25 + const { appview } = getClients(); 26 26 return useInfiniteQuery< 27 27 AppBskyFeedGetQuotes.$output, 28 28 Error,
+2 -2
src/state/queries/post-reposted-by.ts
··· 5 5 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 - import { useClients } from '#/state/session'; 8 + import { getClients } from '#/state/session'; 9 9 10 10 const PAGE_SIZE = 30; 11 11 type RQPageParam = string | undefined; ··· 15 15 export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]; 16 16 17 17 export function usePostRepostedByQuery(resolvedUri: ResourceUri | undefined) { 18 - const { appview } = useClients(); 18 + const { appview } = getClients(); 19 19 return useInfiniteQuery< 20 20 AppBskyFeedGetRepostedBy.$output, 21 21 Error,
+11 -11
src/state/queries/post.ts
··· 12 12 13 13 import { updatePostShadow } from '#/state/cache/post-shadow'; 14 14 import type { Shadow } from '#/state/cache/types'; 15 - import { useClients, useSession } from '#/state/session'; 15 + import { getClients, useSession } from '#/state/session'; 16 16 import * as userActionHistory from '#/state/userActionHistory'; 17 17 18 18 import { useIsThreadMuted, useSetThreadMute } from '../cache/thread-mutes'; ··· 21 21 export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]; 22 22 23 23 export function usePostQuery(uri: ResourceUri | undefined) { 24 - const { appview } = useClients(); 24 + const { appview } = getClients(); 25 25 return useQuery<AppBskyFeedDefs.PostView>({ 26 26 queryKey: RQKEY(uri || ''), 27 27 queryFn: async () => { ··· 58 58 59 59 export function useGetPost() { 60 60 const queryClient = useQueryClient(); 61 - const { appview } = useClients(); 61 + const { appview } = getClients(); 62 62 return useCallback( 63 63 async ({ uri }: { uri: ResourceUri }) => { 64 64 return queryClient.fetchQuery({ ··· 96 96 97 97 export function useGetPosts() { 98 98 const queryClient = useQueryClient(); 99 - const { appview } = useClients(); 99 + const { appview } = getClients(); 100 100 return useCallback( 101 101 async ({ uris }: { uris: ResourceUri[] }) => { 102 102 return queryClient.fetchQuery({ ··· 176 176 } 177 177 178 178 function usePostLikeMutation() { 179 - const { pds } = useClients(); 179 + const { pds } = getClients(); 180 180 const { currentAccount } = useSession(); 181 181 return useMutation< 182 182 { uri: ResourceUri }, // responds with the uri of the like ··· 199 199 } 200 200 201 201 function usePostUnlikeMutation() { 202 - const { pds } = useClients(); 202 + const { pds } = getClients(); 203 203 const { currentAccount } = useSession(); 204 204 return useMutation<void, Error, { postUri: string; likeUri: string }>({ 205 205 mutationFn: ({ postUri: _postUri, likeUri }) => { ··· 271 271 } 272 272 273 273 function usePostRepostMutation() { 274 - const { pds } = useClients(); 274 + const { pds } = getClients(); 275 275 const { currentAccount } = useSession(); 276 276 return useMutation< 277 277 { uri: ResourceUri }, // responds with the uri of the repost ··· 294 294 } 295 295 296 296 function usePostUnrepostMutation() { 297 - const { pds } = useClients(); 297 + const { pds } = getClients(); 298 298 const { currentAccount } = useSession(); 299 299 return useMutation<void, Error, { postUri: string; repostUri: string }>({ 300 300 mutationFn: ({ postUri: _postUri, repostUri }) => { ··· 309 309 310 310 export function usePostDeleteMutation() { 311 311 const queryClient = useQueryClient(); 312 - const { pds } = useClients(); 312 + const { pds } = getClients(); 313 313 const { currentAccount } = useSession(); 314 314 return useMutation<void, Error, { uri: string }>({ 315 315 mutationFn: async ({ uri }) => { ··· 368 368 } 369 369 370 370 function useThreadMuteMutation() { 371 - const { appview } = useClients(); 371 + const { appview } = getClients(); 372 372 return useMutation< 373 373 void, 374 374 Error, ··· 386 386 } 387 387 388 388 function useThreadUnmuteMutation() { 389 - const { appview } = useClients(); 389 + const { appview } = getClients(); 390 390 return useMutation<void, Error, { uri: ResourceUri }>({ 391 391 mutationFn: async ({ uri }) => { 392 392 await ok(
+4 -4
src/state/queries/postgate/index.ts
··· 20 20 mergePostgateRecords, 21 21 POSTGATE_COLLECTION, 22 22 } from '#/state/queries/postgate/util'; 23 - import { useClients, useSession } from '#/state/session'; 23 + import { getClients, useSession } from '#/state/session'; 24 24 25 25 import { logger } from '#/logger'; 26 26 ··· 140 140 141 141 export const createPostgateQueryKey = (postUri: string) => ['postgate-record', postUri]; 142 142 export function usePostgateQuery({ postUri }: { postUri: ResourceUri }) { 143 - const { appview, pds } = useClients(); 143 + const { appview, pds } = getClients(); 144 144 return useQuery({ 145 145 staleTime: STALE.SECONDS.THIRTY, 146 146 queryKey: createPostgateQueryKey(postUri), ··· 151 151 } 152 152 153 153 export function useWritePostgateMutation() { 154 - const { pds } = useClients(); 154 + const { pds } = getClients(); 155 155 const { currentAccount } = useSession(); 156 156 const queryClient = useQueryClient(); 157 157 return useMutation({ ··· 178 178 } 179 179 180 180 export function useToggleQuoteDetachmentMutation() { 181 - const { appview, pds } = useClients(); 181 + const { appview, pds } = getClients(); 182 182 const { currentAccount } = useSession(); 183 183 const queryClient = useQueryClient(); 184 184 const getPosts = useGetPosts();
+16 -16
src/state/queries/preferences/index.ts
··· 34 34 } from '#/state/queries/preferences/const'; 35 35 import type { ThreadViewPreferences, UsePreferencesQueryResponse } from '#/state/queries/preferences/types'; 36 36 import { createQueryKey } from '#/state/queries/util'; 37 - import { useClients, useSession } from '#/state/session'; 37 + import { getClients, useSession } from '#/state/session'; 38 38 import { saveLabelers } from '#/state/session/agent-config'; 39 39 import { setSubscribedLabelers } from '#/state/session/labelers'; 40 40 ··· 51 51 export const preferencesQueryKey = createQueryKey('getPreferences', {}, { persistedVersion: 1 }); 52 52 53 53 export function usePreferencesQuery() { 54 - const { pds } = useClients(); 54 + const { pds } = getClients(); 55 55 const { currentAccount } = useSession(); 56 56 57 57 const query = useQuery({ ··· 95 95 } 96 96 97 97 export function usePreferencesSetContentLabelMutation() { 98 - const { pds } = useClients(); 98 + const { pds } = getClients(); 99 99 const queryClient = useQueryClient(); 100 100 101 101 return useMutation< ··· 115 115 116 116 export function usePreferencesSetAdultContentMutation() { 117 117 const queryClient = useQueryClient(); 118 - const { pds } = useClients(); 118 + const { pds } = getClients(); 119 119 120 120 return useMutation<void, unknown, { enabled: boolean }>({ 121 121 mutationFn: async ({ enabled }) => { ··· 130 130 131 131 export function useSetFeedViewPreferencesMutation() { 132 132 const queryClient = useQueryClient(); 133 - const { pds } = useClients(); 133 + const { pds } = getClients(); 134 134 135 135 return useMutation<void, unknown, Partial<BskyFeedViewPreference>>({ 136 136 mutationFn: async (prefs) => { ··· 155 155 onError?: (error: unknown) => void; 156 156 }) { 157 157 const queryClient = useQueryClient(); 158 - const { pds } = useClients(); 158 + const { pds } = getClients(); 159 159 160 160 return useMutation<void, unknown, Partial<ThreadViewPreferences>>({ 161 161 mutationFn: async (prefs) => { ··· 172 172 173 173 export function useOverwriteSavedFeedsMutation() { 174 174 const queryClient = useQueryClient(); 175 - const { pds } = useClients(); 175 + const { pds } = getClients(); 176 176 177 177 return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({ 178 178 mutationFn: async (savedFeeds) => { ··· 187 187 188 188 export function useAddSavedFeedsMutation() { 189 189 const queryClient = useQueryClient(); 190 - const { pds } = useClients(); 190 + const { pds } = getClients(); 191 191 192 192 return useMutation<void, unknown, Pick<AppBskyActorDefs.SavedFeed, 'type' | 'value' | 'pinned'>[]>({ 193 193 mutationFn: async (savedFeeds) => { ··· 202 202 203 203 export function useRemoveFeedMutation() { 204 204 const queryClient = useQueryClient(); 205 - const { pds } = useClients(); 205 + const { pds } = getClients(); 206 206 207 207 return useMutation<void, unknown, Pick<AppBskyActorDefs.SavedFeed, 'id'>>({ 208 208 mutationFn: async (savedFeed) => { ··· 266 266 267 267 export function useReplaceForYouWithDiscoverFeedMutation() { 268 268 const queryClient = useQueryClient(); 269 - const { pds } = useClients(); 269 + const { pds } = getClients(); 270 270 271 271 return useMutation({ 272 272 mutationFn: async ({ ··· 305 305 306 306 export function useUpdateSavedFeedsMutation() { 307 307 const queryClient = useQueryClient(); 308 - const { pds } = useClients(); 308 + const { pds } = getClients(); 309 309 310 310 return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({ 311 311 mutationFn: async (feeds) => { ··· 321 321 322 322 export function useUpsertMutedWordsMutation() { 323 323 const queryClient = useQueryClient(); 324 - const { pds } = useClients(); 324 + const { pds } = getClients(); 325 325 326 326 return useMutation({ 327 327 mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { ··· 336 336 337 337 export function useUpdateMutedWordMutation() { 338 338 const queryClient = useQueryClient(); 339 - const { pds } = useClients(); 339 + const { pds } = getClients(); 340 340 341 341 return useMutation({ 342 342 mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { ··· 351 351 352 352 export function useRemoveMutedWordMutation() { 353 353 const queryClient = useQueryClient(); 354 - const { pds } = useClients(); 354 + const { pds } = getClients(); 355 355 356 356 return useMutation({ 357 357 mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { ··· 366 366 367 367 export function useRemoveMutedWordsMutation() { 368 368 const queryClient = useQueryClient(); 369 - const { pds } = useClients(); 369 + const { pds } = getClients(); 370 370 371 371 return useMutation({ 372 372 mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { ··· 381 381 382 382 export function useSetVerificationPrefsMutation() { 383 383 const queryClient = useQueryClient(); 384 - const { pds } = useClients(); 384 + const { pds } = getClients(); 385 385 386 386 return useMutation<void, unknown, AppBskyActorDefs.VerificationPrefs>({ 387 387 mutationFn: async (prefs) => {
+2 -2
src/state/queries/profile-feedgens.ts
··· 10 10 11 11 import { type InfiniteData, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 12 12 13 - import { useClients } from '#/state/session'; 13 + import { getClients } from '#/state/session'; 14 14 15 15 import { useModerationOpts } from '../preferences/moderation-opts'; 16 16 ··· 24 24 export function useProfileFeedgensQuery(did: Did) { 25 25 const moderationOpts = useModerationOpts(); 26 26 const enabled = !!moderationOpts; 27 - const { appview } = useClients(); 27 + const { appview } = getClients(); 28 28 return useInfiniteQuery< 29 29 AppBskyFeedGetActorFeeds.$output, 30 30 Error,
+2 -2
src/state/queries/profile-followers.ts
··· 5 5 import { type InfiniteData, type QueryClient, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 - import { useClients } from '#/state/session'; 8 + import { getClients } from '#/state/session'; 9 9 10 10 const PAGE_SIZE = 30; 11 11 type RQPageParam = string | undefined; ··· 14 14 export const RQKEY = (did: string) => [RQKEY_ROOT, did]; 15 15 16 16 export function useProfileFollowersQuery(did: Did | undefined) { 17 - const { appview } = useClients(); 17 + const { appview } = getClients(); 18 18 return useInfiniteQuery< 19 19 AppBskyGraphGetFollowers.$output, 20 20 Error,
+2 -2
src/state/queries/profile-follows.ts
··· 6 6 7 7 import { registerShadowFinders } from '#/state/cache/registry'; 8 8 import { STALE } from '#/state/queries'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 const PAGE_SIZE = 30; 12 12 type RQPageParam = string | undefined; ··· 25 25 limit: PAGE_SIZE, 26 26 }, 27 27 ) { 28 - const { appview } = useClients(); 28 + const { appview } = getClients(); 29 29 return useInfiniteQuery< 30 30 AppBskyGraphGetFollows.$output, 31 31 Error,
+2 -2
src/state/queries/profile-lists.ts
··· 10 10 11 11 import { type InfiniteData, type QueryKey, useInfiniteQuery } from '@tanstack/react-query'; 12 12 13 - import { useClients } from '#/state/session'; 13 + import { getClients } from '#/state/session'; 14 14 15 15 import { useModerationOpts } from '../preferences/moderation-opts'; 16 16 ··· 23 23 export function useProfileListsQuery(did: Did) { 24 24 const moderationOpts = useModerationOpts(); 25 25 const enabled = !!moderationOpts; 26 - const { appview } = useClients(); 26 + const { appview } = getClients(); 27 27 return useInfiniteQuery< 28 28 AppBskyGraphGetLists.$output, 29 29 Error,
+10 -10
src/state/queries/profile.ts
··· 41 41 useUnstableProfileViewCache, 42 42 } from '#/state/queries/unstable-profile-cache'; 43 43 import { useUpdateProfileVerificationCache } from '#/state/queries/verification/useUpdateProfileVerificationCache'; 44 - import { getClients, useClients, useSession } from '#/state/session'; 44 + import { getClients, useSession } from '#/state/session'; 45 45 import * as userActionHistory from '#/state/userActionHistory'; 46 46 47 47 import { RQKEY_ROOT as RQKEY_LIST_CONVOS } from './messages/list-conversations'; ··· 79 79 did: Did | undefined; 80 80 staleTime?: number; 81 81 }) { 82 - const { appview } = useClients(); 82 + const { appview } = getClients(); 83 83 const { getUnstableProfile } = useUnstableProfileViewCache(); 84 84 return useQuery<AppBskyActorDefs.ProfileViewDetailed>({ 85 85 // WARNING ··· 161 161 } 162 162 export function useProfileUpdateMutation() { 163 163 const queryClient = useQueryClient(); 164 - const { appview, pds } = useClients(); 164 + const { appview, pds } = getClients(); 165 165 const updateProfileVerificationCache = useUpdateProfileVerificationCache(); 166 166 return useMutation<void, Error, ProfileUpdateParams>({ 167 167 mutationFn: async ({ profile, updates, newUserAvatar, newUserBanner, checkCommitted }) => { ··· 237 237 } 238 238 239 239 export function useProfileFollowMutationQueue(profile: Shadow<AnyProfileView>) { 240 - const { appview } = useClients(); 240 + const { appview } = getClients(); 241 241 const queryClient = useQueryClient(); 242 242 const { currentAccount } = useSession(); 243 243 const did = profile.did; ··· 344 344 } 345 345 346 346 function useProfileFollowMutation() { 347 - const { pds } = useClients(); 347 + const { pds } = getClients(); 348 348 const { currentAccount } = useSession(); 349 349 350 350 return useMutation<{ uri: ResourceUri; cid: string }, Error, { did: Did }>({ ··· 363 363 } 364 364 365 365 function useProfileUnfollowMutation() { 366 - const { pds } = useClients(); 366 + const { pds } = getClients(); 367 367 const { currentAccount } = useSession(); 368 368 return useMutation<void, Error, { did: Did; followUri: string }>({ 369 369 mutationFn: async ({ followUri }) => { ··· 425 425 426 426 function useProfileMuteMutation() { 427 427 const queryClient = useQueryClient(); 428 - const { appview } = useClients(); 428 + const { appview } = getClients(); 429 429 return useMutation<void, Error, { did: Did }>({ 430 430 mutationFn: async ({ did }) => { 431 431 await ok( ··· 443 443 444 444 function useProfileUnmuteMutation() { 445 445 const queryClient = useQueryClient(); 446 - const { appview } = useClients(); 446 + const { appview } = getClients(); 447 447 return useMutation<void, Error, { did: Did }>({ 448 448 mutationFn: async ({ did }) => { 449 449 await ok( ··· 518 518 519 519 function useProfileBlockMutation() { 520 520 const { currentAccount } = useSession(); 521 - const { pds } = useClients(); 521 + const { pds } = getClients(); 522 522 const queryClient = useQueryClient(); 523 523 return useMutation<{ uri: ResourceUri; cid: string }, Error, { did: Did }>({ 524 524 mutationFn: async ({ did }) => { ··· 544 544 545 545 function useProfileUnblockMutation() { 546 546 const { currentAccount } = useSession(); 547 - const { pds } = useClients(); 547 + const { pds } = getClients(); 548 548 const queryClient = useQueryClient(); 549 549 return useMutation<void, Error, { did: Did; blockUri: string }>({ 550 550 mutationFn: async ({ blockUri }) => {
+2 -2
src/state/queries/resolve-link.ts
··· 5 5 import { type ResolvedLink, resolveGif, resolveLink } from '#/lib/api/resolve'; 6 6 7 7 import { STALE } from '#/state/queries/index'; 8 - import { useClients } from '#/state/session'; 8 + import { getClients } from '#/state/session'; 9 9 10 10 import type { Gif } from '#/features/gifPicker/types'; 11 11 ··· 16 16 export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url]; 17 17 18 18 export function useResolveLinkQuery(url: string) { 19 - const { appview } = useClients(); 19 + const { appview } = getClients(); 20 20 21 21 return useQuery({ 22 22 staleTime: STALE.HOURS.ONE,
+3 -3
src/state/queries/resolve-uri.ts
··· 6 6 import { type QueryClient, queryOptions, useQuery } from '@tanstack/react-query'; 7 7 8 8 import { STALE } from '#/state/queries'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 import { useUnstableProfileViewCache } from './profile'; 12 12 ··· 65 65 const urip = uri && isResourceUri(uri) ? parseResourceUri(uri) : undefined; 66 66 const host = urip?.repo; 67 67 68 - const { appview } = useClients(); 68 + const { appview } = getClients(); 69 69 const { getUnstableProfile } = useUnstableProfileViewCache(); 70 70 71 71 return useQuery({ ··· 78 78 } 79 79 80 80 export function useResolveDidQuery(didOrHandle: string | undefined) { 81 - const { appview } = useClients(); 81 + const { appview } = getClients(); 82 82 const { getUnstableProfile } = useUnstableProfileViewCache(); 83 83 84 84 return useQuery(resolvedDidQueryOptions(appview, getUnstableProfile, didOrHandle));
+2 -2
src/state/queries/search-posts.ts
··· 13 13 14 14 import { registerShadowFinders } from '#/state/cache/registry'; 15 15 import { useModerationOpts } from '#/state/preferences/moderation-opts'; 16 - import { useClients, useSession } from '#/state/session'; 16 + import { getClients, useSession } from '#/state/session'; 17 17 18 18 import { didOrHandleUriMatches, embedViewRecordToPostView, getEmbeddedPost } from './util'; 19 19 ··· 39 39 query: string; 40 40 sort?: 'top' | 'latest'; 41 41 }) { 42 - const { appview } = useClients(); 42 + const { appview } = getClients(); 43 43 const { currentAccount } = useSession(); 44 44 const moderationOpts = useModerationOpts(); 45 45 const viewerDid = currentAccount?.did;
+2 -2
src/state/queries/service-config.ts
··· 3 3 import { useQuery } from '@tanstack/react-query'; 4 4 5 5 import { STALE } from '#/state/queries'; 6 - import { useClients } from '#/state/session'; 6 + import { getClients } from '#/state/session'; 7 7 8 8 type ServiceConfig = { 9 9 checkEmailConfirmed: boolean; ··· 15 15 }; 16 16 17 17 export function useServiceConfigQuery() { 18 - const { appview } = useClients(); 18 + const { appview } = getClients(); 19 19 return useQuery<ServiceConfig>({ 20 20 refetchOnWindowFocus: true, 21 21 staleTime: STALE.MINUTES.FIVE,
+5 -5
src/state/queries/starter-packs.ts
··· 27 27 import { invalidateActorStarterPacksQuery } from '#/state/queries/actor-starter-packs'; 28 28 import { STALE } from '#/state/queries/index'; 29 29 import { invalidateListMembersQuery } from '#/state/queries/list-members'; 30 - import { useClients, useSession } from '#/state/session'; 30 + import { getClients, useSession } from '#/state/session'; 31 31 32 32 async function detectDescriptionFacets( 33 33 appview: Client, ··· 55 55 }; 56 56 57 57 export function useStarterPackQuery({ uri, did, rkey }: { uri?: string; did?: string; rkey?: string }) { 58 - const { appview } = useClients(); 58 + const { appview } = getClients(); 59 59 60 60 return useQuery<AppBskyGraphDefs.StarterPackView>({ 61 61 queryKey: RQKEY(uri ? { uri } : { did, rkey }), ··· 106 106 onError: (e: Error) => void; 107 107 }) { 108 108 const queryClient = useQueryClient(); 109 - const { appview, pds } = useClients(); 109 + const { appview, pds } = getClients(); 110 110 const { currentAccount } = useSession(); 111 111 112 112 return useMutation<{ cid: Cid; uri: ResourceUri }, Error, UseCreateStarterPackMutationParams>({ ··· 164 164 onError: (error: Error) => void; 165 165 }) { 166 166 const queryClient = useQueryClient(); 167 - const { appview, pds } = useClients(); 167 + const { appview, pds } = getClients(); 168 168 const { currentAccount } = useSession(); 169 169 170 170 return useMutation< ··· 284 284 onSuccess: () => void; 285 285 onError: (error: Error) => void; 286 286 }) { 287 - const { appview, pds } = useClients(); 287 + const { appview, pds } = getClients(); 288 288 const { currentAccount } = useSession(); 289 289 const queryClient = useQueryClient(); 290 290
+2 -2
src/state/queries/suggested-follows.ts
··· 10 10 11 11 import { registerShadowFinders } from '#/state/cache/registry'; 12 12 import { STALE } from '#/state/queries'; 13 - import { useClients } from '#/state/session'; 13 + import { getClients } from '#/state/session'; 14 14 15 15 const suggestedFollowsQueryKeyRoot = 'suggested-follows'; 16 16 ··· 26 26 enabled?: boolean; 27 27 staleTime?: number; 28 28 }) { 29 - const { appview } = useClients(); 29 + const { appview } = getClients(); 30 30 return useQuery({ 31 31 staleTime, 32 32 queryKey: suggestedFollowsByActorQueryKey(did),
+3 -3
src/state/queries/threadgate/index.ts
··· 18 18 threadgateViewToAllowUISetting, 19 19 } from '#/state/queries/threadgate/util'; 20 20 import { useUpdatePostThreadThreadgateQueryCache } from '#/state/queries/usePostThread'; 21 - import { useClients, useSession } from '#/state/session'; 21 + import { getClients, useSession } from '#/state/session'; 22 22 import { useThreadgateHiddenReplyUrisAPI } from '#/state/threadgate-hidden-replies'; 23 23 24 24 export * from '#/state/queries/threadgate/types'; ··· 173 173 } 174 174 175 175 export function useSetThreadgateAllowMutation() { 176 - const { appview, pds } = useClients(); 176 + const { appview, pds } = getClients(); 177 177 const { currentAccount } = useSession(); 178 178 const queryClient = useQueryClient(); 179 179 const getPost = useGetPost(); ··· 235 235 } 236 236 237 237 export function useToggleReplyVisibilityMutation() { 238 - const { appview, pds } = useClients(); 238 + const { appview, pds } = getClients(); 239 239 const { currentAccount } = useSession(); 240 240 const queryClient = useQueryClient(); 241 241 const hiddenReplies = useThreadgateHiddenReplyUrisAPI();
+2 -2
src/state/queries/trending/useGetSuggestedFeedsQuery.ts
··· 7 7 import { getContentLanguages } from '#/state/preferences/languages'; 8 8 import { STALE } from '#/state/queries'; 9 9 import { usePreferencesQuery } from '#/state/queries/preferences'; 10 - import { useClients } from '#/state/session'; 10 + import { getClients } from '#/state/session'; 11 11 12 12 export const DEFAULT_LIMIT = 15; 13 13 14 14 export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']; 15 15 16 16 export function useGetSuggestedFeedsQuery({ enabled }: { enabled?: boolean }) { 17 - const { appview } = useClients(); 17 + const { appview } = getClients(); 18 18 const { data: preferences } = usePreferencesQuery(); 19 19 const savedFeeds = preferences?.savedFeeds; 20 20
+2 -2
src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts
··· 9 9 import { getContentLanguages } from '#/state/preferences/languages'; 10 10 import { STALE } from '#/state/queries'; 11 11 import { usePreferencesQuery } from '#/state/queries/preferences'; 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 24 24 ]; 25 25 26 26 export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) { 27 - const { appview } = useClients(); 27 + const { appview } = getClients(); 28 28 const { data: preferences } = usePreferencesQuery(); 29 29 30 30 return useQuery({
+2 -2
src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts
··· 9 9 import { getContentLanguages } from '#/state/preferences/languages'; 10 10 import { STALE } from '#/state/queries'; 11 11 import { usePreferencesQuery } from '#/state/queries/preferences'; 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 26 26 ]; 27 27 28 28 export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) { 29 - const { appview } = useClients(); 29 + const { appview } = getClients(); 30 30 const { data: preferences } = usePreferencesQuery(); 31 31 32 32 return useQuery({
+2 -2
src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts
··· 9 9 import { getContentLanguages } from '#/state/preferences/languages'; 10 10 import { STALE } from '#/state/queries'; 11 11 import { usePreferencesQuery } from '#/state/queries/preferences'; 12 - import { useClients } from '#/state/session'; 12 + import { getClients } from '#/state/session'; 13 13 14 14 import { logger } from '#/logger'; 15 15 ··· 26 26 }) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit]; 27 27 28 28 export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) { 29 - const { appview } = useClients(); 29 + const { appview } = getClients(); 30 30 const { data: preferences } = usePreferencesQuery(); 31 31 32 32 return useQuery({
+2 -2
src/state/queries/trending/useGetTrendsQuery.ts
··· 12 12 import { getContentLanguages } from '#/state/preferences/languages'; 13 13 import { STALE } from '#/state/queries'; 14 14 import { usePreferencesQuery } from '#/state/queries/preferences'; 15 - import { useClients } from '#/state/session'; 15 + import { getClients } from '#/state/session'; 16 16 17 17 export const DEFAULT_LIMIT = 5; 18 18 19 19 export const createGetTrendsQueryKey = () => ['trends']; 20 20 21 21 export function useGetTrendsQuery() { 22 - const { appview } = useClients(); 22 + const { appview } = getClients(); 23 23 const { data: preferences } = usePreferencesQuery(); 24 24 const keywordFilters = useMemo(() => { 25 25 return (preferences?.moderationPrefs?.mutedWords || []).map((word) => interpretMutedWordPreference(word));
+2 -2
src/state/queries/trending/useTrendingTopics.ts
··· 10 10 11 11 import { STALE } from '#/state/queries'; 12 12 import { usePreferencesQuery } from '#/state/queries/preferences'; 13 - import { useClients } from '#/state/session'; 13 + import { getClients } from '#/state/session'; 14 14 15 15 export type TrendingTopic = AppBskyUnspeccedDefs.TrendingTopic; 16 16 ··· 35 35 export const trendingTopicsQueryKey = ['trending-topics']; 36 36 37 37 export function useTrendingTopics({ enabled = true }: { enabled?: boolean } = {}) { 38 - const { appview } = useClients(); 38 + const { appview } = getClients(); 39 39 const { data: preferences } = usePreferencesQuery(); 40 40 const keywordFilters = useMemo( 41 41 () => (preferences?.moderationPrefs?.mutedWords ?? []).map((word) => interpretMutedWordPreference(word)),
+2 -2
src/state/queries/usePostThread/index.ts
··· 27 27 } from '#/state/queries/usePostThread/types'; 28 28 import { getThreadgateRecord } from '#/state/queries/usePostThread/utils'; 29 29 import * as views from '#/state/queries/usePostThread/views'; 30 - import { useClients, useSession } from '#/state/session'; 30 + import { getClients, useSession } from '#/state/session'; 31 31 import { useMergeThreadgateHiddenReplies } from '#/state/threadgate-hidden-replies'; 32 32 33 33 export * from '#/state/queries/usePostThread/context'; ··· 36 36 37 37 export function usePostThread({ anchor }: { anchor?: ResourceUri }) { 38 38 const qc = useQueryClient(); 39 - const { appview } = useClients(); 39 + const { appview } = getClients(); 40 40 const { hasSession } = useSession(); 41 41 const { gtPhone } = useBreakpoints(); 42 42 const moderationOpts = useModerationOpts();
+2 -2
src/state/queries/useSuggestedStarterPacksQuery.ts
··· 7 7 import { getContentLanguages } from '#/state/preferences/languages'; 8 8 import { STALE } from '#/state/queries'; 9 9 import { usePreferencesQuery } from '#/state/queries/preferences'; 10 - import { useClients } from '#/state/session'; 10 + import { getClients } from '#/state/session'; 11 11 12 12 export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [ 13 13 'suggested-starter-packs', ··· 21 21 enabled?: boolean; 22 22 overrideInterests?: string[]; 23 23 }) { 24 - const { appview } = useClients(); 24 + const { appview } = getClients(); 25 25 const { data: preferences } = usePreferencesQuery(); 26 26 const contentLangs = getContentLanguages().join(','); 27 27
+2 -2
src/state/queries/verification/useUpdateProfileVerificationCache.ts
··· 6 6 import { useQueryClient } from '@tanstack/react-query'; 7 7 8 8 import { updateProfileShadow } from '#/state/cache/profile-shadow'; 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 import { logger } from '#/logger'; 12 12 13 13 /** fetches a fresh verification state from the app view and updates the profile cache. */ 14 14 export function useUpdateProfileVerificationCache() { 15 15 const qc = useQueryClient(); 16 - const { appview } = useClients(); 16 + const { appview } = getClients(); 17 17 18 18 return useCallback( 19 19 async ({ profile }: { profile: AnyProfileView }) => {
+1 -10
src/state/session/agent.ts
··· 1 1 import { type Client, ok } from '@atcute/client'; 2 - import { 3 - finalizeAuthorization, 4 - getSession, 5 - OAuthUserAgent, 6 - type Session, 7 - } from '@atcute/oauth-browser-client'; 8 2 9 3 import { networkRetry } from '#/lib/async/retry'; 10 4 11 5 import { type Clients, createOAuthClients, createPublicClients } from './clients'; 12 6 import { configureModerationForAccount, configureModerationForGuest } from './moderation'; 13 - import { configureAppOAuth } from './oauth'; 7 + import { finalizeAuthorization, getSession, OAuthUserAgent, type Session } from './oauth'; 14 8 import type { SessionAccount } from './types'; 15 9 16 10 export class InactiveAccountError extends Error { ··· 90 84 * @returns the signed-in account and its client set. 91 85 */ 92 86 export async function createOAuthSession(params: URLSearchParams) { 93 - configureAppOAuth(); 94 87 const { session } = await finalizeAuthorization(params); 95 88 return prepareOAuthSession(session); 96 89 } ··· 102 95 * @returns the account and its client set. 103 96 */ 104 97 export async function resumeOAuthSession(storedAccount: SessionAccount) { 105 - configureAppOAuth(); 106 98 const session = await networkRetry(1, () => getSession(storedAccount.did)); 107 99 return prepareOAuthSession(session); 108 100 } ··· 117 109 export async function optimisticOAuthSession( 118 110 storedAccount: SessionAccount, 119 111 ): Promise<{ clients: Clients; validate: () => Promise<SessionAccount> }> { 120 - configureAppOAuth(); 121 112 const session = await getSession(storedAccount.did, { allowStale: true }); 122 113 const oauthAgent = new OAuthUserAgent(session); 123 114 const clients = createOAuthClients(oauthAgent);
+1 -1
src/state/session/clients.ts
··· 1 1 import { Client, simpleFetchHandler } from '@atcute/client'; 2 - import type { OAuthUserAgent } from '@atcute/oauth-browser-client'; 3 2 4 3 import { PUBLIC_BSKY_SERVICE } from '#/lib/constants'; 5 4 ··· 7 6 8 7 import { acceptLabelersHeaderValue } from './labelers'; 9 8 import { createOAuthFetchHandler, type FetchHandler, withNetworkEvents } from './network'; 9 + import type { OAuthUserAgent } from './oauth'; 10 10 11 11 /** 12 12 * xrpc clients backing every network call.
+36 -357
src/state/session/index.tsx
··· 1 - import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; 2 - 3 - import type { Did } from '@atcute/lexicons'; 4 - import { deleteStoredSession, OAuthResponseError, TokenRefreshError } from '@atcute/oauth-browser-client'; 5 - 6 - import { clearPersistedQueryStorage } from '#/lib/persisted-query-storage'; 1 + import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from 'react'; 7 2 8 3 import { sessionDropped } from '#/state/events'; 9 - import type { SessionAccount, SessionApiContext, SessionStateContext } from '#/state/session/types'; 10 - 11 - import { logger } from '#/logger'; 4 + import type { SessionStateContext } from '#/state/session/types'; 12 5 13 6 import { useGlobalDialogsHandleContext } from '#/components/dialogs/Context'; 14 7 15 - import { auth, type AuthSession } from '#/storage'; 16 - 17 - import { 18 - createGuestClients, 19 - createOAuthSession, 20 - InactiveAccountError, 21 - optimisticOAuthSession, 22 - resumeOAuthSession, 23 - } from './agent'; 24 - import type { Clients } from './clients'; 25 - import { IS_OAUTH_CALLBACK, startOAuthSignIn } from './oauth'; 8 + import { dropToGuest, getSnapshot, subscribe } from './store'; 26 9 27 10 export type { SessionAccount } from '#/state/session/types'; 28 - 29 - /** 30 - * resolves the session once at boot, keeping it immutable for the page's lifetime. exceptions that avoid a 31 - * page reload are removing a non-current account, or dropping the current session to logged-out when the 32 - * token cannot be refreshed. 33 - */ 34 - 35 - // `auth` storage notifies in-process listeners on every write. A write made by 36 - // this tab is already reflected locally, so the cross-tab listener latches on 37 - // this flag to react only to writes from *other* tabs. 38 - let isWritingSession = false; 39 - 40 - function writeSession(next: AuthSession) { 41 - isWritingSession = true; 42 - try { 43 - auth.set(['session'], next); 44 - } finally { 45 - isWritingSession = false; 46 - } 47 - } 48 - 49 - /** Returns `accounts` with `account` moved to the front (most recent first). */ 50 - function prependAccount(accounts: SessionAccount[], account: SessionAccount): SessionAccount[] { 51 - return [account, ...accounts.filter((a) => a.did !== account.did)]; 52 - } 53 - 54 - /** Persists a logged-out session, clears the given dids' caches, and reloads at the root route. */ 55 - function signOut({ accounts, clearDids = [] }: { accounts: SessionAccount[]; clearDids?: string[] }) { 56 - for (const did of clearDids) { 57 - void clearPersistedQueryStorage(did); 58 - } 59 - writeSession({ accounts, currentAccountDid: undefined }); 60 - history.pushState(null, '', '/'); 61 - window.location.reload(); 62 - } 63 - 64 - /** 65 - * drops the session to logged-out in place without reloading. persists the logged-out state, swaps in guest 66 - * clients, clears the current account, and marks the boot failed to trigger a session expired toast. 67 - */ 68 - function dropToGuestSession( 69 - accounts: SessionAccount[], 70 - setClients: (clients: Clients) => void, 71 - setCurrentDid: (did: Did | undefined) => void, 72 - setStatus: (status: SessionBootStatus) => void, 73 - ) { 74 - writeSession({ accounts, currentAccountDid: undefined }); 75 - setClients(createGuestClients()); 76 - setCurrentDid(undefined); 77 - setStatus('failed'); 78 - } 79 - 80 - /** Extracts a loggable message from an unknown thrown value. */ 81 - function errorMessage(e: unknown): string { 82 - return e instanceof Error ? e.message : String(e); 83 - } 84 - 85 - /** 86 - * determines if a resume or validation error means the stored session is permanently unusable. 87 - * 88 - * returns true if the token was refused or can no longer be refreshed, and false if the failure is transient 89 - * and the session should be kept. 90 - */ 91 - function isFatalSessionError(e: unknown): boolean { 92 - return ( 93 - e instanceof InactiveAccountError || 94 - e instanceof TokenRefreshError || 95 - (e instanceof OAuthResponseError && (e.status === 400 || e.status === 401)) 96 - ); 97 - } 98 - 99 - /** 100 - * boot lifecycle of the persisted session: - `resuming`: builds the agent from the stored token - 101 - * `validating`: keeps the agent live while the session is checked against the server - `failed`: the stored 102 - * session was rejected - `idle`: no stored session or a fully-settled resume 103 - */ 104 - type SessionBootStatus = 'failed' | 'idle' | 'resuming' | 'validating'; 11 + export { 12 + completeOAuthCallback, 13 + getClients, 14 + login, 15 + logoutCurrentAccount, 16 + logoutEveryAccount, 17 + removeAccount, 18 + switchAccount, 19 + } from './store'; 105 20 106 21 const StateContext = createContext<SessionStateContext>({ 107 22 accounts: [], ··· 112 27 }); 113 28 StateContext.displayName = 'SessionStateContext'; 114 29 115 - const ClientsContext = createContext<Clients | null>(null); 116 - ClientsContext.displayName = 'SessionClientsContext'; 117 - 118 - // Module-level mirror of the current clients, for the rare non-React caller. 119 - // Kept in sync by an effect in `Provider` below. 120 - let currentClients: Clients | null = null; 121 - 122 - const ApiContext = createContext<SessionApiContext>({ 123 - completeOAuthCallback: async () => {}, 124 - login: async () => {}, 125 - logoutCurrentAccount: () => {}, 126 - logoutEveryAccount: () => {}, 127 - removeAccount: () => {}, 128 - switchAccount: async () => {}, 129 - }); 130 - ApiContext.displayName = 'SessionApiContext'; 131 - 132 30 export function Provider({ children }: React.PropsWithChildren<{}>) { 133 - const boot = useMemo(() => readPersistedSession(), []); 134 - const bootAccount = IS_OAUTH_CALLBACK 135 - ? undefined 136 - : boot.accounts.find((a) => a.did === boot.currentAccountDid); 137 - const [accounts, setAccounts] = useState<SessionAccount[]>(boot.accounts); 138 - const [clients, setClients] = useState<Clients>(createGuestClients); 139 - const [currentDid, setCurrentDid] = useState<Did | undefined>(undefined); 140 - const [status, setStatus] = useState<SessionBootStatus>(() => (bootAccount ? 'resuming' : 'idle')); 31 + const snapshot = useSyncExternalStore(subscribe, getSnapshot); 141 32 142 - // Boot: resume the persisted current account exactly once. This is the only 143 - // path that resumes a session — every later account change reloads the page. 144 - // The agent is built from the stored token with no network round trip, so the 145 - // app renders immediately; the session is then validated in the background. 33 + // Boot resume and cross-tab watching run once at module load (see store.ts); the only lifecycle the Provider 34 + // owns is the live-drop listener below, which must react to session state. 35 + // 36 + // A live session dropped mid-use: the stored token can no longer be refreshed. Drop to a logged-out guest 37 + // session in place — no reload. Persisting the logged-out session keeps a reload or another tab from 38 + // resuming it, and clearing the current account remounts the tree as logged out (via the 39 + // `key={currentAccount?.did}` reset in InnerApp), so in-flight requests fail like any other and the UI 40 + // surfaces them; `status` 'failed' raises the "session expired" toast. Held off while the boot resume is 41 + // still settling, where it drops the session itself. 146 42 useEffect(() => { 147 - if (!bootAccount) { 43 + if ( 44 + snapshot.currentDid === undefined || 45 + snapshot.status === 'resuming' || 46 + snapshot.status === 'validating' 47 + ) { 148 48 return; 149 49 } 150 - let cancelled = false; 151 - // Latches the one-shot exit from resuming/validating, so a dropped session 152 - // and a rejected validation can't both act on the same boot. 153 - let settled = false; 154 - 155 - // The stored session is unusable: drop back to a logged-out guest session so 156 - // the next boot doesn't retry it. 157 - const failResume = () => { 158 - if (settled) { 159 - return; 160 - } 161 - settled = true; 162 - dropToGuestSession(boot.accounts, setClients, setCurrentDid, setStatus); 163 - }; 164 - 165 - const resume = async () => { 166 - let resumed: { clients: Clients; validate: () => Promise<SessionAccount> }; 167 - try { 168 - resumed = await optimisticOAuthSession(bootAccount); 169 - } catch (resumeError) { 170 - if (cancelled) { 171 - return; 172 - } 173 - if (!(resumeError instanceof TokenRefreshError)) { 174 - logger.error('session: boot resume failed', { message: errorMessage(resumeError) }); 175 - } 176 - failResume(); 177 - return; 178 - } 179 - if (cancelled) { 180 - return; 181 - } 182 - // The agent is usable from the stored token — render now and validate 183 - // the session against the server in the background. 184 - setClients(resumed.clients); 185 - setCurrentDid(bootAccount.did); 186 - setStatus('validating'); 187 - 188 - // A session dropped by live traffic during validation fails the resume; 189 - // the global dropped-session listener stays off until this settles. 190 - const unlistenDropped = sessionDropped.subscribe(() => { 191 - if (!cancelled) { 192 - failResume(); 193 - } 194 - }); 195 - try { 196 - await resumed.validate(); 197 - } catch (validationError) { 198 - // these catch bindings must stay distinctly named, SWC React Compiler 199 - // seems to have an issue with renaming bindings that shares names. 200 - if (cancelled) { 201 - return; 202 - } 203 - if (isFatalSessionError(validationError)) { 204 - failResume(); 205 - } else { 206 - // A transient failure (e.g. network) — keep the optimistic session; 207 - // live traffic will surface a genuine failure. 208 - logger.error('session: boot validation failed', { message: errorMessage(validationError) }); 209 - } 210 - } finally { 211 - unlistenDropped(); 212 - } 213 - if (!cancelled && !settled) { 214 - settled = true; 215 - setStatus('idle'); 216 - } 217 - }; 218 - 219 - void resume(); 220 - return () => { 221 - cancelled = true; 222 - }; 223 - }, [boot, bootAccount]); 224 - 225 - // Another tab changed the session. Reload only when the change affects the 226 - // account this tab is signed in as — it's no longer the current account, or 227 - // it was removed. Edits to other accounts are left alone. 228 - useEffect(() => { 229 - const sub = auth.addOnValueChangedListener(['session'], () => { 230 - if (isWritingSession) { 231 - return; 232 - } 233 - const next = auth.get(['session']); 234 - const accountChanged = next?.currentAccountDid !== currentDid; 235 - const accountRemoved = 236 - currentDid !== undefined && !(next?.accounts.some((a) => a.did === currentDid) ?? false); 237 - if (accountChanged || accountRemoved) { 238 - window.location.reload(); 239 - } 240 - }); 241 - return () => sub.remove(); 242 - }, [currentDid]); 243 - 244 - // A live session dropped mid-use: the stored token can no longer be refreshed. 245 - // Drop to a logged-out guest session in place — no reload. Persisting the 246 - // logged-out session keeps a reload or another tab from resuming it, and 247 - // clearing the current account remounts the tree as logged out (via the 248 - // `key={currentAccount?.did}` reset in InnerApp), so in-flight requests fail 249 - // like any other and the UI surfaces them; `status` 'failed' raises the 250 - // "session expired" toast. Held off while the boot resume is still settling, 251 - // where it drops the session itself. 252 - useEffect(() => { 253 - if (currentDid === undefined || status === 'resuming' || status === 'validating') { 254 - return; 255 - } 256 - return sessionDropped.subscribe(() => { 257 - dropToGuestSession(accounts, setClients, setCurrentDid, setStatus); 258 - }); 259 - }, [accounts, currentDid, status]); 260 - 261 - const login = useCallback<SessionApiContext['login']>(async ({ identifier }) => { 262 - await startOAuthSignIn({ identifier }); 263 - }, []); 264 - 265 - const completeOAuthCallback = useCallback<SessionApiContext['completeOAuthCallback']>( 266 - async (params) => { 267 - const { account } = await createOAuthSession(params); 268 - writeSession({ 269 - accounts: prependAccount(accounts, account), 270 - currentAccountDid: account.did, 271 - }); 272 - }, 273 - [accounts], 274 - ); 275 - 276 - const switchAccount = useCallback<SessionApiContext['switchAccount']>( 277 - async (account) => { 278 - // Validate the stored session resolves before committing the switch. 279 - await resumeOAuthSession(account); 280 - writeSession({ 281 - accounts: prependAccount(accounts, account), 282 - currentAccountDid: account.did, 283 - }); 284 - history.pushState(null, '', '/'); 285 - window.location.reload(); 286 - }, 287 - [accounts], 288 - ); 289 - 290 - const logoutCurrentAccount = useCallback<SessionApiContext['logoutCurrentAccount']>(() => { 291 - signOut({ accounts, clearDids: currentDid ? [currentDid] : [] }); 292 - }, [accounts, currentDid]); 293 - 294 - const logoutEveryAccount = useCallback<SessionApiContext['logoutEveryAccount']>(() => { 295 - signOut({ accounts, clearDids: accounts.map((a) => a.did) }); 296 - }, [accounts]); 297 - 298 - const removeAccount = useCallback<SessionApiContext['removeAccount']>( 299 - (account) => { 300 - deleteStoredSession(account.did); 301 - void clearPersistedQueryStorage(account.did); 302 - const nextAccounts = accounts.filter((a) => a.did !== account.did); 303 - if (account.did === currentDid) { 304 - // Removing the signed-in account is a sign-out — reload. 305 - signOut({ accounts: nextAccounts }); 306 - } else { 307 - setAccounts(nextAccounts); 308 - writeSession({ accounts: nextAccounts, currentAccountDid: currentDid }); 309 - } 310 - }, 311 - [accounts, currentDid], 312 - ); 50 + return sessionDropped.subscribe(() => dropToGuest()); 51 + }, [snapshot.currentDid, snapshot.status]); 313 52 314 53 const stateContext = useMemo<SessionStateContext>( 315 54 () => ({ 316 - accounts, 317 - currentAccount: accounts.find((a) => a.did === currentDid), 318 - hasSession: !!currentDid, 319 - isSessionResuming: status === 'resuming', 320 - sessionResumeFailed: status === 'failed', 321 - }), 322 - [accounts, currentDid, status], 323 - ); 324 - 325 - const api = useMemo<SessionApiContext>( 326 - () => ({ 327 - completeOAuthCallback, 328 - login, 329 - logoutCurrentAccount, 330 - logoutEveryAccount, 331 - removeAccount, 332 - switchAccount, 55 + accounts: snapshot.accounts, 56 + currentAccount: snapshot.accounts.find((a) => a.did === snapshot.currentDid), 57 + hasSession: !!snapshot.currentDid, 58 + isSessionResuming: snapshot.status === 'resuming', 59 + sessionResumeFailed: snapshot.status === 'failed', 333 60 }), 334 - [completeOAuthCallback, login, logoutCurrentAccount, logoutEveryAccount, removeAccount, switchAccount], 335 - ); 336 - 337 - // Mirror the current clients to module scope so non-React callers can reach them. 338 - useEffect(() => { 339 - currentClients = clients; 340 - }, [clients]); 341 - 342 - return ( 343 - <ClientsContext.Provider value={clients}> 344 - <StateContext.Provider value={stateContext}> 345 - <ApiContext.Provider value={api}>{children}</ApiContext.Provider> 346 - </StateContext.Provider> 347 - </ClientsContext.Provider> 61 + [snapshot], 348 62 ); 349 - } 350 63 351 - /** Reads the persisted session from storage, defaulting to a logged-out session. */ 352 - function readPersistedSession(): AuthSession { 353 - return auth.get(['session']) ?? { accounts: [], currentAccountDid: undefined }; 64 + return <StateContext.Provider value={stateContext}>{children}</StateContext.Provider>; 354 65 } 355 66 356 67 export function useSession() { 357 68 return useContext(StateContext); 358 - } 359 - 360 - export function useSessionApi() { 361 - return useContext(ApiContext); 362 69 } 363 70 364 71 export function useRequireAuth() { ··· 376 83 [hasSession, signinDialogHandle], 377 84 ); 378 85 } 379 - 380 - /** 381 - * The `@atcute/client` clients for the current session. `pds` is `null` while logged out. 382 - * 383 - * @returns the session's clients. 384 - * @throws if called outside `<SessionProvider>`. 385 - */ 386 - export function useClients(): Clients { 387 - const clients = useContext(ClientsContext); 388 - if (!clients) { 389 - throw Error('useClients() must be below <SessionProvider>.'); 390 - } 391 - return clients; 392 - } 393 - 394 - /** 395 - * The `@atcute/client` clients for the current session, for non-React callers. Prefer {@link useClients} 396 - * inside components. 397 - * 398 - * @returns the session's clients. 399 - * @throws if called before `<SessionProvider>` has mounted. 400 - */ 401 - export function getClients(): Clients { 402 - if (!currentClients) { 403 - throw Error('getClients() called before <SessionProvider> mounted.'); 404 - } 405 - return currentClients; 406 - }
+2 -2
src/state/session/network.ts
··· 1 - import type { OAuthUserAgent } from '@atcute/oauth-browser-client'; 2 - 3 1 import { networkConfirmed, networkLost, sessionDropped } from '#/state/events'; 2 + 3 + import type { OAuthUserAgent } from './oauth'; 4 4 5 5 /** 6 6 * A fetch handler shape compatible with both `@atproto/api`'s session manager and `@atcute/client`'s `Client`
+21 -34
src/state/session/oauth.ts
··· 1 + export * from '@atcute/oauth-browser-client'; 2 + 1 3 import { Client, ok, simpleFetchHandler } from '@atcute/client'; 2 4 import type { ActorResolver } from '@atcute/identity-resolver'; 3 5 import type { ActorIdentifier } from '@atcute/lexicons'; ··· 19 21 20 22 const CLIENT_ASSERTION_ENDPOINT = `${new URL(OAUTH_CLIENT_ID).origin}/xrpc/internal.app.getClientAssertion`; 21 23 22 - /** 23 - * fetches a DPoP-bound client assertion from the worker's client-assertion backend to authenticate the SPA as 24 - * a confidential client. 25 - */ 26 24 const fetchClientAssertion: ClientAssertionFetcher = async ({ aud, createDpopProof }) => { 27 25 const { client_assertion } = await ok( 28 26 internalClient.post('internal.app.getClientAssertion', { ··· 37 35 }; 38 36 }; 39 37 40 - let configured = false; 41 - 42 - export function configureAppOAuth() { 43 - if (configured) { 44 - return; 45 - } 46 - 47 - configureOAuth({ 48 - fetchClientAssertion: IS_CONFIDENTIAL_CLIENT ? fetchClientAssertion : undefined, 49 - identityResolver: new SlingshotActorResolver(), 50 - metadata: { 51 - client_id: OAUTH_CLIENT_ID, 52 - redirect_uri: OAUTH_REDIRECT_URI, 53 - }, 54 - }); 55 - configured = true; 56 - } 57 - 58 - export async function startOAuthSignIn({ identifier }: { identifier: ActorIdentifier }) { 59 - configureAppOAuth(); 60 - 61 - const authUrl = await createAuthorizationUrl({ 62 - target: { type: 'account', identifier }, 63 - scope: OAUTH_SCOPE, 64 - }); 65 - 66 - await timeout(200); 67 - window.location.assign(authUrl); 68 - } 69 - 70 38 class SlingshotActorResolver implements ActorResolver { 71 39 private client = new Client({ 72 40 handler: simpleFetchHandler({ service: SLINGSHOT_SERVICE_URL }), ··· 89 57 }; 90 58 } 91 59 } 60 + 61 + configureOAuth({ 62 + fetchClientAssertion: IS_CONFIDENTIAL_CLIENT ? fetchClientAssertion : undefined, 63 + identityResolver: new SlingshotActorResolver(), 64 + metadata: { 65 + client_id: OAUTH_CLIENT_ID, 66 + redirect_uri: OAUTH_REDIRECT_URI, 67 + }, 68 + }); 69 + 70 + export async function startOAuthSignIn({ identifier }: { identifier: ActorIdentifier }) { 71 + const authUrl = await createAuthorizationUrl({ 72 + target: { type: 'account', identifier }, 73 + scope: OAUTH_SCOPE, 74 + }); 75 + 76 + await timeout(200); 77 + window.location.assign(authUrl); 78 + }
+254
src/state/session/store.ts
··· 1 + import type { ActorIdentifier, Did } from '@atcute/lexicons'; 2 + 3 + import { SimpleEventEmitter } from '@mary-ext/simple-event-emitter'; 4 + 5 + import { clearPersistedQueryStorage } from '#/lib/persisted-query-storage'; 6 + 7 + import { sessionDropped } from '#/state/events'; 8 + import type { SessionAccount } from '#/state/session/types'; 9 + 10 + import { logger } from '#/logger'; 11 + 12 + import { auth } from '#/storage'; 13 + 14 + import { 15 + createGuestClients, 16 + createOAuthSession, 17 + InactiveAccountError, 18 + optimisticOAuthSession, 19 + resumeOAuthSession, 20 + } from './agent'; 21 + import type { Clients } from './clients'; 22 + import { 23 + deleteStoredSession, 24 + IS_OAUTH_CALLBACK, 25 + OAuthResponseError, 26 + startOAuthSignIn, 27 + TokenRefreshError, 28 + } from './oauth'; 29 + 30 + // #region snapshot 31 + 32 + export type SessionBootStatus = 'failed' | 'idle' | 'resuming' | 'validating'; 33 + 34 + export type SessionSnapshot = { 35 + accounts: readonly SessionAccount[]; 36 + clients: Clients; 37 + currentAccountDid: Did | undefined; 38 + currentDid: Did | undefined; 39 + status: SessionBootStatus; 40 + }; 41 + 42 + let isWritingSession = false; 43 + let snapshot: SessionSnapshot; 44 + 45 + const emitter = new SimpleEventEmitter<[]>(); 46 + 47 + export function subscribe(onChange: () => void): () => void { 48 + return emitter.subscribe(onChange); 49 + } 50 + 51 + export function getSnapshot(): SessionSnapshot { 52 + return snapshot; 53 + } 54 + 55 + function setSnapshot(patch: Partial<SessionSnapshot>): void { 56 + snapshot = { ...snapshot, ...patch }; 57 + emitter.emit(); 58 + } 59 + 60 + function persistSnapshot(patch: Partial<SessionSnapshot>): void { 61 + const next = { ...snapshot, ...patch }; 62 + isWritingSession = true; 63 + try { 64 + auth.set(['session'], { accounts: [...next.accounts], currentAccountDid: next.currentAccountDid }); 65 + } finally { 66 + isWritingSession = false; 67 + } 68 + 69 + snapshot = next; 70 + emitter.emit(); 71 + } 72 + 73 + // #endregion 74 + 75 + // #region helpers 76 + 77 + function prependAccount(accounts: readonly SessionAccount[], account: SessionAccount): SessionAccount[] { 78 + return [account, ...accounts.filter((a) => a.did !== account.did)]; 79 + } 80 + 81 + function errorMessage(e: unknown): string { 82 + return e instanceof Error ? e.message : String(e); 83 + } 84 + 85 + function isFatalSessionError(e: unknown): boolean { 86 + return ( 87 + e instanceof InactiveAccountError || 88 + e instanceof TokenRefreshError || 89 + (e instanceof OAuthResponseError && (e.status === 400 || e.status === 401)) 90 + ); 91 + } 92 + 93 + export function signOut({ 94 + accounts, 95 + clearDids = [], 96 + }: { 97 + accounts: readonly SessionAccount[]; 98 + clearDids?: readonly string[]; 99 + }): void { 100 + for (const did of clearDids) { 101 + void clearPersistedQueryStorage(did); 102 + } 103 + persistSnapshot({ accounts, currentAccountDid: undefined }); 104 + history.pushState(null, '', '/'); 105 + window.location.reload(); 106 + } 107 + 108 + export function dropToGuest(): void { 109 + persistSnapshot({ 110 + clients: createGuestClients(), 111 + currentAccountDid: undefined, 112 + currentDid: undefined, 113 + status: 'failed', 114 + }); 115 + } 116 + 117 + // #endregion 118 + 119 + // #region api 120 + 121 + export async function login({ identifier }: { identifier: ActorIdentifier }) { 122 + await startOAuthSignIn({ identifier }); 123 + } 124 + 125 + export async function completeOAuthCallback(params: URLSearchParams) { 126 + const { account } = await createOAuthSession(params); 127 + persistSnapshot({ 128 + accounts: prependAccount(snapshot.accounts, account), 129 + currentAccountDid: account.did, 130 + }); 131 + } 132 + 133 + export async function switchAccount(account: SessionAccount) { 134 + // Validate the stored session resolves before committing the switch. 135 + await resumeOAuthSession(account); 136 + persistSnapshot({ 137 + accounts: prependAccount(snapshot.accounts, account), 138 + currentAccountDid: account.did, 139 + }); 140 + history.pushState(null, '', '/'); 141 + window.location.reload(); 142 + } 143 + 144 + export function logoutCurrentAccount() { 145 + signOut({ accounts: snapshot.accounts, clearDids: snapshot.currentDid ? [snapshot.currentDid] : [] }); 146 + } 147 + 148 + export function logoutEveryAccount() { 149 + signOut({ accounts: snapshot.accounts, clearDids: snapshot.accounts.map((a) => a.did) }); 150 + } 151 + 152 + export function removeAccount(account: SessionAccount) { 153 + deleteStoredSession(account.did); 154 + void clearPersistedQueryStorage(account.did); 155 + const nextAccounts = snapshot.accounts.filter((a) => a.did !== account.did); 156 + if (account.did === snapshot.currentDid) { 157 + // Removing the signed-in account is a sign-out — reload. 158 + signOut({ accounts: nextAccounts }); 159 + } else { 160 + persistSnapshot({ accounts: nextAccounts }); 161 + } 162 + } 163 + 164 + // #endregion 165 + 166 + export function getClients() { 167 + return snapshot.clients; 168 + } 169 + 170 + { 171 + const persisted = auth.get(['session']); 172 + 173 + const bootAccount = IS_OAUTH_CALLBACK 174 + ? undefined 175 + : persisted?.accounts.find((a) => a.did === persisted.currentAccountDid); 176 + 177 + snapshot = { 178 + accounts: persisted ? persisted.accounts : [], 179 + clients: createGuestClients(), 180 + currentAccountDid: persisted?.currentAccountDid, 181 + currentDid: undefined, 182 + status: bootAccount ? 'resuming' : 'idle', 183 + }; 184 + 185 + if (bootAccount) { 186 + const account = bootAccount; 187 + let settled = false; 188 + 189 + // The stored session is unusable: drop back to a logged-out guest session so the next boot doesn't retry it. 190 + const failResume = (): void => { 191 + if (settled) { 192 + return; 193 + } 194 + settled = true; 195 + dropToGuest(); 196 + }; 197 + 198 + const resume = async (): Promise<void> => { 199 + let resumed: { clients: Clients; validate: () => Promise<SessionAccount> }; 200 + try { 201 + resumed = await optimisticOAuthSession(account); 202 + } catch (resumeError) { 203 + if (!(resumeError instanceof TokenRefreshError)) { 204 + logger.error('session: boot resume failed', { message: errorMessage(resumeError) }); 205 + } 206 + failResume(); 207 + return; 208 + } 209 + // The agent is usable from the stored token — render now and validate the session against the server 210 + // in the background. 211 + setSnapshot({ clients: resumed.clients, currentDid: account.did, status: 'validating' }); 212 + 213 + // A session dropped by live traffic during validation fails the resume; the global dropped-session 214 + // listener stays off until this settles. 215 + const unlistenDropped = sessionDropped.subscribe(failResume); 216 + try { 217 + await resumed.validate(); 218 + } catch (validationError) { 219 + if (isFatalSessionError(validationError)) { 220 + failResume(); 221 + } else { 222 + // A transient failure (e.g. network) — keep the optimistic session; live traffic will surface a 223 + // genuine failure. 224 + logger.error('session: boot validation failed', { message: errorMessage(validationError) }); 225 + } 226 + } finally { 227 + unlistenDropped(); 228 + } 229 + if (!settled) { 230 + settled = true; 231 + setSnapshot({ status: 'idle' }); 232 + } 233 + }; 234 + 235 + void resume(); 236 + } 237 + } 238 + 239 + auth.addOnValueChangedListener(['session'], () => { 240 + if (isWritingSession) { 241 + return; 242 + } 243 + 244 + const next = auth.get(['session']); 245 + 246 + const accountChanged = next?.currentAccountDid !== snapshot.currentDid; 247 + const accountRemoved = 248 + snapshot.currentDid !== undefined && 249 + !(next?.accounts.some((a) => a.did === snapshot.currentDid) ?? false); 250 + 251 + if (accountChanged || accountRemoved) { 252 + window.location.reload(); 253 + } 254 + });
+1 -16
src/state/session/types.ts
··· 1 - import type { ActorIdentifier } from '@atcute/lexicons'; 2 - 3 1 import type { AuthAccount } from '#/storage'; 4 2 5 3 export type SessionAccount = AuthAccount; 6 4 7 5 export type SessionStateContext = { 8 - accounts: SessionAccount[]; 6 + accounts: readonly SessionAccount[]; 9 7 currentAccount: SessionAccount | undefined; 10 8 hasSession: boolean; 11 9 /** True while the boot-time session resume is in flight. */ ··· 13 11 /** True when the boot-time resume failed because the session was rejected. */ 14 12 sessionResumeFailed: boolean; 15 13 }; 16 - 17 - export type SessionApiContext = { 18 - completeOAuthCallback: (params: URLSearchParams) => Promise<void>; 19 - login: (props: { identifier: ActorIdentifier }) => Promise<void>; 20 - logoutCurrentAccount: () => void; 21 - logoutEveryAccount: () => void; 22 - removeAccount: (account: SessionAccount) => void; 23 - /** 24 - * Validates the account's stored session, persists it as the current account, and reloads the page to apply 25 - * the switch. 26 - */ 27 - switchAccount: (account: SessionAccount) => Promise<void>; 28 - };
+2 -3
src/view/com/auth/OAuthCallback.tsx
··· 2 2 3 3 import { useCallOnce } from '#/lib/once'; 4 4 5 - import { useSessionApi } from '#/state/session'; 5 + import { completeOAuthCallback } from '#/state/session'; 6 6 import { InactiveAccountError } from '#/state/session/agent'; 7 7 8 8 import { logger } from '#/logger'; ··· 17 17 import { m } from '#/paraglide/messages'; 18 18 19 19 export function OAuthCallback() { 20 - const { completeOAuthCallback } = useSessionApi(); 21 20 const [error, setError] = useState(''); 22 21 const runOnce = useCallOnce(); 23 22 ··· 42 41 } 43 42 }); 44 43 }); 45 - }, [runOnce, completeOAuthCallback]); 44 + }, [runOnce]); 46 45 47 46 return ( 48 47 <div className={css.container}>
+2 -2
src/view/com/composer/Composer.tsx
··· 42 42 import { toPostLanguages, useLanguagePrefs, useLanguagePrefsApi } from '#/state/preferences/languages'; 43 43 import { usePreferencesQuery } from '#/state/queries/preferences'; 44 44 import { useProfileQuery } from '#/state/queries/profile'; 45 - import { useClients, useSession } from '#/state/session'; 45 + import { getClients, useSession } from '#/state/session'; 46 46 47 47 import { logger } from '#/logger'; 48 48 ··· 114 114 cancelRef?: React.RefObject<CancelRef | null>; 115 115 }) => { 116 116 const { currentAccount } = useSession(); 117 - const { appview, pds, pdsUrl } = useClients(); 117 + const { appview, pds, pdsUrl } = getClients(); 118 118 const queryClient = useQueryClient(); 119 119 const currentDid = currentAccount!.did; 120 120 const { closeComposer } = useComposerControls();
+5 -5
src/view/com/composer/drafts/state/queries.ts
··· 6 6 import { getDeviceId } from '#/lib/device-id'; 7 7 import { isNetworkError } from '#/lib/strings/errors'; 8 8 9 - import { useClients } from '#/state/session'; 9 + import { getClients } from '#/state/session'; 10 10 11 11 import type { ComposerState } from '#/view/com/composer/state/composer'; 12 12 ··· 18 18 19 19 /** Hook to list all drafts for the current account */ 20 20 export function useDraftsQuery() { 21 - const { appview } = useClients(); 21 + const { appview } = getClients(); 22 22 23 23 return useInfiniteQuery({ 24 24 queryKey: DRAFTS_QUERY_KEY, ··· 86 86 87 87 /** hook to save a draft */ 88 88 export function useSaveDraftMutation() { 89 - const { appview } = useClients(); 89 + const { appview } = getClients(); 90 90 const queryClient = useQueryClient(); 91 91 92 92 return useMutation({ ··· 196 196 197 197 /** Hook to delete a draft. Takes the full draft data to avoid re-fetching for media cleanup. */ 198 198 export function useDeleteDraftMutation() { 199 - const { appview } = useClients(); 199 + const { appview } = getClients(); 200 200 const queryClient = useQueryClient(); 201 201 202 202 return useMutation({ ··· 226 226 * @param originalLocalRefs local media references associated with the draft 227 227 */ 228 228 export function useCleanupPublishedDraftMutation() { 229 - const { appview } = useClients(); 229 + const { appview } = getClients(); 230 230 const queryClient = useQueryClient(); 231 231 232 232 return useMutation({
+2 -2
src/view/com/notifications/NotificationFeedItem.tsx
··· 28 28 import type { FeedNotification } from '#/state/queries/notifications/feed'; 29 29 import { useProfileFollowMutationQueue } from '#/state/queries/profile'; 30 30 import { unstableCacheProfileView } from '#/state/queries/unstable-profile-cache'; 31 - import { useClients, useSession } from '#/state/session'; 31 + import { getClients, useSession } from '#/state/session'; 32 32 33 33 import { logger } from '#/logger'; 34 34 ··· 740 740 } 741 741 742 742 function SayHelloBtn({ profile }: { profile: AppBskyActorDefs.ProfileView }) { 743 - const { chat } = useClients(); 743 + const { chat } = getClients(); 744 744 const { currentAccount } = useSession(); 745 745 const navigate = useNavigate(); 746 746 const [isLoading, setIsLoading] = useState(false);
+1 -2
src/view/shell/desktop/LeftNav.tsx
··· 19 19 import { useUnreadMessageCount } from '#/state/queries/messages/list-conversations'; 20 20 import { useUnreadNotifications } from '#/state/queries/notifications/unread'; 21 21 import { useProfilesQuery } from '#/state/queries/profile'; 22 - import { type SessionAccount, useSession, useSessionApi } from '#/state/session'; 22 + import { logoutEveryAccount, type SessionAccount, useSession } from '#/state/session'; 23 23 24 24 import { NavSignInCard } from '#/view/shell/NavSignInCard'; 25 25 ··· 79 79 80 80 function ProfileCard({ minimal }: { minimal: boolean }) { 81 81 const { currentAccount, accounts } = useSession(); 82 - const { logoutEveryAccount } = useSessionApi(); 83 82 const { isLoading, data } = useProfilesQuery({ 84 83 dids: accounts.map((acc) => acc.did), 85 84 });