Compare commits

...

23 Commits

Author SHA1 Message Date
Eric Bailey d5e0653f62 WIP 2024-04-11 21:15:41 -05:00
Eric Bailey 73fec7fb3c Cleanups 2024-04-11 19:29:26 -05:00
Eric Bailey e07269cca2 Double check and clarify persist handling 2024-04-11 17:04:19 -05:00
Eric Bailey c6e77961fc Re-organize for better clarity 2024-04-11 16:58:08 -05:00
Eric Bailey 9721bbaeb3 Cleanup, feedback 2024-04-11 16:25:00 -05:00
Eric Bailey fd085fd437 Remove todo 2024-04-09 10:56:48 -05:00
Eric Bailey 0d88a212ee Memoize persist handler 2024-04-09 10:56:48 -05:00
Eric Bailey d9d9032456 Remove logs, add user alerts, add TODO 2024-04-09 10:56:48 -05:00
Eric Bailey 1f9679b920 Handle thrown errors much better 2024-04-09 10:56:48 -05:00
Eric Bailey 896e67120c Remove too-strong fallback 2024-04-09 10:56:48 -05:00
Eric Bailey d0ffb90fb3 Pare back persisted state to clarify source of truth 2024-04-09 10:56:48 -05:00
Eric Bailey 5be41c967b Add back local web debug, useful 2024-04-09 10:56:48 -05:00
Eric Bailey dfb0aae97a Better handle init failure 2024-04-09 10:56:48 -05:00
Eric Bailey fd001836ed Some comments 2024-04-09 10:56:48 -05:00
Eric Bailey cbac0a91cf Clean up persistSession 2024-04-09 10:56:48 -05:00
Eric Bailey ef7e2a981e Remove global state manipulation in favor of refreshing the agent 2024-04-09 10:56:47 -05:00
Eric Bailey b8ecb06322 Use resumeSession to get email confirmation data 2024-04-09 10:56:03 -05:00
Eric Bailey 57c2d61931 Use BskyAgent as the source of truth 2024-04-09 10:56:03 -05:00
Eric Bailey 5d9e48308d Remove redundant call 2024-04-09 10:56:03 -05:00
Eric Bailey e4e5bfb4c9 Cleanup 2024-04-09 10:56:03 -05:00
Eric Bailey de370bd2cc Align on what happens when expired 2024-04-09 10:56:03 -05:00
Eric Bailey 0ba76deb0c Apply expired logic 2024-04-09 10:56:03 -05:00
Eric Bailey cd5b555b08 Strip back 2024-04-09 10:56:03 -05:00
28 changed files with 809 additions and 644 deletions
+7 -7
View File
@@ -16,8 +16,13 @@ import {useQueryClient} from '@tanstack/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {init as initPersistedState} from '#/state/persisted'
import * as persisted from '#/state/persisted'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {
Provider as SessionProvider,
useSession,
useSessionApi,
} from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
import {useNotificationsListener} from 'lib/notifications/notifications'
@@ -31,11 +36,6 @@ import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import {
Provider as SessionProvider,
useSession,
useSessionApi,
} from 'state/session'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
@@ -66,7 +66,7 @@ function InnerApp() {
Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
})
const account = persisted.get('session').currentAccount
const account = readLastActiveAccount()
resumeSession(account)
}, [resumeSession, _])
+7 -7
View File
@@ -7,8 +7,13 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {init as initPersistedState} from '#/state/persisted'
import * as persisted from '#/state/persisted'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {
Provider as SessionProvider,
useSession,
useSessionApi,
} from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {QueryProvider} from 'lib/react-query'
import {ThemeProvider} from 'lib/ThemeContext'
@@ -19,11 +24,6 @@ import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import {
Provider as SessionProvider,
useSession,
useSessionApi,
} from 'state/session'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
@@ -42,7 +42,7 @@ function InnerApp() {
// init
useEffect(() => {
const account = persisted.get('session').currentAccount
const account = readLastActiveAccount()
resumeSession(account)
}, [resumeSession])
+1
View File
@@ -4,6 +4,7 @@ export const LOCAL_DEV_SERVICE =
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
export const STAGING_SERVICE = 'https://staging.bsky.dev'
export const BSKY_SERVICE = 'https://bsky.social'
export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
export const DEFAULT_SERVICE = BSKY_SERVICE
const HELP_DESK_LANG = 'en-us'
export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
+12 -6
View File
@@ -1,11 +1,12 @@
import {useCallback} from 'react'
import {isWeb} from '#/platform/detection'
import {useAnalytics} from '#/lib/analytics/analytics'
import {useSessionApi, SessionAccount} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {useCloseAllActiveElements} from '#/state/util'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {SessionAccount, useSessionApi} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import * as Toast from '#/view/com/util/Toast'
import {LogEvents} from '../statsig/statsig'
export function useAccountSwitcher() {
@@ -44,9 +45,14 @@ export function useAccountSwitcher() {
'circle-exclamation',
)
}
} catch (e) {
Toast.show('Sorry! We need you to enter your password.')
} catch (e: any) {
logger.error(`switch account: selectAccount failed`, {
message: e.message,
})
clearCurrentAccount() // back user out to login
setTimeout(() => {
Toast.show('Sorry! We need you to enter your password.')
}, 100)
}
},
[
+17 -9
View File
@@ -5,6 +5,7 @@ import {useLingui} from '@lingui/react'
import {useAnalytics} from '#/lib/analytics/analytics'
import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {SessionAccount, useSession, useSessionApi} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import * as Toast from '#/view/com/util/Toast'
@@ -38,15 +39,22 @@ export const ChooseAccountForm = ({
setShowLoggedOut(false)
Toast.show(_(msg`Already signed in as @${account.handle}`))
} else {
await initSession(account)
logEvent('account:loggedIn', {
logContext: 'ChooseAccountForm',
withPassword: false,
})
track('Sign In', {resumedSession: true})
setTimeout(() => {
Toast.show(_(msg`Signed in as @${account.handle}`))
}, 100)
try {
await initSession(account)
logEvent('account:loggedIn', {
logContext: 'ChooseAccountForm',
withPassword: false,
})
track('Sign In', {resumedSession: true})
setTimeout(() => {
Toast.show(_(msg`Signed in as @${account.handle}`))
}, 100)
} catch (e: any) {
logger.error('choose account: initSession failed', {
message: e.message,
})
onSelectAccount(account)
}
}
} else {
onSelectAccount(account)
+7 -6
View File
@@ -1,11 +1,12 @@
import EventEmitter from 'eventemitter3'
import {logger} from '#/logger'
import {defaults, Schema} from '#/state/persisted/schema'
import {migrate} from '#/state/persisted/legacy'
import * as store from '#/state/persisted/store'
import BroadcastChannel from '#/lib/broadcast'
export type {Schema, PersistedAccount} from '#/state/persisted/schema'
import BroadcastChannel from '#/lib/broadcast'
import {logger} from '#/logger'
import {migrate} from '#/state/persisted/legacy'
import {defaults, Schema} from '#/state/persisted/schema'
import * as store from '#/state/persisted/store'
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
export {defaults} from '#/state/persisted/schema'
const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
+19 -2
View File
@@ -1,9 +1,13 @@
import {z} from 'zod'
import {deviceLocales} from '#/platform/detection'
const externalEmbedOptions = ['show', 'hide'] as const
// only data needed for rendering account page
/**
* A account persisted to storage. Stored in the `accounts[]` array. Contains
* base account info and access tokens.
*/
const accountSchema = z.object({
service: z.string(),
did: z.string(),
@@ -16,12 +20,25 @@ const accountSchema = z.object({
})
export type PersistedAccount = z.infer<typeof accountSchema>
/**
* The current account. Stored in the `currentAccount` field.
*
* In previous versions, this included tokens and other info. Now, it's used
* only to reference the `did` field, and all other fields are marked as
* optional. They should be considered deprecated and not used, but are kept
* here for backwards compat.
*/
const currentAccountScheme = accountSchema.extend({
service: z.string().optional(),
handle: z.string().optional(),
})
export const schema = z.object({
colorMode: z.enum(['system', 'light', 'dark']),
darkTheme: z.enum(['dim', 'dark']).optional(),
session: z.object({
accounts: z.array(accountSchema),
currentAccount: accountSchema.optional(),
currentAccount: currentAccountScheme.optional(),
}),
reminders: z.object({
lastEmailConfirm: z.string().optional(),
+14 -7
View File
@@ -2,22 +2,29 @@ import {AppBskyActorDefs} from '@atproto/api'
import {QueryClient, useQuery} from '@tanstack/react-query'
import {STALE} from '#/state/queries'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const RQKEY_ROOT = 'actor-search'
export const RQKEY = (prefix: string) => [RQKEY_ROOT, prefix]
export const RQKEY = (query: string) => [RQKEY_ROOT, query]
export function useActorSearch(prefix: string) {
export function useActorSearch({
query,
enabled,
}: {
query: string
enabled?: boolean
}) {
const agent = useAgent()
return useQuery<AppBskyActorDefs.ProfileView[]>({
staleTime: STALE.MINUTES.ONE,
queryKey: RQKEY(prefix || ''),
queryKey: RQKEY(query || ''),
async queryFn() {
const res = await getAgent().searchActors({
q: prefix,
const res = await agent.searchActors({
q: query,
})
return res.data.actors
},
enabled: !!prefix,
enabled: enabled && !!query,
})
}
+3 -1
View File
@@ -1,7 +1,9 @@
import {BskyAgent} from '@atproto/api'
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
export const PUBLIC_BSKY_AGENT = new BskyAgent({
service: 'https://public.api.bsky.app',
service: PUBLIC_BSKY_SERVICE,
})
export const STALE = {
+10 -6
View File
@@ -5,7 +5,7 @@ import {z} from 'zod'
import {labelersDetailedInfoQueryKeyRoot} from '#/lib/react-query'
import {STALE} from '#/state/queries'
import {preferencesQueryKey} from '#/state/queries/preferences'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const labelerInfoQueryKeyRoot = 'labeler-info'
export const labelerInfoQueryKey = (did: string) => [
@@ -31,11 +31,12 @@ export function useLabelerInfoQuery({
did?: string
enabled?: boolean
}) {
const agent = useAgent()
return useQuery({
enabled: !!did && enabled !== false,
queryKey: labelerInfoQueryKey(did as string),
queryFn: async () => {
const res = await getAgent().app.bsky.labeler.getServices({
const res = await agent.app.bsky.labeler.getServices({
dids: [did as string],
detailed: true,
})
@@ -45,24 +46,26 @@ export function useLabelerInfoQuery({
}
export function useLabelersInfoQuery({dids}: {dids: string[]}) {
const agent = useAgent()
return useQuery({
enabled: !!dids.length,
queryKey: labelersInfoQueryKey(dids),
queryFn: async () => {
const res = await getAgent().app.bsky.labeler.getServices({dids})
const res = await agent.app.bsky.labeler.getServices({dids})
return res.data.views as AppBskyLabelerDefs.LabelerView[]
},
})
}
export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
const agent = useAgent()
return useQuery({
enabled: !!dids.length,
queryKey: labelersDetailedInfoQueryKey(dids),
gcTime: 1000 * 60 * 60 * 6, // 6 hours
staleTime: STALE.MINUTES.ONE,
queryFn: async () => {
const res = await getAgent().app.bsky.labeler.getServices({
const res = await agent.app.bsky.labeler.getServices({
dids,
detailed: true,
})
@@ -73,6 +76,7 @@ export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
export function useLabelerSubscriptionMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
@@ -83,9 +87,9 @@ export function useLabelerSubscriptionMutation() {
}).parse({did, subscribe})
if (subscribe) {
await getAgent().addLabeler(did)
await agent.addLabeler(did)
} else {
await getAgent().removeLabeler(did)
await agent.removeLabeler(did)
}
},
onSuccess() {
+3 -2
View File
@@ -7,7 +7,7 @@ import {
} from '@tanstack/react-query'
import {STALE} from '#/state/queries'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const PAGE_SIZE = 30
type RQPageParam = string | undefined
@@ -16,6 +16,7 @@ const RQKEY_ROOT = 'list-members'
export const RQKEY = (uri: string) => [RQKEY_ROOT, uri]
export function useListMembersQuery(uri: string) {
const agent = useAgent()
return useInfiniteQuery<
AppBskyGraphGetList.OutputSchema,
Error,
@@ -26,7 +27,7 @@ export function useListMembersQuery(uri: string) {
staleTime: STALE.MINUTES.ONE,
queryKey: RQKEY(uri),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().app.bsky.graph.getList({
const res = await agent.app.bsky.graph.getList({
list: uri,
limit: PAGE_SIZE,
cursor: pageParam,
+3 -2
View File
@@ -6,13 +6,14 @@ import {
useInfiniteQuery,
} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const RQKEY_ROOT = 'my-blocked-accounts'
export const RQKEY = () => [RQKEY_ROOT]
type RQPageParam = string | undefined
export function useMyBlockedAccountsQuery() {
const agent = useAgent()
return useInfiniteQuery<
AppBskyGraphGetBlocks.OutputSchema,
Error,
@@ -22,7 +23,7 @@ export function useMyBlockedAccountsQuery() {
>({
queryKey: RQKEY(),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().app.bsky.graph.getBlocks({
const res = await agent.app.bsky.graph.getBlocks({
limit: 30,
cursor: pageParam,
})
+3 -2
View File
@@ -6,13 +6,14 @@ import {
useInfiniteQuery,
} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const RQKEY_ROOT = 'my-muted-accounts'
export const RQKEY = () => [RQKEY_ROOT]
type RQPageParam = string | undefined
export function useMyMutedAccountsQuery() {
const agent = useAgent()
return useInfiniteQuery<
AppBskyGraphGetMutes.OutputSchema,
Error,
@@ -22,7 +23,7 @@ export function useMyMutedAccountsQuery() {
>({
queryKey: RQKEY(),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().app.bsky.graph.getMutes({
const res = await agent.app.bsky.graph.getMutes({
limit: 30,
cursor: pageParam,
})
+3 -2
View File
@@ -19,7 +19,7 @@ import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {logger} from '#/logger'
import {STALE} from '#/state/queries'
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
import {AuthorFeedAPI} from 'lib/api/feed/author'
import {CustomFeedAPI} from 'lib/api/feed/custom'
import {FollowingFeedAPI} from 'lib/api/feed/following'
@@ -111,6 +111,7 @@ export function usePostFeedQuery(
result: InfiniteData<FeedPage>
} | null>(null)
const lastPageCountRef = useRef(0)
const agent = useAgent()
// Make sure this doesn't invalidate unless really needed.
const selectArgs = React.useMemo(
@@ -153,7 +154,7 @@ export function usePostFeedQuery(
* moderations happen later, which results in some posts being shown and
* some not.
*/
if (!getAgent().session) {
if (!agent.session) {
assertSomePostsPassModeration(res.feed)
}
+3 -2
View File
@@ -6,7 +6,7 @@ import {
useInfiniteQuery,
} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const PAGE_SIZE = 30
type RQPageParam = string | undefined
@@ -16,6 +16,7 @@ const RQKEY_ROOT = 'liked-by'
export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]
export function useLikedByQuery(resolvedUri: string | undefined) {
const agent = useAgent()
return useInfiniteQuery<
AppBskyFeedGetLikes.OutputSchema,
Error,
@@ -25,7 +26,7 @@ export function useLikedByQuery(resolvedUri: string | undefined) {
>({
queryKey: RQKEY(resolvedUri || ''),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().getLikes({
const res = await agent.getLikes({
uri: resolvedUri || '',
limit: PAGE_SIZE,
cursor: pageParam,
+3 -2
View File
@@ -6,7 +6,7 @@ import {
useInfiniteQuery,
} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const PAGE_SIZE = 30
type RQPageParam = string | undefined
@@ -16,6 +16,7 @@ const RQKEY_ROOT = 'post-reposted-by'
export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]
export function usePostRepostedByQuery(resolvedUri: string | undefined) {
const agent = useAgent()
return useInfiniteQuery<
AppBskyFeedGetRepostedBy.OutputSchema,
Error,
@@ -25,7 +26,7 @@ export function usePostRepostedByQuery(resolvedUri: string | undefined) {
>({
queryKey: RQKEY(resolvedUri || ''),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().getRepostedBy({
const res = await agent.getRepostedBy({
uri: resolvedUri || '',
limit: PAGE_SIZE,
cursor: pageParam,
+3 -2
View File
@@ -7,7 +7,7 @@ import {
import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from './notifications/feed'
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from './post-feed'
import {precacheThreadPostProfiles} from './profile'
@@ -65,11 +65,12 @@ export type ThreadNode =
export function usePostThreadQuery(uri: string | undefined) {
const queryClient = useQueryClient()
const agent = useAgent()
return useQuery<ThreadNode, Error>({
gcTime: 0,
queryKey: RQKEY(uri || ''),
async queryFn() {
const res = await getAgent().getPostThread({uri: uri!})
const res = await agent.getPostThread({uri: uri!})
if (res.success) {
const nodes = responseToThreadNodes(res.data.thread)
precacheThreadPostProfiles(queryClient, nodes)
+32 -18
View File
@@ -22,7 +22,7 @@ import {
ThreadViewPreferences,
UsePreferencesQueryResponse,
} from '#/state/queries/preferences/types'
import {getAgent, useSession} from '#/state/session'
import {useAgent, useSession} from '#/state/session'
import {saveLabelers} from '#/state/session/agent-config'
export * from '#/state/queries/preferences/const'
@@ -33,14 +33,13 @@ const preferencesQueryKeyRoot = 'getPreferences'
export const preferencesQueryKey = [preferencesQueryKeyRoot]
export function usePreferencesQuery() {
const agent = useAgent()
return useQuery({
staleTime: STALE.SECONDS.FIFTEEN,
structuralSharing: true,
refetchOnWindowFocus: true,
queryKey: preferencesQueryKey,
queryFn: async () => {
const agent = getAgent()
if (agent.session?.did === undefined) {
return DEFAULT_LOGGED_OUT_PREFERENCES
} else {
@@ -118,10 +117,11 @@ export function useModerationOpts() {
export function useClearPreferencesMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
mutationFn: async () => {
await getAgent().app.bsky.actor.putPreferences({preferences: []})
await agent.app.bsky.actor.putPreferences({preferences: []})
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -132,6 +132,7 @@ export function useClearPreferencesMutation() {
export function usePreferencesSetContentLabelMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<
void,
@@ -139,7 +140,7 @@ export function usePreferencesSetContentLabelMutation() {
{label: string; visibility: LabelPreference; labelerDid: string | undefined}
>({
mutationFn: async ({label, visibility, labelerDid}) => {
await getAgent().setContentLabelPref(label, visibility, labelerDid)
await agent.setContentLabelPref(label, visibility, labelerDid)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -150,6 +151,7 @@ export function usePreferencesSetContentLabelMutation() {
export function useSetContentLabelMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
mutationFn: async ({
@@ -161,7 +163,7 @@ export function useSetContentLabelMutation() {
visibility: LabelPreference
labelerDid?: string
}) => {
await getAgent().setContentLabelPref(label, visibility, labelerDid)
await agent.setContentLabelPref(label, visibility, labelerDid)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -172,10 +174,11 @@ export function useSetContentLabelMutation() {
export function usePreferencesSetAdultContentMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {enabled: boolean}>({
mutationFn: async ({enabled}) => {
await getAgent().setAdultContentEnabled(enabled)
await agent.setAdultContentEnabled(enabled)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -186,10 +189,11 @@ export function usePreferencesSetAdultContentMutation() {
export function usePreferencesSetBirthDateMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {birthDate: Date}>({
mutationFn: async ({birthDate}: {birthDate: Date}) => {
await getAgent().setPersonalDetails({birthDate: birthDate.toISOString()})
await agent.setPersonalDetails({birthDate: birthDate.toISOString()})
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -200,10 +204,11 @@ export function usePreferencesSetBirthDateMutation() {
export function useSetFeedViewPreferencesMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, Partial<BskyFeedViewPreference>>({
mutationFn: async prefs => {
await getAgent().setFeedViewPrefs('home', prefs)
await agent.setFeedViewPrefs('home', prefs)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -214,10 +219,11 @@ export function useSetFeedViewPreferencesMutation() {
export function useSetThreadViewPreferencesMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, Partial<ThreadViewPreferences>>({
mutationFn: async prefs => {
await getAgent().setThreadViewPrefs(prefs)
await agent.setThreadViewPrefs(prefs)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -228,6 +234,7 @@ export function useSetThreadViewPreferencesMutation() {
export function useSetSaveFeedsMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<
void,
@@ -235,7 +242,7 @@ export function useSetSaveFeedsMutation() {
Pick<UsePreferencesQueryResponse['feeds'], 'saved' | 'pinned'>
>({
mutationFn: async ({saved, pinned}) => {
await getAgent().setSavedFeeds(saved, pinned)
await agent.setSavedFeeds(saved, pinned)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -246,10 +253,11 @@ export function useSetSaveFeedsMutation() {
export function useSaveFeedMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {uri: string}>({
mutationFn: async ({uri}) => {
await getAgent().addSavedFeed(uri)
await agent.addSavedFeed(uri)
track('CustomFeed:Save')
// triggers a refetch
await queryClient.invalidateQueries({
@@ -261,10 +269,11 @@ export function useSaveFeedMutation() {
export function useRemoveFeedMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {uri: string}>({
mutationFn: async ({uri}) => {
await getAgent().removeSavedFeed(uri)
await agent.removeSavedFeed(uri)
track('CustomFeed:Unsave')
// triggers a refetch
await queryClient.invalidateQueries({
@@ -276,10 +285,11 @@ export function useRemoveFeedMutation() {
export function usePinFeedMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {uri: string}>({
mutationFn: async ({uri}) => {
await getAgent().addPinnedFeed(uri)
await agent.addPinnedFeed(uri)
track('CustomFeed:Pin', {uri})
// triggers a refetch
await queryClient.invalidateQueries({
@@ -291,10 +301,11 @@ export function usePinFeedMutation() {
export function useUnpinFeedMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, unknown, {uri: string}>({
mutationFn: async ({uri}) => {
await getAgent().removePinnedFeed(uri)
await agent.removePinnedFeed(uri)
track('CustomFeed:Unpin', {uri})
// triggers a refetch
await queryClient.invalidateQueries({
@@ -306,10 +317,11 @@ export function useUnpinFeedMutation() {
export function useUpsertMutedWordsMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => {
await getAgent().upsertMutedWords(mutedWords)
await agent.upsertMutedWords(mutedWords)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -320,10 +332,11 @@ export function useUpsertMutedWordsMutation() {
export function useUpdateMutedWordMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
await getAgent().updateMutedWord(mutedWord)
await agent.updateMutedWord(mutedWord)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
@@ -334,10 +347,11 @@ export function useUpdateMutedWordMutation() {
export function useRemoveMutedWordMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation({
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
await getAgent().removeMutedWord(mutedWord)
await agent.removeMutedWord(mutedWord)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
+3 -2
View File
@@ -6,7 +6,7 @@ import {
useInfiniteQuery,
} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const PAGE_SIZE = 30
type RQPageParam = string | undefined
@@ -15,6 +15,7 @@ const RQKEY_ROOT = 'profile-followers'
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
export function useProfileFollowersQuery(did: string | undefined) {
const agent = useAgent()
return useInfiniteQuery<
AppBskyGraphGetFollowers.OutputSchema,
Error,
@@ -24,7 +25,7 @@ export function useProfileFollowersQuery(did: string | undefined) {
>({
queryKey: RQKEY(did || ''),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().app.bsky.graph.getFollowers({
const res = await agent.app.bsky.graph.getFollowers({
actor: did || '',
limit: PAGE_SIZE,
cursor: pageParam,
+3 -2
View File
@@ -7,7 +7,7 @@ import {
} from '@tanstack/react-query'
import {STALE} from '#/state/queries'
import {getAgent} from '#/state/session'
import {useAgent} from '#/state/session'
const PAGE_SIZE = 30
type RQPageParam = string | undefined
@@ -17,6 +17,7 @@ const RQKEY_ROOT = 'profile-follows'
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
export function useProfileFollowsQuery(did: string | undefined) {
const agent = useAgent()
return useInfiniteQuery<
AppBskyGraphGetFollows.OutputSchema,
Error,
@@ -27,7 +28,7 @@ export function useProfileFollowsQuery(did: string | undefined) {
staleTime: STALE.MINUTES.ONE,
queryKey: RQKEY(did || ''),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await getAgent().app.bsky.graph.getFollows({
const res = await agent.app.bsky.graph.getFollows({
actor: did || '',
limit: PAGE_SIZE,
cursor: pageParam,
+22 -15
View File
@@ -25,7 +25,7 @@ import {Shadow} from '#/state/cache/types'
import {STALE} from '#/state/queries'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {updateProfileShadow} from '../cache/profile-shadow'
import {getAgent, useSession} from '../session'
import {getAgent, useAgent, useSession} from '../session'
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
import {ThreadNode} from './post-thread'
@@ -53,6 +53,7 @@ export function useProfileQuery({
staleTime?: number
}) {
const queryClient = useQueryClient()
const agent = useAgent()
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
// WARNING
// this staleTime is load-bearing
@@ -62,7 +63,7 @@ export function useProfileQuery({
refetchOnWindowFocus: true,
queryKey: RQKEY(did ?? ''),
queryFn: async () => {
const res = await getAgent().getProfile({actor: did ?? ''})
const res = await agent.getProfile({actor: did ?? ''})
return res.data
},
placeholderData: () => {
@@ -77,11 +78,12 @@ export function useProfileQuery({
}
export function useProfilesQuery({handles}: {handles: string[]}) {
const agent = useAgent()
return useQuery({
staleTime: STALE.MINUTES.FIVE,
queryKey: profilesQueryKey(handles),
queryFn: async () => {
const res = await getAgent().getProfiles({actors: handles})
const res = await agent.getProfiles({actors: handles})
return res.data
},
})
@@ -89,17 +91,18 @@ export function useProfilesQuery({handles}: {handles: string[]}) {
export function usePrefetchProfileQuery() {
const queryClient = useQueryClient()
const agent = useAgent()
const prefetchProfileQuery = useCallback(
(did: string) => {
queryClient.prefetchQuery({
queryKey: RQKEY(did),
queryFn: async () => {
const res = await getAgent().getProfile({actor: did || ''})
const res = await agent.getProfile({actor: did || ''})
return res.data
},
})
},
[queryClient],
[queryClient, agent],
)
return prefetchProfileQuery
}
@@ -115,6 +118,7 @@ interface ProfileUpdateParams {
}
export function useProfileUpdateMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, Error, ProfileUpdateParams>({
mutationFn: async ({
profile,
@@ -123,7 +127,7 @@ export function useProfileUpdateMutation() {
newUserBanner,
checkCommitted,
}) => {
await getAgent().upsertProfile(async existing => {
await agent.upsertProfile(async existing => {
existing = existing || {}
if (typeof updates === 'function') {
existing = updates(existing)
@@ -254,7 +258,7 @@ function useProfileFollowMutation(
logContext: LogEvents['profile:follow']['logContext'],
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
) {
const {currentAccount} = useSession()
const {currentAccount, agent} = useSession()
const queryClient = useQueryClient()
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
mutationFn: async ({did}) => {
@@ -270,7 +274,7 @@ function useProfileFollowMutation(
followeeClout: toClout(profile.followersCount),
followerClout: toClout(ownProfile?.followersCount),
})
return await getAgent().follow(did)
return await agent.follow(did)
},
onSuccess(data, variables) {
track('Profile:Follow', {username: variables.did})
@@ -281,11 +285,12 @@ function useProfileFollowMutation(
function useProfileUnfollowMutation(
logContext: LogEvents['profile:unfollow']['logContext'],
) {
const agent = useAgent()
return useMutation<void, Error, {did: string; followUri: string}>({
mutationFn: async ({followUri}) => {
logEvent('profile:unfollow', {logContext})
track('Profile:Unfollow', {username: followUri})
return await getAgent().deleteFollow(followUri)
return await agent.deleteFollow(followUri)
},
})
}
@@ -341,9 +346,10 @@ export function useProfileMuteMutationQueue(
function useProfileMuteMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, Error, {did: string}>({
mutationFn: async ({did}) => {
await getAgent().mute(did)
await agent.mute(did)
},
onSuccess() {
queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -353,9 +359,10 @@ function useProfileMuteMutation() {
function useProfileUnmuteMutation() {
const queryClient = useQueryClient()
const agent = useAgent()
return useMutation<void, Error, {did: string}>({
mutationFn: async ({did}) => {
await getAgent().unmute(did)
await agent.unmute(did)
},
onSuccess() {
queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -418,14 +425,14 @@ export function useProfileBlockMutationQueue(
}
function useProfileBlockMutation() {
const {currentAccount} = useSession()
const {currentAccount, agent} = useSession()
const queryClient = useQueryClient()
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
mutationFn: async ({did}) => {
if (!currentAccount) {
throw new Error('Not signed in')
}
return await getAgent().app.bsky.graph.block.create(
return await agent.app.bsky.graph.block.create(
{repo: currentAccount.did},
{subject: did, createdAt: new Date().toISOString()},
)
@@ -438,7 +445,7 @@ function useProfileBlockMutation() {
}
function useProfileUnblockMutation() {
const {currentAccount} = useSession()
const {currentAccount, agent} = useSession()
const queryClient = useQueryClient()
return useMutation<void, Error, {did: string; blockUri: string}>({
mutationFn: async ({blockUri}) => {
@@ -446,7 +453,7 @@ function useProfileUnblockMutation() {
throw new Error('Not signed in')
}
const {rkey} = new AtUri(blockUri)
await getAgent().app.bsky.graph.block.delete({
await agent.app.bsky.graph.block.delete({
repo: currentAccount.did,
rkey,
})
+10 -9
View File
@@ -16,7 +16,7 @@ import {
import {STALE} from '#/state/queries'
import {useModerationOpts} from '#/state/queries/preferences'
import {getAgent, useSession} from '#/state/session'
import {useAgent, useSession} from '#/state/session'
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
const suggestedFollowsQueryKey = [suggestedFollowsQueryKeyRoot]
@@ -28,7 +28,7 @@ const suggestedFollowsByActorQueryKey = (did: string) => [
]
export function useSuggestedFollowsQuery() {
const {currentAccount} = useSession()
const {agent, currentAccount} = useSession()
const moderationOpts = useModerationOpts()
return useInfiniteQuery<
@@ -42,7 +42,7 @@ export function useSuggestedFollowsQuery() {
staleTime: STALE.HOURS.ONE,
queryKey: suggestedFollowsQueryKey,
queryFn: async ({pageParam}) => {
const res = await getAgent().app.bsky.actor.getSuggestions({
const res = await agent.app.bsky.actor.getSuggestions({
limit: 25,
cursor: pageParam,
})
@@ -79,10 +79,11 @@ export function useSuggestedFollowsQuery() {
}
export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
const agent = useAgent()
return useQuery<AppBskyGraphGetSuggestedFollowsByActor.OutputSchema, Error>({
queryKey: suggestedFollowsByActorQueryKey(did),
queryFn: async () => {
const res = await getAgent().app.bsky.graph.getSuggestedFollowsByActor({
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
actor: did,
})
return res.data
@@ -92,6 +93,7 @@ export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
export function useGetSuggestedFollowersByActor() {
const queryClient = useQueryClient()
const agent = useAgent()
return React.useCallback(
async (actor: string) => {
@@ -99,17 +101,16 @@ export function useGetSuggestedFollowersByActor() {
staleTime: STALE.MINUTES.ONE,
queryKey: suggestedFollowsByActorQueryKey(actor),
queryFn: async () => {
const res =
await getAgent().app.bsky.graph.getSuggestedFollowsByActor({
actor: actor,
})
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
actor: actor,
})
return res.data
},
})
return res
},
[queryClient],
[queryClient, agent],
)
}
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
import {BskyAgent} from '@atproto/api'
import {LogEvents} from '#/lib/statsig/statsig'
import {PersistedAccount} from '#/state/persisted'
/**
* Alias for `PersistedAccount` from persisted storage.
*/
export type SessionAccount = PersistedAccount
/**
* Subset of `SessionAccount` that excludes tokens.
*/
export type CurrentAccount = Omit<SessionAccount, 'accessJwt' | 'refreshJwt'>
/**
* Context shape returned from `useSession()`
*/
export type SessionStateContext = {
agent: BskyAgent
isInitialLoad: boolean
isSwitchingAccounts: boolean
hasSession: boolean
accounts: SessionAccount[]
/**
* Contains the full account object persisted to storage, minus access
* tokens.
*/
currentAccount: CurrentAccount | undefined
}
/**
* Context shape returned from `useSessionApi()`
*/
export type SessionApiContext = {
createAccount: (props: {
service: string
email: string
password: string
handle: string
inviteCode?: string
verificationPhone?: string
verificationCode?: string
}) => Promise<void>
login: (
props: {
service: string
identifier: string
password: string
},
logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void>
/**
* A full logout. Clears the `currentAccount` from session, AND removes
* access tokens from all accounts, so that returning as any user will
* require a full login.
*/
logout: (
logContext: LogEvents['account:loggedOut']['logContext'],
) => Promise<void>
/**
* A partial logout. Clears the `currentAccount` from session, but DOES NOT
* clear access tokens from accounts, allowing the user to return to their
* other accounts without logging in.
*
* Used when adding a new account, deleting an account.
*/
clearCurrentAccount: () => void
initSession: (account: SessionAccount) => Promise<void>
resumeSession: (account?: SessionAccount) => Promise<void>
removeAccount: (account: SessionAccount) => void
selectAccount: (
account: SessionAccount,
logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void>
/**
* Refreshes the BskyAgent's session and derive a fresh `currentAccount`
*/
refreshSession: () => void
}
+179
View File
@@ -0,0 +1,179 @@
import {BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
import {jwtDecode} from 'jwt-decode'
import {IS_TEST_USER} from '#/lib/constants'
import {hasProp} from '#/lib/type-guards'
import {logger} from '#/logger'
import * as persisted from '#/state/persisted'
import {readLabelers} from '#/state/session/agent-config'
import {SessionAccount, SessionApiContext} from '#/state/session/types'
export function isSessionDeactivated(accessJwt: string | undefined) {
if (accessJwt) {
const sessData = jwtDecode(accessJwt)
return (
hasProp(sessData, 'scope') && sessData.scope === 'com.atproto.deactivated'
)
}
return false
}
export function readLastActiveAccount() {
const {currentAccount, accounts} = persisted.get('session')
return accounts.find(a => a.did === currentAccount?.did)
}
export function agentToSessionAccount(
agent: BskyAgent,
): SessionAccount | undefined {
if (!agent.session) return undefined
return {
service: agent.service.toString(),
did: agent.session.did,
handle: agent.session.handle,
email: agent.session.email,
emailConfirmed: agent.session.emailConfirmed,
deactivated: isSessionDeactivated(agent.session.accessJwt),
refreshJwt: agent.session.refreshJwt,
accessJwt: agent.session.accessJwt,
}
}
export function sessionAccountToAgentSession(
account: SessionAccount,
): BskyAgent['session'] {
return {
did: account.did,
handle: account.handle,
email: account.email,
emailConfirmed: account.emailConfirmed,
accessJwt: account.accessJwt || '',
refreshJwt: account.refreshJwt || '',
}
}
export async function configureModeration(
agent: BskyAgent,
account?: SessionAccount,
) {
if (account) {
if (IS_TEST_USER(account.handle)) {
const did = (
await agent
.resolveHandle({handle: 'mod-authority.test'})
.catch(_ => undefined)
)?.data.did
if (did) {
console.warn('USING TEST ENV MODERATION')
BskyAgent.configure({appLabelers: [did]})
}
} else {
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
if (account) {
const labelerDids = await readLabelers(account.did).catch(_ => {})
if (labelerDids) {
agent.configureLabelersHeader(
labelerDids.filter(did => did !== BSKY_LABELER_DID),
)
}
}
}
} else {
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
}
}
export function isSessionExpired(account: SessionAccount) {
let canReusePrevSession = false
try {
if (account.accessJwt) {
const decoded = jwtDecode(account.accessJwt)
if (decoded.exp) {
const didExpire = Date.now() >= decoded.exp * 1000
if (!didExpire) {
canReusePrevSession = true
}
}
}
} catch (e) {
logger.error(`session: could not decode jwt`)
}
return !canReusePrevSession
}
export async function createAgentAndLogin({
service,
identifier,
password,
}: {
service: string
identifier: string
password: string
}) {
const agent = new BskyAgent({service})
await agent.login({identifier, password})
if (!agent.session) {
throw new Error(`session: login failed to establish a session`)
}
const account = agentToSessionAccount(agent)!
await configureModeration(agent, account)
return {
agent,
account,
}
}
export async function createAgentAndCreateAccount({
service,
email,
password,
handle,
inviteCode,
verificationPhone,
verificationCode,
}: Parameters<SessionApiContext['createAccount']>[0]) {
const agent = new BskyAgent({service})
await agent.createAccount({
handle,
password,
email,
inviteCode,
verificationPhone,
verificationCode,
})
if (!agent.session) {
throw new Error(`session: createAccount failed to establish a session`)
}
const deactivated = isSessionDeactivated(agent.session.accessJwt)
if (!deactivated) {
/*dont await*/ agent.upsertProfile(_existing => {
return {
displayName: '',
// HACKFIX
// creating a bunch of identical profile objects is breaking the relay
// tossing this unspecced field onto it to reduce the size of the problem
// -prf
createdAt: new Date().toISOString(),
}
})
}
const account = agentToSessionAccount(agent)!
await configureModeration(agent, account)
return {
agent,
account,
}
}
+15 -20
View File
@@ -1,19 +1,20 @@
import React, {useState} from 'react'
import {ActivityIndicator, SafeAreaView, StyleSheet, View} from 'react-native'
import {ScrollView, TextInput} from './util'
import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button'
import {ErrorMessage} from '../util/error/ErrorMessage'
import * as Toast from '../util/Toast'
import {s, colors} from 'lib/styles'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {getAgent, useSession, useSessionApi} from '#/state/session'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useSession, useSessionApi, getAgent} from '#/state/session'
import {colors, s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
import {ScrollView, TextInput} from './util'
enum Stages {
InputEmail,
@@ -26,7 +27,7 @@ export const snapPoints = ['90%']
export function Component() {
const pal = usePalette('default')
const {currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi()
const {refreshSession} = useSessionApi()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(Stages.InputEmail)
const [email, setEmail] = useState<string>(currentAccount?.email || '')
@@ -49,10 +50,7 @@ export function Component() {
setStage(Stages.ConfirmCode)
} else {
await getAgent().com.atproto.server.updateEmail({email: email.trim()})
updateCurrentAccount({
email: email.trim(),
emailConfirmed: false,
})
refreshSession()
Toast.show(_(msg`Email updated`))
setStage(Stages.Done)
}
@@ -81,10 +79,7 @@ export function Component() {
email: email.trim(),
token: confirmationCode.trim(),
})
updateCurrentAccount({
email: email.trim(),
emailConfirmed: false,
})
refreshSession()
Toast.show(_(msg`Email updated`))
setStage(Stages.Done)
} catch (e) {
+3 -5
View File
@@ -72,7 +72,7 @@ export function Inner({
const {_} = useLingui()
const pal = usePalette('default')
const {track} = useAnalytics()
const {updateCurrentAccount} = useSessionApi()
const {refreshSession} = useSessionApi()
const {closeModal} = useModalControls()
const {mutateAsync: updateHandle, isPending: isUpdateHandlePending} =
useUpdateHandleMutation()
@@ -115,9 +115,7 @@ export function Inner({
await updateHandle({
handle: newHandle,
})
updateCurrentAccount({
handle: newHandle,
})
refreshSession()
closeModal()
onChanged()
} catch (err: any) {
@@ -133,7 +131,7 @@ export function Inner({
onChanged,
track,
closeModal,
updateCurrentAccount,
refreshSession,
updateHandle,
serviceInfo,
])
+16 -15
View File
@@ -6,23 +6,24 @@ import {
StyleSheet,
View,
} from 'react-native'
import {Svg, Circle, Path} from 'react-native-svg'
import {ScrollView, TextInput} from './util'
import {Circle, Path, Svg} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button'
import {ErrorMessage} from '../util/error/ErrorMessage'
import * as Toast from '../util/Toast'
import {s, colors} from 'lib/styles'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import {getAgent, useSession, useSessionApi} from '#/state/session'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useSession, useSessionApi, getAgent} from '#/state/session'
import {logger} from '#/logger'
import {colors, s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
import {ScrollView, TextInput} from './util'
export const snapPoints = ['90%']
@@ -35,7 +36,7 @@ enum Stages {
export function Component({showReminder}: {showReminder?: boolean}) {
const pal = usePalette('default')
const {currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi()
const {refreshSession} = useSessionApi()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(
showReminder ? Stages.Reminder : Stages.Email,
@@ -74,7 +75,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
email: (currentAccount?.email || '').trim(),
token: confirmationCode.trim(),
})
updateCurrentAccount({emailConfirmed: true})
refreshSession()
Toast.show(_(msg`Email verified`))
closeModal()
} catch (e) {