[SDK] Migrate search, bookmarks, contacts and feed queries to the lex clients (#11360)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,14 @@ import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {
|
||||
type AutocompleteApi,
|
||||
type AutocompleteItem,
|
||||
type AutocompleteItemType,
|
||||
type AutocompleteProfile,
|
||||
} from '#/components/Autocomplete/types'
|
||||
import {app} from '#/lexicons'
|
||||
import {useEmojiSearch} from './useEmojiSearch'
|
||||
|
||||
const DEFAULT_MOD_OPTS = {
|
||||
@@ -31,7 +32,7 @@ export function useAutocomplete({
|
||||
limit?: number
|
||||
showSearchFallback?: boolean
|
||||
}): AutocompleteApi {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const emojiSearch = useEmojiSearch()
|
||||
|
||||
@@ -52,12 +53,12 @@ export function useAutocomplete({
|
||||
// Going from "foo" to "foo." should not clear matches.
|
||||
q = q.toLowerCase().trim().replace(/\.$/, '')
|
||||
|
||||
const res = await agent.searchActorsTypeahead({
|
||||
const data = await client.call(app.bsky.actor.searchActorsTypeahead, {
|
||||
q,
|
||||
limit: limit || 8,
|
||||
})
|
||||
|
||||
return (res?.data.actors || []).map(profile => ({
|
||||
return (data?.actors || []).map(profile => ({
|
||||
key: profile.did,
|
||||
type: 'profile' as const,
|
||||
value: '@' + profile.handle,
|
||||
|
||||
@@ -2,7 +2,6 @@ import {useState} from 'react'
|
||||
import {Keyboard, View} from 'react-native'
|
||||
import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {AppBskyContactStartPhoneVerification} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -14,8 +13,9 @@ import {
|
||||
getDefaultCountry,
|
||||
} from '#/lib/international-telephone-codes'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
|
||||
import {
|
||||
android,
|
||||
@@ -34,6 +34,7 @@ import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
import {app} from '#/lexicons'
|
||||
import {isFindContactsFeatureEnabled} from '../country-allowlist'
|
||||
import {
|
||||
constructFullPhoneNumber,
|
||||
@@ -56,7 +57,7 @@ export function PhoneInput({
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const t = useTheme()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const location = useGeolocation()
|
||||
const [countryCode, setCountryCode] = useState(
|
||||
() => state.phoneCountryCode ?? getDefaultCountry(location),
|
||||
@@ -78,7 +79,7 @@ export function PhoneInput({
|
||||
phoneNumber: string
|
||||
}) => {
|
||||
// sends a onetime code to the user's phone number
|
||||
await agent.app.bsky.contact.startPhoneVerification({
|
||||
await client.call(app.bsky.contact.startPhoneVerification, {
|
||||
phone: constructFullPhoneNumber(phoneCountryCode, phoneNumber),
|
||||
})
|
||||
},
|
||||
@@ -102,23 +103,22 @@ export function PhoneInput({
|
||||
msg`A network error occurred. Please check your internet connection`,
|
||||
),
|
||||
)
|
||||
} else if (
|
||||
err instanceof
|
||||
AppBskyContactStartPhoneVerification.RateLimitExceededError
|
||||
) {
|
||||
setError(_(msg`Rate limit exceeded. Please try again later.`))
|
||||
} else if (
|
||||
err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError
|
||||
) {
|
||||
setError(
|
||||
_(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
logger.error('Verify phone number failed', {safeMessage: err})
|
||||
setError(_(msg`An error occurred. ${cleanError(err)}`))
|
||||
return
|
||||
}
|
||||
switch (matchXrpcError(err, app.bsky.contact.startPhoneVerification)) {
|
||||
case 'RateLimitExceeded':
|
||||
setError(_(msg`Rate limit exceeded. Please try again later.`))
|
||||
return
|
||||
case 'InvalidPhone':
|
||||
setError(
|
||||
_(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
logger.error('Verify phone number failed', {safeMessage: err})
|
||||
setError(_(msg`An error occurred. ${cleanError(err)}`))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {Text as NestedText, View} from 'react-native'
|
||||
import {
|
||||
AppBskyContactStartPhoneVerification,
|
||||
AppBskyContactVerifyPhone,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -11,8 +7,9 @@ import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {clamp} from '#/lib/numbers'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
|
||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
@@ -25,6 +22,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {app} from '#/lexicons'
|
||||
import {OTPInput} from '../components/OTPInput'
|
||||
import {constructFullPhoneNumber, prettyPhoneNumber} from '../phone-number'
|
||||
import {type Action, type State, useOnPressBackButton} from '../state'
|
||||
@@ -43,7 +41,7 @@ export function VerifyNumber({
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const gutters = useGutters([0, 'wide'])
|
||||
|
||||
const [otpCode, setOtpCode] = useState('')
|
||||
@@ -72,8 +70,11 @@ export function VerifyNumber({
|
||||
isSuccess,
|
||||
} = useMutation({
|
||||
mutationFn: async (code: string) => {
|
||||
const res = await agent.app.bsky.contact.verifyPhone({code, phone})
|
||||
return res.data.token
|
||||
const data = await client.call(app.bsky.contact.verifyPhone, {
|
||||
code,
|
||||
phone,
|
||||
})
|
||||
return data.token
|
||||
},
|
||||
onSuccess: async token => {
|
||||
// let the success state show for a moment
|
||||
@@ -99,44 +100,47 @@ export function VerifyNumber({
|
||||
msg`A network error occurred. Please check your internet connection.`,
|
||||
),
|
||||
})
|
||||
} else if (err instanceof AppBskyContactVerifyPhone.InvalidCodeError) {
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(msg`This code is invalid. Resend to get a new code.`),
|
||||
})
|
||||
} else if (err instanceof AppBskyContactVerifyPhone.InvalidPhoneError) {
|
||||
setError({
|
||||
retryable: false,
|
||||
isResendError: false,
|
||||
message: _(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
})
|
||||
} else if (
|
||||
err instanceof AppBskyContactVerifyPhone.RateLimitExceededError
|
||||
) {
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: false,
|
||||
message: _(
|
||||
msg`Too many attempts. Please wait a few minutes and try again.`,
|
||||
),
|
||||
})
|
||||
} else {
|
||||
logger.error('Verify phone number failed', {safeMessage: err})
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: false,
|
||||
message: _(msg`An error occurred. ${cleanError(err)}`),
|
||||
})
|
||||
return
|
||||
}
|
||||
switch (matchXrpcError(err, app.bsky.contact.verifyPhone)) {
|
||||
case 'InvalidCode':
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(msg`This code is invalid. Resend to get a new code.`),
|
||||
})
|
||||
return
|
||||
case 'InvalidPhone':
|
||||
setError({
|
||||
retryable: false,
|
||||
isResendError: false,
|
||||
message: _(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
})
|
||||
return
|
||||
case 'RateLimitExceeded':
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: false,
|
||||
message: _(
|
||||
msg`Too many attempts. Please wait a few minutes and try again.`,
|
||||
),
|
||||
})
|
||||
return
|
||||
}
|
||||
logger.error('Verify phone number failed', {safeMessage: err})
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: false,
|
||||
message: _(msg`An error occurred. ${cleanError(err)}`),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const {mutate: resendCode, isPending: isResendingCode} = useMutation({
|
||||
mutationFn: async () => {
|
||||
await agent.app.bsky.contact.startPhoneVerification({phone: phone})
|
||||
await client.call(app.bsky.contact.startPhoneVerification, {phone: phone})
|
||||
},
|
||||
onSuccess: () => {
|
||||
dispatch({type: 'RESEND_VERIFICATION_CODE'})
|
||||
@@ -155,35 +159,34 @@ export function VerifyNumber({
|
||||
msg`A network error occurred. Please check your internet connection.`,
|
||||
),
|
||||
})
|
||||
} else if (
|
||||
err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError
|
||||
) {
|
||||
setError({
|
||||
retryable: false,
|
||||
isResendError: true,
|
||||
message: _(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
})
|
||||
} else if (
|
||||
err instanceof
|
||||
AppBskyContactStartPhoneVerification.RateLimitExceededError
|
||||
) {
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(
|
||||
msg`Too many codes sent. Please wait a few minutes and try again.`,
|
||||
),
|
||||
})
|
||||
} else {
|
||||
logger.error('Resend failed', {safeMessage: err})
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(msg`An error occurred. ${cleanError(err)}`),
|
||||
})
|
||||
return
|
||||
}
|
||||
switch (matchXrpcError(err, app.bsky.contact.startPhoneVerification)) {
|
||||
case 'InvalidPhone':
|
||||
setError({
|
||||
retryable: false,
|
||||
isResendError: true,
|
||||
message: _(
|
||||
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
|
||||
),
|
||||
})
|
||||
return
|
||||
case 'RateLimitExceeded':
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(
|
||||
msg`Too many codes sent. Please wait a few minutes and try again.`,
|
||||
),
|
||||
})
|
||||
return
|
||||
}
|
||||
logger.error('Resend failed', {safeMessage: err})
|
||||
setError({
|
||||
retryable: true,
|
||||
isResendError: true,
|
||||
message: _(msg`An error occurred. ${cleanError(err)}`),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {View} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import * as SMS from 'expo-sms'
|
||||
import {type ModerationOpts} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Plural, Trans} from '@lingui/react/macro'
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
optimisticRemoveMatch,
|
||||
useMatchesPassthroughQuery,
|
||||
} from '#/state/queries/find-contacts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAgent, useAppviewClient, useSession} from '#/state/session'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
|
||||
@@ -41,6 +42,7 @@ import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {InviteInfo} from '../components/InviteInfo'
|
||||
import {type Action, type Contact, type Match, type State} from '../state'
|
||||
@@ -90,6 +92,7 @@ export function ViewMatches({
|
||||
const moderationOpts = useModerationOpts()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const insets = useSafeAreaInsets()
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
|
||||
@@ -218,7 +221,9 @@ export function ViewMatches({
|
||||
|
||||
const {mutate: dismissMatch} = useMutation({
|
||||
mutationFn: async (did: string) => {
|
||||
await agent.app.bsky.contact.dismissMatch({subject: did})
|
||||
await client.call(app.bsky.contact.dismissMatch, {
|
||||
subject: did as DidString,
|
||||
})
|
||||
},
|
||||
onMutate: did => {
|
||||
ax.metric('contacts:matches:dismiss', {entryPoint: context})
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {useCallback, useEffect, useState} from 'react'
|
||||
import {type ListRenderItemInfo, View} from 'react-native'
|
||||
import * as Contacts from 'expo-contacts'
|
||||
import {
|
||||
type AppBskyContactDefs,
|
||||
type AppBskyContactGetSyncStatus,
|
||||
type ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {type AppBskyContactDefs, type ModerationOpts} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Plural, Trans} from '@lingui/react/macro'
|
||||
@@ -32,7 +29,7 @@ import {
|
||||
useContactsMatchesQuery,
|
||||
useContactsSyncStatusQuery,
|
||||
} from '#/state/queries/find-contacts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAgent, useAppviewClient, useSession} from '#/state/session'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
|
||||
@@ -52,6 +49,7 @@ import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {InviteFriendsDialog} from '#/features/inviteFriends'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {bulkWriteFollows} from '../Onboarding/util'
|
||||
|
||||
@@ -194,7 +192,7 @@ function SyncStatus({
|
||||
refetchStatus: () => Promise<any>
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {_} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -219,7 +217,9 @@ function SyncStatus({
|
||||
|
||||
const {mutate: dismissMatch} = useMutation({
|
||||
mutationFn: async (did: string) => {
|
||||
await agent.app.bsky.contact.dismissMatch({subject: did})
|
||||
await client.call(app.bsky.contact.dismissMatch, {
|
||||
subject: did as DidString,
|
||||
})
|
||||
},
|
||||
onMutate: async (did: string) => {
|
||||
ax.metric('contacts:settings:dismiss', {})
|
||||
@@ -371,6 +371,7 @@ function StatusHeader({
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
@@ -384,12 +385,12 @@ function StatusHeader({
|
||||
|
||||
let cursor: string | undefined
|
||||
do {
|
||||
const page = await agent.app.bsky.contact.getMatches({
|
||||
const page = await client.call(app.bsky.contact.getMatches, {
|
||||
limit: 100,
|
||||
cursor,
|
||||
})
|
||||
cursor = page.data.cursor
|
||||
for (const profile of page.data.matches) {
|
||||
cursor = page.cursor
|
||||
for (const profile of page.matches) {
|
||||
if (
|
||||
profile.did !== currentAccount?.did &&
|
||||
!isBlockedOrBlocking(profile) &&
|
||||
@@ -487,17 +488,17 @@ function StatusFooter({syncedAt}: {syncedAt: string}) {
|
||||
const {_, i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const {mutate: removeData, isPending} = useMutation({
|
||||
mutationFn: async () => {
|
||||
await agent.app.bsky.contact.removeData({})
|
||||
await client.call(app.bsky.contact.removeData, {})
|
||||
},
|
||||
onMutate: () => ax.metric('contacts:settings:removeData', {}),
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Contacts removed`))
|
||||
queryClient.setQueryData<AppBskyContactGetSyncStatus.OutputSchema>(
|
||||
queryClient.setQueryData<app.bsky.contact.getSyncStatus.$OutputBody>(
|
||||
findContactsStatusQueryKey,
|
||||
{syncStatus: undefined},
|
||||
)
|
||||
|
||||
+22
-13
@@ -8,6 +8,7 @@ import {
|
||||
} from 'react'
|
||||
import {AppState, type AppStateStatus} from 'react-native'
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import {type AtUriString, type DidString} from '@atproto/syntax'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants'
|
||||
@@ -22,7 +23,8 @@ import {
|
||||
} from '#/state/queries/post-feed'
|
||||
import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useAgent} from './session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useAppviewClient} from './session'
|
||||
|
||||
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
||||
|
||||
@@ -65,7 +67,7 @@ export function useFeedFeedback(
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const logger = ax.logger.useChild(ax.logger.Context.FeedFeedback)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
const feed =
|
||||
!!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo)
|
||||
@@ -150,16 +152,19 @@ export function useFeedFeedback(
|
||||
return
|
||||
}
|
||||
|
||||
// Send to the feed
|
||||
agent.app.bsky.feed
|
||||
.sendInteractions(
|
||||
{interactions: interactionsToSend, feed: feed?.uri},
|
||||
/*
|
||||
* Send to the feed. Interactions go to the feed generator rather than the
|
||||
* appview, which the agent did by setting `atproto-proxy` by hand; the
|
||||
* client's per-call `service` option writes that same header.
|
||||
*/
|
||||
client
|
||||
.call(
|
||||
app.bsky.feed.sendInteractions,
|
||||
{
|
||||
encoding: 'application/json',
|
||||
headers: {
|
||||
'atproto-proxy': `${proxyDid}#bsky_fg`,
|
||||
},
|
||||
interactions: interactionsToSend,
|
||||
feed: feed?.uri as AtUriString | undefined,
|
||||
},
|
||||
{service: `${proxyDid as DidString}#bsky_fg`},
|
||||
)
|
||||
.catch(() => {}) // ignore upstream errors
|
||||
|
||||
@@ -172,7 +177,7 @@ export function useFeedFeedback(
|
||||
)
|
||||
throttledFlushAggregatedStats()
|
||||
logger.debug('flushed')
|
||||
}, [agent, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
||||
}, [client, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
||||
|
||||
const sendToFeed = useMemo(
|
||||
() =>
|
||||
@@ -283,9 +288,13 @@ function toString(interaction: AppBskyFeedDefs.Interaction): string {
|
||||
}|${interaction.reqId || ''}`
|
||||
}
|
||||
|
||||
function toInteraction(str: string): AppBskyFeedDefs.Interaction {
|
||||
function toInteraction(str: string): app.bsky.feed.defs.Interaction {
|
||||
const [item, event, feedContext, reqId] = str.split('|')
|
||||
return {item, event, feedContext, reqId}
|
||||
/*
|
||||
* The fields come from splitting an internally-built key, so neither the
|
||||
* at-uri nor the event token is narrowed by the compiler here.
|
||||
*/
|
||||
return {item, event, feedContext, reqId} as app.bsky.feed.defs.Interaction
|
||||
}
|
||||
|
||||
type AggregatedStats = {
|
||||
|
||||
@@ -9,7 +9,8 @@ import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences'
|
||||
|
||||
@@ -27,7 +28,7 @@ export function useActorAutocompleteQuery(
|
||||
limit?: number,
|
||||
) {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
prefix = prefix.toLowerCase().trim()
|
||||
if (prefix.endsWith('.')) {
|
||||
@@ -39,13 +40,13 @@ export function useActorAutocompleteQuery(
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(prefix || ''),
|
||||
async queryFn() {
|
||||
const res = prefix
|
||||
? await agent.searchActorsTypeahead({
|
||||
const data = prefix
|
||||
? await client.call(app.bsky.actor.searchActorsTypeahead, {
|
||||
q: prefix,
|
||||
limit: limit || 8,
|
||||
})
|
||||
: undefined
|
||||
return res?.data.actors || []
|
||||
return data?.actors || []
|
||||
},
|
||||
select: useCallback(
|
||||
(data: AppBskyActorDefs.ProfileViewBasic[]) => {
|
||||
@@ -65,7 +66,7 @@ export type ActorAutocompleteFn = ReturnType<typeof useActorAutocompleteFn>
|
||||
export function useActorAutocompleteFn() {
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useCallback(
|
||||
async ({query, limit = 8}: {query: string; limit?: number}) => {
|
||||
@@ -77,7 +78,7 @@ export function useActorAutocompleteFn() {
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(query || ''),
|
||||
queryFn: () =>
|
||||
agent.searchActorsTypeahead({
|
||||
client.call(app.bsky.actor.searchActorsTypeahead, {
|
||||
q: query,
|
||||
limit,
|
||||
}),
|
||||
@@ -91,11 +92,11 @@ export function useActorAutocompleteFn() {
|
||||
|
||||
return computeSuggestions({
|
||||
q: query,
|
||||
searched: res?.data.actors,
|
||||
searched: res?.actors,
|
||||
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
|
||||
})
|
||||
},
|
||||
[queryClient, moderationOpts, agent],
|
||||
[queryClient, moderationOpts, client],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyActorSearchActors} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
@@ -8,7 +7,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const RQKEY_ROOT = 'actor-search'
|
||||
export const RQKEY = (query: string, limit?: number) => [
|
||||
@@ -28,23 +28,22 @@ export function useActorSearch({
|
||||
maintainData?: boolean
|
||||
limit?: number
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyActorSearchActors.OutputSchema,
|
||||
app.bsky.actor.searchActors.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyActorSearchActors.OutputSchema>,
|
||||
InfiniteData<app.bsky.actor.searchActors.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: RQKEY(query, limit),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.searchActors({
|
||||
return await client.call(app.bsky.actor.searchActors, {
|
||||
q: query,
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
enabled: enabled && !!query,
|
||||
initialPageParam: undefined,
|
||||
@@ -54,7 +53,7 @@ export function useActorSearch({
|
||||
})
|
||||
}
|
||||
|
||||
function select(data: InfiniteData<AppBskyActorSearchActors.OutputSchema>) {
|
||||
function select(data: InfiniteData<app.bsky.actor.searchActors.$OutputBody>) {
|
||||
// enforce uniqueness
|
||||
const dids = new Set()
|
||||
|
||||
@@ -77,7 +76,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyActorSearchActors.OutputSchema>
|
||||
InfiniteData<app.bsky.actor.searchActors.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
@@ -8,7 +9,8 @@ import {
|
||||
optimisticallyDeleteBookmark,
|
||||
optimisticallySaveBookmark,
|
||||
} from '#/state/queries/bookmarks/useBookmarksQuery'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
type MutationArgs =
|
||||
| {action: 'create'; post: AppBskyFeedDefs.PostView}
|
||||
@@ -23,20 +25,20 @@ type MutationArgs =
|
||||
|
||||
export function useBookmarkMutation() {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useMutation({
|
||||
async mutationFn(args: MutationArgs) {
|
||||
if (args.action === 'create') {
|
||||
updatePostShadow(qc, args.post.uri, {bookmarked: true})
|
||||
await agent.app.bsky.bookmark.createBookmark({
|
||||
uri: args.post.uri,
|
||||
await client.call(app.bsky.bookmark.createBookmark, {
|
||||
uri: args.post.uri as AtUriString,
|
||||
cid: args.post.cid,
|
||||
})
|
||||
} else if (args.action === 'delete') {
|
||||
updatePostShadow(qc, args.uri, {bookmarked: false})
|
||||
await agent.app.bsky.bookmark.deleteBookmark({
|
||||
uri: args.uri,
|
||||
await client.call(app.bsky.bookmark.deleteBookmark, {
|
||||
uri: args.uri as AtUriString,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyBookmarkGetBookmarks,
|
||||
AppBskyFeedDefs,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed, AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -16,28 +11,28 @@ import {
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export const bookmarksQueryKeyRoot = 'bookmarks'
|
||||
export const createBookmarksQueryKey = () => [bookmarksQueryKeyRoot]
|
||||
|
||||
export function useBookmarksQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyBookmarkGetBookmarks.OutputSchema,
|
||||
app.bsky.bookmark.getBookmarks.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>,
|
||||
InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
queryKey: createBookmarksQueryKey(),
|
||||
async queryFn({pageParam}) {
|
||||
const res = await agent.app.bsky.bookmark.getBookmarks({
|
||||
return await client.call(app.bsky.bookmark.getBookmarks, {
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -45,7 +40,7 @@ export function useBookmarksQuery() {
|
||||
}
|
||||
|
||||
export async function truncateAndInvalidate(qc: QueryClient) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{queryKey: [bookmarksQueryKeyRoot]},
|
||||
data => {
|
||||
if (data) {
|
||||
@@ -64,7 +59,7 @@ export async function optimisticallySaveBookmark(
|
||||
qc: QueryClient,
|
||||
post: AppBskyFeedDefs.PostView,
|
||||
) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
},
|
||||
@@ -75,19 +70,22 @@ export async function optimisticallySaveBookmark(
|
||||
pages: data.pages.map((page, index) => {
|
||||
if (index === 0) {
|
||||
post.$type = 'app.bsky.feed.defs#postView'
|
||||
/*
|
||||
* The optimistic entry is synthesized from an `@atproto/api`
|
||||
* `PostView`, whose string fields are unbranded, so it is asserted
|
||||
* to the vendored view type the query data is now keyed on.
|
||||
*/
|
||||
const bookmark = {
|
||||
createdAt: new Date().toISOString(),
|
||||
subject: {
|
||||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
},
|
||||
item: post as $Typed<AppBskyFeedDefs.PostView>,
|
||||
} as unknown as app.bsky.bookmark.defs.BookmarkView
|
||||
return {
|
||||
...page,
|
||||
bookmarks: [
|
||||
{
|
||||
createdAt: new Date().toISOString(),
|
||||
subject: {
|
||||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
},
|
||||
item: post as $Typed<AppBskyFeedDefs.PostView>,
|
||||
},
|
||||
...page.bookmarks,
|
||||
],
|
||||
bookmarks: [bookmark, ...page.bookmarks],
|
||||
}
|
||||
}
|
||||
return page
|
||||
@@ -101,7 +99,7 @@ export async function optimisticallyDeleteBookmark(
|
||||
qc: QueryClient,
|
||||
{uri}: {uri: string},
|
||||
) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
},
|
||||
@@ -125,7 +123,7 @@ export function* findAllPostsInQueryData(
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>
|
||||
InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>
|
||||
>({
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
})
|
||||
|
||||
+68
-56
@@ -3,11 +3,11 @@ import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyUnspeccedGetPopularFeedGenerators,
|
||||
AtUri,
|
||||
moderateFeedGenerator,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -26,7 +26,8 @@ import {GCTIME, STALE} from '#/state/queries'
|
||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {router} from '#/routes'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {type FeedDescriptor} from './post-feed'
|
||||
@@ -184,7 +185,7 @@ export function getAvatarTypeFromUri(uri: string) {
|
||||
|
||||
export function useFeedSourceInfoQuery({uri}: {uri: string}) {
|
||||
const type = getFeedTypeFromUri(uri)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
@@ -193,14 +194,16 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
|
||||
let view: FeedSourceInfo
|
||||
|
||||
if (type === 'feed') {
|
||||
const res = await agent.app.bsky.feed.getFeedGenerator({feed: uri})
|
||||
view = hydrateFeedGenerator(res.data.view)
|
||||
const data = await client.call(app.bsky.feed.getFeedGenerator, {
|
||||
feed: uri as AtUriString,
|
||||
})
|
||||
view = hydrateFeedGenerator(data.view)
|
||||
} else {
|
||||
const res = await agent.app.bsky.graph.getList({
|
||||
list: uri,
|
||||
const data = await client.call(app.bsky.graph.getList, {
|
||||
list: uri as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
view = hydrateList(res.data.list)
|
||||
view = hydrateList(data.list)
|
||||
}
|
||||
|
||||
return view
|
||||
@@ -234,7 +237,7 @@ export function createGetPopularFeedsQueryKey(
|
||||
|
||||
export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
const {hasSession} = useSession()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const limit = options?.limit || 10
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -255,24 +258,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
enabled: Boolean(moderationOpts) && options?.enabled !== false,
|
||||
queryKey: createGetPopularFeedsQueryKey(options),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
const data = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
},
|
||||
)
|
||||
|
||||
// precache feeds
|
||||
for (const feed of res.data.feeds) {
|
||||
for (const feed of data.feeds) {
|
||||
const hydratedFeed = hydrateFeedGenerator(feed)
|
||||
precacheFeed(queryClient, hydratedFeed)
|
||||
}
|
||||
|
||||
return res.data
|
||||
return data
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
select: useCallback(
|
||||
(
|
||||
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
|
||||
data: InfiniteData<app.bsky.unspecced.getPopularFeedGenerators.$OutputBody>,
|
||||
) => {
|
||||
const {
|
||||
savedFeeds,
|
||||
@@ -336,24 +342,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
}
|
||||
|
||||
export function useSearchPopularFeedsMutation() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (query: string) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 10,
|
||||
query: query,
|
||||
})
|
||||
const data = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit: 10,
|
||||
query: query,
|
||||
},
|
||||
)
|
||||
|
||||
if (moderationOpts) {
|
||||
return res.data.feeds.filter(feed => {
|
||||
return data.feeds.filter(feed => {
|
||||
const decision = moderateFeedGenerator(feed, moderationOpts)
|
||||
return !decision.ui('contentMedia').blur
|
||||
})
|
||||
}
|
||||
|
||||
return res.data.feeds
|
||||
return data.feeds
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -371,7 +380,7 @@ export function usePopularFeedsSearch({
|
||||
query: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabledInner = enabled ?? Boolean(moderationOpts)
|
||||
|
||||
@@ -379,12 +388,15 @@ export function usePopularFeedsSearch({
|
||||
enabled: enabledInner,
|
||||
queryKey: createPopularFeedsSearchQueryKey(query),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 15,
|
||||
query: query,
|
||||
})
|
||||
const data = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit: 15,
|
||||
query: query,
|
||||
},
|
||||
)
|
||||
|
||||
return res.data.feeds
|
||||
return data.feeds
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
select(data) {
|
||||
@@ -444,7 +456,7 @@ const createPinnedFeedInfosQueryKey = (
|
||||
|
||||
export function usePinnedFeedsInfos() {
|
||||
const {hasSession} = useSession()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? []
|
||||
|
||||
@@ -467,13 +479,13 @@ export function usePinnedFeedsInfos() {
|
||||
const pinnedFeeds = pinnedItems.filter(feed => feed.type === 'feed')
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (pinnedFeeds.length > 0) {
|
||||
feedsPromise = agent.app.bsky.feed
|
||||
.getFeedGenerators({
|
||||
feeds: pinnedFeeds.map(f => f.value),
|
||||
feedsPromise = client
|
||||
.call(app.bsky.feed.getFeedGenerators, {
|
||||
feeds: pinnedFeeds.map(f => f.value as AtUriString),
|
||||
})
|
||||
.then(res => {
|
||||
for (let i = 0; i < res.data.feeds.length; i++) {
|
||||
const feedView = res.data.feeds[i]
|
||||
.then(data => {
|
||||
for (let i = 0; i < data.feeds.length; i++) {
|
||||
const feedView = data.feeds[i]
|
||||
resolved.set(feedView.uri, hydrateFeedGenerator(feedView))
|
||||
}
|
||||
})
|
||||
@@ -482,13 +494,13 @@ export function usePinnedFeedsInfos() {
|
||||
// Get all lists. This currently has to be done individually.
|
||||
const pinnedLists = pinnedItems.filter(feed => feed.type === 'list')
|
||||
const listsPromises = pinnedLists.map(list =>
|
||||
agent.app.bsky.graph
|
||||
.getList({
|
||||
list: list.value,
|
||||
client
|
||||
.call(app.bsky.graph.getList, {
|
||||
list: list.value as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
.then(data => {
|
||||
const listView = data.list
|
||||
resolved.set(listView.uri, hydrateList(listView))
|
||||
}),
|
||||
)
|
||||
@@ -551,7 +563,7 @@ export type SavedFeedItem =
|
||||
}
|
||||
|
||||
export function useSavedFeeds() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const savedItems = preferences?.savedFeeds ?? []
|
||||
const queryClient = useQueryClient()
|
||||
@@ -582,25 +594,25 @@ export function useSavedFeeds() {
|
||||
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (savedFeeds.length > 0) {
|
||||
feedsPromise = agent.app.bsky.feed
|
||||
.getFeedGenerators({
|
||||
feeds: savedFeeds.map(f => f.value),
|
||||
feedsPromise = client
|
||||
.call(app.bsky.feed.getFeedGenerators, {
|
||||
feeds: savedFeeds.map(f => f.value as AtUriString),
|
||||
})
|
||||
.then(res => {
|
||||
res.data.feeds.forEach(f => {
|
||||
.then(data => {
|
||||
data.feeds.forEach(f => {
|
||||
resolvedFeeds.set(f.uri, f)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const listsPromises = savedLists.map(list =>
|
||||
agent.app.bsky.graph
|
||||
.getList({
|
||||
list: list.value,
|
||||
client
|
||||
.call(app.bsky.graph.getList, {
|
||||
list: list.value as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
.then(data => {
|
||||
const listView = data.list
|
||||
resolvedLists.set(listView.uri, listView)
|
||||
}),
|
||||
)
|
||||
@@ -656,7 +668,7 @@ export function useSavedFeeds() {
|
||||
const feedInfoQueryKeyRoot = 'feedInfo'
|
||||
|
||||
export function useFeedInfo(feedUri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
@@ -666,11 +678,11 @@ export function useFeedInfo(feedUri: string | undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const res = await agent.app.bsky.feed.getFeedGenerator({
|
||||
feed: feedUri,
|
||||
const data = await client.call(app.bsky.feed.getFeedGenerator, {
|
||||
feed: feedUri as AtUriString,
|
||||
})
|
||||
|
||||
const feedSourceInfo = hydrateFeedGenerator(res.data.view)
|
||||
const feedSourceInfo = hydrateFeedGenerator(data.view)
|
||||
return feedSourceInfo
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyContactGetMatches} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -6,8 +5,9 @@ import {
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type Match} from '#/components/contacts/state'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {STALE} from '.'
|
||||
|
||||
@@ -15,13 +15,12 @@ const RQ_KEY_ROOT = 'find-contacts'
|
||||
export const findContactsStatusQueryKey = [RQ_KEY_ROOT, 'sync-status']
|
||||
|
||||
export function useContactsSyncStatusQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: findContactsStatusQueryKey,
|
||||
queryFn: async () => {
|
||||
const status = await agent.app.bsky.contact.getSyncStatus()
|
||||
return status.data
|
||||
return await client.call(app.bsky.contact.getSyncStatus, {})
|
||||
},
|
||||
staleTime: STALE.SECONDS.THIRTY,
|
||||
})
|
||||
@@ -30,15 +29,14 @@ export function useContactsSyncStatusQuery() {
|
||||
export const findContactsGetMatchesQueryKey = [RQ_KEY_ROOT, 'matches']
|
||||
|
||||
export function useContactsMatchesQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: findContactsGetMatchesQueryKey,
|
||||
queryFn: async ({pageParam}) => {
|
||||
const matches = await agent.app.bsky.contact.getMatches({
|
||||
return await client.call(app.bsky.contact.getMatches, {
|
||||
cursor: pageParam,
|
||||
})
|
||||
return matches.data
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -47,20 +45,19 @@ export function useContactsMatchesQuery() {
|
||||
}
|
||||
|
||||
export function optimisticRemoveMatch(queryClient: QueryClient, did: string) {
|
||||
queryClient.setQueryData<InfiniteData<AppBskyContactGetMatches.OutputSchema>>(
|
||||
findContactsGetMatchesQueryKey,
|
||||
old => {
|
||||
if (!old) return old
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<app.bsky.contact.getMatches.$OutputBody>
|
||||
>(findContactsGetMatchesQueryKey, old => {
|
||||
if (!old) return old
|
||||
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
matches: page.matches.filter(match => match.did !== did),
|
||||
})),
|
||||
}
|
||||
},
|
||||
)
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
matches: page.matches.filter(match => match.did !== did),
|
||||
})),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const findContactsMatchesPassthroughQueryKey = (dids: string[]) => [
|
||||
@@ -95,7 +92,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
): Generator<bsky.profile.AnyProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyContactGetMatches.OutputSchema>
|
||||
InfiniteData<app.bsky.contact.getMatches.$OutputBody>
|
||||
>({
|
||||
queryKey: findContactsGetMatchesQueryKey,
|
||||
})
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {useState} from 'react'
|
||||
import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
||||
import {type HandleString} from '@atproto/syntax'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||
import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {Agent} from '#/state/session/agent'
|
||||
import {getPublicAppviewClient} from '#/state/session/clients'
|
||||
import {com} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'pds-detection'
|
||||
export const RQKEY = (identifier: string) => [RQKEY_ROOT, identifier]
|
||||
@@ -147,16 +149,25 @@ export async function resolvePdsForIdentifier(
|
||||
identifier: string,
|
||||
): Promise<{did: string; pdsUrl: string | null} | null> {
|
||||
const norm = normalizeIdentifier(identifier)
|
||||
const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE})
|
||||
/*
|
||||
* Resolution runs without a session, so this uses the public appview client
|
||||
* rather than a session-scoped one. It matches the unauthenticated,
|
||||
* unproxied public agent this previously constructed by hand.
|
||||
*/
|
||||
const client = getPublicAppviewClient()
|
||||
try {
|
||||
let did: string
|
||||
if (norm.startsWith('did:')) {
|
||||
did = norm
|
||||
} else {
|
||||
const res = await withResolveTimeout(signal =>
|
||||
agent.resolveHandle({handle: norm}, {signal}),
|
||||
const data = await withResolveTimeout(signal =>
|
||||
client.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: norm as HandleString},
|
||||
{signal},
|
||||
),
|
||||
)
|
||||
did = res.data.did
|
||||
did = data.did
|
||||
}
|
||||
logger.debug('pds-detection: resolved identifier to DID', {
|
||||
identifier: norm,
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import {type AtpAgent, AtUri} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type HandleString} from '@atproto/syntax'
|
||||
import {type QueryClient, queryOptions, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {com} from '#/lexicons'
|
||||
import {useUnstableProfileViewCache} from './profile'
|
||||
|
||||
const RQKEY_ROOT = 'resolved-did'
|
||||
export const RQKEY = (didOrHandle: string) => [RQKEY_ROOT, didOrHandle]
|
||||
|
||||
const resolvedDidQueryOptions = (
|
||||
agent: AtpAgent,
|
||||
client: Client,
|
||||
getUnstableProfile: (did: string) => {did: string} | undefined,
|
||||
didOrHandle: string | undefined,
|
||||
) =>
|
||||
@@ -21,8 +24,15 @@ const resolvedDidQueryOptions = (
|
||||
// Just return the did if it's already one
|
||||
if (didOrHandle.startsWith('did:')) return didOrHandle
|
||||
|
||||
const res = await agent.resolveHandle({handle: didOrHandle})
|
||||
return res.data.did
|
||||
/*
|
||||
* Resolution stays on the appview client: the old agent call was proxied
|
||||
* to the appview, and the PDS implementation is not equivalent for
|
||||
* handles hosted elsewhere.
|
||||
*/
|
||||
const data = await client.call(com.atproto.identity.resolveHandle, {
|
||||
handle: didOrHandle as HandleString,
|
||||
})
|
||||
return data.did
|
||||
},
|
||||
initialData: () => {
|
||||
// Return undefined if no did or handle
|
||||
@@ -37,11 +47,11 @@ export function useResolveUriQuery(uri: string | undefined) {
|
||||
const urip = new AtUri(uri || '')
|
||||
const host = urip.host
|
||||
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {getUnstableProfile} = useUnstableProfileViewCache()
|
||||
|
||||
return useQuery({
|
||||
...resolvedDidQueryOptions(agent, getUnstableProfile, host),
|
||||
...resolvedDidQueryOptions(client, getUnstableProfile, host),
|
||||
select: did => ({
|
||||
did,
|
||||
uri: AtUri.make(did, urip.collection, urip.rkey).toString(),
|
||||
@@ -50,11 +60,11 @@ export function useResolveUriQuery(uri: string | undefined) {
|
||||
}
|
||||
|
||||
export function useResolveDidQuery(didOrHandle: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {getUnstableProfile} = useUnstableProfileViewCache()
|
||||
|
||||
return useQuery(
|
||||
resolvedDidQueryOptions(agent, getUnstableProfile, didOrHandle),
|
||||
resolvedDidQueryOptions(client, getUnstableProfile, didOrHandle),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {useCallback, useEffect, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {profilesQueryKey} from '#/state/queries/profile'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {useSetActiveLanding} from '#/state/shell/landing'
|
||||
import {
|
||||
useLoggedOutView,
|
||||
@@ -23,6 +24,7 @@ import {atoms as a, native, tokens, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {app} from '#/lexicons'
|
||||
import {SplashScreen} from './SplashScreen'
|
||||
|
||||
enum ScreenState {
|
||||
@@ -65,19 +67,18 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const {accounts} = useSession()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
useEffect(() => {
|
||||
const actors = accounts.map(acc => acc.did)
|
||||
const actors = accounts.map(acc => acc.did as DidString)
|
||||
if (actors.length === 0) return
|
||||
void queryClient.prefetchQuery({
|
||||
queryKey: profilesQueryKey(actors),
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryFn: async () => {
|
||||
const res = await agent.getProfiles({actors})
|
||||
return res.data
|
||||
return await client.call(app.bsky.actor.getProfiles, {actors})
|
||||
},
|
||||
})
|
||||
}, [accounts, agent, queryClient])
|
||||
}, [accounts, client, queryClient])
|
||||
|
||||
const onPressDismiss = useCallback(() => {
|
||||
if (onDismiss) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {AppBskyDraftCreateDraft, AppBskyDraftDefs} from '@atproto/api'
|
||||
import {AppBskyDraftDefs} from '@atproto/api'
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type ComposerState} from '#/view/com/composer/state/composer'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {getDeviceId} from '#/analytics/identifiers'
|
||||
import {app} from '#/lexicons'
|
||||
import {composerStateToDraft, draftViewToSummary} from './api'
|
||||
import {logger} from './logger'
|
||||
import * as storage from './storage'
|
||||
@@ -20,7 +22,7 @@ const DRAFTS_QUERY_KEY = ['drafts']
|
||||
* Hook to list all drafts for the current account
|
||||
*/
|
||||
export function useDraftsQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const ax = useAnalytics()
|
||||
|
||||
return useInfiniteQuery({
|
||||
@@ -28,10 +30,12 @@ export function useDraftsQuery() {
|
||||
queryFn: async ({pageParam}) => {
|
||||
// Ensure media cache is populated before checking which media exists
|
||||
await storage.ensureMediaCachePopulated()
|
||||
const res = await agent.app.bsky.draft.getDrafts({cursor: pageParam})
|
||||
const data = await client.call(app.bsky.draft.getDrafts, {
|
||||
cursor: pageParam,
|
||||
})
|
||||
return {
|
||||
cursor: res.data.cursor,
|
||||
drafts: res.data.drafts.map(view =>
|
||||
cursor: data.cursor,
|
||||
drafts: data.drafts.map(view =>
|
||||
draftViewToSummary({
|
||||
view,
|
||||
analytics: ax,
|
||||
@@ -116,7 +120,7 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{
|
||||
* This ensures we don't lose data if the network request fails.
|
||||
*/
|
||||
export function useSaveDraftMutation() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@@ -132,7 +136,14 @@ export function useSaveDraftMutation() {
|
||||
originalLocalRefs: Set<string> | undefined
|
||||
}> => {
|
||||
// Convert composer state to server draft format
|
||||
const {draft, localRefPaths} = await composerStateToDraft(composerState)
|
||||
const {draft: apiDraft, localRefPaths} =
|
||||
await composerStateToDraft(composerState)
|
||||
/*
|
||||
* `composerStateToDraft` builds the draft against the `@atproto/api`
|
||||
* types, whose string fields are unbranded, so it is asserted once here
|
||||
* to the vendored input type.
|
||||
*/
|
||||
const draft = apiDraft as unknown as app.bsky.draft.defs.Draft
|
||||
|
||||
logger.debug('saving draft', {
|
||||
existingDraftId,
|
||||
@@ -147,7 +158,7 @@ export function useSaveDraftMutation() {
|
||||
logger.debug('updating existing draft on server', {
|
||||
draftId: existingDraftId,
|
||||
})
|
||||
await agent.app.bsky.draft.updateDraft({
|
||||
await client.call(app.bsky.draft.updateDraft, {
|
||||
draft: {
|
||||
id: existingDraftId,
|
||||
draft,
|
||||
@@ -157,8 +168,8 @@ export function useSaveDraftMutation() {
|
||||
} else {
|
||||
// Create new draft
|
||||
logger.debug('creating new draft on server')
|
||||
const res = await agent.app.bsky.draft.createDraft({draft})
|
||||
draftId = res.data.id
|
||||
const data = await client.call(app.bsky.draft.createDraft, {draft})
|
||||
draftId = data.id
|
||||
logger.debug('created new draft', {draftId})
|
||||
}
|
||||
|
||||
@@ -203,7 +214,7 @@ export function useSaveDraftMutation() {
|
||||
},
|
||||
onError: error => {
|
||||
// Check for draft limit error
|
||||
if (error instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) {
|
||||
if (matchXrpcError(error, app.bsky.draft.createDraft)) {
|
||||
logger.error('Draft limit reached', {safeMessage: error.message})
|
||||
// Error will be handled by caller
|
||||
} else if (!isNetworkError(error)) {
|
||||
@@ -220,7 +231,7 @@ export function useSaveDraftMutation() {
|
||||
* Takes the full draft data to avoid re-fetching for media cleanup.
|
||||
*/
|
||||
export function useDeleteDraftMutation() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@@ -231,7 +242,7 @@ export function useDeleteDraftMutation() {
|
||||
draft: AppBskyDraftDefs.Draft
|
||||
}) => {
|
||||
// Delete from server first - if this fails, we keep local media for retry
|
||||
await agent.app.bsky.draft.deleteDraft({id: draftId})
|
||||
await client.call(app.bsky.draft.deleteDraft, {id: draftId})
|
||||
},
|
||||
onSuccess: async (_, {draft}) => {
|
||||
// Only delete local media after server deletion succeeds
|
||||
@@ -264,7 +275,7 @@ export function useDeleteDraftMutation() {
|
||||
* Takes draftId and originalLocalRefs from composer state.
|
||||
*/
|
||||
export function useCleanupPublishedDraftMutation() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@@ -280,7 +291,9 @@ export function useCleanupPublishedDraftMutation() {
|
||||
mediaFileCount: originalLocalRefs.size,
|
||||
})
|
||||
// Delete from server first
|
||||
await agent.app.bsky.draft.deleteDraft({id: draftId})
|
||||
await client.call(app.bsky.draft.deleteDraft, {
|
||||
id: draftId,
|
||||
})
|
||||
logger.debug('deleted draft from server', {draftId})
|
||||
},
|
||||
onSuccess: async (_, {originalLocalRefs}) => {
|
||||
|
||||
Reference in New Issue
Block a user