diff --git a/src/ageAssurance/const.ts b/src/ageAssurance/const.ts index 326552681c..9d20a64ea6 100644 --- a/src/ageAssurance/const.ts +++ b/src/ageAssurance/const.ts @@ -1,8 +1,3 @@ -import { - ageAssuranceRuleIDs as ids, - type AppBskyAgeassuranceDefs, -} from '@atproto/api' - import {AgeAssuranceAccess} from '#/ageAssurance/types' import { ANDROID_API_LEVEL, @@ -11,6 +6,7 @@ import { IS_IOS, IS_WEB, } from '#/env' +import {app} from '#/lexicons' /** * Minimum age required to access the app at all. @@ -44,19 +40,17 @@ export const AGE_ASSURANCE_PLATFORM: 'web' | 'ios' | 'android' = IS_WEB export const DEVICE_SIGNALS_SUPPORTED: boolean = (IS_IOS && IOS_MAJOR_VERSION >= 26) || (IS_ANDROID && ANDROID_API_LEVEL >= 23) -export const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { +export const FALLBACK_REGION_CONFIG: app.bsky.ageassurance.defs.ConfigRegion = { countryCode: '*', regionCode: undefined, minAccessAge: MIN_ACCESS_AGE, rules: [ - { - $type: ids.IfDeclaredOverAge, + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: MIN_ACCESS_AGE, access: AgeAssuranceAccess.Full, - }, - { - $type: ids.Default, + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: AgeAssuranceAccess.None, - }, + }), ], } diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index eabb1bebc7..67f94c63af 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -1,12 +1,6 @@ import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' import * as AgeRange from 'expo-age-range' -import { - type AppBskyAgeassuranceDefs, - type AppBskyAgeassuranceGetConfig, - type AppBskyAgeassuranceGetState, - AtpAgent, - type ChatBskyActorDeclaration, -} from '@atproto/api' +import {Client} from '@atproto/lex-client' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {persistQueryClient} from '@tanstack/react-query-persist-client' @@ -37,7 +31,9 @@ import { } from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {useGeolocation} from '#/geolocation' +import {app, type chat} from '#/lexicons' import {device} from '#/storage' +import {toLex} from '#/types/bsky' /** * Special query client for age assurance data so we can prefetch on app @@ -101,16 +97,15 @@ export function setBirthdateForDid({ export const configQueryKey = ['config'] export async function getConfig() { if (debug.enabled) return debug.resolve(debug.config) - const agent = new AtpAgent({ + const client = new Client({ service: PUBLIC_BSKY_SERVICE, }) - const res = await agent.app.bsky.ageassurance.getConfig() - return res.data + return await client.call(app.bsky.ageassurance.getConfig) } export function getConfigFromCache(): - | AppBskyAgeassuranceGetConfig.OutputSchema + | app.bsky.ageassurance.getConfig.$OutputBody | undefined { - return qc.getQueryData( + return qc.getQueryData( configQueryKey, ) } @@ -131,7 +126,7 @@ export function prefetchConfig() { try { logger.debug(`prefetchAgeAssuranceConfig: resolving...`) const res = await networkRetry(3, () => getConfig()) - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -147,7 +142,7 @@ export function prefetchConfig() { export async function refetchConfig() { logger.debug(`refetchConfig: fetching...`) const res = await getConfig() - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -187,7 +182,11 @@ export function useConfigQuery() { export function createServerStateQueryKey({did}: {did: string}) { return ['serverState', did] } -export async function getServerState({agent}: {agent: SessionAgent}) { +export async function getServerState({ + agent, +}: { + agent: SessionAgent +}): Promise { if (debug.enabled && debug.serverState) return debug.resolve(debug.serverState) const geolocation = device.get(['mergedGeolocation']) @@ -207,14 +206,19 @@ export async function getServerState({agent}: {agent: SessionAgent}) { */ data.metadata.accountCreatedAt = createdAtCache.get(did) } - return data ?? null + /* + * TODO(phase4): the bridge agent still returns the old `@atproto/api` + * getState output; `toLex` reconciles it with the `#/lexicons` shape at this + * single boundary until the agent itself is migrated. + */ + return data ? toLex(data) : null } export function getServerStateFromCache({ did, }: { did: string -}): AppBskyAgeassuranceGetState.OutputSchema | undefined { - return qc.getQueryData( +}): app.bsky.ageassurance.getState.$OutputBody | undefined { + return qc.getQueryData( createServerStateQueryKey({did}), ) } @@ -236,7 +240,7 @@ export async function prefetchServerState({agent}: {agent: SessionAgent}) { logger.debug(`prefetchServerState: resolving...`) const res = await networkRetry(3, () => getServerState({agent})) if (res) { - qc.setQueryData(qk, res) + qc.setQueryData(qk, res) } } catch (err) { const e = err as Error @@ -251,7 +255,7 @@ export async function refetchServerState({agent}: {agent: SessionAgent}) { logger.debug(`refetchServerState: fetching...`) const res = await networkRetry(3, () => getServerState({agent})) if (res) { - qc.setQueryData( + qc.setQueryData( createServerStateQueryKey({did}), res, ) @@ -261,16 +265,16 @@ export async function refetchServerState({agent}: {agent: SessionAgent}) { export function usePatchServerState() { const {currentAccount} = useSession() return useCallback( - (next: AppBskyAgeassuranceDefs.State) => { + (next: app.bsky.ageassurance.defs.State) => { if (!currentAccount) return const did = currentAccount.did const prev = getServerStateFromCache({did}) - const merged: AppBskyAgeassuranceGetState.OutputSchema = { + const merged: app.bsky.ageassurance.getState.$OutputBody = { metadata: {}, ...(prev || {}), state: next, } - qc.setQueryData( + qc.setQueryData( createServerStateQueryKey({did}), merged, ) @@ -336,7 +340,7 @@ export function useServerStateQuery() { export type OtherRequiredData = { birthdate: string | undefined - actorDeclaration?: ChatBskyActorDeclaration.Main + actorDeclaration?: chat.bsky.actor.declaration.Main } export function createOtherRequiredDataQueryKey({did}: {did: string}) { return ['otherRequiredData', did] @@ -411,7 +415,7 @@ export function setOtherRequiredDataActorDeclarationCache({ actorDeclaration, }: { did: string - actorDeclaration: ChatBskyActorDeclaration.Main + actorDeclaration: chat.bsky.actor.declaration.Main }) { const prev = getOtherRequiredDataFromCache({did}) const next: OtherRequiredData = { @@ -551,7 +555,7 @@ export function getDeviceSignalsFromCacheForRegion({ region, }: { did: string - region: AppBskyAgeassuranceDefs.ConfigRegion + region: app.bsky.ageassurance.defs.ConfigRegion }): AgeRange.AgeRangeResponse | undefined { const regionKey = createRegionKey(region) return getDeviceSignalsMapFromCache({did})?.[regionKey] @@ -695,11 +699,11 @@ export type AgeAssuranceServerData = { /** * The raw config from the appview. */ - config: AppBskyAgeassuranceDefs.Config | undefined + config: app.bsky.ageassurance.defs.Config | undefined /** * The raw state from the appview. Must be further processed before being useful. */ - state: AppBskyAgeassuranceDefs.State | undefined + state: app.bsky.ageassurance.defs.State | undefined metadata: AgeAssuranceMetadata | undefined /** * The native on-device age signals for the region the user is currently in, diff --git a/src/ageAssurance/debug.ts b/src/ageAssurance/debug.ts index 74121976ca..d0dde24939 100644 --- a/src/ageAssurance/debug.ts +++ b/src/ageAssurance/debug.ts @@ -1,13 +1,10 @@ import type * as AgeRange from 'expo-age-range' -import { - ageAssuranceRuleIDs as ids, - type AppBskyAgeassuranceDefs, - type AppBskyAgeassuranceGetState, -} from '@atproto/api' +import {toDatetimeString} from '@atproto/syntax' import {type OtherRequiredData} from '#/ageAssurance/data' import {IS_DEV, IS_E2E} from '#/env' import {type Geolocation} from '#/geolocation' +import {type app} from '#/lexicons' export const enabled = (IS_DEV && false) || IS_E2E @@ -31,21 +28,22 @@ export const otherRequiredData: OtherRequiredData = { } const serverStateEnabled = false || IS_E2E -export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined = - serverStateEnabled - ? { - state: { - lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(), - status: 'unknown', - access: 'unknown', - }, - metadata: { - accountCreatedAt: new Date(2023, 1, 1).toISOString(), - }, - } - : undefined +export const serverState: + | app.bsky.ageassurance.getState.$OutputBody + | undefined = serverStateEnabled + ? { + state: { + lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(), + status: 'unknown', + access: 'unknown', + }, + metadata: { + accountCreatedAt: toDatetimeString(new Date(2023, 1, 1)), + }, + } + : undefined -export const config: AppBskyAgeassuranceDefs.Config = { +export const config: app.bsky.ageassurance.defs.Config = { regions: [ { countryCode: 'AA', @@ -53,7 +51,7 @@ export const config: AppBskyAgeassuranceDefs.Config = { minAccessAge: 13, rules: [ { - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', access: 'full', }, ], @@ -71,11 +69,11 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -86,16 +84,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -106,26 +104,27 @@ export const config: AppBskyAgeassuranceDefs.Config = { { date: '2025-12-10T00:00:00Z', access: 'none', - $type: ids.IfAccountNewerThan, + $type: + 'app.bsky.ageassurance.defs#configRegionRuleIfAccountNewerThan', }, { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 16, access: 'safe', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 16, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -137,16 +136,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -158,16 +157,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -179,16 +178,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -200,11 +199,11 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -216,16 +215,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 16, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 16, access: 'full', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -237,16 +236,16 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 18, access: 'full', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, @@ -257,21 +256,21 @@ export const config: AppBskyAgeassuranceDefs.Config = { { age: 18, access: 'full', - $type: ids.IfAssuredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge', }, { age: 18, access: 'full', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, + $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge', }, { access: 'none', - $type: ids.Default, + $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault', }, ], }, diff --git a/src/ageAssurance/state.ts b/src/ageAssurance/state.ts index 2a0b76446b..8ede9d13fa 100644 --- a/src/ageAssurance/state.ts +++ b/src/ageAssurance/state.ts @@ -1,9 +1,6 @@ import {useEffect, useMemo, useState} from 'react' import type * as AgeRange from 'expo-age-range' -import { - type AppBskyAgeassuranceDefs, - computeAgeAssuranceRegionAccess, -} from '@atproto/api' +import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {getAge} from '#/lib/strings/time' import {useSession} from '#/state/session' @@ -30,6 +27,7 @@ import { getAgeAssuranceRegionConfigWithFallback, } from '#/ageAssurance/util' import {type Geolocation, useGeolocation} from '#/geolocation' +import {type app} from '#/lexicons' import {device} from '#/storage' /** @@ -47,8 +45,8 @@ function computeAgeAssuranceState({ }: { hasSession: boolean geolocation: Geolocation - config?: AppBskyAgeassuranceDefs.Config - state?: AppBskyAgeassuranceDefs.State + config?: app.bsky.ageassurance.defs.Config + state?: app.bsky.ageassurance.defs.State metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }) { diff --git a/src/ageAssurance/types.ts b/src/ageAssurance/types.ts index 5dcabeb268..79fd5f1820 100644 --- a/src/ageAssurance/types.ts +++ b/src/ageAssurance/types.ts @@ -1,5 +1,5 @@ import type * as AgeRange from 'expo-age-range' -import {type computeAgeAssuranceRegionAccess} from '@atproto/api' +import {type computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {logger} from '#/ageAssurance/logger' diff --git a/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts b/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts index 5ba9e1ba6d..8f88398ce6 100644 --- a/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts +++ b/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {computeAgeAssuranceRegionAccess} from '@atproto/api' +import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data' import {logger} from '#/ageAssurance/logger' diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index 2bceab4d16..95ebe2f01b 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -1,11 +1,10 @@ import {useMemo} from 'react' import type * as AgeRange from 'expo-age-range' +import {type ModerationPrefs} from '@bsky.app/sdk/moderation' import { - AppBskyAgeassuranceDefs, computeAgeAssuranceRegionAccess, getAgeAssuranceRegionConfig, - type ModerationPrefs, -} from '@atproto/api' +} from '@bsky.app/sdk/utils' import {getAge} from '#/lib/strings/time' import {regionName} from '#/locale/helpers' @@ -25,6 +24,7 @@ import { } from '#/ageAssurance/types' import {type Geolocation, useGeolocation} from '#/geolocation' import {USRegionNameToRegionCode} from '#/geolocation/util' +import {app} from '#/lexicons' /** * Resolves a geolocation to its matched age assurance region config, or @@ -41,9 +41,9 @@ import {USRegionNameToRegionCode} from '#/geolocation/util' * risk desyncing the write and read keys and silently losing grants. */ export function getAgeAssuranceRegionConfigForGeolocation( - config: AppBskyAgeassuranceDefs.Config, + config: app.bsky.ageassurance.defs.Config, geolocation: Geolocation, -): AppBskyAgeassuranceDefs.ConfigRegion | undefined { +): app.bsky.ageassurance.defs.ConfigRegion | undefined { return getAgeAssuranceRegionConfig(config, { countryCode: geolocation.countryCode ?? '', regionCode: geolocation.regionCode, @@ -59,9 +59,9 @@ export function getAgeAssuranceRegionConfigForGeolocation( * which can return undefined if the geolocation does not match any AA region. */ export function getAgeAssuranceRegionConfigWithFallback( - config: AppBskyAgeassuranceDefs.Config, + config: app.bsky.ageassurance.defs.Config, geolocation: Geolocation, -): AppBskyAgeassuranceDefs.ConfigRegion { +): app.bsky.ageassurance.defs.ConfigRegion { return ( getAgeAssuranceRegionConfigForGeolocation(config, geolocation) || FALLBACK_REGION_CONFIG @@ -74,9 +74,9 @@ export function getAgeAssuranceRegionConfigWithFallback( * historical KWS-only behavior). */ export function getRegionAdditionalVerificationMethods( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, ): NonNullable< - AppBskyAgeassuranceDefs.ConfigRegion['additionalVerificationMethods'] + app.bsky.ageassurance.defs.ConfigRegion['additionalVerificationMethods'] > { return region.additionalVerificationMethods ?? [] } @@ -86,7 +86,7 @@ export function getRegionAdditionalVerificationMethods( * age APIs (Apple Declared Age Range / Google Play Age Signals). */ export function regionAllowsDeviceVerification( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, ): boolean { return getRegionAdditionalVerificationMethods(region).includes('device') } @@ -122,7 +122,7 @@ export function createRegionKey(region: { * usable data. */ export function getAgeAssuranceDataFromDeviceSignals( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, deviceSignals: AgeRange.AgeRangeResponse | undefined, ): { assuredAge?: number @@ -183,7 +183,7 @@ export function canBirthdateUpdateIncreaseAccess({ metadata, deviceSignals, }: { - region: AppBskyAgeassuranceDefs.ConfigRegion + region: app.bsky.ageassurance.defs.ConfigRegion metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }): boolean { @@ -212,8 +212,12 @@ export function canBirthdateUpdateIncreaseAccess({ const thresholds = new Set([region.minAccessAge]) for (const rule of region.rules) { if ( - AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule) || - AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule) + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.$isTypeOf( + rule, + ) || + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredUnderAge.$isTypeOf( + rule, + ) ) { thresholds.add(rule.age) } @@ -303,7 +307,7 @@ export function computeAgeAssuranceFlags({ deviceSignals, }: { state: AgeAssuranceState - regionConfig: AppBskyAgeassuranceDefs.ConfigRegion + regionConfig: app.bsky.ageassurance.defs.ConfigRegion metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }): AgeAssuranceFlags { diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index a65eeb0722..156ba951bb 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -1,6 +1,5 @@ import {Fragment, useCallback} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -19,6 +18,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/ import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' export function AccountList({ onSelectAccount, @@ -107,7 +107,7 @@ function AccountItem({ isCurrentAccount, isPendingAccount, }: { - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed account: SessionAccount onSelect: (account: SessionAccount) => void isCurrentAccount: boolean diff --git a/src/components/Autocomplete/useAutocomplete/index.ts b/src/components/Autocomplete/useAutocomplete/index.ts index 211c202fd8..ad14ae54bc 100644 --- a/src/components/Autocomplete/useAutocomplete/index.ts +++ b/src/components/Autocomplete/useAutocomplete/index.ts @@ -62,7 +62,9 @@ export function useAutocomplete({ key: profile.did, type: 'profile' as const, value: '@' + profile.handle, - profile, + // TODO(phase4): drop toLex once searchActorsTypeahead (bridge agent) + // emits #/lexicons views + profile: toLex(profile), })) } else if (type === 'emoji') { return emojiSearch(q, limit || 8) diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx index b5e6f867b6..eb19e83351 100644 --- a/src/components/BotBadge.tsx +++ b/src/components/BotBadge.tsx @@ -1,5 +1,4 @@ import {type Insets, View} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {atoms as a, useTheme} from '#/alf' @@ -8,11 +7,12 @@ import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {Bot_Filled as RobotIcon} from '#/components/icons/Bot' import {useAnalytics} from '#/analytics' +import {type com} from '#/lexicons' import type * as bsky from '#/types/bsky' export function isBotAccount(profile: { did: string - labels?: ComAtprotoLabelDefs.Label[] + labels?: com.atproto.label.defs.Label[] }): boolean { return ( profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index c204bb1cb2..b8e60ff286 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -32,11 +31,12 @@ import {RichText, type RichTextProps} from '#/components/RichText' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useActiveLiveEventFeedUris} from '#/features/liveEvents/context' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from './icons/Trash' type Props = { - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView onPress?: () => void } @@ -255,7 +255,7 @@ export function SaveButton({ pin, ...props }: { - view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView + view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView pin?: boolean text?: boolean } & Partial) { @@ -270,7 +270,7 @@ function SaveButtonInner({ text = true, ...buttonProps }: { - view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView + view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView pin?: boolean text?: boolean } & Partial) { @@ -380,7 +380,7 @@ function SaveButtonInner({ export function createProfileFeedHref({ feed, }: { - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView }) { const urip = new AtUri(feed.uri) const handleOrDid = feed.creator.handle || feed.creator.did diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index fec0447b61..aa77a7b36b 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -7,7 +7,6 @@ import Animated, { LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' -import {type AppBskyFeedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -40,6 +39,7 @@ import {ProgressGuideList} from '#/components/ProgressGuide/List' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' @@ -569,7 +569,7 @@ export function SuggestedFeeds() { const {gtMobile} = useBreakpoints() const feeds = useMemo(() => { - const items: AppBskyFeedDefs.GeneratorView[] = [] + const items: app.bsky.feed.defs.GeneratorView[] = [] if (!data) return items diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index 03ea92b0b2..96e01eb767 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -1,6 +1,5 @@ import {useRef} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -10,6 +9,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Link, type LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {toLex} from '#/types/bsky' @@ -24,7 +24,7 @@ const AVI_BORDER = 1 * `count` includes blocked users and `followers` does not. */ export function shouldShowKnownFollowers( - knownFollowers?: AppBskyActorDefs.KnownFollowers, + knownFollowers?: app.bsky.actor.defs.KnownFollowers, ) { return knownFollowers && knownFollowers.followers.length > 0 } @@ -42,7 +42,9 @@ export function KnownFollowers({ minimal?: boolean showIfEmpty?: boolean }) { - const cache = useRef>(new Map()) + const cache = useRef>( + new Map(), + ) /* * Results for `knownFollowers` are not sorted consistently, so when @@ -83,7 +85,7 @@ function KnownFollowersInner({ }: { profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts - cachedKnownFollowers: AppBskyActorDefs.KnownFollowers + cachedKnownFollowers: app.bsky.actor.defs.KnownFollowers onLinkPress?: LinkProps['onPress'] minimal?: boolean showIfEmpty?: boolean diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 10d8c97333..dc40181735 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyLabelerDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -13,10 +12,11 @@ import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {Link as InternalLink, type LinkProps} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' type LabelingServiceProps = { - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed } export function Outer({ @@ -187,7 +187,7 @@ export function Loader({ loading?: React.ComponentType<{}> error?: React.ComponentType<{error: string}> component: React.ComponentType<{ - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed }> }) { const {isLoading, data, error} = useLabelerInfoQuery({did}) diff --git a/src/components/LikedByList.tsx b/src/components/LikedByList.tsx index fcaa50bbcc..28718f3d88 100644 --- a/src/components/LikedByList.tsx +++ b/src/components/LikedByList.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,8 +10,15 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -function renderItem({item, index}: {item: GetLikes.Like; index: number}) { +function renderItem({ + item, + index, +}: { + item: app.bsky.feed.getLikes.Like + index: number +}) { return ( peekable?: boolean }) { @@ -59,9 +55,9 @@ export function Embed({ const tiles: React.ReactNode[] = [] for (const item of e.view.items) { if (tiles.length >= 4) break - if (!AppBskyEmbedGallery.isViewImage(item)) continue + if (!bsky.isType(app.bsky.embed.gallery.viewImage, item)) continue if (peekable) { - const image: AppBskyEmbedImages.ViewImage = { + const image: app.bsky.embed.images.ViewImage = { thumb: item.thumbnail, fullsize: item.fullsize, alt: item.alt, @@ -107,7 +103,12 @@ export function Embed({ // ignore any unknowns e.media.view !== null ) { - return + return ( + + ) } return null @@ -202,7 +203,7 @@ export function VideoItem({ ) } -function PeekableImageItem({image}: {image: AppBskyEmbedImages.ViewImage}) { +function PeekableImageItem({image}: {image: app.bsky.embed.images.ViewImage}) { const {t: l} = useLingui() const saveImage = useSaveImageToMediaLibrary() diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 1ae53839ec..5b346d051a 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {moderateProfile} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -20,13 +19,14 @@ import {Newskie} from '#/components/icons/Newskie' import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' export function NewskieDialog({ profile, disabled, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed disabled?: boolean }) { const t = useTheme() @@ -76,7 +76,7 @@ function DialogInner({ createdAt, now, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed createdAt: string now: number }) { diff --git a/src/components/Post/Embed/ChatInviteEmbed.tsx b/src/components/Post/Embed/ChatInviteEmbed.tsx index d5f69f49e9..f6a9a0e4d9 100644 --- a/src/components/Post/Embed/ChatInviteEmbed.tsx +++ b/src/components/Post/Embed/ChatInviteEmbed.tsx @@ -1,10 +1,10 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyEmbedExternal} from '@atproto/api' import {atoms as a} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' import {ExternalEmbed} from '#/components/Post/Embed/ExternalEmbed' import {JoinRequestEmbedBody} from '#/components/Post/Embed/JoinRequestEmbed' +import {type app} from '#/lexicons' /** * Renders a chat invite link found in an `app.bsky.embed.external` embed (e.g. @@ -18,7 +18,7 @@ export function ChatInviteEmbed({ style, }: { code: string - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp }) { @@ -34,7 +34,7 @@ function ChatInviteEmbedBody({ onOpen, style, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp }) { diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx index 529952f08e..9334581c5b 100644 --- a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx +++ b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx @@ -5,7 +5,6 @@ import { Pressable, } from 'react-native' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,12 +16,13 @@ import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' import {Fill} from '#/components/Fill' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' +import {type app} from '#/lexicons' export function ExternalGif({ link, params, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx index 9decdea95b..63c4236aa1 100644 --- a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx +++ b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx @@ -15,7 +15,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {WebView} from 'react-native-webview' import {scheduleOnRN} from 'react-native-worklets' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -34,6 +33,7 @@ import {Fill} from '#/components/Fill' import {KeepAwake} from '#/components/KeepAwake' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' interface ShouldStartLoadRequest { url: string @@ -122,7 +122,7 @@ export function ExternalPlayer({ link, params, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() diff --git a/src/components/Post/Embed/ExternalEmbed/index.tsx b/src/components/Post/Embed/ExternalEmbed/index.tsx index f920359477..79fa0c2c29 100644 --- a/src/components/Post/Embed/ExternalEmbed/index.tsx +++ b/src/components/Post/Embed/ExternalEmbed/index.tsx @@ -1,7 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -20,6 +19,7 @@ import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {ExternalGif} from './ExternalGif' import {ExternalPlayer} from './ExternalPlayer' import {GifEmbed} from './Gif' @@ -30,7 +30,7 @@ export const ExternalEmbed = ({ style, hideAlt, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp hideAlt?: boolean diff --git a/src/components/Post/Embed/ImageEmbed.tsx b/src/components/Post/Embed/ImageEmbed.tsx index 073c47fbb1..cc3ce6c443 100644 --- a/src/components/Post/Embed/ImageEmbed.tsx +++ b/src/components/Post/Embed/ImageEmbed.tsx @@ -2,7 +2,6 @@ import {useRef} from 'react' import {InteractionManager, View} from 'react-native' import {type AnimatedRef} from 'react-native-reanimated' import {Image} from 'expo-image' -import {AppBskyEmbedGallery, type AppBskyEmbedImages} from '@atproto/api' import {atoms as a, tokens} from '#/alf' import {AutoSizedImage} from '#/components/images/AutoSizedImage' @@ -16,6 +15,8 @@ import {type Dimensions} from '#/components/Lightbox/types' import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {type EmbedType} from '#/types/bsky/post' import {type CommonProps} from './types' @@ -29,14 +30,16 @@ export function ImageEmbed({ }) { const ax = useAnalytics() const {openLightbox} = useLightboxControls() - const images: AppBskyEmbedImages.ViewImage[] = + const images: app.bsky.embed.images.ViewImage[] = embed.type === 'gallery' - ? embed.view.items.filter(AppBskyEmbedGallery.isViewImage).map(item => ({ - thumb: item.thumbnail, - fullsize: item.fullsize, - alt: item.alt, - aspectRatio: item.aspectRatio, - })) + ? embed.view.items + .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item)) + .map(item => ({ + thumb: item.thumbnail, + fullsize: item.fullsize, + alt: item.alt, + aspectRatio: item.aspectRatio, + })) : embed.view.images const useExpandedLayout = embed.type === 'gallery' diff --git a/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx index 00c15c4401..ff08bdd2c0 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx +++ b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx @@ -1,6 +1,5 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {Context, useAlf, utils} from '#/alf' +import {type app} from '#/lexicons' /** * Overrides only the values needed for `secondary_inverted` buttons atm. @@ -12,7 +11,7 @@ export function StandardSiteThemeProvider({ view, children, }: { - view: AppBskyEmbedExternal.ViewExternal + view: app.bsky.embed.external.ViewExternal children: React.ReactNode }) { const alf = useAlf() diff --git a/src/components/Post/Embed/StandardSiteEmbed/publishers.ts b/src/components/Post/Embed/StandardSiteEmbed/publishers.ts index aba3e28cc2..d85cea820b 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/publishers.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/publishers.ts @@ -1,8 +1,7 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {Leaflet} from '#/components/icons/community/Leaflet' import {Offprint} from '#/components/icons/community/Offprint' import {Pckt} from '#/components/icons/community/Pckt' +import {type app} from '#/lexicons' export type StandardSitePublisher = { host: string @@ -25,7 +24,7 @@ function hostFromUri(uri: string | undefined): string | null { } export function getStandardSitePublisherHost( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ): string | null { return hostFromUri(view.source?.uri) } @@ -40,7 +39,7 @@ function matchByHost(host: string | null): StandardSitePublisher | null { } export function matchStandardSitePublisher( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ): StandardSitePublisher | null { return matchByHost(getStandardSitePublisherHost(view)) } diff --git a/src/components/Post/Embed/StandardSiteEmbed/types.ts b/src/components/Post/Embed/StandardSiteEmbed/types.ts index 208f01b7a6..129559b994 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/types.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/types.ts @@ -1,7 +1,7 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' +import {type app} from '#/lexicons' export type CommonProps = { - view: AppBskyEmbedExternal.ViewExternal + view: app.bsky.embed.external.ViewExternal } export type PreviewProps = { diff --git a/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts b/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts index 5f5dd23d60..cd31849516 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts @@ -1,10 +1,9 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - +import {type app} from '#/lexicons' import {isStandardSiteEmbed, isStandardSitePublicationEmbed} from './utils' function makeView( partial: Record, -): AppBskyEmbedExternal.ViewExternal { +): app.bsky.embed.external.ViewExternal { return { uri: 'https://example.com/post', title: 'title', diff --git a/src/components/Post/Embed/StandardSiteEmbed/utils.ts b/src/components/Post/Embed/StandardSiteEmbed/utils.ts index d969fa077e..e43ee0fd99 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/utils.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/utils.ts @@ -1,29 +1,31 @@ -import { - type AppBskyEmbedExternal, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' import {AtUri} from '@atproto/syntax' -export function isStandardSiteDocumentUri(ref: ComAtprotoRepoStrongRef.Main) { +import {type app, type com} from '#/lexicons' + +export function isStandardSiteDocumentUri( + ref: com.atproto.repo.strongRef.Main, +) { return new AtUri(ref.uri).collection.startsWith('site.standard.document') } export function isStandardSitePublicationUri( - ref: ComAtprotoRepoStrongRef.Main, + ref: com.atproto.repo.strongRef.Main, ) { return new AtUri(ref.uri).collection.startsWith('site.standard.publication') } -export function isStandardSiteUri(ref: ComAtprotoRepoStrongRef.Main) { +export function isStandardSiteUri(ref: com.atproto.repo.strongRef.Main) { return new AtUri(ref.uri).collection.startsWith('site.standard.') } -export function isStandardSiteEmbed(view: AppBskyEmbedExternal.ViewExternal) { +export function isStandardSiteEmbed( + view: app.bsky.embed.external.ViewExternal, +) { return view.associatedRefs?.some(ref => isStandardSiteUri(ref)) } export function isStandardSitePublicationEmbed( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ) { return ( view.associatedRefs?.some( diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx index bead58fd91..61b0965eea 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,6 +1,5 @@ import {useImperativeHandle, useRef, useState} from 'react' import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyEmbedVideo} from '@atproto/api' import {BlueskyVideoView} from '@bsky.app/video' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,6 +16,7 @@ import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/compone import {KeepAwake} from '#/components/KeepAwake' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' +import {type app} from '#/lexicons' import {GifPresentationControls} from '../GifPresentationControls' import {TimeIndicator} from './TimeIndicator' @@ -29,7 +29,7 @@ export function VideoEmbedInnerNative({ onError, }: { ref: React.Ref<{togglePlayback: () => void}> - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View setStatus: (status: 'playing' | 'paused') => void setIsLoading: (isLoading: boolean) => void setIsActive: (isActive: boolean) => void diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts index d7c44b0d91..df539ab74a 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts @@ -1,7 +1,7 @@ -import {type AppBskyEmbedVideo} from '@atproto/api' +import {type app} from '#/lexicons' export type VideoEmbedInnerWebProps = { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View active: boolean setActive: () => void onScreen: boolean diff --git a/src/components/Post/Embed/VideoEmbed/index.tsx b/src/components/Post/Embed/VideoEmbed/index.tsx index d78aad4985..dc394b8a6e 100644 --- a/src/components/Post/Embed/VideoEmbed/index.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.tsx @@ -1,7 +1,6 @@ import {useCallback, useEffect, useRef, useState} from 'react' import {ActivityIndicator, View} from 'react-native' import {ImageBackground} from 'expo-image' -import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,12 +16,13 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {ConstrainedImage} from '#/components/images/AutoSizedImage' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {GifPresentationControls} from './GifPresentationControls' import {VideoEmbedInnerNative} from './VideoEmbedInner/VideoEmbedInnerNative' import * as VideoFallback from './VideoEmbedInner/VideoFallback' interface Props { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View } export function VideoEmbed({embed}: Props) { diff --git a/src/components/Post/Embed/VideoEmbed/index.web.tsx b/src/components/Post/Embed/VideoEmbed/index.web.tsx index ee44ba33e8..3ef275330a 100644 --- a/src/components/Post/Embed/VideoEmbed/index.web.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.web.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {View} from 'react-native' -import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -25,6 +24,7 @@ import { } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' import {useAnalytics} from '#/analytics' import {IS_WEB_FIREFOX} from '#/env' +import {type app} from '#/lexicons' import {useActiveVideoWeb} from './ActiveVideoWebContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' @@ -37,7 +37,7 @@ const noop = () => {} */ const MIN_CARD_WIDTH = 280 -export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { +export function VideoEmbed({embed}: {embed: app.bsky.embed.video.View}) { const t = useTheme() const ref = useRef(null) const { @@ -284,7 +284,7 @@ function VideoError({ error, retry, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View error: unknown retry: () => void }) { diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx index 1bf56e5197..33e9bf2d9b 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {AtUri} from '@atproto/syntax' import {moderatePost} from '@bsky.app/sdk/moderation' @@ -259,7 +258,7 @@ export function QuoteEmbed({ linkDisabled?: boolean }) { const moderationOpts = useModerationOpts() - const quote = useMemo<$Typed>( + const quote = useMemo<$Typed>( () => ({ ...embed.view, $type: 'app.bsky.feed.defs#postView', diff --git a/src/components/Post/Embed/types.ts b/src/components/Post/Embed/types.ts index 41031fdf05..eb478347fd 100644 --- a/src/components/Post/Embed/types.ts +++ b/src/components/Post/Embed/types.ts @@ -1,7 +1,8 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' +import {type app} from '#/lexicons' + export enum PostEmbedViewContext { ThreadHighlighted = 'ThreadHighlighted', Feed = 'Feed', @@ -20,10 +21,10 @@ export type CommonProps = { * events (post:photoEmbed:*). When the embed has no owning post (e.g. * composer previews), leave this undefined and no events will be emitted. */ - post?: AppBskyFeedDefs.PostView + post?: app.bsky.feed.defs.PostView feedDescriptor?: string } export type EmbedProps = CommonProps & { - embed?: AppBskyFeedDefs.PostView['embed'] + embed?: app.bsky.feed.defs.PostView['embed'] } diff --git a/src/components/Post/Translated/index.tsx b/src/components/Post/Translated/index.tsx index 6321a0d135..849222df03 100644 --- a/src/components/Post/Translated/index.tsx +++ b/src/components/Post/Translated/index.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {Platform, type StyleProp, type TextStyle, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyFeedPost} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {HITSLOP_30} from '#/lib/constants' @@ -39,7 +38,7 @@ export function TranslatedPost({ postTextStyle = a.text_md, }: { hideTranslateLink?: boolean - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView postTextStyle?: StyleProp }) { const langPrefs = useLanguagePrefs() @@ -47,7 +46,7 @@ export function TranslatedPost({ key: post.uri, }) - const record = useMemo(() => { + const record = useMemo(() => { return bsky.isType(app.bsky.feed.post, post.record) ? post.record : undefined diff --git a/src/components/PostControls/BookmarkButton.tsx b/src/components/PostControls/BookmarkButton.tsx index b2ed043365..8d43947823 100644 --- a/src/components/PostControls/BookmarkButton.tsx +++ b/src/components/PostControls/BookmarkButton.tsx @@ -1,6 +1,5 @@ import {memo} from 'react' import {type Insets} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,6 +14,7 @@ import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import * as toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from './PostControlButton' export const BookmarkButton = memo(function BookmarkButton({ @@ -23,7 +23,7 @@ export const BookmarkButton = memo(function BookmarkButton({ logContext, hitSlop, }: { - post: Shadow + post: Shadow big?: boolean logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' hitSlop?: Insets diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index 108b220361..9995f2741d 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -6,11 +6,6 @@ import { type ViewStyle, } from 'react-native' import * as Clipboard from 'expo-clipboard' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' @@ -97,6 +92,7 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_INTERNAL} from '#/env' +import {type app} from '#/lexicons' let PostMenuItems = ({ post, @@ -110,17 +106,17 @@ let PostMenuItems = ({ forceGoogleTranslate, }: { testID: string - post: Shadow + post: Shadow postFeedContext: string | undefined postReqId: string | undefined - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] size?: 'lg' | 'md' | 'sm' timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' forceGoogleTranslate: boolean }): React.ReactNode => { diff --git a/src/components/PostControls/PostMenu/index.tsx b/src/components/PostControls/PostMenu/index.tsx index c0826683b6..1c7e412065 100644 --- a/src/components/PostControls/PostMenu/index.tsx +++ b/src/components/PostControls/PostMenu/index.tsx @@ -1,10 +1,5 @@ import {memo, useMemo, useState} from 'react' import {type Insets} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' @@ -13,6 +8,7 @@ import {EventStopper} from '#/view/com/util/EventStopper' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import * as Menu from '#/components/Menu' import {useMenuControl} from '#/components/Menu' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {PostMenuItems} from './PostMenuItems' @@ -32,15 +28,15 @@ let PostMenuButton = ({ forceGoogleTranslate, }: { testID: string - post: Shadow + post: Shadow postFeedContext: string | undefined postReqId: string | undefined big?: boolean - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void hitSlop?: Insets logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' forceGoogleTranslate: boolean diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index 50afb298a1..5c2828a01c 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -1,5 +1,4 @@ import {ScrollView, View} from 'react-native' -import {type ChatBskyActorDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -22,6 +21,7 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type chat} from '#/lexicons' import {toLex} from '#/types/bsky' export function RecentChats({ @@ -114,7 +114,7 @@ function RecentChatItem({ onPress: () => void moderationOpts: ModerationOpts convo: ConvoWithDetails - primaryMember: ChatBskyActorDefs.ProfileViewBasic + primaryMember: chat.bsky.actor.defs.ProfileViewBasic }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx index ef93865513..c1ca0f3728 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx @@ -1,22 +1,18 @@ import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type Shadow} from '#/state/cache/post-shadow' +import {type app} from '#/lexicons' export interface ShareMenuItemsProps { testID: string - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] size?: 'lg' | 'md' | 'sm' timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main onShare: () => void } diff --git a/src/components/PostControls/ShareMenu/index.tsx b/src/components/PostControls/ShareMenu/index.tsx index 5c354770c9..e47a13bc95 100644 --- a/src/components/PostControls/ShareMenu/index.tsx +++ b/src/components/PostControls/ShareMenu/index.tsx @@ -1,10 +1,5 @@ import {memo, useMemo, useState} from 'react' import {type Insets} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' @@ -21,6 +16,7 @@ import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/ import * as Menu from '#/components/Menu' import {useMenuControl} from '#/components/Menu' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {ShareMenuItems} from './ShareMenuItems' @@ -37,12 +33,12 @@ let ShareMenuButton = ({ logContext, }: { testID: string - post: Shadow + post: Shadow big?: boolean - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main onShare: () => void hitSlop?: Insets logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index 88f30b0561..66dd50cb84 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -1,10 +1,5 @@ import {memo, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -29,6 +24,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util' import * as Skele from '#/components/Skeleton' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {BookmarkButton} from './BookmarkButton' import { PostControlButton, @@ -57,8 +53,8 @@ let PostControls = ({ forceGoogleTranslate = false, }: { big?: boolean - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI feedContext?: string | undefined reqId?: string | undefined @@ -66,8 +62,8 @@ let PostControls = ({ onPressReply: () => void onPostReply?: (postUri: string | undefined) => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void viaRepost?: {uri: string; cid: string} variant?: 'compact' | 'normal' | 'large' forceGoogleTranslate?: boolean diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 0fcd9fc113..6d60774f78 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -1,6 +1,5 @@ import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom' import {msg, plural} from '@lingui/core/macro' @@ -39,6 +38,7 @@ import {Text} from '#/components/Typography' import {IS_WEB_TOUCH_DEVICE} from '#/env' import {useActorStatus} from '#/features/liveNow' import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import {type ProfileHoverCardProps} from './types' @@ -416,7 +416,7 @@ function Inner({ moderationOpts, hide, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed moderationOpts: ModerationOpts hide: () => void }) { diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index 3ff4569964..f5df4a0645 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -207,7 +207,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { type: 'profile', // Don't share identity across tabs or typing attempts key: resultsKey + ':' + profile.did, - profile, + profile: profile as bsky.profile.AnyProfileView, }) } } diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index befd28178b..f2b920dc20 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -1,7 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, type TextStyle} from 'react-native' -import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' -import {RichText as SdkRichText} from '@bsky.app/sdk/richtext' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {toShortUrl} from '#/lib/strings/url-helpers' import {atoms as a, flatten, type TextStyleProp} from '#/alf' @@ -10,6 +9,8 @@ import {InlineLinkText, type LinkProps} from '#/components/Link' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichTextTag} from '#/components/RichTextTag' import {Text, type TextProps} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const WORD_WRAP = {wordWrap: 1} // lifted from facet detection in `RichText` impl, _without_ `gm` flags @@ -18,16 +19,7 @@ const URL_REGEX = export type RichTextProps = TextStyleProp & Pick & { - /* - * TODO(phase4): drop the `SdkRichText` arm and normalization below, keeping - * only the SDK RichText. Interim dual-world acceptance: the migrated - * `useRichText` hook now produces an `@bsky.app/sdk/richtext` RichText, - * while ~100 call sites still pass the old `@atproto/api` RichText produced - * elsewhere. We accept both and normalize an SDK instance into the old - * RichText below (its `.facets` flow new->old without a cast) so the render - * body stays single-typed until the RichText UI callers migrate (Task 7). - */ - value: RichTextAPI | SdkRichText | string + value: RichTextAPI | string testID?: string numberOfLines?: number disableLinks?: boolean @@ -69,13 +61,6 @@ export function RichText({ const richText = useMemo(() => { if (value instanceof RichTextAPI) { return value - } else if (value instanceof SdkRichText) { - /* - * Normalize the SDK RichText into the old one this component renders - * against. `.facets` are structurally identical modulo branded strings - * (new->old assigns), so they carry over without a cast. - */ - return new RichTextAPI({text: value.text, facets: value.facets}) } else { const rt = new RichTextAPI({text: value}) rt.detectFacetsWithoutResolution() @@ -134,7 +119,7 @@ export function RichText({ if ( mention && (disableMentionFacetValidation || - AppBskyRichtextFacet.validateMention(mention).success) && + bsky.matches(app.bsky.richtext.facet.mention, mention)) && !disableLinks ) { els.push( @@ -151,7 +136,7 @@ export function RichText({ , ) - } else if (link && AppBskyRichtextFacet.validateLink(link).success) { + } else if (link && bsky.matches(app.bsky.richtext.facet.link, link)) { const isValidLink = URL_REGEX.test(link.uri) if (!isValidLink || disableLinks) { els.push(toShortUrl(segment.text)) @@ -176,7 +161,7 @@ export function RichText({ !disableLinks && enableTags && tag && - AppBskyRichtextFacet.validateTag(tag).success + bsky.matches(app.bsky.richtext.facet.tag, tag) ) { els.push( ( const renderItem = ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( + InfiniteData > moderationOpts: ModerationOpts headerHeight: number @@ -84,7 +84,7 @@ export const ProfilesList = forwardRef( const renderItem = ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( void @@ -57,7 +57,7 @@ interface ProfileFeedgensProps { emptyStateIcon?: React.ComponentType | React.ReactElement } -function keyExtractor(item: AppBskyGraphDefs.StarterPackViewBasic) { +function keyExtractor(item: app.bsky.graph.defs.StarterPackViewBasic) { return item.uri } @@ -147,7 +147,7 @@ export function ProfileStarterPacks({ ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( }) { diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index f5e16835b6..483224f9bc 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native' import type ViewShot from 'react-native-view-shot' import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import * as Sharing from 'expo-sharing' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -29,7 +28,7 @@ export function QrCodeDialog({ link, control, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView link?: string control: DialogControlProps }) { diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx index 0dd640d3c0..17c794fc1d 100644 --- a/src/components/StarterPack/ShareDialog.tsx +++ b/src/components/StarterPack/ShareDialog.tsx @@ -1,6 +1,5 @@ import {View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -19,9 +18,10 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' +import {type app} from '#/lexicons' interface Props { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView link?: string imageLoaded?: boolean qrDialogControl: DialogControlProps diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index 498300491d..04b1ae3df0 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -1,7 +1,6 @@ import {useRef} from 'react' import {type ListRenderItemInfo} from 'react-native' import {View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -22,9 +21,10 @@ import { } from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' function keyExtractor( - item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, + item: app.bsky.actor.defs.ProfileViewBasic | app.bsky.feed.defs.GeneratorView, index: number, ) { return `${item.did}-${index}` @@ -41,7 +41,7 @@ export function WizardEditListDialog({ state: WizardState dispatch: (action: WizardAction) => void moderationOpts: ModerationOpts - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index 0d145af1d9..7ade0e79f9 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -1,5 +1,4 @@ import {Keyboard, View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' import { moderateFeedGenerator, moderateProfile, @@ -25,6 +24,7 @@ import * as Toggle from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {toLex} from '#/types/bsky' @@ -41,8 +41,8 @@ function WizardListCard({ }: { type: 'user' | 'algo' btnType: 'checkbox' | 'remove' - profile?: AppBskyActorDefs.ProfileViewBasic - feed?: AppBskyFeedDefs.GeneratorView + profile?: app.bsky.actor.defs.ProfileViewBasic + feed?: app.bsky.feed.defs.GeneratorView displayName: string subtitle: string onPress: () => void @@ -186,7 +186,7 @@ export function WizardFeedCard({ moderationOpts, }: { btnType: 'checkbox' | 'remove' - generator: AppBskyFeedDefs.GeneratorView + generator: app.bsky.feed.defs.GeneratorView state: WizardState dispatch: (action: WizardAction) => void moderationOpts: ModerationOpts diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx index 7a2a5ed02f..ee487e0520 100644 --- a/src/components/VideoPostCard.tsx +++ b/src/components/VideoPostCard.tsx @@ -2,11 +2,6 @@ import {useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' -import { - type AppBskyActorDefs, - AppBskyEmbedVideo, - type AppBskyFeedDefs, -} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' @@ -42,7 +37,7 @@ export function VideoPostCard({ moderation, onInteract, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView sourceContext: VideoFeedSourceContext moderation: ModerationDecision /** @@ -75,7 +70,7 @@ export function VideoPostCard({ * Filtering should be done at a higher level, such as `PostFeed` or * `PostFeedVideoGridRow`, but we need to protect here as well. */ - if (!AppBskyEmbedVideo.isView(embed)) return null + if (!bsky.isType(app.bsky.embed.video.view, embed)) return null const author = post.author const text = bsky.isType(app.bsky.feed.post, post.record) @@ -272,7 +267,7 @@ export function VideoPostCardPlaceholder() { export function VideoPostCardTextPlaceholder({ author, }: { - author?: AppBskyActorDefs.ProfileViewBasic + author?: app.bsky.actor.defs.ProfileViewBasic }) { const t = useTheme() @@ -352,7 +347,7 @@ export function CompactVideoPostCard({ moderation, onInteract, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView sourceContext: VideoFeedSourceContext moderation: ModerationDecision /** @@ -383,7 +378,7 @@ export function CompactVideoPostCard({ * Filtering should be done at a higher level, such as `PostFeed` or * `PostFeedVideoGridRow`, but we need to protect here as well. */ - if (!AppBskyEmbedVideo.isView(embed)) return null + if (!bsky.isType(app.bsky.embed.video.view, embed)) return null const likeCount = post?.likeCount ?? 0 const showLikeCount = false diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx index f4353a92d1..2202ed8e33 100644 --- a/src/components/WhoCanReply.tsx +++ b/src/components/WhoCanReply.tsx @@ -6,7 +6,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -38,7 +37,7 @@ import {app} from '#/lexicons' import * as bsky from '#/types/bsky' interface WhoCanReplyProps { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView isThreadAuthor: boolean style?: StyleProp } @@ -203,7 +202,7 @@ function WhoCanReplyDialog({ embeddingDisabled, }: { control: Dialog.DialogControlProps - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView settings: ThreadgateAllowUISetting[] embeddingDisabled: boolean }) { @@ -249,7 +248,7 @@ function Rules({ settings, embeddingDisabled, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView settings: ThreadgateAllowUISetting[] embeddingDisabled: boolean }) { @@ -307,8 +306,8 @@ function Rule({ lists, }: { rule: ThreadgateAllowUISetting - post: AppBskyFeedDefs.PostView - lists: AppBskyGraphDefs.ListViewBasic[] | undefined + post: app.bsky.feed.defs.PostView + lists: app.bsky.graph.defs.ListViewBasic[] | undefined }) { if (rule.type === 'mention') { return mentioned users diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index 78c4866d4b..aba2c338ee 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -1,10 +1,6 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyNotificationDefs, - type AppBskyNotificationListActivitySubscriptions, - type Un$Typed, -} from '@atproto/api' +import {type Un$Typed} from '@atproto/lex' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -37,6 +33,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function SubscribeProfileDialog({ @@ -120,7 +117,7 @@ function DialogInner({ error, } = useMutation({ mutationFn: async ( - activitySubscription: Un$Typed, + activitySubscription: Un$Typed, ) => { await agent.app.bsky.notification.putActivitySubscription({ subject: profile.did, @@ -148,7 +145,7 @@ function DialogInner({ queryClient.setQueryData( RQKEY_getActivitySubscriptions, ( - old?: InfiniteData, + old?: InfiniteData, ) => { if (!old) return old return { @@ -308,8 +305,8 @@ function DialogInner({ } function parseActivitySubscription( - sub?: AppBskyNotificationDefs.ActivitySubscription, -): Un$Typed { + sub?: app.bsky.notification.defs.ActivitySubscription, +): Un$Typed { if (!sub) return {post: false, reply: false} const {post, reply} = sub return {post, reply} diff --git a/src/components/contacts/contacts.ts b/src/components/contacts/contacts.ts index 205c2ccb49..e30736a8ea 100644 --- a/src/components/contacts/contacts.ts +++ b/src/components/contacts/contacts.ts @@ -1,6 +1,5 @@ -import {type AppBskyContactDefs} from '@atproto/api' - import {type CountryCode} from '#/lib/international-telephone-codes' +import {type app} from '#/lexicons' import {normalizePhoneNumber} from './phone-number' import {type Contact, type Match} from './state' @@ -70,7 +69,7 @@ export function normalizeContactBook( export function filterMatchedNumbers( contacts: Contact[], - results: AppBskyContactDefs.MatchAndContactIndex[], + results: app.bsky.contact.defs.MatchAndContactIndex[], mapping: Map, ) { const filteredIds = new Set() @@ -87,7 +86,7 @@ export function filterMatchedNumbers( export function getMatchedContacts( contacts: Contact[], - results: AppBskyContactDefs.MatchAndContactIndex[], + results: app.bsky.contact.defs.MatchAndContactIndex[], mapping: Map, ): Array { const contactsById = new Map(contacts.map(c => [c.id, c])) diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index 92e9535beb..a755382bf6 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -2,11 +2,10 @@ import {useContext} from 'react' import {Alert, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as Contacts from 'expo-contacts' -import { - type AppBskyActorProfile, - AppBskyContactImportContacts, - type Un$Typed, -} from '@atproto/api' +import {type Un$Typed} from '@atproto/lex' +import {type Client} from '@atproto/lex-client' +import {toDatetimeString} from '@atproto/syntax' +import {upsertProfile} from '@bsky.app/sdk' import {msg, t} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,9 +13,10 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {uploadBlob} from '#/lib/api' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' -import {type SessionAgent, useAgent} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' import { Context as OnboardingContext, type OnboardingAction, @@ -29,6 +29,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import { contactsWithPhoneNumbersOnly, filterMatchedNumbers, @@ -53,7 +54,8 @@ export function GetContacts({ }) { const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const insets = useSafeAreaInsets() const gutters = useGutters([0, 'wide']) const queryClient = useQueryClient() @@ -71,7 +73,7 @@ export function GetContacts({ */ if (context === 'Onboarding' && maybeOnboardingContext) { try { - await createProfileRecord(agent, maybeOnboardingContext) + await createProfileRecord(pdsClient, maybeOnboardingContext) } catch (error) { logger.debug('Error creating profile record:', {safeMessage: error}) } @@ -84,13 +86,13 @@ export function GetContacts({ ) if (phoneNumbers.length > 0) { - const res = await agent.app.bsky.contact.importContacts({ + const res = await appviewClient.call(app.bsky.contact.importContacts, { token: state.token, contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT), }) return { - matches: res.data.matchesAndContactIndexes, + matches: res.matchesAndContactIndexes, indexToContactId, } } else { @@ -147,18 +149,14 @@ export function GetContacts({ ), {type: 'error'}, ) - } else if ( - err instanceof AppBskyContactImportContacts.TooManyContactsError - ) { + } else if (getErrorName(err) === 'TooManyContacts') { Toast.show( _( msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, ), {type: 'error'}, ) - } else if ( - err instanceof AppBskyContactImportContacts.InvalidTokenError - ) { + } else if (getErrorName(err) === 'InvalidToken') { Toast.show( _( msg`Could not upload contacts. You need to re-verify your phone number to proceed`, @@ -324,7 +322,7 @@ function showPermissionDeniedAlert() { * Copied from `#/screens/Onboarding/StepFinished/index.tsx` */ async function createProfileRecord( - agent: SessionAgent, + pdsClient: Client, onboardingContext: { state: OnboardingState dispatch: React.Dispatch @@ -333,21 +331,23 @@ async function createProfileRecord( const profileStepResults = onboardingContext.state.profileStepResults const {imageUri, imageMime} = profileStepResults const blobPromise = - imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined + imageUri && imageMime + ? uploadBlob(pdsClient, imageUri, imageMime) + : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise - if (res.data.blob) { - next.avatar = res.data.blob + if (res.blob) { + next.avatar = res.blob } } next.displayName = '' - next.createdAt = new Date().toISOString() + next.createdAt = toDatetimeString(new Date()) return next }) } diff --git a/src/components/contacts/screens/PhoneInput.tsx b/src/components/contacts/screens/PhoneInput.tsx index 6904789a6e..8426ec04e0 100644 --- a/src/components/contacts/screens/PhoneInput.tsx +++ b/src/components/contacts/screens/PhoneInput.tsx @@ -2,7 +2,6 @@ import {useState} from 'react' import {Keyboard, View} from 'react-native' import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {AppBskyContactStartPhoneVerification} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,6 +13,7 @@ import { getDefaultCountry, } from '#/lib/international-telephone-codes' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useAgent} from '#/state/session' import {OnboardingPosition} from '#/screens/Onboarding/Layout' @@ -102,14 +102,9 @@ export function PhoneInput({ msg`A network error occurred. Please check your internet connection`, ), ) - } else if ( - err instanceof - AppBskyContactStartPhoneVerification.RateLimitExceededError - ) { + } else if (getErrorName(err) === 'RateLimitExceeded') { setError(_(msg`Rate limit exceeded. Please try again later.`)) - } else if ( - err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError - ) { + } else if (getErrorName(err) === 'InvalidPhone') { setError( _( msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, diff --git a/src/components/contacts/screens/VerifyNumber.tsx b/src/components/contacts/screens/VerifyNumber.tsx index f9d062f691..2ecf081671 100644 --- a/src/components/contacts/screens/VerifyNumber.tsx +++ b/src/components/contacts/screens/VerifyNumber.tsx @@ -1,9 +1,5 @@ import {useEffect, useMemo, useState} from 'react' import {Text as NestedText, View} from 'react-native' -import { - AppBskyContactStartPhoneVerification, - AppBskyContactVerifyPhone, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -11,6 +7,7 @@ import {useMutation} from '@tanstack/react-query' import {clamp} from '#/lib/numbers' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useAgent} from '#/state/session' import {OnboardingPosition} from '#/screens/Onboarding/Layout' @@ -99,13 +96,13 @@ export function VerifyNumber({ msg`A network error occurred. Please check your internet connection.`, ), }) - } else if (err instanceof AppBskyContactVerifyPhone.InvalidCodeError) { + } else if (getErrorName(err) === 'InvalidCode') { setError({ retryable: true, isResendError: true, message: _(msg`This code is invalid. Resend to get a new code.`), }) - } else if (err instanceof AppBskyContactVerifyPhone.InvalidPhoneError) { + } else if (getErrorName(err) === 'InvalidPhone') { setError({ retryable: false, isResendError: false, @@ -113,9 +110,7 @@ export function VerifyNumber({ msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, ), }) - } else if ( - err instanceof AppBskyContactVerifyPhone.RateLimitExceededError - ) { + } else if (getErrorName(err) === 'RateLimitExceeded') { setError({ retryable: true, isResendError: false, @@ -155,9 +150,7 @@ export function VerifyNumber({ msg`A network error occurred. Please check your internet connection.`, ), }) - } else if ( - err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError - ) { + } else if (getErrorName(err) === 'InvalidPhone') { setError({ retryable: false, isResendError: true, @@ -165,10 +158,7 @@ export function VerifyNumber({ msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, ), }) - } else if ( - err instanceof - AppBskyContactStartPhoneVerification.RateLimitExceededError - ) { + } else if (getErrorName(err) === 'RateLimitExceeded') { setError({ retryable: true, isResendError: true, diff --git a/src/components/contacts/screens/ViewMatches.tsx b/src/components/contacts/screens/ViewMatches.tsx index 1b8ed60480..1154b582b4 100644 --- a/src/components/contacts/screens/ViewMatches.tsx +++ b/src/components/contacts/screens/ViewMatches.tsx @@ -20,7 +20,12 @@ import { optimisticRemoveMatch, useMatchesPassthroughQuery, } from '#/state/queries/find-contacts' -import {useAgent, useSession} from '#/state/session' +import { + useAgent, + useAppviewClient, + usePdsClient, + useSession, +} from '#/state/session' import {List, type ListMethods} from '#/view/com/util/List' import {UserAvatar} from '#/view/com/util/UserAvatar' import {OnboardingPosition} from '#/screens/Onboarding/Layout' @@ -90,6 +95,8 @@ export function ViewMatches({ const moderationOpts = useModerationOpts() const queryClient = useQueryClient() const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const insets = useSafeAreaInsets() const listRef = useRef(null) @@ -124,7 +131,10 @@ export function ViewMatches({ }) } - const uris = await wait(500, bulkWriteFollows(agent, followableDids)) + const uris = await wait( + 500, + bulkWriteFollows(pdsClient, appviewClient, followableDids), + ) for (const did of followableDids) { const uri = uris.get(did) diff --git a/src/components/dialogs/Embed.tsx b/src/components/dialogs/Embed.tsx index 8a88b15c9d..caecc9a2a9 100644 --- a/src/components/dialogs/Embed.tsx +++ b/src/components/dialogs/Embed.tsx @@ -1,6 +1,5 @@ import {memo, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedPost} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -21,15 +20,16 @@ import { } from '#/components/icons/Chevron' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export type ColorModeValues = 'system' | 'light' | 'dark' type EmbedDialogProps = { control: Dialog.DialogControlProps - postAuthor: AppBskyActorDefs.ProfileViewBasic + postAuthor: app.bsky.actor.defs.ProfileViewBasic postCid: string postUri: string - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main timestamp: string } diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index a885f1bf5f..3263113a73 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -1,6 +1,7 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' +import {type DatetimeString, toDatetimeString} from '@atproto/syntax' +import {sanitizeMutedWordValue} from '@bsky.app/sdk/utils' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -35,6 +36,7 @@ import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' const ONE_DAY = 24 * 60 * 60 * 1000 @@ -68,20 +70,20 @@ function MutedWordsInner() { const sanitizedValue = sanitizeMutedWordValue(field) const surfaces = ['tag', targets.includes('content') && 'content'].filter( Boolean, - ) as AppBskyActorDefs.MutedWord['targets'] + ) as app.bsky.actor.defs.MutedWord['targets'] const actorTarget = excludeFollowing ? 'exclude-following' : 'all' const now = Date.now() const rawDuration = durations.at(0) // undefined evaluates to 'forever' - let duration: string | undefined + let duration: DatetimeString | undefined if (rawDuration === '24_hours') { - duration = new Date(now + ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + ONE_DAY)) } else if (rawDuration === '7_days') { - duration = new Date(now + 7 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 7 * ONE_DAY)) } else if (rawDuration === '30_days') { - duration = new Date(now + 30 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 30 * ONE_DAY)) } if (!sanitizedValue || !surfaces.length) { @@ -421,7 +423,7 @@ function MutedWordsInner() { function MutedWordRow({ style, word, -}: ViewStyleProp & {word: AppBskyActorDefs.MutedWord}) { +}: ViewStyleProp & {word: app.bsky.actor.defs.MutedWord}) { const t = useTheme() const {_} = useLingui() const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation() @@ -440,7 +442,7 @@ function MutedWordRow({ updateMutedWord({ ...word, expiresAt: days - ? new Date(Date.now() + days * ONE_DAY).toISOString() + ? toDatetimeString(new Date(Date.now() + days * ONE_DAY)) : undefined, }) } diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index 62d3e41ad3..4b04c9fefa 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Text as NestedText, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyFeedPostgate} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -33,7 +32,7 @@ import { PostThreadContextProvider, usePostThreadContext, } from '#/state/queries/usePostThread' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -50,6 +49,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' export type PostInteractionSettingsFormProps = { canSave?: boolean @@ -60,8 +60,8 @@ export type PostInteractionSettingsFormProps = { persist?: boolean onChangePersist?: (v: boolean) => void - postgate: AppBskyFeedPostgate.Record - onChangePostgate: (v: AppBskyFeedPostgate.Record) => void + postgate: app.bsky.feed.postgate.Main + onChangePostgate: (v: app.bsky.feed.postgate.Main) => void threadgateAllowUISettings: ThreadgateAllowUISetting[] onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void @@ -132,10 +132,10 @@ export type PostInteractionSettingsDialogProps = { */ rootPostUri: string /** - * Optional initial {@link AppBskyFeedDefs.ThreadgateView} to use if we + * Optional initial {@link app.bsky.feed.defs.ThreadgateView} to use if we * happen to have one before opening the settings dialog. */ - initialThreadgateView?: AppBskyFeedDefs.ThreadgateView + initialThreadgateView?: app.bsky.feed.defs.ThreadgateView } /** @@ -175,7 +175,7 @@ export function PostInteractionSettingsDialogControlledInner( const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation() const [editedPostgate, setEditedPostgate] = - useState() + useState() const [editedAllowUISettings, setEditedAllowUISettings] = useState() @@ -694,7 +694,7 @@ export function usePrefetchPostInteractionSettings({ }) { const ax = useAnalytics() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() const getPost = useGetPost() return useCallback(async () => { @@ -703,7 +703,7 @@ export function usePrefetchPostInteractionSettings({ queryClient.prefetchQuery({ queryKey: createPostgateQueryKey(postUri), queryFn: () => - getPostgateRecord({agent, postUri}).then(res => res ?? null), + getPostgateRecord({pdsClient, postUri}).then(res => res ?? null), staleTime: STALE.SECONDS.THIRTY, }), queryClient.prefetchQuery({ @@ -720,5 +720,5 @@ export function usePrefetchPostInteractionSettings({ safeMessage: e.message, }) } - }, [ax, queryClient, agent, postUri, rootPostUri, getPost]) + }, [ax, queryClient, pdsClient, postUri, rootPostUri, getPost]) } diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index 26ac1672bf..112a46af56 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -1,6 +1,5 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type AppBskyGraphGetStarterPacksWithMembership} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -34,7 +33,7 @@ import {app} from '#/lexicons' import * as bsky from '#/types/bsky' type StarterPackWithMembership = - AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership + app.bsky.graph.getStarterPacksWithMembership.StarterPackWithMembership export type StarterPackDialogProps = { control: Dialog.DialogControlProps diff --git a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx index 4b1c01fa32..a496c31475 100644 --- a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx +++ b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx @@ -1,13 +1,12 @@ import {View} from 'react-native' -import { - type $Typed, - type AppBskyGraphDefs, - type AppBskyGraphListitem, - type AppBskyGraphStarterpack, - type ComAtprotoRepoApplyWrites, -} from '@atproto/api' import {TID} from '@atproto/common-web' -import {AtUri} from '@atproto/syntax' +import {type $Typed} from '@atproto/lex' +import { + type AtIdentifierString, + AtUri, + type AtUriString, + toDatetimeString, +} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,7 +19,7 @@ import {wait} from '#/lib/async/wait' import {type NavigationProp} from '#/lib/routes/types' import {logger} from '#/logger' import {getAllListMembers} from '#/state/queries/list-members' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {atoms as a, platform, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonText} from '#/components/Button' @@ -29,6 +28,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app, com} from '#/lexicons' import {CreateOrEditListDialog} from './CreateOrEditListDialog' export function CreateListFromStarterPackDialog({ @@ -36,11 +36,12 @@ export function CreateListFromStarterPackDialog({ starterPack, }: { control: Dialog.DialogControlProps - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView }) { const {_} = useLingui() const t = useTheme() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const ax = useAnalytics() const {currentAccount} = useSession() const navigation = useNavigation() @@ -48,7 +49,7 @@ export function CreateListFromStarterPackDialog({ const createDialogControl = Dialog.useDialogControl() const loadingDialogControl = Dialog.useDialogControl() - const record = starterPack.record as AppBskyGraphStarterpack.Record + const record = starterPack.record as app.bsky.graph.starterpack.Main const onPressCreate = () => { control.close(() => createDialogControl.open()) @@ -73,16 +74,19 @@ export function CreateListFromStarterPackDialog({ const listItems = await wait( 3000, (async () => { - const items = await getAllListMembers(agent, starterPack.list!.uri) + const items = await getAllListMembers( + appviewClient, + starterPack.list!.uri, + ) if (items.length > 0) { - const listitemWrites: $Typed[] = + const listitemWrites: $Typed[] = items.map(item => { - const listitemRecord: $Typed = { + const listitemRecord: $Typed = { $type: 'app.bsky.graph.listitem', subject: item.subject.did, - list: listUri, - createdAt: new Date().toISOString(), + list: listUri as AtUriString, + createdAt: toDatetimeString(new Date()), } return { $type: 'com.atproto.repo.applyWrites#create', @@ -94,8 +98,8 @@ export function CreateListFromStarterPackDialog({ const chunks = chunk(listitemWrites, 50) for (const c of chunks) { - await agent.com.atproto.repo.applyWrites({ - repo: currentAccount.did, + await pdsClient.call(com.atproto.repo.applyWrites, { + repo: currentAccount.did as AtIdentifierString, writes: c, }) } @@ -103,10 +107,10 @@ export function CreateListFromStarterPackDialog({ await until( 5, 1e3, - (res: {data: {items: unknown[]}}) => res.data.items.length > 0, + (res: {items: unknown[]}) => res.items.length > 0, () => - agent.app.bsky.graph.getList({ - list: listUri, + appviewClient.call(app.bsky.graph.getList, { + list: listUri as AtUriString, limit: 1, }), ) diff --git a/src/components/dialogs/lists/CreateOrEditListDialog.tsx b/src/components/dialogs/lists/CreateOrEditListDialog.tsx index 4ffc954ce5..f3d0b74a65 100644 --- a/src/components/dialogs/lists/CreateOrEditListDialog.tsx +++ b/src/components/dialogs/lists/CreateOrEditListDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -28,6 +27,7 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' const DISPLAY_NAME_MAX_GRAPHEMES = 64 @@ -47,8 +47,8 @@ export function CreateOrEditListDialog({ initialValues, }: { control: Dialog.DialogControlProps - list?: AppBskyGraphDefs.ListView - purpose?: AppBskyGraphDefs.ListPurpose + list?: app.bsky.graph.defs.ListView + purpose?: app.bsky.graph.defs.ListPurpose onSave?: (uri: string) => void initialValues?: InitialListValues }) { @@ -115,8 +115,8 @@ function DialogInner({ onPressCancel, initialValues, }: { - list?: AppBskyGraphDefs.ListView - purpose?: AppBskyGraphDefs.ListPurpose + list?: app.bsky.graph.defs.ListView + purpose?: app.bsky.graph.defs.ListPurpose onSave?: (uri: string) => void setDirty: (dirty: boolean) => void onPressCancel: () => void diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx index a8f04e2857..ab63ea5677 100644 --- a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx +++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -23,6 +22,7 @@ import { import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' import * as Toast from '#/components/Toast' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function ListAddRemoveUsersDialog({ @@ -31,7 +31,7 @@ export function ListAddRemoveUsersDialog({ onChange, }: { control: Dialog.DialogControlProps - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -52,7 +52,7 @@ function DialogInner({ list, onChange, }: { - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -89,7 +89,7 @@ function DialogInner({ * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record) */ function getMembership( - listMembers: AppBskyGraphDefs.ListItemView[] | undefined, + listMembers: app.bsky.graph.defs.ListItemView[] | undefined, actorDid: string, ): string | false | undefined { if (!listMembers) { @@ -107,8 +107,8 @@ function UserResult({ moderationOpts, }: { profile: bsky.profile.AnyProfileView - list: AppBskyGraphDefs.ListView - listMembers: AppBskyGraphDefs.ListItemView[] | undefined + list: app.bsky.graph.defs.ListView + listMembers: app.bsky.graph.defs.ListItemView[] | undefined onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx index 2fa8780d95..bbd40feb52 100644 --- a/src/components/dialogs/nuxs/index.tsx +++ b/src/components/dialogs/nuxs/index.tsx @@ -6,7 +6,6 @@ import { useMemo, useState, } from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {logger} from '#/logger' import {STALE} from '#/state/queries' @@ -30,6 +29,7 @@ import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils' import {useAnalytics} from '#/analytics' import {useGeolocation} from '#/geolocation' +import {type app} from '#/lexicons' type Context = { activeNux: Nux | undefined @@ -93,7 +93,7 @@ function Inner({ preferences, }: { currentAccount: SessionAccount - currentProfile: AppBskyActorDefs.ProfileViewDetailed + currentProfile: app.bsky.actor.defs.ProfileViewDetailed preferences: UsePreferencesQueryResponse }) { const ax = useAnalytics() diff --git a/src/components/dialogs/nuxs/utils.ts b/src/components/dialogs/nuxs/utils.ts index 315908cc85..812f28eb1c 100644 --- a/src/components/dialogs/nuxs/utils.ts +++ b/src/components/dialogs/nuxs/utils.ts @@ -1,14 +1,13 @@ -import {type AppBskyActorDefs} from '@atproto/api' - import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' import {type SessionAccount} from '#/state/session' import {type AnalyticsContextType} from '#/analytics' import {type Geolocation} from '#/geolocation' +import {type app} from '#/lexicons' export type EnabledCheckProps = { features: AnalyticsContextType['features'] currentAccount: SessionAccount - currentProfile: AppBskyActorDefs.ProfileViewDetailed + currentProfile: app.bsky.actor.defs.ProfileViewDetailed preferences: UsePreferencesQueryResponse geolocation: Geolocation } diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx index b9782949b6..837214b74c 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -1,11 +1,11 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {MessageContextMenu} from '#/components/dms/MessageContextMenu' import {useMessageReplies} from '#/components/dms/MessageReplies' import {SwipeToReply} from '#/components/dms/SwipeToReply' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' export function ActionsWrapper({ @@ -15,7 +15,7 @@ export function ActionsWrapper({ moderationOpts, children, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index 0ef5503370..469c339bf8 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -1,6 +1,5 @@ import {useCallback, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -14,6 +13,7 @@ import {MessageContextMenu} from '#/components/dms/MessageContextMenu' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import * as Toast from '#/components/Toast' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import { @@ -29,7 +29,7 @@ export function ActionsWrapper({ moderationOpts, children, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined diff --git a/src/components/dms/AfterReportConversationDialog.tsx b/src/components/dms/AfterReportConversationDialog.tsx index 8e484156f4..98973ea533 100644 --- a/src/components/dms/AfterReportConversationDialog.tsx +++ b/src/components/dms/AfterReportConversationDialog.tsx @@ -1,6 +1,5 @@ import {memo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' @@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' type ReportDialogParams = { convoId: string @@ -113,7 +113,7 @@ function DoneStep({ }: { convoId: string currentScreen: 'list' | 'conversation' - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {t: l} = useLingui() const navigation = useNavigation() diff --git a/src/components/dms/AfterReportDialog.tsx b/src/components/dms/AfterReportDialog.tsx index 060ea1fcfb..c60f1ac0b6 100644 --- a/src/components/dms/AfterReportDialog.tsx +++ b/src/components/dms/AfterReportDialog.tsx @@ -1,6 +1,5 @@ import {memo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' @@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' type ReportDialogParams = { convoId: string @@ -116,7 +116,7 @@ function DoneStep({ }: { convoId: string currentScreen: 'list' | 'conversation' - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {t: l} = useLingui() const navigation = useNavigation() diff --git a/src/components/dms/ChatInvite/Card.tsx b/src/components/dms/ChatInvite/Card.tsx index 92e0e96814..177eb5d5ae 100644 --- a/src/components/dms/ChatInvite/Card.tsx +++ b/src/components/dms/ChatInvite/Card.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {ChatBskyGroupDefs} from '@atproto/api' import {Plural, Trans} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' @@ -10,6 +9,8 @@ import {AvatarBubbles} from '#/components/AvatarBubbles' import {InlineLinkText} from '#/components/Link' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {useChatInvite} from './Context' /** @@ -21,7 +22,8 @@ export function Card({size}: {size: 'large' | 'small'}) { const t = useTheme() const {preview, hasFixedHeight} = useChatInvite() - if (!ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) return null + if (!bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) + return null const ownerDisplayName = createSanitizedDisplayName(preview.owner) const ownerHandle = sanitizeHandle(preview.owner.handle, '@') diff --git a/src/components/dms/ChatInvite/Root.tsx b/src/components/dms/ChatInvite/Root.tsx index 16f5bdfae4..83796e1e61 100644 --- a/src/components/dms/ChatInvite/Root.tsx +++ b/src/components/dms/ChatInvite/Root.tsx @@ -1,5 +1,4 @@ import {setStringAsync} from 'expo-clipboard' -import {ChatBskyGroupDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -18,6 +17,8 @@ import {type Props as SVGIconProps} from '#/components/icons/common' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import * as Toast from '#/components/Toast' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { type ChatInviteAction, ChatInviteProvider, @@ -71,7 +72,7 @@ export function Root({ status = 'loading' } else if (error) { status = 'error' - } else if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + } else if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) { status = 'available' } else { // Resolved to a disabled/invalid/unrecognized preview - nothing to join. @@ -79,7 +80,7 @@ export function Root({ } let action: ChatInviteAction | undefined - if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) { const convoId = preview.convo?.id const isFollowing = preview.owner.viewer?.followedBy ?? false const hasRequested = !convoId && preview.viewer?.requestedAt != null diff --git a/src/components/dms/EmojiReactionPicker.tsx b/src/components/dms/EmojiReactionPicker.tsx index de2b818b96..b54881117e 100644 --- a/src/components/dms/EmojiReactionPicker.tsx +++ b/src/components/dms/EmojiReactionPicker.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {useWindowDimensions, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -14,6 +13,7 @@ import { import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {type TriggerProps} from '#/components/Menu/types' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {EmojiPopup} from './EmojiPopup' import {hasAlreadyReacted, hasReachedReactionLimit} from './util' @@ -21,7 +21,7 @@ export function EmojiReactionPicker({ message, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView children?: TriggerProps['children'] onEmojiSelect: (emoji: string) => void }) { diff --git a/src/components/dms/EmojiReactionPicker.web.tsx b/src/components/dms/EmojiReactionPicker.web.tsx index 8740330575..c84151f367 100644 --- a/src/components/dms/EmojiReactionPicker.web.tsx +++ b/src/components/dms/EmojiReactionPicker.web.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {DropdownMenu} from 'radix-ui' @@ -10,6 +9,7 @@ import * as EmojiPicker from '#/components/EmojiPicker' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Menu from '#/components/Menu' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {hasAlreadyReacted, hasReachedReactionLimit} from './util' export function EmojiReactionPicker({ @@ -17,7 +17,7 @@ export function EmojiReactionPicker({ children, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView children?: EmojiPicker.TriggerProps['children'] onEmojiSelect: (emoji: string) => void }) { @@ -40,7 +40,7 @@ function MenuInner({ message, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView onEmojiSelect: (emoji: string) => void }) { const t = useTheme() diff --git a/src/components/dms/LeaveConvoPrompt.tsx b/src/components/dms/LeaveConvoPrompt.tsx index 5e9943e53f..761c85732e 100644 --- a/src/components/dms/LeaveConvoPrompt.tsx +++ b/src/components/dms/LeaveConvoPrompt.tsx @@ -1,9 +1,9 @@ -import {ChatBskyConvoLeaveConvo} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {type DialogOuterProps} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' @@ -36,11 +36,9 @@ export function LeaveConvoPrompt({ let errorMessage = l`Could not leave chat` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { + } else if (getErrorName(error) === 'InvalidConvo') { errorMessage = l`Conversation not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { + } else if (getErrorName(error) === 'OwnerCannotLeave') { errorMessage = l`Owner must lock the group before leaving.` } Toast.show(errorMessage, {type: 'error'}) diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index eff369e8dc..5abce35fb9 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -2,7 +2,6 @@ import {memo, useCallback} from 'react' import {Platform} from 'react-native' import {type GestureType} from 'react-native-gesture-handler' import * as Clipboard from 'expo-clipboard' -import {type ChatBskyConvoDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' @@ -28,6 +27,7 @@ import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Tra import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' import {toLex} from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' @@ -40,7 +40,7 @@ export let MessageContextMenu = ({ children, swipeGesture, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined children: TriggerProps['children'] diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index bb814f7d6f..590f8f6dde 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -22,12 +22,6 @@ import Animated, { ZoomIn, ZoomOut, } from 'react-native-reanimated' -import { - AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, -} from '@atproto/api' import {moderateProfile} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' @@ -58,7 +52,8 @@ import * as ProfileCard from '#/components/ProfileCard' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -import {toLex} from '#/types/bsky' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {DateDivider} from './DateDivider' import {MessageItemEmbed} from './MessageItemEmbed' import {MessageItemInviteEmbed} from './MessageItemInviteEmbed' @@ -77,16 +72,19 @@ const SQUARED_BORDER_RADIUS = 4 const DISPLAY_NAME_INSET = 20 export type MessageItemNeighbor = - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView | null function messageIsReply(message: MessageItemNeighbor): boolean { return ( - ChatBskyConvoDefs.isMessageView(message) && - (ChatBskyConvoDefs.isMessageView(message.replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || - ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo)) + bsky.isType(chat.bsky.convo.defs.messageView, message) && + (bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) || + bsky.isType( + chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, + message.replyTo, + )) ) } @@ -98,7 +96,7 @@ function isWithinClusterBoundary({ direction, }: { isPending: boolean - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView adjacentMessage: MessageItemNeighbor isFromSameSender: boolean direction: 'prev' | 'next' @@ -110,7 +108,7 @@ function isWithinClusterBoundary({ return true } if (!isFromSameSender) return true - if (ChatBskyConvoDefs.isMessageView(adjacentMessage)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, adjacentMessage)) { const currentSentAt = message.sentAt const thisDate = new Date(currentSentAt) const adjDate = new Date(adjacentMessage.sentAt) @@ -137,7 +135,7 @@ let MessageItem = ({ isGroupChat?: boolean prevMessage: MessageItemNeighbor nextMessage: MessageItemNeighbor - relatedProfiles: Map + relatedProfiles: Map }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() @@ -155,13 +153,17 @@ let MessageItem = ({ // tombstone, or a before-joined placeholder. Narrow away the open-union // fallback so we only render shapes we understand. const replyTo = - ChatBskyConvoDefs.isMessageView(message.replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || - ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) || + bsky.isType( + chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, + message.replyTo, + ) ? message.replyTo : undefined const replyToMessageId = - replyTo && !ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo) + replyTo && + !bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo) ? replyTo.id : undefined const onPressReplyTo = replyToMessageId @@ -175,8 +177,14 @@ let MessageItem = ({ const isFromSelf = message.sender?.did != null && message.sender.did === currentAccount?.did - const prevIsMessage = ChatBskyConvoDefs.isMessageView(prevMessage) - const nextIsMessage = ChatBskyConvoDefs.isMessageView(nextMessage) + const prevIsMessage = bsky.isType( + chat.bsky.convo.defs.messageView, + prevMessage, + ) + const nextIsMessage = bsky.isType( + chat.bsky.convo.defs.messageView, + nextMessage, + ) const isPrevFromSameSender = prevIsMessage && @@ -204,7 +212,7 @@ let MessageItem = ({ }) const hasLargeGapFromPrev = - !ChatBskyConvoDefs.isMessageView(prevMessage) || + !bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) || new Date(message.sentAt).getTime() - new Date(prevMessage.sentAt).getTime() > MESSAGE_GAP_THRESHOLD_MS @@ -251,8 +259,8 @@ let MessageItem = ({ const isEmojiOnly = isOnlyEmoji(message.text) const hasEmbed = - AppBskyEmbedRecord.isView(message.embed) || - ChatBskyEmbedJoinLink.isView(message.embed) + bsky.isType(app.bsky.embed.record.view, message.embed) || + bsky.isType(chat.bsky.embed.joinLink.view, message.embed) const hasEmbedAndText = hasEmbed && rt.text.length > 0 const targetBottomRadius = squaredBottomCorner @@ -336,7 +344,7 @@ let MessageItem = ({ size={AVATAR_SIZE} type={profile.associated?.labeler ? 'labeler' : 'user'} onBeforePress={() => unstableCacheProfileView(queryClient, profile)} - moderation={moderateProfile(toLex(profile), moderationOpts).ui( + moderation={moderateProfile(bsky.toLex(profile), moderationOpts).ui( 'avatar', )} /> @@ -515,7 +523,7 @@ let MessageItem = ({ message={message} senderProfile={profile} moderationOpts={moderationOpts}> - {AppBskyEmbedRecord.isView(message.embed) && ( + {bsky.isType(app.bsky.embed.record.view, message.embed) && ( )} - {ChatBskyEmbedJoinLink.isView(message.embed) && ( + {bsky.isType( + chat.bsky.embed.joinLink.view, + message.embed, + ) && ( + profile: Shadow style?: AnimatedStyle }) { const {t: l} = useLingui() @@ -775,13 +786,13 @@ function ReplyCaption({ onPress, }: { replyTo: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView isFromSelf: boolean isGroupChat: boolean replierDisplayName: string | null - relatedProfiles: Map + relatedProfiles: Map onPress?: () => void }) { const t = useTheme() @@ -790,8 +801,8 @@ function ReplyCaption({ let caption: string = '' if ( - ChatBskyConvoDefs.isMessageView(replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo) ) { const originalSenderIsSelf = replyTo.sender.did === currentAccount?.did const originalProfile = relatedProfiles.get(replyTo.sender.did) @@ -864,11 +875,11 @@ function ReplyQuote({ onPress, }: { replyTo: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView isFromSelf: boolean - relatedProfiles: Map + relatedProfiles: Map onPress?: () => void }) { const t = useTheme() @@ -876,8 +887,8 @@ function ReplyQuote({ const getReplyPreviewText = useReplyPreviewText() const senderDid = - ChatBskyConvoDefs.isMessageView(replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo) ? replyTo.sender.did : undefined const senderProfile = useMaybeProfileShadow( @@ -907,9 +918,11 @@ function ReplyQuote({ comment: 'A reply summary in chat', }) subtle = true - } else if (ChatBskyConvoDefs.isMessageView(replyTo)) { + } else if (bsky.isType(chat.bsky.convo.defs.messageView, replyTo)) { ;({text, subtle} = getReplyPreviewText(replyTo)) - } else if (ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo) + ) { text = l({ message: `(message sent before you joined)`, comment: 'A reply summary in chat', diff --git a/src/components/dms/MessageItemEmbed.tsx b/src/components/dms/MessageItemEmbed.tsx index 5014a9ff11..22c3f7462d 100644 --- a/src/components/dms/MessageItemEmbed.tsx +++ b/src/components/dms/MessageItemEmbed.tsx @@ -5,10 +5,11 @@ import Animated, { type SharedValue, useAnimatedStyle, } from 'react-native-reanimated' -import {type $Typed, type AppBskyEmbedRecord} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {atoms as a, native, useTheme, web} from '#/alf' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' +import {type app} from '#/lexicons' import {MessageContextProvider} from './MessageContext' const BORDER_RADIUS = 20 @@ -22,7 +23,7 @@ let MessageItemEmbed = ({ squaredBottomCorner, highlightSV, }: { - embed: $Typed + embed: $Typed isFromSelf: boolean isGroupChat: boolean squaredTopCorner: boolean diff --git a/src/components/dms/MessageItemInviteEmbed.tsx b/src/components/dms/MessageItemInviteEmbed.tsx index 5d6acd93d1..ba6a2b0630 100644 --- a/src/components/dms/MessageItemInviteEmbed.tsx +++ b/src/components/dms/MessageItemInviteEmbed.tsx @@ -5,12 +5,13 @@ import Animated, { type SharedValue, useAnimatedStyle, } from 'react-native-reanimated' -import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {useConvoActive} from '#/state/messages/convo' import {isKnownJoinLinkPreview} from '#/state/queries/join-links' import {atoms as a, native, useTheme, web} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' +import {type chat} from '#/lexicons' import {MessageContextProvider} from './MessageContext' const BORDER_RADIUS = 20 @@ -24,7 +25,7 @@ let MessageItemInviteEmbed = ({ squaredBottomCorner, highlightSV, }: { - embed: $Typed + embed: $Typed isFromSelf: boolean isGroupChat: boolean squaredTopCorner: boolean diff --git a/src/components/dms/MessageOverlays.tsx b/src/components/dms/MessageOverlays.tsx index 2384aad88d..ddb6fa9acf 100644 --- a/src/components/dms/MessageOverlays.tsx +++ b/src/components/dms/MessageOverlays.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {LayoutAnimation} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -20,15 +19,16 @@ import {ReportDialog} from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt' import * as Toast from '#/components/Toast' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' type MessageDialogsContextType = { - openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void + openDeleteMessage: (message: chat.bsky.convo.defs.MessageView) => void openReportMessage: ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, senderProfile: bsky.profile.AnyProfileView | undefined, ) => void - openReactions: (message: ChatBskyConvoDefs.MessageView) => void + openReactions: (message: chat.bsky.convo.defs.MessageView) => void } const Context = createContext(null) @@ -52,18 +52,18 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { const reactionsControl = useDialogControl() const [deleteTarget, setDeleteTarget] = - useState(null) + useState(null) const [reportTarget, setReportTarget] = useState<{ - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView senderProfile: bsky.profile.AnyProfileView | undefined } | null>(null) const [afterReportTarget, setAfterReportTarget] = - useState(null) + useState(null) const [reactionsTarget, setReactionsTarget] = - useState(null) + useState(null) const openDeleteMessage = useCallback( - (message: ChatBskyConvoDefs.MessageView) => { + (message: chat.bsky.convo.defs.MessageView) => { setDeleteTarget(message) deleteControl.open() }, @@ -72,7 +72,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { const openReportMessage = useCallback( ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, senderProfile: bsky.profile.AnyProfileView | undefined, ) => { setReportTarget({message, senderProfile}) @@ -82,7 +82,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { ) const openReactions = useCallback( - (message: ChatBskyConvoDefs.MessageView) => { + (message: chat.bsky.convo.defs.MessageView) => { setReactionsTarget(message) }, [], diff --git a/src/components/dms/MessageProfileButton.tsx b/src/components/dms/MessageProfileButton.tsx index ede352218b..189f0da632 100644 --- a/src/components/dms/MessageProfileButton.tsx +++ b/src/components/dms/MessageProfileButton.tsx @@ -1,6 +1,5 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -16,11 +15,12 @@ import {canBeMessaged} from '#/components/dms/util' import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' export function MessageProfileButton({ profile, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/dms/MessageReplies.tsx b/src/components/dms/MessageReplies.tsx index 5093d8d157..e5bde7e078 100644 --- a/src/components/dms/MessageReplies.tsx +++ b/src/components/dms/MessageReplies.tsx @@ -7,7 +7,8 @@ import { useRef, useState, } from 'react' -import {type ChatBskyConvoDefs} from '@atproto/api' + +import {type chat} from '#/lexicons' /** * How long a message stays highlighted after scrolling to it, before the flash @@ -28,8 +29,8 @@ type MessageRepliesContextType = { /** * The message currently staged for reply in the composer, or null. */ - replyTo: ChatBskyConvoDefs.MessageView | null - setReply: (message: ChatBskyConvoDefs.MessageView) => void + replyTo: chat.bsky.convo.defs.MessageView | null + setReply: (message: chat.bsky.convo.defs.MessageView) => void clearReply: () => void /** * Scroll the list to a message, if it's currently loaded, and flash it. No-op @@ -66,9 +67,8 @@ export function MessageRepliesProvider({ */ scrollToMessage: (messageId: string) => boolean }) { - const [replyTo, setReplyTo] = useState( - null, - ) + const [replyTo, setReplyTo] = + useState(null) const [highlightedMessage, setHighlightedMessage] = useState(null) const highlightKey = useRef(0) @@ -76,7 +76,7 @@ export function MessageRepliesProvider({ null, ) - const setReply = useCallback((message: ChatBskyConvoDefs.MessageView) => { + const setReply = useCallback((message: chat.bsky.convo.defs.MessageView) => { setReplyTo(message) }, []) diff --git a/src/components/dms/ReactionsDialog.tsx b/src/components/dms/ReactionsDialog.tsx index 3918143d37..5a9f83c6e9 100644 --- a/src/components/dms/ReactionsDialog.tsx +++ b/src/components/dms/ReactionsDialog.tsx @@ -6,7 +6,6 @@ import { useWindowDimensions, View, } from 'react-native' -import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {HITSLOP_10} from '#/lib/constants' @@ -23,12 +22,13 @@ import {filterBlockedReactions} from '#/components/dms/util' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE, IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' type Reaction = { key: string value: string - senders: ChatBskyConvoDefs.ReactionViewSender[] + senders: chat.bsky.convo.defs.ReactionViewSender[] count: number } @@ -41,8 +41,8 @@ export function ReactionsDialog({ onClose, }: { control: Dialog.DialogControlProps - relatedProfiles: Map - message: ChatBskyConvoDefs.MessageView + relatedProfiles: Map + message: chat.bsky.convo.defs.MessageView onClose?: () => void }) { const {t: l} = useLingui() @@ -142,10 +142,10 @@ function ReactionRow({ control: Dialog.DialogControlProps convo: ActiveConvoStates currentAccount?: SessionAccount - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView profile: bsky.profile.AnyProfileView - reaction: ChatBskyConvoDefs.ReactionView - allReactions: ChatBskyConvoDefs.ReactionView[] + reaction: chat.bsky.convo.defs.ReactionView + allReactions: chat.bsky.convo.defs.ReactionView[] selected: string setSelected: React.Dispatch> }) { @@ -398,7 +398,7 @@ function ReactionTab({ } export function groupReactions( - reactions: ChatBskyConvoDefs.ReactionView[] | undefined, + reactions: chat.bsky.convo.defs.ReactionView[] | undefined, ): Reaction[] { const grouped = new Map() for (const reaction of reactions ?? []) { diff --git a/src/components/dms/SystemMessageGroup.tsx b/src/components/dms/SystemMessageGroup.tsx index 930242ca46..5305572c57 100644 --- a/src/components/dms/SystemMessageGroup.tsx +++ b/src/components/dms/SystemMessageGroup.tsx @@ -7,7 +7,6 @@ import Animated, { useDerivedValue, withTiming, } from 'react-native-reanimated' -import {type ChatBskyActorDefs} from '@atproto/api' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -17,6 +16,7 @@ import {atoms as a, useTheme} from '#/alf' import {SystemMessageItem} from '#/components/dms/SystemMessageItem' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' const ANIMATION_DURATION_MS = 200 @@ -29,7 +29,7 @@ export function SystemMessageGroup({ item: SystemMessageGroupItem expanded: boolean onToggle: (key: string) => void - relatedProfiles: Map + relatedProfiles: Map }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/components/dms/SystemMessageItem.tsx b/src/components/dms/SystemMessageItem.tsx index 7de6a0cd91..716c26f474 100644 --- a/src/components/dms/SystemMessageItem.tsx +++ b/src/components/dms/SystemMessageItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {makeProfileLink} from '#/lib/routes/links' @@ -10,13 +9,14 @@ import {Button} from '#/components/Button' import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' export function SystemMessageItem({ item, relatedProfiles, }: { item: ConvoItem & {type: 'system-message'} - relatedProfiles: Map + relatedProfiles: Map }) { const t = useTheme() const {i18n, t: l} = useLingui() diff --git a/src/components/dms/dialogs/NewChatDialog.tsx b/src/components/dms/dialogs/NewChatDialog.tsx index fd9eaa8880..c0b4e614c8 100644 --- a/src/components/dms/dialogs/NewChatDialog.tsx +++ b/src/components/dms/dialogs/NewChatDialog.tsx @@ -1,12 +1,9 @@ import {useCallback} from 'react' -import { - ChatBskyConvoGetConvoForMembers, - ChatBskyGroupCreateGroup, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' @@ -54,26 +51,15 @@ export function NewChat({ let errorMessage = l`An issue occurred starting the chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError - ) { + } else if (getErrorName(error) === 'AccountSuspended') { errorMessage = l`Suspended accounts cannot participate in chat.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError - ) { + } else if (getErrorName(error) === 'BlockedActor') { errorMessage = l`This user has blocked you and cannot be messaged.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError - ) { + } else if (getErrorName(error) === 'MessagesDisabled') { errorMessage = l`This user has disabled chat and cannot be messaged.` - } else if ( - error instanceof - ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError - ) { + } else if (getErrorName(error) === 'NotFollowedBySender') { errorMessage = l`Chat recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError - ) { + } else if (getErrorName(error) === 'RecipientNotFound') { errorMessage = l`Unable to find the selected recipient.` } Toast.show(errorMessage, { @@ -92,28 +78,17 @@ export function NewChat({ let errorMessage = l`An issue occurred starting the group chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError - ) { + } else if (getErrorName(error) === 'AccountSuspended') { errorMessage = l`Suspended accounts cannot participate in a group chat.` - } else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) { + } else if (getErrorName(error) === 'BlockedActor') { errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` - } else if ( - error instanceof - ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError - ) { + } else if (getErrorName(error) === 'NewAccountCannotCreateGroup') { errorMessage = l`You cannot create a group chat yet.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError - ) { + } else if (getErrorName(error) === 'NotFollowedBySender') { errorMessage = l`A selected recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError - ) { + } else if (getErrorName(error) === 'RecipientNotFound') { errorMessage = l`Unable to find a selected recipient.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError - ) { + } else if (getErrorName(error) === 'UserForbidsGroups') { errorMessage = l`One of the selected recipients does not allow group chats.` } Toast.show(errorMessage, { diff --git a/src/components/dms/getMessageInfo.ts b/src/components/dms/getMessageInfo.ts index 5aaa3a8d7d..afe41c625e 100644 --- a/src/components/dms/getMessageInfo.ts +++ b/src/components/dms/getMessageInfo.ts @@ -1,9 +1,3 @@ -import { - AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, -} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -14,12 +8,13 @@ import { toBskyAppUrl, toShortUrl, } from '#/lib/strings/url-helpers' -import type * as bsky from '#/types/bsky' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type UserMessageInfo = { message: string | null sentAt: string - reportableMessage?: ChatBskyConvoDefs.MessageView + reportableMessage?: chat.bsky.convo.defs.MessageView isBlockedMessage: boolean } @@ -36,7 +31,7 @@ export function isDidBlockedInConvo({ primaryProfile, }: { did: string | undefined - members: ChatBskyActorDefs.ProfileViewBasic[] + members: chat.bsky.actor.defs.ProfileViewBasic[] primaryProfile?: bsky.profile.AnyProfileView }): boolean { if (!did) return false @@ -53,12 +48,12 @@ export function getMessageInfo({ primaryProfile, i18n, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentAccountDid: string | undefined primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserMessageInfo | null { - if (!ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { + if (!bsky.isType(chat.bsky.convo.defs.messageView, convo.lastMessage)) { return null } @@ -67,7 +62,7 @@ export function getMessageInfo({ const senderDid = lastMessage.sender?.did const sender = convo.members.find(m => m.did === senderDid) const name = sender ? createSanitizedDisplayName(sender) : null - const isGroup = ChatBskyConvoDefs.isGroupConvo(convo.kind) + const isGroup = bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind) const reportableMessage = isFromMe ? undefined : lastMessage const isBlockedMessage = isDidBlockedInConvo({ @@ -105,10 +100,10 @@ export function getMessageInfo({ msg`(contains embedded content)`, ) - if (AppBskyEmbedRecord.isView(lastMessage.embed)) { + if (bsky.isType(app.bsky.embed.record.view, lastMessage.embed)) { const embed = lastMessage.embed - if (AppBskyEmbedRecord.isViewRecord(embed.record)) { + if (bsky.isType(app.bsky.embed.record.viewRecord, embed.record)) { const record = embed.record const path = postUriToRelativePath(record.uri, { handle: record.author.handle, @@ -119,7 +114,7 @@ export function getMessageInfo({ } else { message = prefix(defaultEmbeddedContentMessage) } - } else if (ChatBskyEmbedJoinLink.isView(lastMessage.embed)) { + } else if (bsky.isType(chat.bsky.embed.joinLink.view, lastMessage.embed)) { message = prefix(i18n._(msg`(chat invite link)`)) } else { message = prefix(defaultEmbeddedContentMessage) diff --git a/src/components/dms/getReactionInfo.ts b/src/components/dms/getReactionInfo.ts index 307eebdea7..477c44fbc3 100644 --- a/src/components/dms/getReactionInfo.ts +++ b/src/components/dms/getReactionInfo.ts @@ -1,10 +1,10 @@ -import {ChatBskyConvoDefs} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {isDidBlockedInConvo} from '#/components/dms/getMessageInfo' -import type * as bsky from '#/types/bsky' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type UserReactionInfo = { message: string @@ -18,12 +18,17 @@ export function getReactionInfo({ primaryProfile, i18n, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentAccountDid: string | undefined primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserReactionInfo | null { - if (!ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) { + if ( + !bsky.isType( + chat.bsky.convo.defs.messageAndReactionView, + convo.lastReaction, + ) + ) { return null } diff --git a/src/components/dms/getSystemMessageInfo.ts b/src/components/dms/getSystemMessageInfo.ts index 756f178566..2262a3eb22 100644 --- a/src/components/dms/getSystemMessageInfo.ts +++ b/src/components/dms/getSystemMessageInfo.ts @@ -1,4 +1,3 @@ -import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' import {type MessageDescriptor} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -15,11 +14,13 @@ import { Unlock_Stroke2_Corner2_Rounded as UnlockIcon, } from '#/components/icons/Lock' import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type SystemMessageAction = | { kind: 'profile' - profile: ChatBskyActorDefs.ProfileViewBasic + profile: chat.bsky.actor.defs.ProfileViewBasic displayName: string } | {kind: 'inviteLink'} @@ -31,8 +32,8 @@ export type SystemMessageInfo = { } function getProfileAction( - user: ChatBskyConvoDefs.SystemMessageReferredUser, - relatedProfiles: Map, + user: chat.bsky.convo.defs.SystemMessageReferredUser, + relatedProfiles: Map, ): Extract | null { const profile = relatedProfiles.get(user.did) if (!profile) return null @@ -44,11 +45,11 @@ function getProfileAction( } export function getSystemMessageInfo( - data: ChatBskyConvoDefs.SystemMessageView['data'], - relatedProfiles: Map, + data: chat.bsky.convo.defs.SystemMessageView['data'], + relatedProfiles: Map, opts = {short: false}, ): SystemMessageInfo | null { - if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) { + if (bsky.isType(chat.bsky.convo.defs.systemMessageDataAddMember, data)) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, @@ -61,7 +62,9 @@ export function getSystemMessageInfo( : msg`Someone was added to the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataRemoveMember(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataRemoveMember, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, @@ -74,7 +77,9 @@ export function getSystemMessageInfo( : msg`Someone was removed from the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataMemberJoin(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberJoin, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, @@ -87,7 +92,9 @@ export function getSystemMessageInfo( : msg`Someone joined the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataMemberLeave(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberLeave, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, @@ -100,13 +107,24 @@ export function getSystemMessageInfo( : msg`Someone left the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvo(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataLockConvo, data) + ) { return {Icon: LockIcon, message: msg`Chat locked`} - } else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataUnlockConvo, data) + ) { return {Icon: UnlockIcon, message: msg`Chat unlocked`} - } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) { + } else if ( + bsky.isType( + chat.bsky.convo.defs.systemMessageDataLockConvoPermanently, + data, + ) + ) { return {Icon: LockIcon, message: msg`Chat ended`} - } else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEditGroup, data) + ) { return { Icon: PencilIcon, message: @@ -114,25 +132,33 @@ export function getSystemMessageInfo( ? msg`Chat title changed to ${data.newName}` : msg`Chat title changed`, } - } else if (ChatBskyConvoDefs.isSystemMessageDataCreateJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataCreateJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link created`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataEditJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEditJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link edited`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataEnableJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEnableJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link enabled`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataDisableJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataDisableJoinLink, data) + ) { return { Icon: ChainLinkBrokenIcon, message: msg`Invite link disabled`, diff --git a/src/components/dms/replyPreview.ts b/src/components/dms/replyPreview.ts index 53c78b2c46..e33dd75bae 100644 --- a/src/components/dms/replyPreview.ts +++ b/src/components/dms/replyPreview.ts @@ -1,13 +1,8 @@ -import { - AppBskyEmbedExternal, - AppBskyEmbedRecord, - type ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, - ChatBskyGroupDefs, -} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' /** * Describes the embed of a quoted message that has no text of its own, so the @@ -21,13 +16,13 @@ type ReplyEmbedSummary = | {type: 'unknown'} function summarizeReplyEmbed( - embed: ChatBskyConvoDefs.MessageView['embed'], + embed: chat.bsky.convo.defs.MessageView['embed'], ): ReplyEmbedSummary { - if (!AppBskyEmbedRecord.isView(embed)) return {type: 'unknown'} + if (!bsky.isType(app.bsky.embed.record.view, embed)) return {type: 'unknown'} const {record} = embed - if (AppBskyEmbedRecord.isViewRecord(record)) { + if (bsky.isType(app.bsky.embed.record.viewRecord, record)) { const inner = record.embeds?.[0] - if (AppBskyEmbedExternal.isView(inner)) { + if (bsky.isType(app.bsky.embed.external.view, inner)) { return {type: 'external', uri: inner.external.uri} } return {type: 'post'} @@ -45,25 +40,32 @@ function summarizeReplyEmbed( * callers can render it in a muted/italic style. */ export function useReplyPreviewText(): ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, ) => {text: string; subtle: boolean} { const {t: l} = useLingui() - return (message: ChatBskyConvoDefs.MessageView) => { + return (message: chat.bsky.convo.defs.MessageView) => { const text = message.text if (text.trim()) { return {text, subtle: false} } - if (ChatBskyEmbedJoinLink.isView(message.embed)) { + if (bsky.isType(chat.bsky.embed.joinLink.view, message.embed)) { const {joinLinkPreview} = message.embed - if (ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, joinLinkPreview) + ) { return { text: `${BSKY_APP_HOST}/chat/${joinLinkPreview.code}`, subtle: true, } } - if (ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType( + chat.bsky.group.defs.disabledJoinLinkPreviewView, + joinLinkPreview, + ) + ) { return { text: l({ message: '(disabled chat invite link)', @@ -72,7 +74,12 @@ export function useReplyPreviewText(): ( subtle: true, } } - if (ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType( + chat.bsky.group.defs.invalidJoinLinkPreviewView, + joinLinkPreview, + ) + ) { return { text: l({ message: '(invalid chat invite link)', diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 683b421275..8b68270bd6 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,4 +1,3 @@ -import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' @@ -78,7 +77,7 @@ export function localDateString(date: Date) { } export function hasAlreadyReacted( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, emoji: string, ): boolean { @@ -99,9 +98,9 @@ export function hasAlreadyReacted( * already render anonymously ("Someone reacted"). */ export function filterBlockedReactions( - reactions: ChatBskyConvoDefs.ReactionView[] | undefined, - relatedProfiles: Map, -): ChatBskyConvoDefs.ReactionView[] { + reactions: chat.bsky.convo.defs.ReactionView[] | undefined, + relatedProfiles: Map, +): chat.bsky.convo.defs.ReactionView[] { if (!reactions) return [] return reactions.filter(reaction => { const profile = relatedProfiles.get(reaction.sender.did) @@ -110,7 +109,7 @@ export function filterBlockedReactions( } export function hasReachedReactionLimit( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, ): boolean { if (!message.reactions) { @@ -174,25 +173,25 @@ export function canReact({ return true } -export type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & { +export type GroupConvoMember = chat.bsky.actor.defs.ProfileViewBasic & { // can be missing if account deleted - kind?: $Typed + kind?: $Typed } -export type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & { - kind: $Typed +export type DirectConvoMember = chat.bsky.actor.defs.ProfileViewBasic & { + kind: $Typed } -export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( +export type ConvoWithDetails = {view: chat.bsky.convo.defs.ConvoView} & ( | { kind: 'group' - details: $Typed + details: $Typed primaryMember?: GroupConvoMember // the owner - may have left, thus optional members: Array } | { kind: 'direct' - details: $Typed + details: $Typed primaryMember: DirectConvoMember // the other user members: Array } @@ -203,7 +202,7 @@ export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( * and enforces the correct type for convo members. */ export function parseConvoView( - convoView: ChatBskyConvoDefs.ConvoView, + convoView: chat.bsky.convo.defs.ConvoView, ownDid: string | undefined, ): ConvoWithDetails | null { if (bsky.isType(chat.bsky.convo.defs.groupConvo, convoView.kind)) { diff --git a/src/components/feeds/PostFeedVideoGridRow.tsx b/src/components/feeds/PostFeedVideoGridRow.tsx index 53bc99d2f0..54c79c0afe 100644 --- a/src/components/feeds/PostFeedVideoGridRow.tsx +++ b/src/components/feeds/PostFeedVideoGridRow.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {AppBskyEmbedVideo} from '@atproto/api' import {type FeedPostSliceItem} from '#/state/queries/post-feed' import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' @@ -10,6 +9,8 @@ import { VideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' export function PostFeedVideoGridRow({ items: slices, @@ -21,7 +22,7 @@ export function PostFeedVideoGridRow({ const ax = useAnalytics() const gutters = useGutters(['base', 'base', 0, 'base']) const posts = slices - .filter(slice => AppBskyEmbedVideo.isView(slice.post.embed)) + .filter(slice => bsky.isType(app.bsky.embed.video.view, slice.post.embed)) .map(slice => ({ post: slice.post, moderation: slice.moderation, diff --git a/src/components/images/AutoSizedImage.tsx b/src/components/images/AutoSizedImage.tsx index fdc7d1988a..0842d2ebde 100644 --- a/src/components/images/AutoSizedImage.tsx +++ b/src/components/images/AutoSizedImage.tsx @@ -5,7 +5,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -18,6 +17,7 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' export function ConstrainedImage({ aspectRatio, @@ -71,7 +71,7 @@ export function AutoSizedImage({ onContainerRef, onDimsChange, }: { - image: AppBskyEmbedImages.ViewImage + image: app.bsky.embed.images.ViewImage crop?: 'none' | 'square' | 'constrained' onPress?: ( containerRef: AnimatedRef, diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index c87957503f..aad2f86b29 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -14,7 +14,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {Trans, useLingui} from '@lingui/react/macro' import debounce from 'lodash.debounce' @@ -41,12 +40,13 @@ import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID, IS_WEB} from '#/env' +import {type app} from '#/lexicons' export * from './const' export * from './maybeApplyGalleryOffsetStyles' interface GalleryProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], @@ -404,7 +404,7 @@ function GalleryImage({ onPreviewPress, }: { contentHeight: number - image: AppBskyEmbedImages.ViewImage + image: app.bsky.embed.images.ViewImage index: number imageCount: number onWidthChange: (index: number, width: number) => void diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts index 860960191c..6c1bf8beb1 100644 --- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {unique} from '#/lib/moderation' @@ -17,7 +16,7 @@ export function maybeApplyGalleryOffsetStyles( modui, additionalCauses, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView modui: ModerationUI additionalCauses?: ModerationCause[] | AppModerationCause[] }, diff --git a/src/components/images/ImageLayoutGrid.tsx b/src/components/images/ImageLayoutGrid.tsx index f69de63bd1..e01e853447 100644 --- a/src/components/images/ImageLayoutGrid.tsx +++ b/src/components/images/ImageLayoutGrid.tsx @@ -1,15 +1,15 @@ import {useRef} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' -import {type AppBskyEmbedImages} from '@atproto/api' import {atoms as a, useBreakpoints} from '#/alf' import {type Dimensions} from '#/components/Lightbox/types' import {type PostEmbedViewContext} from '#/components/Post/Embed/types' +import {type app} from '#/lexicons' import {GalleryItem} from './ImageLayoutGridItem' interface ImageLayoutGridProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], @@ -45,7 +45,7 @@ export function ImageLayoutGrid({ } interface ImageLayoutGridInnerProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], diff --git a/src/components/images/ImageLayoutGridItem.tsx b/src/components/images/ImageLayoutGridItem.tsx index 8a3202df37..69f3c7a5bf 100644 --- a/src/components/images/ImageLayoutGridItem.tsx +++ b/src/components/images/ImageLayoutGridItem.tsx @@ -1,7 +1,6 @@ import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {type AnimatedRef} from 'react-native-reanimated' import {Image, type ImageStyle} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -14,11 +13,12 @@ import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {type PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type EventFunction = (index: number) => void interface Props { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] index: number onPress?: ( index: number, diff --git a/src/components/intents/GroupChatJoinDialog.tsx b/src/components/intents/GroupChatJoinDialog.tsx index ae430b9364..bc928f09f4 100644 --- a/src/components/intents/GroupChatJoinDialog.tsx +++ b/src/components/intents/GroupChatJoinDialog.tsx @@ -1,10 +1,5 @@ import {useEffect} from 'react' import {View} from 'react-native' -import { - ChatBskyGroupDefs, - ChatBskyGroupRequestJoin, - ChatBskyGroupWithdrawJoinRequest, -} from '@atproto/api' import {moderateProfile} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -16,6 +11,7 @@ import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { @@ -49,7 +45,8 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' -import {toLex} from '#/types/bsky' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ProfileBadges} from '../ProfileBadges' export function GroupChatJoinDialog() { @@ -148,32 +145,26 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { let errorMessage = l`Failed to join the group chat. Please try again.` if (isNetworkError(error)) { errorMessage = l`There was a problem with your internet connection, please try again` - } else if (error instanceof ChatBskyGroupRequestJoin.ConvoLockedError) { + } else if (getErrorName(error) === 'ConvoLocked') { errorMessage = l`This conversation is locked.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.FollowRequiredError - ) { + } else if (getErrorName(error) === 'FollowRequired') { errorMessage = l`Only followers can join this group chat.` - } else if (error instanceof ChatBskyGroupRequestJoin.InvalidCodeError) { + } else if (getErrorName(error) === 'InvalidCode') { errorMessage = l`Invalid group chat code.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.LinkDisabledError - ) { + } else if (getErrorName(error) === 'LinkDisabled') { errorMessage = l`This invite link has been disabled.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError - ) { + } else if (getErrorName(error) === 'MemberLimitReached') { errorMessage = l`The member limit has been reached.` const preview = data?.joinLinkPreviews[0] if ( - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) && preview.convo?.id ) { ax.metric('groupchat:join:memberLimitReached', { convoId: preview.convo.id, }) } - } else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) { + } else if (getErrorName(error) === 'UserKicked') { errorMessage = l`You have been previously removed from this group and can’t join it using this link.` } Toast.show(errorMessage) @@ -194,10 +185,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { let errorMessage = l`Failed to rescind your request. Please try again.` if (isNetworkError(error)) { errorMessage = l`There was a problem with your internet connection, please try again` - } else if ( - error instanceof - ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError - ) { + } else if (getErrorName(error) === 'InvalidJoinRequest') { errorMessage = l`Invalid rescind request.` } Toast.show(errorMessage) @@ -253,7 +241,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { const joinLinkPreview = data.joinLinkPreviews[0] - if (!ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { + if (!bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, joinLinkPreview)) { return ( <> @@ -409,7 +397,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { true, // TODO(phase4): drop toLex once join link preview emits #/lexicons views moderateProfile( - toLex(joinLinkPreview.owner), + bsky.toLex(joinLinkPreview.owner), moderationOpts, ).ui('displayName'), )} diff --git a/src/components/interstitials/TrendingVideos.tsx b/src/components/interstitials/TrendingVideos.tsx index abbbcd56b3..c719f6c66c 100644 --- a/src/components/interstitials/TrendingVideos.tsx +++ b/src/components/interstitials/TrendingVideos.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo} from 'react' import {ScrollView, View} from 'react-native' -import {AppBskyEmbedVideo} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -22,6 +21,8 @@ import { CompactVideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const CARD_WIDTH = 108 @@ -154,7 +155,7 @@ function VideoCards({ .flatMap(page => page.slices) .map(slice => slice.items[0]) .filter(Boolean) - .filter(item => AppBskyEmbedVideo.isView(item.post.embed)) + .filter(item => bsky.isType(app.bsky.embed.video.view, item.post.embed)) .slice(0, 8) }, [data]) diff --git a/src/components/moderation/BlockDialog.tsx b/src/components/moderation/BlockDialog.tsx index a575cd7433..d00a823171 100644 --- a/src/components/moderation/BlockDialog.tsx +++ b/src/components/moderation/BlockDialog.tsx @@ -1,14 +1,10 @@ import {useState} from 'react' import {View} from 'react-native' -import { - type ChatBskyConvoDefs, - ChatBskyConvoLeaveConvo, - ChatBskyGroupRemoveMembers, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {type Shadow} from '#/state/cache/types' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' @@ -27,9 +23,10 @@ import {parseConvoView} from '#/components/dms/util' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {type AnyProfileView} from '#/types/bsky/profile' -type Item = ChatBskyConvoDefs.ConvoView +type Item = chat.bsky.convo.defs.ConvoView type BlockDialogProps = { control: DialogControlProps @@ -254,7 +251,7 @@ function MutualGroupChat({ onOptimisticallyRemoveConvo, onRestoreConvo, }: { - view: ChatBskyConvoDefs.ConvoView + view: chat.bsky.convo.defs.ConvoView profileDid: string currentConvoId?: string onOptimisticallyRemoveConvo: (convoId: string) => void @@ -282,11 +279,9 @@ function MutualGroupChat({ let errorMessage = l`Could not leave chat.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { + } else if (getErrorName(error) === 'InvalidConvo') { errorMessage = l`Chat not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { + } else if (getErrorName(error) === 'OwnerCannotLeave') { errorMessage = l`Chat owners cannot leave a group chat.` } Toast.show(errorMessage, {type: 'error'}) @@ -308,13 +303,9 @@ function MutualGroupChat({ let errorMessage = l`Could not remove member.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupRemoveMembers.InvalidConvoError - ) { + } else if (getErrorName(error) === 'InvalidConvo') { errorMessage = l`Chat not found.` - } else if ( - error instanceof ChatBskyGroupRemoveMembers.InsufficientRoleError - ) { + } else if (getErrorName(error) === 'InsufficientRole') { errorMessage = l`You must be a chat owner to remove a member.` } Toast.show(errorMessage, {type: 'error'}) diff --git a/src/components/moderation/LabelsOnMe.tsx b/src/components/moderation/LabelsOnMe.tsx index 85cb1e4a3a..ee56d1bae2 100644 --- a/src/components/moderation/LabelsOnMe.tsx +++ b/src/components/moderation/LabelsOnMe.tsx @@ -1,5 +1,4 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural} from '@lingui/react/macro' @@ -18,13 +17,14 @@ import { LabelsOnMeDialog, useLabelsOnMeDialogControl, } from '#/components/moderation/LabelsOnMeDialog' +import {type com} from '#/lexicons' export function LabelsOnMe({ labels, size, style, }: { - labels: ComAtprotoLabelDefs.Label[] | undefined + labels: com.atproto.label.defs.Label[] | undefined size?: ButtonSize style?: StyleProp }) { diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx index 963e4b762a..a12e129925 100644 --- a/src/components/moderation/LabelsOnMeDialog.tsx +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,13 +16,14 @@ import * as Dialog from '#/components/Dialog' import {InlineLinkText} from '#/components/Link' import {AppealForm} from '#/components/moderation/AppealForm' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' import {Divider} from '../Divider' export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' export interface LabelsOnMeDialogProps { control: Dialog.DialogOuterProps['control'] - labels: ComAtprotoLabelDefs.Label[] + labels: com.atproto.label.defs.Label[] /** * Whether the labels are being shown on the user's account or on a post. * With `content`, the list may include account-level labels, which get a @@ -47,7 +47,7 @@ function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { const {_} = useLingui() const {currentAccount} = useSession() const [appealingLabel, setAppealingLabel] = useState< - ComAtprotoLabelDefs.Label | undefined + com.atproto.label.defs.Label | undefined >(undefined) const {labels} = props const isAccount = props.type === 'account' @@ -118,7 +118,7 @@ function Label({ control, onPressAppeal, }: { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label isSelfLabel: boolean /** * Call out that the label applies to the whole account, for contexts that @@ -126,7 +126,7 @@ function Label({ */ showAccountCallout?: boolean control: Dialog.DialogOuterProps['control'] - onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void + onPressAppeal: (label: com.atproto.label.defs.Label) => void }) { const t = useTheme() const {_} = useLingui() diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx index 2bfd40705b..cd9e841f5f 100644 --- a/src/components/moderation/PostAlerts.tsx +++ b/src/components/moderation/PostAlerts.tsx @@ -1,5 +1,4 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, type ComAtprotoLabelDefs} from '@atproto/api' import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -16,6 +15,7 @@ import { useLabelsOnMeDialogControl, } from '#/components/moderation/LabelsOnMeDialog' import * as Pills from '#/components/Pills' +import {type app, type com} from '#/lexicons' import {toLex} from '#/types/bsky' export function PostAlerts({ @@ -25,7 +25,7 @@ export function PostAlerts({ style, additionalCauses, }: { - post?: AppBskyFeedDefs.PostView + post?: app.bsky.feed.defs.PostView modui: ModerationUI /** * Expanded views (e.g. the thread anchor post) render larger pills and @@ -49,7 +49,7 @@ export function PostAlerts({ * author's only entry point to appeal labels on their content. */ const isOwnPost = !!post && post.author.did === currentAccount?.did - const allLabels: ComAtprotoLabelDefs.Label[] = + const allLabels: com.atproto.label.defs.Label[] = isOwnPost && view === 'expanded' ? [ ...(post.labels ?? []), @@ -136,7 +136,7 @@ function AdditionalLabels({ size, hasPrecedingPills, }: { - labels: ComAtprotoLabelDefs.Label[] + labels: com.atproto.label.defs.Label[] size?: Pills.CommonProps['size'] /** * The compact "+n" syntax only makes sense as a continuation of other diff --git a/src/components/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index 54d4acf945..874345e084 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -7,7 +7,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -25,13 +24,14 @@ import { useModerationDetailsDialogControl, } from '#/components/moderation/ModerationDetailsDialog' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' interface Props extends React.ComponentProps { disabled: boolean iconSize: number iconStyles: StyleProp modui: ModerationUI - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic interpretFilterAsBlur?: boolean hiderStyle?: StyleProp } diff --git a/src/components/moderation/ReportDialog/types.ts b/src/components/moderation/ReportDialog/types.ts index 57ed3cd279..9b66cb72ce 100644 --- a/src/components/moderation/ReportDialog/types.ts +++ b/src/components/moderation/ReportDialog/types.ts @@ -1,17 +1,12 @@ -import { - type $Typed, - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type ChatBskyConvoDefs, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import type * as Dialog from '#/components/Dialog' +import {type app, type chat} from '#/lexicons' export type ReportSubjectConvoMessage = { view: 'convo' | 'message' convoId: string - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView } export type ReportSubjectConvo = { @@ -20,14 +15,14 @@ export type ReportSubjectConvo = { } export type ReportSubject = - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed | ReportSubjectConvoMessage | ReportSubjectConvo diff --git a/src/components/verification/VerificationRemovePrompt.tsx b/src/components/verification/VerificationRemovePrompt.tsx index 96b119ceb1..5eeafecb3a 100644 --- a/src/components/verification/VerificationRemovePrompt.tsx +++ b/src/components/verification/VerificationRemovePrompt.tsx @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -8,6 +7,7 @@ import {useVerificationsRemoveMutation} from '#/state/queries/verification/useVe import {type DialogControlProps} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export {useDialogControl as usePromptControl} from '#/components/Dialog' @@ -20,7 +20,7 @@ export function VerificationRemovePrompt({ }: { control: DialogControlProps profile: bsky.profile.AnyProfileView - verifications: AppBskyActorDefs.VerificationView[] + verifications: app.bsky.actor.defs.VerificationView[] onConfirm?: () => void }) { const {_} = useLingui() diff --git a/src/components/verification/VerificationsDialog.tsx b/src/components/verification/VerificationsDialog.tsx index cb24b182a5..5f3562ee6e 100644 --- a/src/components/verification/VerificationsDialog.tsx +++ b/src/components/verification/VerificationsDialog.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,6 +20,7 @@ import {Text} from '#/components/Typography' import {type FullVerificationState} from '#/components/verification' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export {useDialogControl} from '#/components/Dialog' @@ -180,7 +180,7 @@ function VerifierCard({ subject, outerDialogControl, }: { - verification: AppBskyActorDefs.VerificationView + verification: app.bsky.actor.defs.VerificationView subject: bsky.profile.AnyProfileView outerDialogControl: Dialog.DialogControlProps }) { diff --git a/src/env/common.ts b/src/env/common.ts index 5d8d89aace..bcda2e4982 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -1,4 +1,4 @@ -import {type Did} from '@atproto/api' +import {type DidString} from '@atproto/syntax' import packageJson from '#/../package.json' @@ -75,14 +75,16 @@ export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || '' /** * The DID of the Bluesky appview to proxy to */ -export const BLUESKY_PROXY_DID: Did = - process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app' +export const BLUESKY_PROXY_DID: DidString = + (process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID as DidString) || + 'did:web:api.bsky.app' /** * The DID of the chat service to proxy to */ -export const CHAT_PROXY_DID: Did = - process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat' +export const CHAT_PROXY_DID: DidString = + (process.env.EXPO_PUBLIC_CHAT_PROXY_DID as DidString) || + 'did:web:api.bsky.chat' /** * Metrics API host diff --git a/src/features/liveEvents/preferences.ts b/src/features/liveEvents/preferences.ts index dd791327ff..bd8cbbd645 100644 --- a/src/features/liveEvents/preferences.ts +++ b/src/features/liveEvents/preferences.ts @@ -14,7 +14,7 @@ import { type LiveEventFeed, type LiveEventFeedMetricContext, } from '#/features/liveEvents/types' -import {app} from '#/lexicons' +import {type app} from '#/lexicons' export type LiveEventPreferencesAction = Parameters< typeof updateLiveEventPreferences diff --git a/src/features/liveNow/components/GoLiveDisabledDialog.tsx b/src/features/liveNow/components/GoLiveDisabledDialog.tsx index e25f7ab5c9..e3290d6ba5 100644 --- a/src/features/liveNow/components/GoLiveDisabledDialog.tsx +++ b/src/features/liveNow/components/GoLiveDisabledDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {api} from '@bsky.app/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -15,7 +14,7 @@ import * as Dialog from '#/components/Dialog' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' -import {com, tools} from '#/lexicons' +import {type app, com, tools} from '#/lexicons' import {toLex} from '#/types/bsky' export function GoLiveDisabledDialog({ @@ -23,7 +22,7 @@ export function GoLiveDisabledDialog({ status, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { return ( @@ -38,7 +37,7 @@ export function DialogInner({ status, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { const {_} = useLingui() const {currentAccount} = useSession() diff --git a/src/features/liveNow/index.tsx b/src/features/liveNow/index.tsx index 2341a211d1..24aeecc309 100644 --- a/src/features/liveNow/index.tsx +++ b/src/features/liveNow/index.tsx @@ -332,7 +332,7 @@ export function useUpsertLiveStatusMutation( $type: 'app.bsky.actor.defs#statusView', status: 'app.bsky.actor.status#live', isActive: true, - expiresAt: expiresAt.toISOString(), + expiresAt: expiresAt.toISOString() as DatetimeString, embed: record.embed && image ? { @@ -340,7 +340,7 @@ export function useUpsertLiveStatusMutation( external: { ...record.embed.external, $type: 'app.bsky.embed.external#viewExternal', - thumb: image, + thumb: image as UriString, }, } : undefined, diff --git a/src/lib/api/computeCid.ts b/src/lib/api/computeCid.ts index 32c97a5225..894f7a8c03 100644 --- a/src/lib/api/computeCid.ts +++ b/src/lib/api/computeCid.ts @@ -2,7 +2,7 @@ import {sha256} from 'js-sha256' import {CID} from 'multiformats/cid' import * as Hasher from 'multiformats/hashes/hasher' -import {app} from '#/lexicons' +import {type app} from '#/lexicons' /* * Client-side CID computation for post records, extracted from the post diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts index 335bf28c84..374c847d6f 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 @@ -68,17 +61,17 @@ export class FeedViewPostsSlice { this.isFallbackMarker = true return } - if ( - !AppBskyFeedPost.isRecord(post.record) || - !bsky.validate(post.record, AppBskyFeedPost.validateRecord) - ) { + if (!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 +93,17 @@ 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.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 +116,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 +134,8 @@ 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.matches(app.bsky.feed.post, root.record) ) { this.isOrphan = true return @@ -167,14 +158,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 +186,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 +199,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 +505,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/constants.ts b/src/lib/constants.ts index 80d8639ff2..89dff6145b 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,8 +1,9 @@ import {type Insets, Platform} from 'react-native' -import {type AppBskyActorDefs, BSKY_LABELER_DID} from '@atproto/api' +import {api} from '@bsky.app/sdk' import {type ProxyHeaderValue} from '#/state/session/agent' -import {BLUESKY_PROXY_DID, CHAT_PROXY_DID, IS_DEV} from '#/env' +import {BLUESKY_PROXY_DID, IS_DEV} from '#/env' +import {type app} from '#/lexicons' export const LOCAL_DEV_SERVICE = Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' @@ -173,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] @@ -246,12 +247,8 @@ export const BLUESKY_PROXY_HEADER = { }, } -export const DM_SERVICE_HEADERS = { - 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, -} - export const BLUESKY_MOD_SERVICE_HEADERS = { - 'atproto-proxy': `${BSKY_LABELER_DID}#atproto_labeler`, + 'atproto-proxy': `${api.moderation.did}#atproto_labeler`, } export const BLUESKY_NOTIF_SERVICE_HEADERS = { diff --git a/src/lib/demo.ts b/src/lib/demo.ts index 5ead62c9d3..826049045a 100644 --- a/src/lib/demo.ts +++ b/src/lib/demo.ts @@ -1,11 +1,13 @@ -import {type AppBskyFeedGetFeed} from '@atproto/api' +import {toDatetimeString} from '@atproto/syntax' import {subDays, subMinutes} from 'date-fns' +import {type app} from '#/lexicons' + const DID = `did:plc:z72i7hdynmk6r22z27h6tvur` const NOW = new Date() -const POST_1_DATE = subMinutes(NOW, 2).toISOString() -const POST_2_DATE = subMinutes(NOW, 4).toISOString() -const POST_3_DATE = subMinutes(NOW, 5).toISOString() +const POST_1_DATE = toDatetimeString(subMinutes(NOW, 2)) +const POST_2_DATE = toDatetimeString(subMinutes(NOW, 4)) +const POST_3_DATE = toDatetimeString(subMinutes(NOW, 5)) export const DEMO_FEED = { feed: [ @@ -31,7 +33,7 @@ export const DEMO_FEED = { issuer: DID, uri: `at://${DID}/app.bsky.graph.verification/post1`, isValid: true, - createdAt: subDays(NOW, 11).toISOString(), + createdAt: toDatetimeString(subDays(NOW, 11)), }, ], verifiedStatus: 'valid', @@ -197,6 +199,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..3689d608e0 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -1,6 +1,6 @@ import {useEffect} from 'react' import * as Notifications from 'expo-notifications' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {useLingui} from '@lingui/react/macro' import {CommonActions, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/lib/hooks/usePostViewTracking.ts b/src/lib/hooks/usePostViewTracking.ts index bbe8bb9f24..28fe8ba448 100644 --- a/src/lib/hooks/usePostViewTracking.ts +++ b/src/lib/hooks/usePostViewTracking.ts @@ -1,7 +1,7 @@ import {useCallback, useRef} from 'react' -import {type AppBskyFeedDefs} from '@atproto/api' import {type Metrics, useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' /** * Hook that returns a callback to track post:view events. @@ -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 ea0882e0eb..ab95cd9b35 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -1,9 +1,8 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {LINK_META_PROXY} from '#/lib/constants' import {getGiphyMetaUri} from '#/lib/strings/embed-player' import {parseStarterPackUri} from '#/lib/strings/starter-pack' import {type SessionAgent} from '#/state/session' +import {type app} from '#/lexicons' import {isBskyAppUrl} from '../strings/url-helpers' export enum LikelyType { @@ -27,8 +26,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/media/video/upload.ts b/src/lib/media/video/upload.ts index 9cc2194435..ec7f972c55 100644 --- a/src/lib/media/video/upload.ts +++ b/src/lib/media/video/upload.ts @@ -11,7 +11,7 @@ import { type VideoUploadTransport, } from '#/lib/media/video/types' import {Features, features} from '#/analytics/features' -import {app} from '#/lexicons' +import {type app} from '#/lexicons' import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' import {createVideoEndpointUrl, mimeToExt} from './util' diff --git a/src/lib/media/video/upload.web.ts b/src/lib/media/video/upload.web.ts index 3042ed0565..2028a346b8 100644 --- a/src/lib/media/video/upload.web.ts +++ b/src/lib/media/video/upload.web.ts @@ -10,7 +10,7 @@ import { type VideoUploadTransport, } from '#/lib/media/video/types' import {Features, features} from '#/analytics/features' -import {app} from '#/lexicons' +import {type app} from '#/lexicons' import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' import {createVideoEndpointUrl, mimeToExt} from './util' diff --git a/src/lib/media/video/util.ts b/src/lib/media/video/util.ts index c57c86b0d7..8d8cb11a72 100644 --- a/src/lib/media/video/util.ts +++ b/src/lib/media/video/util.ts @@ -31,6 +31,17 @@ export function createVideoServiceClient(token: string) { }) } +/** + * An unauthenticated lex {@link Client} scoped to the video service, used for + * public reads like `getJobStatus` polling. Mirrors the old unauthenticated + * `AtpAgent` at `VIDEO_SERVICE`. + */ +export function createTokenlessVideoServiceClient() { + return new Client({ + service: VIDEO_SERVICE, + }) +} + export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) { switch (mimeType) { case 'video/mp4': diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts index 146c940fd0..580b50d881 100644 --- a/src/lib/notifications/notifications.ts +++ b/src/lib/notifications/notifications.ts @@ -2,7 +2,6 @@ import {useCallback, useEffect} from 'react' import {Platform} from 'react-native' import * as Notifications from 'expo-notifications' import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications' -import {type AppBskyNotificationRegisterPush} from '@atproto/api' import debounce from 'lodash.debounce' import { @@ -18,6 +17,7 @@ import BackgroundNotificationHandler from '#/../modules/expo-background-notifica import {useAgeAssurance} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {IS_DEV, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' /** * @private @@ -37,7 +37,7 @@ async function _registerPushToken({ } }) { try { - const payload: AppBskyNotificationRegisterPush.InputSchema = { + const payload: app.bsky.notification.registerPush.$InputBody = { serviceDid: currentAccount.service?.includes('staging') ? PUBLIC_STAGING_APPVIEW_DID : PUBLIC_APPVIEW_DID, diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index c87ccb5a39..e568c66ff6 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -1,6 +1,7 @@ -import {type AppBskyGraphDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {isInvalidHandle} from '#/lib/strings/handles' +import {type app} from '#/lexicons' export function makeProfileLink( info: { @@ -44,8 +45,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/locale/helpers.ts b/src/locale/helpers.ts index 63b09b3141..e91c540e83 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -1,8 +1,9 @@ -import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' import * as bcp47Match from 'bcp-47-match' import lande from 'lande' import {hasProp} from '#/lib/type-guards' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import { AppLanguage, type Language, @@ -61,8 +62,8 @@ function getLocalizedLanguage( } } -export function getPostLanguageTags(post: AppBskyFeedDefs.PostView) { - return AppBskyFeedPost.isRecord(post.record) && +export function getPostLanguageTags(post: app.bsky.feed.defs.PostView) { + return bsky.isType(app.bsky.feed.post, post.record) && hasProp(post.record, 'langs') && Array.isArray(post.record.langs) ? post.record.langs @@ -86,7 +87,7 @@ export function codeToLanguageName(lang2or3: string, appLang: string): string { } export function getPostLanguage( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, ): string | undefined { let candidates: string[] = getPostLanguageTags(post) let postText: string = '' @@ -121,7 +122,7 @@ export function getPostLanguage( } export function isPostInLanguage( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, targetLangs: string[], ): boolean { const lang = getPostLanguage(post) diff --git a/src/screens/Bookmarks.tsx b/src/screens/Bookmarks.tsx index fdeb91777c..c36cb0c0a5 100644 --- a/src/screens/Bookmarks.tsx +++ b/src/screens/Bookmarks.tsx @@ -1,10 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type $Typed, - type AppBskyBookmarkDefs, - AppBskyFeedDefs, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -38,6 +34,8 @@ import * as toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' type Props = NativeStackScreenProps @@ -78,15 +76,15 @@ type ListItem = | { type: 'bookmark' key: string - bookmark: Omit & { - item: $Typed + bookmark: Omit & { + item: $Typed } } | { type: 'bookmarkNotFound' key: string - bookmark: Omit & { - item: $Typed + bookmark: Omit & { + item: $Typed } } @@ -132,7 +130,7 @@ function BookmarksInner() { if (bookmarks.length > 0) { for (const bookmark of bookmarks) { - if (AppBskyFeedDefs.isNotFoundPost(bookmark.item)) { + if (bsky.isType(app.bsky.feed.defs.notFoundPost, bookmark.item)) { i.push({ type: 'bookmarkNotFound', key: bookmark.item.uri, @@ -142,7 +140,7 @@ function BookmarksInner() { }, }) } - if (AppBskyFeedDefs.isPostView(bookmark.item)) { + if (bsky.isType(app.bsky.feed.defs.postView, bookmark.item)) { i.push({ type: 'bookmark', key: bookmark.item.uri, @@ -199,7 +197,7 @@ function BookmarkNotFound({ post, }: { hideTopBorder: boolean - post: $Typed + post: $Typed }) { const t = useTheme() const {_} = useLingui() diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 39c2341f88..10d0c91884 100644 --- a/src/screens/Hashtag.tsx +++ b/src/screens/Hashtag.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -28,12 +27,15 @@ import {InlineLinkText} from '#/components/Link' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {SearchError} from '#/components/SearchError' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' -const renderItem = ({item}: ListRenderItemInfo) => { +const renderItem = ({ + item, +}: ListRenderItemInfo) => { return } -const keyExtractor = (item: AppBskyFeedDefs.PostView, index: number) => { +const keyExtractor = (item: app.bsky.feed.defs.PostView, index: number) => { return `${item.uri}-${index}` } diff --git a/src/screens/List/ListHiddenScreen.tsx b/src/screens/List/ListHiddenScreen.tsx index aea2465d7f..a36e975640 100644 --- a/src/screens/List/ListHiddenScreen.tsx +++ b/src/screens/List/ListHiddenScreen.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -27,12 +26,13 @@ import {Loader} from '#/components/Loader' import {useHider} from '#/components/moderation/Hider' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function ListHiddenScreen({ list, preferences, }: { - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView preferences: UsePreferencesQueryResponse }) { const {_} = useLingui() @@ -43,7 +43,7 @@ export function ListHiddenScreen({ const goBack = useGoBack() const queryClient = useQueryClient() - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isModList = list.purpose === 'app.bsky.graph.defs#modlist' const [isProcessing, setIsProcessing] = useState(false) const listBlockMutation = useListBlockMutation() diff --git a/src/screens/Login/ForgotPasswordForm.tsx b/src/screens/Login/ForgotPasswordForm.tsx index 79fdbc5d6a..244f4f0bef 100644 --- a/src/screens/Login/ForgotPasswordForm.tsx +++ b/src/screens/Login/ForgotPasswordForm.tsx @@ -1,6 +1,5 @@ import {useCallback, useState} from 'react' import {Keyboard, View} from 'react-native' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import * as EmailValidator from 'email-validator' @@ -16,9 +15,10 @@ import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type com} from '#/lexicons' import {FormContainer} from './FormContainer' -type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema +type ServiceDescription = com.atproto.server.describeServer.$OutputBody export const ForgotPasswordForm = ({ error, diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index 27361219ec..624bc66b00 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -1,9 +1,5 @@ import {useRef, useState} from 'react' import {Keyboard, type TextInput, View} from 'react-native' -import { - ComAtprotoServerCreateSession, - type ComAtprotoServerDescribeServer, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {DEFAULT_SERVICE, HITSLOP_10, HITSLOP_20} from '#/lib/constants' @@ -11,6 +7,7 @@ import {useRequestNotificationsPermission} from '#/lib/notifications/notificatio import {cleanError, isNetworkError} from '#/lib/strings/errors' import {createFullHandle} from '#/lib/strings/handles' import {isBlueskyHostedUrl, toNiceHostingUrl} from '#/lib/strings/url-helpers' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' import { @@ -35,11 +32,12 @@ import {createStaticClick, InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type com} from '#/lexicons' import {ConfirmHostingProviderDialog} from './components/ConfirmHostingProviderDialog' import {HostingProviderDialog} from './components/HostingProviderDialog' import {FormContainer} from './FormContainer' -type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema +type ServiceDescription = com.atproto.server.describeServer.$OutputBody export const LoginForm = ({ error, @@ -142,10 +140,7 @@ export const LoginForm = ({ } catch (err) { const errMsg = String(err) setIsProcessing(false) - if ( - err instanceof - ComAtprotoServerCreateSession.AuthFactorTokenRequiredError - ) { + if (getErrorName(err) === 'AuthFactorTokenRequired') { setIsAuthFactorTokenNeeded(true) } else { onAttemptFailed() diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index b7eb113a49..c0bfa156db 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {View} from 'react-native' -import {type ChatBskyActorGetStatus, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import { useFocusEffect, @@ -57,17 +56,18 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {IS_NATIVE, IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import {ChatDisabled} from './components/ChatDisabled' import {ChatListItem} from './components/ChatListItem' import {InboxRequests} from './components/InboxRequests' import {useIsWithinSplitView} from './components/splitView/context' import {splitViewLeftScroll} from './components/splitView/leftColumnScroll' -type ChatStatus = ChatBskyActorGetStatus.OutputSchema +type ChatStatus = chat.bsky.actor.getStatus.$OutputBody type ListItem = { type: 'CONVERSATION' - conversation: ChatBskyConvoDefs.ConvoView + conversation: chat.bsky.convo.defs.ConvoView selected: boolean } diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index 17fa79fe8a..27be5d0c18 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -1,7 +1,6 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {type LayoutChangeEvent, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {ChatBskyConvoDefs} from '@atproto/api' import { ScrollEdgeEffect, ScrollEdgeEffectProvider, @@ -46,7 +45,8 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {Error} from '#/components/Error' import * as Layout from '#/components/Layout' import {IS_LIQUID_GLASS} from '#/env' -import {toLex} from '#/types/bsky' +import {chat} from '#/lexicons' +import {isType, toLex} from '#/types/bsky' import {ChatDisabled} from './components/ChatDisabled' import {ChatEnded} from './components/ChatEnded' import {ChatLocked} from './components/ChatLocked' @@ -180,7 +180,8 @@ function InnerReady({ const emailDialogControl = useEmailDialogControl() const unreadRequestCount = - convo?.kind === 'group' && ChatBskyConvoDefs.isGroupConvo(convo.view.kind) + convo?.kind === 'group' && + isType(chat.bsky.convo.defs.groupConvo, convo.view.kind) ? (convo.view.kind.unreadJoinRequestCount ?? 0) : 0 const {mutate: markJoinRequestsRead} = useMarkJoinRequestsRead(convo?.view.id) diff --git a/src/screens/Messages/Inbox.tsx b/src/screens/Messages/Inbox.tsx index f6b4216b2c..30e06e3ab2 100644 --- a/src/screens/Messages/Inbox.tsx +++ b/src/screens/Messages/Inbox.tsx @@ -1,10 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - ChatBskyConvoDefs, - type ChatBskyConvoListConvoRequests, - ChatBskyGroupDefs, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useFocusEffect, useNavigation} from '@react-navigation/native' import { @@ -44,6 +39,8 @@ import {ListFooter} from '#/components/Lists' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {IncomingRequestListItem} from './components/IncomingRequestListItem' import {OutgoingRequestListItem} from './components/OutgoingRequestListItem' import {useIsWithinSplitView} from './components/splitView/context' @@ -51,8 +48,8 @@ import {useIsWithinSplitView} from './components/splitView/context' type Props = NativeStackScreenProps type RequestItem = - | {type: 'incoming'; view: ChatBskyConvoDefs.ConvoView} - | {type: 'outgoing'; view: ChatBskyGroupDefs.JoinRequestConvoView} + | {type: 'incoming'; view: chat.bsky.convo.defs.ConvoView} + | {type: 'outgoing'; view: chat.bsky.group.defs.JoinRequestConvoView} export function MessagesInboxScreen(props: Props) { const {t: l} = useLingui() @@ -75,9 +72,11 @@ export function MessagesInboxScreenInner({}: Props) { const items: RequestItem[] = [] for (const page of data.pages) { for (const item of page.requests) { - if (ChatBskyConvoDefs.isConvoView(item)) { + if (bsky.isType(chat.bsky.convo.defs.convoView, item)) { items.push({type: 'incoming', view: item}) - } else if (ChatBskyGroupDefs.isJoinRequestConvoView(item)) { + } else if ( + bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item) + ) { items.push({type: 'outgoing', view: item}) } } @@ -112,7 +111,7 @@ function RequestList({ conversations, }: { listConvosQuery: UseInfiniteQueryResult< - InfiniteData, + InfiniteData, Error > conversations: RequestItem[] diff --git a/src/screens/Messages/JoinRequest.tsx b/src/screens/Messages/JoinRequest.tsx index d9593b11f5..4c9adf1ce0 100644 --- a/src/screens/Messages/JoinRequest.tsx +++ b/src/screens/Messages/JoinRequest.tsx @@ -1,7 +1,6 @@ import {useEffect} from 'react' import {View} from 'react-native' import {ImageBackground} from 'expo-image' -import {ChatBskyGroupDefs} from '@atproto/api' import {type ThemeName} from '@bsky.app/alf' import {moderateProfile} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' @@ -21,7 +20,8 @@ import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/componen import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' -import {toLex} from '#/types/bsky' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' const desktopDarkBg = require('../../../assets/images/chat-desktop-bg-dark.webp') const desktopDimBg = require('../../../assets/images/chat-desktop-bg-dim.webp') @@ -85,7 +85,10 @@ export function JoinRequest({setScreenState}: Props) { ]}> {error || (data && - !ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) ? ( + !bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + joinLinkPreview, + )) ? ( ) : data && moderationOpts && - ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview) ? ( + bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + joinLinkPreview, + ) ? ( { const data = queryClient.getQueryData< - InfiniteData + InfiniteData >(createListJoinRequestsQueryKey({convoId})) return data?.pages.reduce((sum, page) => sum + page.requests.length, 0) ?? 0 } diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index ee27931636..1c107b7d0c 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import { moderateProfile, type ModerationDecision, @@ -65,7 +64,7 @@ export function ChatListItem({ selected = false, children, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView showMenu?: boolean selected?: boolean children?: React.ReactNode diff --git a/src/screens/Messages/components/IncomingRequestListItem.tsx b/src/screens/Messages/components/IncomingRequestListItem.tsx index aa4618188a..4753b92aea 100644 --- a/src/screens/Messages/components/IncomingRequestListItem.tsx +++ b/src/screens/Messages/components/IncomingRequestListItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -8,13 +7,14 @@ import {atoms as a, tokens} from '#/alf' import {parseConvoView} from '#/components/dms/util' import {KnownFollowers} from '#/components/KnownFollowers' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {ChatListItem, ChatListItemPortal} from './ChatListItem' import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons' export function IncomingRequestListItem({ convo: convoView, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView }) { const {currentAccount} = useSession() const moderationOpts = useModerationOpts() diff --git a/src/screens/Messages/components/InviteLinkDialog.tsx b/src/screens/Messages/components/InviteLinkDialog.tsx index 7b028bb39b..9ebef4c9e5 100644 --- a/src/screens/Messages/components/InviteLinkDialog.tsx +++ b/src/screens/Messages/components/InviteLinkDialog.tsx @@ -1,7 +1,6 @@ import {useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {type ChatBskyGroupDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -34,6 +33,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import {toLex} from '#/types/bsky' import {CopyTextButton} from './CopyTextButton' import {EditTextButton} from './EditTextButton' @@ -561,13 +561,13 @@ export function InviteLinkDialog({ ) } -function joinLinkToKey(joinLink: ChatBskyGroupDefs.JoinLinkView): string { +function joinLinkToKey(joinLink: chat.bsky.group.defs.JoinLinkView): string { return `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}` } function keyToJoinLink( key: string, -): Pick { +): Pick { const [joinRule, requireApproval] = key.split(':') return { joinRule, diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index f57b8d5837..e47373af48 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,8 +1,8 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' -import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {moderatePost} from '@bsky.app/sdk/moderation' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' @@ -103,7 +103,7 @@ export function useExtractEmbedFromFacets( for (const facet of rt.facets ?? []) { for (const feature of facet.features) { if ( - AppBskyRichtextFacet.isLink(feature) && + bsky.isType(app.bsky.richtext.facet.link, feature) && (isBskyPostUrl(feature.uri) || isBskyChatInviteUrl(feature.uri)) ) { uriFromFacet = feature.uri diff --git a/src/screens/Messages/components/MessageInputReply.tsx b/src/screens/Messages/components/MessageInputReply.tsx index 6b28b2f7b1..ff0f2d0b23 100644 --- a/src/screens/Messages/components/MessageInputReply.tsx +++ b/src/screens/Messages/components/MessageInputReply.tsx @@ -1,5 +1,4 @@ import {LayoutAnimation, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {HITSLOP_20} from '#/lib/constants' @@ -11,6 +10,7 @@ import {useMessageReplies} from '#/components/dms/MessageReplies' import {useReplyPreviewText} from '#/components/dms/replyPreview' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' /** * The reply staged in the message composer. Renders a preview of the message @@ -29,7 +29,7 @@ export function MessageInputReply() { function MessageInputReplyInner({ replyTo, }: { - replyTo: ChatBskyConvoDefs.MessageView + replyTo: chat.bsky.convo.defs.MessageView }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/screens/Messages/components/OutgoingRequestListItem.tsx b/src/screens/Messages/components/OutgoingRequestListItem.tsx index ef280919ba..13dba348d9 100644 --- a/src/screens/Messages/components/OutgoingRequestListItem.tsx +++ b/src/screens/Messages/components/OutgoingRequestListItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyGroupDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {isNetworkError} from '#/lib/strings/errors' @@ -12,11 +11,12 @@ import {createStaticClick, Link} from '#/components/Link' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' export function OutgoingRequestListItem({ convo: convoView, }: { - convo: ChatBskyGroupDefs.JoinRequestConvoView + convo: chat.bsky.group.defs.JoinRequestConvoView }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx index 5b316201b3..0161d5351f 100644 --- a/src/screens/Messages/components/RequestButtons.tsx +++ b/src/screens/Messages/components/RequestButtons.tsx @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -58,7 +57,7 @@ export function RejectMenu({ label?: string icon?: boolean convo: ConvoWithDetails - profile: ChatBskyActorDefs.ProfileViewBasic + profile: chat.bsky.actor.defs.ProfileViewBasic showDeleteConvo?: boolean currentScreen: 'list' | 'conversation' }) { @@ -216,7 +215,7 @@ export function AcceptChatButton({ }: Omit & { label?: string icon?: boolean - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView onAcceptConvo?: () => void currentScreen: 'list' | 'conversation' }) { @@ -314,7 +313,7 @@ export function DeleteChatButton({ }: Omit & { label?: string icon?: boolean - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentScreen: 'list' | 'conversation' }) { const {t: l} = useLingui() diff --git a/src/screens/ModerationInteractionSettings/index.tsx b/src/screens/ModerationInteractionSettings/index.tsx index 261e6b55ae..c6ea9db622 100644 --- a/src/screens/ModerationInteractionSettings/index.tsx +++ b/src/screens/ModerationInteractionSettings/index.tsx @@ -1,5 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -68,8 +69,8 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) { const allowUI = useMemo(() => { return threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', - post: '', - createdAt: new Date().toString(), + post: '' as AtUriString, + createdAt: toDatetimeString(new Date()), allow: preferences.postInteractionSettings.threadgateAllowRules, }) }, [preferences.postInteractionSettings.threadgateAllowRules]) diff --git a/src/screens/Onboarding/StepFinished/index.tsx b/src/screens/Onboarding/StepFinished/index.tsx index 88cd651c3a..9eb22efc70 100644 --- a/src/screens/Onboarding/StepFinished/index.tsx +++ b/src/screens/Onboarding/StepFinished/index.tsx @@ -1,12 +1,13 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyActorProfile, - type AppBskyGraphDefs, - type Un$Typed, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type Un$Typed} from '@atproto/lex' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' +import { + overwriteSavedFeeds, + setInterestsPref, + upsertProfile, +} from '@bsky.app/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -25,7 +26,7 @@ import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-p import {getAllListMembers} from '#/state/queries/list-members' import {preferencesQueryKey} from '#/state/queries/preferences' import {RQKEY as profileRQKey} from '#/state/queries/profile' -import {useAgent} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import { useActiveStarterPack, @@ -57,7 +58,8 @@ export function StepFinished() { const onboardDispatch = useOnboardingDispatch() const [saving, setSaving] = useState(false) const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const requestNotificationsPermission = useRequestNotificationsPermission() const activeStarterPack = useActiveStarterPack() const setActiveStarterPack = useSetActiveStarterPack() @@ -67,22 +69,25 @@ export function StepFinished() { const finishOnboarding = useCallback(async () => { setSaving(true) - let starterPack: AppBskyGraphDefs.StarterPackView | undefined - let listItems: AppBskyGraphDefs.ListItemView[] | undefined + let starterPack: app.bsky.graph.defs.StarterPackView | undefined + let listItems: app.bsky.graph.defs.ListItemView[] | undefined if (activeStarterPack?.uri) { try { - const spRes = await agent.app.bsky.graph.getStarterPack({ - starterPack: activeStarterPack.uri, + const spRes = await appviewClient.call(app.bsky.graph.getStarterPack, { + starterPack: activeStarterPack.uri as AtUriString, }) - starterPack = spRes.data.starterPack + starterPack = spRes.starterPack } catch (e) { logger.error('Failed to fetch starter pack', {safeMessage: e}) // don't tell the user, just get them through onboarding. } try { if (starterPack?.list) { - listItems = await getAllListMembers(agent, starterPack.list.uri) + listItems = await getAllListMembers( + appviewClient, + starterPack.list.uri, + ) } } catch (e) { logger.error('Failed to fetch starter pack list items', { @@ -98,7 +103,8 @@ export function StepFinished() { await Promise.all([ bulkWriteFollows( - agent, + pdsClient, + appviewClient, [BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])], starterPack ? {uri: starterPack.uri, cid: starterPack.cid} @@ -106,10 +112,10 @@ export function StepFinished() { ), (async () => { // Interests need to get saved first, then we can write the feeds to prefs - await agent.setInterestsPref({tags: selectedInterests}) + await pdsClient.call(setInterestsPref, {tags: selectedInterests}) // Default feeds that every user should have pinned when landing in the app - const feedsToSave: AppBskyActorDefs.SavedFeed[] = [ + const feedsToSave: app.bsky.actor.defs.SavedFeed[] = [ { ...DISCOVER_SAVED_FEED, id: TID.nextStr(), @@ -128,7 +134,7 @@ export function StepFinished() { if (starterPack && starterPack.feeds?.length) { feedsToSave.push( ...starterPack.feeds.map(f => ({ - type: 'feed', + type: 'feed' as const, value: f.uri, pinned: true, id: TID.nextStr(), @@ -136,22 +142,22 @@ export function StepFinished() { ) } - await agent.overwriteSavedFeeds(feedsToSave) + await pdsClient.call(overwriteSavedFeeds, feedsToSave) })(), (async () => { const {imageUri, imageMime} = profileStepResults const blobPromise = imageUri && imageMime - ? uploadBlob(agent, imageUri, imageMime) + ? uploadBlob(pdsClient, imageUri, imageMime) : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise - if (res.data.blob) { - next.avatar = res.data.blob + if (res.blob) { + next.avatar = res.blob } } @@ -165,7 +171,7 @@ export function StepFinished() { next.displayName = '' if (!next.createdAt) { - next.createdAt = new Date().toISOString() + next.createdAt = toDatetimeString(new Date()) } return next }) @@ -192,7 +198,7 @@ export function StepFinished() { queryKey: preferencesQueryKey, }), queryClient.invalidateQueries({ - queryKey: profileRQKey(agent.session?.did ?? ''), + queryKey: profileRQKey(pdsClient.did ?? ''), }), ]).catch(e => { logger.error(e) @@ -227,7 +233,8 @@ export function StepFinished() { }, [ ax, queryClient, - agent, + pdsClient, + appviewClient, dispatch, onboardDispatch, activeStarterPack, diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index e4885c87f5..1476b50f0b 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -14,7 +14,7 @@ import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {useLanguagePrefs} from '#/state/preferences' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import { OnboardingControls, OnboardingPosition, @@ -42,7 +42,8 @@ export function StepSuggestedAccounts() { const t = useTheme() const {gtMobile} = useBreakpoints() const moderationOpts = useModerationOpts() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const {currentAccount} = useSession() const queryClient = useQueryClient() @@ -119,7 +120,10 @@ export function StepSuggestedAccounts() { followingUri: 'pending', }) } - const uris = await wait(1e3, bulkWriteFollows(agent, followableDids)) + const uris = await wait( + 1e3, + bulkWriteFollows(pdsClient, appviewClient, followableDids), + ) for (const did of followableDids) { const uri = uris.get(did) updateProfileShadow(queryClient, did, { diff --git a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx index f6a82b7c70..878a326cf1 100644 --- a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx +++ b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -11,7 +10,7 @@ import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {getAllListMembers} from '#/state/queries/list-members' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {bulkWriteFollows} from '#/screens/Onboarding/util' import {AvatarStack} from '#/screens/Search/components/StarterPackCard' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' @@ -29,14 +28,15 @@ const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp' export function StarterPackCard({ view, }: { - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView }) { const t = useTheme() const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() const {gtPhone} = useBreakpoints() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const queryClient = useQueryClient() const record = view.record const [isProcessing, setIsProcessing] = useState(false) @@ -47,9 +47,9 @@ export function StarterPackCard({ setIsProcessing(true) - let listItems: AppBskyGraphDefs.ListItemView[] = [] + let listItems: app.bsky.graph.defs.ListItemView[] = [] try { - listItems = await getAllListMembers(agent, view.list.uri) + listItems = await getAllListMembers(appviewClient, view.list.uri) } catch (e) { setIsProcessing(false) Toast.show(_(msg`An error occurred while trying to follow all`), { @@ -74,7 +74,7 @@ export function StarterPackCard({ let followUris: Map try { - followUris = await bulkWriteFollows(agent, dids, { + followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { uri: view.uri, cid: view.cid, }) diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index 7093b4f374..ae5caee357 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -1,37 +1,34 @@ -import { - type $Typed, - type AppBskyGraphFollow, - type AppBskyGraphGetFollows, - type ComAtprotoRepoApplyWrites, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type $Typed} from '@atproto/lex' +import {type Client} from '@atproto/lex-client' +import { + type AtIdentifierString, + type DidString, + toDatetimeString, +} from '@atproto/syntax' import chunk from 'lodash.chunk' import {until} from '#/lib/async/until' -import {type SessionAgent} from '#/state/session' +import {app, com} from '#/lexicons' export async function bulkWriteFollows( - agent: SessionAgent, + pdsClient: Client, + appviewClient: Client, dids: string[], - via?: ComAtprotoRepoStrongRef.Main, + via?: com.atproto.repo.strongRef.Main, ) { - const session = agent.session + const did = pdsClient.assertDid - if (!session) { - throw new Error(`bulkWriteFollows failed: no session`) - } - - const followRecords: $Typed[] = dids.map(did => { + const followRecords: $Typed[] = dids.map(did => { return { $type: 'app.bsky.graph.follow', - subject: did, - createdAt: new Date().toISOString(), + subject: did as DidString, + createdAt: toDatetimeString(new Date()), via, } }) - const followWrites: $Typed[] = + const followWrites: $Typed[] = followRecords.map(r => ({ $type: 'com.atproto.repo.applyWrites#create', collection: 'app.bsky.graph.follow', @@ -41,35 +38,35 @@ export async function bulkWriteFollows( const chunks = chunk(followWrites, 50) for (const chunk of chunks) { - await agent.com.atproto.repo.applyWrites({ - repo: session.did, + await pdsClient.call(com.atproto.repo.applyWrites, { + repo: did, writes: chunk, }) } - await whenFollowsIndexed(agent, session.did, res => !!res.data.follows.length) + await whenFollowsIndexed(appviewClient, did, res => !!res.follows.length) const followUris = new Map() for (const r of followWrites) { followUris.set( r.value.subject as string, - `at://${session.did}/app.bsky.graph.follow/${r.rkey}`, + `at://${did}/app.bsky.graph.follow/${r.rkey}`, ) } return followUris } async function whenFollowsIndexed( - agent: SessionAgent, + appviewClient: Client, actor: string, - fn: (res: AppBskyGraphGetFollows.Response) => boolean, + fn: (res: app.bsky.graph.getFollows.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, () => - agent.app.bsky.graph.getFollows({ - actor, + appviewClient.call(app.bsky.graph.getFollows, { + actor: actor as AtIdentifierString, limit: 1, }), ) diff --git a/src/screens/PostThread/components/LikesStat.tsx b/src/screens/PostThread/components/LikesStat.tsx index bde37ef920..667e150b67 100644 --- a/src/screens/PostThread/components/LikesStat.tsx +++ b/src/screens/PostThread/components/LikesStat.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {moderateProfile} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -16,6 +15,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' const AVI_SIZE = 20 @@ -24,7 +24,7 @@ const AVI_SIZE = 20 * The plain "N likes" stat for the expanded anchor post, linking to the likes * list. Renders nothing when the post has no likes. */ -export function LikesStat({post}: {post: AppBskyFeedDefs.PostView}) { +export function LikesStat({post}: {post: app.bsky.feed.defs.PostView}) { const t = useTheme() const {t: l} = useLingui() const formatPostStatCount = useFormatPostStatCount() diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index 8851fb488e..691890e70e 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -1,10 +1,5 @@ import {memo, useMemo} from 'react' import {Text as RNText, View} from 'react-native' -import { - AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -69,7 +64,7 @@ export function ThreadItemAnchor({ }: { item: Extract onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main postSource?: PostSource }) { const postShadow = usePostShadow(item.value.post) @@ -170,9 +165,9 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ }: { item: Extract isRoot: boolean - postShadow: Shadow + postShadow: Shadow onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main postSource?: PostSource }) { const t = useTheme() @@ -236,7 +231,11 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ const viaRepost = useMemo(() => { const reason = postSource?.post.reason - if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { + if ( + bsky.isType(app.bsky.feed.defs.reasonRepost, reason) && + reason.uri && + reason.cid + ) { return { uri: reason.uri, cid: reason.cid, @@ -558,7 +557,7 @@ function ExpandedPostDetails({ ) } -function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { +function BackdatedPostIndicator({post}: {post: app.bsky.feed.defs.PostView}) { const t = useTheme() const {t: l, i18n} = useLingui() const control = Prompt.usePromptControl() @@ -645,8 +644,8 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { } function getThreadAuthor( - post: AppBskyFeedDefs.PostView, - record: AppBskyFeedPost.Record, + post: app.bsky.feed.defs.PostView, + record: app.bsky.feed.post.Main, ): string { if (!record.reply) { return post.author.did diff --git a/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx index f7ea1c53fb..fcf873f97c 100644 --- a/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx @@ -1,5 +1,4 @@ import {useCallback, useEffect, useMemo, useState} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -18,6 +17,7 @@ import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Che import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Toast from '#/components/Toast' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import {GrowthHack} from './GrowthHack' export function ThreadItemAnchorFollowButton({ @@ -57,7 +57,7 @@ export function ThreadItemAnchorFollowButtonInner({ function PostThreadFollowBtnLoaded({ profile: profileUnshadowed, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const navigation = useNavigation() const {_} = useLingui() diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx index 09a96f3247..084d3225a9 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -1,6 +1,5 @@ import {memo, type ReactNode, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyFeedThreadgate} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' @@ -45,6 +44,7 @@ import * as Skele from '#/components/Skeleton' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' export type ThreadItemPostProps = { item: Extract @@ -53,7 +53,7 @@ export type ThreadItemPostProps = { topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main } export function ThreadItemPost({ @@ -185,7 +185,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ onPostSuccess, threadgateRecord, }: ThreadItemPostProps & { - postShadow: Shadow + postShadow: Shadow }) { const t = useTheme() const {openComposer} = useOpenComposer() diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx index 88ea0c12c3..1f7a80d887 100644 --- a/src/screens/PostThread/components/ThreadItemTreePost.tsx +++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx @@ -1,6 +1,5 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyFeedThreadgate} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' @@ -41,6 +40,7 @@ import {RichText} from '#/components/RichText' import * as Skele from '#/components/Skeleton' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' /** * Mimic the space in PostMeta @@ -59,7 +59,7 @@ export function ThreadItemTreePost({ topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main }) { const postShadow = usePostShadow(item.value.post) @@ -242,13 +242,13 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({ threadgateRecord, }: { item: Extract - postShadow: Shadow + postShadow: Shadow overrides?: { moderation?: boolean topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main }): React.ReactNode { const {openComposer} = useOpenComposer() const {currentAccount} = useSession() diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index a835becbfe..e20bb17425 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' @@ -8,12 +7,13 @@ import {type Shadow} from '#/state/cache/types' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function ProfileHeaderDisplayName({ profile, moderation, }: { - profile: Shadow + profile: Shadow moderation: ModerationDecision }) { const t = useTheme() diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx index 1715328f54..d110753143 100644 --- a/src/screens/Profile/Header/EditProfileDialog.tsx +++ b/src/screens/Profile/Header/EditProfileDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -25,13 +24,14 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' +import {type app} from '#/lexicons' export function EditProfileDialog({ profile, control, onUpdate, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed control: Dialog.DialogControlProps onUpdate?: () => void }) { @@ -89,7 +89,7 @@ function DialogInner({ setDirty, onPressCancel, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed onUpdate?: () => void setDirty: (dirty: boolean) => void onPressCancel: () => void diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index f659b923cd..33e7cbcb8b 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -10,12 +9,13 @@ import {atoms as a, useTheme, web} from '#/alf' import {NewskieDialog} from '#/components/NewskieDialog' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' export function ProfileHeaderHandle({ profile, disableTaps, }: { - profile: Shadow + profile: Shadow disableTaps?: boolean }) { const t = useTheme() diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index 45f09597bc..84d25f761e 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -9,11 +8,12 @@ import {formatCount} from '#/view/com/util/numeric/format' import {atoms as a, useTheme} from '#/alf' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function ProfileHeaderMetrics({ profile, }: { - profile: Shadow + profile: Shadow }) { const t = useTheme() const {_, i18n} = useLingui() diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 2c44c3cc03..608e7faca7 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -1,6 +1,5 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyLabelerDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg, plural} from '@lingui/core/macro' @@ -33,6 +32,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import {ProfileHeaderDisplayName} from './DisplayName' import {EditProfileDialog} from './EditProfileDialog' @@ -41,8 +41,8 @@ import {ProfileHeaderMetrics} from './Metrics' import {ProfileHeaderShell} from './Shell' interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -57,7 +57,7 @@ let ProfileHeaderLabeler = ({ hideBackButton = false, isPlaceholderProfile, }: Props): React.ReactNode => { - const profile: Shadow = + const profile: Shadow = useProfileShadow(profileUnshadowed) const t = useTheme() const ax = useAnalytics() @@ -231,7 +231,7 @@ export function HeaderLabelerButtons({ profile, minimal = false, }: { - profile: Shadow + profile: Shadow /** disable the subscribe button */ minimal?: boolean }) { diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 87cac9a3b5..480c10d8bb 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -1,6 +1,5 @@ import {memo, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import { moderateProfile, type ModerationDecision, @@ -40,6 +39,7 @@ import {useAnalytics} from '#/analytics' import {IS_IOS, IS_NATIVE} from '#/env' import {InviteFriendsDialog} from '#/features/inviteFriends' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import {GermButton} from '../components/GermButton' import {ProfileHeaderDisplayName} from './DisplayName' @@ -50,7 +50,7 @@ import {ProfileHeaderShell} from './Shell' import {ProfileHeaderSuggestedFollows} from './SuggestedFollows' interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -65,7 +65,7 @@ let ProfileHeaderStandard = ({ isPlaceholderProfile, }: Props): React.ReactNode => { const profile = - useProfileShadow(profileUnshadowed) + useProfileShadow(profileUnshadowed) const {currentAccount} = useSession() const {_} = useLingui() const moderation = useMemo( @@ -214,7 +214,7 @@ export function HeaderStandardButtons({ onUnfollow, minimal, }: { - profile: Shadow + profile: Shadow moderation: ModerationDecision moderationOpts: ModerationOpts onFollow?: () => void diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 07b2b659a3..bf7a39b2e6 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -5,7 +5,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {type AppBskyActorDefs} from '@atproto/api' import {utils} from '@bsky.app/alf' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' @@ -32,12 +31,13 @@ import {useActorStatus} from '#/features/liveNow' import {EditLiveDialog} from '#/features/liveNow/components/EditLiveDialog' import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator' import {LiveStatusDialog} from '#/features/liveNow/components/LiveStatusDialog' +import {type app} from '#/lexicons' import {GrowableAvatar} from './GrowableAvatar' import {GrowableBanner} from './GrowableBanner' import {StatusBarShadow} from './StatusBarShadow' interface Props { - profile: Shadow + profile: Shadow moderation: ModerationDecision hideBackButton?: boolean isPlaceholderProfile?: boolean diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index bc18109fcc..fc46d9c2ed 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -7,7 +7,6 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' -import {type AppBskyActorDefs, type AppBskyLabelerDefs} from '@atproto/api' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useIsFocused} from '@react-navigation/native' @@ -22,6 +21,7 @@ import {atoms as a, useTheme} from '#/alf' import {Header} from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import { HeaderLabelerButtons, @@ -57,8 +57,8 @@ ProfileHeaderLoading = memo(ProfileHeaderLoading) export {ProfileHeaderLoading} interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -102,8 +102,8 @@ const MinimalHeader = memo(function MinimalHeader({ hideBackButton = false, }: { onLayout: (e: LayoutChangeEvent) => void - profile: AppBskyActorDefs.ProfileViewDetailed - labeler?: AppBskyLabelerDefs.LabelerViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler?: app.bsky.labeler.defs.LabelerViewDetailed hideBackButton?: boolean }) { const t = useTheme() diff --git a/src/screens/Profile/KnownFollowers.tsx b/src/screens/Profile/KnownFollowers.tsx index 09c1034b07..5ccc70c16f 100644 --- a/src/screens/Profile/KnownFollowers.tsx +++ b/src/screens/Profile/KnownFollowers.tsx @@ -1,5 +1,4 @@ import {useMemo, useState} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,12 +16,13 @@ import {List} from '#/view/com/util/List' import {ViewHeader} from '#/view/com/util/ViewHeader' import * as Layout from '#/components/Layout' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' function renderItem({ item, index, }: { - item: AppBskyActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) { return ( @@ -34,7 +34,7 @@ function renderItem({ ) } -function keyExtractor(item: AppBskyActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } diff --git a/src/screens/Profile/ProfileFeed/index.tsx b/src/screens/Profile/ProfileFeed/index.tsx index 69f743c03a..044ab47afe 100644 --- a/src/screens/Profile/ProfileFeed/index.tsx +++ b/src/screens/Profile/ProfileFeed/index.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {useAnimatedRef} from 'react-native-reanimated' -import {AppBskyFeedDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useIsFocused} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -175,7 +174,7 @@ export function ProfileFeedScreenInner({ const isVideoFeed = useMemo(() => { const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) const feedIsVideoMode = - feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO + feedInfo.contentMode === 'app.bsky.feed.defs#contentModeVideo' const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode return IS_NATIVE && _isVideoFeed }, [feedInfo]) diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx index 854dd37ce2..649c214acb 100644 --- a/src/screens/Profile/Sections/Labels.tsx +++ b/src/screens/Profile/Sections/Labels.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyLabelerDefs} from '@atproto/api' import { type InterpretedLabelValueDefinition, interpretLabelValueDefinitions, @@ -21,13 +20,14 @@ import {Loader} from '#/components/Loader' import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {ErrorState} from '../ErrorState' import {type SectionRef} from './types' interface LabelsSectionProps { ref: React.Ref isLabelerLoading: boolean - labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined + labelerInfo: app.bsky.labeler.defs.LabelerViewDetailed | undefined labelerError: Error | null moderationOpts: ModerationOpts scrollElRef: ListRef @@ -161,7 +161,7 @@ export function LabelerListHeader({ }: { isLabelerLoading: boolean labelerError?: Error | null - labelerInfo?: AppBskyLabelerDefs.LabelerViewDetailed + labelerInfo?: app.bsky.labeler.defs.LabelerViewDetailed hasValues: boolean isSubscribed: boolean }) { diff --git a/src/screens/ProfileList/AboutSection.tsx b/src/screens/ProfileList/AboutSection.tsx index d7f6be5c33..6ec615c4c0 100644 --- a/src/screens/ProfileList/AboutSection.tsx +++ b/src/screens/ProfileList/AboutSection.tsx @@ -1,6 +1,5 @@ import {useCallback, useImperativeHandle, useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,6 +14,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' interface SectionRef { scrollToTop: () => void @@ -22,7 +22,7 @@ interface SectionRef { interface AboutSectionProps { ref?: React.Ref - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onPressAddUser: () => void headerHeight: number scrollElRef: ListRef diff --git a/src/screens/ProfileList/components/Header.tsx b/src/screens/ProfileList/components/Header.tsx index 41e0b02ca7..66c0844238 100644 --- a/src/screens/ProfileList/components/Header.tsx +++ b/src/screens/ProfileList/components/Header.tsx @@ -1,6 +1,5 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -24,6 +23,7 @@ import {Loader} from '#/components/Loader' import {RichText} from '#/components/RichText' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import {MoreOptionsMenu} from './MoreOptionsMenu' import {SubscribeMenu} from './SubscribeMenu' @@ -34,14 +34,14 @@ export function Header({ preferences, }: { rkey: string - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView preferences: UsePreferencesQueryResponse }) { const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' + const isModList = list.purpose === 'app.bsky.graph.defs#modlist' const isBlocking = !!list.viewer?.blocked const isMuting = !!list.viewer?.muted const playHaptic = useHaptics() diff --git a/src/screens/ProfileList/components/MoreOptionsMenu.tsx b/src/screens/ProfileList/components/MoreOptionsMenu.tsx index 04974d62c6..8c396048a3 100644 --- a/src/screens/ProfileList/components/MoreOptionsMenu.tsx +++ b/src/screens/ProfileList/components/MoreOptionsMenu.tsx @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -37,13 +36,14 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' export function MoreOptionsMenu({ list, savedFeedConfig, }: { - list: AppBskyGraphDefs.ListView - savedFeedConfig?: AppBskyActorDefs.SavedFeed + list: app.bsky.graph.defs.ListView + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const {_} = useLingui() const ax = useAnalytics() @@ -58,8 +58,8 @@ export function MoreOptionsMenu({ const {mutateAsync: muteList} = useListMuteMutation() const {mutateAsync: blockList} = useListBlockMutation() - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' + const isModList = list.purpose === 'app.bsky.graph.defs#modlist' const isBlocking = !!list.viewer?.blocked const isMuting = !!list.viewer?.muted const isPinned = Boolean(savedFeedConfig?.pinned) diff --git a/src/screens/ProfileList/components/SubscribeMenu.tsx b/src/screens/ProfileList/components/SubscribeMenu.tsx index af16e0de0b..1553e2bc1e 100644 --- a/src/screens/ProfileList/components/SubscribeMenu.tsx +++ b/src/screens/ProfileList/components/SubscribeMenu.tsx @@ -1,4 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -13,8 +12,9 @@ import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' -export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) { +export function SubscribeMenu({list}: {list: app.bsky.graph.defs.ListView}) { const {_} = useLingui() const ax = useAnalytics() const subscribeMutePromptControl = Prompt.usePromptControl() diff --git a/src/screens/ProfileList/index.tsx b/src/screens/ProfileList/index.tsx index 1467885b46..bb53af5c46 100644 --- a/src/screens/ProfileList/index.tsx +++ b/src/screens/ProfileList/index.tsx @@ -1,7 +1,6 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' -import {AppBskyGraphDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {moderateUserList, type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' @@ -39,6 +38,7 @@ import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Hider from '#/components/moderation/Hider' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' import {toLex} from '#/types/bsky' import {AboutSection} from './AboutSection' import {ErrorScreen} from './components/ErrorScreen' @@ -144,7 +144,7 @@ function ProfileListScreenLoaded({ preferences, }: Props & { uri: string - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView moderationOpts: ModerationOpts preferences: UsePreferencesQueryResponse }) { @@ -156,7 +156,7 @@ function ProfileListScreenLoaded({ const {rkey} = route.params const feedSectionRef = useRef(null) const aboutSectionRef = useRef(null) - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST + const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' const isScreenFocused = useIsFocused() const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 const isOwner = currentAccount?.did === list.creator.did diff --git a/src/screens/SavedFeeds.tsx b/src/screens/SavedFeeds.tsx index db574318f3..26ea5c5e18 100644 --- a/src/screens/SavedFeeds.tsx +++ b/src/screens/SavedFeeds.tsx @@ -2,7 +2,6 @@ import {useState} from 'react' import {View} from 'react-native' import type Animated from 'react-native-reanimated' import {useAnimatedRef, useScrollOffset} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {TID} from '@atproto/common-web' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -43,6 +42,7 @@ import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps export function SavedFeeds({}: Props) { @@ -424,10 +424,10 @@ function PinnedFeedItem({ onMoveUp, onMoveDown, }: { - feed: AppBskyActorDefs.SavedFeed - currentFeeds: AppBskyActorDefs.SavedFeed[] + feed: app.bsky.actor.defs.SavedFeed + currentFeeds: app.bsky.actor.defs.SavedFeed[] setCurrentFeeds: React.Dispatch< - React.SetStateAction + React.SetStateAction > dragHandle?: React.ReactNode index?: number @@ -507,10 +507,10 @@ function UnpinnedFeedItem({ currentFeeds, setCurrentFeeds, }: { - feed: AppBskyActorDefs.SavedFeed - currentFeeds: AppBskyActorDefs.SavedFeed[] + feed: app.bsky.actor.defs.SavedFeed + currentFeeds: app.bsky.actor.defs.SavedFeed[] setCurrentFeeds: React.Dispatch< - React.SetStateAction + React.SetStateAction > }) { const {_} = useLingui() diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 364b7cbca2..1587996adc 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -1,10 +1,5 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View, type ViewabilityConfig} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import * as bcp47Match from 'bcp-47-match' @@ -68,6 +63,7 @@ import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' import {ExploreScreenLiveEventFeedsBanner} from '#/features/liveEvents/components/ExploreScreenLiveEventFeedsBanner' +import {type app} from '#/lexicons' import * as ModuleHeader from './components/ModuleHeader' import { SuggestedAccountsTabBar, @@ -155,7 +151,7 @@ type ExploreScreenItems = | { type: 'profile' key: string - profile: AppBskyActorDefs.ProfileView + profile: app.bsky.actor.defs.ProfileView recId?: string } | { @@ -165,7 +161,7 @@ type ExploreScreenItems = | { type: 'feed' key: string - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView } | { type: 'loadMore' @@ -191,7 +187,7 @@ type ExploreScreenItems = | { type: 'starterPack' key: string - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView } | { type: 'starterPackSkeleton' diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index cb7ac53d09..300c36fd65 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -1,6 +1,5 @@ import {memo, useCallback, useMemo, useState} from 'react' import {ActivityIndicator, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {urls} from '#/lib/constants' @@ -37,6 +36,7 @@ import {ListFooter} from '#/components/Lists' import {SearchError} from '#/components/SearchError' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' let SearchResults = ({ @@ -292,7 +292,7 @@ type SearchResultSlice = | { type: 'post' key: string - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView } | { type: 'loadingMore' @@ -504,7 +504,7 @@ function SearchPost({ }: { from: Metrics['search:result:press']['tab'] position: Metrics['search:result:press']['position'] - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView }) { const ax = useAnalytics() @@ -672,7 +672,7 @@ let SearchScreenFeedsResults = ({ item, index, }: { - item: AppBskyFeedDefs.GeneratorView + item: app.bsky.feed.defs.GeneratorView index: number }) => ( )} - keyExtractor={(item: AppBskyFeedDefs.GeneratorView) => item.uri} + keyExtractor={(item: app.bsky.feed.defs.GeneratorView) => item.uri} desktopFixedHeight ListFooterComponent={} /> @@ -704,7 +704,7 @@ function SearchFeedCard({ view, }: { position: number - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView }) { const ax = useAnalytics() @@ -790,14 +790,14 @@ let SearchScreenStarterPackResults = ({ item, index, }: { - item: AppBskyGraphDefs.StarterPackView + item: app.bsky.graph.defs.StarterPackView index: number }) => ( )} - keyExtractor={(item: AppBskyGraphDefs.StarterPackView) => item.uri} + keyExtractor={(item: app.bsky.graph.defs.StarterPackView) => item.uri} refreshing={isPTR} onRefresh={() => void onPullToRefresh()} onEndReached={onEndReached} @@ -824,7 +824,7 @@ function SearchStarterPack({ view, }: { position: number - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView }) { const ax = useAnalytics() diff --git a/src/screens/Search/components/ModuleHeader.tsx b/src/screens/Search/components/ModuleHeader.tsx index 65fbe8eb01..a0e02f6b3a 100644 --- a/src/screens/Search/components/ModuleHeader.tsx +++ b/src/screens/Search/components/ModuleHeader.tsx @@ -1,6 +1,5 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {PressableScale} from '#/lib/custom-animations/PressableScale' @@ -15,6 +14,7 @@ import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/component import {Link} from '#/components/Link' import {Text, type TextProps} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' export function Container({ style, @@ -47,7 +47,7 @@ export function FeedLink({ feed, children, }: { - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView children?: React.ReactNode }) { const t = useTheme() @@ -76,7 +76,7 @@ export function FeedLink({ ) } -export function FeedAvatar({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { +export function FeedAvatar({feed}: {feed: app.bsky.feed.defs.GeneratorView}) { return } @@ -179,7 +179,7 @@ export function EllipsisButton({ ) } -export function PinButton({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { +export function PinButton({feed}: {feed: app.bsky.feed.defs.GeneratorView}) { return ( void }) { const t = useTheme() diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index ef6dc0cba9..ad8a8b8a72 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -1,6 +1,5 @@ import {memo, useEffect} from 'react' import {View} from 'react-native' -import {type AppBskyActorSearchActors} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -14,6 +13,7 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs' import * as ProfileCard from '#/components/ProfileCard' import {SubtleHover} from '#/components/SubtleHover' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function useLoadEnoughProfiles({ @@ -25,7 +25,7 @@ export function useLoadEnoughProfiles({ fetchNextPage, }: { interest: string | null - data?: InfiniteData + data?: InfiniteData isLoading: boolean isFetchingNextPage: boolean hasNextPage: boolean diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index b20da3524c..51b69e64de 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,7 +1,6 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyUnspeccedDefs} from '@atproto/api' import {moderateProfile} from '@bsky.app/sdk/moderation' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -28,6 +27,7 @@ import {SubtleHover} from '#/components/SubtleHover' import {useTrendingTopicSeen} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import * as ModuleHeader from '../components/ModuleHeader' import {toLex} from '#/types/bsky' @@ -119,7 +119,7 @@ export function TrendRow({ children, onPress, }: ViewStyleProp & { - trend: AppBskyUnspeccedDefs.TrendView + trend: app.bsky.unspecced.defs.TrendView rank: number recId?: string children?: React.ReactNode @@ -230,7 +230,7 @@ export function TrendRow({ // Unused atm, but leaving here so we don't lose localization. -dsb export function useCategoryDisplayName( - category: AppBskyUnspeccedDefs.TrendView['category'], + category: app.bsky.unspecced.defs.TrendView['category'], ) { const {t: l} = useLingui() @@ -298,7 +298,7 @@ export function TrendingTopicRowSkeleton() { } function useModerateTrendingActors( - actors: AppBskyUnspeccedDefs.TrendView['actors'], + actors: app.bsky.unspecced.defs.TrendView['actors'], ) { const moderationOpts = useModerationOpts() diff --git a/src/screens/Search/modules/ExploreTrendingVideos.tsx b/src/screens/Search/modules/ExploreTrendingVideos.tsx index a015108951..86eafbc54c 100644 --- a/src/screens/Search/modules/ExploreTrendingVideos.tsx +++ b/src/screens/Search/modules/ExploreTrendingVideos.tsx @@ -1,6 +1,5 @@ import {useMemo} from 'react' import {ScrollView, View} from 'react-native' -import {AppBskyEmbedVideo} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -22,6 +21,8 @@ import { CompactVideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const CARD_WIDTH = 100 @@ -159,7 +160,7 @@ function VideoCards({ .flatMap(page => page.slices) .map(slice => slice.items[0]) .filter(Boolean) - .filter(item => AppBskyEmbedVideo.isView(item.post.embed)) + .filter(item => bsky.isType(app.bsky.embed.video.view, item.post.embed)) .slice(0, 8) }, [data]) const href = useMemo(() => { diff --git a/src/screens/Settings/ActivityPrivacySettings.tsx b/src/screens/Settings/ActivityPrivacySettings.tsx index b1d90cf9c1..06515b6efc 100644 --- a/src/screens/Settings/ActivityPrivacySettings.tsx +++ b/src/screens/Settings/ActivityPrivacySettings.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyNotificationDeclaration} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -18,6 +17,7 @@ import * as Toggle from '#/components/forms/Toggle' import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' +import {type app} from '#/lexicons' import * as SettingsList from './components/SettingsList' import {ItemTextWithSubtitle} from './NotificationSettings/components/ItemTextWithSubtitle' @@ -85,7 +85,7 @@ export function Inner({ notificationDeclaration: { uri?: string cid?: string - value: AppBskyNotificationDeclaration.Record + value: app.bsky.notification.declaration.Main } }) { const t = useTheme() diff --git a/src/screens/Settings/AppPasswords.tsx b/src/screens/Settings/AppPasswords.tsx index 65ebdf3d4f..49173b9629 100644 --- a/src/screens/Settings/AppPasswords.tsx +++ b/src/screens/Settings/AppPasswords.tsx @@ -6,7 +6,6 @@ import Animated, { LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' -import {type ComAtprotoServerListAppPasswords} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -33,6 +32,7 @@ import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' import {AddAppPasswordDialog} from './components/AddAppPasswordDialog' import * as SettingsList from './components/SettingsList' @@ -135,7 +135,7 @@ export function AppPasswordsScreen({}: Props) { function AppPasswordCard({ appPassword, }: { - appPassword: ComAtprotoServerListAppPasswords.AppPassword + appPassword: com.atproto.server.listAppPasswords.AppPassword }) { const t = useTheme() const {i18n, _} = useLingui() diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx index f2009e4e12..ecd0c6f5ff 100644 --- a/src/screens/Settings/AutomationLabelSettings.tsx +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {type $Typed, type ComAtprotoLabelDefs} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {Trans, useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useQueryClient} from '@tanstack/react-query' @@ -54,15 +54,13 @@ export function AutomationLabelSettingsScreen({}: Props) { { profile, updates: existing => { - const labels: $Typed = bsky.matches( - com.atproto.label.defs.selfLabels, - existing.labels, - ) - ? existing.labels - : { - $type: 'com.atproto.label.defs#selfLabels', - values: [], - } + const labels: $Typed = + bsky.matches(com.atproto.label.defs.selfLabels, existing.labels) + ? existing.labels + : { + $type: 'com.atproto.label.defs#selfLabels', + values: [], + } const hasLabel = labels.values.some(l => l.val === 'bot') if (hasLabel) { @@ -82,7 +80,7 @@ export function AutomationLabelSettingsScreen({}: Props) { return existing }, checkCommitted: res => { - const exists = !!res.data.labels?.some(l => l.val === 'bot') + const exists = !!res.labels?.some(l => l.val === 'bot') return exists === wasAdded }, }, diff --git a/src/screens/Settings/FindContactsSettings.tsx b/src/screens/Settings/FindContactsSettings.tsx index a7d9af7812..2f0dcc5804 100644 --- a/src/screens/Settings/FindContactsSettings.tsx +++ b/src/screens/Settings/FindContactsSettings.tsx @@ -1,10 +1,6 @@ import {useCallback, useEffect, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import * as Contacts from 'expo-contacts' -import { - type AppBskyContactDefs, - type AppBskyContactGetSyncStatus, -} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -32,7 +28,12 @@ import { useContactsMatchesQuery, useContactsSyncStatusQuery, } from '#/state/queries/find-contacts' -import {useAgent, useSession} from '#/state/session' +import { + useAgent, + useAppviewClient, + usePdsClient, + useSession, +} from '#/state/session' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {List} from '#/view/com/util/List' import {atoms as a, tokens, useGutters, useTheme} from '#/alf' @@ -52,6 +53,7 @@ import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {InviteFriendsDialog} from '#/features/inviteFriends' +import {app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {bulkWriteFollows} from '../Onboarding/util' @@ -190,7 +192,7 @@ function SyncStatus({ info, refetchStatus, }: { - info: AppBskyContactDefs.SyncStatus + info: app.bsky.contact.defs.SyncStatus refetchStatus: () => Promise }) { const ax = useAnalytics() @@ -370,7 +372,8 @@ function StatusHeader({ }) { const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const queryClient = useQueryClient() const {currentAccount} = useSession() @@ -384,12 +387,12 @@ function StatusHeader({ let cursor: string | undefined do { - const page = await agent.app.bsky.contact.getMatches({ + const page = await appviewClient.call(app.bsky.contact.getMatches, { limit: 100, cursor, }) - cursor = page.data.cursor - for (const profile of page.data.matches) { + cursor = page.cursor + for (const profile of page.matches) { if ( profile.did !== currentAccount?.did && !isBlockedOrBlocking(profile) && @@ -405,7 +408,10 @@ function StatusHeader({ followCount: didsToFollow.length, }) - const uris = await wait(500, bulkWriteFollows(agent, didsToFollow)) + const uris = await wait( + 500, + bulkWriteFollows(pdsClient, appviewClient, didsToFollow), + ) for (const did of didsToFollow) { const uri = uris.get(did) @@ -497,7 +503,7 @@ function StatusFooter({syncedAt}: {syncedAt: string}) { onMutate: () => ax.metric('contacts:settings:removeData', {}), onSuccess: () => { Toast.show(_(msg`Contacts removed`)) - queryClient.setQueryData( + queryClient.setQueryData( findContactsStatusQueryKey, {syncStatus: undefined}, ) diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx index 2979ace213..9d2a7c8a11 100644 --- a/src/screens/Settings/PrivacyAndSecuritySettings.tsx +++ b/src/screens/Settings/PrivacyAndSecuritySettings.tsx @@ -1,4 +1,3 @@ -import {type AppBskyNotificationDeclaration} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,6 +16,7 @@ import {Key_Stroke2_Corner2_Rounded as KeyIcon} from '#/components/icons/Key' import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' import * as Layout from '#/components/Layout' import {InlineLinkText} from '#/components/Link' +import {type app} from '#/lexicons' import {Email2FAToggle} from './components/Email2FAToggle' import {PwiOptOut} from './components/PwiOptOut' import {ItemTextWithSubtitle} from './NotificationSettings/components/ItemTextWithSubtitle' @@ -146,7 +146,7 @@ function NotificationDeclaration({ isError, }: { data?: { - value: AppBskyNotificationDeclaration.Record + value: app.bsky.notification.declaration.Main } isError?: boolean }) { diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index b8d23b7324..b8fc6002e5 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,7 +1,6 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {moderateProfile} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -68,6 +67,7 @@ import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {device, useStorage} from '#/storage' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' import {toLex} from '#/types/bsky' @@ -321,7 +321,7 @@ export function SettingsScreen({}: Props) { function ProfilePreview({ profile, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const t = useTheme() const {gtMobile} = useBreakpoints() @@ -609,7 +609,7 @@ function AccountRow({ pendingDid, onPressSwitchAccount, }: { - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed account: SessionAccount pendingDid: string | null onPressSwitchAccount: ( diff --git a/src/screens/Settings/components/AddAppPasswordDialog.tsx b/src/screens/Settings/components/AddAppPasswordDialog.tsx index 5029ad9f16..21b9f1ea1d 100644 --- a/src/screens/Settings/components/AddAppPasswordDialog.tsx +++ b/src/screens/Settings/components/AddAppPasswordDialog.tsx @@ -8,7 +8,6 @@ import Animated, { SlideInRight, SlideOutLeft, } from 'react-native-reanimated' -import {type ComAtprotoServerCreateAppPassword} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -25,6 +24,7 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/components/icons/SquareBehindSquare4' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type com} from '#/lexicons' import {CopyButton} from './CopyButton' export function AddAppPasswordDialog({ @@ -70,7 +70,7 @@ function CreateDialogInner({passwords}: {passwords: string[]}) { error: validationError, isPending, } = useMutation< - ComAtprotoServerCreateAppPassword.AppPassword, + com.atproto.server.createAppPassword.AppPassword, Error | DisplayableError >({ mutationFn: async () => { diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index 1b31a7de2a..917303064d 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -10,7 +10,6 @@ import Animated, { SlideOutLeft, SlideOutRight, } from 'react-native-reanimated' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -46,6 +45,7 @@ import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' +import {type com} from '#/lexicons' import {CopyButton} from './CopyButton' export function ChangeHandleDialog({ @@ -147,7 +147,7 @@ function ProvidedHandlePage({ serviceInfo, goToOwnHandle, }: { - serviceInfo: ComAtprotoServerDescribeServer.OutputSchema + serviceInfo: com.atproto.server.describeServer.$OutputBody goToOwnHandle: () => void }) { const {_} = useLingui() diff --git a/src/screens/Settings/components/DeleteAccountDialog.tsx b/src/screens/Settings/components/DeleteAccountDialog.tsx index 9903fce3ba..881bb0b7d0 100644 --- a/src/screens/Settings/components/DeleteAccountDialog.tsx +++ b/src/screens/Settings/components/DeleteAccountDialog.tsx @@ -4,11 +4,15 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' -import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useCleanError} from '#/lib/hooks/useCleanError' import {sanitizeHandle} from '#/lib/strings/handles' import {logger} from '#/logger' -import {useAgent, useSession, useSessionApi} from '#/state/session' +import { + useAgent, + useChatClient, + useSession, + useSessionApi, +} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {type DialogOuterProps} from '#/components/Dialog' @@ -24,6 +28,7 @@ import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import * as toast from '#/components/Toast' import {Span, Text} from '#/components/Typography' +import {chat} from '#/lexicons' import {resetToTab} from '#/Navigation' const WHITESPACE_RE = /\s/gu @@ -73,6 +78,7 @@ function DeleteAccountDialogInner({ const {_} = useLingui() const cleanError = useCleanError() const agent = useAgent() + const chatClient = useChatClient() const {currentAccount} = useSession() const {removeAccount} = useSessionApi() @@ -112,13 +118,9 @@ function DeleteAccountDialogInner({ throw new Error('Invalid did') } const token = confirmCode.replace(WHITESPACE_RE, '') - // Inform chat service of intent to delete account. - const {success} = await agent.chat.bsky.actor.deleteAccount(undefined, { - headers: DM_SERVICE_HEADERS, - }) - if (!success) { - throw new Error('Failed to inform chat service of account deletion') - } + // Inform chat service of intent to delete account. The chat client is + // proxied to the chat service; a failure throws. + await chatClient.call(chat.bsky.actor.deleteAccount) await agent.com.atproto.server.deleteAccount({ did: currentAccount.did, password, diff --git a/src/screens/Settings/components/ExportCarDialog.tsx b/src/screens/Settings/components/ExportCarDialog.tsx index 13b60dfdaa..6919bc4acb 100644 --- a/src/screens/Settings/components/ExportCarDialog.tsx +++ b/src/screens/Settings/components/ExportCarDialog.tsx @@ -2,7 +2,6 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' -import {DM_SERVICE_HEADERS} from '#/lib/constants' import {saveBytesToDisk} from '#/lib/media/manip' import {logger} from '#/logger' import {useAgent} from '#/state/session' @@ -14,6 +13,7 @@ import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {CHAT_PROXY_DID} from '#/env' export function ExportCarDialog({ control, @@ -58,9 +58,11 @@ export function ExportCarDialog({ setLoading('chat') // Using raw fetch because the XRPC client incorrectly tries to JSON-parse // application/jsonl responses (substring match on application/json). + // The chat-service proxy header is inlined here (the endpoint is proxied + // to `did:web:api.bsky.chat`); this raw path bypasses the lex client. const res = await agent.sessionManager.fetchHandler( '/xrpc/chat.bsky.actor.exportAccountData', - {headers: DM_SERVICE_HEADERS}, + {headers: {'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`}}, ) if (!res.ok) { throw new Error(`HTTP ${res.status}`) diff --git a/src/screens/Settings/components/PwiOptOut.tsx b/src/screens/Settings/components/PwiOptOut.tsx index 4c045db9d1..5dc67915f0 100644 --- a/src/screens/Settings/components/PwiOptOut.tsx +++ b/src/screens/Settings/components/PwiOptOut.tsx @@ -1,6 +1,6 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type $Typed, type ComAtprotoLabelDefs} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -36,7 +36,7 @@ export function PwiOptOut() { profile, updates: existing => { // create labels attr if needed - const labels: $Typed = bsky.matches( + const labels: $Typed = bsky.matches( com.atproto.label.defs.selfLabels, existing.labels, ) @@ -70,9 +70,7 @@ export function PwiOptOut() { return existing }, checkCommitted: res => { - const exists = !!res.data.labels?.some( - l => l.val === '!no-unauthenticated', - ) + const exists = !!res.labels?.some(l => l.val === '!no-unauthenticated') return exists === wasAdded }, }) diff --git a/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx b/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx index 0efe113d09..79eb2516e4 100644 --- a/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx +++ b/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx @@ -1,13 +1,12 @@ -import {type ComAtprotoTempCheckHandleAvailability} from '@atproto/api' import {Sift, SiftItem} from '@bsky.app/sift' import {Trans, useLingui} from '@lingui/react/macro' import {atoms as a, useTheme} from '#/alf' import {Portal} from '#/components/Portal' import {Text} from '#/components/Typography' -import {type HandleSuggestionsProps} from './shared' +import {type HandleSuggestion, type HandleSuggestionsProps} from './shared' -type Suggestion = ComAtprotoTempCheckHandleAvailability.Suggestion & { +type Suggestion = HandleSuggestion & { key: string } diff --git a/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts b/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts index 38644bdb93..f48d8d76b1 100644 --- a/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts +++ b/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts @@ -1,11 +1,23 @@ -import {type ComAtprotoTempCheckHandleAvailability} from '@atproto/api' import {type UseSiftReturn} from '@bsky.app/sift' +/** + * A handle suggestion from `com.atproto.temp.checkHandleAvailability`. The + * unspecced temp lexicon isn't generated into `#/lexicons`, so we describe the + * shape locally (mirrors `#/state/queries/handle-availability`). + */ +export type HandleSuggestion = { + $type?: 'com.atproto.temp.checkHandleAvailability#suggestion' + handle: string + /** + * Method used to build this suggestion. Should be considered opaque to + * clients. Can be used for metrics. + */ + method: string +} + export type HandleSuggestionsProps = { - suggestions: ComAtprotoTempCheckHandleAvailability.Suggestion[] - onSelect: ( - suggestion: ComAtprotoTempCheckHandleAvailability.Suggestion, - ) => void + suggestions: HandleSuggestion[] + onSelect: (suggestion: HandleSuggestion) => void /** * Web only: the Sift instance shared with the handle input. It carries the * anchor/positioning refs and keyboard bindings for the floating dropdown. diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx index 9d7b7e9ff1..c84e0d0404 100644 --- a/src/screens/Signup/StepInfo/Policies.tsx +++ b/src/screens/Signup/StepInfo/Policies.tsx @@ -1,6 +1,5 @@ import {type ReactElement} from 'react' import {View} from 'react-native' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -9,11 +8,12 @@ import {atoms as a, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' export const Policies = ({ serviceDescription, }: { - serviceDescription: ComAtprotoServerDescribeServer.OutputSchema + serviceDescription: com.atproto.server.describeServer.$OutputBody }) => { const t = useTheme() const {_} = useLingui() diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts index 95b3185758..47831ee85f 100644 --- a/src/screens/Signup/state.ts +++ b/src/screens/Signup/state.ts @@ -1,9 +1,5 @@ import {createContext, useCallback, useContext} from 'react' import {LayoutAnimation} from 'react-native' -import { - ComAtprotoServerCreateAccount, - type ComAtprotoServerDescribeServer, -} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import * as EmailValidator from 'email-validator' @@ -11,11 +7,13 @@ import {DEFAULT_SERVICE} from '#/lib/constants' import {cleanError, isNetworkError} from '#/lib/strings/errors' import {createFullHandle} from '#/lib/strings/handles' import {getAge} from '#/lib/strings/time' +import {getErrorName} from '#/lib/xrpc-error' import {useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {type AnalyticsContextType, useAnalytics} from '#/analytics' +import {type com} from '#/lexicons' -export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema +export type ServiceDescription = com.atproto.server.describeServer.$OutputBody const date = new Date() date.setFullYear(date.getFullYear() - 20) // default to 20 years ago @@ -357,7 +355,7 @@ export function useSubmitSignup() { } catch (err) { const e = err as Error let errMsg = e.toString() - if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) { + if (getErrorName(e) === 'InvalidInviteCode') { dispatch({ type: 'setError', value: l`Invite code not accepted. Check that you input it correctly and try again.`, diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx index a3cc9a6198..a5a7c3c9f6 100644 --- a/src/screens/StarterPack/StarterPackLandingScreen.tsx +++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx @@ -1,7 +1,6 @@ import {useEffect, useState} from 'react' import {Pressable, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' -import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' @@ -70,8 +69,8 @@ export function LandingScreen({ const isValid = starterPack && starterPack.list && - AppBskyGraphDefs.validateStarterPackView(starterPack) && - AppBskyGraphStarterpack.validateRecord(starterPack.record) + bsky.matches(app.bsky.graph.defs.starterPackView, starterPack) && + bsky.matches(app.bsky.graph.starterpack, starterPack.record) useEffect(() => { if (isErrorStarterPack || (starterPack && !isValid)) { @@ -106,8 +105,8 @@ function LandingScreenLoaded({ moderationOpts, }: { - starterPack: AppBskyGraphDefs.StarterPackView - starterPackRecord: AppBskyGraphStarterpack.Record + starterPack: app.bsky.graph.defs.StarterPackView + starterPackRecord: app.bsky.graph.starterpack.Main setScreenState: (state: LoggedOutScreenState) => void moderationOpts: ModerationOpts }) { diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 4272d069bc..8091a38055 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -1,7 +1,6 @@ import {useCallback, useEffect, useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' @@ -33,7 +32,7 @@ import { useDeleteStarterPackMutation, useStarterPackQuery, } from '#/state/queries/starter-packs' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {useSetActiveStarterPack} from '#/state/shell/landing' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import { @@ -147,8 +146,8 @@ export function StarterPackScreenInner({ const isValid = starterPack && (starterPack.list || starterPack?.creator?.did === currentAccount?.did) && - AppBskyGraphDefs.validateStarterPackView(starterPack) && - AppBskyGraphStarterpack.validateRecord(starterPack.record) + bsky.matches(app.bsky.graph.defs.starterPackView, starterPack) && + bsky.matches(app.bsky.graph.starterpack, starterPack.record) if (!did || !starterPack || !isValid || !moderationOpts) { return ( @@ -179,7 +178,7 @@ function StarterPackScreenLoaded({ routeParams, moderationOpts, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] moderationOpts: ModerationOpts }) { @@ -301,14 +300,15 @@ function Header({ routeParams, onOpenShareDialog, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] onOpenShareDialog: () => void }) { const {_} = useLingui() const t = useTheme() const {currentAccount, hasSession} = useSession() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const queryClient = useQueryClient() const setActiveStarterPack = useSetActiveStarterPack() const {requestSwitchToAccount} = useLoggedOutViewControls() @@ -349,9 +349,9 @@ function Header({ setIsProcessing(true) - let listItems: AppBskyGraphDefs.ListItemView[] = [] + let listItems: app.bsky.graph.defs.ListItemView[] = [] try { - listItems = await getAllListMembers(agent, starterPack.list.uri) + listItems = await getAllListMembers(appviewClient, starterPack.list.uri) } catch (e) { setIsProcessing(false) Toast.show(_(msg`An error occurred while trying to follow all`), { @@ -375,7 +375,7 @@ function Header({ let followUris: Map try { - followUris = await bulkWriteFollows(agent, dids, { + followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { uri: starterPack.uri, cid: starterPack.cid, }) @@ -516,7 +516,7 @@ function OverflowMenu({ routeParams, onOpenShareDialog, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] onOpenShareDialog: () => void }) { diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx index 5e27fc012d..b28a0bd728 100644 --- a/src/screens/StarterPack/Wizard/State.tsx +++ b/src/screens/StarterPack/Wizard/State.tsx @@ -1,5 +1,4 @@ import {createContext, useContext, useReducer} from 'react' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {msg, plural} from '@lingui/core/macro' import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' @@ -18,7 +17,7 @@ type Action = | {type: 'SetDescription'; description: string} | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} | {type: 'RemoveProfile'; profileDid: string} - | {type: 'AddFeed'; feed: AppBskyFeedDefs.GeneratorView} + | {type: 'AddFeed'; feed: app.bsky.feed.defs.GeneratorView} | {type: 'RemoveFeed'; feedUri: string} | {type: 'SetProcessing'; processing: boolean} | {type: 'SetError'; error: string} @@ -29,7 +28,7 @@ interface State { name?: string description?: string profiles: bsky.profile.AnyProfileView[] - feeds: AppBskyFeedDefs.GeneratorView[] + feeds: app.bsky.feed.defs.GeneratorView[] processing: boolean error?: string transitionDirection: 'Backward' | 'Forward' @@ -122,8 +121,8 @@ export function Provider({ targetProfile, children, }: { - starterPack?: AppBskyGraphDefs.StarterPackView - listItems?: AppBskyGraphDefs.ListItemView[] + starterPack?: app.bsky.graph.defs.StarterPackView + listItems?: app.bsky.graph.defs.ListItemView[] targetProfile: bsky.profile.AnyProfileView children: React.ReactNode }) { diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index 183f9fb843..788f740765 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -1,7 +1,6 @@ import {useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' @@ -21,8 +20,9 @@ import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/ScreenTransition' import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' -function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { +function keyExtractor(item: app.bsky.feed.defs.GeneratorView) { return item.uri } @@ -37,7 +37,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { useSavedFeeds() const savedFeeds = savedFeedsAndLists?.feeds .filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI) - .map(f => f.view) as AppBskyFeedDefs.GeneratorView[] + .map(f => f.view) as app.bsky.feed.defs.GeneratorView[] const { data: popularFeedsPages, @@ -67,7 +67,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { const renderItem = ({ item, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( void @@ -625,7 +621,7 @@ function Footer({ } function getName( - item: bsky.profile.AnyProfileView | AppBskyFeedDefs.GeneratorView, + item: bsky.profile.AnyProfileView | app.bsky.feed.defs.GeneratorView, ) { if (typeof item.displayName === 'string') { return enforceLen(sanitizeDisplayName(item.displayName), 28, true) diff --git a/src/screens/Topic.tsx b/src/screens/Topic.tsx index 8336cb1356..6e3be7c2a7 100644 --- a/src/screens/Topic.tsx +++ b/src/screens/Topic.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -21,12 +20,15 @@ import {Button, ButtonIcon} from '#/components/Button' import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import * as Layout from '#/components/Layout' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -const renderItem = ({item}: ListRenderItemInfo) => { +const renderItem = ({ + item, +}: ListRenderItemInfo) => { return } -const keyExtractor = (item: AppBskyFeedDefs.PostView, index: number) => { +const keyExtractor = (item: app.bsky.feed.defs.PostView, index: number) => { return `${item.uri}-${index}` } diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 47b92362a3..45c8436a43 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -25,7 +25,6 @@ import {useEvent, useEventListener} from 'expo' import {Image, type ImageStyle} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video' -import {AppBskyEmbedVideo, type AppBskyFeedDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' @@ -172,8 +171,8 @@ type CurrentSource = { type VideoItem = { moderation: ModerationDecision - post: AppBskyFeedDefs.PostView - video: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + video: app.bsky.embed.video.View feedContext: string | undefined reqId: string | undefined } @@ -211,8 +210,8 @@ function Feed() { const items: { _reactKey: string moderation: ModerationDecision - post: AppBskyFeedDefs.PostView - video: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + video: app.bsky.embed.video.View feedContext: string | undefined reqId: string | undefined }[] = [] @@ -220,7 +219,10 @@ function Feed() { const feedPost = slice.items.find( item => item.uri === slice.feedPostUri, ) - if (feedPost && AppBskyEmbedVideo.isView(feedPost.post.embed)) { + if ( + feedPost && + bsky.isType(app.bsky.embed.video.view, feedPost.post.embed) + ) { items.push({ _reactKey: feedPost._reactKey, moderation: feedPost.moderation, @@ -289,14 +291,14 @@ function Feed() { const prevPost = prevSlice?.post const prevEmbed = prevPost?.embed const prevVideo = - prevEmbed && AppBskyEmbedVideo.isView(prevEmbed) + prevEmbed && bsky.isType(app.bsky.embed.video.view, prevEmbed) ? prevEmbed.playlist : null const currSlice = videos.at(index) const currPost = currSlice?.post const currEmbed = currPost?.embed const currVideo = - currEmbed && AppBskyEmbedVideo.isView(currEmbed) + currEmbed && bsky.isType(app.bsky.embed.video.view, currEmbed) ? currEmbed.playlist : null const currVideoModeration = currSlice?.moderation @@ -304,7 +306,7 @@ function Feed() { const nextPost = nextSlice?.post const nextEmbed = nextPost?.embed const nextVideo = - nextEmbed && AppBskyEmbedVideo.isView(nextEmbed) + nextEmbed && bsky.isType(app.bsky.embed.video.view, nextEmbed) ? nextEmbed.playlist : null @@ -474,8 +476,8 @@ let VideoItem = ({ reqId, }: { player?: VideoPlayer - post: AppBskyFeedDefs.PostView - embed: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + embed: app.bsky.embed.video.View active: boolean adjacent: boolean scrollGesture: NativeGesture @@ -579,7 +581,7 @@ function VideoItemInner({ active, }: { player: VideoPlayer - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View active: boolean }) { const {bottom} = useSafeAreaInsets() @@ -681,7 +683,7 @@ function ModerationOverlay({ embed, onPressShow, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View onPressShow: () => void }) { const {t: l} = useLingui() @@ -779,8 +781,8 @@ function Overlay({ reqId, }: { player?: VideoPlayer - post: Shadow - embed: AppBskyEmbedVideo.View + post: Shadow + embed: app.bsky.embed.video.View active: boolean scrollGesture: NativeGesture moderation: ModerationDecision @@ -1062,7 +1064,7 @@ function VideoItemPlaceholder({ style, blur, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View style?: ImageStyle blur?: boolean }) { @@ -1103,7 +1105,7 @@ function PlayPauseTapArea({ reqId, }: { player: VideoPlayer - post: Shadow + post: Shadow feedContext: string | undefined reqId: string | undefined }) { @@ -1245,7 +1247,9 @@ function EndMessage() { /* * If the video is taller than 9:16 */ -function isTallAspectRatio(aspectRatio: AppBskyEmbedVideo.View['aspectRatio']) { +function isTallAspectRatio( + aspectRatio: app.bsky.embed.video.View['aspectRatio'], +) { const videoAspectRatio = (aspectRatio?.width ?? 1) / (aspectRatio?.height ?? 1) return videoAspectRatio <= 9 / 16 diff --git a/src/state/cache/post-shadow.ts b/src/state/cache/post-shadow.ts index 99b913f7de..26fcb6136c 100644 --- a/src/state/cache/post-shadow.ts +++ b/src/state/cache/post-shadow.ts @@ -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 > = 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 | typeof POST_TOMBSTONE { + post: app.bsky.feed.defs.PostView, +): Shadow | 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, -): Shadow | typeof POST_TOMBSTONE { +): Shadow | 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({ ...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 { +): Generator { for (let post of findAllPostsInFeedQueryData(queryClient, uri)) { yield post } diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index 015452749a..c0e171ed87 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -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< diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index 15c12e732d..f3ea97813c 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -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({ 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 + WeakSet >(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) { diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index c20d62352d..d9a5a3c559 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -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) diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 720df61acb..2d2d3c1fa3 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -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 diff --git a/src/state/preferences/label-defs.tsx b/src/state/preferences/label-defs.tsx index dd1859ecb6..8000bd231c 100644 --- a/src/state/preferences/label-defs.tsx +++ b/src/state/preferences/label-defs.tsx @@ -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 - labelers: AppBskyLabelerDefs.LabelerViewDetailed[] + labelers: app.bsky.labeler.defs.LabelerViewDetailed[] } const stateContext = createContext({ diff --git a/src/state/preferences/moderation-opts.tsx b/src/state/preferences/moderation-opts.tsx index cfa2638a5f..7d506021bc 100644 --- a/src/state/preferences/moderation-opts.tsx +++ b/src/state/preferences/moderation-opts.tsx @@ -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]) diff --git a/src/state/queries/join-links.ts b/src/state/queries/join-links.ts index 945bc7f626..30d48d2478 100644 --- a/src/state/queries/join-links.ts +++ b/src/state/queries/join-links.ts @@ -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 - | $Typed - | $Typed + | $Typed + | $Typed + | $Typed /** - * 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( + queryClient.setQueriesData( { 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], ) } diff --git a/src/state/queries/pinned-post.ts b/src/state/queries/pinned-post.ts index 09a0b4c9b9..2df94aec32 100644 --- a/src/state/queries/pinned-post.ts +++ b/src/state/queries/pinned-post.ts @@ -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) { diff --git a/src/state/queries/post-interaction-settings.ts b/src/state/queries/post-interaction-settings.ts index 2b660b3501..45d964f435 100644 --- a/src/state/queries/post-interaction-settings.ts +++ b/src/state/queries/post-interaction-settings.ts @@ -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, diff --git a/src/state/queries/postgate/util.ts b/src/state/queries/postgate/util.ts index 9f79048b19..d0b3c87bbf 100644 --- a/src/state/queries/postgate/util.ts +++ b/src/state/queries/postgate/util.ts @@ -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, } diff --git a/src/state/queries/preferences/useThreadPreferences.ts b/src/state/queries/preferences/useThreadPreferences.ts index 629d1c61b8..2c3c850ffe 100644 --- a/src/state/queries/preferences/useThreadPreferences.ts +++ b/src/state/queries/preferences/useThreadPreferences.ts @@ -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' diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 53a0534dd9..db12545a64 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -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({ diff --git a/src/state/queries/service.ts b/src/state/queries/service.ts index e9661db9e3..3b1df174e8 100644 --- a/src/state/queries/service.ts +++ b/src/state/queries/service.ts @@ -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), }) diff --git a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts index 42d27d9f39..464b1ae51c 100644 --- a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts +++ b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts @@ -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( + data.feeds.filter(feed => { + const isSaved = !!savedFeeds?.find(s => s.value === feed.uri) + return !isSaved + }), + ), } }, }) diff --git a/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts b/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts index bbdb762cf7..d0f7ada1ba 100644 --- a/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts +++ b/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts @@ -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}) }, }) } diff --git a/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts index 62e28fc781..02c8d19aae 100644 --- a/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts @@ -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}) }, }) } diff --git a/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts index eebe77790d..926a07141c 100644 --- a/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts @@ -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}) }, }) } diff --git a/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts index cd98552d7f..f0a65a30da 100644 --- a/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts @@ -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}) }, }) } diff --git a/src/state/queries/useOnboardingSuggestedStarterPacksQuery.ts b/src/state/queries/useOnboardingSuggestedStarterPacksQuery.ts index 5e76ebf100..442f49bbf7 100644 --- a/src/state/queries/useOnboardingSuggestedStarterPacksQuery.ts +++ b/src/state/queries/useOnboardingSuggestedStarterPacksQuery.ts @@ -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( + data, + ) }, }) } diff --git a/src/state/queries/useSuggestedStarterPacksQuery.ts b/src/state/queries/useSuggestedStarterPacksQuery.ts index 6cb1dd0869..6112d63c41 100644 --- a/src/state/queries/useSuggestedStarterPacksQuery.ts +++ b/src/state/queries/useSuggestedStarterPacksQuery.ts @@ -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( + data, + ) }, }) } diff --git a/src/state/queries/verification/useUpdateProfileVerificationCache.ts b/src/state/queries/verification/useUpdateProfileVerificationCache.ts index cb3e3890dd..457c7b6898 100644 --- a/src/state/queries/verification/useUpdateProfileVerificationCache.ts +++ b/src/state/queries/verification/useUpdateProfileVerificationCache.ts @@ -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, diff --git a/src/state/queries/verification/useVerificationCreateMutation.tsx b/src/state/queries/verification/useVerificationCreateMutation.tsx index 9ba4df9e46..77757219f1 100644 --- a/src/state/queries/verification/useVerificationCreateMutation.tsx +++ b/src/state/queries/verification/useVerificationCreateMutation.tsx @@ -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 ?? '', }) }, ) diff --git a/src/state/queries/verification/useVerificationsRemoveMutation.tsx b/src/state/queries/verification/useVerificationsRemoveMutation.tsx index e97b40fc89..0619ef6337 100644 --- a/src/state/queries/verification/useVerificationsRemoveMutation.tsx +++ b/src/state/queries/verification/useVerificationsRemoveMutation.tsx @@ -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 ?? '', }) }, ) diff --git a/src/state/session/additional-moderation-authorities.ts b/src/state/session/additional-moderation-authorities.ts index 2d174740de..1efe0f74b8 100644 --- a/src/state/session/additional-moderation-authorities.ts +++ b/src/state/session/additional-moderation-authorities.ts @@ -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}) } diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index ad1e7c78cb..ed483a5044 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -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, diff --git a/src/state/session/logging.ts b/src/state/session/logging.ts index e99269822e..0e096eef01 100644 --- a/src/state/session/logging.ts +++ b/src/state/session/logging.ts @@ -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 diff --git a/src/state/session/moderation.ts b/src/state/session/moderation.ts index face2ad345..5b2e0828dd 100644 --- a/src/state/session/moderation.ts +++ b/src/state/session/moderation.ts @@ -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]) } /** diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts index 404e0af88d..a806f1014b 100644 --- a/src/state/session/reducer.ts +++ b/src/state/session/reducer.ts @@ -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' diff --git a/src/state/session/session-core.ts b/src/state/session/session-core.ts index 89cfa0c426..73a0257172 100644 --- a/src/state/session/session-core.ts +++ b/src/state/session/session-core.ts @@ -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. diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index 9a1e212f48..12e734bf6c 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -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}[] diff --git a/src/state/threadgate-hidden-replies.tsx b/src/state/threadgate-hidden-replies.tsx index 698b450ef2..8bdc462e2e 100644 --- a/src/state/threadgate-hidden-replies.tsx +++ b/src/state/threadgate-hidden-replies.tsx @@ -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 @@ -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) diff --git a/src/state/unstable-post-source.tsx b/src/state/unstable-post-source.tsx index c5b7d1c2bf..e374cbbeae 100644 --- a/src/state/unstable-post-source.tsx +++ b/src/state/unstable-post-source.tsx @@ -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 } diff --git a/src/types/bsky/post.ts b/src/types/bsky/post.ts index 45a3602bcc..ed0d8a9d83 100644 --- a/src/types/bsky/post.ts +++ b/src/types/bsky/post.ts @@ -1,105 +1,56 @@ -import { - type $Typed as $TypedApi, - type AppBskyEmbedExternal, - type AppBskyEmbedGallery, - type AppBskyEmbedImages, - type AppBskyEmbedRecord, - type AppBskyEmbedVideo, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type AppBskyLabelerDefs, -} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {app} from '#/lexicons' import {isType} from '#/types/bsky' -/* - * TODO(phase4): drop every `| $TypedApi` arm below. This is a - * dual-world widening of the `Embed` union for the migration interim: the - * `.view` slots are populated both by `parseEmbed` (which returns `#/lexicons` - * views, the target) and by call sites that still pass old `@atproto/api` - * views produced through the bridge agent (e.g. ExternalEmbed, LazyQuoteEmbed). - * Each variant therefore accepts both worlds until those producers flip, after - * which the old arms are removed and this becomes a pure new-world union. - */ export type Embed = | { type: 'post' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_not_found' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_blocked' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_detached' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'feed' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'list' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'labeler' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'starter_pack' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'images' - /* - * TODO(phase4): flip to `$Typed`. Kept on the - * old `@atproto/api` view for now because the ImageEmbed consumer narrows - * gallery/images items with old-world `is*` guards that do not narrow the - * new union's `Unknown$TypedObject` arm. `parseEmbed` produces a new view - * here; new->old assignability lets it flow into this old slot until the - * consumer migrates (Task 7). - */ - view: $TypedApi + view: $Typed } | { type: 'gallery' - /** TODO(phase4): flip to `$Typed` - see the `images` arm above. */ - view: $TypedApi + view: $Typed } | { type: 'link' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'video' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_with_media' @@ -164,17 +115,7 @@ export function parseEmbedRecordView({ } } -export function parseEmbed( - /* - * TODO(phase4): drop the `| AppBskyFeedDefs.PostView['embed']` arm. Widened - * for the interim so call sites still passing an old bridge-produced - * `PostView.embed` typecheck against the `#/lexicons` guards below (which - * narrow on `$type` regardless of world). - */ - embed: - | app.bsky.feed.defs.PostView['embed'] - | AppBskyFeedDefs.PostView['embed'], -): Embed { +export function parseEmbed(embed: app.bsky.feed.defs.PostView['embed']): Embed { if (isType(app.bsky.embed.images.view, embed)) { return { type: 'images', diff --git a/src/types/bsky/profile.ts b/src/types/bsky/profile.ts index f5a4b59cd3..edf1aa1c08 100644 --- a/src/types/bsky/profile.ts +++ b/src/types/bsky/profile.ts @@ -1,23 +1,10 @@ -import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api' - import {type app, type chat} from '#/lexicons' /** * Matches any profile view exported by our SDK. - * - * TODO(phase4): drop the `@atproto/api` arms. This is a dual-world widening - * alias for the migration interim: profile producers (state/queries/profile.ts - * etc.) still return old `@atproto/api` views via the bridge agent, so the - * union must accept both the new `#/lexicons` views (the target) and the old - * ones until those producers flip. Once every producer emits `#/lexicons` - * views, remove the old arms and this becomes a pure new-world union. */ export type AnyProfileView = | app.bsky.actor.defs.ProfileViewBasic | app.bsky.actor.defs.ProfileView | app.bsky.actor.defs.ProfileViewDetailed | chat.bsky.actor.defs.ProfileViewBasic - | AppBskyActorDefs.ProfileViewBasic - | AppBskyActorDefs.ProfileView - | AppBskyActorDefs.ProfileViewDetailed - | ChatBskyActorDefs.ProfileViewBasic diff --git a/src/types/bsky/starterPack.ts b/src/types/bsky/starterPack.ts index 5d4f4a5d76..ed080864ab 100644 --- a/src/types/bsky/starterPack.ts +++ b/src/types/bsky/starterPack.ts @@ -1,5 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' - import {app} from '#/lexicons' /* @@ -30,14 +28,7 @@ export function isView(v: unknown): v is app.bsky.graph.defs.StarterPackView { /** * Matches any starter pack view exported by our SDK. - * - * TODO(phase4): drop the `@atproto/api` arms. Dual-world widening alias for the - * migration interim - starter-pack producers still return old views via the - * bridge agent. Remove the old arms once every producer emits `#/lexicons` - * views. */ export type AnyStarterPackView = | app.bsky.graph.defs.StarterPackViewBasic | app.bsky.graph.defs.StarterPackView - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 0d46f5c4b1..fbd2fc8222 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -46,14 +46,9 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnUI} from 'react-native-worklets' import * as FileSystem from 'expo-file-system' import {type ImagePickerAsset} from 'expo-image-picker' -import { - AppBskyDraftCreateDraft, - AppBskyUnspeccedDefs, - type AppBskyUnspeccedGetPostThreadV2, - ChatBskyGroupDefs, - type RichText, -} from '@atproto/api' -import {AtUri} from '@atproto/syntax' +import {type Client} from '@atproto/lex-client' +import {AtUri, type AtUriString} from '@atproto/syntax' +import {type RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -78,6 +73,7 @@ import {useCallOnce} from '#/lib/once' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {colors} from '#/lib/styles' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useDialogStateControlContext} from '#/state/dialogs' import {emitPostCreated} from '#/state/events' @@ -99,13 +95,7 @@ import { resolveLinkQueryOptions, useResolveClients, } from '#/state/queries/resolve-link' -import { - type SessionAgent, - useAgent, - useLexClient, - usePdsClient, - useSession, -} from '#/state/session' +import {useLexClient, usePdsClient, useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' @@ -152,6 +142,8 @@ import { IS_WEB_SAFARI, } from '#/env' import {type Gif} from '#/features/gifPicker/types' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' import { draftToComposerPosts, @@ -275,12 +267,12 @@ export const ComposePost = ({ const {currentAccount} = useSession() const t = useTheme() const ax = useAnalytics() - const agent = useAgent() const pdsClient = usePdsClient() const appviewClient = useLexClient() const resolveClients = useResolveClients() const queryClient = useQueryClient() const currentDid = currentAccount!.did + const currentDispatchUrl = currentAccount!.pdsUrl ?? currentAccount!.service const {closeComposer} = useComposerControls() const {t: l, i18n} = useLingui() const requireAltTextEnabled = useRequireAltTextEnabled() @@ -473,14 +465,23 @@ export const ComposePost = ({ }, }) }, - agent, + pdsClient, + currentDispatchUrl, currentDid, abortController.signal, i18n, telemetry, ) }, - [l, i18n, agent, currentDid, composerDispatch, ax.metric], + [ + l, + i18n, + pdsClient, + currentDispatchUrl, + currentDid, + composerDispatch, + ax.metric, + ], ) const onInitVideo = useNonReactiveCallback(() => { @@ -644,7 +645,8 @@ export const ComposePost = ({ }, }) }, - agent, + pdsClient, + currentDispatchUrl, currentDid, abortController.signal, i18n, @@ -657,7 +659,15 @@ export const ComposePost = ({ }) } }, - [l, i18n, agent, currentDid, composerDispatch, ax.metric], + [ + l, + i18n, + pdsClient, + currentDispatchUrl, + currentDid, + composerDispatch, + ax.metric, + ], ) const handleSelectDraft = useCallback( @@ -735,7 +745,7 @@ export const ComposePost = ({ const getDraftSaveError = useCallback( (e: unknown): string => { - if (e instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { + if (getErrorName(e) === 'DraftLimitReached') { return l`You've reached the maximum number of drafts` } return l`Failed to save draft` @@ -974,7 +984,7 @@ export const ComposePost = ({ const hasUnavailableChatInvite = linkQueries.some( q => q.data?.type === 'chat-invite' && - !ChatBskyGroupDefs.isJoinLinkPreviewView(q.data.view), + !bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, q.data.view), ) const canPost = @@ -1091,23 +1101,26 @@ export const ComposePost = ({ 5, _e => true, async () => { - const res = await agent.app.bsky.unspecced.getPostThreadV2({ - anchor: postUri!, - above: false, - below: filteredThread.posts.length - 1, - branchingFactor: 1, - }) - if (res.data.thread.length !== filteredThread.posts.length) { + const res = await appviewClient.call( + app.bsky.unspecced.getPostThreadV2, + { + anchor: postUri! as AtUriString, + above: false, + below: filteredThread.posts.length - 1, + branchingFactor: 1, + }, + ) + if (res.thread.length !== filteredThread.posts.length) { throw new Error(`composer: app view is not ready`) } if ( - !res.data.thread.every(p => - AppBskyUnspeccedDefs.isThreadItemPost(p.value), + !res.thread.every(p => + bsky.isType(app.bsky.unspecced.defs.threadItemPost, p.value), ) ) { throw new Error(`composer: app view returned non-post items`) } - return res.data.thread + return res.thread }, 1e3, ) @@ -1174,7 +1187,7 @@ export const ComposePost = ({ const resolved = q.data if ( resolved?.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolved.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolved.view) ) { ax.metric('groupchat:inviteLink:shared', { convoId: resolved.view.convoId, @@ -1210,10 +1223,10 @@ export const ComposePost = ({ setLangPrefs.savePostLanguageToHistory() if (initQuote) { // We want to wait for the quote count to update before we call `onPost`, which will refetch data - void whenAppViewReady(agent, initQuote.uri, res => { - const anchor = res.data.thread.at(0) + void whenAppViewReady(appviewClient, initQuote.uri, res => { + const anchor = res.thread.at(0) if ( - AppBskyUnspeccedDefs.isThreadItemPost(anchor?.value) && + bsky.isType(app.bsky.unspecced.defs.threadItemPost, anchor?.value) && anchor.value.post.quoteCount !== initQuote.quoteCount ) { onPost?.(postUri) @@ -1257,7 +1270,7 @@ export const ComposePost = ({ }, [ l, ax, - agent, + pdsClient, canPost, isPublishing, currentLanguages, @@ -2469,17 +2482,17 @@ function useKeyboardVerticalOffset() { } async function whenAppViewReady( - agent: SessionAgent, + appviewClient: Client, uri: string, - fn: (res: AppBskyUnspeccedGetPostThreadV2.Response) => boolean, + fn: (res: app.bsky.unspecced.getPostThreadV2.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, () => - agent.app.bsky.unspecced.getPostThreadV2({ - anchor: uri, + appviewClient.call(app.bsky.unspecced.getPostThreadV2, { + anchor: uri as AtUriString, above: false, below: 0, branchingFactor: 0, diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 00a108bb54..175cda7d30 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -1,13 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Pressable, View} from 'react-native' import {Image} from 'expo-image' -import { - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedPost, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,6 +13,8 @@ import {atoms as a, useTheme, utils, web} from '#/alf' import {QuoteEmbed} from '#/components/Post/Embed' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {parseEmbed} from '#/types/bsky/post' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { @@ -39,15 +34,15 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const quoteEmbed = useMemo(() => { if ( - AppBskyEmbedRecord.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record) && - AppBskyFeedPost.isRecord(embed.record.value) + bsky.isType(app.bsky.embed.record.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record) && + bsky.isType(app.bsky.feed.post, embed.record.value) ) { return embed } else if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record.record) && - AppBskyFeedPost.isRecord(embed.record.record.value) + bsky.isType(app.bsky.embed.recordWithMedia.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record.record) && + bsky.isType(app.bsky.feed.post, embed.record.record.value) ) { return embed.record } @@ -61,20 +56,20 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { : null const {images, totalNumber} = useMemo(() => { - if (AppBskyEmbedImages.isView(embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed)) { return {images: embed.images, totalNumber: embed.images.length} - } else if (AppBskyEmbedGallery.isView(embed)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed)) { return { images: galleryItemsToImages(embed.items), totalNumber: embed.items.length, } - } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { - if (AppBskyEmbedImages.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.recordWithMedia.view, embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed.media)) { return { images: embed.media.images, totalNumber: embed.media.images.length, } - } else if (AppBskyEmbedGallery.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed.media)) { return { images: galleryItemsToImages(embed.media.items), totalNumber: embed.media.items.length, @@ -145,12 +140,12 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { } function galleryItemsToImages( - items: AppBskyEmbedGallery.View['items'], -): AppBskyEmbedImages.ViewImage[] { + items: app.bsky.embed.gallery.View['items'], +): app.bsky.embed.images.ViewImage[] { // The reply-to thumbnail only renders up to 4 tiles; slicing here keeps // the existing layout switch valid for galleries up to 10 items. return items - .filter(AppBskyEmbedGallery.isViewImage) + .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item)) .slice(0, 4) .map(item => ({ thumb: item.thumbnail, @@ -164,7 +159,7 @@ function ComposerReplyToImages({ images, totalNumber, }: { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] totalNumber: number }) { const t = useTheme() diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx index 0b14f16ed3..6c2d3c42b1 100644 --- a/src/view/com/composer/ExternalEmbed.tsx +++ b/src/view/com/composer/ExternalEmbed.tsx @@ -1,5 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' +import {type UriString} from '@atproto/syntax' import {cleanError} from '#/lib/strings/errors' import { @@ -32,9 +33,9 @@ export const ExternalEmbedGif = ({ () => data && { title: data.title ?? data.uri, - uri: data.uri, + uri: data.uri as UriString, description: data.description ?? '', - thumb: data.thumb?.source.path, + thumb: data.thumb?.source.path as UriString | undefined, }, [data], ) @@ -96,11 +97,12 @@ export const ExternalEmbedLink = ({ view={{ ...data.view?.external, title: data.view?.external?.title || data.title || uri, - uri, + uri: uri as UriString, description: data.view?.external?.description || data.description, // prefer opengraph data to atproto record-derived image - thumb: data.thumb?.source.path || data.view?.external?.thumb, + thumb: (data.thumb?.source.path || + data.view?.external?.thumb) as UriString | undefined, }} /> ) @@ -109,9 +111,9 @@ export const ExternalEmbedLink = ({ diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index 398c416e20..66aca07d5c 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -1,7 +1,8 @@ /** * Type converters for Draft API - convert between ComposerState and server Draft types. */ -import {AppBskyDraftDefs, AtUri, RichText} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' import {resolveLink} from '#/lib/api/resolve' @@ -23,6 +24,8 @@ import {type VideoState} from '#/view/com/composer/state/video' import {type AnalyticsContextType} from '#/analytics' import {getDeviceId} from '#/analytics/identifiers' import {type Gif} from '#/features/gifPicker/types' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {logger} from './logger' import {type DraftPostDisplay, type DraftSummary} from './schema' import * as storage from './storage' @@ -61,18 +64,18 @@ function parseVideoMimeType(localRefPath: string): string { * Returns both the draft and a map of localRef paths to their source paths. */ export async function composerStateToDraft(state: ComposerState): Promise<{ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft localRefPaths: Map }> { const localRefPaths = new Map() - const posts: AppBskyDraftDefs.DraftPost[] = await Promise.all( + const posts: app.bsky.draft.defs.DraftPost[] = await Promise.all( state.thread.posts.map(post => { return postDraftToServerPost(post, localRefPaths) }), ) - const draft: AppBskyDraftDefs.Draft = { + const draft: app.bsky.draft.defs.Draft = { $type: 'app.bsky.draft.defs#draft', deviceId: getDeviceId(), deviceName: getDeviceName().slice(0, 100), // max length of 100 in lex @@ -96,8 +99,8 @@ export async function composerStateToDraft(state: ComposerState): Promise<{ async function postDraftToServerPost( post: PostDraft, localRefPaths: Map, -): Promise { - const draftPost: AppBskyDraftDefs.DraftPost = { +): Promise { + const draftPost: app.bsky.draft.defs.DraftPost = { $type: 'app.bsky.draft.defs#draftPost', text: post.richtext.text, } @@ -162,7 +165,8 @@ async function postDraftToServerPost( draftPost.embedExternals = [ { $type: 'app.bsky.draft.defs#draftEmbedExternal', - uri: post.embed.link.uri, + uri: post.embed.link + .uri as app.bsky.draft.defs.DraftEmbedExternal['uri'], }, ] } @@ -178,7 +182,7 @@ async function postDraftToServerPost( function serializeImages( images: ComposerImage[], localRefPaths: Map, -): AppBskyDraftDefs.DraftEmbedGalleryItems { +): app.bsky.draft.defs.DraftEmbedGalleryItems { return images.map(image => { const sourcePath = image.transformed?.path || image.source.path // Reuse existing localRefPath if present (editing draft), otherwise generate new @@ -210,7 +214,7 @@ function serializeImages( async function serializeVideo( videoState: VideoState, localRefPaths: Map, -): Promise { +): Promise { // Only save videos that have been compressed (have a video file) if (!videoState.video) { return undefined @@ -223,7 +227,7 @@ async function serializeVideo( localRefPaths.set(localRefPath, videoState.video.uri) // Read caption file contents as text - const captions: AppBskyDraftDefs.DraftEmbedCaption[] = [] + const captions: app.bsky.draft.defs.DraftEmbedCaption[] = [] for (const caption of videoState.captions) { if (caption.lang) { const content = await caption.file.text() @@ -254,7 +258,7 @@ function serializeGif(gifMedia: { type: 'gif' gif: Gif alt: string -}): AppBskyDraftDefs.DraftEmbedExternal | undefined { +}): app.bsky.draft.defs.DraftEmbedExternal | undefined { const gif = gifMedia.gif const gifFormat = gif.media_formats.gif || gif.media_formats.tinygif @@ -275,7 +279,7 @@ function serializeGif(gifMedia: { return { $type: 'app.bsky.draft.defs#draftEmbedExternal', - uri: url.toString(), + uri: url.toString() as app.bsky.draft.defs.DraftEmbedExternal['uri'], } } @@ -284,7 +288,7 @@ function serializeGif(gifMedia: { * both the `embedImages` and `embedGallery` paths in draftToComposerPosts. */ async function restoreDraftImages( - draftImages: AppBskyDraftDefs.DraftEmbedImage[], + draftImages: app.bsky.draft.defs.DraftEmbedImage[], loadedMedia: Map, ): Promise { const imagePromises = draftImages.map(async img => { @@ -340,7 +344,7 @@ export function draftViewToSummary({ view, analytics, }: { - view: AppBskyDraftDefs.DraftView + view: app.bsky.draft.defs.DraftView analytics: AnalyticsContextType }): DraftSummary { const meta = { @@ -380,7 +384,7 @@ export function draftViewToSummary({ // Process gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue meta.mediaCount++ meta.hasMedia = true const exists = storage.mediaExists(item.localRef.path) @@ -496,7 +500,7 @@ function parseGifFromUrl( * by initiating video processing for each entry. */ export async function draftToComposerPosts( - draft: AppBskyDraftDefs.Draft, + draft: app.bsky.draft.defs.Draft, loadedMedia: Map, ): Promise<{posts: PostDraft[]; restoredVideos: Map}> { const restoredVideos = new Map() @@ -525,8 +529,8 @@ export async function draftToComposerPosts( ) } if (post.embedGallery && post.embedGallery.items.length > 0) { - const galleryImages = post.embedGallery.items.filter( - AppBskyDraftDefs.isDraftEmbedImage, + const galleryImages = post.embedGallery.items.filter(item => + bsky.isType(app.bsky.draft.defs.draftEmbedImage, item), ) restoredImages.push( ...(await restoreDraftImages(galleryImages, loadedMedia)), @@ -646,7 +650,7 @@ export async function draftToComposerPosts( * Convert server threadgate rules back to UI settings. */ export function threadgateToUISettings( - threadgateAllow?: AppBskyDraftDefs.Draft['threadgateAllow'], + threadgateAllow?: app.bsky.draft.defs.Draft['threadgateAllow'], ): Array<{type: string; list?: string}> { if (!threadgateAllow) { return [] @@ -680,7 +684,9 @@ export function threadgateToUISettings( * Extract all localRef paths from a draft. * Used to identify which media files belong to a draft for cleanup. */ -export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { +export function extractLocalRefs( + draft: app.bsky.draft.defs.Draft, +): Set { const refs = new Set() for (const post of draft.posts) { if (post.embedImages) { @@ -690,7 +696,7 @@ export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue refs.add(item.localRef.path) } } diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts index 07dd67dd88..b0d7eda30d 100644 --- a/src/view/com/composer/drafts/state/queries.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -1,4 +1,3 @@ -import {AppBskyDraftCreateDraft, AppBskyDraftDefs} from '@atproto/api' import { useInfiniteQuery, useMutation, @@ -6,10 +5,13 @@ import { } from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' -import {useAgent} from '#/state/session' +import {getErrorName} from '#/lib/xrpc-error' +import {useAppviewClient} from '#/state/session' import {type ComposerState} from '#/view/com/composer/state/composer' import {useAnalytics} from '#/analytics' import {getDeviceId} from '#/analytics/identifiers' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {composerStateToDraft, draftViewToSummary} from './api' import {logger} from './logger' import * as storage from './storage' @@ -20,7 +22,7 @@ const DRAFTS_QUERY_KEY = ['drafts'] * Hook to list all drafts for the current account */ export function useDraftsQuery() { - const agent = useAgent() + const client = useAppviewClient() const ax = useAnalytics() return useInfiniteQuery({ @@ -28,10 +30,12 @@ export function useDraftsQuery() { queryFn: async ({pageParam}) => { // Ensure media cache is populated before checking which media exists await storage.ensureMediaCachePopulated() - const res = await agent.app.bsky.draft.getDrafts({cursor: pageParam}) + const res = await client.call(app.bsky.draft.getDrafts, { + cursor: pageParam, + }) return { - cursor: res.data.cursor, - drafts: res.data.drafts.map(view => + cursor: res.cursor, + drafts: res.drafts.map(view => draftViewToSummary({ view, analytics: ax, @@ -48,7 +52,9 @@ export function useDraftsQuery() { * Load a draft's local media for editing. * Takes the full Draft object (from DraftSummary) to avoid re-fetching. */ -export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ +export async function loadDraftMedia( + draft: app.bsky.draft.defs.Draft, +): Promise<{ loadedMedia: Map }> { // Load local media files @@ -77,7 +83,7 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ // Load gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue try { const url = await storage.loadMediaFromLocal(item.localRef.path) loadedMedia.set(item.localRef.path, url) @@ -116,7 +122,7 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ * This ensures we don't lose data if the network request fails. */ export function useSaveDraftMutation() { - const agent = useAgent() + const client = useAppviewClient() const queryClient = useQueryClient() return useMutation({ @@ -147,7 +153,7 @@ export function useSaveDraftMutation() { logger.debug('updating existing draft on server', { draftId: existingDraftId, }) - await agent.app.bsky.draft.updateDraft({ + await client.call(app.bsky.draft.updateDraft, { draft: { id: existingDraftId, draft, @@ -157,8 +163,8 @@ export function useSaveDraftMutation() { } else { // Create new draft logger.debug('creating new draft on server') - const res = await agent.app.bsky.draft.createDraft({draft}) - draftId = res.data.id + const res = await client.call(app.bsky.draft.createDraft, {draft}) + draftId = res.id logger.debug('created new draft', {draftId}) } @@ -203,7 +209,7 @@ export function useSaveDraftMutation() { }, onError: error => { // Check for draft limit error - if (error instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { + if (getErrorName(error) === 'DraftLimitReached') { logger.error('Draft limit reached', {safeMessage: error.message}) // Error will be handled by caller } else if (!isNetworkError(error)) { @@ -220,7 +226,7 @@ export function useSaveDraftMutation() { * Takes the full draft data to avoid re-fetching for media cleanup. */ export function useDeleteDraftMutation() { - const agent = useAgent() + const client = useAppviewClient() const queryClient = useQueryClient() return useMutation({ @@ -228,10 +234,10 @@ export function useDeleteDraftMutation() { draftId, }: { draftId: string - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft }) => { // Delete from server first - if this fails, we keep local media for retry - await agent.app.bsky.draft.deleteDraft({id: draftId}) + await client.call(app.bsky.draft.deleteDraft, {id: draftId}) }, onSuccess: async (_, {draft}) => { // Only delete local media after server deletion succeeds @@ -243,7 +249,8 @@ export function useDeleteDraftMutation() { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) + continue await storage.deleteMediaFromLocal(item.localRef.path) } } @@ -264,7 +271,7 @@ export function useDeleteDraftMutation() { * Takes draftId and originalLocalRefs from composer state. */ export function useCleanupPublishedDraftMutation() { - const agent = useAgent() + const client = useAppviewClient() const queryClient = useQueryClient() return useMutation({ @@ -280,7 +287,7 @@ export function useCleanupPublishedDraftMutation() { mediaFileCount: originalLocalRefs.size, }) // Delete from server first - await agent.app.bsky.draft.deleteDraft({id: draftId}) + await client.call(app.bsky.draft.deleteDraft, {id: draftId}) logger.debug('deleted draft from server', {draftId}) }, onSuccess: async (_, {originalLocalRefs}) => { diff --git a/src/view/com/composer/drafts/state/schema.ts b/src/view/com/composer/drafts/state/schema.ts index 9f88aa07da..123fc1d715 100644 --- a/src/view/com/composer/drafts/state/schema.ts +++ b/src/view/com/composer/drafts/state/schema.ts @@ -1,8 +1,8 @@ +import {type app} from '#/lexicons' /** * Types for draft display and local media tracking. * Server draft types come from @atproto/api. */ -import {type AppBskyDraftDefs} from '@atproto/api' /** * Reference to locally cached media file for display @@ -55,7 +55,7 @@ export type DraftSummary = { /** ISO timestamp of last update */ updatedAt: string /** The full draft data from the server */ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft /** All posts in the draft for full display */ posts: DraftPostDisplay[] /** Metadata about the draft for display purposes */ diff --git a/src/view/com/composer/select-language/SuggestedLanguage.tsx b/src/view/com/composer/select-language/SuggestedLanguage.tsx index ab731cda22..71a45ec796 100644 --- a/src/view/com/composer/select-language/SuggestedLanguage.tsx +++ b/src/view/com/composer/select-language/SuggestedLanguage.tsx @@ -1,11 +1,11 @@ import {useEffect, useMemo, useRef, useState} from 'react' import {Platform, Text as RNText, View} from 'react-native' -import {RichText} from '@atproto/api' import {parseLanguageString} from '@atproto/syntax' import { guessLanguageAsync, type LanguageResult, } from '@bsky.app/expo-guess-language' +import {RichText} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import debounce from 'lodash.debounce' diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts index fc8b341270..34c536e79a 100644 --- a/src/view/com/composer/state/composer.ts +++ b/src/view/com/composer/state/composer.ts @@ -1,11 +1,6 @@ import {type ImagePickerAsset} from 'expo-image-picker' -import { - type AppBskyActorDefs, - type AppBskyDraftDefs, - type AppBskyFeedPostgate, - AppBskyRichtextFacet, - RichText, -} from '@atproto/api' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' +import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' import {type VideoTelemetry} from '#/lib/media/video/telemetry' @@ -28,6 +23,8 @@ import { suggestLinkCardUri, } from '#/view/com/composer/text-input/text-input-util' import {type Gif} from '#/features/gifPicker/types' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import { createVideoState, type VideoAction, @@ -102,7 +99,7 @@ export type PostAction = export type ThreadDraft = { posts: PostDraft[] - postgate: AppBskyFeedPostgate.Record + postgate: app.bsky.feed.postgate.Main threadgate: ThreadgateAllowUISetting[] } @@ -121,7 +118,7 @@ export type ComposerState = { } export type ComposerAction = - | {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record} + | {type: 'update_postgate'; postgate: app.bsky.feed.postgate.Main} | {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]} | { type: 'update_post' @@ -143,8 +140,8 @@ export type ComposerAction = type: 'restore_from_draft' draftId: string posts: PostDraft[] - threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] - postgateEmbeddingRules: AppBskyDraftDefs.Draft['postgateEmbeddingRules'] + threadgateAllow: app.bsky.draft.defs.Draft['threadgateAllow'] + postgateEmbeddingRules: app.bsky.draft.defs.Draft['postgateEmbeddingRules'] /** Map of localRefPath -> loaded media path/URL */ loadedMedia: Map @@ -154,7 +151,7 @@ export type ComposerAction = | { type: 'clear' initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined } | { @@ -322,8 +319,8 @@ export function composerReducer( }), threadgate: threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', - post: '', - createdAt: new Date().toString(), + post: '' as AtUriString, + createdAt: toDatetimeString(new Date()), allow: threadgateAllow, }), }, @@ -629,7 +626,7 @@ export function createComposerState({ initImageUris: ComposerOpts['imageUris'] initQuoteUri: string | undefined initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined }): ComposerState { let media: ImagesMedia | GalleryMedia | undefined @@ -677,7 +674,7 @@ export function createComposerState({ if (initRichText.facets) { for (const facet of initRichText.facets) { for (const feature of facet.features) { - if (AppBskyRichtextFacet.isLink(feature)) { + if (bsky.isType(app.bsky.richtext.facet.link, feature)) { if (isBskyPostUrl(feature.uri)) { detectedPostUris.set(feature.uri, {facet, rt: initRichText}) } else { @@ -747,8 +744,8 @@ export function createComposerState({ }), threadgate: threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', - post: '', - createdAt: new Date().toString(), + post: '' as AtUriString, + createdAt: toDatetimeString(new Date()), allow: initInteractionSettings?.threadgateAllowRules, }), }, diff --git a/src/view/com/composer/state/video.ts b/src/view/com/composer/state/video.ts index 8da73f5f74..34d7a806e1 100644 --- a/src/view/com/composer/state/video.ts +++ b/src/view/com/composer/state/video.ts @@ -1,5 +1,6 @@ import {type ImagePickerAsset} from 'expo-image-picker' -import {type AppBskyVideoDefs, type BlobRef} from '@atproto/api' +import {type BlobRef} from '@atproto/lex' +import {type Client} from '@atproto/lex-client' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -14,10 +15,10 @@ import { import {type VideoTelemetry} from '#/lib/media/video/telemetry' import {type CompressedVideo} from '#/lib/media/video/types' import {uploadVideo} from '#/lib/media/video/upload' -import {createVideoAgent} from '#/lib/media/video/util' +import {createTokenlessVideoServiceClient} from '#/lib/media/video/util' import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' -import {type SessionAgent} from '#/state/session' +import {app} from '#/lexicons' type CaptionsTrack = {lang: string; file: File} @@ -51,7 +52,7 @@ export type VideoAction = } | { type: 'update_job_status' - jobStatus: AppBskyVideoDefs.JobStatus + jobStatus: app.bsky.video.defs.JobStatus signal: AbortSignal } @@ -120,7 +121,7 @@ type ProcessingState = { asset: ImagePickerAsset video: CompressedVideo jobId: string - jobStatus: AppBskyVideoDefs.JobStatus | null + jobStatus: app.bsky.video.defs.JobStatus | null pendingPublish?: undefined telemetry: VideoTelemetry altText: string @@ -275,7 +276,8 @@ function trunc2dp(num: number) { export async function processVideo( asset: ImagePickerAsset, dispatch: (action: VideoAction) => void, - agent: SessionAgent, + client: Client, + dispatchUrl: string | URL, did: string, signal: AbortSignal, i18n: I18n, @@ -318,12 +320,13 @@ export async function processVideo( signal, }) - let uploadResponse: AppBskyVideoDefs.JobStatus | undefined + let uploadResponse: app.bsky.video.defs.JobStatus | undefined try { telemetry.uploadStarted(video.size) uploadResponse = await uploadVideo({ video, - agent, + client, + dispatchUrl, did, signal, i18n, @@ -360,12 +363,14 @@ export async function processVideo( return // Exit async loop } - const videoAgent = createVideoAgent() - let status: AppBskyVideoDefs.JobStatus | undefined + const videoClient = createTokenlessVideoServiceClient() + let status: app.bsky.video.defs.JobStatus | undefined let blob: BlobRef | undefined try { - const response = await videoAgent.app.bsky.video.getJobStatus({jobId}) - status = response.data.jobStatus + const response = await videoClient.call(app.bsky.video.getJobStatus, { + jobId, + }) + status = response.jobStatus pollFailures = 0 if (status.state === 'JOB_STATE_COMPLETED') { diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 88389ffd56..d09d65481a 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -12,7 +12,7 @@ import { View, } from 'react-native' import {type PasteEventPayload, TextInputWrapper} from 'expo-paste-input' -import {AppBskyRichtextFacet, RichText} from '@atproto/api' +import {RichText} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants' @@ -26,7 +26,9 @@ import { } from '#/view/com/composer/text-input/text-input-util' import {atoms as a, useAlf} from '#/alf' import {normalizeTextStyles} from '#/alf/typography' -import {IS_ANDROID} from '#/env' +import {IS_ANDROID, IS_NATIVE} from '#/env' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {Autocomplete} from './mobile/Autocomplete' import {type TextInputProps} from './TextInput.types' @@ -88,7 +90,7 @@ export function TextInput({ if (newRt.facets) { for (const facet of newRt.facets) { for (const feature of facet.features) { - if (AppBskyRichtextFacet.isLink(feature)) { + if (bsky.isType(app.bsky.richtext.facet.link, feature)) { if (isUriImage(feature.uri)) { const res = await downloadAndResize({ uri: feature.uri, diff --git a/src/view/com/composer/text-input/TextInput.types.ts b/src/view/com/composer/text-input/TextInput.types.ts index fab2bc32f8..e60432b41e 100644 --- a/src/view/com/composer/text-input/TextInput.types.ts +++ b/src/view/com/composer/text-input/TextInput.types.ts @@ -1,5 +1,5 @@ import {type TextInput} from 'react-native' -import {type RichText} from '@atproto/api' +import {type RichText} from '@bsky.app/sdk/richtext' export type TextInputRef = { focus: () => void diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index e76d9e3f3d..f75d60f4bd 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -8,7 +8,7 @@ import { } from 'react' import {StyleSheet, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' -import {AppBskyRichtextFacet, RichText} from '@atproto/api' +import {RichText} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' import {getSchema} from '@tiptap/core' import {Document} from '@tiptap/extension-document' @@ -40,6 +40,8 @@ import {normalizeTextStyles} from '#/alf/typography' import {type Emoji} from '#/components/EmojiPicker' import {Portal} from '#/components/Portal' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {type TextInputProps} from './TextInput.types' import {type AutocompleteRef, createSuggestion} from './web/Autocomplete' import {LinkDecorator} from './web/LinkDecorator' @@ -265,7 +267,7 @@ export function TextInput({ if (newRt.facets) { for (const facet of newRt.facets) { for (const feature of facet.features) { - if (AppBskyRichtextFacet.isLink(feature)) { + if (bsky.isType(app.bsky.richtext.facet.link, feature)) { nextDetectedUris.set(feature.uri, {facet, rt: newRt}) } } diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index 9621ebd82c..c2f80165a1 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,6 +1,5 @@ import {View} from 'react-native' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' @@ -11,6 +10,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, platform, useTheme} from '#/alf' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function Autocomplete({ prefix, @@ -70,7 +70,7 @@ function AutocompleteProfileCard({ totalItems, onPress, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic itemIndex: number totalItems: number onPress: () => void diff --git a/src/view/com/composer/text-input/text-input-util.ts b/src/view/com/composer/text-input/text-input-util.ts index 71db8c3238..998d7074d9 100644 --- a/src/view/com/composer/text-input/text-input-util.ts +++ b/src/view/com/composer/text-input/text-input-util.ts @@ -1,8 +1,10 @@ -import {type AppBskyRichtextFacet, type RichText} from '@atproto/api' +import {type RichText} from '@bsky.app/sdk/richtext' + +import {type app} from '#/lexicons' export type LinkFacetMatch = { rt: RichText - facet: AppBskyRichtextFacet.Main + facet: app.bsky.richtext.facet.Main } export function suggestLinkCardUri( diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 267d4a4829..945ce59adc 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,6 +1,5 @@ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' import {ReactRenderer} from '@tiptap/react' @@ -16,6 +15,7 @@ import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {atoms as a, useTheme} from '#/alf' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' interface MentionListRef { onKeyDown: (props: SuggestionKeyDownProps) => boolean @@ -205,7 +205,7 @@ function AutocompleteProfileCard({ onHover, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic isSelected: boolean onPress: () => void onHover: () => void diff --git a/src/view/com/composer/text-input/web/LinkDecorator.ts b/src/view/com/composer/text-input/web/LinkDecorator.ts index 4843f0ddfc..bcb81b2cf5 100644 --- a/src/view/com/composer/text-input/web/LinkDecorator.ts +++ b/src/view/com/composer/text-input/web/LinkDecorator.ts @@ -14,7 +14,7 @@ * the facet-set. */ -import {URL_REGEX} from '@atproto/api' +import {URL_REGEX} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/composer/text-input/web/TagDecorator.ts b/src/view/com/composer/text-input/web/TagDecorator.ts index 8f1142b86c..34f6396abb 100644 --- a/src/view/com/composer/text-input/web/TagDecorator.ts +++ b/src/view/com/composer/text-input/web/TagDecorator.ts @@ -18,7 +18,7 @@ import { CASHTAG_REGEX, TAG_REGEX, TRAILING_PUNCTUATION_REGEX, -} from '@atproto/api' +} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/composer/threadgate/ThreadgateBtn.tsx b/src/view/com/composer/threadgate/ThreadgateBtn.tsx index 0f342b41c4..388c5e2be0 100644 --- a/src/view/com/composer/threadgate/ThreadgateBtn.tsx +++ b/src/view/com/composer/threadgate/ThreadgateBtn.tsx @@ -1,7 +1,7 @@ import {useEffect, useMemo, useState} from 'react' import {Keyboard, type StyleProp, type ViewStyle} from 'react-native' import {type AnimatedStyle} from 'react-native-reanimated' -import {type AppBskyFeedPostgate} from '@atproto/api' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import deepEqual from 'fast-deep-equal' @@ -24,6 +24,7 @@ import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Gr import * as Tooltip from '#/components/Tooltip' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged' export function ThreadgateBtn({ @@ -32,8 +33,8 @@ export function ThreadgateBtn({ threadgateAllowUISettings, onChangeThreadgateAllowUISettings, }: { - postgate: AppBskyFeedPostgate.Record - onChangePostgate: (v: AppBskyFeedPostgate.Record) => void + postgate: app.bsky.feed.postgate.Main + onChangePostgate: (v: app.bsky.feed.postgate.Main) => void threadgateAllowUISettings: ThreadgateAllowUISetting[] onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void @@ -83,8 +84,8 @@ export function ThreadgateBtn({ const prefThreadgateAllowUISettings = threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', - post: '', - createdAt: new Date().toISOString(), + post: '' as AtUriString, + createdAt: toDatetimeString(new Date()), allow: preferences?.postInteractionSettings.threadgateAllowRules, }) const prefPostgate = createPostgateRecord({ diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 331c62e0d1..33b55f6354 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {type NavigationProp, useNavigation} from '@react-navigation/native' @@ -38,6 +37,7 @@ import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' const POLL_FREQ = 60e3 // 60sec @@ -59,7 +59,7 @@ export function FeedPage({ isPageAdjacent: boolean renderEmptyState: () => JSX.Element renderEndOfFeed?: () => JSX.Element - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed feedInfo: FeedSourceInfo }) { const ax = useAnalytics() @@ -77,7 +77,7 @@ export function FeedPage({ const isVideoFeed = useMemo(() => { const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) const feedIsVideoMode = - feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO + feedInfo.contentMode === 'app.bsky.feed.defs#contentModeVideo' const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode return IS_NATIVE && _isVideoFeed }, [feedInfo]) diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 9f054e0ab0..535790e2d8 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -1,5 +1,4 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' @@ -26,8 +25,8 @@ import {MissingFeed} from './MissingFeed' type FeedSourceCardProps = { feedUri: string feedData?: - | $Typed - | $Typed + | $Typed + | $Typed style?: StyleProp showSaveBtn?: boolean showDescription?: boolean diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 04876eefb2..2f24dfec09 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -6,7 +6,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -26,6 +25,7 @@ import {useDialogControl} from '#/components/Dialog' import {UserAddRemoveListsDialog} from '#/components/dialogs/lists/UserAddRemoveListsDialog' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' const LOADING_ITEM = {kind: 'loading', _reactKey: '__loading__'} as const @@ -43,7 +43,7 @@ type Item = | typeof LOAD_MORE_ERROR_ITEM | { kind: 'list_item' - listItem: AppBskyGraphDefs.ListItemView + listItem: app.bsky.graph.defs.ListItemView } export function ListMembers({ diff --git a/src/view/com/lists/MyLists.tsx b/src/view/com/lists/MyLists.tsx index 0ec9828102..e1f5a607fd 100644 --- a/src/view/com/lists/MyLists.tsx +++ b/src/view/com/lists/MyLists.tsx @@ -7,7 +7,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -21,6 +20,7 @@ import {atoms as a, useTheme} from '#/alf' import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' import * as ListCard from '#/components/ListCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {ErrorMessage} from '../util/error/ErrorMessage' import {List} from '../util/List' @@ -38,7 +38,10 @@ export function MyLists({ filter: MyListsFilter inline?: boolean style?: StyleProp - renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element + renderItem?: ( + list: app.bsky.graph.defs.ListView, + index: number, + ) => JSX.Element testID?: string }) { const pal = usePalette('default') diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index e65c5d2bca..fa510ded59 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -8,9 +8,8 @@ import { TouchableOpacity, View, } from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' import {TID} from '@atproto/common-web' -import {AtUri, type DidString} from '@atproto/syntax' +import {AtUri} from '@atproto/syntax' import { moderateProfile, type ModerationDecision, @@ -74,7 +73,7 @@ import * as bsky from '#/types/bsky' const MAX_AUTHORS = 5 interface Author { - profile: AppBskyActorDefs.ProfileView + profile: app.bsky.actor.defs.ProfileView href: string moderation: ModerationDecision } @@ -738,7 +737,7 @@ export {NotificationFeedItem} function FollowedViaStarterPack({ starterPack, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: app.bsky.graph.defs.StarterPackViewBasic }) { const t = useTheme() const link = useStarterPackLink({view: starterPack}) @@ -772,12 +771,9 @@ function FollowedViaStarterPack({ } function getStarterPackName( - starterPack: AppBskyGraphDefs.StarterPackViewBasic, + starterPack: app.bsky.graph.defs.StarterPackViewBasic, ) { - return bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) + return bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? starterPack.record.name : undefined } @@ -811,7 +807,11 @@ function ExpandListPressable({ } } -function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function FollowBackButton({ + profile, +}: { + profile: app.bsky.actor.defs.ProfileView +}) { const {t: l} = useLingui() const {currentAccount, hasSession} = useSession() const profileShadow = useProfileShadow(profile) @@ -917,7 +917,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { ) } -function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function SayHelloBtn({profile}: {profile: app.bsky.actor.defs.ProfileView}) { const {t: l} = useLingui() const chatClient = useChatClient() const navigation = useNavigation() @@ -1151,7 +1151,7 @@ function ExpandedAuthorProfileCard({ ) } -function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { +function AdditionalPostText({post}: {post?: app.bsky.feed.defs.PostView}) { const t = useTheme() if (post && bsky.isType(app.bsky.feed.post, post?.record)) { const text = post.record.text diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index 48669d99ce..8f297d0428 100644 --- a/src/view/com/post-thread/PostLikedBy.tsx +++ b/src/view/com/post-thread/PostLikedBy.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,8 +10,15 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -function renderItem({item, index}: {item: GetLikes.Like; index: number}) { +function renderItem({ + item, + index, +}: { + item: app.bsky.feed.getLikes.Like + index: number +}) { return ( page.posts.map(post => { if ( - !bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) || + !bsky.isType(app.bsky.feed.post, post.record) || !moderationOpts ) { return null } // TODO(phase4): drop toLex once usePostQuotesQuery emits #/lexicons views - const moderation = moderatePost(toLex(post), moderationOpts) + const moderation = moderatePost(bsky.toLex(post), moderationOpts) return {post, record: post.record, moderation} }), ) diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 4c2db9eda0..06bdda13df 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,12 +10,13 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' function renderItem({ item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) { return ( @@ -28,7 +28,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index dc0dc57189..96cb6d62c7 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {moderatePost, type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' @@ -44,14 +43,14 @@ export function Post({ style, onBeforePress, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView showReplyLine?: boolean hideTopBorder?: boolean style?: StyleProp onBeforePress?: () => void }) { const moderationOpts = useModerationOpts() - const record = useMemo( + const record = useMemo( () => bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], @@ -105,8 +104,8 @@ function PostInner({ style, onBeforePress: outerOnBeforePress, }: { - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI moderation: ModerationDecision showReplyLine?: boolean diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index eea2453739..dbda8d0c41 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -18,15 +18,7 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyActorDefs, - AppBskyEmbedExternal, - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedVideo, - type AppBskyFeedDefs, - type RichText as RichTextType, -} from '@atproto/api' +import {type RichText as RichTextType} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -82,6 +74,8 @@ import { isStatusValidForViewers, useLiveNowConfig, } from '#/features/liveNow' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ComposerPrompt} from '../feeds/ComposerPrompt' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FeedShutdownMsg} from './FeedShutdownMsg' @@ -256,7 +250,7 @@ let PostFeed = ({ desktopFixedHeightOffset?: number ListHeaderComponent?: () => React.ReactElement extraData?: Record - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed initialNumToRender?: number isVideoFeed?: boolean lastFetchDate?: () => number @@ -290,7 +284,7 @@ let PostFeed = ({ () => new Set(), ) const onPressShowLess = useCallback( - (interaction: AppBskyFeedDefs.Interaction) => { + (interaction: app.bsky.feed.defs.Interaction) => { if (interaction.item) { const uri = interaction.item setHasPressedShowLessUris(prev => new Set([...prev, uri])) @@ -492,7 +486,7 @@ let PostFeed = ({ ) if ( item && - AppBskyEmbedVideo.isView(item.post.embed) && + bsky.isType(app.bsky.embed.video.view, item.post.embed) && !blockedOrMutedAuthors.includes(item.post.author.did) ) { videos.push({ @@ -1030,13 +1024,13 @@ let PostFeed = ({ // Events that should fire exactly once for every new post, regardless of // its position within a slice or video grid row. - const onPostSeen = (post: AppBskyFeedDefs.PostView) => { + const onPostSeen = (post: app.bsky.feed.defs.PostView) => { if (seenPerPostUrisRef.current.has(post.uri)) return seenPerPostUrisRef.current.add(post.uri) // Standard site embed view tracking if ( - AppBskyEmbedExternal.isView(post.embed) && + bsky.isType(app.bsky.embed.external.view, post.embed) && isStandardSiteEmbed(post.embed.external) ) { ax.metric('embed:standardSite:view', {url: post.embed.external.uri}) @@ -1044,13 +1038,21 @@ let PostFeed = ({ // Photo embed impression tracking if ( - AppBskyEmbedImages.isView(post.embed) || - AppBskyEmbedGallery.isView(post.embed) + bsky.isType(app.bsky.embed.images.view, post.embed) || + bsky.isType(app.bsky.embed.gallery.view, post.embed) ) { - const totalImages = AppBskyEmbedGallery.isView(post.embed) - ? post.embed.items.filter(AppBskyEmbedGallery.isViewImage).length + const totalImages = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) + ? post.embed.items.filter(item => + bsky.isType(app.bsky.embed.gallery.viewImage, item), + ).length : post.embed.images.length - const useExpandedLayout = AppBskyEmbedGallery.isView(post.embed) + const useExpandedLayout = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) ? totalImages > 4 : ax.features.enabled(ax.features.PostGalleryEmbedEnable) const layout = diff --git a/src/view/com/posts/PostFeedErrorMessage.tsx b/src/view/com/posts/PostFeedErrorMessage.tsx index 9c84a77727..d51c9a0161 100644 --- a/src/view/com/posts/PostFeedErrorMessage.tsx +++ b/src/view/com/posts/PostFeedErrorMessage.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, AppBskyFeedGetAuthorFeed} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg as msgLingui} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -10,11 +9,13 @@ import {useNavigation} from '@react-navigation/native' import {usePalette} from '#/lib/hooks/usePalette' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' +import {getErrorName} from '#/lib/xrpc-error' import {logger} from '#/logger' import {type FeedDescriptor} from '#/state/queries/post-feed' import {useRemoveFeedMutation} from '#/state/queries/preferences' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import * as Prompt from '#/components/Prompt' +import {type app} from '#/lexicons' import {EmptyState} from '../util/EmptyState' import {ErrorMessage} from '../util/error/ErrorMessage' import {Button} from '../util/forms/Button' @@ -42,7 +43,7 @@ export function PostFeedErrorMessage({ feedDesc: FeedDescriptor error?: Error onPressTryAgain: () => void - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const {_: _l} = useLingui() const knownError = useMemo( @@ -93,7 +94,7 @@ function FeedgenErrorMessage({ feedDesc: FeedDescriptor knownError: KnownError rawError?: Error - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const pal = usePalette('default') const {_: _l} = useLingui() @@ -239,8 +240,8 @@ function detectKnownError( return undefined } if ( - error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError || - error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError + getErrorName(error) === 'BlockedActor' || + getErrorName(error) === 'BlockedByActor' ) { return KnownError.Block } diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index 60e3a5915a..ef2ca2e3e9 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -1,10 +1,6 @@ import {memo, useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyFeedPost, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {AtUri} from '@atproto/syntax' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' @@ -56,15 +52,15 @@ import * as bsky from '#/types/bsky' import {PostFeedReason} from './PostFeedReason' interface FeedItemProps { - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main reason: - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | ReasonFeedSource | {[k: string]: unknown; $type: string} | undefined moderation: ModerationDecision - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined showReplyTo: boolean isThreadChild?: boolean isThreadLastChild?: boolean @@ -94,9 +90,9 @@ export function PostFeedItem({ rootPost, onShowLess, }: FeedItemProps & { - post: AppBskyFeedDefs.PostView - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: app.bsky.feed.defs.PostView + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode { const postShadowed = usePostShadow(post) const richText = useMemo( @@ -159,9 +155,9 @@ let FeedItemInner = ({ onShowLess, }: FeedItemProps & { richText: RichTextAPI - post: Shadow - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: Shadow + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode => { const ax = useAnalytics() const queryClient = useQueryClient() @@ -257,7 +253,9 @@ let FeedItemInner = ({ feedSourceInfo, post: { post, - reason: AppBskyFeedDefs.isReasonRepost(reason) ? reason : undefined, + reason: bsky.isType(app.bsky.feed.defs.reasonRepost, reason) + ? (reason as $Typed) + : undefined, feedContext, reqId, }, @@ -291,7 +289,11 @@ let FeedItemInner = ({ const {isActive: live} = useActorStatus(post.author) const viaRepost = useMemo(() => { - if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { + if ( + bsky.isType(app.bsky.feed.defs.reasonRepost, reason) && + reason.uri && + reason.cid + ) { return { uri: reason.uri, cid: reason.cid, @@ -461,10 +463,10 @@ let PostContent = ({ }: { moderation: ModerationDecision richText: RichTextAPI - postEmbed: AppBskyFeedDefs.PostView['embed'] - postAuthor: AppBskyFeedDefs.PostView['author'] + postEmbed: app.bsky.feed.defs.PostView['embed'] + postAuthor: app.bsky.feed.defs.PostView['author'] onOpenEmbed: () => void - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView additionalPostAlerts?: AppModerationCause[] feedDescriptor?: string }): React.ReactNode => { @@ -472,7 +474,7 @@ let PostContent = ({ () => countLines(richText.text) >= MAX_POST_LINES, ) - const record = useMemo( + const record = useMemo( () => bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], diff --git a/src/view/com/posts/PostFeedReason.tsx b/src/view/com/posts/PostFeedReason.tsx index 9d2bd3892c..9e1a043946 100644 --- a/src/view/com/posts/PostFeedReason.tsx +++ b/src/view/com/posts/PostFeedReason.tsx @@ -1,5 +1,4 @@ import {StyleSheet, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -26,8 +25,8 @@ export function PostFeedReason({ }: { reason: | ReasonFeedSource - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | {[k: string]: unknown; $type: string} moderation?: ModerationDecision onOpenReposter?: () => void diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index b5040aae9d..ded4eed990 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -1,5 +1,4 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -19,6 +18,7 @@ import { FollowersPromoBanner, useFollowersPromoDismissed, } from '#/features/inviteFriends' +import {type app} from '#/lexicons' import {List} from '../util/List' import {ProfileCardWithFollowBtn} from './ProfileCard' @@ -27,7 +27,7 @@ function renderItem({ index, contextProfileDid, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number contextProfileDid: string | undefined }) { @@ -42,7 +42,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } @@ -138,7 +138,7 @@ export function ProfileFollowers({name}: {name: string}) { }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) const renderItemWithContext = useCallback( - ({item, index}: {item: ActorDefs.ProfileView; index: number}) => + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -160,7 +160,7 @@ export function ProfileFollowers({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index b72161187c..86755904db 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -1,5 +1,4 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -15,6 +14,7 @@ import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/compo import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' import {List} from '../util/List' import {ProfileCardWithFollowBtn} from './ProfileCard' @@ -23,7 +23,7 @@ function renderItem({ index, contextProfileDid, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number contextProfileDid: string | undefined }) { @@ -38,7 +38,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } @@ -143,7 +143,7 @@ export function ProfileFollows({name}: {name: string}) { }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) const renderItemWithContext = useCallback( - ({item, index}: {item: ActorDefs.ProfileView; index: number}) => + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -165,7 +165,7 @@ export function ProfileFollows({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 51e89f843a..b12ecee627 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -1,5 +1,4 @@ import {memo, useCallback, useMemo} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -61,12 +60,13 @@ import {GoLiveDialog} from '#/features/liveNow/components/GoLiveDialog' import {GoLiveDisabledDialog} from '#/features/liveNow/components/GoLiveDisabledDialog' import {Dot} from '#/features/nuxs/components/Dot' import {Gradient} from '#/features/nuxs/components/Gradient' +import {type app} from '#/lexicons' import {useDevMode} from '#/storage/hooks/dev-mode' let ProfileMenu = ({ profile, }: { - profile: Shadow + profile: Shadow }): React.ReactNode => { const t = useTheme() const ax = useAnalytics() diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 072583938a..847e3b1f84 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,7 +1,6 @@ import {useCallback} from 'react' import {Pressable, View} from 'react-native' import Animated, {useAnimatedRef} from 'react-native-reanimated' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,6 +19,7 @@ import {UserAvatar, type UserAvatarType} from '#/view/com/util/UserAvatar' import {StarterPack} from '#/components/icons/StarterPack' import * as Layout from '#/components/Layout' import {useLightboxControls} from '#/components/Lightbox/state' +import {type app} from '#/lexicons' export function ProfileSubpageHeader({ isLoading, @@ -37,7 +37,7 @@ export function ProfileSubpageHeader({ title: string | undefined avatar: string | undefined isOwner: boolean | undefined - purpose: AppBskyGraphDefs.ListPurpose | undefined + purpose: app.bsky.graph.defs.ListPurpose | undefined creator: | { did: string diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index f50e14b18f..2c83017c96 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,6 +1,5 @@ import {memo, useCallback} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -21,11 +20,12 @@ import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Text} from '#/components/Typography' import {IS_ANDROID} from '#/env' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {TimeElapsed} from './TimeElapsed' import {PreviewableUserAvatar} from './UserAvatar' interface PostMetaOpts { - author: AppBskyActorDefs.ProfileViewBasic + author: app.bsky.actor.defs.ProfileViewBasic moderation: ModerationDecision | undefined postHref: string timestamp: string diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 028b85d38c..e70c512881 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,5 +1,4 @@ import {type StyleProp, type TextStyle} from 'react-native' -import {type AppBskyActorGetProfile} from '@atproto/api' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' @@ -9,6 +8,7 @@ import {useProfileQuery} from '#/state/queries/profile' import {atoms as a} from '#/alf' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {LoadingPlaceholder} from './LoadingPlaceholder' export function UserInfoText({ @@ -19,7 +19,7 @@ export function UserInfoText({ style, }: { did: string - attr?: keyof AppBskyActorGetProfile.OutputSchema + attr?: keyof app.bsky.actor.getProfile.$OutputBody loading?: string failed?: string prefix?: string diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index c3fe8fba8e..c58edac3c9 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -2,21 +2,16 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' import {useSharedValue} from 'react-native-reanimated' import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyFeedPost, - type ComAtprotoLabelDefs, interpretLabelValueDefinition, type LabelPreference, LABELS, - mock, moderatePost, moderateProfile, type ModerationBehavior, type ModerationDecision, type ModerationOpts, - RichText, -} from '@atproto/api' +} from '@bsky.app/sdk/moderation' +import {RichText} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -53,6 +48,7 @@ import { import * as Layout from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {H1, H3, P, Text} from '#/components/Typography' +import {type app, type com} from '#/lexicons' import {ScreenHider} from '../../components/moderation/ScreenHider' import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem' import {PagerHeaderProvider} from '../com/pager/PagerHeaderContext' @@ -62,6 +58,211 @@ const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( LABELS, ) as (keyof typeof LABELS)[] +const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq' + +/* + * Local test-data builders for this dev-only moderation debug screen. These + * replace the `mock` object the old api package used to export (the SDK does + * not ship one). Each builder returns a plain `#/lexicons` object literal with + * the same field values the old `mock` builders produced. Branded string slots + * (`did`/`at-uri`/`cid`/`lang`) are cast, since this is trusted mock data. + */ +const mock = { + post({ + text, + facets, + reply, + embed, + }: { + text: string + facets?: app.bsky.feed.post.Main['facets'] + reply?: app.bsky.feed.post.Main['reply'] + embed?: app.bsky.feed.post.Main['embed'] + }): app.bsky.feed.post.Main { + return { + $type: 'app.bsky.feed.post', + text, + facets, + reply, + embed, + langs: ['en'], + createdAt: + new Date().toISOString() as app.bsky.feed.post.Main['createdAt'], + } + }, + postView({ + record, + author, + embed, + replyCount, + repostCount, + likeCount, + viewer, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + embed?: app.bsky.feed.defs.PostView['embed'] + replyCount?: number + repostCount?: number + likeCount?: number + viewer?: app.bsky.feed.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.feed.defs.PostView { + return { + $type: 'app.bsky.feed.defs#postView', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + record, + embed, + replyCount, + repostCount, + likeCount, + indexedAt: + new Date().toISOString() as app.bsky.feed.defs.PostView['indexedAt'], + viewer, + labels, + } + }, + embedRecordView({ + record, + author, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.embed.record.View { + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewRecord', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + value: record, + labels, + indexedAt: + new Date().toISOString() as app.bsky.embed.record.ViewRecord['indexedAt'], + }, + } + }, + profileViewBasic({ + handle, + displayName, + description, + viewer, + labels, + }: { + handle: string + displayName?: string + description?: string + viewer?: app.bsky.actor.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.actor.defs.ProfileViewBasic & {description?: string} { + return { + did: `did:web:${handle}`, + handle: handle as app.bsky.actor.defs.ProfileViewBasic['handle'], + displayName, + description, + viewer, + labels, + } + }, + actorViewerState({ + muted, + mutedByList, + blockedBy, + blocking, + blockingByList, + following, + followedBy, + }: { + muted?: boolean + mutedByList?: app.bsky.graph.defs.ListViewBasic + blockedBy?: boolean + blocking?: string + blockingByList?: app.bsky.graph.defs.ListViewBasic + following?: string + followedBy?: string + }): app.bsky.actor.defs.ViewerState { + return { + muted, + mutedByList, + blockedBy, + blocking: blocking as app.bsky.actor.defs.ViewerState['blocking'], + blockingByList, + following: following as app.bsky.actor.defs.ViewerState['following'], + followedBy: followedBy as app.bsky.actor.defs.ViewerState['followedBy'], + } + }, + replyNotification({ + author, + record, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'reply', + reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`, + record, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + followNotification({ + author, + subjectDid, + labels, + }: { + author: app.bsky.actor.defs.ProfileViewBasic + subjectDid: string + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.graph.follow/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'follow', + record: { + $type: 'app.bsky.graph.follow', + createdAt: new Date().toISOString(), + subject: subjectDid, + }, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + label({ + val, + uri, + src, + }: { + val: string + uri: string + src?: string + }): com.atproto.label.defs.Label { + return { + src: (src || + 'did:plc:fake-labeler') as com.atproto.label.defs.Label['src'], + uri: uri as com.atproto.label.defs.Label['uri'], + val, + cts: new Date().toISOString() as com.atproto.label.defs.Label['cts'], + } + }, +} + export const DebugModScreen = ({}: NativeStackScreenProps< CommonNavigatorParams, 'DebugMod' @@ -73,7 +274,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< const [target, setTarget] = useState(['account']) const [visibility, setVisiblity] = useState(['warn']) const [customLabelDef, setCustomLabelDef] = - useState({ + useState({ identifier: 'custom', blurs: 'content', severity: 'alert', @@ -140,7 +341,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< blockingByList: undefined, }), }) - mockedProfile.did = did + mockedProfile.did = did as app.bsky.actor.defs.ProfileViewBasic['did'] mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' // @ts-expect-error ProfileViewBasic is close enough -esb mockedProfile.banner = @@ -164,36 +365,35 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }), ] : undefined, - embed: - target[0] === 'embed' - ? mock.embedRecordView({ - record: mock.post({ - text: 'Embed', - }), - labels: - scenario[0] === 'label' && target[0] === 'embed' - ? [ - mock.label({ - src: isSelfLabel ? did : undefined, - val: label[0], - uri: `at://${did}/app.bsky.feed.post/fake`, - }), - ] - : undefined, - author: profile, - }) - : { - $type: 'app.bsky.embed.images#view', - images: [ - { - thumb: - 'https://bsky.social/about/images/social-card-default-gradient.png', - fullsize: - 'https://bsky.social/about/images/social-card-default-gradient.png', - alt: '', - }, - ], - }, + embed: (target[0] === 'embed' + ? mock.embedRecordView({ + record: mock.post({ + text: 'Embed', + }), + labels: + scenario[0] === 'label' && target[0] === 'embed' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + author: profile, + }) + : { + $type: 'app.bsky.embed.images#view', + images: [ + { + thumb: + 'https://bsky.social/about/images/social-card-default-gradient.png', + fullsize: + 'https://bsky.social/about/images/social-card-default-gradient.png', + alt: '', + }, + ], + }) as app.bsky.feed.defs.PostView['embed'], }) }, [scenario, label, target, profile, isSelfLabel, did]) @@ -226,7 +426,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }) const [item] = groupNotifications([notif]) item.subject = mock.postView({ - record: notif.record as AppBskyFeedPost.Record, + record: notif.record as app.bsky.feed.post.Main, author: profile, labels: notif.labels, }) @@ -242,9 +442,13 @@ export const DebugModScreen = ({}: NativeStackScreenProps< return item }, [profile, currentAccount]) - const modOpts = useMemo(() => { + const modOpts = useMemo(() => { return { - userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test', + userDid: (isLoggedOut + ? '' + : isTargetMe + ? did + : 'did:web:alice.test') as ModerationOpts['userDid'], prefs: { adultContentEnabled: !noAdult, labels: { @@ -629,9 +833,9 @@ function CustomLabelForm({ def, setDef, }: { - def: ComAtprotoLabelDefs.LabelValueDefinition + def: com.atproto.label.defs.LabelValueDefinition setDef: React.Dispatch< - React.SetStateAction + React.SetStateAction > }) { const t = useTheme() @@ -833,7 +1037,7 @@ function MockPostFeedItem({ post, moderation, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView moderation: ModerationDecision }) { const t = useTheme() @@ -847,7 +1051,7 @@ function MockPostFeedItem({ return ( @@ -91,7 +91,7 @@ type FlatlistSlice = type: 'popularFeed' key: string feedUri: string - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView } | { type: 'popularFeedsLoadingMore' diff --git a/src/view/screens/Lists.tsx b/src/view/screens/Lists.tsx index 54f3568fc0..fe9827ac88 100644 --- a/src/view/screens/Lists.tsx +++ b/src/view/screens/Lists.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index 258a3c9b5f..da3463233c 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -16,6 +15,7 @@ import * as Layout from '#/components/Layout' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -68,7 +68,7 @@ export function ModerationBlockedAccounts({}: Props) { item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) => { if (!moderationOpts) return null @@ -113,7 +113,7 @@ export function ModerationBlockedAccounts({}: Props) { ) : ( item.did} + keyExtractor={(item: app.bsky.actor.defs.ProfileView) => item.did} refreshing={isPTRing} onRefresh={onRefresh} onEndReached={onEndReached} diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx index 4ef555a6c2..aa8adbacc6 100644 --- a/src/view/screens/ModerationModlists.tsx +++ b/src/view/screens/ModerationModlists.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 122464d301..cfdc4a7a80 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -16,6 +15,7 @@ import * as Layout from '#/components/Layout' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -68,7 +68,7 @@ export function ModerationMutedAccounts({}: Props) { item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) => { if (!moderationOpts) return null diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index ead74bae94..58c5aa1138 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -2,12 +2,8 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import {ScrollForwarderView} from 'react-native-scroll-forwarder' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, - RichText as RichTextAPI, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -33,7 +29,7 @@ import {useLabelerInfoQuery} from '#/state/queries/labeler' import {resetProfilePostsQueries} from '#/state/queries/post-feed' import {useProfileQuery} from '#/state/queries/profile' import {useResolveDidQuery} from '#/state/queries/resolve-uri' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession} from '#/state/session' import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens' import {ProfileLists} from '#/view/com/lists/ProfileLists' import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' @@ -54,6 +50,7 @@ import {VideoClip_Stroke1_Corner0_Rounded as VideoIcon} from '#/components/icons import * as Layout from '#/components/Layout' import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' +import {type app} from '#/lexicons' import {navigate} from '#/Navigation' interface SectionRef { @@ -168,7 +165,7 @@ function ProfileScreenLoaded({ moderationOpts, hideBackButton, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed moderationOpts: ModerationOpts hideBackButton: boolean isPlaceholderProfile: boolean @@ -613,7 +610,7 @@ function ProfileScreenLoaded({ } function useRichText(text: string): [RichTextAPI, boolean] { - const agent = useAgent() + const client = usePdsClient() const [prevText, setPrevText] = useState(text) const [rawRT, setRawRT] = useState(() => new RichTextAPI({text})) const [resolvedRT, setResolvedRT] = useState(null) @@ -628,7 +625,7 @@ function useRichText(text: string): [RichTextAPI, boolean] { async function resolveRTFacets() { // new each time const resolvedRT = new RichTextAPI({text}) - await resolvedRT.detectFacets(agent) + await resolvedRT.detectFacets(client) if (!ignore) { setResolvedRT(resolvedRT) } @@ -637,7 +634,7 @@ function useRichText(text: string): [RichTextAPI, boolean] { return () => { ignore = true } - }, [text, agent]) + }, [text, client]) const isResolving = resolvedRT === null return [resolvedRT ?? rawRT, isResolving] } diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index d777b09e86..a7ce796865 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation, useNavigationState} from '@react-navigation/native' @@ -86,6 +85,7 @@ import {useAgeAssurance} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {type Events} from '#/analytics/metrics/types' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {router} from '#/routes' import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army' @@ -239,7 +239,7 @@ function SwitchMenuItems({ accounts: | { account: SessionAccount - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed }[] | undefined signOutPromptControl: DialogControlProps @@ -350,7 +350,7 @@ function SwitchMenuItem({ profile, }: { account: SessionAccount - profile: AppBskyActorDefs.ProfileViewDetailed | undefined + profile: app.bsky.actor.defs.ProfileViewDetailed | undefined }) { const {t: l} = useLingui() const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()