diff options
author | Eric Bailey <git@esb.lol> | 2025-02-18 08:54:25 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-18 08:54:25 -0600 |
commit | a51fc8e434b63a3f85accbf1bd29a01397c4b057 (patch) | |
tree | 44be28fc8812c6d4e20f0784be3a558ede9283cb | |
parent | e2c0f78719a9e576ad18ef0e4657c63a3147efed (diff) | |
download | voidsky-a51fc8e434b63a3f85accbf1bd29a01397c4b057.tar.zst |
`@atproto/api@next` integration (#7344)
* Bump SDK * Use consistent type in profile query * Omit from constraint for profile shadow * Replace isRecord with isValidRecord in QuoteEmbed * Omit type from constraint for old ProfileCard * Omit type from constraint in profile queries where appropriate * Use correct type for update profile mutation * Conslidate and fix check for isValidRecord in Post.tsx * Replace isRecord with isValidRecord in PostThreadItem * Remove redundant cast in PostThreadFollowBtn * Ignore errors in DebugMod screen * Use matching type in ProfileFollows screen * Use matching type in ProfileFollowers screen * Migrate to isValidRecord in PostFeedItem * Use matching type if PostRepostedBy * Omit type from constraint in avatar props * Use matching types in NotificationFeedItem * Todo * Use isValidRecord in NotfyFeedItem * Improve MediaPreview types * Migrate another isValidRecord in NotificationFeedItem * Migrate to isValidView in queries/util * Migrate to isValidRecord in threadgate/util * Fix types in threadgates * Fix up types in starter-packs queries * Todo * Specify exact types in search-posts * Use internal type util to align types * Ditto last * Migrate postgate/index * Specify exact types in post-thread * Use correct type in post-quotes * FIX potential bug in post-thread * Use correct type in post-feed * Add correct type guards to notifications/feed * Migrate a guard in notifications/util * Migrate guard in Wizard/State * [@next] Profile handling, migrate `ProfileCard` (#7347) * Introduce new utils for profiles, migrate old ProfileCard * Rename, reorg * Add parseEmbed utils * Expand AnyProfileView to include chat profile view, update post shadow to reflect this * Cast for perf reasons * Tighten up types now that we have AnyProfileView * Add fastIsType util * Use `assertDid` Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com> * Use util types * Comment * Use fastIsType where no validation was happening before * suggestions (#7382) * suggestions * Revert unneeded changes --------- Co-authored-by: Eric Bailey <git@esb.lol> * Use new util * Rename to dangerousIsType * Convert object shape * Use dangerous util * Use dangerous util * Use dangerous util, we can trust post records * Use dangerous util * Use AnyProfileVIew * Convert object shape * Clean up handling * Patch moderateProfile to accept known profile views, to discuss * Add AnyStarterPackView and related, implement in first usage * Remove validation, fix type, fix ref * Migrate over list-conversations * Clarify intent behind precacheProfile and its unstable query cache * Clean up unstable profile cache * Fix types during label creation in PwiOptOut (#7346) * Tighten types in queries/list * Chat: use correct profile views * Chat: fix log type check * Chat: construct lexically correct shape, even though it's only internal usage * Chat: use correct profile types * Chat: fix type check in logs * Starter: use correct profile types * Starter: use correct profile types * Starter: tighten types to match lex * Any profile type will work in blocked-and-muted * Use dangerous util * Use dangerousIsType * Update new ProfileCard to use AnyProfileView * Use dangerousIsType * Remove outdated todo * Use correct profile type * Use correct profile types * Tighten up types * Use dangerousIsType * Chat: more type fixes * Remove unused file * Add a few utils * Remove unused file * Ignore feedPost.__source * Clean up types, leave validation in critical path * Use dangerousIstype * Use ANyProfileView * Use isValidRecord * Use dangerousIsType * Fix types in ListCard * Fix FeedInterstitials types * Fix types in FeedCard * Fix types in dms ReportDialog * Fix types in SearchablePeopleList * Fix bad type in composer opts * Starter: ok these need to be loose too * Clarify docs Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com> * Less code Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com> * Use package exports Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com> * Use package exports * Bump sdk, update $Typed imports * Format * Format * Fix weird TS error * Remove patch * Beter name * It's memo, can validate * Tighten up parseEmbed, dogfood * Bump sdk * Use asPredicate * Loosen types a bit * use asPredicate * Fix types * Use asPredicate * Use asPredicate * Use asPredicate * Clean up upsertProfile types * Use asPredicate * Use Un util * Fix types * Use new AnyProfileView * Use dangerousIsType * Use asPredicate * Use asPredicate * Add fallback content-type to pass typecheck * Clean up upsertProfile types * Align types * Use dangerousIsType * Use dangerousIsType * Use asPredicate * Align types * Convert findLast * Align types * Just ignore type errors and use findLast * Rename atproto -> bsky * Add validate util * Fix type error * Loosen types * Export post * rename atp bsky * Remove unused code * minor changes * Bump deps * Fix types * Tighten back up loose check * Tighten back up loose check * Fix small bug * Update comment * Revert change --------- Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com> Co-authored-by: Matthieu Sieben <matthieu.sieben@gmail.com>
93 files changed, 1030 insertions, 731 deletions
diff --git a/package.json b/package.json index 1b351e0d6..9d5c2e409 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.13.35", + "@atproto/api": "^0.14.0", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", @@ -212,7 +212,7 @@ "zod": "^3.20.2" }, "devDependencies": { - "@atproto/dev-env": "^0.3.67", + "@atproto/dev-env": "^0.3.87", "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/runtime": "^7.26.0", diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index de94d7e19..709d0631d 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,7 +1,6 @@ import React from 'react' import {GestureResponderEvent, View} from 'react-native' import { - AppBskyActorDefs, AppBskyFeedDefs, AppBskyGraphDefs, AtUri, @@ -32,6 +31,7 @@ import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import {RichText, RichTextProps} from '#/components/RichText' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' type Props = { view: AppBskyFeedDefs.GeneratorView @@ -115,7 +115,7 @@ export function TitleAndByline({ creator, }: { title: string - creator?: AppBskyActorDefs.ProfileViewBasic + creator?: bsky.profile.AnyProfileView }) { const t = useTheme() diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 926d27baa..eafed25e5 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -1,6 +1,7 @@ import React from 'react' -import {ScrollView, View} from 'react-native' -import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' +import {View} from 'react-native' +import {ScrollView} from 'react-native-gesture-handler' +import {AppBskyFeedDefs, AtUri} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -26,6 +27,7 @@ import {PersonPlus_Stroke2_Corner0_Rounded as Person} from '#/components/icons/P import {InlineLinkText} from '#/components/Link' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' import {ProgressGuideList} from './ProgressGuide/List' const MOBILE_CARD_WIDTH = 300 @@ -227,7 +229,7 @@ export function ProfileGrid({ viewContext = 'feed', }: { isSuggestionsLoading: boolean - profiles: AppBskyActorDefs.ProfileViewDetailed[] + profiles: bsky.profile.AnyProfileView[] recId?: number error: Error | null viewContext: 'profile' | 'feed' diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index b5c501039..1e7cf448a 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -10,6 +10,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Link, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' const AVI_SIZE = 30 const AVI_SIZE_SMALL = 20 @@ -33,7 +34,7 @@ export function KnownFollowers({ onLinkPress, minimal, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts onLinkPress?: LinkProps['onPress'] minimal?: boolean @@ -77,7 +78,7 @@ function KnownFollowersInner({ onLinkPress, minimal, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts cachedKnownFollowers: AppBskyActorDefs.KnownFollowers onLinkPress?: LinkProps['onPress'] diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx index ed5838fb0..30156ee0d 100644 --- a/src/components/ListCard.tsx +++ b/src/components/ListCard.tsx @@ -1,7 +1,6 @@ import React from 'react' import {View} from 'react-native' import { - AppBskyActorDefs, AppBskyGraphDefs, AtUri, moderateUserList, @@ -26,6 +25,7 @@ import { import {Link as InternalLink, LinkProps} from '#/components/Link' import * as Hider from '#/components/moderation/Hider' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' /* * This component is based on `FeedCard` and is tightly coupled with that @@ -107,7 +107,7 @@ export function TitleAndByline({ modUi, }: { title: string - creator?: AppBskyActorDefs.ProfileViewBasic + creator?: bsky.profile.AnyProfileView purpose?: AppBskyGraphDefs.ListView['purpose'] modUi?: ModerationUI }) { diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx index 9a05b54df..6e368e7dc 100644 --- a/src/components/MediaPreview.tsx +++ b/src/components/MediaPreview.tsx @@ -1,19 +1,15 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {Image} from 'expo-image' -import { - AppBskyEmbedExternal, - AppBskyEmbedImages, - AppBskyEmbedRecordWithMedia, - AppBskyEmbedVideo, -} from '@atproto/api' +import {AppBskyFeedDefs} from '@atproto/api' import {Trans} from '@lingui/macro' -import {parseTenorGif} from '#/lib/strings/embed-player' +import {isTenorGifUri} from '#/lib/strings/embed-player' import {atoms as a, useTheme} from '#/alf' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {Text} from '#/components/Typography' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' +import * as bsky from '#/types/bsky' /** * Streamlined MediaPreview component which just handles images, gifs, and videos @@ -22,20 +18,17 @@ export function Embed({ embed, style, }: { - embed?: - | AppBskyEmbedImages.View - | AppBskyEmbedRecordWithMedia.View - | AppBskyEmbedExternal.View - | AppBskyEmbedVideo.View - | {[k: string]: unknown} + embed: AppBskyFeedDefs.PostView['embed'] style?: StyleProp<ViewStyle> }) { - let media = AppBskyEmbedRecordWithMedia.isView(embed) ? embed.media : embed + const e = bsky.post.parseEmbed(embed) - if (AppBskyEmbedImages.isView(media)) { + if (!e) return null + + if (e.type === 'images') { return ( <Outer style={style}> - {media.images.map(image => ( + {e.view.images.map(image => ( <ImageItem key={image.thumb} thumbnail={image.thumb} @@ -44,28 +37,21 @@ export function Embed({ ))} </Outer> ) - } else if (AppBskyEmbedExternal.isView(media) && media.external.thumb) { - let url: URL | undefined - try { - url = new URL(media.external.uri) - } catch {} - if (url) { - const {success} = parseTenorGif(url) - if (success) { - return ( - <Outer style={style}> - <GifItem - thumbnail={media.external.thumb} - alt={media.external.title} - /> - </Outer> - ) - } - } - } else if (AppBskyEmbedVideo.isView(media)) { + } else if (e.type === 'link') { + if (!e.view.external.thumb) return null + if (!isTenorGifUri(e.view.external.uri)) return null + return ( + <Outer style={style}> + <GifItem + thumbnail={e.view.external.thumb} + alt={e.view.external.title} + /> + </Outer> + ) + } else if (e.type === 'video') { return ( <Outer style={style}> - <VideoItem thumbnail={media.thumbnail} alt={media.alt} /> + <VideoItem thumbnail={e.view.thumbnail} alt={e.view.alt} /> </Outer> ) } diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 78d86ab36..b56112dcf 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -1,7 +1,6 @@ import React from 'react' import {GestureResponderEvent, View} from 'react-native' import { - AppBskyActorDefs, moderateProfile, ModerationOpts, RichText as RichTextApi, @@ -25,13 +24,14 @@ import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus import {Link as InternalLink, LinkProps} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function Default({ profile, moderationOpts, logContext = 'ProfileCard', }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' }) { @@ -51,7 +51,7 @@ export function Card({ moderationOpts, logContext = 'ProfileCard', }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' }) { @@ -101,7 +101,7 @@ export function Link({ style, ...rest }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView } & Omit<LinkProps, 'to' | 'label'>) { const {_} = useLingui() return ( @@ -126,7 +126,7 @@ export function Avatar({ profile, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts }) { const moderation = moderateProfile(profile, moderationOpts) @@ -161,7 +161,7 @@ export function NameAndHandle({ profile, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts }) { const t = useTheme() @@ -224,17 +224,16 @@ export function Description({ profile: profileUnshadowed, numberOfLines = 3, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: bsky.profile.AnyProfileView numberOfLines?: number }) { const profile = useProfileShadow(profileUnshadowed) - const {description} = profile const rt = React.useMemo(() => { - if (!description) return - const rt = new RichTextApi({text: description || ''}) + if (!('description' in profile)) return + const rt = new RichTextApi({text: profile.description || ''}) rt.detectFacetsWithoutResolution() return rt - }, [description]) + }, [profile]) if (!rt) return null if ( profile.viewer && @@ -281,7 +280,7 @@ export function DescriptionPlaceholder({ } export type FollowButtonProps = { - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts logContext: LogEvents['profile:follow']['logContext'] & LogEvents['profile:unfollow']['logContext'] diff --git a/src/components/StarterPack/QrCode.tsx b/src/components/StarterPack/QrCode.tsx index 515a9059a..6443ec694 100644 --- a/src/components/StarterPack/QrCode.tsx +++ b/src/components/StarterPack/QrCode.tsx @@ -13,6 +13,7 @@ import {useTheme} from '#/alf' import {atoms as a} from '#/alf' import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' const LazyViewShot = React.lazy( // @ts-expect-error dynamic import @@ -30,7 +31,12 @@ export const QrCode = React.forwardRef<ViewShot, Props>(function QrCode( ) { const {record} = starterPack - if (!AppBskyGraphStarterpack.isRecord(record)) { + if ( + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + record, + AppBskyGraphStarterpack.isRecord, + ) + ) { return null } diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index 2feea0973..43d8b72da 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -18,6 +18,7 @@ import * as Dialog from '#/components/Dialog' import {DialogControlProps} from '#/components/Dialog' import {Loader} from '#/components/Loader' import {QrCode} from '#/components/StarterPack/QrCode' +import * as bsky from '#/types/bsky' export function QrCodeDialog({ starterPack, @@ -77,7 +78,12 @@ export function QrCodeDialog({ } else { setIsProcessing(true) - if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { + if ( + !bsky.validate( + starterPack.record, + AppBskyGraphStarterpack.validateRecord, + ) + ) { return } diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx index 2a9da509d..caa052726 100644 --- a/src/components/StarterPack/StarterPackCard.tsx +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -1,7 +1,7 @@ import React from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api' +import {AppBskyGraphStarterpack, AtUri} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' @@ -15,11 +15,12 @@ import {atoms as a, useTheme} from '#/alf' import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack' import {Link as BaseLink, LinkProps as BaseLinkProps} from '#/components/Link' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function Default({ starterPack, }: { - starterPack?: AppBskyGraphDefs.StarterPackViewBasic + starterPack?: bsky.starterPack.AnyStarterPackView }) { if (!starterPack) return null return ( @@ -32,7 +33,7 @@ export function Default({ export function Notification({ starterPack, }: { - starterPack?: AppBskyGraphDefs.StarterPackViewBasic + starterPack?: bsky.starterPack.AnyStarterPackView }) { if (!starterPack) return null return ( @@ -47,7 +48,7 @@ export function Card({ noIcon, noDescription, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: bsky.starterPack.AnyStarterPackView noIcon?: boolean noDescription?: boolean }) { @@ -57,7 +58,12 @@ export function Card({ const t = useTheme() const {currentAccount} = useSession() - if (!AppBskyGraphStarterpack.isRecord(record)) { + if ( + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + record, + AppBskyGraphStarterpack.isRecord, + ) + ) { return null } @@ -100,7 +106,7 @@ export function Link({ starterPack, children, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: bsky.starterPack.AnyStarterPackView onPress?: () => void children: BaseLinkProps['children'] }) { @@ -139,7 +145,7 @@ export function Link({ export function Embed({ starterPack, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: bsky.starterPack.AnyStarterPackView }) { const t = useTheme() const imageUri = getStarterPackOgCard(starterPack) diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index b67a8d302..5ce298842 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -38,7 +38,7 @@ export function WizardEditListDialog({ state: WizardState dispatch: (action: WizardAction) => void moderationOpts: ModerationOpts - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index 75d2bff60..e1a70a0b7 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -22,6 +22,7 @@ import {Button, ButtonText} from '#/components/Button' import * as Toggle from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' function WizardListCard({ type, @@ -123,7 +124,7 @@ export function WizardProfileCard({ btnType: 'checkbox' | 'remove' state: WizardState dispatch: (action: WizardAction) => void - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts }) { const {currentAccount} = useSession() diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx index cad5eb234..c28adad8b 100644 --- a/src/components/VideoPostCard.tsx +++ b/src/components/VideoPostCard.tsx @@ -27,6 +27,7 @@ import {Link} from '#/components/Link' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import * as Hider from '#/components/moderation/Hider' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' function getBlackColor(t: ReturnType<typeof useTheme>) { return select(t.name, { @@ -78,7 +79,12 @@ export function VideoPostCard({ if (!AppBskyEmbedVideo.isView(embed)) return null const author = post.author - const text = AppBskyFeedPost.isRecord(post.record) ? post.record?.text : '' + const text = bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) + ? post.record?.text + : '' const likeCount = post?.likeCount ?? 0 const repostCount = post?.repostCount ?? 0 const {thumbnail} = embed diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx index 7d74a50c6..29f4ac5bc 100644 --- a/src/components/WhoCanReply.tsx +++ b/src/components/WhoCanReply.tsx @@ -29,6 +29,7 @@ import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil' interface WhoCanReplyProps { @@ -48,7 +49,10 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { * unexpectedly, we should check to make sure it's for sure the root URI. */ const rootUri = - AppBskyFeedPost.isRecord(post.record) && post.record.reply?.root + bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) && post.record.reply?.root ? post.record.reply.root.uri : post.uri const settings = React.useMemo(() => { diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index f44692a2e..590f25dd3 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -1,10 +1,6 @@ import React, {useCallback} from 'react' import {Keyboard, Pressable, View} from 'react-native' -import { - AppBskyActorDefs, - ChatBskyConvoDefs, - ModerationCause, -} from '@atproto/api' +import {ChatBskyConvoDefs, ModerationCause} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -34,6 +30,7 @@ import { import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' +import * as bsky from '#/types/bsky' import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' import {ReportDialog} from './ReportDialog' @@ -49,7 +46,7 @@ let ConvoMenu = ({ style, }: { convo: ChatBskyConvoDefs.ConvoView - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile: Shadow<bsky.profile.AnyProfileView> control?: Menu.MenuControlProps currentScreen: 'list' | 'conversation' showMarkAsRead?: boolean @@ -148,7 +145,7 @@ function MenuContent({ blockedByListControl, }: { convo: ChatBskyConvoDefs.ConvoView - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile: Shadow<bsky.profile.AnyProfileView> showMarkAsRead?: boolean blockInfo: { listBlocks: ModerationCause[] diff --git a/src/components/dms/MessageProfileButton.tsx b/src/components/dms/MessageProfileButton.tsx index 22936b4c0..5eac7f5c5 100644 --- a/src/components/dms/MessageProfileButton.tsx +++ b/src/components/dms/MessageProfileButton.tsx @@ -19,7 +19,7 @@ import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog' export function MessageProfileButton({ profile, }: { - profile: AppBskyActorDefs.ProfileView + profile: AppBskyActorDefs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/dms/MessagesListBlockedFooter.tsx b/src/components/dms/MessagesListBlockedFooter.tsx index 19a7cc9c2..9c63ef2c7 100644 --- a/src/components/dms/MessagesListBlockedFooter.tsx +++ b/src/components/dms/MessagesListBlockedFooter.tsx @@ -1,6 +1,6 @@ import React from 'react' import {View} from 'react-native' -import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' +import {ModerationDecision} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -14,6 +14,7 @@ import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function MessagesListBlockedFooter({ recipient: initialRecipient, @@ -21,7 +22,7 @@ export function MessagesListBlockedFooter({ hasMessages, moderation, }: { - recipient: AppBskyActorDefs.ProfileViewBasic + recipient: bsky.profile.AnyProfileView convoId: string hasMessages: boolean moderation: ModerationDecision diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index f8d9b290d..7c35c30ba 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -17,6 +17,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names' import {isWeb} from '#/platform/detection' import {Shadow} from '#/state/cache/profile-shadow' import {isConvoActive, useConvo} from '#/state/messages/convo' +import {ConvoItem} from '#/state/messages/convo/types' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {ConvoMenu} from '#/components/dms/ConvoMenu' @@ -31,7 +32,7 @@ export let MessagesListHeader = ({ profile, moderation, }: { - profile?: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> moderation?: ModerationDecision }): React.ReactNode => { const t = useTheme() @@ -138,7 +139,7 @@ function HeaderReady({ moderation, blockInfo, }: { - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> moderation: ModerationDecision blockInfo: { listBlocks: ModerationCause[] @@ -157,8 +158,10 @@ function HeaderReady({ moderation.ui('displayName'), ) + // @ts-ignore findLast is polyfilled - esb const latestMessageFromOther = convoState.items.findLast( - item => item.type === 'message' && item.message.sender.did === profile.did, + (item: ConvoItem) => + item.type === 'message' && item.message.sender.did === profile.did, ) const latestReportableMessage = diff --git a/src/components/dms/ReportDialog.tsx b/src/components/dms/ReportDialog.tsx index af24a7246..71cca897a 100644 --- a/src/components/dms/ReportDialog.tsx +++ b/src/components/dms/ReportDialog.tsx @@ -1,6 +1,7 @@ import React, {memo, useMemo, useState} from 'react' import {View} from 'react-native' import { + $Typed, AppBskyActorDefs, ChatBskyConvoDefs, ComAtprotoModerationCreateReport, @@ -154,15 +155,16 @@ function SubmitStep({ mutationFn: async () => { if (params.type === 'convoMessage') { const {convoId, message} = params + const subject: $Typed<ChatBskyConvoDefs.MessageRef> = { + $type: 'chat.bsky.convo.defs#messageRef', + messageId: message.id, + convoId, + did: message.sender.did, + } const report = { reasonType: reportOption.reason, - subject: { - $type: 'chat.bsky.convo.defs#messageRef', - messageId: message.id, - convoId, - did: message.sender.did, - } satisfies ChatBskyConvoDefs.MessageRef, + subject, reason: details, } satisfies ComAtprotoModerationCreateReport.InputSchema @@ -285,7 +287,7 @@ function DoneStep({ }: { convoId: string currentScreen: 'list' | 'conversation' - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileViewDetailed }) { const {_} = useLingui() const navigation = useNavigation<NavigationProp>() diff --git a/src/components/dms/dialogs/SearchablePeopleList.tsx b/src/components/dms/dialogs/SearchablePeopleList.tsx index 9e15e2ba8..3ac0b3ab0 100644 --- a/src/components/dms/dialogs/SearchablePeopleList.tsx +++ b/src/components/dms/dialogs/SearchablePeopleList.tsx @@ -6,7 +6,7 @@ import React, { useState, } from 'react' import {TextInput, View} from 'react-native' -import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' +import {moderateProfile, ModerationOpts} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -28,13 +28,14 @@ import {useInteractionState} from '#/components/hooks/useInteractionState' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' type Item = | { type: 'profile' key: string enabled: boolean - profile: AppBskyActorDefs.ProfileView + profile: bsky.profile.AnyProfileView } | { type: 'empty' @@ -330,7 +331,7 @@ function ProfileCard({ onPress, }: { enabled: boolean - profile: AppBskyActorDefs.ProfileView + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts onPress: (did: string) => void }) { diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 003532d0c..7315f5fc9 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,6 +1,6 @@ -import {AppBskyActorDefs} from '@atproto/api' +import * as bsky from '#/types/bsky' -export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) { +export function canBeMessaged(profile: bsky.profile.AnyProfileView) { switch (profile.associated?.chat?.allowIncoming) { case 'none': return false diff --git a/src/components/hooks/useFollowMethods.ts b/src/components/hooks/useFollowMethods.ts index d67c3690f..e6b3f2c47 100644 --- a/src/components/hooks/useFollowMethods.ts +++ b/src/components/hooks/useFollowMethods.ts @@ -1,5 +1,4 @@ import React from 'react' -import {AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -9,12 +8,13 @@ import {Shadow} from '#/state/cache/types' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useRequireAuth} from '#/state/session' import * as Toast from '#/view/com/util/Toast' +import * as bsky from '#/types/bsky' export function useFollowMethods({ profile, logContext, }: { - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile: Shadow<bsky.profile.AnyProfileView> logContext: LogEvents['profile:follow']['logContext'] & LogEvents['profile:unfollow']['logContext'] }) { diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts index 4aa20fd12..a1b2e2bc9 100644 --- a/src/lib/api/feed-manip.ts +++ b/src/lib/api/feed-manip.ts @@ -6,6 +6,7 @@ import { AppBskyFeedPost, } from '@atproto/api' +import * as bsky from '#/types/bsky' import {isPostInLanguage} from '../../locale/helpers' import {FALLBACK_MARKER_POST} from './feed/home' import {ReasonFeedSource} from './feed/types' @@ -57,7 +58,9 @@ export class FeedViewPostsSlice { } this._feedPost = feedPost this._reactKey = `slice-${post.uri}-${ - feedPost.reason?.indexedAt || post.indexedAt + feedPost.reason && 'indexedAt' in feedPost.reason + ? feedPost.reason.indexedAt + : post.indexedAt }` if (feedPost.post.uri === FALLBACK_MARKER_POST.post.uri) { this.isFallbackMarker = true @@ -65,7 +68,7 @@ export class FeedViewPostsSlice { } if ( !AppBskyFeedPost.isRecord(post.record) || - !AppBskyFeedPost.validateRecord(post.record).success + !bsky.validate(post.record, AppBskyFeedPost.validateRecord) ) { return } @@ -97,7 +100,7 @@ export class FeedViewPostsSlice { if ( !AppBskyFeedDefs.isPostView(parent) || !AppBskyFeedPost.isRecord(parent.record) || - !AppBskyFeedPost.validateRecord(parent.record).success + !bsky.validate(parent.record, AppBskyFeedPost.validateRecord) ) { this.isOrphan = true return @@ -139,7 +142,7 @@ export class FeedViewPostsSlice { if ( !AppBskyFeedDefs.isPostView(root) || !AppBskyFeedPost.isRecord(root.record) || - !AppBskyFeedPost.validateRecord(root.record).success + !bsky.validate(root.record, AppBskyFeedPost.validateRecord) ) { this.isOrphan = true return diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 35c344055..7f8c1c275 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -311,6 +311,7 @@ class MergeFeedSource_Custom extends MergeFeedSource { ) // attach source info for (const post of res.data.feed) { + // @ts-ignore post.__source = this.sourceInfo } return res diff --git a/src/lib/api/hack-add-deleted-embed.ts b/src/lib/api/hack-add-deleted-embed.ts deleted file mode 100644 index 59aad21a2..000000000 --- a/src/lib/api/hack-add-deleted-embed.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - AppBskyFeedDefs, - AppBskyFeedPost, - ComAtprotoRepoStrongRef, -} from '@atproto/api' - -/** - * HACK - * The server doesnt seem to be correctly giving the notFound view yet - * so I'm adding it manually for now - * -prf - */ -export function hackAddDeletedEmbed(post: AppBskyFeedDefs.PostView) { - const record = post.record as AppBskyFeedPost.Record - if (record.embed?.$type === 'app.bsky.embed.record' && !post.embed) { - post.embed = { - $type: 'app.bsky.embed.record#view', - record: { - $type: 'app.bsky.embed.record#viewNotFound', - uri: (record.embed.record as ComAtprotoRepoStrongRef.Main).uri, - }, - } - } -} diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 5cc0d6336..d1f304d4a 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,4 +1,5 @@ import { + $Typed, AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, @@ -74,7 +75,7 @@ export async function post( } const did = agent.assertDid - const writes: ComAtprotoRepoApplyWrites.Create[] = [] + const writes: $Typed<ComAtprotoRepoApplyWrites.Create>[] = [] const uris: string[] = [] let now = new Date() @@ -91,7 +92,7 @@ export async function post( draft, opts.onStateChange, ) - let labels: ComAtprotoLabelDefs.SelfLabels | undefined + let labels: $Typed<ComAtprotoLabelDefs.SelfLabels> | undefined if (draft.labels.length) { labels = { $type: 'com.atproto.label.defs#selfLabels', @@ -230,11 +231,11 @@ async function resolveEmbed( draft: PostDraft, onStateChange: ((state: string) => void) | undefined, ): Promise< - | AppBskyEmbedImages.Main - | AppBskyEmbedVideo.Main - | AppBskyEmbedExternal.Main - | AppBskyEmbedRecord.Main - | AppBskyEmbedRecordWithMedia.Main + | $Typed<AppBskyEmbedImages.Main> + | $Typed<AppBskyEmbedVideo.Main> + | $Typed<AppBskyEmbedExternal.Main> + | $Typed<AppBskyEmbedRecord.Main> + | $Typed<AppBskyEmbedRecordWithMedia.Main> | undefined > { if (draft.embed.quote) { @@ -288,9 +289,9 @@ async function resolveMedia( embedDraft: EmbedDraft, onStateChange: ((state: string) => void) | undefined, ): Promise< - | AppBskyEmbedExternal.Main - | AppBskyEmbedImages.Main - | AppBskyEmbedVideo.Main + | $Typed<AppBskyEmbedExternal.Main> + | $Typed<AppBskyEmbedImages.Main> + | $Typed<AppBskyEmbedVideo.Main> | undefined > { if (embedDraft.media?.type === 'images') { diff --git a/src/lib/embeds.ts b/src/lib/embeds.ts deleted file mode 100644 index 2904f1cc3..000000000 --- a/src/lib/embeds.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedDefs, -} from '@atproto/api' - -export function isEmbedByEmbedder( - embed: AppBskyFeedDefs.PostView['embed'], - did: string, -): boolean { - if (!embed) { - return false - } - if (AppBskyEmbedRecord.isViewRecord(embed.record)) { - return embed.record.author.did === did - } - if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record.record) - ) { - return embed.record.record.author.did === did - } - return true -} diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts index 3be338ac8..11e334329 100644 --- a/src/lib/generate-starterpack.ts +++ b/src/lib/generate-starterpack.ts @@ -1,7 +1,9 @@ import { + $Typed, AppBskyActorDefs, AppBskyGraphGetStarterPack, BskyAgent, + ComAtprotoRepoApplyWrites, Facet, } from '@atproto/api' import {msg} from '@lingui/macro' @@ -13,6 +15,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {enforceLen} from '#/lib/strings/helpers' import {useAgent} from '#/state/session' +import * as bsky from '#/types/bsky' export const createStarterPackList = async ({ name, @@ -24,7 +27,7 @@ export const createStarterPackList = async ({ name: string description?: string descriptionFacets?: Facet[] - profiles: AppBskyActorDefs.ProfileViewBasic[] + profiles: bsky.profile.AnyProfileView[] agent: BskyAgent }): Promise<{uri: string; cid: string}> => { if (profiles.length === 0) throw new Error('No profiles given') @@ -68,8 +71,8 @@ export function useGenerateStarterPackMutation({ return useMutation<{uri: string; cid: string}, Error, void>({ mutationFn: async () => { - let profile: AppBskyActorDefs.ProfileViewBasic | undefined - let profiles: AppBskyActorDefs.ProfileViewBasic[] | undefined + let profile: AppBskyActorDefs.ProfileViewDetailed | undefined + let profiles: AppBskyActorDefs.ProfileView[] | undefined await Promise.all([ (async () => { @@ -136,7 +139,13 @@ export function useGenerateStarterPackMutation({ }) } -function createListItem({did, listUri}: {did: string; listUri: string}) { +function createListItem({ + did, + listUri, +}: { + did: string + listUri: string +}): $Typed<ComAtprotoRepoApplyWrites.Create> { return { $type: 'com.atproto.repo.applyWrites#create', collection: 'app.bsky.graph.listitem', diff --git a/src/lib/moderation/blocked-and-muted.ts b/src/lib/moderation/blocked-and-muted.ts index 18e6ef3e3..27c461a3d 100644 --- a/src/lib/moderation/blocked-and-muted.ts +++ b/src/lib/moderation/blocked-and-muted.ts @@ -1,17 +1,9 @@ -import {AppBskyActorDefs} from '@atproto/api' +import * as bsky from '#/types/bsky' -export function isBlockedOrBlocking( - profile: - | AppBskyActorDefs.ProfileViewBasic - | AppBskyActorDefs.ProfileViewDetailed, -) { +export function isBlockedOrBlocking(profile: bsky.profile.AnyProfileView) { return profile.viewer?.blockedBy || profile.viewer?.blocking } -export function isMuted( - profile: - | AppBskyActorDefs.ProfileViewBasic - | AppBskyActorDefs.ProfileViewDetailed, -) { +export function isMuted(profile: bsky.profile.AnyProfileView) { return profile.viewer?.muted || profile.viewer?.mutedByList } diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts index 9ee5128c8..0b3073b95 100644 --- a/src/lib/strings/embed-player.ts +++ b/src/lib/strings/embed-player.ts @@ -568,3 +568,12 @@ export function parseTenorGif(urlp: URL): dimensions, } } + +export function isTenorGifUri(url: URL | string) { + try { + return parseTenorGif(typeof url === 'string' ? new URL(url) : url).success + } catch { + // Invalid URL + return false + } +} diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts index ca3410015..ced947b59 100644 --- a/src/lib/strings/starter-pack.ts +++ b/src/lib/strings/starter-pack.ts @@ -1,4 +1,6 @@ -import {AppBskyGraphDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/api' + +import * as bsky from '#/types/bsky' export function createStarterPackLinkFromAndroidReferrer( referrerQueryString: string, @@ -79,7 +81,7 @@ export function httpStarterPackUriToAtUri(httpUri?: string): string | null { } export function getStarterPackOgCard( - didOrStarterPack: AppBskyGraphDefs.StarterPackView | string, + didOrStarterPack: bsky.starterPack.AnyStarterPackView | string, rkey?: string, ) { if (typeof didOrStarterPack === 'string') { diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index f51822952..69af0ea58 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -165,7 +165,7 @@ function InnerReady({ setHasScrolled, }: { moderation: ModerationDecision - recipient: Shadow<AppBskyActorDefs.ProfileViewBasic> + recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> hasScrolled: boolean setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> }) { diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index ad97497f4..501ab2374 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -1,7 +1,6 @@ import React, {useCallback, useMemo, useState} from 'react' import {GestureResponderEvent, View} from 'react-native' import { - AppBskyActorDefs, AppBskyEmbedRecord, ChatBskyConvoDefs, moderateProfile, @@ -44,6 +43,7 @@ import {Link} from '#/components/Link' import {useMenuControl} from '#/components/Menu' import {PostAlerts} from '#/components/moderation/PostAlerts' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export let ChatListItem = ({ convo, @@ -78,7 +78,7 @@ function ChatListItemReady({ moderationOpts, }: { convo: ChatBskyConvoDefs.ConvoView - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts }) { const t = useTheme() diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 6df0ef2fc..d368f05b6 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -30,6 +30,7 @@ import {ContentHider} from '#/components/moderation/ContentHider' import {PostAlerts} from '#/components/moderation/PostAlerts' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function useMessageEmbed() { const route = @@ -113,8 +114,10 @@ export function MessageInputEmbed({ const {rt, record} = useMemo(() => { if ( post && - AppBskyFeedPost.isRecord(post.record) && - AppBskyFeedPost.validateRecord(post.record).success + bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) ) { return { rt: new RichTextAPI({ diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 071ce1cd7..10a2b1d37 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -10,7 +10,12 @@ import Animated, { } from 'react-native-reanimated' import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {AppBskyEmbedRecord, AppBskyRichtextFacet, RichText} from '@atproto/api' +import { + $Typed, + AppBskyEmbedRecord, + AppBskyRichtextFacet, + RichText, +} from '@atproto/api' import {clamp} from '#/lib/numbers' import {ScrollProvider} from '#/lib/ScrollContext' @@ -297,7 +302,7 @@ export function MessagesList({ // we want to remove the post link from the text, re-trim, then detect facets rt.detectFacetsWithoutResolution() - let embed: AppBskyEmbedRecord.Main | undefined + let embed: $Typed<AppBskyEmbedRecord.Main> | undefined if (embedUri) { try { diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index 33d27cf66..d0b0cacca 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -1,6 +1,11 @@ import React from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' +import { + AppBskyActorProfile, + AppBskyGraphDefs, + AppBskyGraphStarterpack, + Un$Typed, +} from '@atproto/api' import {SavedFeed} from '@atproto/api/dist/client/types/app/bsky/actor/defs' import {TID} from '@atproto/common-web' import {msg, Trans} from '@lingui/macro' @@ -44,6 +49,7 @@ import {News2_Stroke2_Corner0_Rounded as News} from '#/components/icons/News2' import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function StepFinished() { const {_} = useLingui() @@ -141,29 +147,29 @@ export function StepFinished() { : undefined await agent.upsertProfile(async existing => { - existing = existing ?? {} + let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} if (blobPromise) { const res = await blobPromise if (res.data.blob) { - existing.avatar = res.data.blob + next.avatar = res.data.blob } } if (starterPack) { - existing.joinedViaStarterPack = { + next.joinedViaStarterPack = { uri: starterPack.uri, cid: starterPack.cid, } } - existing.displayName = '' + next.displayName = '' // HACKFIX // creating a bunch of identical profile objects is breaking the relay // tossing this unspecced field onto it to reduce the size of the problem // -prf - existing.createdAt = new Date().toISOString() - return existing + next.createdAt = new Date().toISOString() + return next }) logEvent('onboarding:finished:avatarResult', { @@ -205,9 +211,14 @@ export function StepFinished() { onboardDispatch({type: 'finish'}) logEvent('onboarding:finished:nextPressed', { usedStarterPack: Boolean(starterPack), - starterPackName: AppBskyGraphStarterpack.isRecord(starterPack?.record) - ? starterPack.record.name - : undefined, + starterPackName: + starterPack && + bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + starterPack.record, + AppBskyGraphStarterpack.isRecord, + ) + ? starterPack.record.name + : undefined, starterPackCreator: starterPack?.creator.did, starterPackUri: starterPack?.uri, profilesFollowed: listItems?.length ?? 0, diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index 14750f34c..d14c9562e 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -1,7 +1,9 @@ import { + $Typed, AppBskyGraphFollow, AppBskyGraphGetFollows, BskyAgent, + ComAtprotoRepoApplyWrites, } from '@atproto/api' import {TID} from '@atproto/common-web' import chunk from 'lodash.chunk' @@ -15,7 +17,7 @@ export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) { throw new Error(`bulkWriteFollows failed: no session`) } - const followRecords: AppBskyGraphFollow.Record[] = dids.map(did => { + const followRecords: $Typed<AppBskyGraphFollow.Record>[] = dids.map(did => { return { $type: 'app.bsky.graph.follow', subject: did, @@ -23,12 +25,13 @@ export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) { } }) - const followWrites = followRecords.map(r => ({ - $type: 'com.atproto.repo.applyWrites#create', - collection: 'app.bsky.graph.follow', - rkey: TID.nextStr(), - value: r, - })) + const followWrites: $Typed<ComAtprotoRepoApplyWrites.Create>[] = + followRecords.map(r => ({ + $type: 'com.atproto.repo.applyWrites#create', + collection: 'app.bsky.graph.follow', + rkey: TID.nextStr(), + value: r, + })) const chunks = chunk(followWrites, 50) for (const chunk of chunks) { diff --git a/src/screens/Profile/KnownFollowers.tsx b/src/screens/Profile/KnownFollowers.tsx index d6dd15c69..6b22a0add 100644 --- a/src/screens/Profile/KnownFollowers.tsx +++ b/src/screens/Profile/KnownFollowers.tsx @@ -21,7 +21,7 @@ function renderItem({ item, index, }: { - item: AppBskyActorDefs.ProfileViewBasic + item: AppBskyActorDefs.ProfileView index: number }) { return ( diff --git a/src/screens/Settings/components/PwiOptOut.tsx b/src/screens/Settings/components/PwiOptOut.tsx index 4339ade9b..e58514976 100644 --- a/src/screens/Settings/components/PwiOptOut.tsx +++ b/src/screens/Settings/components/PwiOptOut.tsx @@ -1,6 +1,6 @@ import React from 'react' import {View} from 'react-native' -import {ComAtprotoLabelDefs} from '@atproto/api' +import {$Typed, ComAtprotoLabelDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -12,6 +12,7 @@ import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import * as Toggle from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function PwiOptOut() { const t = useTheme() @@ -33,7 +34,10 @@ export function PwiOptOut() { profile, updates: existing => { // create labels attr if needed - existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) + const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate( + existing.labels, + ComAtprotoLabelDefs.validateSelfLabels, + ) ? existing.labels : { $type: 'com.atproto.label.defs#selfLabels', @@ -41,23 +45,26 @@ export function PwiOptOut() { } // toggle the label - const hasLabel = existing.labels.values.some( + const hasLabel = labels.values.some( l => l.val === '!no-unauthenticated', ) if (hasLabel) { wasAdded = false - existing.labels.values = existing.labels.values.filter( + labels.values = labels.values.filter( l => l.val !== '!no-unauthenticated', ) } else { wasAdded = true - existing.labels.values.push({val: '!no-unauthenticated'}) + labels.values.push({val: '!no-unauthenticated'}) } // delete if no longer needed - if (existing.labels.values.length === 0) { + if (labels.values.length === 0) { delete existing.labels + } else { + existing.labels = labels } + return existing }, checkCommitted: res => { diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index 5f406eb7a..e82d0da1c 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -26,6 +26,7 @@ import {Divider} from '#/components/Divider' import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' export function Signup({onPressBack}: {onPressBack: () => void}) { const {_} = useLingui() @@ -95,7 +96,10 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { scrollable> <View testID="createAccount" style={a.flex_1}> {showStarterPackCard && - AppBskyGraphStarterpack.isRecord(starterPack.record) ? ( + bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + starterPack.record, + AppBskyGraphStarterpack.isRecord, + ) ? ( <Animated.View entering={!isFetchedAtMount ? FadeIn : undefined}> <LinearGradientBackground style={[a.mx_lg, a.p_lg, a.gap_sm, a.rounded_sm]}> diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx index ec31fc21d..2d9a91969 100644 --- a/src/screens/StarterPack/StarterPackLandingScreen.tsx +++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx @@ -38,6 +38,7 @@ import {Default as ProfileCard} from '#/components/ProfileCard' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' const AnimatedPressable = Animated.createAnimatedComponent(Pressable) @@ -85,7 +86,12 @@ export function LandingScreen({ } // Just for types, this cannot be hit - if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { + if ( + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + starterPack.record, + AppBskyGraphStarterpack.isRecord, + ) + ) { return null } diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 3a3e4234f..ac61c153b 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -66,6 +66,7 @@ import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList' import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' import {ShareDialog} from '#/components/StarterPack/ShareDialog' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' type StarterPackScreeProps = NativeStackScreenProps< CommonNavigatorParams, @@ -387,7 +388,12 @@ function Header({ }) } - if (!AppBskyGraphStarterpack.isRecord(record)) { + if ( + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + record, + AppBskyGraphStarterpack.isRecord, + ) + ) { return null } diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx index f65933fbb..baf0195d8 100644 --- a/src/screens/StarterPack/Wizard/State.tsx +++ b/src/screens/StarterPack/Wizard/State.tsx @@ -1,15 +1,12 @@ import React from 'react' -import { - AppBskyActorDefs, - AppBskyGraphDefs, - AppBskyGraphStarterpack, -} from '@atproto/api' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {msg} from '@lingui/macro' import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' +import * as bsky from '#/types/bsky' const steps = ['Details', 'Profiles', 'Feeds'] as const type Step = (typeof steps)[number] @@ -20,7 +17,7 @@ type Action = | {type: 'SetCanNext'; canNext: boolean} | {type: 'SetName'; name: string} | {type: 'SetDescription'; description: string} - | {type: 'AddProfile'; profile: AppBskyActorDefs.ProfileViewBasic} + | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} | {type: 'RemoveProfile'; profileDid: string} | {type: 'AddFeed'; feed: GeneratorView} | {type: 'RemoveFeed'; feedUri: string} @@ -32,7 +29,7 @@ interface State { currentStep: Step name?: string description?: string - profiles: AppBskyActorDefs.ProfileViewBasic[] + profiles: bsky.profile.AnyProfileView[] feeds: GeneratorView[] processing: boolean error?: string @@ -113,7 +110,6 @@ function reducer(state: State, action: Action): State { return updatedState } -// TODO supply the initial state to this component export function Provider({ starterPack, listItems, @@ -126,7 +122,10 @@ export function Provider({ const {currentAccount} = useSession() const createInitialState = (): State => { - if (starterPack && AppBskyGraphStarterpack.isRecord(starterPack.record)) { + if ( + starterPack && + bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) + ) { return { canNext: true, currentStep: 'Details', diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index e13febc75..8a9a891e1 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -16,6 +16,7 @@ import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { return item?.did ?? '' @@ -50,7 +51,7 @@ export function StepProfiles({ const renderItem = ({ item, - }: ListRenderItemInfo<AppBskyActorDefs.ProfileViewBasic>) => { + }: ListRenderItemInfo<bsky.profile.AnyProfileView>) => { return ( <WizardProfileCard profile={item} diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx index 3f8ce3c00..92cad2f65 100644 --- a/src/screens/StarterPack/Wizard/index.tsx +++ b/src/screens/StarterPack/Wizard/index.tsx @@ -54,6 +54,7 @@ import {ListMaybePlaceholder} from '#/components/Lists' import {Loader} from '#/components/Loader' import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' import {Provider} from './State' export function Wizard({ @@ -141,7 +142,7 @@ function WizardInner({ }: { currentStarterPack?: AppBskyGraphDefs.StarterPackView currentListItems?: AppBskyGraphDefs.ListItemView[] - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileViewDetailed moderationOpts: ModerationOpts }) { const navigation = useNavigation<NavigationProp>() @@ -363,7 +364,7 @@ function Footer({ onNext: () => void nextBtnText: string moderationOpts: ModerationOpts - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() @@ -577,10 +578,10 @@ function Footer({ ) } -function getName(item: AppBskyActorDefs.ProfileViewBasic | GeneratorView) { +function getName(item: bsky.profile.AnyProfileView | GeneratorView) { if (typeof item.displayName === 'string') { return enforceLen(sanitizeDisplayName(item.displayName), 28, true) - } else if (typeof item.handle === 'string') { + } else if ('handle' in item && typeof item.handle === 'string') { return enforceLen(sanitizeHandle(item.handle), 28, true) } return '' diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 8198d45a3..04c2d7792 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -90,6 +90,7 @@ import {ListFooter} from '#/components/Lists' import * as Hider from '#/components/moderation/Hider' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import * as bsky from '#/types/bsky' import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' function createThreeVideoPlayers( @@ -694,7 +695,12 @@ function Overlay({ ) const rkey = new AtUri(post.uri).rkey - const record = AppBskyFeedPost.isRecord(post.record) ? post.record : undefined + const record = bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) + ? post.record + : undefined const richText = new RichTextAPI({ text: record?.text || '', facets: record?.facets, diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index 4d823ec8e..adbff3919 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -1,9 +1,9 @@ import {useEffect, useMemo, useState} from 'react' -import {AppBskyActorDefs} from '@atproto/api' import {QueryClient} from '@tanstack/react-query' import EventEmitter from 'eventemitter3' import {batchedUpdates} from '#/lib/batchedUpdates' +import * as bsky from '#/types/bsky' import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search' import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers' import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members' @@ -20,6 +20,7 @@ import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '../queries/profile-follows' import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '../queries/suggested-follows' import {castAsShadow, Shadow} from './types' + export type {Shadow} from './types' export interface ProfileShadow { @@ -29,13 +30,13 @@ export interface ProfileShadow { } const shadows: WeakMap< - AppBskyActorDefs.ProfileView, + bsky.profile.AnyProfileView, Partial<ProfileShadow> > = new WeakMap() const emitter = new EventEmitter() export function useProfileShadow< - TProfileView extends AppBskyActorDefs.ProfileView, + TProfileView extends bsky.profile.AnyProfileView, >(profile: TProfileView): Shadow<TProfileView> { const [shadow, setShadow] = useState(() => shadows.get(profile)) const [prevPost, setPrevPost] = useState(profile) @@ -68,7 +69,7 @@ export function useProfileShadow< * This is useful for when the profile is not guaranteed to be loaded yet. */ export function useMaybeProfileShadow< - TProfileView extends AppBskyActorDefs.ProfileView, + TProfileView extends bsky.profile.AnyProfileView, >(profile?: TProfileView): Shadow<TProfileView> | undefined { const [shadow, setShadow] = useState(() => profile ? shadows.get(profile) : undefined, @@ -115,7 +116,7 @@ export function updateProfileShadow( }) } -function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>( +function mergeShadow<TProfileView extends bsky.profile.AnyProfileView>( profile: TProfileView, shadow: Partial<ProfileShadow>, ): Shadow<TProfileView> { @@ -137,7 +138,7 @@ function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>( function* findProfilesInCache( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, void> { +): Generator<bsky.profile.AnyProfileView, void> { yield* findAllProfilesInListMembersQueryData(queryClient, did) yield* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did) yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did) diff --git a/src/state/cache/thread-mutes.tsx b/src/state/cache/thread-mutes.tsx index dc5104c14..4492977f2 100644 --- a/src/state/cache/thread-mutes.tsx +++ b/src/state/cache/thread-mutes.tsx @@ -69,6 +69,7 @@ function useMigrateMutes(setThreadMute: SetStateContext) { while (!cancelled) { const threads = persisted.get('mutedThreads') + // @ts-ignore findLast is polyfilled - esb const root = threads.findLast(uri => uri.includes(currentAccount.did)) if (!root) break diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 91dd59813..eed44c757 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -1,6 +1,6 @@ import { - AppBskyActorDefs, BskyAgent, + ChatBskyActorDefs, ChatBskyConvoDefs, ChatBskyConvoGetLog, ChatBskyConvoSendMessage, @@ -80,8 +80,8 @@ export class Convo { convoId: string convo: ChatBskyConvoDefs.ConvoView | undefined - sender: AppBskyActorDefs.ProfileViewBasic | undefined - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined + sender: ChatBskyActorDefs.ProfileViewBasic | undefined + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined snapshot: ConvoState | undefined constructor(params: ConvoParams) { @@ -463,7 +463,7 @@ export class Convo { throw new Error('Convo: could not find recipients in convo') } - const userIsDisabled = this.sender.chatDisabled as boolean + const userIsDisabled = Boolean(this.sender.chatDisabled) if (userIsDisabled) { this.dispatch({event: ConvoDispatchEvent.Disable}) @@ -529,8 +529,8 @@ export class Convo { private pendingFetchConvo: | Promise<{ convo: ChatBskyConvoDefs.ConvoView - sender: AppBskyActorDefs.ProfileViewBasic | undefined - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic | undefined + recipients: ChatBskyActorDefs.ProfileViewBasic[] }> | undefined async fetchConvo() { @@ -538,8 +538,8 @@ export class Convo { this.pendingFetchConvo = new Promise<{ convo: ChatBskyConvoDefs.ConvoView - sender: AppBskyActorDefs.ProfileViewBasic | undefined - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic | undefined + recipients: ChatBskyActorDefs.ProfileViewBasic[] }>(async (resolve, reject) => { try { const response = await networkRetry(2, () => { @@ -704,7 +704,7 @@ export class Convo { * If there's a rev, we should handle it. If there's not a rev, we don't * know what it is. */ - if (typeof ev.rev === 'string') { + if ('rev' in ev && typeof ev.rev === 'string') { const isUninitialized = !this.latestRev const isNewEvent = this.latestRev && ev.rev > this.latestRev @@ -1049,7 +1049,10 @@ export class Convo { * `getItems` is only run in "active" status states, where * `this.sender` is defined */ - sender: this.sender!, + sender: { + $type: 'chat.bsky.convo.defs#messageViewSender', + did: this.sender!.did, + }, }, nextMessage: null, prevMessage: null, diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 9f1707c71..69e15acc4 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -1,6 +1,6 @@ import { - AppBskyActorDefs, BskyAgent, + ChatBskyActorDefs, ChatBskyConvoDefs, ChatBskyConvoSendMessage, } from '@atproto/api' @@ -147,8 +147,8 @@ export type ConvoStateUninitialized = { items: [] convo: ChatBskyConvoDefs.ConvoView | undefined error: undefined - sender: AppBskyActorDefs.ProfileViewBasic | undefined - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined + sender: ChatBskyActorDefs.ProfileViewBasic | undefined + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined isFetchingHistory: false deleteMessage: undefined sendMessage: undefined @@ -159,8 +159,8 @@ export type ConvoStateInitializing = { items: [] convo: ChatBskyConvoDefs.ConvoView | undefined error: undefined - sender: AppBskyActorDefs.ProfileViewBasic | undefined - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined + sender: ChatBskyActorDefs.ProfileViewBasic | undefined + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined isFetchingHistory: boolean deleteMessage: undefined sendMessage: undefined @@ -171,8 +171,8 @@ export type ConvoStateReady = { items: ConvoItem[] convo: ChatBskyConvoDefs.ConvoView error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic + recipients: ChatBskyActorDefs.ProfileViewBasic[] isFetchingHistory: boolean deleteMessage: DeleteMessage sendMessage: SendMessage @@ -183,8 +183,8 @@ export type ConvoStateBackgrounded = { items: ConvoItem[] convo: ChatBskyConvoDefs.ConvoView error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic + recipients: ChatBskyActorDefs.ProfileViewBasic[] isFetchingHistory: boolean deleteMessage: DeleteMessage sendMessage: SendMessage @@ -195,8 +195,8 @@ export type ConvoStateSuspended = { items: ConvoItem[] convo: ChatBskyConvoDefs.ConvoView error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic + recipients: ChatBskyActorDefs.ProfileViewBasic[] isFetchingHistory: boolean deleteMessage: DeleteMessage sendMessage: SendMessage @@ -219,8 +219,8 @@ export type ConvoStateDisabled = { items: ConvoItem[] convo: ChatBskyConvoDefs.ConvoView error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] + sender: ChatBskyActorDefs.ProfileViewBasic + recipients: ChatBskyActorDefs.ProfileViewBasic[] isFetchingHistory: boolean deleteMessage: DeleteMessage sendMessage: SendMessage diff --git a/src/state/messages/events/agent.ts b/src/state/messages/events/agent.ts index 01165256a..9244a4fa5 100644 --- a/src/state/messages/events/agent.ts +++ b/src/state/messages/events/agent.ts @@ -65,10 +65,7 @@ export class MessagesEventBus { const handle = (event: MessagesEventBusEvent) => { if (event.type === 'logs' && options.convoId) { const filteredLogs = event.logs.filter(log => { - if ( - typeof log.convoId === 'string' && - log.convoId === options.convoId - ) { + if ('convoId' in log && log.convoId === options.convoId) { return log.convoId === options.convoId } return false @@ -355,7 +352,7 @@ export class MessagesEventBus { * If there's a rev, we should handle it. If there's not a rev, we don't * know what it is. */ - if (typeof ev.rev === 'string') { + if ('rev' in ev && typeof ev.rev === 'string') { /* * We only care about new events */ diff --git a/src/state/queries/list.ts b/src/state/queries/list.ts index be7542880..260a0bf2c 100644 --- a/src/state/queries/list.ts +++ b/src/state/queries/list.ts @@ -1,11 +1,14 @@ import {Image as RNImage} from 'react-native-image-crop-picker' import { + $Typed, AppBskyGraphDefs, AppBskyGraphGetList, AppBskyGraphList, AtUri, BskyAgent, + ComAtprotoRepoApplyWrites, Facet, + Un$Typed, } from '@atproto/api' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import chunk from 'lodash.chunk' @@ -68,7 +71,7 @@ export function useListCreateMutation() { ) { throw new Error('Invalid list purpose: must be curatelist or modlist') } - const record: AppBskyGraphList.Record = { + const record: Un$Typed<AppBskyGraphList.Record> = { purpose, name, description, @@ -212,7 +215,9 @@ export function useListDeleteMutation() { } // batch delete the list and listitem records - const createDel = (uri: string) => { + const createDel = ( + uri: string, + ): $Typed<ComAtprotoRepoApplyWrites.Delete> => { const urip = new AtUri(uri) return { $type: 'com.atproto.repo.applyWrites#delete', diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts index 828b85d9e..34fb10935 100644 --- a/src/state/queries/messages/actor-declaration.ts +++ b/src/state/queries/messages/actor-declaration.ts @@ -69,12 +69,10 @@ export function useDeleteActorDeclaration() { return useMutation({ mutationFn: async () => { if (!currentAccount) throw new Error('Not signed in') - // TODO(sam): remove validate: false once PDSes have the new lexicon const result = await agent.api.com.atproto.repo.deleteRecord({ repo: currentAccount.did, collection: 'chat.bsky.actor.declaration', rkey: 'self', - validate: false, }) return result }, diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx index ae379f962..8c9d6c429 100644 --- a/src/state/queries/messages/list-conversations.tsx +++ b/src/state/queries/messages/list-conversations.tsx @@ -101,7 +101,7 @@ export function ListConvosProviderInner({ events => { if (events.type !== 'logs') return - events.logs.forEach(log => { + for (const log of events.logs) { if (ChatBskyConvoDefs.isLogBeginConvo(log)) { debouncedRefetch() } else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) { @@ -110,30 +110,40 @@ export function ListConvosProviderInner({ ) } else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) { queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => - optimisticUpdate(log.convoId, old, convo => - log.message.id === convo.lastMessage?.id - ? { - ...convo, - rev: log.rev, - lastMessage: log.message, - } - : convo, - ), + optimisticUpdate(log.convoId, old, convo => { + if ( + (ChatBskyConvoDefs.isDeletedMessageView(log.message) || + ChatBskyConvoDefs.isMessageView(log.message)) && + (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) || + ChatBskyConvoDefs.isMessageView(convo.lastMessage)) + ) { + return log.message.id === convo.lastMessage.id + ? { + ...convo, + rev: log.rev, + lastMessage: log.message, + } + : convo + } else { + return convo + } + }), ) } else if (ChatBskyConvoDefs.isLogCreateMessage(log)) { + // Store in a new var to avoid TS errors due to closures. + const logRef: ChatBskyConvoDefs.LogCreateMessage = log + queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => { if (!old) return old function updateConvo(convo: ChatBskyConvoDefs.ConvoView) { - if (!ChatBskyConvoDefs.isLogCreateMessage(log)) return convo - let unreadCount = convo.unreadCount if (convo.id !== currentConvoId) { if ( - ChatBskyConvoDefs.isMessageView(log.message) || - ChatBskyConvoDefs.isDeletedMessageView(log.message) + ChatBskyConvoDefs.isMessageView(logRef.message) || + ChatBskyConvoDefs.isDeletedMessageView(logRef.message) ) { - if (log.message.sender.did !== currentAccount?.did) { + if (logRef.message.sender.did !== currentAccount?.did) { unreadCount++ } } @@ -143,8 +153,8 @@ export function ListConvosProviderInner({ return { ...convo, - rev: log.rev, - lastMessage: log.message, + rev: logRef.rev, + lastMessage: logRef.message, unreadCount, } } @@ -152,10 +162,10 @@ export function ListConvosProviderInner({ function filterConvoFromPage( convo: ChatBskyConvoDefs.ConvoView[], ) { - return convo.filter(c => c.id !== log.convoId) + return convo.filter(c => c.id !== logRef.convoId) } - const existingConvo = getConvoFromQueryData(log.convoId, old) + const existingConvo = getConvoFromQueryData(logRef.convoId, old) if (existingConvo) { return { @@ -186,7 +196,7 @@ export function ListConvosProviderInner({ } }) } - }) + } }, { // get events for all chats diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 72100a624..396994110 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -295,9 +295,11 @@ export function* findAllPostsInQueryData( } } - const quotedPost = getEmbeddedPost(item.subject?.embed) - if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { - yield embedViewRecordToPostView(quotedPost!) + if (AppBskyFeedDefs.isPostView(item.subject)) { + const quotedPost = getEmbeddedPost(item.subject?.embed) + if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { + yield embedViewRecordToPostView(quotedPost!) + } } } } @@ -307,7 +309,7 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, void> { +): Generator<AppBskyActorDefs.ProfileViewBasic, void> { const queryDatas = queryClient.getQueriesData<InfiniteData<FeedPage>>({ queryKey: [RQKEY_ROOT], }) @@ -323,9 +325,11 @@ export function* findAllProfilesInQueryData( ) { yield item.subject.author } - const quotedPost = getEmbeddedPost(item.subject?.embed) - if (quotedPost?.author.did === did) { - yield quotedPost.author + if (AppBskyFeedDefs.isPostView(item.subject)) { + const quotedPost = getEmbeddedPost(item.subject?.embed) + if (quotedPost?.author.did === did) { + yield quotedPost.author + } } } } diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 0d72e9e92..f6f53f58f 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -14,6 +14,7 @@ import {QueryClient} from '@tanstack/react-query' import chunk from 'lodash.chunk' import {labelIsHideableOffense} from '#/lib/moderation' +import * as bsky from '#/types/bsky' import {precacheProfile} from '../profile' import {FeedNotification, FeedPage, NotificationType} from './types' @@ -205,12 +206,9 @@ async function fetchSubjects( ), ) const postsMap = new Map<string, AppBskyFeedDefs.PostView>() - const packsMap = new Map<string, AppBskyGraphDefs.StarterPackView>() + const packsMap = new Map<string, AppBskyGraphDefs.StarterPackViewBasic>() for (const post of postsChunks.flat()) { - if ( - AppBskyFeedPost.isRecord(post.record) && - AppBskyFeedPost.validateRecord(post.record).success - ) { + if (AppBskyFeedPost.isRecord(post.record)) { postsMap.set(post.uri, post) } } @@ -255,8 +253,14 @@ function getSubjectUri( return notif.uri } else if (type === 'post-like' || type === 'repost') { if ( - AppBskyFeedRepost.isRecord(notif.record) || - AppBskyFeedLike.isRecord(notif.record) + bsky.dangerousIsType<AppBskyFeedRepost.Record>( + notif.record, + AppBskyFeedRepost.isRecord, + ) || + bsky.dangerousIsType<AppBskyFeedLike.Record>( + notif.record, + AppBskyFeedLike.isRecord, + ) ) { return typeof notif.record.subject?.uri === 'string' ? notif.record.subject?.uri diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 350970ffd..b29384e03 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -547,7 +547,7 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, undefined> { +): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> { const queryDatas = queryClient.getQueriesData< InfiniteData<FeedPageUnselected> >({ diff --git a/src/state/queries/post-quotes.ts b/src/state/queries/post-quotes.ts index be51eaab0..af9699d2b 100644 --- a/src/state/queries/post-quotes.ts +++ b/src/state/queries/post-quotes.ts @@ -70,7 +70,7 @@ export function usePostQuotesQuery(resolvedUri: string | undefined) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, void> { +): Generator<AppBskyActorDefs.ProfileViewBasic, void> { const queryDatas = queryClient.getQueriesData< InfiniteData<AppBskyFeedGetQuotes.OutputSchema> >({ diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index 79350c119..b1cd626cf 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -18,6 +18,7 @@ import { findAllProfilesInQueryData as findAllProfilesInSearchQueryData, } from '#/state/queries/search-posts' import {useAgent} from '#/state/session' +import * as bsky from '#/types/bsky' import { findAllPostsInQueryData as findAllPostsInNotifsQueryData, findAllProfilesInQueryData as findAllProfilesInNotifsQueryData, @@ -332,8 +333,10 @@ function responseToThreadNodes( ): ThreadNode { if ( AppBskyFeedDefs.isThreadViewPost(node) && - AppBskyFeedPost.isRecord(node.post.record) && - AppBskyFeedPost.validateRecord(node.post.record).success + bsky.dangerousIsType<AppBskyFeedPost.Record>( + node.post.record, + AppBskyFeedPost.isRecord, + ) ) { const post = node.post // These should normally be present. They're missing only for @@ -364,7 +367,7 @@ function responseToThreadNodes( depth, isHighlightedPost: depth === 0, hasMore: - direction === 'down' && !node.replies?.length && !!node.replyCount, + direction === 'down' && !node.replies?.length && !!post.replyCount, isSelfThread: false, // populated `annotateSelfThread` hasMoreSelfThread: false, // populated in `annotateSelfThread` }, @@ -497,7 +500,7 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, void> { +): Generator<AppBskyActorDefs.ProfileViewBasic, void> { const queryDatas = queryClient.getQueriesData<PostThreadQueryData>({ queryKey: [RQKEY_ROOT], }) diff --git a/src/state/queries/postgate/index.ts b/src/state/queries/postgate/index.ts index 149b9cbe9..346e7bfe2 100644 --- a/src/state/queries/postgate/index.ts +++ b/src/state/queries/postgate/index.ts @@ -21,6 +21,7 @@ import { POSTGATE_COLLECTION, } from '#/state/queries/postgate/util' import {useAgent} from '#/state/session' +import * as bsky from '#/types/bsky' export async function getPostgateRecord({ agent, @@ -60,7 +61,10 @@ export async function getPostgateRecord({ }), ) - if (data.value && AppBskyFeedPostgate.isRecord(data.value)) { + if ( + data.value && + bsky.validate(data.value, AppBskyFeedPostgate.validateRecord) + ) { return data.value } else { return undefined diff --git a/src/state/queries/postgate/util.ts b/src/state/queries/postgate/util.ts index 96762d38c..c1955cc74 100644 --- a/src/state/queries/postgate/util.ts +++ b/src/state/queries/postgate/util.ts @@ -1,4 +1,5 @@ import { + $Typed, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, @@ -45,8 +46,12 @@ export function mergePostgateRecords( }) } -export function createEmbedViewDetachedRecord({uri}: {uri: string}) { - const record: AppBskyEmbedRecord.ViewDetached = { +export function createEmbedViewDetachedRecord({ + uri, +}: { + uri: string +}): $Typed<AppBskyEmbedRecord.View> { + const record: $Typed<AppBskyEmbedRecord.ViewDetached> = { $type: 'app.bsky.embed.record#viewDetached', uri, detached: true, @@ -95,7 +100,7 @@ export function createMaybeDetachedQuoteEmbed({ export function createEmbedViewRecordFromPost( post: AppBskyFeedDefs.PostView, -): AppBskyEmbedRecord.ViewRecord { +): $Typed<AppBskyEmbedRecord.ViewRecord> { return { $type: 'app.bsky.embed.record#viewRecord', uri: post.uri, diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 291999ae1..2c98df634 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -8,6 +8,7 @@ import { AtUri, BskyAgent, ComAtprotoRepoUploadBlob, + Un$Typed, } from '@atproto/api' import { keepPreviousData, @@ -24,7 +25,12 @@ import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig' import {Shadow} from '#/state/cache/types' import {STALE} from '#/state/queries' import {resetProfilePostsQueries} from '#/state/queries/post-feed' +import { + unstableCacheProfileView, + useUnstableProfileViewCache, +} from '#/state/queries/unstable-profile-cache' import * as userActionHistory from '#/state/userActionHistory' +import * as bsky from '#/types/bsky' import {updateProfileShadow} from '../cache/profile-shadow' import {useAgent, useSession} from '../session' import { @@ -35,6 +41,12 @@ import {RQKEY as RQKEY_LIST_CONVOS} from './messages/list-conversations' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' +export * from '#/state/queries/unstable-profile-cache' +/** + * @deprecated use {@link unstableCacheProfileView} instead + */ +export const precacheProfile = unstableCacheProfileView + const RQKEY_ROOT = 'profile' export const RQKEY = (did: string) => [RQKEY_ROOT, did] @@ -44,12 +56,6 @@ export const profilesQueryKey = (handles: string[]) => [ handles, ] -const profileBasicQueryKeyRoot = 'profileBasic' -export const profileBasicQueryKey = (didOrHandle: string) => [ - profileBasicQueryKeyRoot, - didOrHandle, -] - export function useProfileQuery({ did, staleTime = STALE.SECONDS.FIFTEEN, @@ -57,8 +63,8 @@ export function useProfileQuery({ did: string | undefined staleTime?: number }) { - const queryClient = useQueryClient() const agent = useAgent() + const {getUnstableProfile} = useUnstableProfileViewCache() return useQuery<AppBskyActorDefs.ProfileViewDetailed>({ // WARNING // this staleTime is load-bearing @@ -73,10 +79,7 @@ export function useProfileQuery({ }, placeholderData: () => { if (!did) return - - return queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>( - profileBasicQueryKey(did), - ) + return getUnstableProfile(did) as AppBskyActorDefs.ProfileViewDetailed }, enabled: !!did, }) @@ -121,10 +124,12 @@ export function usePrefetchProfileQuery() { } interface ProfileUpdateParams { - profile: AppBskyActorDefs.ProfileView + profile: AppBskyActorDefs.ProfileViewDetailed updates: - | AppBskyActorProfile.Record - | ((existing: AppBskyActorProfile.Record) => AppBskyActorProfile.Record) + | Un$Typed<AppBskyActorProfile.Record> + | (( + existing: Un$Typed<AppBskyActorProfile.Record>, + ) => Un$Typed<AppBskyActorProfile.Record>) newUserAvatar?: RNImage | undefined | null newUserBanner?: RNImage | undefined | null checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean @@ -161,29 +166,29 @@ export function useProfileUpdateMutation() { ) } await agent.upsertProfile(async existing => { - existing = existing || {} + let next: Un$Typed<AppBskyActorProfile.Record> = existing || {} if (typeof updates === 'function') { - existing = updates(existing) + next = updates(next) } else { - existing.displayName = updates.displayName - existing.description = updates.description + next.displayName = updates.displayName + next.description = updates.description if ('pinnedPost' in updates) { - existing.pinnedPost = updates.pinnedPost + next.pinnedPost = updates.pinnedPost } } if (newUserAvatarPromise) { const res = await newUserAvatarPromise - existing.avatar = res.data.blob + next.avatar = res.data.blob } else if (newUserAvatar === null) { - existing.avatar = undefined + next.avatar = undefined } if (newUserBannerPromise) { const res = await newUserBannerPromise - existing.banner = res.data.blob + next.banner = res.data.blob } else if (newUserBanner === null) { - existing.banner = undefined + next.banner = undefined } - return existing + return next }) await whenAppViewReady( agent, @@ -228,7 +233,7 @@ export function useProfileUpdateMutation() { } export function useProfileFollowMutationQueue( - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, + profile: Shadow<bsky.profile.AnyProfileView>, logContext: LogEvents['profile:follow']['logContext'] & LogEvents['profile:follow']['logContext'], ) { @@ -302,7 +307,7 @@ export function useProfileFollowMutationQueue( function useProfileFollowMutation( logContext: LogEvents['profile:follow']['logContext'], - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, + profile: Shadow<bsky.profile.AnyProfileView>, ) { const {currentAccount} = useSession() const agent = useAgent() @@ -321,7 +326,10 @@ function useProfileFollowMutation( didBecomeMutual: profile.viewer ? Boolean(profile.viewer.followedBy) : undefined, - followeeClout: toClout(profile.followersCount), + followeeClout: + 'followersCount' in profile + ? toClout(profile.followersCount) + : undefined, followerClout: toClout(ownProfile?.followersCount), }) return await agent.follow(did) @@ -342,7 +350,7 @@ function useProfileUnfollowMutation( } export function useProfileMuteMutationQueue( - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, + profile: Shadow<bsky.profile.AnyProfileView>, ) { const queryClient = useQueryClient() const did = profile.did @@ -417,7 +425,7 @@ function useProfileUnmuteMutation() { } export function useProfileBlockMutationQueue( - profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, + profile: Shadow<bsky.profile.AnyProfileView>, ) { const queryClient = useQueryClient() const did = profile.did @@ -513,14 +521,6 @@ function useProfileUnblockMutation() { }) } -export function precacheProfile( - queryClient: QueryClient, - profile: AppBskyActorDefs.ProfileViewBasic, -) { - queryClient.setQueryData(profileBasicQueryKey(profile.handle), profile) - queryClient.setQueryData(profileBasicQueryKey(profile.did), profile) -} - async function whenAppViewReady( agent: BskyAgent, actor: string, diff --git a/src/state/queries/resolve-uri.ts b/src/state/queries/resolve-uri.ts index c1fd8e240..1422a2dae 100644 --- a/src/state/queries/resolve-uri.ts +++ b/src/state/queries/resolve-uri.ts @@ -1,14 +1,9 @@ -import {AppBskyActorDefs, AtUri} from '@atproto/api' -import { - QueryClient, - useQuery, - useQueryClient, - UseQueryResult, -} from '@tanstack/react-query' +import {AtUri} from '@atproto/api' +import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query' import {STALE} from '#/state/queries' import {useAgent} from '#/state/session' -import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from './profile' +import {useUnstableProfileViewCache} from './profile' const RQKEY_ROOT = 'resolved-did' export const RQKEY = (didOrHandle: string) => [RQKEY_ROOT, didOrHandle] @@ -28,8 +23,8 @@ export function useResolveUriQuery(uri: string | undefined): UriUseQueryResult { } export function useResolveDidQuery(didOrHandle: string | undefined) { - const queryClient = useQueryClient() const agent = useAgent() + const {getUnstableProfile} = useUnstableProfileViewCache() return useQuery<string, Error>({ staleTime: STALE.HOURS.ONE, @@ -45,11 +40,7 @@ export function useResolveDidQuery(didOrHandle: string | undefined) { initialData: () => { // Return undefined if no did or handle if (!didOrHandle) return - - const profile = - queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>( - RQKEY_PROFILE_BASIC(didOrHandle), - ) + const profile = getUnstableProfile(didOrHandle) return profile?.did }, enabled: !!didOrHandle, diff --git a/src/state/queries/search-posts.ts b/src/state/queries/search-posts.ts index 8a8a3fa52..d0bfd55df 100644 --- a/src/state/queries/search-posts.ts +++ b/src/state/queries/search-posts.ts @@ -174,7 +174,7 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileView, undefined> { +): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> { const queryDatas = queryClient.getQueriesData< InfiniteData<AppBskyFeedSearchPosts.OutputSchema> >({ diff --git a/src/state/queries/service-config.ts b/src/state/queries/service-config.ts index 9a9db7865..12d2cc6be 100644 --- a/src/state/queries/service-config.ts +++ b/src/state/queries/service-config.ts @@ -19,6 +19,7 @@ export function useServiceConfigQuery() { const {data} = await agent.api.app.bsky.unspecced.getConfig() return { checkEmailConfirmed: Boolean(data.checkEmailConfirmed), + // @ts-expect-error not included in types atm topicsEnabled: Boolean(data.topicsEnabled), } } catch (e) { diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts index b90a57037..5b39fa45f 100644 --- a/src/state/queries/starter-packs.ts +++ b/src/state/queries/starter-packs.ts @@ -1,5 +1,4 @@ import { - AppBskyActorDefs, AppBskyFeedDefs, AppBskyGraphDefs, AppBskyGraphGetStarterPack, @@ -29,6 +28,7 @@ import {invalidateActorStarterPacksQuery} from '#/state/queries/actor-starter-pa import {STALE} from '#/state/queries/index' import {invalidateListMembersQuery} from '#/state/queries/list-members' import {useAgent} from '#/state/session' +import * as bsky from '#/types/bsky' const RQKEY_ROOT = 'starter-pack' const RQKEY = ({ @@ -93,7 +93,7 @@ export async function invalidateStarterPack({ interface UseCreateStarterPackMutationParams { name: string description?: string - profiles: AppBskyActorDefs.ProfileViewBasic[] + profiles: bsky.profile.AnyProfileView[] feeds?: AppBskyFeedDefs.GeneratorView[] } @@ -131,7 +131,7 @@ export function useCreateStarterPackMutation({ return await agent.app.bsky.graph.starterpack.create( { - repo: agent.session?.did, + repo: agent.assertDid, }, { name, @@ -366,7 +366,10 @@ export async function precacheStarterPack( let starterPackView: AppBskyGraphDefs.StarterPackView | undefined if (AppBskyGraphDefs.isStarterPackView(starterPack)) { starterPackView = starterPack - } else if (AppBskyGraphDefs.isStarterPackViewBasic(starterPack)) { + } else if ( + AppBskyGraphDefs.isStarterPackViewBasic(starterPack) && + bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) + ) { const listView: AppBskyGraphDefs.ListViewBasic = { uri: starterPack.record.list, // This will be populated once the data from server is fetched diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index 8aa932081..478658fe8 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -20,6 +20,7 @@ import { } from '#/state/queries/threadgate/util' import {useAgent} from '#/state/session' import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies' +import * as bsky from '#/types/bsky' export * from '#/state/queries/threadgate/types' export * from '#/state/queries/threadgate/util' @@ -138,7 +139,10 @@ export async function getThreadgateRecord({ }), ) - if (data.value && AppBskyFeedThreadgate.isRecord(data.value)) { + if ( + data.value && + bsky.validate(data.value, AppBskyFeedThreadgate.validateRecord) + ) { return data.value } else { return null diff --git a/src/state/queries/threadgate/types.ts b/src/state/queries/threadgate/types.ts index 56eadabcd..bbe677ad4 100644 --- a/src/state/queries/threadgate/types.ts +++ b/src/state/queries/threadgate/types.ts @@ -4,4 +4,4 @@ export type ThreadgateAllowUISetting = | {type: 'mention'} | {type: 'following'} | {type: 'followers'} - | {type: 'list'; list: unknown} + | {type: 'list'; list: string} diff --git a/src/state/queries/threadgate/util.ts b/src/state/queries/threadgate/util.ts index 4459eddbe..cbe8d4695 100644 --- a/src/state/queries/threadgate/util.ts +++ b/src/state/queries/threadgate/util.ts @@ -1,14 +1,15 @@ import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' +import * as bsky from '#/types/bsky' export function threadgateViewToAllowUISetting( threadgateView: AppBskyFeedDefs.ThreadgateView | undefined, ): ThreadgateAllowUISetting[] { + // Validate the record for clarity, since backwards compat code is a little confusing const threadgate = threadgateView && - AppBskyFeedThreadgate.isRecord(threadgateView.record) && - AppBskyFeedThreadgate.validateRecord(threadgateView.record).success + bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord) ? threadgateView.record : undefined return threadgateRecordToAllowUISetting(threadgate) @@ -39,14 +40,14 @@ export function threadgateRecordToAllowUISetting( const settings: ThreadgateAllowUISetting[] = threadgate.allow .map(allow => { let setting: ThreadgateAllowUISetting | undefined - if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') { + if (AppBskyFeedThreadgate.isMentionRule(allow)) { setting = {type: 'mention'} - } else if (allow.$type === 'app.bsky.feed.threadgate#followingRule') { + } else if (AppBskyFeedThreadgate.isFollowingRule(allow)) { setting = {type: 'following'} - } else if (allow.$type === 'app.bsky.feed.threadgate#followerRule') { - setting = {type: 'followers'} - } else if (allow.$type === 'app.bsky.feed.threadgate#listRule') { + } else if (AppBskyFeedThreadgate.isListRule(allow)) { setting = {type: 'list', list: allow.list} + } else if (AppBskyFeedThreadgate.isFollowerRule(allow)) { + setting = {type: 'followers'} } return setting }) @@ -69,11 +70,7 @@ export function threadgateAllowUISettingToAllowRecordValue( return undefined } - let allow: ( - | AppBskyFeedThreadgate.MentionRule - | AppBskyFeedThreadgate.FollowingRule - | AppBskyFeedThreadgate.ListRule - )[] = [] + let allow: Exclude<AppBskyFeedThreadgate.Record['allow'], undefined> = [] if (!threadgate.find(v => v.type === 'nobody')) { for (const rule of threadgate) { diff --git a/src/state/queries/unstable-profile-cache.ts b/src/state/queries/unstable-profile-cache.ts new file mode 100644 index 000000000..4ac5001b7 --- /dev/null +++ b/src/state/queries/unstable-profile-cache.ts @@ -0,0 +1,51 @@ +import {useCallback} from 'react' +import {QueryClient, useQueryClient} from '@tanstack/react-query' + +import * as bsky from '#/types/bsky' + +const unstableProfileViewCacheQueryKeyRoot = 'unstableProfileViewCache' +export const unstableProfileViewCacheQueryKey = (didOrHandle: string) => [ + unstableProfileViewCacheQueryKeyRoot, + didOrHandle, +] + +/** + * Used as a rough cache of profile views to make loading snappier. This method + * accepts and stores any profile view type by both handle and DID. + * + * Access the cache via {@link useUnstableProfileViewCache}. + */ +export function unstableCacheProfileView( + queryClient: QueryClient, + profile: bsky.profile.AnyProfileView, +) { + queryClient.setQueryData( + unstableProfileViewCacheQueryKey(profile.handle), + profile, + ) + queryClient.setQueryData( + unstableProfileViewCacheQueryKey(profile.did), + profile, + ) +} + +/** + * Hook to access the unstable profile view cache. This cache can return ANY + * profile view type, so if the object shape is important, you need to use the + * identity validators shipped in the atproto SDK e.g. + * `AppBskyActorDefs.isValidProfileViewBasic` to confirm before using. + * + * To cache a profile, use {@link unstableCacheProfileView}. + */ +export function useUnstableProfileViewCache() { + const qc = useQueryClient() + const getUnstableProfile = useCallback( + (didOrHandle: string) => { + return qc.getQueryData<bsky.profile.AnyProfileView>( + unstableProfileViewCacheQueryKey(didOrHandle), + ) + }, + [qc], + ) + return {getUnstableProfile} +} diff --git a/src/state/queries/util.ts b/src/state/queries/util.ts index 887c1df0a..71d185bec 100644 --- a/src/state/queries/util.ts +++ b/src/state/queries/util.ts @@ -8,6 +8,8 @@ import { } from '@atproto/api' import {InfiniteData, QueryClient, QueryKey} from '@tanstack/react-query' +import * as bsky from '#/types/bsky' + export async function truncateAndInvalidate<T = any>( queryClient: QueryClient, queryKey: QueryKey, @@ -44,7 +46,9 @@ export function didOrHandleUriMatches( export function getEmbeddedPost( v: unknown, ): AppBskyEmbedRecord.ViewRecord | undefined { - if (AppBskyEmbedRecord.isView(v)) { + if ( + bsky.dangerousIsType<AppBskyEmbedRecord.View>(v, AppBskyEmbedRecord.isView) + ) { if ( AppBskyEmbedRecord.isViewRecord(v.record) && AppBskyFeedPost.isRecord(v.record.value) @@ -52,7 +56,12 @@ export function getEmbeddedPost( return v.record } } - if (AppBskyEmbedRecordWithMedia.isView(v)) { + if ( + bsky.dangerousIsType<AppBskyEmbedRecordWithMedia.View>( + v, + AppBskyEmbedRecordWithMedia.isView, + ) + ) { if ( AppBskyEmbedRecord.isViewRecord(v.record.record) && AppBskyFeedPost.isRecord(v.record.record.value) diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index f1ea41c64..33634c047 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -1,7 +1,6 @@ import React from 'react' import { AppBskyActorDefs, - AppBskyEmbedRecord, AppBskyFeedDefs, ModerationDecision, } from '@atproto/api' @@ -21,7 +20,7 @@ export interface ComposerOptsPostRef { cid: string text: string author: AppBskyActorDefs.ProfileViewBasic - embed?: AppBskyEmbedRecord.ViewRecord['embed'] + embed?: AppBskyFeedDefs.PostView['embed'] moderation?: ModerationDecision } diff --git a/src/types/bsky/index.ts b/src/types/bsky/index.ts new file mode 100644 index 000000000..d5acbdbb5 --- /dev/null +++ b/src/types/bsky/index.ts @@ -0,0 +1,51 @@ +import {ValidationResult} from '@atproto/lexicon' + +export * as post from '#/types/bsky/post' +export * as profile from '#/types/bsky/profile' +export * as starterPack from '#/types/bsky/starterPack' + +/** + * Fast type checking without full schema validation, for use with data we + * trust, or for non-critical path use cases. Why? Our SDK's `is*` identity + * utils do not assert the type of the entire object, only the `$type` string. + * + * For full validation of the object schema, use the `validate` export from + * this file. + * + * Usage: + * ```ts + * import * as bsky from '#/types/bsky' + * + * if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) { + * // `item` has type `$Typed<AppBskyFeedPost.Record>` here + * } + * ``` + */ +export function dangerousIsType<R extends {$type?: string}>( + record: unknown, + identity: <V>(v: V) => v is V & {$type: NonNullable<R['$type']>}, +): record is R { + return identity(record) +} + +/** + * Fully validates the object schema, which has a performance cost. + * + * For faster checks with data we trust, like that from our app view, use the + * `dangerousIsType` export from this same file. + * + * Usage: + * ```ts + * import * as bsky from '#/types/bsky' + * + * if (bsky.validate(item, AppBskyFeedPost.validateRecord)) { + * // `item` has type `$Typed<AppBskyFeedPost.Record>` here + * } + * ``` + */ +export function validate<R extends {$type?: string}>( + record: unknown, + validator: (v: unknown) => ValidationResult<R>, +): record is R { + return validator(record).success +} diff --git a/src/types/bsky/post.ts b/src/types/bsky/post.ts new file mode 100644 index 000000000..225726f41 --- /dev/null +++ b/src/types/bsky/post.ts @@ -0,0 +1,148 @@ +import { + AppBskyEmbedExternal, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyEmbedVideo, + AppBskyFeedDefs, + AppBskyGraphDefs, + AppBskyLabelerDefs, +} from '@atproto/api' + +export type Embed = + | { + type: 'post' + view: AppBskyEmbedRecord.ViewRecord + } + | { + type: 'post_not_found' + view: AppBskyEmbedRecord.ViewNotFound + } + | { + type: 'post_blocked' + view: AppBskyEmbedRecord.ViewBlocked + } + | { + type: 'post_detached' + view: AppBskyEmbedRecord.ViewDetached + } + | { + type: 'feed' + view: AppBskyFeedDefs.GeneratorView + } + | { + type: 'list' + view: AppBskyGraphDefs.ListView + } + | { + type: 'labeler' + view: AppBskyLabelerDefs.LabelerView + } + | { + type: 'starter_pack' + view: AppBskyGraphDefs.StarterPackViewBasic + } + | { + type: 'images' + view: AppBskyEmbedImages.View + } + | { + type: 'link' + view: AppBskyEmbedExternal.View + } + | { + type: 'video' + view: AppBskyEmbedVideo.View + } + | { + type: 'post_with_media' + view: Embed + media: Embed + } + | { + type: 'unknown' + view: null + } + +export type EmbedType<T extends Embed['type']> = Extract<Embed, {type: T}> + +export function parseEmbedRecordView({record}: AppBskyEmbedRecord.View): Embed { + if (AppBskyEmbedRecord.isViewRecord(record)) { + return { + type: 'post', + view: record, + } + } else if (AppBskyEmbedRecord.isViewNotFound(record)) { + return { + type: 'post_not_found', + view: record, + } + } else if (AppBskyEmbedRecord.isViewBlocked(record)) { + return { + type: 'post_blocked', + view: record, + } + } else if (AppBskyEmbedRecord.isViewDetached(record)) { + return { + type: 'post_detached', + view: record, + } + } else if (AppBskyFeedDefs.isGeneratorView(record)) { + return { + type: 'feed', + view: record, + } + } else if (AppBskyGraphDefs.isListView(record)) { + return { + type: 'list', + view: record, + } + } else if (AppBskyLabelerDefs.isLabelerView(record)) { + return { + type: 'labeler', + view: record, + } + } else if (AppBskyGraphDefs.isStarterPackViewBasic(record)) { + return { + type: 'starter_pack', + view: record, + } + } else { + return { + type: 'unknown', + view: null, + } + } +} + +export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed { + if (AppBskyEmbedImages.isView(embed)) { + return { + type: 'images', + view: embed, + } + } else if (AppBskyEmbedExternal.isView(embed)) { + return { + type: 'link', + view: embed, + } + } else if (AppBskyEmbedVideo.isView(embed)) { + return { + type: 'video', + view: embed, + } + } else if (AppBskyEmbedRecord.isView(embed)) { + return parseEmbedRecordView(embed) + } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { + return { + type: 'post_with_media', + view: parseEmbedRecordView(embed.record), + media: parseEmbed(embed.media), + } + } else { + return { + type: 'unknown', + view: null, + } + } +} diff --git a/src/types/bsky/profile.ts b/src/types/bsky/profile.ts new file mode 100644 index 000000000..7449f117e --- /dev/null +++ b/src/types/bsky/profile.ts @@ -0,0 +1,10 @@ +import {AppBskyActorDefs, ChatBskyActorDefs} from '@atproto/api' + +/** + * Matches any profile view exported by our SDK + */ +export type AnyProfileView = + | AppBskyActorDefs.ProfileViewBasic + | AppBskyActorDefs.ProfileView + | AppBskyActorDefs.ProfileViewDetailed + | ChatBskyActorDefs.ProfileViewBasic diff --git a/src/types/bsky/starterPack.ts b/src/types/bsky/starterPack.ts new file mode 100644 index 000000000..0064e16bc --- /dev/null +++ b/src/types/bsky/starterPack.ts @@ -0,0 +1,11 @@ +import {AppBskyGraphDefs} from '@atproto/api' + +export const isBasicView = AppBskyGraphDefs.isStarterPackViewBasic +export const isView = AppBskyGraphDefs.isStarterPackView + +/** + * Matches any starter pack view exported by our SDK + */ +export type AnyStarterPackView = + | AppBskyGraphDefs.StarterPackViewBasic + | AppBskyGraphDefs.StarterPackView diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 0caae6701..31d2b5fb5 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -1,6 +1,6 @@ import React, {useCallback} from 'react' import {Dimensions, StyleProp, View, ViewStyle} from 'react-native' -import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' +import {AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -11,6 +11,7 @@ import {useModalControls} from '#/state/modals' import {useListMembersQuery} from '#/state/queries/list-members' import {useSession} from '#/state/session' import {ListFooter} from '#/components/Lists' +import * as bsky from '#/types/bsky' import {ProfileCard} from '../profile/ProfileCard' import {ErrorMessage} from '../util/error/ErrorMessage' import {Button} from '../util/forms/Button' @@ -116,7 +117,7 @@ export function ListMembers({ }, [fetchNextPage]) const onPressEditMembership = React.useCallback( - (profile: AppBskyActorDefs.ProfileViewBasic) => { + (profile: bsky.profile.AnyProfileView) => { openModal({ name: 'user-add-remove-lists', subject: profile.did, @@ -131,7 +132,7 @@ export function ListMembers({ // = const renderMemberButton = React.useCallback( - (profile: AppBskyActorDefs.ProfileViewBasic) => { + (profile: bsky.profile.AnyProfileView) => { if (!isOwner) { return null } diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 1267ce089..84694fe3b 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -58,6 +58,7 @@ import * as MediaPreview from '#/components/MediaPreview' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {SubtleWebHover} from '#/components/SubtleWebHover' +import * as bsky from '#/types/bsky' import {FeedSourceCard} from '../feeds/FeedSourceCard' import {Post} from '../post/Post' import {Link, TextLink} from '../util/Link' @@ -71,7 +72,7 @@ const MAX_AUTHORS = 5 const EXPANDED_AUTHOR_EL_HEIGHT = 35 interface Author { - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileView href: string moderation: ModerationDecision } @@ -264,7 +265,10 @@ let NotificationFeedItem = ({ if ( item.notification.author.viewer?.following && - AppBskyGraphFollow.isRecord(item.notification.record) + bsky.dangerousIsType<AppBskyGraphFollow.Record>( + item.notification.record, + AppBskyGraphFollow.isRecord, + ) ) { let followingTimestamp try { @@ -521,7 +525,7 @@ function ExpandListPressable({ } } -function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) { +function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { const {_} = useLingui() const agent = useAgent() const navigation = useNavigation<NavigationProp>() @@ -716,7 +720,13 @@ function ExpandedAuthorsList({ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { const pal = usePalette('default') - if (post && AppBskyFeedPost.isRecord(post?.record)) { + if ( + post && + bsky.dangerousIsType<AppBskyFeedPost.Record>( + post?.record, + AppBskyFeedPost.isRecord, + ) + ) { const text = post.record.text return ( diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 2143bd9c2..c27e37f49 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -16,7 +16,7 @@ function renderItem({ item, index, }: { - item: ActorDefs.ProfileViewBasic + item: ActorDefs.ProfileView index: number }) { return ( diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx index 9dc93916a..145e919f9 100644 --- a/src/view/com/post-thread/PostThreadFollowBtn.tsx +++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx @@ -5,7 +5,7 @@ import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {logger} from '#/logger' -import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow' +import {useProfileShadow} from '#/state/cache/profile-shadow' import { useProfileFollowMutationQueue, useProfileQuery, @@ -35,8 +35,7 @@ function PostThreadFollowBtnLoaded({ const navigation = useNavigation() const {_} = useLingui() const {gtMobile} = useBreakpoints() - const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> = - useProfileShadow(profileUnshadowed) + const profile = useProfileShadow(profileUnshadowed) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profile, 'PostThreadItem', diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 928ccd783..024629198 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -58,6 +58,7 @@ import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' import {Text} from '#/components/Typography' import {WhoCanReply} from '#/components/WhoCanReply' +import * as bsky from '#/types/bsky' export function PostThreadItem({ post, @@ -790,7 +791,10 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { const control = Prompt.usePromptControl() const indexedAt = new Date(post.indexedAt) - const createdAt = AppBskyFeedPost.isRecord(post.record) + const createdAt = bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) ? new Date(post.record.createdAt) : new Date(post.indexedAt) diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index c87e361e1..2645237ad 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -28,6 +28,7 @@ import {atoms as a} from '#/alf' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' +import * as bsky from '#/types/bsky' import {ContentHider} from '../../../components/moderation/ContentHider' import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {PostAlerts} from '../../../components/moderation/PostAlerts' @@ -53,8 +54,7 @@ export function Post({ const moderationOpts = useModerationOpts() const record = useMemo<AppBskyFeedPost.Record | undefined>( () => - AppBskyFeedPost.isRecord(post.record) && - AppBskyFeedPost.validateRecord(post.record).success + bsky.validate(post.record, AppBskyFeedPost.validateRecord) ? post.record : undefined, [post], diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index 4b18c470a..13c243c0a 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -47,6 +47,7 @@ import {AppModerationCause} from '#/components/Pills' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' +import * as bsky from '#/types/bsky' import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link' import {AviFollowButton} from './AviFollowButton' @@ -232,8 +233,9 @@ let FeedItemInner = ({ * If `post[0]` in this slice is the actual root post (not an orphan thread), * then we may have a threadgate record to reference */ - const threadgateRecord = AppBskyFeedThreadgate.isRecord( + const threadgateRecord = bsky.dangerousIsType<AppBskyFeedThreadgate.Record>( rootPost.threadgate?.record, + AppBskyFeedThreadgate.isRecord, ) ? rootPost.threadgate.record : undefined @@ -461,7 +463,10 @@ let PostContent = ({ }) const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => { const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) - const rootPostUri = AppBskyFeedPost.isRecord(post.record) + const rootPostUri = bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) ? post.record?.reply?.root?.uri || post.uri : undefined const isControlledByViewer = diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx index ff58dc945..656ed914a 100644 --- a/src/view/com/profile/FollowButton.tsx +++ b/src/view/com/profile/FollowButton.tsx @@ -1,10 +1,10 @@ import {StyleProp, TextStyle, View} from 'react-native' -import {AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {Shadow} from '#/state/cache/types' import {useProfileFollowMutationQueue} from '#/state/queries/profile' +import * as bsky from '#/types/bsky' import {Button, ButtonType} from '../util/forms/Button' import * as Toast from '../util/Toast' @@ -18,7 +18,7 @@ export function FollowButton({ }: { unfollowedType?: ButtonType followedType?: ButtonType - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> + profile: Shadow<bsky.profile.AnyProfileView> labelStyle?: StyleProp<TextStyle> logContext: 'ProfileCard' | 'StarterPackProfilesList' onFollow?: () => void diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index f710d7b4e..bd09d6514 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -24,6 +24,7 @@ import { shouldShowKnownFollowers, } from '#/components/KnownFollowers' import * as Pills from '#/components/Pills' +import * as bsky from '#/types/bsky' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {PreviewableUserAvatar} from '../util/UserAvatar' @@ -41,12 +42,12 @@ export function ProfileCard({ showKnownFollowers, }: { testID?: string - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView noModFilter?: boolean noBg?: boolean noBorder?: boolean renderButton?: ( - profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, + profile: Shadow<bsky.profile.AnyProfileView>, ) => React.ReactNode onPress?: () => void style?: StyleProp<ViewStyle> @@ -76,6 +77,7 @@ export function ProfileCard({ showKnownFollowers && shouldShowKnownFollowers(profile.viewer?.knownFollowers) && moderationOpts + const hasDescription = 'description' in profile return ( <Link @@ -126,9 +128,9 @@ export function ProfileCard({ <View style={styles.layoutButton}>{renderButton(profile)}</View> ) : undefined} </View> - {profile.description || knownFollowersVisible ? ( + {hasDescription || knownFollowersVisible ? ( <View style={styles.details}> - {profile.description ? ( + {hasDescription && profile.description ? ( <Text emoji style={pal.text} numberOfLines={4}> {profile.description as string} </Text> @@ -139,7 +141,7 @@ export function ProfileCard({ a.flex_row, a.align_center, a.gap_sm, - !!profile.description && a.mt_md, + !!hasDescription && a.mt_md, ]}> <KnownFollowers minimal diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index 3c0476929..d6b764656 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -17,7 +17,7 @@ function renderItem({ item, index, }: { - item: ActorDefs.ProfileViewBasic + item: ActorDefs.ProfileView index: number }) { return ( diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index 1cd65c74c..d67a7261a 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -17,7 +17,7 @@ function renderItem({ item, index, }: { - item: ActorDefs.ProfileViewBasic + item: ActorDefs.ProfileView index: number }) { return ( diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 2496f9d2a..934e8f50c 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -2,7 +2,7 @@ import React, {memo, useMemo} from 'react' import {Image, Pressable, StyleSheet, View} from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' import Svg, {Circle, Path, Rect} from 'react-native-svg' -import {AppBskyActorDefs, ModerationUI} from '@atproto/api' +import {ModerationUI} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -31,6 +31,7 @@ import {Link} from '#/components/Link' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import * as Menu from '#/components/Menu' import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import * as bsky from '#/types/bsky' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' @@ -55,7 +56,7 @@ interface EditableUserAvatarProps extends BaseUserAvatarProps { interface PreviewableUserAvatarProps extends BaseUserAvatarProps { moderation?: ModerationUI - profile: AppBskyActorDefs.ProfileViewBasic + profile: bsky.profile.AnyProfileView disableHoverCard?: boolean onBeforePress?: () => void } diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index cb549f7cd..e283a2eec 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -36,6 +36,7 @@ import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' +import * as bsky from '#/types/bsky' import {ContentHider} from '../../../../components/moderation/ContentHider' import {PostAlerts} from '../../../../components/moderation/PostAlerts' import {Link} from '../Link' @@ -171,10 +172,14 @@ export function QuoteEmbed({ const itemTitle = `Post by ${quote.author.handle}` const richText = React.useMemo(() => { - const text = AppBskyFeedPost.isRecord(quote.record) ? quote.record.text : '' - const facets = AppBskyFeedPost.isRecord(quote.record) - ? quote.record.facets - : undefined + if ( + !bsky.dangerousIsType<AppBskyFeedPost.Record>( + quote.record, + AppBskyFeedPost.isRecord, + ) + ) + return undefined + const {text, facets} = quote.record return text.trim() ? new RichTextAPI({text: text, facets: facets}) : undefined diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index 2224a4462..9774c644c 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -133,6 +133,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }) mockedProfile.did = did mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' + // @ts-expect-error ProfileViewBasic is close enough -esb mockedProfile.banner = 'https://bsky.social/about/images/social-card-default-gradient.png' return mockedProfile @@ -922,6 +923,7 @@ function MockAccountScreen({ // @ts-ignore ProfileViewBasic is close enough -prf profile={profile} moderationOpts={moderationOpts} + // @ts-ignore ProfileViewBasic is close enough -esb descriptionRT={new RichText({text: profile.description as string})} /> </ScreenHider> diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 83503a706..b6d75b274 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -76,6 +76,7 @@ import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Glo import * as Layout from '#/components/Layout' import * as Menu from '#/components/Menu' import {account, useStorage} from '#/storage' +import * as bsky from '#/types/bsky' function Loader() { return ( @@ -656,7 +657,7 @@ export function SearchScreenShell({ ) const updateProfileHistory = useCallback( - async (item: AppBskyActorDefs.ProfileViewBasic) => { + async (item: bsky.profile.AnyProfileView) => { const newAccountHistory = [ item.did, ...accountHistory.filter(p => p !== item.did), @@ -673,7 +674,7 @@ export function SearchScreenShell({ [termHistory, setTermHistory], ) const deleteProfileHistoryItem = useCallback( - async (item: AppBskyActorDefs.ProfileViewBasic) => { + async (item: AppBskyActorDefs.ProfileViewDetailed) => { setAccountHistory(accountHistory.filter(p => p !== item.did)) }, [accountHistory, setAccountHistory], @@ -766,7 +767,7 @@ export function SearchScreenShell({ ) const handleProfileClick = React.useCallback( - (profile: AppBskyActorDefs.ProfileViewBasic) => { + (profile: bsky.profile.AnyProfileView) => { // Slight delay to avoid updating during push nav animation. setTimeout(() => { updateProfileHistory(profile) @@ -1013,11 +1014,11 @@ function SearchHistory({ onRemoveProfileClick, }: { searchHistory: string[] - selectedProfiles: AppBskyActorDefs.ProfileViewBasic[] + selectedProfiles: AppBskyActorDefs.ProfileViewDetailed[] onItemClick: (item: string) => void - onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void + onProfileClick: (profile: AppBskyActorDefs.ProfileViewDetailed) => void onRemoveItemClick: (item: string) => void - onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void + onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewDetailed) => void }) { const {isMobile} = useWebMediaQueries() const pal = usePalette('default') diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index bb5de2eb4..522b51dba 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -220,7 +220,7 @@ function SwitchMenuItems({ accounts: | { account: SessionAccount - profile?: AppBskyActorDefs.ProfileView + profile?: AppBskyActorDefs.ProfileViewDetailed }[] | undefined signOutPromptControl: DialogControlProps diff --git a/yarn.lock b/yarn.lock index d7b37a3ee..691bce303 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,21 +20,21 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@atproto-labs/fetch-node@0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.4.tgz#03859a39556eab936e2b3bec2d087585c6408cb3" - integrity sha512-hwYx0XpgIl2zydRy13DtWvywruuHk1EX+yCjqjgUIezUm8fi35ZN4QvR6INEm0MpN2MD/kQsImPbd8ZftzZ3zw== +"@atproto-labs/fetch-node@0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.7.tgz#b4538ee99bed6ca5843a9266004e1d7c1c0bf186" + integrity sha512-vZ627PQqVGiBmPxulnviIGvvBPpTdzOcnfU1WcLeES3E0WjNxRGQqFaodBl5Zc4cj3QSPG/KC6wPcj/rjhbDrQ== dependencies: - "@atproto-labs/fetch" "0.1.2" + "@atproto-labs/fetch" "0.2.1" "@atproto-labs/pipe" "0.1.0" ipaddr.js "^2.1.0" psl "^1.9.0" undici "^6.14.1" -"@atproto-labs/fetch@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.1.2.tgz#e1b9354205fb76f106ae3e1c6b56e7865a39600f" - integrity sha512-7mQQIRtVenqtdBQKCqoLjyAhPS2aA56EGEjyz5zB3sramM3qkrvzyusr55GAzGDS0tvB6cy9cDEtSLmfK7LUnA== +"@atproto-labs/fetch@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.2.1.tgz#7e82eb6998d9694614fbe6cc9a68f0c217898a13" + integrity sha512-V22/7C7r+FfIDZA/BVn5UeuK5JccDp7nOiRfp5JITpVw2OXQbVfd8kywN7voWvPXw4sjd4cHoIPgQa0wvQGenQ== dependencies: "@atproto-labs/pipe" "0.1.0" optionalDependencies: @@ -45,83 +45,81 @@ resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.0.tgz#c8d86923b6d8e900d39efe6fdcdf0d897c434086" integrity sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w== -"@atproto-labs/simple-store-memory@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.1.tgz#54526a1f8ec978822be9fad75106ad8b78500dd3" - integrity sha512-PCRqhnZ8NBNBvLku53O56T0lsVOtclfIrQU/rwLCc4+p45/SBPrRYNBi6YFq5rxZbK6Njos9MCmILV/KLQxrWA== +"@atproto-labs/simple-store-memory@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.2.tgz#234dbdb7162682795e09dfd7ea72cf448788ac8c" + integrity sha512-q6wawjKKXuhUzr2MnkSlgr6zU6VimYkL8eNvLQvkroLnIDyMkoCKO4+EJ885ZD8lGwBo4pX9Lhrg9JJ+ncJI8g== dependencies: - "@atproto-labs/simple-store" "0.1.1" + "@atproto-labs/simple-store" "0.1.2" lru-cache "^10.2.0" -"@atproto-labs/simple-store@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.1.1.tgz#e743a2722b5d8732166f0a72aca8bd10e9bff106" - integrity sha512-WKILW2b3QbAYKh+w5U2x6p5FqqLl0nAeLwGeDY+KjX01K4Dq3vQTR9b/qNp0jZm48CabPQVrqCv0PPU9LgRRRg== - -"@atproto/api@^0.13.20": - version "0.13.20" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.20.tgz#5140db303c3b0981958dfe6a5fa6d7d1cd7bb3cc" - integrity sha512-z/+CvG6BEttRHf856tKSe1AeUQNfrobRJldaHAthGmFk7O3wLZQyfcI9DUmBJQ9+4wAt0dZwvKWVGLZOV9eLHA== - dependencies: - "@atproto/common-web" "^0.3.1" - "@atproto/lexicon" "^0.4.4" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc" "^0.6.5" - await-lock "^2.2.2" - multiformats "^9.9.0" - tlds "^1.234.0" - zod "^3.23.8" +"@atproto-labs/simple-store@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.1.2.tgz#39c1fa0326ae89204777e028886f79d6c22dc0ef" + integrity sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ== -"@atproto/api@^0.13.35": - version "0.13.35" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.35.tgz#1e3a6c6e035c8e06302508983ed206effc92a7e8" - integrity sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g== +"@atproto-labs/xrpc-utils@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@atproto-labs/xrpc-utils/-/xrpc-utils-0.0.7.tgz#351ddce177f2731383b2b8c62e0afe1de8112903" + integrity sha512-mNev88mtNo79h4bkEQYuLoTlejc1zMl9lLwKbpKYfFaaU0IS9VdhiPdRTEcQ6JGYK915OZ5Lv7OJQNF0g9qq9w== + dependencies: + "@atproto/xrpc" "^0.6.9" + "@atproto/xrpc-server" "^0.7.11" + +"@atproto/api@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.14.0.tgz#359debd4bc058fd24a2562dd674721e77a453d24" + integrity sha512-KB+kMVdsDo7rW5S0vBpsPASepS717WPec8FAY04azhdCknlj7yh2FhMLYQu9dDb/uSJASAZGQEkDQUhumBk9fw== dependencies: "@atproto/common-web" "^0.4.0" - "@atproto/lexicon" "^0.4.6" - "@atproto/syntax" "^0.3.2" - "@atproto/xrpc" "^0.6.8" + "@atproto/lexicon" "^0.4.7" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc" "^0.6.9" await-lock "^2.2.2" multiformats "^9.9.0" tlds "^1.234.0" zod "^3.23.8" -"@atproto/aws@^0.2.10": - version "0.2.10" - resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.10.tgz#e0b888fd50308cc24b7086cf3ec209587c13bbe4" - integrity sha512-zQElKk6wGTQo5aKdXtmx/dINjkVgbJU9+C/xOVTs+M88I8IrrBxPvo1dASLJcMtRb9VjXh5snLJeAjgyx6qC6Q== +"@atproto/aws@^0.2.15": + version "0.2.15" + resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.15.tgz#edc534a420b4da37e2f049d471bf40df93447a25" + integrity sha512-4fR7wEnlGtkchfL7XdQ61yALNbIMpX1xL4H0XEq+o3LzM7/08lw2vhQCDFCqqjOJwWXxefQRsVXG5p7iyy3HPA== dependencies: - "@atproto/common" "^0.4.5" - "@atproto/crypto" "^0.4.2" - "@atproto/repo" "^0.6.0" + "@atproto/common" "^0.4.8" + "@atproto/crypto" "^0.4.4" + "@atproto/repo" "^0.6.5" "@aws-sdk/client-cloudfront" "^3.261.0" "@aws-sdk/client-kms" "^3.196.0" "@aws-sdk/client-s3" "^3.224.0" "@aws-sdk/lib-storage" "^3.226.0" - "@noble/curves" "^1.1.0" + "@noble/curves" "^1.7.0" key-encoder "^2.0.3" multiformats "^9.9.0" uint8arrays "3.0.0" -"@atproto/bsky@^0.0.98": - version "0.0.98" - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.98.tgz#4c4746e588568df1878647ae80cf4b963bc95924" - integrity sha512-Y+un2pD1W1H0s0IWdY6S4vLy8rgR8cpqThz9onn4wDppmGWvOBNXeD8AjNzIWC0iFlYcfR4rwCKSoccUXYzxNg== - dependencies: - "@atproto/api" "^0.13.20" - "@atproto/common" "^0.4.5" - "@atproto/crypto" "^0.4.2" - "@atproto/identity" "^0.4.3" - "@atproto/lexicon" "^0.4.4" - "@atproto/repo" "^0.6.0" - "@atproto/sync" "^0.1.7" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc-server" "^0.7.4" +"@atproto/bsky@^0.0.117": + version "0.0.117" + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.117.tgz#869ac8f853cf43d893cba46a5a79f0f6a4a9f3f0" + integrity sha512-C+KKNROLUgSkt5J7IlWMvqUKFRbZAoCg1vyuLiY/jf5+7NFkQ5YYghaJguMrQdpqvF8KLmVLI3clhyPwvlDIOg== + dependencies: + "@atproto-labs/fetch-node" "0.1.7" + "@atproto-labs/xrpc-utils" "0.0.7" + "@atproto/api" "^0.14.0" + "@atproto/common" "^0.4.8" + "@atproto/crypto" "^0.4.4" + "@atproto/did" "^0.1.5" + "@atproto/identity" "^0.4.6" + "@atproto/lexicon" "^0.4.7" + "@atproto/repo" "^0.6.5" + "@atproto/sync" "^0.1.14" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc-server" "^0.7.11" "@bufbuild/protobuf" "^1.5.0" "@connectrpc/connect" "^1.1.4" "@connectrpc/connect-express" "^1.1.4" "@connectrpc/connect-node" "^1.1.4" "@did-plc/lib" "^0.0.1" + "@types/http-errors" "^2.0.1" compression "^1.7.4" cors "^2.8.5" express "^4.17.2" @@ -142,14 +140,15 @@ structured-headers "^1.0.1" typed-emitter "^2.1.0" uint8arrays "3.0.0" + undici "^6.19.8" -"@atproto/bsync@^0.0.10": - version "0.0.10" - resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.10.tgz#fa16acfaf67112449b703778a20c785226c94189" - integrity sha512-qviPMyYade/sqhX/9X9eTT4KaQ+FLvOyz+140LCDk/0vbZUCZPuYSEXZDCQkL5nlEXzScsQ3iyVeoYCGvV5kYw== +"@atproto/bsync@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.14.tgz#ed25942e03e5c120cc89f3529143b2b197e4f3b1" + integrity sha512-y6ioCJxmqnwQUc/MqBDCrNciJqrPanqSMjMneEU7mRSdbxXW27b1TblADSJeavkn8vbUGJUEmMWcqgWOrRClpw== dependencies: - "@atproto/common" "^0.4.5" - "@atproto/syntax" "^0.3.1" + "@atproto/common" "^0.4.8" + "@atproto/syntax" "^0.3.3" "@bufbuild/protobuf" "^1.5.0" "@connectrpc/connect" "^1.1.4" "@connectrpc/connect-node" "^1.1.4" @@ -159,16 +158,6 @@ pino-http "^8.2.1" typed-emitter "^2.1.0" -"@atproto/common-web@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.1.tgz#86f8efb10a4b9073839cee914c6c08a664917cc4" - integrity sha512-N7wiTnus5vAr+lT//0y8m/FaHHLJ9LpGuEwkwDAeV3LCiPif4m/FS8x/QOYrx1PdZQwKso95RAPzCGWQBH5j6Q== - dependencies: - graphemer "^1.4.0" - multiformats "^9.9.0" - uint8arrays "3.0.0" - zod "^3.23.8" - "@atproto/common-web@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.0.tgz#b1407ae3f964f0ee23c2c3184f38041bac99d1f4" @@ -199,12 +188,12 @@ pino "^8.6.1" zod "^3.14.2" -"@atproto/common@^0.4.5": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.5.tgz#28fd176a9b5527c723828e725586bc0be9fa9516" - integrity sha512-LFAGqHcxCI5+b31Xgk+VQQtZU258iGPpHJzNeHVcdh6teIKZi4C2l6YV+m+3CEz+yYcfP7jjUmgqesx7l9Arsg== +"@atproto/common@^0.4.8": + version "0.4.8" + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.8.tgz#4ca61807448c672f19d17443b569fcdb81cc6df7" + integrity sha512-/etCtnWQGLcfiGhIPwxAWrzgzoGB22nMWMeQcU6xZgRT4Cqrfg3A08jAMIHqve/AQpL+6D82lHYp36CG7a5G0w== dependencies: - "@atproto/common-web" "^0.3.1" + "@atproto/common-web" "^0.4.0" "@ipld/dag-cbor" "^7.0.3" cbor-x "^1.5.1" iso-datestring-validator "^2.2.2" @@ -222,102 +211,97 @@ one-webcrypto "^1.0.3" uint8arrays "3.0.0" -"@atproto/crypto@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.2.tgz#07417887ddbd4baae5298d4b9499fc727f261a31" - integrity sha512-aeOfPQYCDbhn2hV06oBF2KXrWjf/BK4yL8lfANJKSmKl3tKWCkiW/moi643rUXXxSE72KtWtQeqvNFYnnFJ0ig== +"@atproto/crypto@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.4.tgz#3bd5066643d08e09da55bd59ac1f319d1fcff803" + integrity sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA== dependencies: - "@noble/curves" "^1.1.0" - "@noble/hashes" "^1.3.1" + "@noble/curves" "^1.7.0" + "@noble/hashes" "^1.6.1" uint8arrays "3.0.0" -"@atproto/dev-env@^0.3.67": - version "0.3.67" - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.67.tgz#4f6a20f0aafa8125ed9ec715abceedd11580882e" - integrity sha512-7Ize4Y5vdjQjyrxTwjBPbkxKXQdE02KpE7AJLJt6Xpvowd2vbn8l8rDXfha+LtVi6t/613U4s+Slo5c1YD3x9A== - dependencies: - "@atproto/api" "^0.13.20" - "@atproto/bsky" "^0.0.98" - "@atproto/bsync" "^0.0.10" - "@atproto/common-web" "^0.3.1" - "@atproto/crypto" "^0.4.2" - "@atproto/identity" "^0.4.3" - "@atproto/lexicon" "^0.4.4" - "@atproto/ozone" "^0.1.59" - "@atproto/pds" "^0.4.76" - "@atproto/sync" "^0.1.7" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc-server" "^0.7.4" +"@atproto/dev-env@^0.3.87": + version "0.3.87" + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.87.tgz#dad1a7cdde1d38cbd5a88c75f5106261c6361b66" + integrity sha512-xUeI94hqSnksjwdAi+Q0ML2qJlwRKYdqSD3kmR8LHIGeF6cWv+rjoSkK6+LVuV//LpC1EigoqkKQdX0UbDROOA== + dependencies: + "@atproto/api" "^0.14.0" + "@atproto/bsky" "^0.0.117" + "@atproto/bsync" "^0.0.14" + "@atproto/common-web" "^0.4.0" + "@atproto/crypto" "^0.4.4" + "@atproto/identity" "^0.4.6" + "@atproto/lexicon" "^0.4.7" + "@atproto/ozone" "^0.1.78" + "@atproto/pds" "^0.4.95" + "@atproto/sync" "^0.1.14" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc-server" "^0.7.11" "@did-plc/lib" "^0.0.1" "@did-plc/server" "^0.0.1" - axios "^0.27.2" dotenv "^16.0.3" express "^4.18.2" get-port "^5.1.1" multiformats "^9.9.0" uint8arrays "3.0.0" + undici "^6.14.1" -"@atproto/identity@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.3.tgz#fd387d4f2dd68a514e3d0138009a4b9db7f489fd" - integrity sha512-DLXMWh57dHvIeBl+IvC+q20z0IdDZT1awOn84vDyxacL9DfhbiTy/zCUPFEzHyvfrilNG1tDA4zQzURubdFqNg== +"@atproto/did@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@atproto/did/-/did-0.1.5.tgz#5bfe73625d54c4c687c00ff370971ce01c39bd61" + integrity sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ== dependencies: - "@atproto/common-web" "^0.3.1" - "@atproto/crypto" "^0.4.2" - axios "^0.27.2" + zod "^3.23.8" -"@atproto/jwk-jose@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.2.tgz#236eadb740b498689d9a912d1254aa9ff58890a1" - integrity sha512-lDwc/6lLn2aZ/JpyyggyjLFsJPMntrVzryyGUx5aNpuTS8SIuc4Ky0REhxqfLopQXJJZCuRRjagHG3uP05/moQ== +"@atproto/identity@^0.4.6": + version "0.4.6" + resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.6.tgz#d2e7e3cd9b2af9ee2a82b7ffd8f6e2fcbd813a86" + integrity sha512-fJq/cIp9MOgHxZfxuyki6mobk0QxRnbts53DstRixlvb5mOoxwttb9Gp6A8u9q49zBsfOmXNTHmP97I9iMHmTQ== dependencies: - "@atproto/jwk" "0.1.1" - jose "^5.2.0" + "@atproto/common-web" "^0.4.0" + "@atproto/crypto" "^0.4.4" -"@atproto/jwk@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.1.1.tgz#15bcad4a1778eeb20c82108e0ec55fef45cd07b6" - integrity sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og== +"@atproto/jwk-jose@0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.4.tgz#c36c4332ce41d612a09492e9f6da479a6b7b2b9b" + integrity sha512-JzLn1wUzuLfweznSECdTjSHTxQBEz7Q8oJ4XKjRNludqzyJW8etEH00l1WolLipFxoj1QCG9qy00JmlC59Y6Rw== dependencies: - multiformats "^9.9.0" - zod "^3.23.8" + "@atproto/jwk" "0.1.3" + jose "^5.2.0" -"@atproto/lexicon@^0.4.4": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.4.tgz#0d97314bb57b693b76f2495fa5e02872469dd93a" - integrity sha512-QFEmr3rpj/RoAmfX9ALU/asBG/rsVtQZnw+9nOB1/AuIwoxXd+ZyndR6lVUc2+DL4GEjl6W2yvBru5xbQIZWyA== +"@atproto/jwk@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.1.3.tgz#c42feb53a39573cf84eeec73c62776d1d2497a55" + integrity sha512-5rBgA8Fk4fg6MfNyEQvUnwq1MRn5xZOXYj4oxLuZ549XeNp2Rm2v+psuEkICD+o6pfIoMX4Hw7UTlXDrpsKKlQ== dependencies: - "@atproto/common-web" "^0.3.1" - "@atproto/syntax" "^0.3.1" - iso-datestring-validator "^2.2.2" multiformats "^9.9.0" zod "^3.23.8" -"@atproto/lexicon@^0.4.6": - version "0.4.6" - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6.tgz#74b2a0f3e4c867b33f75430d4ccec70c47e41576" - integrity sha512-RbiwXcnTuLp9vQrNoQ7xly8HyifKkovqCYtbfXVwqdylWYKPhmRsYkRfcPNv/lILhT9Lm0GVnxNwGGwvvgIsfA== +"@atproto/lexicon@^0.4.7": + version "0.4.7" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.7.tgz#f5d31615c21bcfd3e655f1e4f11a40a62fea9f86" + integrity sha512-/x6h3tAiDNzSi4eXtC8ke65B7UzsagtlGRHmUD95698x5lBRpDnpizj0fZWTZVYed5qnOmz/ZEue+v3wDmO61g== dependencies: "@atproto/common-web" "^0.4.0" - "@atproto/syntax" "^0.3.2" + "@atproto/syntax" "^0.3.3" iso-datestring-validator "^2.2.2" multiformats "^9.9.0" zod "^3.23.8" -"@atproto/oauth-provider@^0.2.10": - version "0.2.10" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901" - integrity sha512-cF42lo0+Mj+Zq2RXwS2NxmobmtL7YL1vXlYcN6iKflZ8pQ5WvpR/cZKsKEZOT9cEBBTw5MARKTYxbr8CPDKlHg== +"@atproto/oauth-provider@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.17.tgz#4644d391eedbbbbe5825ecc0e8cc03f1c6433b95" + integrity sha512-fvEbONJfjDRqQoIkB76n1cLz7y6f99Fhgs8h2u1LNZak1p95JZs3Tc5HsDhmUHo2Yk9h22CIwMRRjHImU/m1Nw== dependencies: - "@atproto-labs/fetch" "0.1.2" - "@atproto-labs/fetch-node" "0.1.4" + "@atproto-labs/fetch" "0.2.1" + "@atproto-labs/fetch-node" "0.1.7" "@atproto-labs/pipe" "0.1.0" - "@atproto-labs/simple-store" "0.1.1" - "@atproto-labs/simple-store-memory" "0.1.1" - "@atproto/common" "^0.4.5" - "@atproto/jwk" "0.1.1" - "@atproto/jwk-jose" "0.1.2" - "@atproto/oauth-types" "0.2.1" + "@atproto-labs/simple-store" "0.1.2" + "@atproto-labs/simple-store-memory" "0.1.2" + "@atproto/common" "^0.4.8" + "@atproto/jwk" "0.1.3" + "@atproto/jwk-jose" "0.1.4" + "@atproto/oauth-types" "0.2.3" "@hapi/accept" "^6.0.3" "@hapi/bourne" "^3.0.0" "@hapi/content" "^6.0.0" @@ -329,29 +313,28 @@ psl "^1.9.0" zod "^3.23.8" -"@atproto/oauth-types@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.2.1.tgz#a7ace557cc91817fcde6195f023e4e1838e4aef6" - integrity sha512-hDisUXzcq5KU1HMuCYZ8Kcz7BePl7V11bFjjgZvND3mdSphiyBpJ8MCNn3QzAa6cXpFo0w9PDcYMAlCCRZHdVw== +"@atproto/oauth-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.2.3.tgz#a2e9470cbf48c6e7663906f8b43045077945e1f2" + integrity sha512-M+0WW/alS2BfhKtwvdU3rSaLoycw6kTH1kGKeyDdmb/xN/8QjU7T6dkJe+wX4NC7F23xdKfti9DZhBpEtn+/kg== dependencies: - "@atproto/jwk" "0.1.1" + "@atproto/jwk" "0.1.3" zod "^3.23.8" -"@atproto/ozone@^0.1.59": - version "0.1.59" - resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.59.tgz#219984a46617b0ac039f2f02767290eaa0b4cfc3" - integrity sha512-AD03Ocb3fZW+grxO/VwMld5iNdCLgbahFzku6xh1qEw0tLOBKp3GXSfepVd9XWu5fb1yPhGPd2JgjApV5hbJvw== - dependencies: - "@atproto/api" "^0.13.20" - "@atproto/common" "^0.4.5" - "@atproto/crypto" "^0.4.2" - "@atproto/identity" "^0.4.3" - "@atproto/lexicon" "^0.4.4" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc" "^0.6.5" - "@atproto/xrpc-server" "^0.7.4" +"@atproto/ozone@^0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.78.tgz#9ed4535967b79ba291c15560b4b598f863c5ded0" + integrity sha512-l7T6d4+gieVbxscZ3ou/PSE2VtO9w3/C30gAhVhRPvqbUzAAzpvPLkQGyP7cUSnuXrb32QCyuOy2QoAJ84lR8Q== + dependencies: + "@atproto/api" "^0.14.0" + "@atproto/common" "^0.4.8" + "@atproto/crypto" "^0.4.4" + "@atproto/identity" "^0.4.6" + "@atproto/lexicon" "^0.4.7" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc" "^0.6.9" + "@atproto/xrpc-server" "^0.7.11" "@did-plc/lib" "^0.0.1" - axios "^1.6.7" compression "^1.7.4" cors "^2.8.5" express "^4.17.2" @@ -365,24 +348,26 @@ structured-headers "^1.0.1" typed-emitter "^2.1.0" uint8arrays "3.0.0" + undici "^6.14.1" -"@atproto/pds@^0.4.76": - version "0.4.76" - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.76.tgz#cd7b3f13359a7c31dc9362a5e4309419512c4102" - integrity sha512-+cFVpqlgpCS0BuGac5fCQPZUugpS1r7ghnSQLVdjnTnvQJCqLRA++BlJWYbGgRP6FJrumCY2jtuwG8t59Rjt8Q== - dependencies: - "@atproto-labs/fetch-node" "0.1.4" - "@atproto/api" "^0.13.20" - "@atproto/aws" "^0.2.10" - "@atproto/common" "^0.4.5" - "@atproto/crypto" "^0.4.2" - "@atproto/identity" "^0.4.3" - "@atproto/lexicon" "^0.4.4" - "@atproto/oauth-provider" "^0.2.10" - "@atproto/repo" "^0.6.0" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc" "^0.6.5" - "@atproto/xrpc-server" "^0.7.4" +"@atproto/pds@^0.4.95": + version "0.4.95" + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.95.tgz#a0881f7de2cfa900b82c3a465798a75c02302f2e" + integrity sha512-HiOoWvBvU/hICgOslOddX6yIUN/e5um59Py0Ngl85xPkwt1TUb1/c57P2/wM1HGZt6wrORplDS4gzCP9wmGnRQ== + dependencies: + "@atproto-labs/fetch-node" "0.1.7" + "@atproto-labs/xrpc-utils" "0.0.7" + "@atproto/api" "^0.14.0" + "@atproto/aws" "^0.2.15" + "@atproto/common" "^0.4.8" + "@atproto/crypto" "^0.4.4" + "@atproto/identity" "^0.4.6" + "@atproto/lexicon" "^0.4.7" + "@atproto/oauth-provider" "^0.2.17" + "@atproto/repo" "^0.6.5" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc" "^0.6.9" + "@atproto/xrpc-server" "^0.7.11" "@did-plc/lib" "^0.0.4" "@hapi/address" "^5.1.1" better-sqlite3 "^10.0.0" @@ -412,55 +397,50 @@ undici "^6.19.8" zod "^3.23.8" -"@atproto/repo@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.6.0.tgz#29e698731e6df63636b0f7c91ce106a9de50ad19" - integrity sha512-6YGVhjiHKmqCW5Ce4oY49E3NCEfbvAGowJ5ETXX2sx2l4D2bOL7a2hn5zWqsPHYpWSLjrPfnj7PVpApK0kmL7A== +"@atproto/repo@^0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.6.5.tgz#a45cb0df5b1e0ec078a535a4acb69e9364938020" + integrity sha512-Sa95LaEMDtwL9M0kp3vuVQIcgEJI+6EssDLIiuPnJAi9SbEPESdUfEiIR5t2oFCkMwrS7OJQCLdCa7CMy+plUg== dependencies: - "@atproto/common" "^0.4.5" - "@atproto/common-web" "^0.3.1" - "@atproto/crypto" "^0.4.2" - "@atproto/lexicon" "^0.4.4" + "@atproto/common" "^0.4.8" + "@atproto/common-web" "^0.4.0" + "@atproto/crypto" "^0.4.4" + "@atproto/lexicon" "^0.4.7" "@ipld/car" "^3.2.3" "@ipld/dag-cbor" "^7.0.0" multiformats "^9.9.0" uint8arrays "3.0.0" zod "^3.23.8" -"@atproto/sync@^0.1.7": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.7.tgz#c7f78d99bb40eacf93ca13fdd04134a0985bf421" - integrity sha512-liJH2EsD4AbWA8G0oRDURgbHW6Uq4NnM2rNfbrTlqgtj0kyGRY3FcVEyqeRcaQYfCuscChIg5DQKHqY421/7Mw== - dependencies: - "@atproto/common" "^0.4.5" - "@atproto/identity" "^0.4.3" - "@atproto/lexicon" "^0.4.4" - "@atproto/repo" "^0.6.0" - "@atproto/syntax" "^0.3.1" - "@atproto/xrpc-server" "^0.7.4" +"@atproto/sync@^0.1.14": + version "0.1.14" + resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.14.tgz#e35ff18ab314eb26d3bc4d8f55e3c8530b8eed0a" + integrity sha512-8+8o4aWnWVJiiNG63k9K/etFz7KZgwmYKIXgT13AO8hGRAKO4eZDTtM2GbEfKqja2Grs7iHSZ4EIKoTYaK4Daw== + dependencies: + "@atproto/common" "^0.4.8" + "@atproto/identity" "^0.4.6" + "@atproto/lexicon" "^0.4.7" + "@atproto/repo" "^0.6.5" + "@atproto/syntax" "^0.3.3" + "@atproto/xrpc-server" "^0.7.11" multiformats "^9.9.0" p-queue "^6.6.2" ws "^8.12.0" -"@atproto/syntax@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.1.tgz#4346418728f9643d783d2ffcf7c77e132e1f53d4" - integrity sha512-fzW0Mg1QUOVCWUD3RgEsDt6d1OZ6DdFmbKcDdbzUfh0t4rhtRAC05KbZYmxuMPWDAiJ4BbbQ5dkAc/mNypMXkw== - -"@atproto/syntax@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.2.tgz#188f8dccba11e5ace1bf83cbff8ed9e1a3d2d66c" - integrity sha512-JLMhTbXER1Im98RrozfsLAZARGIAzKCZEm+Inh1IF00XU6tHcoGKS+HOw0Uy4R2r04yvxoFs8fswmwAhmMpMdw== - -"@atproto/xrpc-server@^0.7.4": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.7.4.tgz#dfac8f7276c1c971a35eaba627eb6372088441c3" - integrity sha512-MrAwxfJBQm/kCol3D8qc+vpQzBMzLqvtUbauSSfVVJ10PlGtxg4LlXqcjkAuhrjyrqp3dQH9LHuhDpgVQK+G3w== - dependencies: - "@atproto/common" "^0.4.5" - "@atproto/crypto" "^0.4.2" - "@atproto/lexicon" "^0.4.4" - "@atproto/xrpc" "^0.6.5" +"@atproto/syntax@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.3.tgz#6debe8983985378104822172a128e429931bf3f7" + integrity sha512-F1LZweesNYdBbZBXVa72N/cSvchG8Q1tG4/209ZXbIuM3FwQtkgn+zgmmV4P4ORmhOeXPBNXvMBpcqiwx/gEQQ== + +"@atproto/xrpc-server@^0.7.11": + version "0.7.11" + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.7.11.tgz#efadcfdaaaa0ff5576d1ee97e46dcbc6dafcb0b6" + integrity sha512-kywMZMw2FbUFk0xBCtSI1mik+dc3uSvloNndI+N4X/+Qv1FGvoCRMi//9TqaSL13MFevTOynVoMVmaZbnaDG9A== + dependencies: + "@atproto/common" "^0.4.8" + "@atproto/crypto" "^0.4.4" + "@atproto/lexicon" "^0.4.7" + "@atproto/xrpc" "^0.6.9" cbor-x "^1.5.1" express "^4.17.2" http-errors "^2.0.0" @@ -470,20 +450,12 @@ ws "^8.12.0" zod "^3.23.8" -"@atproto/xrpc@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.5.tgz#8b180fc5f6b8374fd00c41b9e4cd7b24ead48e6b" - integrity sha512-t6u8iPEVbWge5RhzKZDahSzNDYIAxUtop6Q/X/apAZY1rgreVU0/1sSvvRoRFH19d3UIKjYdLuwFqMi9w8nY3Q== - dependencies: - "@atproto/lexicon" "^0.4.4" - zod "^3.23.8" - -"@atproto/xrpc@^0.6.8": - version "0.6.8" - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.8.tgz#cede54e17b6f8863f78e16f27f87c1966446eea6" - integrity sha512-+KW0NcwdFyLziccYimX6tPkORiwwxlJPqlkVL9bJyj8nJ0aB8cyqo9HXkziMI+R6ansB1BuWQ0tfdPlLLwrUcA== +"@atproto/xrpc@^0.6.9": + version "0.6.9" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.9.tgz#6e1effc42cdab40741a73ead5c276183284887d2" + integrity sha512-vQGA7++DYMNaHx3C7vEjT+2X6hYYLG7JNbBnDLWu0km1/1KYXgRkAz4h+FfYqg1mvzvIorHU7DAs5wevkJDDlw== dependencies: - "@atproto/lexicon" "^0.4.6" + "@atproto/lexicon" "^0.4.7" zod "^3.23.8" "@aws-crypto/crc32@3.0.0": @@ -3295,7 +3267,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== @@ -3356,19 +3328,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" - debug "^4.3.1" - globals "^11.1.0" - "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" @@ -5117,17 +5076,17 @@ dependencies: eslint-scope "5.1.1" -"@noble/curves@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== +"@noble/curves@^1.7.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== dependencies: - "@noble/hashes" "1.3.1" + "@noble/hashes" "1.7.1" -"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.7.1", "@noble/hashes@^1.6.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== "@noble/secp256k1@^1.7.0": version "1.7.1" @@ -6947,6 +6906,11 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== +"@types/http-errors@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/http-proxy@^1.17.8": version "1.17.11" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" @@ -7983,14 +7947,6 @@ await-lock@^2.2.2: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - axios@^1.3.4: version "1.4.0" resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" @@ -8000,15 +7956,6 @@ axios@^1.3.4: form-data "^4.0.0" proxy-from-env "^1.1.0" -axios@^1.6.7: - version "1.6.8" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" - integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -11200,16 +11147,11 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.215.0.tgz#9b153fa27ab238bcc0bb1ff73b63bdb15d3f277d" integrity sha512-8bjwzy8vi+fNDy8YoTBNtQUSZa53i7UWJJTunJojOtjab9cMNhOCwohionuMgDQUU0y21QTTtPOX6OQEOQT72A== -follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: +follow-redirects@^1.0.0, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -follow-redirects@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - fontfaceobserver@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8" @@ -17657,16 +17599,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17766,7 +17699,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17780,13 +17713,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19061,7 +18987,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -19079,15 +19005,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" |