diff --git a/src/lib/api/__tests__/computeCid.test.ts b/src/lib/api/__tests__/computeCid.test.ts index ce0adc4a85..d5f8b50726 100644 --- a/src/lib/api/__tests__/computeCid.test.ts +++ b/src/lib/api/__tests__/computeCid.test.ts @@ -7,7 +7,6 @@ jest.unmock('multiformats/cid') jest.unmock('multiformats/hashes/hasher') -import {BlobRef} from '@atproto/api' import {CID} from 'multiformats/cid' import {computeCid} from '#/lib/api/computeCid' @@ -67,8 +66,8 @@ describe('computeCid', () => { * `{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID. The * structural lex-blob guard passes it through `prepareForHashing` * untouched and DAG-CBOR encodes its CID `ref` as a CID link. The golden - * CID below is the byte-identical value the pre-migration `BlobRef` class - * instance produced via `.ipld()`. + * CID below is the byte-identical value the pre-migration legacy blob + * class instance produced via `.ipld()`. */ const blob = { $type: 'blob' as const, @@ -81,18 +80,6 @@ describe('computeCid', () => { ) }) - it('case 2b: a legacy BlobRef instance hashes to the same CID', async () => { - /* - * The video pipeline still yields legacy `BlobRef` class instances, so - * `prepareForHashing` keeps its `instanceof` guard. Both branches must - * agree: this asserts the SAME golden CID as case 2. - */ - const blob = new BlobRef(CID.parse(BLOB_CID), 'image/jpeg', 12345) - expect(await computeCid(postWithImageBlob(blob))).toBe( - 'bafyreiem7g6vja66nebr7he4fshfnlyndyldbvle2n265oixscmepjcbii', - ) - }) - it('case 3: three-post thread chains reply StrongRef CIDs', async () => { const did = 'did:plc:abc123' const base = new Date('2024-01-01T00:00:00.000Z') diff --git a/src/lib/api/computeCid.ts b/src/lib/api/computeCid.ts index 18821793ae..edd23e5901 100644 --- a/src/lib/api/computeCid.ts +++ b/src/lib/api/computeCid.ts @@ -1,4 +1,3 @@ -import {BlobRef} from '@atproto/api' import {sha256} from 'js-sha256' import {CID} from 'multiformats/cid' import * as Hasher from 'multiformats/hashes/hasher' @@ -73,17 +72,6 @@ function prepareForHashing(v: any): any { return v } - /* - * The video pipeline still reads its blob off the legacy agent - * (`app.bsky.video.getJobStatus` in composer `state/video`), which yields a - * `BlobRef` class instance. `ipld()` gives the same IPLD shape a lex blob - * already has, so both branches hash identically. Drop this guard once the - * video client is migrated. - */ - if (v instanceof BlobRef) { - return v.ipld() - } - // Walk through arrays if (Array.isArray(v)) { let pure = true diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts index 335bf28c84..cd99fc9d57 100644 --- a/src/lib/api/feed-manip.ts +++ b/src/lib/api/feed-manip.ts @@ -1,17 +1,10 @@ -import { - type AppBskyActorDefs, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedDefs, - AppBskyFeedPost, -} from '@atproto/api' - +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {isPostInLanguage} from '../../locale/helpers' import {FALLBACK_MARKER_POST} from './feed/home' import {type ReasonFeedSource} from './feed/types' -type FeedViewPost = AppBskyFeedDefs.FeedViewPost +type FeedViewPost = app.bsky.feed.defs.FeedViewPost export type FeedTunerFn = ( tuner: FeedTuner, @@ -20,18 +13,18 @@ export type FeedTunerFn = ( ) => FeedViewPostsSlice[] type FeedSliceItem = { - post: AppBskyFeedDefs.PostView - record: AppBskyFeedPost.Record - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + post: app.bsky.feed.defs.PostView + record: app.bsky.feed.post.Main + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined isParentBlocked: boolean isParentNotFound: boolean } type AuthorContext = { - author: AppBskyActorDefs.ProfileViewBasic - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + author: app.bsky.actor.defs.ProfileViewBasic + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + grandparentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + rootAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined } export class FeedViewPostsSlice { @@ -53,7 +46,7 @@ export class FeedViewPostsSlice { this.isOrphan = false this.isThreadMuted = post.viewer?.threadMuted ?? false this.feedPostUri = post.uri - if (AppBskyFeedDefs.isPostView(reply?.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, reply?.root)) { this.rootUri = reply.root.uri } else { this.rootUri = post.uri @@ -69,16 +62,19 @@ export class FeedViewPostsSlice { return } if ( - !AppBskyFeedPost.isRecord(post.record) || - !bsky.validate(post.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.post, post.record) || + !bsky.matches(app.bsky.feed.post, post.record) ) { return } const parent = reply?.parent - const isParentBlocked = AppBskyFeedDefs.isBlockedPost(parent) - const isParentNotFound = AppBskyFeedDefs.isNotFoundPost(parent) - let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - if (AppBskyFeedDefs.isPostView(parent)) { + const isParentBlocked = bsky.isType(app.bsky.feed.defs.blockedPost, parent) + const isParentNotFound = bsky.isType( + app.bsky.feed.defs.notFoundPost, + parent, + ) + let parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + if (bsky.isType(app.bsky.feed.defs.postView, parent)) { parentAuthor = parent.author } this.items.push({ @@ -100,18 +96,18 @@ export class FeedViewPostsSlice { return } if ( - !AppBskyFeedDefs.isPostView(parent) || - !AppBskyFeedPost.isRecord(parent.record) || - !bsky.validate(parent.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.defs.postView, parent) || + !bsky.isType(app.bsky.feed.post, parent.record) || + !bsky.matches(app.bsky.feed.post, parent.record) ) { this.isOrphan = true return } const root = reply.root const rootIsView = - AppBskyFeedDefs.isPostView(root) || - AppBskyFeedDefs.isBlockedPost(root) || - AppBskyFeedDefs.isNotFoundPost(root) + bsky.isType(app.bsky.feed.defs.postView, root) || + bsky.isType(app.bsky.feed.defs.blockedPost, root) || + bsky.isType(app.bsky.feed.defs.notFoundPost, root) /* * If the parent is also the root, we just so happen to have the data we * need to compute if the parent's parent (grandparent) is blocked. This @@ -124,10 +120,10 @@ export class FeedViewPostsSlice { : undefined const grandparentAuthor = reply.grandparentAuthor const isGrandparentBlocked = Boolean( - grandparent && AppBskyFeedDefs.isBlockedPost(grandparent), + grandparent && bsky.isType(app.bsky.feed.defs.blockedPost, grandparent), ) const isGrandparentNotFound = Boolean( - grandparent && AppBskyFeedDefs.isNotFoundPost(grandparent), + grandparent && bsky.isType(app.bsky.feed.defs.notFoundPost, grandparent), ) this.items.unshift({ post: parent, @@ -142,9 +138,9 @@ export class FeedViewPostsSlice { // de-deduping } if ( - !AppBskyFeedDefs.isPostView(root) || - !AppBskyFeedPost.isRecord(root.record) || - !bsky.validate(root.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.defs.postView, root) || + !bsky.isType(app.bsky.feed.post, root.record) || + !bsky.matches(app.bsky.feed.post, root.record) ) { this.isOrphan = true return @@ -167,14 +163,14 @@ export class FeedViewPostsSlice { get isQuotePost() { const embed = this._feedPost.post.embed return ( - AppBskyEmbedRecord.isView(embed) || - AppBskyEmbedRecordWithMedia.isView(embed) + bsky.isType(app.bsky.embed.record.view, embed) || + bsky.isType(app.bsky.embed.recordWithMedia.view, embed) ) } get isReply() { return ( - AppBskyFeedPost.isRecord(this._feedPost.post.record) && + bsky.isType(app.bsky.feed.post, this._feedPost.post.record) && !!this._feedPost.post.record.reply ) } @@ -195,7 +191,7 @@ export class FeedViewPostsSlice { get isRepost() { const reason = this._feedPost.reason - return AppBskyFeedDefs.isReasonRepost(reason) + return bsky.isType(app.bsky.feed.defs.reasonRepost, reason) } get likeCount() { @@ -208,18 +204,18 @@ export class FeedViewPostsSlice { getAuthors(): AuthorContext { const feedPost = this._feedPost - let author: AppBskyActorDefs.ProfileViewBasic = feedPost.post.author - let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - let grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - let rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + let author: app.bsky.actor.defs.ProfileViewBasic = feedPost.post.author + let parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + let grandparentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + let rootAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined if (feedPost.reply) { - if (AppBskyFeedDefs.isPostView(feedPost.reply.parent)) { + if (bsky.isType(app.bsky.feed.defs.postView, feedPost.reply.parent)) { parentAuthor = feedPost.reply.parent.author } if (feedPost.reply.grandparentAuthor) { grandparentAuthor = feedPost.reply.grandparentAuthor } - if (AppBskyFeedDefs.isPostView(feedPost.reply.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, feedPost.reply.root)) { rootAuthor = feedPost.reply.root.author } } @@ -514,7 +510,7 @@ function shouldDisplayReplyInFollowing( } function isSelfOrFollowing( - profile: AppBskyActorDefs.ProfileViewBasic, + profile: app.bsky.actor.defs.ProfileViewBasic, userDid: string, ) { return Boolean(profile.did === userDid || profile.viewer?.following) diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts index 92f8d327b4..e89c26cee0 100644 --- a/src/lib/api/feed/author.ts +++ b/src/lib/api/feed/author.ts @@ -1,6 +1,6 @@ -import {AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' +import * as bsky from '#/types/bsky' import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' @@ -29,7 +29,7 @@ export class AuthorFeedAPI implements FeedAPI { return params } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getAuthorFeed, { ...this.params, limit: 1, @@ -61,12 +61,15 @@ export class AuthorFeedAPI implements FeedAPI { } } - _filter(feed: AppBskyFeedDefs.FeedViewPost[]) { + _filter(feed: app.bsky.feed.defs.FeedViewPost[]) { if (this.params.filter === 'posts_and_author_threads') { return feed.filter(post => { const isReply = post.reply - const isRepost = AppBskyFeedDefs.isReasonRepost(post.reason) - const isPin = AppBskyFeedDefs.isReasonPin(post.reason) + const isRepost = bsky.isType( + app.bsky.feed.defs.reasonRepost, + post.reason, + ) + const isPin = bsky.isType(app.bsky.feed.defs.reasonPin, post.reason) if (!isReply) return true if (isRepost || isPin) return true return isReply && isAuthorReplyChain(this.params.actor, post, feed) @@ -79,15 +82,15 @@ export class AuthorFeedAPI implements FeedAPI { function isAuthorReplyChain( actor: string, - post: AppBskyFeedDefs.FeedViewPost, - posts: AppBskyFeedDefs.FeedViewPost[], + post: app.bsky.feed.defs.FeedViewPost, + posts: app.bsky.feed.defs.FeedViewPost[], ): boolean { // current post is by a different user (shouldn't happen) if (post.post.author.did !== actor) return false const replyParent = post.reply?.parent - if (AppBskyFeedDefs.isPostView(replyParent)) { + if (bsky.isType(app.bsky.feed.defs.postView, replyParent)) { // reply parent is by a different user if (replyParent.author.did !== actor) return false diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index 6d960813d3..6185f788df 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -1,5 +1,4 @@ -import {type AppBskyFeedDefs, jsonStringToLex} from '@atproto/api' -import {Client, type XrpcRequestParams} from '@atproto/lex' +import {Client, lexParse, type XrpcRequestParams} from '@atproto/lex' import { getAppLanguageAsContentLanguage, @@ -30,7 +29,7 @@ export class CustomFeedAPI implements FeedAPI { this.userInterests = userInterests } - async peekLatest(): Promise { + async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') const data = await this.client.call( app.bsky.feed.getFeed, @@ -140,7 +139,7 @@ async function loggedOutFetch({ * is asserted here just as the old-world one was. */ let data = res.ok - ? (jsonStringToLex(await res.text()) as app.bsky.feed.getFeed.$OutputBody) + ? (lexParse(await res.text()) as app.bsky.feed.getFeed.$OutputBody) : null if (data?.feed?.length) { return data @@ -154,7 +153,7 @@ async function loggedOutFetch({ {method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}}, ) data = res.ok - ? (jsonStringToLex(await res.text()) as app.bsky.feed.getFeed.$OutputBody) + ? (lexParse(await res.text()) as app.bsky.feed.getFeed.$OutputBody) : null if (data?.feed?.length) { return data diff --git a/src/lib/api/feed/demo.ts b/src/lib/api/feed/demo.ts index a4c1c360e9..a2aca5073d 100644 --- a/src/lib/api/feed/demo.ts +++ b/src/lib/api/feed/demo.ts @@ -1,6 +1,6 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' +import {app} from '#/lexicons' import {DEMO_FEED} from '#/lib/demo' import {type FeedAPI, type FeedAPIResponse} from './types' @@ -11,7 +11,7 @@ export class DemoFeedAPI implements FeedAPI { this.client = client } - async peekLatest(): Promise { + async peekLatest(): Promise { return DEMO_FEED.feed[0] } diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts index 596483577a..27a08c6985 100644 --- a/src/lib/api/feed/following.ts +++ b/src/lib/api/feed/following.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {app} from '#/lexicons' @@ -11,7 +10,7 @@ export class FollowingFeedAPI implements FeedAPI { this.client = client } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index a5ff0f3b93..6e15b75530 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,7 +1,7 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' +import {app} from '#/lexicons' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {CustomFeedAPI} from './custom' import {FollowingFeedAPI} from './following' @@ -15,7 +15,7 @@ import {type FeedAPI, type FeedAPIResponse} from './types' // we use this fallback marker post to drive this instead. see Feed.tsx // for the usage. // -prf -export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { +export const FALLBACK_MARKER_POST: app.bsky.feed.defs.FeedViewPost = { post: { uri: 'fallback-marker-post', cid: 'fake', @@ -63,7 +63,7 @@ export class HomeFeedAPI implements FeedAPI { this.itemCursor = 0 } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.usingDiscover) { return this.discover.peekLatest() } @@ -82,7 +82,7 @@ export class HomeFeedAPI implements FeedAPI { } let returnCursor - let posts: AppBskyFeedDefs.FeedViewPost[] = [] + let posts: app.bsky.feed.defs.FeedViewPost[] = [] if (!this.usingDiscover) { const res = await this.following.fetch({cursor, limit}) diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts index d7d63fc4db..d8bba416ac 100644 --- a/src/lib/api/feed/likes.ts +++ b/src/lib/api/feed/likes.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {app} from '#/lexicons' @@ -23,7 +22,7 @@ export class LikesFeedAPI implements FeedAPI { this.params = feedParams } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getActorLikes, { ...this.params, limit: 1, diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts index e4d07d73d7..51e67bb4e8 100644 --- a/src/lib/api/feed/list.ts +++ b/src/lib/api/feed/list.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {app} from '#/lexicons' @@ -23,7 +22,7 @@ export class ListFeedAPI implements FeedAPI { this.params = feedParams } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getListFeed, { ...this.params, limit: 1, diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 2e3abcb8e3..91d300afb8 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' import shuffle from 'lodash.shuffle' @@ -28,7 +27,7 @@ const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours */ type MergeFeedPage = { cursor?: string - feed: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.FeedViewPost[] } | null export class MergeFeedAPI implements FeedAPI { @@ -89,7 +88,7 @@ export class MergeFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) @@ -136,7 +135,7 @@ export class MergeFeedAPI implements FeedAPI { await Promise.all(promises) // assemble a response by sampling from feeds with content - const posts: AppBskyFeedDefs.FeedViewPost[] = [] + const posts: app.bsky.feed.defs.FeedViewPost[] = [] while (posts.length < limit) { let slice = this.sampleItem() if (slice[0]) { @@ -188,7 +187,7 @@ class MergeFeedSource { feedTuners: FeedTunerFn[] sourceInfo: ReasonFeedSource | undefined cursor: string | undefined = undefined - queue: AppBskyFeedDefs.FeedViewPost[] = [] + queue: app.bsky.feed.defs.FeedViewPost[] = [] hasMore = true constructor({ @@ -210,7 +209,7 @@ class MergeFeedSource { return this.hasMore && this.queue.length === 0 } - take(n: number): AppBskyFeedDefs.FeedViewPost[] { + take(n: number): app.bsky.feed.defs.FeedViewPost[] { return this.queue.splice(0, n) } @@ -327,7 +326,7 @@ class MergeFeedSource_Custom extends MergeFeedSource { // some custom feeds fail to enforce the pagination limit // so we manually truncate here // -prf - let feed: AppBskyFeedDefs.FeedViewPost[] = + let feed: app.bsky.feed.defs.FeedViewPost[] = limit && data.feed.length > limit ? data.feed.slice(0, limit) : data.feed diff --git a/src/lib/api/feed/posts.ts b/src/lib/api/feed/posts.ts index d2bcfc4317..18835ecbd2 100644 --- a/src/lib/api/feed/posts.ts +++ b/src/lib/api/feed/posts.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {logger} from '#/logger' @@ -10,7 +9,7 @@ type GetPostsParams = XrpcRequestParams export class PostListFeedAPI implements FeedAPI { client: Client params: GetPostsParams - peek: AppBskyFeedDefs.FeedViewPost | null = null + peek: app.bsky.feed.defs.FeedViewPost | null = null constructor({ client, @@ -30,7 +29,7 @@ export class PostListFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.peek) return this.peek throw new Error('Has not fetched yet') } diff --git a/src/lib/api/feed/types.ts b/src/lib/api/feed/types.ts index 27fa066fbd..aaed5e5045 100644 --- a/src/lib/api/feed/types.ts +++ b/src/lib/api/feed/types.ts @@ -1,12 +1,12 @@ -import {type AppBskyFeedDefs} from '@atproto/api' +import {app} from '#/lexicons' export interface FeedAPIResponse { cursor?: string - feed: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.FeedViewPost[] } export interface FeedAPI { - peekLatest(): Promise + peekLatest(): Promise fetch({ cursor, limit, diff --git a/src/lib/api/feed/utils.ts b/src/lib/api/feed/utils.ts index c52f402326..f1be1139ab 100644 --- a/src/lib/api/feed/utils.ts +++ b/src/lib/api/feed/utils.ts @@ -1,5 +1,4 @@ -import {AtUri} from '@atproto/api' - +import {AtUri} from '@atproto/syntax' import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' import {IS_WEB} from '#/env' diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index e03a40c391..9119914062 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,4 +1,3 @@ -import {ChatBskyGroupDefs} from '@atproto/api' import {TID} from '@atproto/common-web' import {type $Typed, type Client} from '@atproto/lex' import { @@ -29,7 +28,7 @@ import { type PostDraft, type ThreadDraft, } from '#/view/com/composer/state/composer' -import {app, com} from '#/lexicons' +import {app, chat, com} from '#/lexicons' import * as bsky from '#/types/bsky' import {createGIFDescription} from '../gif-alt-text' import {computeCid} from './computeCid' @@ -483,7 +482,7 @@ async function resolveMedia( } if ( resolvedLink.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolvedLink.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolvedLink.view) ) { return { $type: 'app.bsky.embed.external', diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index 297388edb0..4d9ec3e457 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,11 +1,5 @@ -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - 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 {AtUri, type AtUriString, type HandleString} from '@atproto/syntax' import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' @@ -49,30 +43,30 @@ type ResolvedExternalLink = { type ResolvedPostRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'post' - view: AppBskyFeedDefs.PostView + view: app.bsky.feed.defs.PostView } type ResolvedFeedRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'feed' - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView } type ResolvedListRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'list' - view: AppBskyGraphDefs.ListView + view: app.bsky.graph.defs.ListView } type ResolvedStarterPackRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'starter-pack' - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView } type ResolvedChatInvite = { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index ad1c231129..c9983e393f 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,8 +1,8 @@ import {type Insets, Platform} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type Service} from '@atproto/lex' import {api} from '@bsky.app/sdk' +import {app} from '#/lexicons' import {BLUESKY_PROXY_DID, CHAT_PROXY_DID, IS_DEV} from '#/env' export const LOCAL_DEV_SERVICE = @@ -174,7 +174,7 @@ export const VIDEO_SAVED_FEED = { } export const RECOMMENDED_SAVED_FEEDS: Pick< - AppBskyActorDefs.SavedFeed, + app.bsky.actor.defs.SavedFeed, 'type' | 'value' | 'pinned' >[] = [DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED] diff --git a/src/lib/demo.ts b/src/lib/demo.ts index 5ead62c9d3..b83b4b9a68 100644 --- a/src/lib/demo.ts +++ b/src/lib/demo.ts @@ -1,5 +1,5 @@ -import {type AppBskyFeedGetFeed} from '@atproto/api' import {subDays, subMinutes} from 'date-fns' +import {app} from '#/lexicons' const DID = `did:plc:z72i7hdynmk6r22z27h6tvur` const NOW = new Date() @@ -197,6 +197,6 @@ export const DEMO_FEED = { }, }, ], -} satisfies AppBskyFeedGetFeed.OutputSchema +} satisfies app.bsky.feed.getFeed.$OutputBody export const BOTTOM_BAR_AVI = 'https://bsky.social/about/adi/user_avi.jpg' diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts index e705f0ce95..888d720fa5 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -1,10 +1,10 @@ import {useEffect} from 'react' import * as Notifications from 'expo-notifications' -import {AtUri} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {CommonActions, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {AtUri} from '@atproto/syntax' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {logger as notyLogger} from '#/lib/notifications/util' import {type NavigationProp} from '#/lib/routes/types' diff --git a/src/lib/hooks/usePostViewTracking.ts b/src/lib/hooks/usePostViewTracking.ts index bbe8bb9f24..939a2f1c60 100644 --- a/src/lib/hooks/usePostViewTracking.ts +++ b/src/lib/hooks/usePostViewTracking.ts @@ -1,6 +1,6 @@ import {useCallback, useRef} from 'react' -import {type AppBskyFeedDefs} from '@atproto/api' +import {app} from '#/lexicons' import {type Metrics, useAnalytics} from '#/analytics' /** @@ -17,7 +17,7 @@ export function usePostViewTracking( const seenUrisRef = useRef(new Set()) const trackPostView = useCallback( - (post: AppBskyFeedDefs.PostView) => { + (post: app.bsky.feed.defs.PostView) => { if (seenUrisRef.current.has(post.uri)) return seenUrisRef.current.add(post.uri) diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index ee96d6db43..65b1379046 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -1,5 +1,4 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - +import {app} from '#/lexicons' import {LINK_META_PROXY} from '#/lib/constants' import {getGiphyMetaUri} from '#/lib/strings/embed-player' import {parseStarterPackUri} from '#/lib/strings/starter-pack' @@ -26,8 +25,8 @@ export interface LinkMeta { * The AT-URI of the Atmosphere record representing this external content, if * it exists. Example: a site.standard.document record. */ - associatedRefs?: AppBskyEmbedExternal.External['associatedRefs'] - view?: AppBskyEmbedExternal.View + associatedRefs?: app.bsky.embed.external.External['associatedRefs'] + view?: app.bsky.embed.external.View } export async function getLinkMeta( diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index b2d4c194e0..73daea9ee5 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -1,5 +1,4 @@ import {useMemo} from 'react' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {Client} from '@atproto/lex' import {type DidString} from '@atproto/syntax' import { @@ -13,7 +12,7 @@ import { import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {type AppModerationCause} from '#/components/Pills' -import {type app} from '#/lexicons' +import {type app, com} from '#/lexicons' export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] as const export const OTHER_SELF_LABELS = ['graphic-media'] as const @@ -54,7 +53,7 @@ export function moduiContainsHideableOffense(modui: ModerationUI): boolean { } export function labelIsHideableOffense( - label: ComAtprotoLabelDefs.Label, + label: com.atproto.label.defs.Label, ): boolean { return ['!hide', '!takedown'].includes(label.val) } @@ -64,9 +63,9 @@ export function labelIsHideableOffense( * with `!`) and the user's own "bot" self-label. */ export function filterUserFacingLabels( - labels: ComAtprotoLabelDefs.Label[], + labels: com.atproto.label.defs.Label[], currentAccountDid: string | undefined, -): ComAtprotoLabelDefs.Label[] { +): com.atproto.label.defs.Label[] { return labels.filter( label => !label.val.startsWith('!') && @@ -135,7 +134,11 @@ export type Subject = did: string } -export function useLabelSubject({label}: {label: ComAtprotoLabelDefs.Label}): { +export function useLabelSubject({ + label, +}: { + label: com.atproto.label.defs.Label +}): { subject: Subject } { return useMemo(() => { diff --git a/src/lib/moderation/subjects.ts b/src/lib/moderation/subjects.ts index c1621b300c..fa6de1366a 100644 --- a/src/lib/moderation/subjects.ts +++ b/src/lib/moderation/subjects.ts @@ -1,11 +1,3 @@ -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type AppBskyNotificationListNotifications, - type AppBskyRichtextFacet, - type ChatBskyActorDefs, -} from '@atproto/api' import { hasMutedWord as sdkHasMutedWord, moderateFeedGenerator as sdkModerateFeedGenerator, @@ -18,7 +10,7 @@ import { type ModerationOpts, } from '@bsky.app/sdk/moderation' -import {type app, type chat} from '#/lexicons' +import {app, chat} from '#/lexicons' /* * TRANSITIONAL. The moderation implementation now comes from @@ -43,26 +35,26 @@ type AnyProfileSubject = | app.bsky.actor.defs.ProfileView | app.bsky.actor.defs.ProfileViewDetailed | chat.bsky.actor.defs.ProfileViewBasic - | AppBskyActorDefs.ProfileViewBasic - | AppBskyActorDefs.ProfileView - | AppBskyActorDefs.ProfileViewDetailed - | ChatBskyActorDefs.ProfileViewBasic + | app.bsky.actor.defs.ProfileViewBasic + | app.bsky.actor.defs.ProfileView + | app.bsky.actor.defs.ProfileViewDetailed + | chat.bsky.actor.defs.ProfileViewBasic -type AnyPostSubject = app.bsky.feed.defs.PostView | AppBskyFeedDefs.PostView +type AnyPostSubject = app.bsky.feed.defs.PostView | app.bsky.feed.defs.PostView type AnyUserListSubject = | app.bsky.graph.defs.ListViewBasic | app.bsky.graph.defs.ListView - | AppBskyGraphDefs.ListViewBasic - | AppBskyGraphDefs.ListView + | app.bsky.graph.defs.ListViewBasic + | app.bsky.graph.defs.ListView type AnyFeedGeneratorSubject = | app.bsky.feed.defs.GeneratorView - | AppBskyFeedDefs.GeneratorView + | app.bsky.feed.defs.GeneratorView type AnyNotificationSubject = | app.bsky.notification.listNotifications.Notification - | AppBskyNotificationListNotifications.Notification + | app.bsky.notification.listNotifications.Notification export function moderateProfile( subject: AnyProfileSubject, @@ -120,7 +112,7 @@ export function moderateNotification( export function hasMutedWord(params: { mutedWords: app.bsky.actor.defs.MutedWord[] text: string - facets?: app.bsky.richtext.facet.Main[] | AppBskyRichtextFacet.Main[] + facets?: app.bsky.richtext.facet.Main[] | app.bsky.richtext.facet.Main[] outlineTags?: string[] languages?: string[] actor?: AnyProfileSubject diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts index fc565c3d45..4e4326f84c 100644 --- a/src/lib/moderation/useLabelInfo.ts +++ b/src/lib/moderation/useLabelInfo.ts @@ -1,4 +1,3 @@ -import {type ComAtprotoLabelDefs} from '@atproto/api' import { type InterpretedLabelValueDefinition, interpretLabelValueDefinition, @@ -12,16 +11,16 @@ import { useGlobalLabelStrings, } from '#/lib/moderation/useGlobalLabelStrings' import {useLabelDefinitions} from '#/state/preferences' -import {type app} from '#/lexicons' +import {type app, com} from '#/lexicons' export interface LabelInfo { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label def: InterpretedLabelValueDefinition - strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings + strings: com.atproto.label.defs.LabelValueDefinitionStrings labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined } -export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { +export function useLabelInfo(label: com.atproto.label.defs.Label): LabelInfo { const {i18n} = useLingui() const {labelDefs, labelers} = useLabelDefinitions() const globalLabelStrings = useGlobalLabelStrings() @@ -36,7 +35,7 @@ export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { export function getDefinition( labelDefs: Record, - label: ComAtprotoLabelDefs.Label, + label: com.atproto.label.defs.Label, ): InterpretedLabelValueDefinition { // check local definitions const customDef = @@ -71,13 +70,13 @@ export function getLabelStrings( locale: string, globalLabelStrings: GlobalLabelStrings, def: InterpretedLabelValueDefinition, -): ComAtprotoLabelDefs.LabelValueDefinitionStrings { +): com.atproto.label.defs.LabelValueDefinitionStrings { if (!def.definedBy) { // global definition, look up strings if (def.identifier in globalLabelStrings) { return globalLabelStrings[ def.identifier - ] as ComAtprotoLabelDefs.LabelValueDefinitionStrings + ] as com.atproto.label.defs.LabelValueDefinitionStrings } } else { // try to find locale match in the definition's strings diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index c87ccb5a39..814a9bfe5e 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -1,5 +1,5 @@ -import {type AppBskyGraphDefs, AtUri} from '@atproto/api' - +import {AtUri} from '@atproto/syntax' +import {app} from '#/lexicons' import {isInvalidHandle} from '#/lib/strings/handles' export function makeProfileLink( @@ -44,8 +44,8 @@ export function makeSearchLink(props: {query: string; from?: 'me' | string}) { export function makeStarterPackLink( starterPackOrName: - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView + | app.bsky.graph.defs.StarterPackViewBasic + | app.bsky.graph.defs.StarterPackView | string, rkey?: string, ) { diff --git a/src/lib/strings/__tests__/errors.test.ts b/src/lib/strings/__tests__/errors.test.ts index 1d9ccd8a3d..c5ef2d30b9 100644 --- a/src/lib/strings/__tests__/errors.test.ts +++ b/src/lib/strings/__tests__/errors.test.ts @@ -1,4 +1,3 @@ -import {XRPCError} from '@atproto/api' import {LexError, XrpcResponseError} from '@atproto/lex' import {beforeAll, describe, expect, it} from '@jest/globals' import {i18n} from '@lingui/core' @@ -72,20 +71,10 @@ describe('cleanError', () => { ) }) - it('matches the upstream-failure branch on a legacy XRPC error', () => { - const e = new XRPCError(502) - expect(cleanError(e)).toBe( - 'The server appears to be experiencing issues. Please try again in a few moments.', - ) - }) - - it('matches NotEnoughResources on both error shapes', () => { + it('matches NotEnoughResources', () => { expect(cleanError(xrpcStatusError(503))).toBe( 'The server appears to be experiencing issues. Please try again in a few moments.', ) - expect(cleanError(new XRPCError(503))).toBe( - 'The server appears to be experiencing issues. Please try again in a few moments.', - ) }) it('matches the app-password branch on a lex error message', () => { diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index fb2b0c5b9d..365a069e2d 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -1,7 +1,8 @@ -import {XRPCError} from '@atproto/api' import {LexError} from '@atproto/lex' import {t} from '@lingui/core/macro' +import {isXrpcError} from '#/lib/xrpc-error' + /** * The text to show the user when no special case applies. * @@ -39,7 +40,7 @@ export function cleanError(e: unknown): string { return t`Unable to connect. Please check your internet connection and try again.` } /* - * `@atproto/api` names these with spaces ("Upstream Failure"); lexicon error + * The legacy client named these with spaces ("Upstream Failure"); lexicon error * codes are space-free ("UpstreamFailure"). Match both while the app throws * both shapes. */ @@ -97,9 +98,20 @@ export function isNetworkError(e: unknown) { return false } +/** + * The PDS answers an app-password-scope rejection with the lexicon code + * `InvalidToken` and a message of 'Bad token scope' or 'Bad token method' + * (pipethrough), so the typed path matches the code AND the message. The + * pre-migration check compared against 'TokenInvalid', which the PDS never + * sends - the string fallback was doing all the work. + */ export function isErrorMaybeAppPasswordPermissions(e: unknown) { - if (e instanceof XRPCError && e.error === 'TokenInvalid') { - return true + if (isXrpcError(e)) { + return ( + e.error === 'InvalidToken' && + (e.message.includes('Bad token scope') || + e.message.includes('Bad token method')) + ) } const str = String(e) return str.includes('Bad token scope') || str.includes('Bad token method') @@ -124,5 +136,5 @@ export function isRetryableHttpStatus(status: number) { } export function shouldRetryError(e: unknown) { - return e instanceof XRPCError && isRetryableHttpStatus(e.status) + return isXrpcError(e) && e.shouldRetry() } diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts index 475b000336..6918c9f661 100644 --- a/src/lib/strings/starter-pack.ts +++ b/src/lib/strings/starter-pack.ts @@ -1,5 +1,4 @@ -import {AtUri} from '@atproto/api' - +import {AtUri} from '@atproto/syntax' import type * as bsky from '#/types/bsky' export function createStarterPackLinkFromAndroidReferrer( diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index 4fb8034a51..e6d8cdffc4 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -1,7 +1,7 @@ -import {AtUri} from '@atproto/api' import {parse} from 'psl' import TLDs from 'tlds' +import {AtUri} from '@atproto/syntax' import {BSKY_SERVICE} from '#/lib/constants' import {isInvalidHandle} from '#/lib/strings/handles' import {startUriToStarterPackUri} from '#/lib/strings/starter-pack' diff --git a/src/lib/xrpc-error.ts b/src/lib/xrpc-error.ts index 855aaf803e..ab4af7a6b1 100644 --- a/src/lib/xrpc-error.ts +++ b/src/lib/xrpc-error.ts @@ -4,11 +4,29 @@ import { type Main, type Procedure, type Query, + XrpcError, XrpcResponseError, } from '@atproto/lex' import {com} from '#/lexicons' +/** + * True for an XRPC error from a lex `Client` (`XrpcError` is the abstract base + * of `XrpcResponseError`/`XrpcInvalidResponseError`/`XrpcInternalError`). + */ +export function isXrpcError(e: unknown): e is XrpcError { + return e instanceof XrpcError +} + +/** + * HTTP status, or undefined when `e` is not an XRPC error carrying a response. + * Only `XrpcResponseError` (a genuine server response) has a status; the + * internal/fetch lex errors do not. + */ +export function getErrorStatus(e: unknown): number | undefined { + return e instanceof XrpcResponseError ? e.status : undefined +} + /** * Same nsid means `e` was thrown for this method schema, so `e` can be * treated as an `XrpcResponseError` - which is what lets the SDK's