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