[SDK] Delete the bridge agent and rework the session bundle onto lex clients (#11385)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {type AppBskyFeedDefs, AtpAgent, jsonStringToLex} from '@atproto/api'
|
||||
import {type Client, type XrpcRequestParams} from '@atproto/lex'
|
||||
import {type AppBskyFeedDefs, jsonStringToLex} from '@atproto/api'
|
||||
import {Client, type XrpcRequestParams} from '@atproto/lex'
|
||||
|
||||
import {
|
||||
getAppLanguageAsContentLanguage,
|
||||
@@ -115,12 +115,12 @@ async function loggedOutFetch({
|
||||
}): Promise<app.bsky.feed.getFeed.$OutputBody | null> {
|
||||
let contentLangs = getAppLanguageAsContentLanguage()
|
||||
|
||||
/**
|
||||
* Copied from our root `Agent` class
|
||||
* @see https://github.com/bluesky-social/atproto/blob/60df3fc652b00cdff71dd9235d98a7a4bb828f05/packages/api/src/agent.ts#L120
|
||||
/*
|
||||
* This request is hand-rolled rather than issued through a client, so it has
|
||||
* to reproduce the header lex would have emitted from the global static.
|
||||
*/
|
||||
const labelersHeader = {
|
||||
'atproto-accept-labelers': AtpAgent.appLabelers
|
||||
'atproto-accept-labelers': Client.appLabelers
|
||||
.map(l => `${l};redact`)
|
||||
.join(', '),
|
||||
}
|
||||
|
||||
+37
-27
@@ -1,4 +1,4 @@
|
||||
import {type AtpAgent, ChatBskyGroupDefs} from '@atproto/api'
|
||||
import {ChatBskyGroupDefs} from '@atproto/api'
|
||||
import {TID} from '@atproto/common-web'
|
||||
import {type $Typed, type Client} from '@atproto/lex'
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {type LinkResolvers} from '#/lib/api/resolve'
|
||||
import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||
@@ -47,6 +48,11 @@ interface PostOpts {
|
||||
* fallback keeps facet detection working when logged out.
|
||||
*/
|
||||
appviewClient: Client
|
||||
/*
|
||||
* A quoted chat invite link resolves its preview through the chat service, so
|
||||
* the embed resolver needs the chat client alongside the appview one.
|
||||
*/
|
||||
chatClient: Client
|
||||
/*
|
||||
* The repo write itself (applyWrites) plus every record blob (images,
|
||||
* gallery items, link thumbnails, video captions) goes to the account's own
|
||||
@@ -55,15 +61,7 @@ interface PostOpts {
|
||||
pdsClient: Client
|
||||
}
|
||||
|
||||
/**
|
||||
* The `agent` is only still here for the link/gif resolvers in `resolve.ts`,
|
||||
* which read through it; drop the parameter when those move to the clients.
|
||||
*/
|
||||
export async function post(
|
||||
agent: AtpAgent,
|
||||
queryClient: QueryClient,
|
||||
opts: PostOpts,
|
||||
) {
|
||||
export async function post(queryClient: QueryClient, opts: PostOpts) {
|
||||
const thread = opts.thread
|
||||
opts.onStateChange?.(t`Processing...`)
|
||||
|
||||
@@ -95,7 +93,8 @@ export async function post(
|
||||
// Not awaited to avoid waterfalls.
|
||||
const rtPromise = resolveRT(opts.appviewClient, draft.richtext)
|
||||
const embedPromise = resolveEmbed(
|
||||
agent,
|
||||
opts.appviewClient,
|
||||
opts.chatClient,
|
||||
opts.pdsClient,
|
||||
queryClient,
|
||||
draft,
|
||||
@@ -251,7 +250,8 @@ async function resolveReply(appviewClient: Client, replyTo: string) {
|
||||
}
|
||||
|
||||
async function resolveEmbed(
|
||||
agent: AtpAgent,
|
||||
appviewClient: Client,
|
||||
chatClient: Client,
|
||||
pdsClient: Client,
|
||||
queryClient: QueryClient,
|
||||
draft: PostDraft,
|
||||
@@ -259,8 +259,19 @@ async function resolveEmbed(
|
||||
): Promise<app.bsky.feed.post.Main['embed']> {
|
||||
if (draft.embed.quote) {
|
||||
const [resolvedMedia, resolvedQuote] = await Promise.all([
|
||||
resolveMedia(agent, pdsClient, queryClient, draft.embed, onStateChange),
|
||||
resolveRecord(agent, queryClient, draft.embed.quote.uri),
|
||||
resolveMedia(
|
||||
appviewClient,
|
||||
chatClient,
|
||||
pdsClient,
|
||||
queryClient,
|
||||
draft.embed,
|
||||
onStateChange,
|
||||
),
|
||||
resolveRecord(
|
||||
{appviewClient, chatClient},
|
||||
queryClient,
|
||||
draft.embed.quote.uri,
|
||||
),
|
||||
])
|
||||
if (resolvedMedia) {
|
||||
return {
|
||||
@@ -278,7 +289,8 @@ async function resolveEmbed(
|
||||
}
|
||||
}
|
||||
const resolvedMedia = await resolveMedia(
|
||||
agent,
|
||||
appviewClient,
|
||||
chatClient,
|
||||
pdsClient,
|
||||
queryClient,
|
||||
draft.embed,
|
||||
@@ -290,7 +302,7 @@ async function resolveEmbed(
|
||||
if (draft.embed.link) {
|
||||
const resolvedLink = await fetchResolveLinkQuery(
|
||||
queryClient,
|
||||
agent,
|
||||
{appviewClient, chatClient},
|
||||
draft.embed.link.uri,
|
||||
)
|
||||
if (resolvedLink.type === 'record') {
|
||||
@@ -309,7 +321,8 @@ async function resolveEmbed(
|
||||
}
|
||||
|
||||
async function resolveMedia(
|
||||
agent: AtpAgent,
|
||||
appviewClient: Client,
|
||||
chatClient: Client,
|
||||
pdsClient: Client,
|
||||
queryClient: QueryClient,
|
||||
embedDraft: EmbedDraft,
|
||||
@@ -423,11 +436,7 @@ async function resolveMedia(
|
||||
}
|
||||
if (embedDraft.media?.type === 'gif') {
|
||||
const gifDraft = embedDraft.media
|
||||
const resolvedGif = await fetchResolveGifQuery(
|
||||
queryClient,
|
||||
agent,
|
||||
gifDraft.gif,
|
||||
)
|
||||
const resolvedGif = await fetchResolveGifQuery(queryClient, gifDraft.gif)
|
||||
let blob: app.bsky.embed.external.External['thumb']
|
||||
if (resolvedGif.thumb) {
|
||||
onStateChange?.(t`Uploading link thumbnail...`)
|
||||
@@ -448,7 +457,7 @@ async function resolveMedia(
|
||||
if (embedDraft.link) {
|
||||
const resolvedLink = await fetchResolveLinkQuery(
|
||||
queryClient,
|
||||
agent,
|
||||
{appviewClient, chatClient},
|
||||
embedDraft.link.uri,
|
||||
)
|
||||
if (resolvedLink.type === 'external') {
|
||||
@@ -490,15 +499,16 @@ async function resolveMedia(
|
||||
}
|
||||
|
||||
/*
|
||||
* `resolve.ts` still resolves through the agent and returns legacy-typed refs;
|
||||
* assert at the boundary until it moves to the clients.
|
||||
* `resolve.ts` still returns legacy-typed views, so its strong refs carry plain
|
||||
* strings where the record write wants the branded syntax types; assert at the
|
||||
* boundary until those views come from the generated lexicons.
|
||||
*/
|
||||
async function resolveRecord(
|
||||
agent: AtpAgent,
|
||||
clients: LinkResolvers,
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Promise<com.atproto.repo.strongRef.Main> {
|
||||
const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri)
|
||||
const resolvedLink = await fetchResolveLinkQuery(queryClient, clients, uri)
|
||||
if (resolvedLink.type !== 'record') {
|
||||
throw Error(t`Expected uri to resolve to a record`)
|
||||
}
|
||||
|
||||
+54
-34
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AtpAgent,
|
||||
type ComAtprotoRepoStrongRef,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type AtUriString, type HandleString} from '@atproto/syntax'
|
||||
|
||||
import {DM_SERVICE_HEADERS, IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
||||
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
||||
import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta'
|
||||
import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
|
||||
import {downloadAndResize} from '#/lib/media/manip'
|
||||
@@ -29,6 +30,7 @@ import {type ComposerImage} from '#/state/gallery'
|
||||
import {createComposerImage} from '#/state/gallery'
|
||||
import {type ChatInvitePreview} from '#/state/queries/join-links'
|
||||
import {type Gif} from '#/features/gifPicker/types'
|
||||
import {app, chat, com} from '#/lexicons'
|
||||
import {createGIFDescription} from '../gif-alt-text'
|
||||
|
||||
type ResolvedExternalLink = {
|
||||
@@ -94,8 +96,21 @@ export class EmbeddingDisabledError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The clients a link resolution may need.
|
||||
*
|
||||
* Everything but the chat-invite branch is an appview read, and the chat invite
|
||||
* preview goes through the chat client so it is proxied to the chat service.
|
||||
* Both are passed together because the caller cannot know which branch a URL
|
||||
* will take until it is parsed.
|
||||
*/
|
||||
export type LinkResolvers = {
|
||||
appviewClient: Client
|
||||
chatClient: Client
|
||||
}
|
||||
|
||||
export async function resolveLink(
|
||||
agent: AtpAgent,
|
||||
{appviewClient, chatClient}: LinkResolvers,
|
||||
uri: string,
|
||||
): Promise<ResolvedLink> {
|
||||
if (isShortLink(uri)) {
|
||||
@@ -124,15 +139,17 @@ export async function resolveLink(
|
||||
const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean)
|
||||
const did = await fetchDid(handleOrDid)
|
||||
const feed = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
|
||||
const res = await agent.app.bsky.feed.getFeedGenerator({feed})
|
||||
const data = await appviewClient.call(app.bsky.feed.getFeedGenerator, {
|
||||
feed: feed,
|
||||
})
|
||||
return {
|
||||
type: 'record',
|
||||
record: {
|
||||
uri: res.data.view.uri,
|
||||
cid: res.data.view.cid,
|
||||
uri: data.view.uri,
|
||||
cid: data.view.cid,
|
||||
},
|
||||
kind: 'feed',
|
||||
view: res.data.view,
|
||||
view: data.view,
|
||||
}
|
||||
}
|
||||
if (isBskyListUrl(uri)) {
|
||||
@@ -140,28 +157,29 @@ export async function resolveLink(
|
||||
const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean)
|
||||
const did = await fetchDid(handleOrDid)
|
||||
const list = makeRecordUri(did, 'app.bsky.graph.list', rkey)
|
||||
const res = await agent.app.bsky.graph.getList({list})
|
||||
const data = await appviewClient.call(app.bsky.graph.getList, {
|
||||
list: list,
|
||||
})
|
||||
return {
|
||||
type: 'record',
|
||||
record: {
|
||||
uri: res.data.list.uri,
|
||||
cid: res.data.list.cid,
|
||||
uri: data.list.uri,
|
||||
cid: data.list.cid,
|
||||
},
|
||||
kind: 'list',
|
||||
view: res.data.list,
|
||||
view: data.list,
|
||||
}
|
||||
}
|
||||
const chatInviteCode = getChatInviteCodeFromUrl(uri)
|
||||
if (chatInviteCode) {
|
||||
const res = await agent.chat.bsky.group.getJoinLinkPreviews(
|
||||
{codes: [chatInviteCode]},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.getJoinLinkPreviews, {
|
||||
codes: [chatInviteCode],
|
||||
})
|
||||
return {
|
||||
type: 'chat-invite',
|
||||
uri,
|
||||
code: chatInviteCode,
|
||||
view: res.data.joinLinkPreviews[0],
|
||||
view: data.joinLinkPreviews[0],
|
||||
}
|
||||
}
|
||||
if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) {
|
||||
@@ -173,15 +191,17 @@ export async function resolveLink(
|
||||
}
|
||||
const did = await fetchDid(parsed.name)
|
||||
const starterPack = createStarterPackUri({did, rkey: parsed.rkey})
|
||||
const res = await agent.app.bsky.graph.getStarterPack({starterPack})
|
||||
const data = await appviewClient.call(app.bsky.graph.getStarterPack, {
|
||||
starterPack: starterPack as AtUriString,
|
||||
})
|
||||
return {
|
||||
type: 'record',
|
||||
record: {
|
||||
uri: res.data.starterPack.uri,
|
||||
cid: res.data.starterPack.cid,
|
||||
uri: data.starterPack.uri,
|
||||
cid: data.starterPack.cid,
|
||||
},
|
||||
kind: 'starter-pack',
|
||||
view: res.data.starterPack,
|
||||
view: data.starterPack,
|
||||
}
|
||||
}
|
||||
return resolveExternal(uri)
|
||||
@@ -190,17 +210,17 @@ export async function resolveLink(
|
||||
async function getPost({uri}: {uri: string}) {
|
||||
const urip = new AtUri(uri)
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
const data = await appviewClient.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: urip.host as HandleString},
|
||||
)
|
||||
urip.host = data.did
|
||||
}
|
||||
const res = await agent.getPosts({
|
||||
const data = await appviewClient.call(app.bsky.feed.getPosts, {
|
||||
uris: [urip.toString()],
|
||||
})
|
||||
if (res.success && res.data.posts[0]) {
|
||||
return res.data.posts[0]
|
||||
if (data.posts[0]) {
|
||||
return data.posts[0]
|
||||
}
|
||||
throw new Error('getPost: post not found')
|
||||
}
|
||||
@@ -209,17 +229,17 @@ export async function resolveLink(
|
||||
async function fetchDid(handleOrDid: string) {
|
||||
let identifier = handleOrDid
|
||||
if (!identifier.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({handle: identifier})
|
||||
identifier = res.data.did
|
||||
const data = await appviewClient.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: identifier as HandleString},
|
||||
)
|
||||
identifier = data.did
|
||||
}
|
||||
return identifier
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveGif(
|
||||
agent: AtpAgent,
|
||||
gif: Gif,
|
||||
): Promise<ResolvedExternalLink> {
|
||||
export async function resolveGif(gif: Gif): Promise<ResolvedExternalLink> {
|
||||
const gifUrl = gif.media_formats.gif.url
|
||||
const params = new URLSearchParams()
|
||||
params.set('hh', String(gif.media_formats.gif.dims[1]))
|
||||
|
||||
Reference in New Issue
Block a user