migrate the blob upload helper to the pds client

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-08-04 00:21:55 +03:00
parent 28313bb81c
commit 83fdc95237
10 changed files with 127 additions and 63 deletions
@@ -8,16 +8,18 @@ import {
AppBskyContactImportContacts, AppBskyContactImportContacts,
type Un$Typed, type Un$Typed,
} from '@atproto/api' } from '@atproto/api'
import {type Client} from '@atproto/lex'
import {msg, t} from '@lingui/core/macro' import {msg, t} 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 {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import {cleanError, isNetworkError} from '#/lib/strings/errors' import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger' import {logger} from '#/logger'
import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts'
import {useAgent} from '#/state/session' import {useAgent, usePdsClient} from '#/state/session'
import { import {
Context as OnboardingContext, Context as OnboardingContext,
type OnboardingAction, type OnboardingAction,
@@ -55,6 +57,7 @@ export function GetContacts({
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const pdsClient = usePdsClient()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const gutters = useGutters([0, 'wide']) const gutters = useGutters([0, 'wide'])
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -72,7 +75,7 @@ export function GetContacts({
*/ */
if (context === 'Onboarding' && maybeOnboardingContext) { if (context === 'Onboarding' && maybeOnboardingContext) {
try { try {
await createProfileRecord(agent, maybeOnboardingContext) await createProfileRecord(agent, pdsClient, maybeOnboardingContext)
} catch (error) { } catch (error) {
logger.debug('Error creating profile record:', {safeMessage: error}) logger.debug('Error creating profile record:', {safeMessage: error})
} }
@@ -326,6 +329,7 @@ function showPermissionDeniedAlert() {
*/ */
async function createProfileRecord( async function createProfileRecord(
agent: AtpAgent, agent: AtpAgent,
pdsClient: Client,
onboardingContext: { onboardingContext: {
state: OnboardingState state: OnboardingState
dispatch: React.Dispatch<OnboardingAction> dispatch: React.Dispatch<OnboardingAction>
@@ -334,15 +338,17 @@ async function createProfileRecord(
const profileStepResults = onboardingContext.state.profileStepResults const profileStepResults = onboardingContext.state.profileStepResults
const {imageUri, imageMime} = profileStepResults const {imageUri, imageMime} = profileStepResults
const blobPromise = const blobPromise =
imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined imageUri && imageMime
? uploadBlob(pdsClient, imageUri, imageMime)
: undefined
await agent.upsertProfile(async existing => { await agent.upsertProfile(async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {}
if (blobPromise) { if (blobPromise) {
const res = await blobPromise const res = await blobPromise
if (res.data.blob) { if (res.blob) {
next.avatar = res.data.blob next.avatar = toLegacyBlobRef(res.blob)
} }
} }
+2 -7
View File
@@ -224,7 +224,6 @@ export function useUpsertLiveStatusMutation(
) { ) {
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const control = useDialogContext() const control = useDialogContext()
@@ -244,15 +243,11 @@ export function useUpsertLiveStatusMutation(
const img = await imageToThumb(linkMeta.image) const img = await imageToThumb(linkMeta.image)
if (img) { if (img) {
const blob = await uploadBlob( const blob = await uploadBlob(
agent, pdsClient,
img.source.path, img.source.path,
img.source.mime, img.source.mime,
) )
/* thumb = blob.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) { } catch (e: any) {
ax.logger.error(`Failed to upload thumbnail for live status`, { ax.logger.error(`Failed to upload thumbnail for live status`, {
+19 -10
View File
@@ -43,6 +43,7 @@ import {type app} from '#/lexicons'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
import {createGIFDescription} from '../gif-alt-text' import {createGIFDescription} from '../gif-alt-text'
import {computeCid} from './computeCid' import {computeCid} from './computeCid'
import {toLegacyBlobRef} from './legacy-blob'
import {uploadBlob} from './upload-blob' import {uploadBlob} from './upload-blob'
export {uploadBlob} export {uploadBlob}
@@ -55,9 +56,13 @@ interface PostOpts {
/* /*
* Facet/mention resolution is an appview job - it resolves handles through * Facet/mention resolution is an appview job - it resolves handles through
* the appview, and the public fallback keeps it working when logged out. * the appview, and the public fallback keeps it working when logged out.
* The rest of this pipeline still writes through the agent.
*/ */
appviewClient: Client appviewClient: Client
/*
* Record blobs (images, gallery items, link thumbnails, video captions)
* upload to the account's own PDS, never the appview.
*/
pdsClient: Client
} }
export async function post( export async function post(
@@ -97,6 +102,7 @@ export async function post(
const rtPromise = resolveRT(opts.appviewClient, draft.richtext) const rtPromise = resolveRT(opts.appviewClient, draft.richtext)
const embedPromise = resolveEmbed( const embedPromise = resolveEmbed(
agent, agent,
opts.pdsClient,
queryClient, queryClient,
draft, draft,
opts.onStateChange, opts.onStateChange,
@@ -263,6 +269,7 @@ async function resolveReply(agent: AtpAgent, replyTo: string) {
async function resolveEmbed( async function resolveEmbed(
agent: AtpAgent, agent: AtpAgent,
pdsClient: Client,
queryClient: QueryClient, queryClient: QueryClient,
draft: PostDraft, draft: PostDraft,
onStateChange: ((state: string) => void) | undefined, onStateChange: ((state: string) => void) | undefined,
@@ -277,7 +284,7 @@ async function resolveEmbed(
> { > {
if (draft.embed.quote) { if (draft.embed.quote) {
const [resolvedMedia, resolvedQuote] = await Promise.all([ const [resolvedMedia, resolvedQuote] = await Promise.all([
resolveMedia(agent, queryClient, draft.embed, onStateChange), resolveMedia(agent, pdsClient, queryClient, draft.embed, onStateChange),
resolveRecord(agent, queryClient, draft.embed.quote.uri), resolveRecord(agent, queryClient, draft.embed.quote.uri),
]) ])
if (resolvedMedia) { if (resolvedMedia) {
@@ -297,6 +304,7 @@ async function resolveEmbed(
} }
const resolvedMedia = await resolveMedia( const resolvedMedia = await resolveMedia(
agent, agent,
pdsClient,
queryClient, queryClient,
draft.embed, draft.embed,
onStateChange, onStateChange,
@@ -322,6 +330,7 @@ async function resolveEmbed(
async function resolveMedia( async function resolveMedia(
agent: AtpAgent, agent: AtpAgent,
pdsClient: Client,
queryClient: QueryClient, queryClient: QueryClient,
embedDraft: EmbedDraft, embedDraft: EmbedDraft,
onStateChange: ((state: string) => void) | undefined, onStateChange: ((state: string) => void) | undefined,
@@ -346,9 +355,9 @@ async function resolveMedia(
IMAGE_SIZE_CONFIG_POSTS, IMAGE_SIZE_CONFIG_POSTS,
) )
logger.debug(`Uploading image #${i}`) logger.debug(`Uploading image #${i}`)
const res = await uploadBlob(agent, path, mime) const res = await uploadBlob(pdsClient, path, mime)
return { return {
image: res.data.blob, image: toLegacyBlobRef(res.blob),
alt: image.alt, alt: image.alt,
aspectRatio: {width, height}, aspectRatio: {width, height},
} }
@@ -373,10 +382,10 @@ async function resolveMedia(
IMAGE_SIZE_CONFIG_POSTS, IMAGE_SIZE_CONFIG_POSTS,
) )
logger.debug(`Uploading image #${i}`) logger.debug(`Uploading image #${i}`)
const res = await uploadBlob(agent, path, mime) const res = await uploadBlob(pdsClient, path, mime)
return { return {
$type: 'app.bsky.embed.gallery#image' as const, $type: 'app.bsky.embed.gallery#image' as const,
image: res.data.blob, image: toLegacyBlobRef(res.blob),
alt: image.alt, alt: image.alt,
aspectRatio: {width, height}, aspectRatio: {width, height},
} }
@@ -438,8 +447,8 @@ async function resolveMedia(
if (resolvedGif.thumb) { if (resolvedGif.thumb) {
onStateChange?.(t`Uploading link thumbnail...`) onStateChange?.(t`Uploading link thumbnail...`)
const {path, mime} = resolvedGif.thumb.source const {path, mime} = resolvedGif.thumb.source
const response = await uploadBlob(agent, path, mime) const response = await uploadBlob(pdsClient, path, mime)
blob = response.data.blob blob = toLegacyBlobRef(response.blob)
} }
return { return {
$type: 'app.bsky.embed.external', $type: 'app.bsky.embed.external',
@@ -462,8 +471,8 @@ async function resolveMedia(
if (resolvedLink.thumb) { if (resolvedLink.thumb) {
onStateChange?.(t`Uploading link thumbnail...`) onStateChange?.(t`Uploading link thumbnail...`)
const {path, mime} = resolvedLink.thumb.source const {path, mime} = resolvedLink.thumb.source
const response = await uploadBlob(agent, path, mime) const response = await uploadBlob(pdsClient, path, mime)
blob = response.data.blob blob = toLegacyBlobRef(response.blob)
} }
return { return {
$type: 'app.bsky.embed.external', $type: 'app.bsky.embed.external',
+16
View File
@@ -0,0 +1,16 @@
import {BlobRef} from '@atproto/api'
import {type BlobRef as LexBlobRef} from '@atproto/lex'
/**
* Bridge a lex blob ref (the plain-JSON `{$type: 'blob', ref, mimeType, size}`
* that {@link uploadBlob} now returns) back to the legacy `BlobRef` class
* instance.
*
* Only needed where a blob is handed to a legacy agent write: the legacy
* lexicon blob validator checks `value instanceof BlobRef`, so a plain lex
* blob fails validation, and the legacy serializer would put the wrong shape
* on the wire. Drop each call as its write moves to the lex client.
*/
export function toLegacyBlobRef(blob: LexBlobRef): BlobRef {
return BlobRef.fromJsonRef(blob as Parameters<typeof BlobRef.fromJsonRef>[0])
}
+32 -8
View File
@@ -1,38 +1,62 @@
import {copyAsync} from 'expo-file-system/legacy' import {copyAsync} from 'expo-file-system/legacy'
import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' import {type BlobRef, type Client, type EncodingString} from '@atproto/lex'
import {safeDeleteAsync} from '#/lib/media/manip' import {safeDeleteAsync} from '#/lib/media/manip'
/** /**
* @param encoding Allows overriding the blob's type * The blob-upload response body: `{blob}`. lex `Client.uploadBlob` returns the
* full XRPC response, so this helper unwraps `res.body` for callers.
*/
type UploadBlobResult = {blob: BlobRef}
/**
* @param encoding Allows overriding the blob's type. Passed as the lex upload
* option (NEVER a content-type header - lex-client throws if the encoding is
* set via headers).
*/ */
export async function uploadBlob( export async function uploadBlob(
agent: AtpAgent, client: Client,
input: string | Blob, input: string | Blob,
encoding?: string, encoding?: string,
): Promise<ComAtprotoRepoUploadBlob.Response> { ): Promise<UploadBlobResult> {
if (typeof input === 'string' && input.startsWith('file:')) { if (typeof input === 'string' && input.startsWith('file:')) {
const blob = await asBlob(input) const blob = await asBlob(input)
return agent.uploadBlob(blob, {encoding}) return uploadBlobResult(client, blob, encoding)
} }
if (typeof input === 'string' && input.startsWith('/')) { if (typeof input === 'string' && input.startsWith('/')) {
const blob = await asBlob(`file://${input}`) const blob = await asBlob(`file://${input}`)
return agent.uploadBlob(blob, {encoding}) return uploadBlobResult(client, blob, encoding)
} }
if (typeof input === 'string' && input.startsWith('data:')) { if (typeof input === 'string' && input.startsWith('data:')) {
const blob = await fetch(input).then(r => r.blob()) const blob = await fetch(input).then(r => r.blob())
return agent.uploadBlob(blob, {encoding}) return uploadBlobResult(client, blob, encoding)
} }
if (input instanceof Blob) { if (input instanceof Blob) {
return agent.uploadBlob(input, {encoding}) return uploadBlobResult(client, input, encoding)
} }
throw new TypeError(`Invalid uploadBlob input: ${typeof input}`) throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
} }
async function uploadBlobResult(
client: Client,
blob: Blob,
encoding?: string,
): Promise<UploadBlobResult> {
const res = await client.uploadBlob(blob, {
/*
* The lex encoding option is a branded mime string
* (`${string}/${string}`); callers pass a plain mime string, so assert the
* brand here.
*/
encoding: encoding as EncodingString | undefined,
})
return {blob: res.body.blob}
}
async function asBlob(uri: string): Promise<Blob> { async function asBlob(uri: string): Promise<Blob> {
return withSafeFile(uri, async safeUri => { return withSafeFile(uri, async safeUri => {
// Note // Note
+21 -7
View File
@@ -1,28 +1,42 @@
import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' import {type BlobRef, type Client, type EncodingString} from '@atproto/lex'
/**
* The blob-upload response body: `{blob}`. lex `Client.uploadBlob` returns the
* full XRPC response, so this helper unwraps `res.body` for callers.
*/
type UploadBlobResult = {blob: BlobRef}
/** /**
* @note It is recommended, on web, to use the `file` instance of the file * @note It is recommended, on web, to use the `file` instance of the file
* selector input element, rather than a `data:` URL, to avoid * selector input element, rather than a `data:` URL, to avoid
* loading the file into memory. `File` extends `Blob` "file" instances can * loading the file into memory. `File` extends `Blob` "file" instances can
* be passed directly to this function. * be passed directly to this function.
*
* @param encoding Passed as the lex upload option (NEVER a content-type header
* - lex-client throws if the encoding is set via headers).
*/ */
export async function uploadBlob( export async function uploadBlob(
agent: AtpAgent, client: Client,
input: string | Blob, input: string | Blob,
encoding?: string, encoding?: string,
): Promise<ComAtprotoRepoUploadBlob.Response> { ): Promise<UploadBlobResult> {
/*
* The lex encoding option is a branded mime string (`${string}/${string}`);
* callers pass a plain mime string, so assert the brand here.
*/
const enc = encoding as EncodingString | undefined
if ( if (
typeof input === 'string' && typeof input === 'string' &&
(input.startsWith('data:') || input.startsWith('blob:')) (input.startsWith('data:') || input.startsWith('blob:'))
) { ) {
const blob = await fetch(input).then(r => r.blob()) const blob = await fetch(input).then(r => r.blob())
return agent.uploadBlob(blob, {encoding}) const res = await client.uploadBlob(blob, {encoding: enc})
return {blob: res.body.blob}
} }
if (input instanceof Blob) { if (input instanceof Blob) {
return agent.uploadBlob(input, { const res = await client.uploadBlob(input, {encoding: enc})
encoding, return {blob: res.body.blob}
})
} }
throw new TypeError(`Invalid uploadBlob input: ${typeof input}`) throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
@@ -15,6 +15,7 @@ import {Trans} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import { import {
BSKY_APP_ACCOUNT_DID, BSKY_APP_ACCOUNT_DID,
DISCOVER_SAVED_FEED, DISCOVER_SAVED_FEED,
@@ -153,7 +154,7 @@ export function StepFinished() {
const {imageUri, imageMime} = profileStepResults const {imageUri, imageMime} = profileStepResults
const blobPromise = const blobPromise =
imageUri && imageMime imageUri && imageMime
? uploadBlob(agent, imageUri, imageMime) ? uploadBlob(pdsClient, imageUri, imageMime)
: undefined : undefined
await agent.upsertProfile(async existing => { await agent.upsertProfile(async existing => {
@@ -161,8 +162,8 @@ export function StepFinished() {
if (blobPromise) { if (blobPromise) {
const res = await blobPromise const res = await blobPromise
if (res.data.blob) { if (res.blob) {
next.avatar = res.data.blob next.avatar = toLegacyBlobRef(res.blob)
} }
} }
+5 -11
View File
@@ -1,5 +1,5 @@
import {type AppBskyGraphDefs} from '@atproto/api' import {type AppBskyGraphDefs} from '@atproto/api'
import {type $Typed, type Client, type l} from '@atproto/lex' import {type $Typed, type Client} from '@atproto/lex'
import { import {
type AtIdentifierString, type AtIdentifierString,
AtUri, AtUri,
@@ -56,7 +56,6 @@ export interface ListCreateMutateParams {
export function useListCreateMutation() { export function useListCreateMutation() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
return useMutation<{uri: string; cid: string}, Error, ListCreateMutateParams>( return useMutation<{uri: string; cid: string}, Error, ListCreateMutateParams>(
@@ -86,12 +85,8 @@ export function useListCreateMutation() {
createdAt: toDatetimeString(new Date()), createdAt: toDatetimeString(new Date()),
} }
if (avatar) { if (avatar) {
const blobRes = await uploadBlob(agent, avatar.path, avatar.mime) const blobRes = await uploadBlob(pdsClient, avatar.path, avatar.mime)
/* record.avatar = blobRes.blob
* `uploadBlob` still returns the legacy `BlobRef` class instance;
* it moves to the client with the rest of the blob pipeline.
*/
record.avatar = blobRes.data.blob as unknown as l.BlobRef
} }
const res = await pdsClient.create(app.bsky.graph.list, record) const res = await pdsClient.create(app.bsky.graph.list, record)
@@ -120,7 +115,6 @@ export interface ListMetadataMutateParams {
} }
export function useListMetadataMutation() { export function useListMetadataMutation() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -149,8 +143,8 @@ export function useListMetadataMutation() {
record.description = description record.description = description
record.descriptionFacets = descriptionFacets record.descriptionFacets = descriptionFacets
if (avatar) { if (avatar) {
const blobRes = await uploadBlob(agent, avatar.path, avatar.mime) const blobRes = await uploadBlob(pdsClient, avatar.path, avatar.mime)
record.avatar = blobRes.data.blob as unknown as l.BlobRef record.avatar = blobRes.blob
} else if (avatar === null) { } else if (avatar === null) {
record.avatar = undefined record.avatar = undefined
} }
+8 -11
View File
@@ -7,7 +7,6 @@ import {
type AppBskyGraphGetFollows, type AppBskyGraphGetFollows,
type AtpAgent, type AtpAgent,
AtUri, AtUri,
type ComAtprotoRepoUploadBlob,
type Un$Typed, type Un$Typed,
} from '@atproto/api' } from '@atproto/api'
import { import {
@@ -25,6 +24,7 @@ import {
} from '@tanstack/react-query' } from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {updateProfileShadow} from '#/state/cache/profile-shadow' import {updateProfileShadow} from '#/state/cache/profile-shadow'
@@ -149,6 +149,7 @@ interface ProfileUpdateParams {
export function useProfileUpdateMutation() { export function useProfileUpdateMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
const pdsClient = usePdsClient()
const updateProfileVerificationCache = useUpdateProfileVerificationCache() const updateProfileVerificationCache = useUpdateProfileVerificationCache()
return useMutation<void, Error, ProfileUpdateParams>({ return useMutation<void, Error, ProfileUpdateParams>({
mutationFn: async ({ mutationFn: async ({
@@ -158,22 +159,18 @@ export function useProfileUpdateMutation() {
newUserBanner, newUserBanner,
checkCommitted, checkCommitted,
}) => { }) => {
let newUserAvatarPromise: let newUserAvatarPromise: ReturnType<typeof uploadBlob> | undefined
| Promise<ComAtprotoRepoUploadBlob.Response>
| undefined
if (newUserAvatar) { if (newUserAvatar) {
newUserAvatarPromise = uploadBlob( newUserAvatarPromise = uploadBlob(
agent, pdsClient,
newUserAvatar.path, newUserAvatar.path,
newUserAvatar.mime, newUserAvatar.mime,
) )
} }
let newUserBannerPromise: let newUserBannerPromise: ReturnType<typeof uploadBlob> | undefined
| Promise<ComAtprotoRepoUploadBlob.Response>
| undefined
if (newUserBanner) { if (newUserBanner) {
newUserBannerPromise = uploadBlob( newUserBannerPromise = uploadBlob(
agent, pdsClient,
newUserBanner.path, newUserBanner.path,
newUserBanner.mime, newUserBanner.mime,
) )
@@ -191,13 +188,13 @@ export function useProfileUpdateMutation() {
} }
if (newUserAvatarPromise) { if (newUserAvatarPromise) {
const res = await newUserAvatarPromise const res = await newUserAvatarPromise
next.avatar = res.data.blob next.avatar = toLegacyBlobRef(res.blob)
} else if (newUserAvatar === null) { } else if (newUserAvatar === null) {
next.avatar = undefined next.avatar = undefined
} }
if (newUserBannerPromise) { if (newUserBannerPromise) {
const res = await newUserBannerPromise const res = await newUserBannerPromise
next.banner = res.data.blob next.banner = toLegacyBlobRef(res.blob)
} else if (newUserBanner === null) { } else if (newUserBanner === null) {
next.banner = undefined next.banner = undefined
} }
+9 -1
View File
@@ -96,7 +96,12 @@ import {
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {resolveLinkQueryOptions} from '#/state/queries/resolve-link' import {resolveLinkQueryOptions} from '#/state/queries/resolve-link'
import {useAgent, useAppviewClient, useSession} from '#/state/session' import {
useAgent,
useAppviewClient,
usePdsClient,
useSession,
} from '#/state/session'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
@@ -277,6 +282,7 @@ export const ComposePost = ({
: VIDEO_MAX_DURATION_MS : VIDEO_MAX_DURATION_MS
const agent = useAgent() const agent = useAgent()
const client = useAppviewClient() const client = useAppviewClient()
const pdsClient = usePdsClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const currentDid = currentAccount!.did const currentDid = currentAccount!.did
const {closeComposer} = useComposerControls() const {closeComposer} = useComposerControls()
@@ -1084,6 +1090,7 @@ export const ComposePost = ({
onStateChange: setPublishingStage, onStateChange: setPublishingStage,
langs: currentLanguages, langs: currentLanguages,
appviewClient: client, appviewClient: client,
pdsClient,
}) })
).uris[0] ).uris[0]
@@ -1279,6 +1286,7 @@ export const ComposePost = ({
ax, ax,
agent, agent,
client, client,
pdsClient,
canPost, canPost,
isPublishing, isPublishing,
currentLanguages, currentLanguages,