finish phase 3: type-only codemod and zero non-bridge @atproto/api imports

Task 8: codemod repointed 150 namespace-type files to #/lexicons
(AppBskyFeedDefs.PostView -> app.bsky.feed.defs.PostView, .Record -> .Main)
plus ~105 hand-fixed consumers (composer/video state, DebugMod, Profile,
onboarding, dialogs). DM_SERVICE_HEADERS deleted; last chat call sites on
the chat client. RichText.tsx flipped to SDK-only. Remaining @atproto/api
importers: the three session bridge files, the deliberate dual-world
matcher in lib/xrpc-error.ts, and test fixtures. toLex boundary casts are
TODO(phase4)-tagged for bridge removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-17 02:08:48 +03:00
parent 300a50b69a
commit e7d349bc30
283 changed files with 2029 additions and 1806 deletions
+28 -21
View File
@@ -1,9 +1,5 @@
import {useEffect, useMemo, useState} from 'react'
import {
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
type AppBskyFeedDefs,
} from '@atproto/api'
import {type AtUriString} from '@atproto/syntax'
import {type QueryClient} from '@tanstack/react-query'
import {EventEmitter} from 'eventemitter3'
@@ -15,6 +11,8 @@ import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/qu
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts-v2'
import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/state/queries/usePostThread/queryCache'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {castAsShadow, type Shadow} from './types'
export type {Shadow} from './types'
@@ -22,7 +20,10 @@ export interface PostShadow {
likeUri: string | undefined
repostUri: string | undefined
isDeleted: boolean
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
embed:
| app.bsky.embed.record.View
| app.bsky.embed.recordWithMedia.View
| undefined
pinned: boolean
optimisticReplyCount: number | undefined
bookmarked: boolean | undefined
@@ -32,7 +33,7 @@ export const POST_TOMBSTONE = Symbol('PostTombstone')
const emitter = new EventEmitter()
const shadows: WeakMap<
AppBskyFeedDefs.PostView,
app.bsky.feed.defs.PostView,
Partial<PostShadow>
> = new WeakMap()
@@ -40,13 +41,13 @@ const shadows: WeakMap<
* Use with caution! This function returns the raw shadow data for a post.
* Prefer using `usePostShadow`.
*/
export function dangerousGetPostShadow(post: AppBskyFeedDefs.PostView) {
export function dangerousGetPostShadow(post: app.bsky.feed.defs.PostView) {
return shadows.get(post)
}
export function usePostShadow(
post: AppBskyFeedDefs.PostView,
): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
post: app.bsky.feed.defs.PostView,
): Shadow<app.bsky.feed.defs.PostView> | typeof POST_TOMBSTONE {
const [shadow, setShadow] = useState(() => shadows.get(post))
const [prevPost, setPrevPost] = useState(post)
if (post !== prevPost) {
@@ -74,9 +75,9 @@ export function usePostShadow(
}
function mergeShadow(
post: AppBskyFeedDefs.PostView,
post: app.bsky.feed.defs.PostView,
shadow: Partial<PostShadow>,
): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
): Shadow<app.bsky.feed.defs.PostView> | typeof POST_TOMBSTONE {
if (shadow.isDeleted) {
return POST_TOMBSTONE
}
@@ -125,16 +126,16 @@ function mergeShadow(
let embed: typeof post.embed
if ('embed' in shadow) {
if (
(AppBskyEmbedRecord.isView(post.embed) &&
AppBskyEmbedRecord.isView(shadow.embed)) ||
(AppBskyEmbedRecordWithMedia.isView(post.embed) &&
AppBskyEmbedRecordWithMedia.isView(shadow.embed))
(bsky.isType(app.bsky.embed.record.view, post.embed) &&
bsky.isType(app.bsky.embed.record.view, shadow.embed)) ||
(bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed) &&
bsky.isType(app.bsky.embed.recordWithMedia.view, shadow.embed))
) {
embed = shadow.embed
embed = shadow.embed as typeof post.embed
}
}
return castAsShadow({
return castAsShadow<app.bsky.feed.defs.PostView>({
...post,
embed: embed || post.embed,
likeCount: likeCount,
@@ -143,8 +144,14 @@ function mergeShadow(
bookmarkCount: bookmarkCount,
viewer: {
...(post.viewer || {}),
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
like:
'likeUri' in shadow
? (shadow.likeUri as AtUriString | undefined)
: post.viewer?.like,
repost:
'repostUri' in shadow
? (shadow.repostUri as AtUriString | undefined)
: post.viewer?.repost,
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
bookmarked:
'bookmarked' in shadow ? shadow.bookmarked : post.viewer?.bookmarked,
@@ -169,7 +176,7 @@ export function updatePostShadow(
function* findPostsInCache(
queryClient: QueryClient,
uri: string,
): Generator<AppBskyFeedDefs.PostView, void> {
): Generator<app.bsky.feed.defs.PostView, void> {
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
yield post
}
+6 -4
View File
@@ -1,5 +1,4 @@
import {useEffect, useMemo, useState} from 'react'
import {type AppBskyActorDefs, type AppBskyNotificationDefs} from '@atproto/api'
import {type QueryClient} from '@tanstack/react-query'
import {EventEmitter} from 'eventemitter3'
@@ -33,6 +32,7 @@ import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForDiscover
import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForExploreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery'
import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForSeeMoreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'
import {findAllProfilesInQueryData as findAllProfilesInPostThreadV2QueryData} from '#/state/queries/usePostThread/queryCache'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {castAsShadow, type Shadow} from './types'
@@ -42,9 +42,11 @@ export interface ProfileShadow {
followingUri: string | undefined
muted: boolean | undefined
blockingUri: string | undefined
verification: AppBskyActorDefs.VerificationState
status: AppBskyActorDefs.StatusView | undefined
activitySubscription: AppBskyNotificationDefs.ActivitySubscription | undefined
verification: app.bsky.actor.defs.VerificationState
status: app.bsky.actor.defs.StatusView | undefined
activitySubscription:
| app.bsky.notification.defs.ActivitySubscription
| undefined
}
const shadows: WeakMap<
+13 -12
View File
@@ -7,7 +7,7 @@ import {
useRef,
} from 'react'
import {AppState, type AppStateStatus} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type AtUriString} from '@atproto/syntax'
import throttle from 'lodash.throttle'
import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants'
@@ -22,12 +22,13 @@ import {
} from '#/state/queries/post-feed'
import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {useAgent} from './session'
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
AppBskyFeedDefs.Interaction['event']
app.bsky.feed.defs.Interaction['event']
>([
// These are explicit actions and are therefore fine to send.
'app.bsky.feed.defs#requestLess',
@@ -45,7 +46,7 @@ export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
export type StateContext = {
enabled: boolean
onItemSeen: (item: any) => void
sendInteraction: (interaction: AppBskyFeedDefs.Interaction) => void
sendInteraction: (interaction: app.bsky.feed.defs.Interaction) => void
feedDescriptor: FeedDescriptor | undefined
feedSourceInfo: FeedSourceInfo | undefined
}
@@ -53,7 +54,7 @@ export type StateContext = {
const stateContext = createContext<StateContext>({
enabled: false,
onItemSeen: (_item: any) => {},
sendInteraction: (_interaction: AppBskyFeedDefs.Interaction) => {},
sendInteraction: (_interaction: app.bsky.feed.defs.Interaction) => {},
feedDescriptor: undefined,
feedSourceInfo: undefined,
})
@@ -82,7 +83,7 @@ export function useFeedFeedback(
const history = useRef<
// Use a WeakSet so that we don't need to clear it.
// This assumes that referential identity of slice items maps 1:1 to feed (re)fetches.
WeakSet<FeedPostSliceItem | AppBskyFeedDefs.Interaction>
WeakSet<FeedPostSliceItem | app.bsky.feed.defs.Interaction>
>(new WeakSet())
const flushEvents = useCallback(
@@ -206,7 +207,7 @@ export function useFeedFeedback(
history.current.add(postItem)
queue.current.add(
toString({
item: postItem.uri,
item: postItem.uri as AtUriString,
event: 'app.bsky.feed.defs#interactionSeen',
feedContext,
reqId,
@@ -220,7 +221,7 @@ export function useFeedFeedback(
)
const sendInteraction = useCallback(
(interaction: AppBskyFeedDefs.Interaction) => {
(interaction: app.bsky.feed.defs.Interaction) => {
if (!enabled) {
return
}
@@ -268,7 +269,7 @@ export function isDiscoverFeed(feed?: FeedDescriptor) {
function isInteractionAllowed(
enabled: boolean,
feed: FeedSourceFeedInfo | undefined,
interaction: AppBskyFeedDefs.Interaction['event'],
interaction: app.bsky.feed.defs.Interaction['event'],
) {
if (!enabled || !feed) {
return false
@@ -277,15 +278,15 @@ function isInteractionAllowed(
return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction)
}
function toString(interaction: AppBskyFeedDefs.Interaction): string {
function toString(interaction: app.bsky.feed.defs.Interaction): string {
return `${interaction.item}|${interaction.event}|${
interaction.feedContext || ''
}|${interaction.reqId || ''}`
}
function toInteraction(str: string): AppBskyFeedDefs.Interaction {
function toInteraction(str: string): app.bsky.feed.defs.Interaction {
const [item, event, feedContext, reqId] = str.split('|')
return {item, event, feedContext, reqId}
return {item: item as AtUriString, event, feedContext, reqId}
}
type AggregatedStats = {
@@ -304,7 +305,7 @@ function createAggregatedStats(): AggregatedStats {
function sendOrAggregateInteractionsForStats(
stats: AggregatedStats,
interactions: AppBskyFeedDefs.Interaction[],
interactions: app.bsky.feed.defs.Interaction[],
) {
for (let interaction of interactions) {
switch (interaction.event) {
+1 -1
View File
@@ -43,7 +43,7 @@ import {
parseConvoView,
} from '#/components/dms/util'
import {IS_NATIVE} from '#/env'
import {app, chat} from '#/lexicons'
import {type app, chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
const logger = Logger.create(Logger.Context.ConversationAgent)
+1 -1
View File
@@ -3,7 +3,7 @@ import {type Client} from '@atproto/lex-client'
import {type MessagesEventBus} from '#/state/messages/events/agent'
import {type ConvoWithDetails} from '#/components/dms/util'
import {app, type chat} from '#/lexicons'
import {type app, type chat} from '#/lexicons'
export type ConvoParams = {
convoId: string
+3 -5
View File
@@ -1,14 +1,12 @@
import {createContext, useContext} from 'react'
import {
type AppBskyLabelerDefs,
type InterpretedLabelValueDefinition,
} from '@atproto/api'
import {type InterpretedLabelValueDefinition} from '@bsky.app/sdk/moderation'
import {type app} from '#/lexicons'
import {useLabelDefinitionsQuery} from '../queries/preferences'
interface StateContext {
labelDefs: Record<string, InterpretedLabelValueDefinition[]>
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
labelers: app.bsky.labeler.defs.LabelerViewDetailed[]
}
const stateContext = createContext<StateContext>({
+1 -8
View File
@@ -55,14 +55,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
hiddenPosts: (hiddenPosts ||
[]) as ModerationOpts['prefs']['hiddenPosts'],
},
/*
* TODO(phase4): drop this cast once `#/state/preferences/label-defs`
* flips its `InterpretedLabelValueDefinition` source from `@atproto/api`
* to `@bsky.app/sdk/moderation`. The value is already produced by the
* SDK's `interpretLabelValueDefinitions` (see `../queries/preferences`);
* only the intermediate context type is still old-world.
*/
labelDefs: labelDefs,
labelDefs,
}
}, [override, userDid, labelDefs, moderationPrefs, hiddenPosts])
+53 -40
View File
@@ -1,17 +1,28 @@
import {useCallback} from 'react'
import {
type $Typed,
AtpAgent,
ChatBskyGroupDefs,
type ChatBskyGroupGetJoinLinkPreviews,
} from '@atproto/api'
import {type $Typed} from '@atproto/lex'
import {Client} from '@atproto/lex-client'
import {toDatetimeString} from '@atproto/syntax'
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
import {CHAT_SERVICE, DM_SERVICE_HEADERS} from '#/lib/constants'
import {CHAT_SERVICE} from '#/lib/constants'
import {logger} from '#/logger'
import {STALE} from '#/state/queries/index'
import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util'
import {type SessionAgent, useAgent} from '#/state/session'
import {useMaybeChatClient} from '#/state/session'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
/**
* Unauthenticated client pointed directly at the chat service, for the
* logged-out join-link preview path (mirrors the old public `AtpAgent` at
* `CHAT_SERVICE`). Chat requires no `atproto-proxy` here since we hit the
* service directly.
*/
let publicChatClient: Client | undefined
function getPublicChatClient(): Client {
publicChatClient ??= new Client({service: CHAT_SERVICE})
return publicChatClient
}
/**
* The three preview shapes we currently support. Excludes the `{$type: string}`
@@ -19,15 +30,17 @@ import {type SessionAgent, useAgent} from '#/state/session'
* `ChatInvitePreview` for that.
*/
export type KnownChatInvitePreview =
| $Typed<ChatBskyGroupDefs.JoinLinkPreviewView>
| $Typed<ChatBskyGroupDefs.DisabledJoinLinkPreviewView>
| $Typed<ChatBskyGroupDefs.InvalidJoinLinkPreviewView>
| $Typed<chat.bsky.group.defs.JoinLinkPreviewView>
| $Typed<chat.bsky.group.defs.DisabledJoinLinkPreviewView>
| $Typed<chat.bsky.group.defs.InvalidJoinLinkPreviewView>
/**
* The full open-union shape, including the `{$type: string}` fallback for
* future variants.
* The full open-union shape, including the open-union fallback for future
* variants. Sourced from the endpoint's output element type so it matches the
* lex `Unknown$TypedObject` fallback exactly.
*/
export type ChatInvitePreview = KnownChatInvitePreview | {$type: string}
export type ChatInvitePreview =
chat.bsky.group.getJoinLinkPreviews.$OutputBody['joinLinkPreviews'][number]
/**
* Narrows a preview to one of the three known variants, filtering out the
@@ -37,9 +50,9 @@ export function isKnownJoinLinkPreview(
preview: unknown,
): preview is KnownChatInvitePreview {
return (
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) ||
ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(preview) ||
ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(preview)
bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) ||
bsky.isType(chat.bsky.group.defs.disabledJoinLinkPreviewView, preview) ||
bsky.isType(chat.bsky.group.defs.invalidJoinLinkPreviewView, preview)
)
}
@@ -88,7 +101,7 @@ export function setJoinLinkPreviewRequestedForCode(
code: string,
requested: boolean,
) {
queryClient.setQueriesData<ChatBskyGroupGetJoinLinkPreviews.OutputSchema>(
queryClient.setQueriesData<chat.bsky.group.getJoinLinkPreviews.$OutputBody>(
{
predicate: query => {
const [root, args] = query.queryKey as Partial<
@@ -107,14 +120,16 @@ export function setJoinLinkPreviewRequestedForCode(
...old,
joinLinkPreviews: old.joinLinkPreviews.map(preview => {
if (
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) &&
bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) &&
preview.code === code
) {
return {
...preview,
viewer: {
...preview.viewer,
requestedAt: requested ? new Date().toISOString() : undefined,
requestedAt: requested
? toDatetimeString(new Date())
: undefined,
},
}
}
@@ -141,12 +156,12 @@ export function invalidateJoinLinkPreviewsForConvo(
const [root] = query.queryKey
if (root !== joinLinkPreviewQueryKeyRoot) return false
const data = query.state.data as
| ChatBskyGroupGetJoinLinkPreviews.OutputSchema
| chat.bsky.group.getJoinLinkPreviews.$OutputBody
| undefined
return (
data?.joinLinkPreviews.some(
preview =>
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) &&
bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) &&
preview.convoId === convoId,
) ?? false
)
@@ -155,22 +170,20 @@ export function invalidateJoinLinkPreviewsForConvo(
}
async function fetchJoinLinkPreviews({
agent,
chatClient,
codes,
hasSession,
}: {
agent: SessionAgent
/**
* Authed chat client (proxied to the chat service via `atproto-proxy`), or
* null when logged out - the logged-out path uses the public chat client.
*/
chatClient: Client | null
codes: string[]
hasSession: boolean
}) {
const previewAgent = new AtpAgent({service: CHAT_SERVICE})
const res = hasSession
? await agent.chat.bsky.group.getJoinLinkPreviews(
{codes},
{headers: DM_SERVICE_HEADERS},
)
: await previewAgent.chat.bsky.group.getJoinLinkPreviews({codes})
return res.data
const client = hasSession && chatClient ? chatClient : getPublicChatClient()
return await client.call(chat.bsky.group.getJoinLinkPreviews, {codes})
}
export function useJoinLinkPreviewsQuery({
@@ -186,16 +199,16 @@ export function useJoinLinkPreviewsQuery({
* Seed the query with an already-known preview (e.g. a DM message embed
* already carries the resolved view), avoiding a duplicate fetch.
*/
initialData?: ChatBskyGroupGetJoinLinkPreviews.OutputSchema
initialData?: chat.bsky.group.getJoinLinkPreviews.$OutputBody
}) {
const agent = useAgent()
const chatClient = useMaybeChatClient()
return useQuery({
queryKey: createJoinLinkPreviewQueryKey({codes: codes ?? [], hasSession}),
queryFn: async () => {
if (!codes) throw new Error('No invite code')
try {
return await fetchJoinLinkPreviews({agent, codes, hasSession})
return await fetchJoinLinkPreviews({chatClient, codes, hasSession})
} catch (error) {
logger.error('Failed to fetch join link preview', {safeMessage: error})
throw error
@@ -208,13 +221,13 @@ export function useJoinLinkPreviewsQuery({
}
export function usePrefetchJoinLinkPreviews() {
const agent = useAgent()
const chatClient = useMaybeChatClient()
const queryClient = useQueryClient()
return ({codes, hasSession}: {codes: string[]; hasSession: boolean}) => {
return queryClient.prefetchQuery({
queryKey: createJoinLinkPreviewQueryKey({codes, hasSession}),
queryFn: () => fetchJoinLinkPreviews({agent, codes, hasSession}),
queryFn: () => fetchJoinLinkPreviews({chatClient, codes, hasSession}),
staleTime: STALE.SECONDS.FIFTEEN,
})
}
@@ -226,7 +239,7 @@ export function usePrefetchJoinLinkPreviews() {
* Returns undefined if the preview can't be resolved.
*/
export function useGetJoinLinkPreview() {
const agent = useAgent()
const chatClient = useMaybeChatClient()
const queryClient = useQueryClient()
return useCallback(
@@ -241,7 +254,7 @@ export function useGetJoinLinkPreview() {
const data = await queryClient.fetchQuery({
queryKey: createJoinLinkPreviewQueryKey({codes: [code], hasSession}),
queryFn: () =>
fetchJoinLinkPreviews({agent, codes: [code], hasSession}),
fetchJoinLinkPreviews({chatClient, codes: [code], hasSession}),
staleTime: STALE.SECONDS.FIFTEEN,
})
const found = data.joinLinkPreviews[0]
@@ -251,6 +264,6 @@ export function useGetJoinLinkPreview() {
return undefined
}
},
[agent, queryClient],
[chatClient, queryClient],
)
}
+11 -8
View File
@@ -1,3 +1,4 @@
import {type AtIdentifierString} from '@atproto/syntax'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {useMutation, useQueryClient} from '@tanstack/react-query'
@@ -5,14 +6,15 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import * as Toast from '#/components/Toast'
import {app, type com} from '#/lexicons'
import {updatePostShadow} from '../cache/post-shadow'
import {useAgent, useSession} from '../session'
import {useAppviewClient, useSession} from '../session'
import {useProfileUpdateMutation} from './profile'
export function usePinnedPostMutation() {
const {_} = useLingui()
const {currentAccount} = useSession()
const agent = useAgent()
const appviewClient = useAppviewClient()
const queryClient = useQueryClient()
const {mutateAsync: profileUpdateMutate} = useProfileUpdateMutation()
@@ -33,8 +35,8 @@ export function usePinnedPostMutation() {
// get the currently pinned post so we can optimistically remove the pin from it
if (!currentAccount) throw new Error('Not signed in')
const {data: profile} = await agent.getProfile({
actor: currentAccount.did,
const profile = await appviewClient.call(app.bsky.actor.getProfile, {
actor: currentAccount.did as AtIdentifierString,
})
prevPinnedPost = profile.pinnedPost?.uri
if (prevPinnedPost && prevPinnedPost !== postUri) {
@@ -45,14 +47,15 @@ export function usePinnedPostMutation() {
profile,
updates: existing => {
existing.pinnedPost = pinCurrentPost
? {uri: postUri, cid: postCid}
? ({
uri: postUri,
cid: postCid,
} as com.atproto.repo.strongRef.Main)
: undefined
return existing
},
checkCommitted: res =>
pinCurrentPost
? res.data.pinnedPost?.uri === postUri
: !res.data.pinnedPost,
pinCurrentPost ? res.pinnedPost?.uri === postUri : !res.pinnedPost,
})
if (pinCurrentPost) {
@@ -3,7 +3,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {preferencesQueryKey} from '#/state/queries/preferences'
import {usePdsClient} from '#/state/session'
import {app} from '#/lexicons'
import {type app} from '#/lexicons'
export function usePostInteractionSettingsMutation({
onError,
+1 -1
View File
@@ -203,5 +203,5 @@ export function getMaybeDetachedQuoteEmbed({
}
export const embeddingRules = {
disableRule: {$type: 'app.bsky.feed.postgate#disableRule'},
disableRule: {$type: 'app.bsky.feed.postgate#disableRule'} as const,
}
@@ -1,5 +1,4 @@
import {useCallback, useMemo, useRef, useState} from 'react'
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
import {useFocusEffect} from '@react-navigation/native'
import debounce from 'lodash.debounce'
@@ -10,10 +9,11 @@ import {
} from '#/state/queries/preferences'
import {type ThreadViewPreferences} from '#/state/queries/preferences/types'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {type Literal} from '#/types/utils'
export type ThreadSortOption = Literal<
AppBskyUnspeccedGetPostThreadV2.QueryParams['sort'],
app.bsky.unspecced.getPostThreadV2.$Params['sort'],
string
>
export type ThreadViewOption = 'linear' | 'tree'
+3 -3
View File
@@ -275,7 +275,7 @@ export function useProfileFollowMutationQueue(
did,
})
userActionHistory.follow([did])
return uri
return uri as AtUriString
} else {
if (prevFollowingUri) {
await unfollowMutation.mutateAsync({
@@ -337,7 +337,7 @@ export function useProfileFollowMutationQueue(
if (finalFollowingUri) {
void client
.call(app.bsky.graph.getSuggestedFollowsByActor, {
actor: did as AtIdentifierString,
actor: did,
})
.then(res => {
const dids = res.suggestions
@@ -516,7 +516,7 @@ export function useProfileBlockMutationQueue(
did,
})
ax.metric('profile:block', {})
return uri
return uri as AtUriString
} else {
if (prevBlockUri) {
await unblockMutation.mutateAsync({
+8 -4
View File
@@ -1,6 +1,7 @@
import {Client} from '@atproto/lex-client'
import {useQuery} from '@tanstack/react-query'
import {Agent} from '../session/agent'
import {com} from '#/lexicons'
const RQKEY_ROOT = 'service'
export const RQKEY = (serviceUrl: string) => [RQKEY_ROOT, serviceUrl]
@@ -9,9 +10,12 @@ export function useServiceQuery(serviceUrl: string) {
return useQuery({
queryKey: RQKEY(serviceUrl),
queryFn: async () => {
const agent = new Agent(null, {service: serviceUrl})
const res = await agent.com.atproto.server.describeServer()
return res.data
/*
* Unauthenticated throwaway client pointed at the candidate service -
* describeServer is a public endpoint on the target PDS/entryway.
*/
const client = new Client({service: serviceUrl})
return await client.call(com.atproto.server.describeServer)
},
enabled: isValidUrl(serviceUrl),
})
@@ -8,6 +8,8 @@ import {getContentLanguages} from '#/state/preferences/languages'
import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export const DEFAULT_LIMIT = 15
@@ -36,11 +38,17 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
},
)
/*
* TODO(phase4): drop toLex once getSuggestedFeeds migrates off the bridge
* agent (intentionally left on the bridge in Phase 3).
*/
return {
feeds: data.feeds.filter(feed => {
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
return !isSaved
}),
feeds: toLex<app.bsky.feed.defs.GeneratorView[]>(
data.feeds.filter(feed => {
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
return !isSaved
}),
),
}
},
})
@@ -7,6 +7,7 @@ import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export type QueryProps = {
category?: string | null
@@ -55,7 +56,15 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
if (!data.recIdStr) {
logger.debug('getSuggestedOnboardingUsers response missing recIdStr')
}
return {...data, recId: data.recIdStr}
/*
* TODO(phase4): drop toLex once getSuggestedOnboardingUsers migrates off
* the bridge agent (this unspecced endpoint is intentionally left on the
* bridge in Phase 3, so it returns old `@atproto/api` view types).
*/
return toLex<{
actors: app.bsky.actor.defs.ProfileView[]
recId: string | undefined
}>({...data, recId: data.recIdStr})
},
})
}
@@ -10,6 +10,7 @@ import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export type QueryProps = {
limit?: number
@@ -47,7 +48,15 @@ export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
if (!data.recIdStr) {
logger.debug('getSuggestedUsersForDiscover response missing recIdStr')
}
return {...data, recId: data.recIdStr}
/*
* TODO(phase4): drop toLex once getSuggestedUsersForDiscover migrates off
* the bridge agent (this unspecced endpoint is intentionally left on the
* bridge in Phase 3, so it returns old `@atproto/api` view types).
*/
return toLex<{
actors: app.bsky.actor.defs.ProfileView[]
recId: string | undefined
}>({...data, recId: data.recIdStr})
},
})
}
@@ -10,6 +10,7 @@ import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export type QueryProps = {
category?: string | null
@@ -49,7 +50,14 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
if (!data.recIdStr) {
logger.debug('getSuggestedUsersForExplore response missing recIdStr')
}
return {...data, recId: data.recIdStr}
/*
* TODO(phase4): drop toLex once getSuggestedUsersForExplore migrates off
* the bridge agent (intentionally left on the bridge in Phase 3).
*/
return toLex<{
actors: app.bsky.actor.defs.ProfileView[]
recId: string | undefined
}>({...data, recId: data.recIdStr})
},
})
}
@@ -10,6 +10,7 @@ import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export type QueryProps = {
category?: string | null
@@ -55,7 +56,14 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
if (!data.recIdStr) {
logger.debug('getSuggestedUsersForSeeMore response missing recIdStr')
}
return {...data, recId: data.recIdStr}
/*
* TODO(phase4): drop toLex once this unspecced endpoint migrates off the
* bridge agent (intentionally left on the bridge in Phase 3).
*/
return toLex<{
actors: app.bsky.actor.defs.ProfileView[]
recId: string | undefined
}>({...data, recId: data.recIdStr})
},
})
}
@@ -8,6 +8,8 @@ import {getContentLanguages} from '#/state/preferences/languages'
import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export const createOnboardingSuggestedStarterPacksQueryKey = (
interests?: string[],
@@ -43,7 +45,14 @@ export function useOnboardingSuggestedStarterPacksQuery({
},
},
)
return data
/*
* TODO(phase4): drop toLex once getOnboardingSuggestedStarterPacks
* migrates off the bridge agent (intentionally left on the bridge in
* Phase 3).
*/
return toLex<app.bsky.unspecced.getOnboardingSuggestedStarterPacks.$OutputBody>(
data,
)
},
})
}
@@ -8,6 +8,8 @@ import {getContentLanguages} from '#/state/preferences/languages'
import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [
'suggested-starter-packs',
@@ -43,7 +45,13 @@ export function useSuggestedStarterPacksQuery({
},
},
)
return data
/*
* TODO(phase4): drop toLex once getSuggestedStarterPacks migrates off the
* bridge agent (intentionally left on the bridge in Phase 3).
*/
return toLex<app.bsky.unspecced.getSuggestedStarterPacks.$OutputBody>(
data,
)
},
})
}
@@ -1,5 +1,4 @@
import {useCallback} from 'react'
import {type AtIdentifierString} from '@atproto/syntax'
import {useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
@@ -21,7 +20,7 @@ export function useUpdateProfileVerificationCache() {
async ({profile}: {profile: bsky.profile.AnyProfileView}) => {
try {
const updated = await client.call(app.bsky.actor.getProfile, {
actor: (profile.did ?? '') as AtIdentifierString,
actor: profile.did ?? '',
})
updateProfileShadow(qc, profile.did, {
verification: updated.verification,
@@ -1,6 +1,4 @@
import {type AtIdentifierString, type DidString} from '@atproto/lex-client'
import {type DatetimeString} from '@atproto/lex-schema'
import {type HandleString} from '@atproto/syntax'
import {useMutation} from '@tanstack/react-query'
import {until} from '#/lib/async/until'
@@ -24,9 +22,9 @@ export function useVerificationCreateMutation() {
}
const {uri} = await pdsClient.create(app.bsky.graph.verification, {
subject: profile.did as DidString,
subject: profile.did,
createdAt: new Date().toISOString() as DatetimeString,
handle: profile.handle as HandleString,
handle: profile.handle,
displayName: profile.displayName || '',
})
@@ -44,7 +42,7 @@ export function useVerificationCreateMutation() {
},
() => {
return appviewClient.call(app.bsky.actor.getProfile, {
actor: (profile.did ?? '') as AtIdentifierString,
actor: profile.did ?? '',
})
},
)
@@ -1,4 +1,3 @@
import {type AtIdentifierString} from '@atproto/lex-client'
import {AtUri} from '@atproto/syntax'
import {useMutation} from '@tanstack/react-query'
@@ -51,7 +50,7 @@ export function useVerificationsRemoveMutation() {
},
() => {
return appviewClient.call(app.bsky.actor.getProfile, {
actor: (profile.did ?? '') as AtIdentifierString,
actor: profile.did ?? '',
})
},
)
@@ -1,7 +1,7 @@
import {Agent} from '@atproto/api'
import {Client} from '@atproto/lex-client'
import {device} from '#/storage'
import {BridgeAgent} from './session-core'
export const BR_LABELER = 'did:plc:ekitcvx7uwnauoqy5oest3hm' // Brazil
export const DE_LABELER = 'did:plc:r55ow3tocux5kafs5dq445fy' // Germany
@@ -90,9 +90,9 @@ export function configureAdditionalModerationAuthorities() {
* static - so both emit identical global `atproto-accept-labelers` headers.
*/
const appLabelers = Array.from(
new Set([...Agent.appLabelers, ...additionalLabelers]),
new Set([...BridgeAgent.appLabelers, ...additionalLabelers]),
)
Client.configure({appLabelers: appLabelers as `did:${string}:${string}`[]})
Agent.configure({appLabelers})
BridgeAgent.configure({appLabelers})
}
+1 -1
View File
@@ -8,7 +8,6 @@ import {
useState,
useSyncExternalStore,
} from 'react'
import {type AtpSessionEvent} from '@atproto/api'
import {type Client} from '@atproto/lex-client'
import {PasswordSession} from '@atproto/lex-password-session'
@@ -22,6 +21,7 @@ import {emitSessionDropped} from '../events'
import {getPublicLexClient, getUnauthenticatedClient} from './clients'
import {type Action, getInitialState, reducer, type State} from './reducer'
import {
type AtpSessionEvent,
buildBundle,
createSessionBundleAndCreateAccount,
createSessionBundleAndLogin,
+1 -1
View File
@@ -1,8 +1,8 @@
import {type AtpSessionEvent} from '@atproto/api'
import {type SessionData} from '@atproto/lex-password-session'
import {type Schema} from '../persisted'
import {type Action, type State} from './reducer'
import {type AtpSessionEvent} from './session-core'
import {type SessionAccount} from './types'
type Reducer = (state: State, action: Action) => State
+11 -12
View File
@@ -1,4 +1,3 @@
import {Agent, BSKY_LABELER_DID} from '@atproto/api'
import {Client} from '@atproto/lex-client'
import {api} from '@bsky.app/sdk'
@@ -6,17 +5,17 @@ import {IS_TEST_USER} from '#/lib/constants'
import {com} from '#/lexicons'
import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities'
import {readLabelers} from './agent-config'
import {type SessionBundle} from './session-core'
import {BridgeAgent, type SessionBundle} from './session-core'
import {type SessionAccount} from './types'
/*
* The Bluesky moderation labeler DID. `BSKY_LABELER_DID` (from '@atproto/api')
* and `api.moderation.did` (from '@bsky.app/sdk') are the SAME value -
* `did:plc:ar7c4by46qjdydhdevvrndac` - verified at implementation. We keep
* using `BSKY_LABELER_DID` for the global appLabelers config and the
* per-account filter (matching the old code) and `api.moderation.did` as the
* appview client's base labeler (matching `buildAppviewClient`); both resolve
* to identical `atproto-accept-labelers` headers.
* The Bluesky moderation labeler DID. The old `BSKY_LABELER_DID` (from
* '@atproto/api') and `api.moderation.did` (from '@bsky.app/sdk') are the SAME
* value - `did:plc:ar7c4by46qjdydhdevvrndac` - verified at implementation. We
* use `api.moderation.did` everywhere: the global appLabelers config, the
* per-account filter, and the appview client's base labeler (matching
* `buildAppviewClient`); all resolve to identical `atproto-accept-labelers`
* headers.
*/
/**
@@ -32,7 +31,7 @@ import {type SessionAccount} from './types'
*/
function configureGlobalAppLabelers(dids: string[]) {
Client.configure({appLabelers: dids as `did:${string}:${string}`[]})
Agent.configure({appLabelers: dids})
BridgeAgent.configure({appLabelers: dids})
}
export function configureModerationForGuest() {
@@ -64,7 +63,7 @@ export async function configureModerationForAccount(
// The code below is actually relevant to production (and isn't global).
const labelerDids = await readLabelers(account.did).catch(_ => {})
if (labelerDids) {
const perAccount = labelerDids.filter(did => did !== BSKY_LABELER_DID)
const perAccount = labelerDids.filter(did => did !== api.moderation.did)
/*
* Apply the per-account labelers to both live request paths. The appview
* client re-asserts the Bluesky moderation labeler as its base because
@@ -86,7 +85,7 @@ export async function configureModerationForAccount(
}
function switchToBskyAppLabeler() {
configureGlobalAppLabelers([BSKY_LABELER_DID])
configureGlobalAppLabelers([api.moderation.did])
}
/**
+1 -3
View File
@@ -1,9 +1,7 @@
import {type AtpSessionEvent} from '@atproto/api'
import {unregisterPushToken} from '#/lib/notifications/notifications'
import {logger} from '#/lib/notifications/util'
import {wrapSessionReducerForLogging} from './logging'
import {createPublicSessionBundle} from './session-core'
import {type AtpSessionEvent, createPublicSessionBundle} from './session-core'
import {type SessionAccount} from './types'
import {createTemporaryAgentsAndResume} from './util'
+10
View File
@@ -50,6 +50,16 @@ import {
import {type SessionAccount} from './types'
import {isSessionExpired} from './util'
/*
* Re-exported so session-layer siblings (index.tsx, reducer.ts, logging.ts,
* moderation.ts, additional-moderation-authorities.ts) import the bridge
* vocabulary through this whitelisted bridge module rather than from
* '@atproto/api' directly. `Agent` is re-exported as `BridgeAgent` for the
* labeler-config statics (`Agent.configure`/`Agent.appLabelers`). Dies with
* the bridge in Phase 4.
*/
export {type AtpSessionEvent, Agent as BridgeAgent} from '@atproto/api'
/**
* Whether an access token was issued for a queued (waitlisted) signup rather
* than a full session.
+6 -10
View File
@@ -1,10 +1,5 @@
import {createContext, useContext, useMemo, useState} from 'react'
import {
type AppBskyActorDefs,
type AppBskyFeedDefs,
type AppBskyUnspeccedGetPostThreadV2,
type ModerationDecision,
} from '@atproto/api'
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
@@ -18,21 +13,22 @@ import {
RQKEY_LINK_ROOT,
} from '#/state/queries/resolve-link'
import * as Toast from '#/components/Toast'
import {type app} from '#/lexicons'
export interface ComposerOptsPostRef {
uri: string
cid: string
text: string
langs?: string[]
author: AppBskyActorDefs.ProfileViewBasic
embed?: AppBskyFeedDefs.PostView['embed']
author: app.bsky.actor.defs.ProfileViewBasic
embed?: app.bsky.feed.defs.PostView['embed']
moderation?: ModerationDecision
}
export type OnPostSuccessData =
| {
replyToUri?: string
posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
posts: app.bsky.unspecced.getPostThreadV2.ThreadItem[]
}
| undefined
@@ -48,7 +44,7 @@ export interface ComposerOpts {
replyTo?: ComposerOptsPostRef
onPost?: (postUri: string | undefined) => void
onPostSuccess?: (data: OnPostSuccessData) => void
quote?: AppBskyFeedDefs.PostView
quote?: app.bsky.feed.defs.PostView
mention?: string // handle of user to mention
text?: string
imageUris?: {uri: string; width: number; height: number; altText?: string}[]
+4 -3
View File
@@ -1,5 +1,6 @@
import {createContext, useCallback, useContext, useMemo, useState} from 'react'
import {type AppBskyFeedThreadgate} from '@atproto/api'
import {type app} from '#/lexicons'
type StateContext = {
uris: Set<string>
@@ -74,7 +75,7 @@ export function useThreadgateHiddenReplyUrisAPI() {
export function useMergedThreadgateHiddenReplies({
threadgateRecord,
}: {
threadgateRecord?: AppBskyFeedThreadgate.Record
threadgateRecord?: app.bsky.feed.threadgate.Main
}) {
const {uris, recentlyUnhiddenUris} = useThreadgateHiddenReplyUris()
return useMemo(() => {
@@ -89,7 +90,7 @@ export function useMergedThreadgateHiddenReplies({
export function useMergeThreadgateHiddenReplies() {
const {uris, recentlyUnhiddenUris} = useThreadgateHiddenReplyUris()
return useCallback(
(threadgate?: AppBskyFeedThreadgate.Record) => {
(threadgate?: app.bsky.feed.threadgate.Main) => {
const set = new Set([...(threadgate?.hiddenReplies || []), ...uris])
for (const uri of recentlyUnhiddenUris) {
set.delete(uri)
+3 -2
View File
@@ -1,8 +1,9 @@
import {useEffect, useId, useState} from 'react'
import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {Logger} from '#/logger'
import {type FeedSourceInfo} from '#/state/queries/feed'
import {type app} from '#/lexicons'
/**
* Separate logger for better debugging
@@ -10,7 +11,7 @@ import {type FeedSourceInfo} from '#/state/queries/feed'
const logger = Logger.create(Logger.Context.PostSource)
export type PostSource = {
post: AppBskyFeedDefs.FeedViewPost
post: app.bsky.feed.defs.FeedViewPost
feedSourceInfo?: FeedSourceInfo
}