phase 4: migrate account flows, trending queries, notifications, and report subjects off the bridge
This commit is contained in:
@@ -6,14 +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 {toLex} from '#/types/bsky'
|
import {app} from '#/lexicons'
|
||||||
import {useEmojiSearch} from './useEmojiSearch'
|
import {useEmojiSearch} from './useEmojiSearch'
|
||||||
|
|
||||||
const DEFAULT_MOD_OPTS = {
|
const DEFAULT_MOD_OPTS = {
|
||||||
@@ -32,7 +32,7 @@ export function useAutocomplete({
|
|||||||
limit?: number
|
limit?: number
|
||||||
showSearchFallback?: boolean
|
showSearchFallback?: boolean
|
||||||
}): AutocompleteApi {
|
}): AutocompleteApi {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const emojiSearch = useEmojiSearch()
|
const emojiSearch = useEmojiSearch()
|
||||||
|
|
||||||
@@ -53,17 +53,19 @@ 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 res = await appviewClient.call(
|
||||||
|
app.bsky.actor.searchActorsTypeahead,
|
||||||
|
{
|
||||||
q,
|
q,
|
||||||
limit: limit || 8,
|
limit: limit || 8,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return (res?.data.actors || []).map(profile => ({
|
return (res?.actors || []).map(profile => ({
|
||||||
key: profile.did,
|
key: profile.did,
|
||||||
type: 'profile' as const,
|
type: 'profile' as const,
|
||||||
value: '@' + profile.handle,
|
value: '@' + profile.handle,
|
||||||
// emits #/lexicons views
|
profile,
|
||||||
profile: toLex<AutocompleteProfile['profile']>(profile),
|
|
||||||
}))
|
}))
|
||||||
} else if (type === 'emoji') {
|
} else if (type === 'emoji') {
|
||||||
return emojiSearch(q, limit || 8)
|
return emojiSearch(q, limit || 8)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {cleanError} from '#/lib/strings/errors'
|
|||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions'
|
import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {atoms as a, platform, useTheme, web} from '#/alf'
|
import {atoms as a, platform, useTheme, web} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {
|
import {
|
||||||
@@ -33,7 +33,7 @@ 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 {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function SubscribeProfileDialog({
|
export function SubscribeProfileDialog({
|
||||||
@@ -71,7 +71,7 @@ function DialogInner({
|
|||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const initialState = parseActivitySubscription(
|
const initialState = parseActivitySubscription(
|
||||||
@@ -119,7 +119,7 @@ function DialogInner({
|
|||||||
mutationFn: async (
|
mutationFn: async (
|
||||||
activitySubscription: Un$Typed<app.bsky.notification.defs.ActivitySubscription>,
|
activitySubscription: Un$Typed<app.bsky.notification.defs.ActivitySubscription>,
|
||||||
) => {
|
) => {
|
||||||
await agent.app.bsky.notification.putActivitySubscription({
|
await appviewClient.call(app.bsky.notification.putActivitySubscription, {
|
||||||
subject: profile.did,
|
subject: profile.did,
|
||||||
activitySubscription,
|
activitySubscription,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
|
|||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {accountReportSubject} from '#/components/moderation/ReportDialog/utils/reportSubject'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {logger} from '#/ageAssurance'
|
import {logger} from '#/ageAssurance'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {com, tools} from '#/lexicons'
|
import {com, tools} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export function AgeAssuranceAppealDialog({
|
export function AgeAssuranceAppealDialog({
|
||||||
control,
|
control,
|
||||||
@@ -54,14 +54,11 @@ function Inner({control}: {control: Dialog.DialogControlProps}) {
|
|||||||
|
|
||||||
await pdsClient.call(
|
await pdsClient.call(
|
||||||
com.atproto.moderation.createReport,
|
com.atproto.moderation.createReport,
|
||||||
toLex<com.atproto.moderation.createReport.$InputBody>({
|
{
|
||||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||||
subject: {
|
subject: accountReportSubject(currentAccount?.did ?? ''),
|
||||||
$type: 'com.atproto.admin.defs#repoRef',
|
|
||||||
did: currentAccount?.did,
|
|
||||||
},
|
|
||||||
reason: `AGE_ASSURANCE_INQUIRY: ` + details,
|
reason: `AGE_ASSURANCE_INQUIRY: ` + details,
|
||||||
}),
|
},
|
||||||
{
|
{
|
||||||
service: api.moderation.service,
|
service: api.moderation.service,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {getErrorName} from '#/lib/xrpc-error'
|
import {getErrorName} 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 appviewClient = 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 appviewClient.call(app.bsky.contact.startPhoneVerification, {
|
||||||
phone: constructFullPhoneNumber(phoneCountryCode, phoneNumber),
|
phone: constructFullPhoneNumber(phoneCountryCode, phoneNumber),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {clamp} from '#/lib/numbers'
|
|||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {getErrorName} from '#/lib/xrpc-error'
|
import {getErrorName} 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'
|
||||||
@@ -22,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'
|
||||||
@@ -40,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 appviewClient = useAppviewClient()
|
||||||
const gutters = useGutters([0, 'wide'])
|
const gutters = useGutters([0, 'wide'])
|
||||||
|
|
||||||
const [otpCode, setOtpCode] = useState('')
|
const [otpCode, setOtpCode] = useState('')
|
||||||
@@ -69,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 res = await appviewClient.call(app.bsky.contact.verifyPhone, {
|
||||||
return res.data.token
|
code,
|
||||||
|
phone,
|
||||||
|
})
|
||||||
|
return res.token
|
||||||
},
|
},
|
||||||
onSuccess: async token => {
|
onSuccess: async token => {
|
||||||
// let the success state show for a moment
|
// let the success state show for a moment
|
||||||
@@ -131,7 +135,9 @@ export function VerifyNumber({
|
|||||||
|
|
||||||
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 appviewClient.call(app.bsky.contact.startPhoneVerification, {
|
||||||
|
phone: phone,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
dispatch({type: 'RESEND_VERIFICATION_CODE'})
|
dispatch({type: 'RESEND_VERIFICATION_CODE'})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {useCallback, useMemo, useRef, 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 * as SMS from 'expo-sms'
|
import * as SMS from 'expo-sms'
|
||||||
|
import {type DidString} from '@atproto/syntax'
|
||||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -20,12 +21,7 @@ import {
|
|||||||
optimisticRemoveMatch,
|
optimisticRemoveMatch,
|
||||||
useMatchesPassthroughQuery,
|
useMatchesPassthroughQuery,
|
||||||
} from '#/state/queries/find-contacts'
|
} from '#/state/queries/find-contacts'
|
||||||
import {
|
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||||
useAgent,
|
|
||||||
useAppviewClient,
|
|
||||||
usePdsClient,
|
|
||||||
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'
|
||||||
@@ -46,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'
|
||||||
@@ -94,7 +91,6 @@ export function ViewMatches({
|
|||||||
const gutter = useGutters([0, 'wide'])
|
const gutter = useGutters([0, 'wide'])
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
|
||||||
const pdsClient = usePdsClient()
|
const pdsClient = usePdsClient()
|
||||||
const appviewClient = useAppviewClient()
|
const appviewClient = useAppviewClient()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
@@ -228,7 +224,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 appviewClient.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,13 +1,15 @@
|
|||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function useConfirmEmail({
|
export function useConfirmEmail({
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
}: {onSuccess?: () => void; onError?: () => void} = {}) {
|
}: {onSuccess?: () => void; onError?: () => void} = {}) {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
const {refreshSession} = useSessionApi()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({token}: {token: string}) => {
|
mutationFn: async ({token}: {token: string}) => {
|
||||||
@@ -15,12 +17,12 @@ export function useConfirmEmail({
|
|||||||
throw new Error('No email found for the current account')
|
throw new Error('No email found for the current account')
|
||||||
}
|
}
|
||||||
|
|
||||||
await agent.com.atproto.server.confirmEmail({
|
await pdsClient.call(com.atproto.server.confirmEmail, {
|
||||||
email: currentAccount.email.trim(),
|
email: currentAccount.email.trim(),
|
||||||
token: token.trim(),
|
token: token.trim(),
|
||||||
})
|
})
|
||||||
// will update session state at root of app
|
// will update session state at root of app
|
||||||
await agent.resumeSession(agent.session!)
|
await refreshSession()
|
||||||
},
|
},
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function useManageEmail2FA() {
|
export function useManageEmail2FA() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
const {refreshSession} = useSessionApi()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
@@ -17,13 +19,13 @@ export function useManageEmail2FA() {
|
|||||||
throw new Error('No email found for the current account')
|
throw new Error('No email found for the current account')
|
||||||
}
|
}
|
||||||
|
|
||||||
await agent.com.atproto.server.updateEmail({
|
await pdsClient.call(com.atproto.server.updateEmail, {
|
||||||
email: currentAccount.email,
|
email: currentAccount.email,
|
||||||
emailAuthFactor: enabled,
|
emailAuthFactor: enabled,
|
||||||
token,
|
token,
|
||||||
})
|
})
|
||||||
// will update session state at root of app
|
// will update session state at root of app
|
||||||
await agent.resumeSession(agent.session!)
|
await refreshSession()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function useRequestEmailUpdate() {
|
export function useRequestEmailUpdate() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
return (await agent.com.atproto.server.requestEmailUpdate()).data
|
return await pdsClient.call(com.atproto.server.requestEmailUpdate)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function useRequestEmailVerification() {
|
export function useRequestEmailVerification() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await agent.com.atproto.server.requestEmailConfirmation()
|
await pdsClient.call(com.atproto.server.requestEmailConfirmation)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
|
import {type Client} from '@atproto/lex-client'
|
||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient, useSessionApi} from '#/state/session'
|
||||||
import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'
|
import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
async function updateEmailAndRefreshSession(
|
async function updateEmailAndRefreshSession(
|
||||||
agent: ReturnType<typeof useAgent>,
|
pdsClient: Client,
|
||||||
|
refreshSession: () => Promise<unknown>,
|
||||||
email: string,
|
email: string,
|
||||||
token?: string,
|
token?: string,
|
||||||
) {
|
) {
|
||||||
await agent.com.atproto.server.updateEmail({email: email.trim(), token})
|
await pdsClient.call(com.atproto.server.updateEmail, {
|
||||||
await agent.resumeSession(agent.session!)
|
email: email.trim(),
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
await refreshSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUpdateEmail() {
|
export function useUpdateEmail() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
const {refreshSession} = useSessionApi()
|
||||||
const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate()
|
const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate()
|
||||||
|
|
||||||
return useMutation<
|
return useMutation<
|
||||||
@@ -23,7 +30,12 @@ export function useUpdateEmail() {
|
|||||||
>({
|
>({
|
||||||
mutationFn: async ({email, token}: {email: string; token?: string}) => {
|
mutationFn: async ({email, token}: {email: string; token?: string}) => {
|
||||||
if (token) {
|
if (token) {
|
||||||
await updateEmailAndRefreshSession(agent, email, token)
|
await updateEmailAndRefreshSession(
|
||||||
|
pdsClient,
|
||||||
|
refreshSession,
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
}
|
}
|
||||||
@@ -34,7 +46,12 @@ export function useUpdateEmail() {
|
|||||||
status: 'tokenRequired',
|
status: 'tokenRequired',
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await updateEmailAndRefreshSession(agent, email, token)
|
await updateEmailAndRefreshSession(
|
||||||
|
pdsClient,
|
||||||
|
refreshSession,
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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'
|
||||||
|
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {usePdsClient, useSession} from '#/state/session'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
@@ -16,6 +16,7 @@ import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function VerifyEmailIntentDialog() {
|
export function VerifyEmailIntentDialog() {
|
||||||
const {verifyEmailDialogControl: control} = useIntentDialogs()
|
const {verifyEmailDialogControl: control} = useIntentDialogs()
|
||||||
@@ -37,7 +38,7 @@ function Inner({}: {control: DialogControlProps}) {
|
|||||||
'loading' | 'success' | 'failure' | 'resent'
|
'loading' | 'success' | 'failure' | 'resent'
|
||||||
>('loading')
|
>('loading')
|
||||||
const [sending, setSending] = useState(false)
|
const [sending, setSending] = useState(false)
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {mutate: confirmEmail} = useConfirmEmail({
|
const {mutate: confirmEmail} = useConfirmEmail({
|
||||||
onSuccess: () => setStatus('success'),
|
onSuccess: () => setStatus('success'),
|
||||||
@@ -52,7 +53,7 @@ function Inner({}: {control: DialogControlProps}) {
|
|||||||
|
|
||||||
const onPressResendEmail = async () => {
|
const onPressResendEmail = async () => {
|
||||||
setSending(true)
|
setSending(true)
|
||||||
await agent.com.atproto.server.requestEmailConfirmation()
|
await pdsClient.call(com.atproto.server.requestEmailConfirmation)
|
||||||
setSending(false)
|
setSending(false)
|
||||||
setStatus('resent')
|
setStatus('resent')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {
|
||||||
|
accountReportSubject,
|
||||||
|
recordReportSubject,
|
||||||
|
} from '#/components/moderation/ReportDialog/utils/reportSubject'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_ANDROID} from '#/env'
|
import {IS_ANDROID} from '#/env'
|
||||||
import {com, tools} from '#/lexicons'
|
import {com, tools} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export function AppealForm({
|
export function AppealForm({
|
||||||
label,
|
label,
|
||||||
@@ -39,7 +42,6 @@ export function AppealForm({
|
|||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const [details, setDetails] = useState('')
|
const [details, setDetails] = useState('')
|
||||||
const {subject} = useLabelSubject({label})
|
const {subject} = useLabelSubject({label})
|
||||||
const isAccountReport = 'did' in subject
|
|
||||||
const pdsClient = usePdsClient()
|
const pdsClient = usePdsClient()
|
||||||
const sourceName = labeler
|
const sourceName = labeler
|
||||||
? sanitizeHandle(labeler.creator.handle, '@')
|
? sanitizeHandle(labeler.creator.handle, '@')
|
||||||
@@ -48,19 +50,16 @@ export function AppealForm({
|
|||||||
|
|
||||||
const {mutate, isPending} = useMutation({
|
const {mutate, isPending} = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const $type = !isAccountReport
|
|
||||||
? 'com.atproto.repo.strongRef'
|
|
||||||
: 'com.atproto.admin.defs#repoRef'
|
|
||||||
await pdsClient.call(
|
await pdsClient.call(
|
||||||
com.atproto.moderation.createReport,
|
com.atproto.moderation.createReport,
|
||||||
toLex<com.atproto.moderation.createReport.$InputBody>({
|
{
|
||||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||||
subject: {
|
subject:
|
||||||
$type,
|
'did' in subject
|
||||||
...subject,
|
? accountReportSubject(subject.did)
|
||||||
},
|
: recordReportSubject(subject.uri, subject.cid),
|
||||||
reason: details,
|
reason: details,
|
||||||
}),
|
},
|
||||||
{
|
{
|
||||||
service: `${label.src}#atproto_labeler` as Service,
|
service: `${label.src}#atproto_labeler` as Service,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ import {useMutation} from '@tanstack/react-query'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {usePdsClient} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
import {com} from '#/lexicons'
|
import {com} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
import {NEW_TO_OLD_REASONS_MAP} from './const'
|
import {NEW_TO_OLD_REASONS_MAP} from './const'
|
||||||
import {type ReportState} from './state'
|
import {type ReportState} from './state'
|
||||||
import {type ParsedReportSubject} from './types'
|
import {type ParsedReportSubject} from './types'
|
||||||
|
import {
|
||||||
|
accountReportSubject,
|
||||||
|
chatReportSubject,
|
||||||
|
recordReportSubject,
|
||||||
|
} from './utils/reportSubject'
|
||||||
|
|
||||||
type CreateReportBody = com.atproto.moderation.createReport.$InputBody
|
type CreateReportBody = com.atproto.moderation.createReport.$InputBody
|
||||||
|
|
||||||
@@ -54,25 +58,20 @@ export function useSubmitReportMutation() {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* The generated `createReport` subject union only declares repoRef and
|
* The generated `createReport` subject union only declares repoRef and
|
||||||
* strongRef with branded did/uri strings; chat subjects (message/convo
|
* strongRef; chat subjects (message/convo refs) are accepted on the wire
|
||||||
* refs) are accepted on the wire but not in the lexicon, and the subject
|
* but not in the lexicon. The builders in `./utils/reportSubject` brand
|
||||||
* ids we hold here are plain strings. We build the body against a loose
|
* the plain-string ids into the schema `subject` slot, keeping the
|
||||||
* subject shape and `toLex` it to the schema body at the call boundary
|
* runtime values exact and confining the chat-subject assertion to one
|
||||||
* (matching the old widened-InputSchema shape).
|
* place.
|
||||||
*/
|
*/
|
||||||
let report: Omit<CreateReportBody, 'subject'> & {
|
let report: CreateReportBody
|
||||||
subject: {$type: string} & Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (subject.type) {
|
switch (subject.type) {
|
||||||
case 'account': {
|
case 'account': {
|
||||||
report = {
|
report = {
|
||||||
reasonType,
|
reasonType,
|
||||||
reason: state.details,
|
reason: state.details,
|
||||||
subject: {
|
subject: accountReportSubject(subject.did),
|
||||||
$type: 'com.atproto.admin.defs#repoRef',
|
|
||||||
did: subject.did,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -84,11 +83,7 @@ export function useSubmitReportMutation() {
|
|||||||
report = {
|
report = {
|
||||||
reasonType,
|
reasonType,
|
||||||
reason: state.details,
|
reason: state.details,
|
||||||
subject: {
|
subject: recordReportSubject(subject.uri, subject.cid),
|
||||||
$type: 'com.atproto.repo.strongRef',
|
|
||||||
uri: subject.uri,
|
|
||||||
cid: subject.cid,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -96,12 +91,12 @@ export function useSubmitReportMutation() {
|
|||||||
report = {
|
report = {
|
||||||
reasonType,
|
reasonType,
|
||||||
reason: state.details,
|
reason: state.details,
|
||||||
subject: {
|
subject: chatReportSubject({
|
||||||
$type: 'chat.bsky.convo.defs#messageRef',
|
$type: 'chat.bsky.convo.defs#messageRef',
|
||||||
messageId: subject.message.id,
|
messageId: subject.message.id,
|
||||||
convoId: subject.convoId,
|
convoId: subject.convoId,
|
||||||
did: subject.message.sender.did,
|
did: subject.message.sender.did,
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -109,11 +104,11 @@ export function useSubmitReportMutation() {
|
|||||||
report = {
|
report = {
|
||||||
reasonType,
|
reasonType,
|
||||||
reason: state.details,
|
reason: state.details,
|
||||||
subject: {
|
subject: chatReportSubject({
|
||||||
$type: 'chat.bsky.convo.defs#convoRef',
|
$type: 'chat.bsky.convo.defs#convoRef',
|
||||||
convoId: subject.convoId,
|
convoId: subject.convoId,
|
||||||
did: subject.did,
|
did: subject.did,
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -133,13 +128,9 @@ export function useSubmitReportMutation() {
|
|||||||
* per-call `service` option (previously an explicit header on the
|
* per-call `service` option (previously an explicit header on the
|
||||||
* bridge agent).
|
* bridge agent).
|
||||||
*/
|
*/
|
||||||
await pdsClient.call(
|
await pdsClient.call(com.atproto.moderation.createReport, report, {
|
||||||
com.atproto.moderation.createReport,
|
|
||||||
toLex<CreateReportBody>(report),
|
|
||||||
{
|
|
||||||
service: `${labeler.creator.did}#atproto_labeler` as Service,
|
service: `${labeler.creator.did}#atproto_labeler` as Service,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import {
|
||||||
|
accountReportSubject,
|
||||||
|
chatReportSubject,
|
||||||
|
recordReportSubject,
|
||||||
|
} from '#/components/moderation/ReportDialog/utils/reportSubject'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These tests pin the wire shape of report subjects. The builders exist only
|
||||||
|
* to brand plain strings into the generated `createReport` union; the runtime
|
||||||
|
* object must stay byte-identical to the inline literals the consumers used
|
||||||
|
* before the migration. Each expected object is hand-written (not derived from
|
||||||
|
* the builder) so a shape change fails the test.
|
||||||
|
*/
|
||||||
|
describe('reportSubject builders', () => {
|
||||||
|
it('accountReportSubject produces a repoRef', () => {
|
||||||
|
expect(accountReportSubject('did:plc:abc123')).toEqual({
|
||||||
|
$type: 'com.atproto.admin.defs#repoRef',
|
||||||
|
did: 'did:plc:abc123',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('recordReportSubject produces a strongRef', () => {
|
||||||
|
expect(
|
||||||
|
recordReportSubject(
|
||||||
|
'at://did:plc:abc123/app.bsky.feed.post/xyz',
|
||||||
|
'bafyreiexamplecid',
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
$type: 'com.atproto.repo.strongRef',
|
||||||
|
uri: 'at://did:plc:abc123/app.bsky.feed.post/xyz',
|
||||||
|
cid: 'bafyreiexamplecid',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chatReportSubject preserves a messageRef verbatim', () => {
|
||||||
|
expect(
|
||||||
|
chatReportSubject({
|
||||||
|
$type: 'chat.bsky.convo.defs#messageRef',
|
||||||
|
messageId: 'msg-1',
|
||||||
|
convoId: 'convo-1',
|
||||||
|
did: 'did:plc:sender',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
$type: 'chat.bsky.convo.defs#messageRef',
|
||||||
|
messageId: 'msg-1',
|
||||||
|
convoId: 'convo-1',
|
||||||
|
did: 'did:plc:sender',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chatReportSubject preserves a convoRef verbatim', () => {
|
||||||
|
expect(
|
||||||
|
chatReportSubject({
|
||||||
|
$type: 'chat.bsky.convo.defs#convoRef',
|
||||||
|
convoId: 'convo-1',
|
||||||
|
did: 'did:plc:owner',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
$type: 'chat.bsky.convo.defs#convoRef',
|
||||||
|
convoId: 'convo-1',
|
||||||
|
did: 'did:plc:owner',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import {type com} from '#/lexicons'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `subject` union of the generated `createReport` input body. The lexicon
|
||||||
|
* declares only `com.atproto.admin.defs#repoRef` and `com.atproto.repo.strongRef`
|
||||||
|
* with branded string fields (`did: l.DidString`, `uri: l.AtUriString`,
|
||||||
|
* `cid: l.CidString`).
|
||||||
|
*/
|
||||||
|
type ReportSubject = com.atproto.moderation.createReport.$InputBody['subject']
|
||||||
|
|
||||||
|
/** The repoRef arm of the subject union, carrying the branded `did`. */
|
||||||
|
type RepoRefSubject = Extract<
|
||||||
|
ReportSubject,
|
||||||
|
{$type: 'com.atproto.admin.defs#repoRef'}
|
||||||
|
>
|
||||||
|
|
||||||
|
/** The strongRef arm of the subject union, carrying the branded `uri`/`cid`. */
|
||||||
|
type StrongRefSubject = Extract<
|
||||||
|
ReportSubject,
|
||||||
|
{$type: 'com.atproto.repo.strongRef'}
|
||||||
|
>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branded repoRef subject from a plain did string. The app holds dids as plain
|
||||||
|
* strings; brand the single field to the lexicon's `did` slot.
|
||||||
|
*/
|
||||||
|
export function accountReportSubject(did: string): ReportSubject {
|
||||||
|
return {
|
||||||
|
$type: 'com.atproto.admin.defs#repoRef',
|
||||||
|
did: did as RepoRefSubject['did'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branded strongRef subject from plain uri/cid strings. The app holds these as
|
||||||
|
* plain strings; brand `uri` to the lexicon's `uri` slot (`cid` is a plain
|
||||||
|
* string in the generated type, so it needs no assertion).
|
||||||
|
*/
|
||||||
|
export function recordReportSubject(uri: string, cid: string): ReportSubject {
|
||||||
|
return {
|
||||||
|
$type: 'com.atproto.repo.strongRef',
|
||||||
|
uri: uri as StrongRefSubject['uri'],
|
||||||
|
cid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat subjects (messageRef/convoRef) are accepted by the moderation service on
|
||||||
|
* the wire but are not part of the `createReport` lexicon union. This is the ONE
|
||||||
|
* place that asserts them into the body type - keep the runtime value exact.
|
||||||
|
*/
|
||||||
|
export function chatReportSubject(
|
||||||
|
v:
|
||||||
|
| {
|
||||||
|
$type: 'chat.bsky.convo.defs#messageRef'
|
||||||
|
messageId: string
|
||||||
|
convoId: string
|
||||||
|
did: string
|
||||||
|
}
|
||||||
|
| {$type: 'chat.bsky.convo.defs#convoRef'; convoId: string; did: string},
|
||||||
|
): ReportSubject {
|
||||||
|
return v as unknown as ReportSubject
|
||||||
|
}
|
||||||
+14
-4
@@ -1,10 +1,17 @@
|
|||||||
import {type Insets, Platform} from 'react-native'
|
import {type Insets, Platform} from 'react-native'
|
||||||
|
import {type Service} from '@atproto/lex-client'
|
||||||
import {api} from '@bsky.app/sdk'
|
import {api} from '@bsky.app/sdk'
|
||||||
|
|
||||||
import {type ProxyHeaderValue} from '#/state/session/agent'
|
|
||||||
import {BLUESKY_PROXY_DID, IS_DEV} from '#/env'
|
import {BLUESKY_PROXY_DID, IS_DEV} from '#/env'
|
||||||
import {type app} from '#/lexicons'
|
import {type app} from '#/lexicons'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `atproto-proxy` header value: a DID plus a service fragment, e.g.
|
||||||
|
* `did:web:api.bsky.app#bsky_appview`. Kept local to this module (previously
|
||||||
|
* lived in the now-removed session `agent.ts` compat layer).
|
||||||
|
*/
|
||||||
|
type ProxyHeaderValue = `did:${string}:${string}#${string}`
|
||||||
|
|
||||||
export const LOCAL_DEV_SERVICE =
|
export const LOCAL_DEV_SERVICE =
|
||||||
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
|
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
|
||||||
export const STAGING_SERVICE = 'https://staging.bsky.dev'
|
export const STAGING_SERVICE = 'https://staging.bsky.dev'
|
||||||
@@ -251,9 +258,12 @@ export const BLUESKY_MOD_SERVICE_HEADERS = {
|
|||||||
'atproto-proxy': `${api.moderation.did}#atproto_labeler`,
|
'atproto-proxy': `${api.moderation.did}#atproto_labeler`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BLUESKY_NOTIF_SERVICE_HEADERS = {
|
/**
|
||||||
'atproto-proxy': `${BLUESKY_PROXY_DID}#bsky_notif`,
|
* Service proxy identifier for the notification/entryway service. Passed as the
|
||||||
}
|
* per-call `service` option on the account client so lex-client emits the
|
||||||
|
* `atproto-proxy` header (replaces the old `BLUESKY_NOTIF_SERVICE_HEADERS`).
|
||||||
|
*/
|
||||||
|
export const NOTIF_SERVICE = `${BLUESKY_PROXY_DID}#bsky_notif` as Service
|
||||||
|
|
||||||
export const webLinks = {
|
export const webLinks = {
|
||||||
tos: `https://bsky.social/about/support/tos`,
|
tos: `https://bsky.social/about/support/tos`,
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {Client} from '@atproto/lex-client'
|
||||||
|
import {describe, expect, it} from '@jest/globals'
|
||||||
|
|
||||||
|
import {NOTIF_SERVICE} from '#/lib/constants'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Proxy-emission guard for the push-notification register/unregister calls.
|
||||||
|
*
|
||||||
|
* registerPush/unregisterPush move from an explicit `atproto-proxy` header on
|
||||||
|
* the old bridge agent to a per-call `service` option on the lex account
|
||||||
|
* client (see notifications.ts). This test proves the notif service DID
|
||||||
|
* actually reaches the wire as the `atproto-proxy` header when that option is
|
||||||
|
* used, so a wrong proxy target cannot fail silently (design Risk #2).
|
||||||
|
*
|
||||||
|
* The technique mirrors clients-bundle-test.ts: build a Client over a fake
|
||||||
|
* `fetchHandler` agent (no session/native chain), issue a real `Client.call`
|
||||||
|
* with the same per-call `service: NOTIF_SERVICE` option the notification
|
||||||
|
* calls use, and assert the emitted request header. Procedure request bodies
|
||||||
|
* cannot be encoded under the jest CID interop, so the call uses a query - the
|
||||||
|
* `service` -> `atproto-proxy` header path is shared by queries and procedures
|
||||||
|
* alike, so this faithfully exercises what registerPush/unregisterPush emit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DID = 'did:plc:example123'
|
||||||
|
const HANDLE = 'alice.test'
|
||||||
|
const SERVICE_ORIGIN = 'https://bsky.social'
|
||||||
|
|
||||||
|
function makeCapturingClient() {
|
||||||
|
const seen: {path: string; headers: Headers}[] = []
|
||||||
|
const client = new Client({
|
||||||
|
did: DID,
|
||||||
|
fetchHandler: (path, init) => {
|
||||||
|
seen.push({path, headers: new Headers(init.headers)})
|
||||||
|
return Promise.resolve(
|
||||||
|
new Response(JSON.stringify({did: DID, handle: HANDLE}), {
|
||||||
|
status: 200,
|
||||||
|
headers: {'content-type': 'application/json'},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return {seen, client}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('notifications proxy emission', () => {
|
||||||
|
it('NOTIF_SERVICE targets the notif service fragment', () => {
|
||||||
|
/* the constant is the single source of the proxy DID reaching the wire */
|
||||||
|
expect(NOTIF_SERVICE).toMatch(/#bsky_notif$/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits atproto-proxy: <NOTIF_SERVICE> when the per-call service option is set', async () => {
|
||||||
|
const {seen, client} = makeCapturingClient()
|
||||||
|
|
||||||
|
await client
|
||||||
|
.call(
|
||||||
|
app.bsky.actor.getProfile,
|
||||||
|
{actor: HANDLE},
|
||||||
|
{service: NOTIF_SERVICE},
|
||||||
|
)
|
||||||
|
.catch(() => {})
|
||||||
|
|
||||||
|
expect(seen.length).toBe(1)
|
||||||
|
expect(seen[0].headers.get('atproto-proxy')).toBe(NOTIF_SERVICE)
|
||||||
|
/* the account origin is never the proxy target */
|
||||||
|
expect(seen[0].headers.get('atproto-proxy')).not.toContain(SERVICE_ORIGIN)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,34 +2,47 @@ import {useCallback, useEffect} from 'react'
|
|||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
|
import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
|
||||||
|
import {type Client} from '@atproto/lex-client'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BLUESKY_NOTIF_SERVICE_HEADERS,
|
NOTIF_SERVICE,
|
||||||
PUBLIC_APPVIEW_DID,
|
PUBLIC_APPVIEW_DID,
|
||||||
PUBLIC_STAGING_APPVIEW_DID,
|
PUBLIC_STAGING_APPVIEW_DID,
|
||||||
} from '#/lib/constants'
|
} from '#/lib/constants'
|
||||||
import {logger as notyLogger} from '#/lib/notifications/util'
|
import {logger as notyLogger} from '#/lib/notifications/util'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {type SessionAccount, useAgent, useSession} from '#/state/session'
|
import {type SessionAccount, usePdsClient, useSession} from '#/state/session'
|
||||||
import {type SessionAgent} from '#/state/session/session-core'
|
|
||||||
import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
|
import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
|
||||||
import {useAgeAssurance} from '#/ageAssurance'
|
import {useAgeAssurance} from '#/ageAssurance'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_DEV, IS_NATIVE} from '#/env'
|
import {IS_DEV, IS_NATIVE} from '#/env'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A resumed throwaway account client paired with the account's service origin
|
||||||
|
* and handle. Produced by `createTemporaryClientsAndResume` (session util) and
|
||||||
|
* consumed by {@link unregisterPushToken}, which needs the service host to pick
|
||||||
|
* the correct appview DID and the handle for a debug log line without reaching
|
||||||
|
* into the session internals.
|
||||||
|
*/
|
||||||
|
export type TemporaryPushClient = {
|
||||||
|
client: Client
|
||||||
|
service: string
|
||||||
|
handle: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* Registers the device's push notification token with the Bluesky server.
|
* Registers the device's push notification token with the Bluesky server.
|
||||||
*/
|
*/
|
||||||
async function _registerPushToken({
|
async function _registerPushToken({
|
||||||
agent,
|
client,
|
||||||
currentAccount,
|
currentAccount,
|
||||||
token,
|
token,
|
||||||
extra = {},
|
extra = {},
|
||||||
}: {
|
}: {
|
||||||
agent: SessionAgent
|
client: Client
|
||||||
currentAccount: SessionAccount
|
currentAccount: SessionAccount
|
||||||
token: Notifications.DevicePushToken
|
token: Notifications.DevicePushToken
|
||||||
extra?: {
|
extra?: {
|
||||||
@@ -49,8 +62,8 @@ async function _registerPushToken({
|
|||||||
|
|
||||||
notyLogger.debug(`registerPushToken: registering`, {...payload})
|
notyLogger.debug(`registerPushToken: registering`, {...payload})
|
||||||
|
|
||||||
await agent.app.bsky.notification.registerPush(payload, {
|
await client.call(app.bsky.notification.registerPush, payload, {
|
||||||
headers: BLUESKY_NOTIF_SERVICE_HEADERS,
|
service: NOTIF_SERVICE,
|
||||||
})
|
})
|
||||||
|
|
||||||
notyLogger.debug(`registerPushToken: success`)
|
notyLogger.debug(`registerPushToken: success`)
|
||||||
@@ -75,7 +88,7 @@ const _registerPushTokenDebounced = debounce(_registerPushToken, 100)
|
|||||||
* `_registerPushTokenDebounced` directly.
|
* `_registerPushTokenDebounced` directly.
|
||||||
*/
|
*/
|
||||||
export function useRegisterPushToken() {
|
export function useRegisterPushToken() {
|
||||||
const agent = useAgent()
|
const client = usePdsClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
@@ -88,7 +101,7 @@ export function useRegisterPushToken() {
|
|||||||
}) => {
|
}) => {
|
||||||
if (!currentAccount) return
|
if (!currentAccount) return
|
||||||
return _registerPushTokenDebounced({
|
return _registerPushTokenDebounced({
|
||||||
agent,
|
client,
|
||||||
currentAccount,
|
currentAccount,
|
||||||
token,
|
token,
|
||||||
extra: {
|
extra: {
|
||||||
@@ -96,7 +109,7 @@ export function useRegisterPushToken() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[agent, currentAccount],
|
[client, currentAccount],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,16 +340,17 @@ export async function resetBadgeCount() {
|
|||||||
await setBadgeCountAsync(0)
|
await setBadgeCountAsync(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unregisterPushToken(agents: SessionAgent[]) {
|
export async function unregisterPushToken(clients: TemporaryPushClient[]) {
|
||||||
if (!IS_NATIVE) return
|
if (!IS_NATIVE) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await getPushToken()
|
const token = await getPushToken()
|
||||||
if (token) {
|
if (token) {
|
||||||
for (const agent of agents) {
|
for (const {client, service, handle} of clients) {
|
||||||
await agent.app.bsky.notification.unregisterPush(
|
await client.call(
|
||||||
|
app.bsky.notification.unregisterPush,
|
||||||
{
|
{
|
||||||
serviceDid: agent.serviceUrl.hostname.includes('staging')
|
serviceDid: service.includes('staging')
|
||||||
? PUBLIC_STAGING_APPVIEW_DID
|
? PUBLIC_STAGING_APPVIEW_DID
|
||||||
: PUBLIC_APPVIEW_DID,
|
: PUBLIC_APPVIEW_DID,
|
||||||
platform: Platform.OS,
|
platform: Platform.OS,
|
||||||
@@ -344,10 +358,10 @@ export async function unregisterPushToken(agents: SessionAgent[]) {
|
|||||||
appId: 'xyz.blueskyweb.app',
|
appId: 'xyz.blueskyweb.app',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: BLUESKY_NOTIF_SERVICE_HEADERS,
|
service: NOTIF_SERVICE,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
notyLogger.debug(`Push token unregistered for ${agent.session?.handle}`)
|
notyLogger.debug(`Push token unregistered for ${handle}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notyLogger.debug('Tried to unregister push token, but could not find one')
|
notyLogger.debug('Tried to unregister push token, but could not find one')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
type SessionAccount,
|
type SessionAccount,
|
||||||
useAgent,
|
usePdsClient,
|
||||||
useSession,
|
useSession,
|
||||||
useSessionApi,
|
useSessionApi,
|
||||||
} from '#/state/session'
|
} from '#/state/session'
|
||||||
@@ -25,6 +25,7 @@ import * as Layout from '#/components/Layout'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
const COL_WIDTH = 400
|
const COL_WIDTH = 400
|
||||||
|
|
||||||
@@ -36,8 +37,8 @@ export function Deactivated() {
|
|||||||
const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()
|
const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const hasOtherAccounts = accounts.length > 1
|
const hasOtherAccounts = accounts.length > 1
|
||||||
const {logoutCurrentAccount} = useSessionApi()
|
const {logoutCurrentAccount, refreshSession} = useSessionApi()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const [pending, setPending] = useState(false)
|
const [pending, setPending] = useState(false)
|
||||||
const [error, setError] = useState<string | undefined>()
|
const [error, setError] = useState<string | undefined>()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -70,9 +71,9 @@ export function Deactivated() {
|
|||||||
const handleActivate = useCallback(async () => {
|
const handleActivate = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setPending(true)
|
setPending(true)
|
||||||
await agent.com.atproto.server.activateAccount()
|
await pdsClient.call(com.atproto.server.activateAccount)
|
||||||
await queryClient.resetQueries()
|
await queryClient.resetQueries()
|
||||||
await agent.resumeSession(agent.session!)
|
await refreshSession()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
switch (e.message) {
|
switch (e.message) {
|
||||||
case 'Bad token scope':
|
case 'Bad token scope':
|
||||||
@@ -93,7 +94,7 @@ export function Deactivated() {
|
|||||||
} finally {
|
} finally {
|
||||||
setPending(false)
|
setPending(false)
|
||||||
}
|
}
|
||||||
}, [_, agent, setPending, setError, queryClient])
|
}, [_, pdsClient, refreshSession, setPending, setError, queryClient])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.util_screen_outer, a.flex_1]}>
|
<View style={[a.util_screen_outer, a.flex_1]}>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {useCallback, useState} from 'react'
|
import {useCallback, useState} from 'react'
|
||||||
import {Keyboard, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
|
import {Client} from '@atproto/lex-client'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import * as EmailValidator from 'email-validator'
|
import * as EmailValidator from 'email-validator'
|
||||||
|
|
||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Agent} from '#/state/session/agent'
|
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -15,7 +15,7 @@ import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {type com} from '#/lexicons'
|
import {com} from '#/lexicons'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
type ServiceDescription = com.atproto.server.describeServer.$OutputBody
|
type ServiceDescription = com.atproto.server.describeServer.$OutputBody
|
||||||
@@ -55,8 +55,8 @@ export const ForgotPasswordForm = ({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const agent = new Agent(null, {service: serviceUrl})
|
const client = new Client({service: serviceUrl})
|
||||||
await agent.com.atproto.server.requestPasswordReset({email})
|
await client.call(com.atproto.server.requestPasswordReset, {email})
|
||||||
onEmailSent()
|
onEmailSent()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Failed to request password reset', {error: err})
|
logger.warn('Failed to request password reset', {error: err})
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import {Client} from '@atproto/lex-client'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Agent} from '#/state/session/agent'
|
|
||||||
import {atoms as a, web} from '#/alf'
|
import {atoms as a, web} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -16,6 +16,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 {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
export const SetNewPasswordForm = ({
|
export const SetNewPasswordForm = ({
|
||||||
@@ -61,8 +62,8 @@ export const SetNewPasswordForm = ({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const agent = new Agent(null, {service: serviceUrl})
|
const client = new Client({service: serviceUrl})
|
||||||
await agent.com.atproto.server.resetPassword({
|
await client.call(com.atproto.server.resetPassword, {
|
||||||
token: formattedCode,
|
token: formattedCode,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -169,16 +169,14 @@ function keyExtractor(item: Item) {
|
|||||||
return item.key
|
return item.key
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The member list now comes from the migrated lexicon-typed query, but the
|
* Narrows a lexicon `ProfileViewBasic` member to a `GroupConvoMember` (a
|
||||||
* narrowed `GroupConvoMember` target is still the old-typed shape from
|
* `ProfileViewBasic` whose `kind` is a group member, or absent when the
|
||||||
* `#/components/dms/util` (migrates in a later task) - the guard doubles as
|
* account has been deleted).
|
||||||
* the mixed-world bridge. TODO(phase4): retype to the lexicon member types
|
|
||||||
* once dms/util migrates.
|
|
||||||
*/
|
*/
|
||||||
function isGroupMember(
|
function isGroupMember(
|
||||||
member: chat.bsky.actor.defs.ProfileViewBasic,
|
member: chat.bsky.actor.defs.ProfileViewBasic,
|
||||||
): member is chat.bsky.actor.defs.ProfileViewBasic & GroupConvoMember {
|
): member is GroupConvoMember {
|
||||||
// Kind is missing when the account has been deleted.
|
// Kind is missing when the account has been deleted.
|
||||||
return (
|
return (
|
||||||
member.kind === undefined ||
|
member.kind === undefined ||
|
||||||
@@ -205,15 +203,7 @@ function GroupSettings({
|
|||||||
|
|
||||||
const {data: memberListData = [], refetch} = useListConvoMembersQuery({
|
const {data: memberListData = [], refetch} = useListConvoMembersQuery({
|
||||||
convoId: convo.view.id,
|
convoId: convo.view.id,
|
||||||
/*
|
placeholderData: convo.members,
|
||||||
* `convo.members` comes from the still-old-typed `#/components/dms/util`
|
|
||||||
* (migrates in a later task) while the member-list query is now typed on
|
|
||||||
* the lexicon ProfileViewBasic. TODO(phase4): drop toLex once dms/util
|
|
||||||
* migrates.
|
|
||||||
*/
|
|
||||||
placeholderData: bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
|
||||||
convo.members,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
|
const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {accountReportSubject} from '#/components/moderation/ReportDialog/utils/reportSubject'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {com, tools} from '#/lexicons'
|
import {com, tools} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export function ChatDisabled({
|
export function ChatDisabled({
|
||||||
shape = 'pill',
|
shape = 'pill',
|
||||||
@@ -102,14 +102,11 @@ function DialogInner() {
|
|||||||
throw new Error('No current account, should be unreachable')
|
throw new Error('No current account, should be unreachable')
|
||||||
await pdsClient.call(
|
await pdsClient.call(
|
||||||
com.atproto.moderation.createReport,
|
com.atproto.moderation.createReport,
|
||||||
toLex<com.atproto.moderation.createReport.$InputBody>({
|
{
|
||||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||||
subject: {
|
subject: accountReportSubject(currentAccount.did),
|
||||||
$type: 'com.atproto.admin.defs#repoRef',
|
|
||||||
did: currentAccount.did,
|
|
||||||
},
|
|
||||||
reason: details,
|
reason: details,
|
||||||
}),
|
},
|
||||||
{
|
{
|
||||||
service: api.moderation.service,
|
service: api.moderation.service,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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 {type DidString} from '@atproto/syntax'
|
||||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -28,12 +29,7 @@ import {
|
|||||||
useContactsMatchesQuery,
|
useContactsMatchesQuery,
|
||||||
useContactsSyncStatusQuery,
|
useContactsSyncStatusQuery,
|
||||||
} from '#/state/queries/find-contacts'
|
} from '#/state/queries/find-contacts'
|
||||||
import {
|
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||||
useAgent,
|
|
||||||
useAppviewClient,
|
|
||||||
usePdsClient,
|
|
||||||
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'
|
||||||
@@ -196,7 +192,7 @@ function SyncStatus({
|
|||||||
refetchStatus: () => Promise<any>
|
refetchStatus: () => Promise<any>
|
||||||
}) {
|
}) {
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
@@ -221,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 appviewClient.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', {})
|
||||||
@@ -493,12 +491,12 @@ 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 appviewClient = 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 appviewClient.call(app.bsky.contact.removeData, {})
|
||||||
},
|
},
|
||||||
onMutate: () => ax.metric('contacts:settings:removeData', {}),
|
onMutate: () => ax.metric('contacts:settings:removeData', {}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {useMemo, useState} from 'react'
|
import {useMemo, useState} from 'react'
|
||||||
import {type TextStyle, View, type ViewStyle} from 'react-native'
|
import {type TextStyle, View, type ViewStyle} from 'react-native'
|
||||||
|
import {setInterestsPref} from '@bsky.app/sdk'
|
||||||
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'
|
||||||
@@ -23,7 +24,7 @@ import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trendi
|
|||||||
import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery'
|
import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery'
|
||||||
import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'
|
import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'
|
||||||
import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
|
import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
@@ -88,7 +89,7 @@ function Inner({
|
|||||||
setIsSaving: (isSaving: boolean) => void
|
setIsSaving: (isSaving: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
const interestsDisplayNames = useInterestsDisplayNames()
|
const interestsDisplayNames = useInterestsDisplayNames()
|
||||||
const preselectedInterests = useMemo(
|
const preselectedInterests = useMemo(
|
||||||
@@ -110,7 +111,7 @@ function Inner({
|
|||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await agent.setInterestsPref({tags: interests})
|
await pdsClient.call(setInterestsPref, {tags: interests})
|
||||||
qc.setQueriesData(
|
qc.setQueriesData(
|
||||||
{queryKey: preferencesQueryKey},
|
{queryKey: preferencesQueryKey},
|
||||||
(old?: UsePreferencesQueryResponse) => {
|
(old?: UsePreferencesQueryResponse) => {
|
||||||
@@ -157,7 +158,7 @@ function Inner({
|
|||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
}
|
}
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}, [_, agent, setIsSaving, qc, preselectedInterests])
|
}, [_, pdsClient, setIsSaving, qc, preselectedInterests])
|
||||||
|
|
||||||
const onChangeInterests = async (interests: string[]) => {
|
const onChangeInterests = async (interests: string[]) => {
|
||||||
setInterests(interests)
|
setInterests(interests)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
|
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
|
||||||
import {useReducedMotion} from 'react-native-reanimated'
|
import {useReducedMotion} from 'react-native-reanimated'
|
||||||
|
import {removeNuxs} from '@bsky.app/sdk'
|
||||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
@@ -21,8 +22,12 @@ import {clearStorage} from '#/state/persisted'
|
|||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
|
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
|
||||||
import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile'
|
import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile'
|
||||||
import {useAgent} from '#/state/session'
|
import {
|
||||||
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
type SessionAccount,
|
||||||
|
usePdsClient,
|
||||||
|
useSession,
|
||||||
|
useSessionApi,
|
||||||
|
} from '#/state/session'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
@@ -386,7 +391,7 @@ function ProfilePreview({
|
|||||||
|
|
||||||
function DevOptions() {
|
function DevOptions() {
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const [override, setOverride] = useStorage(device, [
|
const [override, setOverride] = useStorage(device, [
|
||||||
'policyUpdateDebugOverride',
|
'policyUpdateDebugOverride',
|
||||||
])
|
])
|
||||||
@@ -561,7 +566,7 @@ function DevOptions() {
|
|||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
device.set([PolicyUpdate202508], false)
|
device.set([PolicyUpdate202508], false)
|
||||||
void agent.bskyAppRemoveNuxs([PolicyUpdate202508])
|
void pdsClient.call(removeNuxs, [PolicyUpdate202508])
|
||||||
Toast.show(`Done`, {
|
Toast.show(`Done`, {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {useFetchDid, useUpdateHandleMutation} from '#/state/queries/handle'
|
|||||||
import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile'
|
import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile'
|
||||||
import {useServiceQuery} from '#/state/queries/service'
|
import {useServiceQuery} from '#/state/queries/service'
|
||||||
import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
|
import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useSession, useSessionApi} from '#/state/session'
|
||||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {atoms as a, native, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, native, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
@@ -63,12 +63,12 @@ export function ChangeHandleDialog({
|
|||||||
function ChangeHandleDialogInner() {
|
function ChangeHandleDialogInner() {
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const agent = useAgent()
|
const {currentAccount} = useSession()
|
||||||
const {
|
const {
|
||||||
data: serviceInfo,
|
data: serviceInfo,
|
||||||
error: serviceInfoError,
|
error: serviceInfoError,
|
||||||
refetch,
|
refetch,
|
||||||
} = useServiceQuery(agent.serviceUrl.toString())
|
} = useServiceQuery(currentAccount?.service ?? '')
|
||||||
|
|
||||||
const [page, setPage] = useState<'provided-handle' | 'own-handle'>(
|
const [page, setPage] = useState<'provided-handle' | 'own-handle'>(
|
||||||
'provided-handle',
|
'provided-handle',
|
||||||
@@ -152,7 +152,7 @@ function ProvidedHandlePage({
|
|||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const [subdomain, setSubdomain] = useState('')
|
const [subdomain, setSubdomain] = useState('')
|
||||||
const agent = useAgent()
|
const {refreshSession} = useSessionApi()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -173,7 +173,7 @@ function ProvidedHandlePage({
|
|||||||
queryKey: RQKEY_PROFILE(currentAccount.did),
|
queryKey: RQKEY_PROFILE(currentAccount.did),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
agent.resumeSession(agent.session!).then(() => control.close())
|
refreshSession().then(() => control.close())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ function OwnHandlePage({goToServiceHandle}: {goToServiceHandle: () => void}) {
|
|||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const [dnsPanel, setDNSPanel] = useState(true)
|
const [dnsPanel, setDNSPanel] = useState(true)
|
||||||
const [domain, setDomain] = useState('')
|
const [domain, setDomain] = useState('')
|
||||||
const agent = useAgent()
|
const {refreshSession} = useSessionApi()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const fetchDid = useFetchDid()
|
const fetchDid = useFetchDid()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -328,7 +328,7 @@ function OwnHandlePage({goToServiceHandle}: {goToServiceHandle: () => void}) {
|
|||||||
queryKey: RQKEY_PROFILE(currentAccount.did),
|
queryKey: RQKEY_PROFILE(currentAccount.did),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
agent.resumeSession(agent.session!).then(() => control.close())
|
refreshSession().then(() => control.close())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as EmailValidator from 'email-validator'
|
|||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {usePdsClient, useSession} from '#/state/session'
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {android, atoms as a, web} from '#/alf'
|
import {android, atoms as a, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -17,6 +17,7 @@ import * as TextField from '#/components/forms/TextField'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
enum Stages {
|
enum Stages {
|
||||||
RequestCode = 'RequestCode',
|
RequestCode = 'RequestCode',
|
||||||
@@ -44,7 +45,7 @@ export function ChangePasswordDialog({
|
|||||||
function Inner() {
|
function Inner() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
|
|
||||||
const [stage, setStage] = useState(Stages.RequestCode)
|
const [stage, setStage] = useState(Stages.RequestCode)
|
||||||
@@ -85,7 +86,7 @@ function Inner() {
|
|||||||
setError('')
|
setError('')
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
try {
|
try {
|
||||||
await agent.com.atproto.server.requestPasswordReset({
|
await pdsClient.call(com.atproto.server.requestPasswordReset, {
|
||||||
email: currentAccount.email,
|
email: currentAccount.email,
|
||||||
})
|
})
|
||||||
setStage(Stages.ChangePassword)
|
setStage(Stages.ChangePassword)
|
||||||
@@ -129,7 +130,7 @@ function Inner() {
|
|||||||
setError('')
|
setError('')
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
try {
|
try {
|
||||||
await agent.com.atproto.server.resetPassword({
|
await pdsClient.call(com.atproto.server.resetPassword, {
|
||||||
token: formattedCode,
|
token: formattedCode,
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useAgent, useSessionApi} from '#/state/session'
|
import {usePdsClient, useSessionApi} from '#/state/session'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {type DialogOuterProps} from '#/components/Dialog'
|
import {type DialogOuterProps} from '#/components/Dialog'
|
||||||
@@ -14,6 +14,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function DeactivateAccountDialog({
|
export function DeactivateAccountDialog({
|
||||||
control,
|
control,
|
||||||
@@ -34,7 +35,7 @@ function DeactivateAccountDialogInner({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const {logoutCurrentAccount} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const [pending, setPending] = useState(false)
|
const [pending, setPending] = useState(false)
|
||||||
const [error, setError] = useState<string | undefined>()
|
const [error, setError] = useState<string | undefined>()
|
||||||
@@ -42,7 +43,7 @@ function DeactivateAccountDialogInner({
|
|||||||
const handleDeactivate = useCallback(async () => {
|
const handleDeactivate = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setPending(true)
|
setPending(true)
|
||||||
await agent.com.atproto.server.deactivateAccount({})
|
await pdsClient.call(com.atproto.server.deactivateAccount, {})
|
||||||
control.close(() => {
|
control.close(() => {
|
||||||
logoutCurrentAccount('Deactivated')
|
logoutCurrentAccount('Deactivated')
|
||||||
})
|
})
|
||||||
@@ -66,7 +67,7 @@ function DeactivateAccountDialogInner({
|
|||||||
} finally {
|
} finally {
|
||||||
setPending(false)
|
setPending(false)
|
||||||
}
|
}
|
||||||
}, [agent, control, logoutCurrentAccount, _, setPending])
|
}, [pdsClient, control, logoutCurrentAccount, _, setPending])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {useCallback, useRef, useState} from 'react'
|
import {useCallback, useRef, useState} from 'react'
|
||||||
import {type TextInput, View} from 'react-native'
|
import {type TextInput, View} from 'react-native'
|
||||||
|
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 {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
@@ -8,8 +9,8 @@ import {useCleanError} from '#/lib/hooks/useCleanError'
|
|||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
useAgent,
|
|
||||||
useChatClient,
|
useChatClient,
|
||||||
|
usePdsClient,
|
||||||
useSession,
|
useSession,
|
||||||
useSessionApi,
|
useSessionApi,
|
||||||
} from '#/state/session'
|
} from '#/state/session'
|
||||||
@@ -28,7 +29,7 @@ import {Loader} from '#/components/Loader'
|
|||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import * as toast from '#/components/Toast'
|
import * as toast from '#/components/Toast'
|
||||||
import {Span, Text} from '#/components/Typography'
|
import {Span, Text} from '#/components/Typography'
|
||||||
import {chat} from '#/lexicons'
|
import {chat, com} from '#/lexicons'
|
||||||
import {resetToTab} from '#/Navigation'
|
import {resetToTab} from '#/Navigation'
|
||||||
|
|
||||||
const WHITESPACE_RE = /\s/gu
|
const WHITESPACE_RE = /\s/gu
|
||||||
@@ -77,7 +78,7 @@ function DeleteAccountDialogInner({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const cleanError = useCleanError()
|
const cleanError = useCleanError()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const chatClient = useChatClient()
|
const chatClient = useChatClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {removeAccount} = useSessionApi()
|
const {removeAccount} = useSessionApi()
|
||||||
@@ -95,7 +96,7 @@ function DeleteAccountDialogInner({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setEmailState(EmailState.PENDING)
|
setEmailState(EmailState.PENDING)
|
||||||
await agent.com.atproto.server.requestAccountDelete()
|
await pdsClient.call(com.atproto.server.requestAccountDelete)
|
||||||
setError('')
|
setError('')
|
||||||
setEmailSentCount(prevCount => prevCount + 1)
|
setEmailSentCount(prevCount => prevCount + 1)
|
||||||
setStep(Step.VERIFY_CODE)
|
setStep(Step.VERIFY_CODE)
|
||||||
@@ -109,7 +110,7 @@ function DeleteAccountDialogInner({
|
|||||||
} finally {
|
} finally {
|
||||||
setEmailState(EmailState.DEFAULT)
|
setEmailState(EmailState.DEFAULT)
|
||||||
}
|
}
|
||||||
}, [agent, cleanError, emailState, setEmailState])
|
}, [pdsClient, cleanError, emailState, setEmailState])
|
||||||
|
|
||||||
const confirmDeletion = useCallback(async () => {
|
const confirmDeletion = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -121,8 +122,8 @@ function DeleteAccountDialogInner({
|
|||||||
// Inform chat service of intent to delete account. The chat client is
|
// Inform chat service of intent to delete account. The chat client is
|
||||||
// proxied to the chat service; a failure throws.
|
// proxied to the chat service; a failure throws.
|
||||||
await chatClient.call(chat.bsky.actor.deleteAccount)
|
await chatClient.call(chat.bsky.actor.deleteAccount)
|
||||||
await agent.com.atproto.server.deleteAccount({
|
await pdsClient.call(com.atproto.server.deleteAccount, {
|
||||||
did: currentAccount.did,
|
did: currentAccount.did as DidString,
|
||||||
password,
|
password,
|
||||||
token,
|
token,
|
||||||
})
|
})
|
||||||
@@ -144,7 +145,8 @@ function DeleteAccountDialogInner({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
_,
|
_,
|
||||||
agent,
|
pdsClient,
|
||||||
|
chatClient,
|
||||||
cleanError,
|
cleanError,
|
||||||
confirmCode,
|
confirmCode,
|
||||||
control,
|
control,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -16,6 +16,7 @@ import {Loader} from '#/components/Loader'
|
|||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {P, Text} from '#/components/Typography'
|
import {P, Text} from '#/components/Typography'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
enum Stages {
|
enum Stages {
|
||||||
Email,
|
Email,
|
||||||
@@ -31,7 +32,8 @@ export function DisableEmail2FADialog({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
const {refreshSession} = useSessionApi()
|
||||||
|
|
||||||
const [stage, setStage] = useState<Stages>(Stages.Email)
|
const [stage, setStage] = useState<Stages>(Stages.Email)
|
||||||
const [confirmationCode, setConfirmationCode] = useState<string>('')
|
const [confirmationCode, setConfirmationCode] = useState<string>('')
|
||||||
@@ -42,7 +44,7 @@ export function DisableEmail2FADialog({
|
|||||||
setError('')
|
setError('')
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
try {
|
try {
|
||||||
await agent.com.atproto.server.requestEmailUpdate()
|
await pdsClient.call(com.atproto.server.requestEmailUpdate)
|
||||||
setStage(Stages.ConfirmCode)
|
setStage(Stages.ConfirmCode)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(cleanError(String(e)))
|
setError(cleanError(String(e)))
|
||||||
@@ -56,12 +58,12 @@ export function DisableEmail2FADialog({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
try {
|
try {
|
||||||
if (currentAccount?.email) {
|
if (currentAccount?.email) {
|
||||||
await agent.com.atproto.server.updateEmail({
|
await pdsClient.call(com.atproto.server.updateEmail, {
|
||||||
email: currentAccount.email,
|
email: currentAccount.email,
|
||||||
token: confirmationCode.trim(),
|
token: confirmationCode.trim(),
|
||||||
emailAuthFactor: false,
|
emailAuthFactor: false,
|
||||||
})
|
})
|
||||||
await agent.resumeSession(agent.session!)
|
await refreshSession()
|
||||||
Toast.show(_(msg({message: 'Email 2FA disabled', context: 'toast'})))
|
Toast.show(_(msg({message: 'Email 2FA disabled', context: 'toast'})))
|
||||||
}
|
}
|
||||||
control.close()
|
control.close()
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import {useCallback, useState} from 'react'
|
import {useCallback, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import {type DidString} from '@atproto/syntax'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {saveBytesToDisk} from '#/lib/media/manip'
|
import {saveBytesToDisk} from '#/lib/media/manip'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useAgent} from '#/state/session'
|
import {useChatClient, usePdsClient, useSession} from '#/state/session'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
@@ -13,7 +14,7 @@ import {InlineLinkText} from '#/components/Link'
|
|||||||
import {Loader} from '#/components/Loader'
|
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 {CHAT_PROXY_DID} from '#/env'
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
export function ExportCarDialog({
|
export function ExportCarDialog({
|
||||||
control,
|
control,
|
||||||
@@ -22,21 +23,28 @@ export function ExportCarDialog({
|
|||||||
}) {
|
}) {
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const agent = useAgent()
|
const {currentAccount} = useSession()
|
||||||
|
const pdsClient = usePdsClient()
|
||||||
|
const chatClient = useChatClient()
|
||||||
const [loading, setLoading] = useState<'repo' | 'chat' | false>(false)
|
const [loading, setLoading] = useState<'repo' | 'chat' | false>(false)
|
||||||
|
|
||||||
const download = useCallback(async () => {
|
const download = useCallback(async () => {
|
||||||
if (!agent.session) {
|
if (!currentAccount) {
|
||||||
return // shouldn't ever happen
|
return // shouldn't ever happen
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setLoading('repo')
|
setLoading('repo')
|
||||||
const did = agent.session.did
|
const did = currentAccount.did as DidString
|
||||||
const downloadRes = await agent.com.atproto.sync.getRepo({did})
|
const data = await pdsClient.call(com.atproto.sync.getRepo, {did})
|
||||||
|
/*
|
||||||
|
* getRepo returns raw bytes; the lex client does not surface the response
|
||||||
|
* content-type, and this endpoint always returns CAR data, so the constant
|
||||||
|
* matches the old header-derived value in practice.
|
||||||
|
*/
|
||||||
const saveRes = await saveBytesToDisk(
|
const saveRes = await saveBytesToDisk(
|
||||||
'repo.car',
|
'repo.car',
|
||||||
downloadRes.data,
|
data,
|
||||||
downloadRes.headers['content-type'] || 'application/vnd.ipld.car',
|
'application/vnd.ipld.car',
|
||||||
)
|
)
|
||||||
|
|
||||||
if (saveRes) {
|
if (saveRes) {
|
||||||
@@ -48,21 +56,24 @@ export function ExportCarDialog({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [l, agent])
|
}, [l, currentAccount, pdsClient])
|
||||||
|
|
||||||
const downloadChatData = useCallback(async () => {
|
const downloadChatData = useCallback(async () => {
|
||||||
if (!agent.session) {
|
if (!currentAccount) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setLoading('chat')
|
setLoading('chat')
|
||||||
// Using raw fetch because the XRPC client incorrectly tries to JSON-parse
|
/*
|
||||||
// application/jsonl responses (substring match on application/json).
|
* Using the client's low-level fetchHandler because the XRPC client
|
||||||
// The chat-service proxy header is inlined here (the endpoint is proxied
|
* incorrectly tries to JSON-parse application/jsonl responses (substring
|
||||||
// to `did:web:api.bsky.chat`); this raw path bypasses the lex client.
|
* match on application/json). The chat client already proxies to
|
||||||
const res = await agent.sessionManager.fetchHandler(
|
* `did:web:api.bsky.chat`, so no manual proxy header is needed - it would
|
||||||
|
* otherwise be double-set.
|
||||||
|
*/
|
||||||
|
const res = await chatClient.fetchHandler(
|
||||||
'/xrpc/chat.bsky.actor.exportAccountData',
|
'/xrpc/chat.bsky.actor.exportAccountData',
|
||||||
{headers: {'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`}},
|
{},
|
||||||
)
|
)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`HTTP ${res.status}`)
|
throw new Error(`HTTP ${res.status}`)
|
||||||
@@ -83,7 +94,7 @@ export function ExportCarDialog({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [l, agent])
|
}, [l, currentAccount, chatClient])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isSignupQueued, useAgent, useSessionApi} from '#/state/session'
|
import {isSignupQueued, usePdsClient, useSessionApi} from '#/state/session'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
@@ -15,6 +15,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {P, Text} from '#/components/Typography'
|
import {P, Text} from '#/components/Typography'
|
||||||
import {IS_IOS, IS_LIQUID_GLASS, IS_WEB} from '#/env'
|
import {IS_IOS, IS_LIQUID_GLASS, IS_WEB} from '#/env'
|
||||||
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
const COL_WIDTH = 400
|
const COL_WIDTH = 400
|
||||||
|
|
||||||
@@ -24,8 +25,8 @@ export function SignupQueued() {
|
|||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const {logoutCurrentAccount} = useSessionApi()
|
const {logoutCurrentAccount, refreshSession} = useSessionApi()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
const [isProcessing, setProcessing] = useState(false)
|
const [isProcessing, setProcessing] = useState(false)
|
||||||
const [estimatedTime, setEstimatedTime] = useState<string | undefined>(
|
const [estimatedTime, setEstimatedTime] = useState<string | undefined>(
|
||||||
@@ -38,18 +39,18 @@ export function SignupQueued() {
|
|||||||
const checkStatus = useCallback(async () => {
|
const checkStatus = useCallback(async () => {
|
||||||
setProcessing(true)
|
setProcessing(true)
|
||||||
try {
|
try {
|
||||||
const res = await agent.com.atproto.temp.checkSignupQueue()
|
const res = await pdsClient.call(com.atproto.temp.checkSignupQueue)
|
||||||
if (res.data.activated) {
|
if (res.activated) {
|
||||||
// ready to go, exchange the access token for a usable one and kick off onboarding
|
// ready to go, exchange the access token for a usable one and kick off onboarding
|
||||||
await agent.resumeSession()
|
const refreshed = await refreshSession()
|
||||||
if (!isSignupQueued(agent.session?.accessJwt)) {
|
if (!isSignupQueued(refreshed?.accessJwt)) {
|
||||||
onboardingDispatch({type: 'start'})
|
onboardingDispatch({type: 'start'})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// not ready, update UI
|
// not ready, update UI
|
||||||
setEstimatedTime(msToString(res.data.estimatedTimeMs))
|
setEstimatedTime(msToString(res.estimatedTimeMs))
|
||||||
if (typeof res.data.placeInQueue !== 'undefined') {
|
if (typeof res.placeInQueue !== 'undefined') {
|
||||||
setPlaceInQueue(Math.max(res.data.placeInQueue, 1))
|
setPlaceInQueue(Math.max(res.placeInQueue, 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -62,7 +63,8 @@ export function SignupQueued() {
|
|||||||
setEstimatedTime,
|
setEstimatedTime,
|
||||||
setPlaceInQueue,
|
setPlaceInQueue,
|
||||||
onboardingDispatch,
|
onboardingDispatch,
|
||||||
agent,
|
pdsClient,
|
||||||
|
refreshSession,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {SimpleInlineLinkText} from '#/components/Link'
|
import {SimpleInlineLinkText} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {accountReportSubject} from '#/components/moderation/ReportDialog/utils/reportSubject'
|
||||||
import {P, Text} from '#/components/Typography'
|
import {P, Text} from '#/components/Typography'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {com, tools} from '#/lexicons'
|
import {com, tools} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
const COL_WIDTH = 400
|
const COL_WIDTH = 400
|
||||||
|
|
||||||
@@ -51,14 +51,11 @@ export function Takendown() {
|
|||||||
if (!currentAccount) throw new Error('No session')
|
if (!currentAccount) throw new Error('No session')
|
||||||
await pdsClient.call(
|
await pdsClient.call(
|
||||||
com.atproto.moderation.createReport,
|
com.atproto.moderation.createReport,
|
||||||
toLex<com.atproto.moderation.createReport.$InputBody>({
|
{
|
||||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||||
subject: {
|
subject: accountReportSubject(currentAccount.did),
|
||||||
$type: 'com.atproto.admin.defs#repoRef',
|
|
||||||
did: currentAccount.did,
|
|
||||||
},
|
|
||||||
reason: appealText,
|
reason: appealText,
|
||||||
}),
|
},
|
||||||
{
|
{
|
||||||
service: api.moderation.service,
|
service: api.moderation.service,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
|
import {setPersonalDetails} from '@bsky.app/sdk'
|
||||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings'
|
import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings'
|
||||||
import {preferencesQueryKey} from '#/state/queries/preferences'
|
import {preferencesQueryKey} from '#/state/queries/preferences'
|
||||||
import {useAgent, usePdsClient, useSession} from '#/state/session'
|
import {usePdsClient, useSession} from '#/state/session'
|
||||||
import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance'
|
import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance'
|
||||||
import {isUnderAge} from '#/ageAssurance/util'
|
import {isUnderAge} from '#/ageAssurance/util'
|
||||||
import {IS_DEV} from '#/env'
|
import {IS_DEV} from '#/env'
|
||||||
@@ -54,14 +55,14 @@ export function useIsBirthdateUpdateAllowed() {
|
|||||||
|
|
||||||
export function useBirthdateMutation() {
|
export function useBirthdateMutation() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const {currentAccount} = useSession()
|
||||||
const pdsClient = usePdsClient()
|
const pdsClient = usePdsClient()
|
||||||
const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData()
|
const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData()
|
||||||
|
|
||||||
return useMutation<void, unknown, {birthDate: Date}>({
|
return useMutation<void, unknown, {birthDate: Date}>({
|
||||||
mutationFn: async ({birthDate}: {birthDate: Date}) => {
|
mutationFn: async ({birthDate}: {birthDate: Date}) => {
|
||||||
const bday = birthDate.toISOString()
|
const bday = birthDate.toISOString()
|
||||||
await agent.setPersonalDetails({birthDate: bday})
|
await pdsClient.call(setPersonalDetails, {birthDate})
|
||||||
// triggers a refetch
|
// triggers a refetch
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: preferencesQueryKey,
|
queryKey: preferencesQueryKey,
|
||||||
@@ -80,7 +81,9 @@ export function useBirthdateMutation() {
|
|||||||
* birthdate, which may change the user's age assurance access level.
|
* birthdate, which may change the user's age assurance access level.
|
||||||
*/
|
*/
|
||||||
void patchOtherRequiredData({birthdate: bday})
|
void patchOtherRequiredData({birthdate: bday})
|
||||||
snoozeBirthdateUpdateAllowedForDid(agent.sessionManager.did!)
|
if (currentAccount) {
|
||||||
|
snoozeBirthdateUpdateAllowedForDid(currentAccount.did)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+7
-5
@@ -5,9 +5,11 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import {type AtUriString} from '@atproto/syntax'
|
||||||
|
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {useAgent, useSession} from '../session'
|
import {app} from '#/lexicons'
|
||||||
|
import {usePdsClient, useSession} from '../session'
|
||||||
|
|
||||||
type StateContext = Map<string, boolean>
|
type StateContext = Map<string, boolean>
|
||||||
type SetStateContext = (uri: string, value: boolean) => void
|
type SetStateContext = (uri: string, value: boolean) => void
|
||||||
@@ -56,7 +58,7 @@ export function useSetThreadMute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useMigrateMutes(setThreadMute: SetStateContext) {
|
function useMigrateMutes(setThreadMute: SetStateContext) {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -87,8 +89,8 @@ function useMigrateMutes(setThreadMute: SetStateContext) {
|
|||||||
|
|
||||||
setThreadMute(root, true)
|
setThreadMute(root, true)
|
||||||
|
|
||||||
await agent.api.app.bsky.graph
|
await pdsClient
|
||||||
.muteThread({root})
|
.call(app.bsky.graph.muteThread, {root: root as AtUriString})
|
||||||
// not a big deal if this fails, since the post might have been deleted
|
// not a big deal if this fails, since the post might have been deleted
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
@@ -100,5 +102,5 @@ function useMigrateMutes(setThreadMute: SetStateContext) {
|
|||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [agent, currentAccount, setThreadMute])
|
}, [pdsClient, currentAccount, setThreadMute])
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-11
@@ -7,6 +7,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {AppState, type AppStateStatus} from 'react-native'
|
import {AppState, type AppStateStatus} from 'react-native'
|
||||||
|
import {type Service} from '@atproto/lex-client'
|
||||||
import {type AtUriString} from '@atproto/syntax'
|
import {type AtUriString} from '@atproto/syntax'
|
||||||
import throttle from 'lodash.throttle'
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
@@ -22,8 +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 {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {useAgent} from './session'
|
import {useAppviewClient} from './session'
|
||||||
|
|
||||||
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
||||||
|
|
||||||
@@ -66,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 appviewClient = useAppviewClient()
|
||||||
|
|
||||||
const feed =
|
const feed =
|
||||||
!!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo)
|
!!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo)
|
||||||
@@ -151,15 +152,20 @@ export function useFeedFeedback(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to the feed
|
/*
|
||||||
agent.app.bsky.feed
|
* Send to the feed generator via a per-call `service` option, which
|
||||||
.sendInteractions(
|
* lex-client turns into the `atproto-proxy` header. This overrides the
|
||||||
{interactions: interactionsToSend, feed: feed?.uri},
|
* appview client's own service target for this one call.
|
||||||
|
*/
|
||||||
|
appviewClient
|
||||||
|
.call(
|
||||||
|
app.bsky.feed.sendInteractions,
|
||||||
{
|
{
|
||||||
encoding: 'application/json',
|
interactions: interactionsToSend,
|
||||||
headers: {
|
feed: feed?.uri as AtUriString | undefined,
|
||||||
'atproto-proxy': `${proxyDid}#bsky_fg`,
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
service: `${proxyDid}#bsky_fg` as Service,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.catch(() => {}) // ignore upstream errors
|
.catch(() => {}) // ignore upstream errors
|
||||||
@@ -173,7 +179,7 @@ export function useFeedFeedback(
|
|||||||
)
|
)
|
||||||
throttledFlushAggregatedStats()
|
throttledFlushAggregatedStats()
|
||||||
logger.debug('flushed')
|
logger.debug('flushed')
|
||||||
}, [agent, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
}, [appviewClient, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
||||||
|
|
||||||
const sendToFeed = useMemo(
|
const sendToFeed = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -595,42 +595,25 @@ export class Convo {
|
|||||||
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
|
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
|
||||||
if (this.convo) {
|
if (this.convo) {
|
||||||
for (const member of this.convo.members) {
|
for (const member of this.convo.members) {
|
||||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
this.relatedProfiles.set(member.did, member)
|
||||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
|
||||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
|
||||||
// toLex once dms/util migrates.
|
|
||||||
this.relatedProfiles.set(
|
|
||||||
member.did,
|
|
||||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.applyProfileShadows()
|
this.applyProfileShadows()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The partial merges into `this.convo.view` and is re-parsed by the
|
* The partial merges into `this.convo.view` and is re-parsed by
|
||||||
* still-old-typed `parseConvoView` (`#/components/dms/util`, migrates in a
|
* `parseConvoView`, and its callers build it from `this.convo.details` /
|
||||||
* later task), and its callers build it from old-typed `this.convo.details` /
|
* members. The stored view is the lexicon `ConvoView`, so the partial is
|
||||||
* members. So this boundary stays in the old view world - typing the param
|
* typed off it directly.
|
||||||
* off `ConvoWithDetails['view']` keeps it internally consistent without a
|
|
||||||
* per-call `toLex`. TODO(phase4): flip to `chat.bsky.convo.defs.ConvoView`
|
|
||||||
* once dms/util migrates.
|
|
||||||
*/
|
*/
|
||||||
private updateConvo(convo: Partial<ConvoWithDetails['view']>) {
|
private updateConvo(convo: Partial<chat.bsky.convo.defs.ConvoView>) {
|
||||||
if (this.convo) {
|
if (this.convo) {
|
||||||
this.convo =
|
this.convo =
|
||||||
parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ??
|
parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ??
|
||||||
this.convo
|
this.convo
|
||||||
for (const member of this.convo.members) {
|
for (const member of this.convo.members) {
|
||||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
this.relatedProfiles.set(member.did, member)
|
||||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
|
||||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
|
||||||
// toLex once dms/util migrates.
|
|
||||||
this.relatedProfiles.set(
|
|
||||||
member.did,
|
|
||||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
this.applyProfileShadows()
|
this.applyProfileShadows()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import {type AtIdentifierString} from '@atproto/syntax'
|
||||||
import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query'
|
import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
|
||||||
export const RQKEY_ROOT = 'actor-starter-packs'
|
export const RQKEY_ROOT = 'actor-starter-packs'
|
||||||
export const RQKEY_WITH_MEMBERSHIP_ROOT = 'actor-starter-packs-with-membership'
|
export const RQKEY_WITH_MEMBERSHIP_ROOT = 'actor-starter-packs-with-membership'
|
||||||
@@ -17,17 +19,16 @@ export function useActorStarterPacksQuery({
|
|||||||
did?: string
|
did?: string
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
|
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
queryKey: RQKEY(did),
|
queryKey: RQKEY(did),
|
||||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||||
const res = await agent.app.bsky.graph.getActorStarterPacks({
|
return await appviewClient.call(app.bsky.graph.getActorStarterPacks, {
|
||||||
actor: did!,
|
actor: did! as AtIdentifierString,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
cursor: pageParam,
|
cursor: pageParam,
|
||||||
})
|
})
|
||||||
return res.data
|
|
||||||
},
|
},
|
||||||
enabled: Boolean(did) && enabled,
|
enabled: Boolean(did) && enabled,
|
||||||
initialPageParam: undefined,
|
initialPageParam: undefined,
|
||||||
@@ -42,17 +43,19 @@ export function useActorStarterPacksWithMembershipsQuery({
|
|||||||
did?: string
|
did?: string
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
|
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
queryKey: RQKEY_WITH_MEMBERSHIP(did),
|
queryKey: RQKEY_WITH_MEMBERSHIP(did),
|
||||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||||
const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
|
return await appviewClient.call(
|
||||||
actor: did!,
|
app.bsky.graph.getStarterPacksWithMembership,
|
||||||
|
{
|
||||||
|
actor: did! as AtIdentifierString,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
cursor: pageParam,
|
cursor: pageParam,
|
||||||
})
|
},
|
||||||
return res.data
|
)
|
||||||
},
|
},
|
||||||
enabled: Boolean(did) && enabled,
|
enabled: Boolean(did) && enabled,
|
||||||
initialPageParam: undefined,
|
initialPageParam: undefined,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {Client} from '@atproto/lex-client'
|
||||||
|
import {type DatetimeString, type HandleString} from '@atproto/syntax'
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -8,20 +10,8 @@ import {
|
|||||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||||
import {createFullHandle} from '#/lib/strings/handles'
|
import {createFullHandle} from '#/lib/strings/handles'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {Agent} from '../session/agent'
|
import {com} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
/*
|
|
||||||
* `com.atproto.temp.checkHandleAvailability` is an entryway-only endpoint that
|
|
||||||
* isn't generated into `#/lexicons`, so we describe its result union locally.
|
|
||||||
* The response is discriminated by `$type`; we narrow against these shapes
|
|
||||||
* rather than a branded lexicon guard.
|
|
||||||
*/
|
|
||||||
type CheckHandleAvailabilityResult =
|
|
||||||
| {$type: 'com.atproto.temp.checkHandleAvailability#resultAvailable'}
|
|
||||||
| {
|
|
||||||
$type: 'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
|
||||||
suggestions: {handle: string; method: string}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RQKEY_handleAvailability = (
|
export const RQKEY_handleAvailability = (
|
||||||
handle: string,
|
handle: string,
|
||||||
@@ -90,24 +80,28 @@ export async function checkHandleAvailability(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (serviceDid === BSKY_SERVICE_DID) {
|
if (serviceDid === BSKY_SERVICE_DID) {
|
||||||
const agent = new Agent(null, {service: BSKY_SERVICE})
|
|
||||||
// entryway has a special API for handle availability
|
// entryway has a special API for handle availability
|
||||||
const {data} = await agent.com.atproto.temp.checkHandleAvailability({
|
const client = new Client({service: BSKY_SERVICE})
|
||||||
handle,
|
const data = await client.call(com.atproto.temp.checkHandleAvailability, {
|
||||||
birthDate,
|
handle: handle as HandleString,
|
||||||
|
birthDate: birthDate as DatetimeString | undefined,
|
||||||
email,
|
email,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = data.result as CheckHandleAvailabilityResult
|
const result = data.result
|
||||||
|
|
||||||
if (
|
if (
|
||||||
result.$type ===
|
bsky.isType(
|
||||||
'com.atproto.temp.checkHandleAvailability#resultAvailable'
|
com.atproto.temp.checkHandleAvailability.resultAvailable,
|
||||||
|
result,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return {available: true} as const
|
return {available: true} as const
|
||||||
} else if (
|
} else if (
|
||||||
result.$type ===
|
bsky.isType(
|
||||||
'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
com.atproto.temp.checkHandleAvailability.resultUnavailable,
|
||||||
|
result,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
available: false,
|
available: false,
|
||||||
@@ -120,13 +114,13 @@ export async function checkHandleAvailability(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 3rd party PDSes won't have this API so just try and resolve the handle
|
// 3rd party PDSes won't have this API so just try and resolve the handle
|
||||||
const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE})
|
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||||
try {
|
try {
|
||||||
const res = await agent.resolveHandle({
|
const res = await client.call(com.atproto.identity.resolveHandle, {
|
||||||
handle,
|
handle: handle as HandleString,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.data.did) {
|
if (res.did) {
|
||||||
return {available: false} as const
|
return {available: false} as const
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
+21
-11
@@ -1,8 +1,10 @@
|
|||||||
import {useCallback} from 'react'
|
import {useCallback} from 'react'
|
||||||
|
import {type AtIdentifierString, type HandleString} from '@atproto/syntax'
|
||||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient, usePdsClient} from '#/state/session'
|
||||||
|
import {app, com} from '#/lexicons'
|
||||||
|
|
||||||
const handleQueryKeyRoot = 'handle'
|
const handleQueryKeyRoot = 'handle'
|
||||||
const fetchHandleQueryKey = (handleOrDid: string) => [
|
const fetchHandleQueryKey = (handleOrDid: string) => [
|
||||||
@@ -14,7 +16,7 @@ const fetchDidQueryKey = (handleOrDid: string) => [didQueryKeyRoot, handleOrDid]
|
|||||||
|
|
||||||
export function useFetchHandle() {
|
export function useFetchHandle() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async (handleOrDid: string) => {
|
async (handleOrDid: string) => {
|
||||||
@@ -22,13 +24,16 @@ export function useFetchHandle() {
|
|||||||
const res = await queryClient.fetchQuery({
|
const res = await queryClient.fetchQuery({
|
||||||
staleTime: STALE.MINUTES.FIVE,
|
staleTime: STALE.MINUTES.FIVE,
|
||||||
queryKey: fetchHandleQueryKey(handleOrDid),
|
queryKey: fetchHandleQueryKey(handleOrDid),
|
||||||
queryFn: () => agent.getProfile({actor: handleOrDid}),
|
queryFn: () =>
|
||||||
|
appviewClient.call(app.bsky.actor.getProfile, {
|
||||||
|
actor: handleOrDid as AtIdentifierString,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
return res.data.handle
|
return res.handle
|
||||||
}
|
}
|
||||||
return handleOrDid
|
return handleOrDid
|
||||||
},
|
},
|
||||||
[queryClient, agent],
|
[queryClient, appviewClient],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +41,13 @@ export function useUpdateHandleMutation(opts?: {
|
|||||||
onSuccess?: (handle: string) => void
|
onSuccess?: (handle: string) => void
|
||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({handle}: {handle: string}) => {
|
mutationFn: async ({handle}: {handle: string}) => {
|
||||||
await agent.updateHandle({handle})
|
await pdsClient.call(com.atproto.identity.updateHandle, {
|
||||||
|
handle: handle as HandleString,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onSuccess(_data, variables) {
|
onSuccess(_data, variables) {
|
||||||
opts?.onSuccess?.(variables.handle)
|
opts?.onSuccess?.(variables.handle)
|
||||||
@@ -53,7 +60,7 @@ export function useUpdateHandleMutation(opts?: {
|
|||||||
|
|
||||||
export function useFetchDid() {
|
export function useFetchDid() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async (handleOrDid: string) => {
|
async (handleOrDid: string) => {
|
||||||
@@ -63,13 +70,16 @@ export function useFetchDid() {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
let identifier = handleOrDid
|
let identifier = handleOrDid
|
||||||
if (!identifier.startsWith('did:')) {
|
if (!identifier.startsWith('did:')) {
|
||||||
const res = await agent.resolveHandle({handle: identifier})
|
const res = await appviewClient.call(
|
||||||
identifier = res.data.did
|
com.atproto.identity.resolveHandle,
|
||||||
|
{handle: identifier as HandleString},
|
||||||
|
)
|
||||||
|
identifier = res.did
|
||||||
}
|
}
|
||||||
return identifier
|
return identifier
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[queryClient, agent],
|
[queryClient, appviewClient],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
|
import {type AtUriString} from '@atproto/syntax'
|
||||||
|
import {deleteLike, like} from '@bsky.app/sdk'
|
||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
|
|
||||||
export function useLikeMutation() {
|
export function useLikeMutation() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({uri, cid}: {uri: string; cid: string}) => {
|
mutationFn: async ({uri, cid}: {uri: string; cid: string}) => {
|
||||||
const res = await agent.like(uri, cid)
|
const res = await pdsClient.call(like, {
|
||||||
|
uri: uri as AtUriString,
|
||||||
|
cid,
|
||||||
|
})
|
||||||
return {uri: res.uri}
|
return {uri: res.uri}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUnlikeMutation() {
|
export function useUnlikeMutation() {
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({uri}: {uri: string}) => {
|
mutationFn: async ({uri}: {uri: string}) => {
|
||||||
await agent.deleteLike(uri)
|
await pdsClient.call(deleteLike, uri as AtUriString)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -745,17 +745,18 @@ export function ListConvosProviderInner({
|
|||||||
/*
|
/*
|
||||||
* `log.message` can also be a deleted-message view per the
|
* `log.message` can also be a deleted-message view per the
|
||||||
* log union, which the strict MessageAndReactionView type
|
* log union, which the strict MessageAndReactionView type
|
||||||
* rejects - the old types absorbed this via the open-union
|
* rejects - the raw ConvoView cache type only admits a live
|
||||||
* catch-all. Keep the pre-migration runtime behavior (always
|
* MessageView here. Keep the pre-migration runtime behavior
|
||||||
* store the view we got) and assert the cache type.
|
* (always store the view we got) and assert into the cache
|
||||||
|
* type; the runtime value is unchanged.
|
||||||
*/
|
*/
|
||||||
lastReaction: bsky.toLex<
|
lastReaction: {
|
||||||
NonNullable<chat.bsky.convo.defs.ConvoView['lastReaction']>
|
|
||||||
>({
|
|
||||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||||
reaction: log.reaction,
|
reaction: log.reaction,
|
||||||
message: log.message,
|
message: log.message,
|
||||||
}),
|
} as NonNullable<
|
||||||
|
chat.bsky.convo.defs.ConvoView['lastReaction']
|
||||||
|
>,
|
||||||
rev: log.rev,
|
rev: log.rev,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {removeNuxs, upsertNux} from '@bsky.app/sdk'
|
||||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {type AppNux, type Nux} from '#/state/queries/nuxs/definitions'
|
import {type AppNux, type Nux} from '#/state/queries/nuxs/definitions'
|
||||||
@@ -6,7 +7,7 @@ import {
|
|||||||
preferencesQueryKey,
|
preferencesQueryKey,
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
|
|
||||||
export {Nux} from '#/state/queries/nuxs/definitions'
|
export {Nux} from '#/state/queries/nuxs/definitions'
|
||||||
|
|
||||||
@@ -42,11 +43,11 @@ export function useNuxs():
|
|||||||
|
|
||||||
// if (__DEV__) {
|
// if (__DEV__) {
|
||||||
// const queryClient = useQueryClient()
|
// const queryClient = useQueryClient()
|
||||||
// const agent = useAgent()
|
// const pdsClient = usePdsClient()
|
||||||
|
|
||||||
// // @ts-ignore
|
// // @ts-ignore
|
||||||
// window.clearNux = async (ids: string[]) => {
|
// window.clearNux = async (ids: string[]) => {
|
||||||
// await agent.bskyAppRemoveNuxs(ids)
|
// await pdsClient.call(removeNuxs, ids)
|
||||||
// // triggers a refetch
|
// // triggers a refetch
|
||||||
// await queryClient.invalidateQueries({
|
// await queryClient.invalidateQueries({
|
||||||
// queryKey: preferencesQueryKey,
|
// queryKey: preferencesQueryKey,
|
||||||
@@ -97,12 +98,12 @@ export function useNux<T extends Nux>(
|
|||||||
|
|
||||||
export function useSaveNux() {
|
export function useSaveNux() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
retry: 3,
|
retry: 3,
|
||||||
mutationFn: async (nux: AppNux) => {
|
mutationFn: async (nux: AppNux) => {
|
||||||
await agent.bskyAppUpsertNux(serializeAppNux(nux))
|
await pdsClient.call(upsertNux, serializeAppNux(nux))
|
||||||
// triggers a refetch
|
// triggers a refetch
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: preferencesQueryKey,
|
queryKey: preferencesQueryKey,
|
||||||
@@ -113,12 +114,12 @@ export function useSaveNux() {
|
|||||||
|
|
||||||
export function useResetNuxs() {
|
export function useResetNuxs() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const pdsClient = usePdsClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
retry: 3,
|
retry: 3,
|
||||||
mutationFn: async (ids: string[]) => {
|
mutationFn: async (ids: string[]) => {
|
||||||
await agent.bskyAppRemoveNuxs(ids)
|
await pdsClient.call(removeNuxs, ids)
|
||||||
// triggers a refetch
|
// triggers a refetch
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: preferencesQueryKey,
|
queryKey: preferencesQueryKey,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
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 {Client} from '@atproto/lex-client'
|
||||||
|
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, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||||
@@ -7,7 +9,7 @@ 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 {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,24 @@ 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})
|
/*
|
||||||
|
* Unauthenticated throwaway client pointed at the public appview -
|
||||||
|
* resolveHandle is a public read.
|
||||||
|
*/
|
||||||
|
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||||
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 res = await withResolveTimeout(signal =>
|
||||||
agent.resolveHandle({handle: norm}, {signal}),
|
client.call(
|
||||||
|
com.atproto.identity.resolveHandle,
|
||||||
|
{handle: norm as HandleString},
|
||||||
|
{signal},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
did = res.data.did
|
did = res.did
|
||||||
}
|
}
|
||||||
logger.debug('pds-detection: resolved identifier to DID', {
|
logger.debug('pds-detection: resolved identifier to DID', {
|
||||||
identifier: norm,
|
identifier: norm,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {useQuery} from '@tanstack/react-query'
|
import {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 {app} from '#/lexicons'
|
||||||
|
|
||||||
type ServiceConfig = {
|
type ServiceConfig = {
|
||||||
checkEmailConfirmed: boolean
|
checkEmailConfirmed: boolean
|
||||||
@@ -13,18 +14,24 @@ type ServiceConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useServiceConfigQuery() {
|
export function useServiceConfigQuery() {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
return useQuery<ServiceConfig>({
|
return useQuery<ServiceConfig>({
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
staleTime: STALE.MINUTES.FIVE,
|
staleTime: STALE.MINUTES.FIVE,
|
||||||
queryKey: ['service-config'],
|
queryKey: ['service-config'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const {data} = await agent.api.app.bsky.unspecced.getConfig()
|
const data = await appviewClient.call(app.bsky.unspecced.getConfig)
|
||||||
return {
|
return {
|
||||||
checkEmailConfirmed: Boolean(data.checkEmailConfirmed),
|
checkEmailConfirmed: Boolean(data.checkEmailConfirmed),
|
||||||
// @ts-expect-error not included in types atm
|
/*
|
||||||
topicsEnabled: Boolean(data.topicsEnabled),
|
* `topicsEnabled` is served by the appview but is not (yet) part of
|
||||||
|
* the getConfig lexicon schema, so read it through a narrow local
|
||||||
|
* cast rather than the generated body type.
|
||||||
|
*/
|
||||||
|
topicsEnabled: Boolean(
|
||||||
|
(data as {topicsEnabled?: boolean}).topicsEnabled,
|
||||||
|
),
|
||||||
liveNow: data.liveNow ?? [],
|
liveNow: data.liveNow ?? [],
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -7,16 +7,15 @@ import {
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export const DEFAULT_LIMIT = 15
|
export const DEFAULT_LIMIT = 15
|
||||||
|
|
||||||
export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']
|
export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']
|
||||||
|
|
||||||
export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const savedFeeds = preferences?.savedFeeds
|
const savedFeeds = preferences?.savedFeeds
|
||||||
|
|
||||||
@@ -26,7 +25,8 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
|||||||
queryKey: createGetSuggestedFeedsQueryKey(),
|
queryKey: createGetSuggestedFeedsQueryKey(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedFeeds(
|
const data = await appviewClient.call(
|
||||||
|
app.bsky.unspecced.getSuggestedFeeds,
|
||||||
{
|
{
|
||||||
limit: DEFAULT_LIMIT,
|
limit: DEFAULT_LIMIT,
|
||||||
},
|
},
|
||||||
@@ -38,17 +38,11 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO(phase4): drop toLex once getSuggestedFeeds migrates off the bridge
|
|
||||||
* agent (intentionally left on the bridge in Phase 3).
|
|
||||||
*/
|
|
||||||
return {
|
return {
|
||||||
feeds: toLex<app.bsky.feed.defs.GeneratorView[]>(
|
feeds: data.feeds.filter(feed => {
|
||||||
data.feeds.filter(feed => {
|
|
||||||
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
|
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
|
||||||
return !isSaved
|
return !isSaved
|
||||||
}),
|
}),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import {logger} from '#/logger'
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
category?: string | null
|
category?: string | null
|
||||||
@@ -28,7 +27,7 @@ export const createGetSuggestedOnboardingUsersQueryKey = (
|
|||||||
]
|
]
|
||||||
|
|
||||||
export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -40,7 +39,8 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
|||||||
|
|
||||||
const overrideInterests = props.overrideInterests.join(',')
|
const overrideInterests = props.overrideInterests.join(',')
|
||||||
|
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedOnboardingUsers(
|
const data = await appviewClient.call(
|
||||||
|
app.bsky.unspecced.getSuggestedOnboardingUsers,
|
||||||
{
|
{
|
||||||
category: props.category ?? undefined,
|
category: props.category ?? undefined,
|
||||||
limit: props.limit || 10,
|
limit: props.limit || 10,
|
||||||
@@ -56,15 +56,7 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
|||||||
if (!data.recIdStr) {
|
if (!data.recIdStr) {
|
||||||
logger.debug('getSuggestedOnboardingUsers response missing recIdStr')
|
logger.debug('getSuggestedOnboardingUsers response missing recIdStr')
|
||||||
}
|
}
|
||||||
/*
|
return {...data, recId: data.recIdStr}
|
||||||
* TODO(phase4): drop toLex once getSuggestedOnboardingUsers migrates off
|
|
||||||
* the bridge agent (this unspecced endpoint is intentionally left on the
|
|
||||||
* bridge in Phase 3, so it returns old `@atproto/api` view types).
|
|
||||||
*/
|
|
||||||
return toLex<{
|
|
||||||
actors: app.bsky.actor.defs.ProfileView[]
|
|
||||||
recId: string | undefined
|
|
||||||
}>({...data, recId: data.recIdStr})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -23,7 +22,7 @@ export const createGetSuggestedUsersForDiscoverQueryKey = (props: {
|
|||||||
}) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit]
|
}) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit]
|
||||||
|
|
||||||
export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -33,8 +32,8 @@ export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
|||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const userInterests = aggregateUserInterests(preferences)
|
const userInterests = aggregateUserInterests(preferences)
|
||||||
|
|
||||||
const {data} =
|
const data = await appviewClient.call(
|
||||||
await agent.app.bsky.unspecced.getSuggestedUsersForDiscover(
|
app.bsky.unspecced.getSuggestedUsersForDiscover,
|
||||||
{
|
{
|
||||||
limit: props.limit || 10,
|
limit: props.limit || 10,
|
||||||
},
|
},
|
||||||
@@ -48,15 +47,7 @@ export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
|||||||
if (!data.recIdStr) {
|
if (!data.recIdStr) {
|
||||||
logger.debug('getSuggestedUsersForDiscover response missing recIdStr')
|
logger.debug('getSuggestedUsersForDiscover response missing recIdStr')
|
||||||
}
|
}
|
||||||
/*
|
return {...data, recId: data.recIdStr}
|
||||||
* TODO(phase4): drop toLex once getSuggestedUsersForDiscover migrates off
|
|
||||||
* the bridge agent (this unspecced endpoint is intentionally left on the
|
|
||||||
* bridge in Phase 3, so it returns old `@atproto/api` view types).
|
|
||||||
*/
|
|
||||||
return toLex<{
|
|
||||||
actors: app.bsky.actor.defs.ProfileView[]
|
|
||||||
recId: string | undefined
|
|
||||||
}>({...data, recId: data.recIdStr})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
category?: string | null
|
category?: string | null
|
||||||
@@ -24,7 +23,7 @@ export const createGetSuggestedUsersForExploreQueryKey = (
|
|||||||
) => [getSuggestedUsersForExploreQueryKeyRoot, props.category, props.limit]
|
) => [getSuggestedUsersForExploreQueryKeyRoot, props.category, props.limit]
|
||||||
|
|
||||||
export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -34,7 +33,8 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
|||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const userInterests = aggregateUserInterests(preferences)
|
const userInterests = aggregateUserInterests(preferences)
|
||||||
|
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForExplore(
|
const data = await appviewClient.call(
|
||||||
|
app.bsky.unspecced.getSuggestedUsersForExplore,
|
||||||
{
|
{
|
||||||
category: props.category ?? undefined,
|
category: props.category ?? undefined,
|
||||||
limit: props.limit || 10,
|
limit: props.limit || 10,
|
||||||
@@ -50,14 +50,7 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
|||||||
if (!data.recIdStr) {
|
if (!data.recIdStr) {
|
||||||
logger.debug('getSuggestedUsersForExplore response missing recIdStr')
|
logger.debug('getSuggestedUsersForExplore response missing recIdStr')
|
||||||
}
|
}
|
||||||
/*
|
return {...data, recId: data.recIdStr}
|
||||||
* TODO(phase4): drop toLex once getSuggestedUsersForExplore migrates off
|
|
||||||
* the bridge agent (intentionally left on the bridge in Phase 3).
|
|
||||||
*/
|
|
||||||
return toLex<{
|
|
||||||
actors: app.bsky.actor.defs.ProfileView[]
|
|
||||||
recId: string | undefined
|
|
||||||
}>({...data, recId: data.recIdStr})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
category?: string | null
|
category?: string | null
|
||||||
@@ -26,7 +25,7 @@ export const createGetSuggestedUsersForSeeMoreQueryKey = (props: {
|
|||||||
}) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit]
|
}) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit]
|
||||||
|
|
||||||
export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -40,7 +39,8 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
|||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const userInterests = aggregateUserInterests(preferences)
|
const userInterests = aggregateUserInterests(preferences)
|
||||||
|
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForSeeMore(
|
const data = await appviewClient.call(
|
||||||
|
app.bsky.unspecced.getSuggestedUsersForSeeMore,
|
||||||
{
|
{
|
||||||
category: props.category ?? undefined,
|
category: props.category ?? undefined,
|
||||||
limit: props.limit || 50,
|
limit: props.limit || 50,
|
||||||
@@ -56,14 +56,7 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
|||||||
if (!data.recIdStr) {
|
if (!data.recIdStr) {
|
||||||
logger.debug('getSuggestedUsersForSeeMore response missing recIdStr')
|
logger.debug('getSuggestedUsersForSeeMore response missing recIdStr')
|
||||||
}
|
}
|
||||||
/*
|
return {...data, recId: data.recIdStr}
|
||||||
* TODO(phase4): drop toLex once this unspecced endpoint migrates off the
|
|
||||||
* bridge agent (intentionally left on the bridge in Phase 3).
|
|
||||||
*/
|
|
||||||
return toLex<{
|
|
||||||
actors: app.bsky.actor.defs.ProfileView[]
|
|
||||||
recId: string | undefined
|
|
||||||
}>({...data, recId: data.recIdStr})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import {
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export const createOnboardingSuggestedStarterPacksQueryKey = (
|
export const createOnboardingSuggestedStarterPacksQueryKey = (
|
||||||
interests?: string[],
|
interests?: string[],
|
||||||
@@ -22,7 +21,7 @@ export function useOnboardingSuggestedStarterPacksQuery({
|
|||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
overrideInterests?: string[]
|
overrideInterests?: string[]
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
|
|
||||||
@@ -31,8 +30,8 @@ export function useOnboardingSuggestedStarterPacksQuery({
|
|||||||
staleTime: STALE.MINUTES.THREE,
|
staleTime: STALE.MINUTES.THREE,
|
||||||
queryKey: createOnboardingSuggestedStarterPacksQueryKey(overrideInterests),
|
queryKey: createOnboardingSuggestedStarterPacksQueryKey(overrideInterests),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {data} =
|
return await appviewClient.call(
|
||||||
await agent.app.bsky.unspecced.getOnboardingSuggestedStarterPacks(
|
app.bsky.unspecced.getOnboardingSuggestedStarterPacks,
|
||||||
{limit: 6},
|
{limit: 6},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -45,14 +44,6 @@ export function useOnboardingSuggestedStarterPacksQuery({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
/*
|
|
||||||
* TODO(phase4): drop toLex once getOnboardingSuggestedStarterPacks
|
|
||||||
* migrates off the bridge agent (intentionally left on the bridge in
|
|
||||||
* Phase 3).
|
|
||||||
*/
|
|
||||||
return toLex<app.bsky.unspecced.getOnboardingSuggestedStarterPacks.$OutputBody>(
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import {
|
|||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {type app} from '#/lexicons'
|
import {app} from '#/lexicons'
|
||||||
import {toLex} from '#/types/bsky'
|
|
||||||
|
|
||||||
export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [
|
export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [
|
||||||
'suggested-starter-packs',
|
'suggested-starter-packs',
|
||||||
@@ -23,7 +22,7 @@ export function useSuggestedStarterPacksQuery({
|
|||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
overrideInterests?: string[]
|
overrideInterests?: string[]
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
|
|
||||||
@@ -32,8 +31,9 @@ export function useSuggestedStarterPacksQuery({
|
|||||||
staleTime: STALE.MINUTES.THREE,
|
staleTime: STALE.MINUTES.THREE,
|
||||||
queryKey: createSuggestedStarterPacksQueryKey(overrideInterests),
|
queryKey: createSuggestedStarterPacksQueryKey(overrideInterests),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
|
return await appviewClient.call(
|
||||||
undefined,
|
app.bsky.unspecced.getSuggestedStarterPacks,
|
||||||
|
{},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
...createBskyTopicsHeader(
|
...createBskyTopicsHeader(
|
||||||
@@ -45,13 +45,6 @@ export function useSuggestedStarterPacksQuery({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
/*
|
|
||||||
* TODO(phase4): drop toLex once getSuggestedStarterPacks migrates off the
|
|
||||||
* bridge agent (intentionally left on the bridge in Phase 3).
|
|
||||||
*/
|
|
||||||
return toLex<app.bsky.unspecced.getSuggestedStarterPacks.$OutputBody>(
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
|
import {Client} from '@atproto/lex-client'
|
||||||
import {PasswordSession} from '@atproto/lex-password-session'
|
import {PasswordSession} from '@atproto/lex-password-session'
|
||||||
|
|
||||||
import {isJwtExpired} from '#/lib/jwt'
|
import {isJwtExpired} from '#/lib/jwt'
|
||||||
|
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {
|
import {networkAwareFetch, sessionAccountToSessionData} from './session-core'
|
||||||
networkAwareFetch,
|
|
||||||
sessionAccountToSessionData,
|
|
||||||
SessionAgent,
|
|
||||||
} from './session-core'
|
|
||||||
import {type SessionAccount} from './types'
|
import {type SessionAccount} from './types'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -33,22 +31,30 @@ export function isSessionExpired(account: SessionAccount) {
|
|||||||
* Creates and resumes a throwaway session for every stored account.
|
* Creates and resumes a throwaway session for every stored account.
|
||||||
* Intended to send push token revocations just before logout.
|
* Intended to send push token revocations just before logout.
|
||||||
*
|
*
|
||||||
* Each returned {@link SessionAgent} wraps a temporary `PasswordSession`
|
* Each returned {@link TemporaryPushClient} wraps a temporary `PasswordSession`
|
||||||
* resumed over the network to obtain a valid access token. These sessions are
|
* resumed over the network to obtain a valid access token. These sessions are
|
||||||
* deliberately hook-free (no `onUpdated`/`onDeleted`): they must NEVER persist
|
* deliberately hook-free (no `onUpdated`/`onDeleted`): they must NEVER persist
|
||||||
* or race the active session. They are used once for the unregister call and
|
* or race the active session. They are used once for the unregister call and
|
||||||
* discarded (reclaimed by GC), so we never call `logout()` on them.
|
* discarded (reclaimed by GC), so we never call `logout()` on them.
|
||||||
|
*
|
||||||
|
* Each session is wrapped in a plain account-shaped `Client` (no proxy header)
|
||||||
|
* paired with the account's service origin and handle, matching the contract
|
||||||
|
* {@link unregisterPushToken} consumes.
|
||||||
*/
|
*/
|
||||||
export async function createTemporaryAgentsAndResume(
|
export async function createTemporaryAgentsAndResume(
|
||||||
accounts: SessionAccount[],
|
accounts: SessionAccount[],
|
||||||
): Promise<SessionAgent[]> {
|
): Promise<TemporaryPushClient[]> {
|
||||||
const settled = await Promise.allSettled(
|
const settled = await Promise.allSettled(
|
||||||
accounts.map(async account => {
|
accounts.map(async account => {
|
||||||
const session = await PasswordSession.resume(
|
const session = await PasswordSession.resume(
|
||||||
sessionAccountToSessionData(account),
|
sessionAccountToSessionData(account),
|
||||||
{fetch: networkAwareFetch},
|
{fetch: networkAwareFetch},
|
||||||
)
|
)
|
||||||
return new SessionAgent(session)
|
return {
|
||||||
|
client: new Client(session),
|
||||||
|
service: session.session.service,
|
||||||
|
handle: session.session.handle,
|
||||||
|
} satisfies TemporaryPushClient
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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 AtIdentifierString} 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,7 +67,7 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
|||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {accounts} = useSession()
|
const {accounts} = useSession()
|
||||||
const agent = useAgent()
|
const appviewClient = useAppviewClient()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const actors = accounts.map(acc => acc.did)
|
const actors = accounts.map(acc => acc.did)
|
||||||
if (actors.length === 0) return
|
if (actors.length === 0) return
|
||||||
@@ -73,11 +75,12 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
|||||||
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 appviewClient.call(app.bsky.actor.getProfiles, {
|
||||||
return res.data
|
actors: actors as AtIdentifierString[],
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [accounts, agent, queryClient])
|
}, [accounts, appviewClient, queryClient])
|
||||||
|
|
||||||
const onPressDismiss = useCallback(() => {
|
const onPressDismiss = useCallback(() => {
|
||||||
if (onDismiss) {
|
if (onDismiss) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {LogBox, Pressable, TextInput, View} from 'react-native'
|
|||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {BLUESKY_PROXY_HEADER} from '#/lib/constants'
|
import {BLUESKY_PROXY_HEADER} from '#/lib/constants'
|
||||||
import {useAgent, useSessionApi} from '#/state/session'
|
import {useSessionApi} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
||||||
import {navigate} from '../../../Navigation'
|
import {navigate} from '../../../Navigation'
|
||||||
@@ -31,7 +31,6 @@ const BTN = {height: 1, width: 1, backgroundColor: 'red'}
|
|||||||
let hasConfiguredProxy = false
|
let hasConfiguredProxy = false
|
||||||
|
|
||||||
export function TestCtrls() {
|
export function TestCtrls() {
|
||||||
const agent = useAgent()
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {logoutEveryAccount, login} = useSessionApi()
|
const {logoutEveryAccount, login} = useSessionApi()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
@@ -74,8 +73,12 @@ export function TestCtrls() {
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
const header = `${proxyHeader}#bsky_appview`
|
const header = `${proxyHeader}#bsky_appview`
|
||||||
|
/*
|
||||||
|
* The appview lex client reads BLUESKY_PROXY_HEADER.get() at build
|
||||||
|
* time (see clients.ts), so setting it here retargets the proxy for
|
||||||
|
* subsequent sign-ins without an explicit client reconfigure.
|
||||||
|
*/
|
||||||
BLUESKY_PROXY_HEADER.set(header)
|
BLUESKY_PROXY_HEADER.set(header)
|
||||||
agent.configureProxy(header as any)
|
|
||||||
hasConfiguredProxy = true
|
hasConfiguredProxy = true
|
||||||
setIsProxyConfigured(true)
|
setIsProxyConfigured(true)
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user