migrate the blob upload helper to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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',
|
||||||
|
|||||||
@@ -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])
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user