migrate the status, germ declaration and block records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,13 +2,12 @@ import {useMemo} from 'react'
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyActorStatus,
|
||||
AppBskyEmbedExternal,
|
||||
AtUri,
|
||||
ComAtprotoRepoPutRecord,
|
||||
moderateStatus,
|
||||
} from '@atproto/api'
|
||||
import {retry} from '@atproto/common-web'
|
||||
import {type l} from '@atproto/lex'
|
||||
import {type AtIdentifierString, AtUri, toDatetimeString} from '@atproto/syntax'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
@@ -17,18 +16,20 @@ import {isAfter, parseISO} from 'date-fns'
|
||||
import {uploadBlob} from '#/lib/api'
|
||||
import {imageToThumb} from '#/lib/api/resolve'
|
||||
import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta'
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import {useAppConfig} from '#/state/appConfig'
|
||||
import {
|
||||
updateProfileShadow,
|
||||
useMaybeProfileShadow,
|
||||
} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAgent, usePdsClient, useSession} from '#/state/session'
|
||||
import {useTickEveryMinute} from '#/state/shell'
|
||||
import {useDialogContext} from '#/components/Dialog'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {getLiveNowHost, getLiveServiceNames} from '#/features/liveNow/utils'
|
||||
import {app, com} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export * from '#/features/liveNow/utils'
|
||||
@@ -224,6 +225,7 @@ export function useUpsertLiveStatusMutation(
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const control = useDialogContext()
|
||||
const {_} = useLingui()
|
||||
@@ -232,10 +234,10 @@ export function useUpsertLiveStatusMutation(
|
||||
mutationFn: async () => {
|
||||
if (!currentAccount) throw new Error('Not logged in')
|
||||
|
||||
let embed: $Typed<AppBskyEmbedExternal.Main> | undefined
|
||||
let embed: $Typed<app.bsky.embed.external.Main> | undefined
|
||||
|
||||
if (linkMeta) {
|
||||
let thumb
|
||||
let thumb: l.BlobRef | undefined
|
||||
|
||||
if (linkMeta.image) {
|
||||
try {
|
||||
@@ -246,7 +248,11 @@ export function useUpsertLiveStatusMutation(
|
||||
img.source.path,
|
||||
img.source.mime,
|
||||
)
|
||||
thumb = blob.data.blob
|
||||
/*
|
||||
* `uploadBlob` still returns the legacy `BlobRef` class
|
||||
* instance; it moves to the client with the blob pipeline.
|
||||
*/
|
||||
thumb = blob.data.blob as unknown as l.BlobRef
|
||||
}
|
||||
} catch (e: any) {
|
||||
ax.logger.error(`Failed to upload thumbnail for live status`, {
|
||||
@@ -263,7 +269,8 @@ export function useUpsertLiveStatusMutation(
|
||||
$type: 'app.bsky.embed.external#external',
|
||||
title: linkMeta.title ?? '',
|
||||
description: linkMeta.description ?? '',
|
||||
uri: linkMeta.url,
|
||||
// `getLinkMeta` returns a plain url string
|
||||
uri: linkMeta.url as l.UriString,
|
||||
thumb,
|
||||
},
|
||||
}
|
||||
@@ -271,32 +278,41 @@ export function useUpsertLiveStatusMutation(
|
||||
|
||||
const record = {
|
||||
$type: 'app.bsky.actor.status',
|
||||
createdAt: createdAt ?? new Date().toISOString(),
|
||||
createdAt: toDatetimeString(
|
||||
createdAt ? new Date(createdAt) : new Date(),
|
||||
),
|
||||
status: 'app.bsky.actor.status#live',
|
||||
durationMinutes: duration,
|
||||
embed,
|
||||
} satisfies AppBskyActorStatus.Record
|
||||
} satisfies app.bsky.actor.status.Main
|
||||
|
||||
const upsert = async () => {
|
||||
const repo = currentAccount.did
|
||||
// the session account is still legacy-typed, so its did is unbranded
|
||||
const repo = currentAccount.did as AtIdentifierString
|
||||
const collection = 'app.bsky.actor.status'
|
||||
|
||||
const existing = await agent.com.atproto.repo
|
||||
.getRecord({repo, collection, rkey: 'self'})
|
||||
const existing = await pdsClient
|
||||
.call(com.atproto.repo.getRecord, {repo, collection, rkey: 'self'})
|
||||
.catch(_e => undefined)
|
||||
|
||||
await agent.com.atproto.repo.putRecord({
|
||||
/*
|
||||
* Stays on the raw `putRecord`, not `pdsClient.put`: the lexicon lets
|
||||
* `swapRecord` be null (meaning "must not already exist"), while the
|
||||
* record-helper option type is `string | undefined`.
|
||||
*/
|
||||
await pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo,
|
||||
collection,
|
||||
rkey: 'self',
|
||||
record,
|
||||
swapRecord: existing?.data.cid || null,
|
||||
swapRecord: existing?.cid || null,
|
||||
})
|
||||
}
|
||||
|
||||
await retry(upsert, {
|
||||
maxRetries: 5,
|
||||
retryable: e => e instanceof ComAtprotoRepoPutRecord.InvalidSwapError,
|
||||
retryable: e =>
|
||||
matchXrpcError(e, com.atproto.repo.putRecord) === 'InvalidSwap',
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -353,7 +369,7 @@ export function useUpsertLiveStatusMutation(
|
||||
export function useRemoveLiveStatusMutation() {
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const control = useDialogContext()
|
||||
const {_} = useLingui()
|
||||
@@ -362,8 +378,8 @@ export function useRemoveLiveStatusMutation() {
|
||||
mutationFn: async () => {
|
||||
if (!currentAccount) throw new Error('Not logged in')
|
||||
|
||||
await agent.app.bsky.actor.status.delete({
|
||||
repo: currentAccount.did,
|
||||
await pdsClient.delete(app.bsky.actor.status, {
|
||||
repo: currentAccount.did as AtIdentifierString,
|
||||
rkey: 'self',
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {Platform, View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyActorGetProfile,
|
||||
type AtpAgent,
|
||||
} from '@atproto/api'
|
||||
import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -13,7 +11,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
import {until} from '#/lib/async/until'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {RQKEY} from '#/state/queries/profile'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
@@ -24,6 +22,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {app, com} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export function GermButton({
|
||||
@@ -119,25 +118,27 @@ function GermSelfButton({did}: {did: string}) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const selfExplanationDialogControl = Dialog.useDialogControl()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const {mutate: deleteDeclaration, isPending} = useMutation({
|
||||
mutationFn: async () => {
|
||||
const previousRecord = await agent.com.germnetwork.declaration
|
||||
.get({
|
||||
repo: did,
|
||||
const previousRecord = await pdsClient
|
||||
// the profile view is still legacy-typed, so its did is unbranded
|
||||
.get(com.germnetwork.declaration, {
|
||||
repo: did as DidString,
|
||||
rkey: 'self',
|
||||
})
|
||||
.then(res => res.value)
|
||||
.catch(() => null)
|
||||
|
||||
await agent.com.germnetwork.declaration.delete({
|
||||
repo: did,
|
||||
await pdsClient.delete(com.germnetwork.declaration, {
|
||||
repo: did as DidString,
|
||||
rkey: 'self',
|
||||
})
|
||||
|
||||
await whenAppViewReady(agent, did, res => !res.data.associated?.germ)
|
||||
await whenAppViewReady(appviewClient, did, res => !res.associated?.germ)
|
||||
|
||||
return previousRecord
|
||||
},
|
||||
@@ -147,14 +148,15 @@ function GermSelfButton({did}: {did: string}) {
|
||||
async function undo() {
|
||||
if (!previousRecord) return
|
||||
try {
|
||||
await agent.com.germnetwork.declaration.put(
|
||||
{
|
||||
repo: did,
|
||||
rkey: 'self',
|
||||
},
|
||||
previousRecord,
|
||||
await pdsClient.put(com.germnetwork.declaration, previousRecord, {
|
||||
repo: did as DidString,
|
||||
rkey: 'self',
|
||||
})
|
||||
await whenAppViewReady(
|
||||
appviewClient,
|
||||
did,
|
||||
res => !!res.associated?.germ,
|
||||
)
|
||||
await whenAppViewReady(agent, did, res => !!res.data.associated?.germ)
|
||||
await queryClient.refetchQueries({queryKey: RQKEY(did)})
|
||||
|
||||
Toast.show(_(msg`Germ DM reconnected`))
|
||||
@@ -323,14 +325,17 @@ function platform() {
|
||||
}
|
||||
|
||||
async function whenAppViewReady(
|
||||
agent: AtpAgent,
|
||||
appviewClient: Client,
|
||||
actor: string,
|
||||
fn: (res: AppBskyActorGetProfile.Response) => boolean,
|
||||
fn: (res: app.bsky.actor.getProfile.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
() => agent.app.bsky.actor.getProfile({actor}),
|
||||
() =>
|
||||
appviewClient.call(app.bsky.actor.getProfile, {
|
||||
actor: actor as DidString,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ import {
|
||||
type ComAtprotoRepoUploadBlob,
|
||||
type Un$Typed,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
type AtIdentifierString,
|
||||
type DidString,
|
||||
toDatetimeString,
|
||||
} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
@@ -33,10 +38,11 @@ import {
|
||||
useUnstableProfileViewCache,
|
||||
} from '#/state/queries/unstable-profile-cache'
|
||||
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAgent, usePdsClient, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Metrics, toClout} from '#/analytics/metrics'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {
|
||||
ProgressGuideAction,
|
||||
@@ -634,17 +640,18 @@ export function useProfileBlockMutationQueue(
|
||||
|
||||
function useProfileBlockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
return await agent.app.bsky.graph.block.create(
|
||||
{repo: currentAccount.did},
|
||||
{subject: did, createdAt: new Date().toISOString()},
|
||||
)
|
||||
return await pdsClient.create(app.bsky.graph.block, {
|
||||
// the profile view is still legacy-typed, so its did is unbranded
|
||||
subject: did as DidString,
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
})
|
||||
},
|
||||
onSuccess(_, {did}) {
|
||||
void queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
|
||||
@@ -655,16 +662,16 @@ function useProfileBlockMutation() {
|
||||
|
||||
function useProfileUnblockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, Error, {did: string; blockUri: string}>({
|
||||
mutationFn: async ({blockUri}) => {
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
const {rkey} = new AtUri(blockUri)
|
||||
await agent.app.bsky.graph.block.delete({
|
||||
repo: currentAccount.did,
|
||||
const {rkeySafe: rkey} = new AtUri(blockUri)
|
||||
await pdsClient.delete(app.bsky.graph.block, {
|
||||
repo: currentAccount.did as AtIdentifierString,
|
||||
rkey,
|
||||
})
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user