Bluesky app fork with some witchin' additions 💫

`@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>

+1021 -722
+2 -2
package.json
··· 57 57 "icons:optimize": "svgo -f ./assets/icons" 58 58 }, 59 59 "dependencies": { 60 - "@atproto/api": "^0.13.35", 60 + "@atproto/api": "^0.14.0", 61 61 "@bitdrift/react-native": "^0.6.8", 62 62 "@braintree/sanitize-url": "^6.0.2", 63 63 "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", ··· 212 212 "zod": "^3.20.2" 213 213 }, 214 214 "devDependencies": { 215 - "@atproto/dev-env": "^0.3.67", 215 + "@atproto/dev-env": "^0.3.87", 216 216 "@babel/core": "^7.26.0", 217 217 "@babel/preset-env": "^7.26.0", 218 218 "@babel/runtime": "^7.26.0",
+2 -2
src/components/FeedCard.tsx
··· 1 1 import React from 'react' 2 2 import {GestureResponderEvent, View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 4 AppBskyFeedDefs, 6 5 AppBskyGraphDefs, 7 6 AtUri, ··· 32 31 import * as Prompt from '#/components/Prompt' 33 32 import {RichText, RichTextProps} from '#/components/RichText' 34 33 import {Text} from '#/components/Typography' 34 + import * as bsky from '#/types/bsky' 35 35 36 36 type Props = { 37 37 view: AppBskyFeedDefs.GeneratorView ··· 115 115 creator, 116 116 }: { 117 117 title: string 118 - creator?: AppBskyActorDefs.ProfileViewBasic 118 + creator?: bsky.profile.AnyProfileView 119 119 }) { 120 120 const t = useTheme() 121 121
+5 -3
src/components/FeedInterstitials.tsx
··· 1 1 import React from 'react' 2 - import {ScrollView, View} from 'react-native' 3 - import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' 2 + import {View} from 'react-native' 3 + import {ScrollView} from 'react-native-gesture-handler' 4 + import {AppBskyFeedDefs, AtUri} from '@atproto/api' 4 5 import {msg, Trans} from '@lingui/macro' 5 6 import {useLingui} from '@lingui/react' 6 7 import {useNavigation} from '@react-navigation/native' ··· 26 27 import {InlineLinkText} from '#/components/Link' 27 28 import * as ProfileCard from '#/components/ProfileCard' 28 29 import {Text} from '#/components/Typography' 30 + import * as bsky from '#/types/bsky' 29 31 import {ProgressGuideList} from './ProgressGuide/List' 30 32 31 33 const MOBILE_CARD_WIDTH = 300 ··· 227 229 viewContext = 'feed', 228 230 }: { 229 231 isSuggestionsLoading: boolean 230 - profiles: AppBskyActorDefs.ProfileViewDetailed[] 232 + profiles: bsky.profile.AnyProfileView[] 231 233 recId?: number 232 234 error: Error | null 233 235 viewContext: 'profile' | 'feed'
+3 -2
src/components/KnownFollowers.tsx
··· 10 10 import {atoms as a, useTheme} from '#/alf' 11 11 import {Link, LinkProps} from '#/components/Link' 12 12 import {Text} from '#/components/Typography' 13 + import * as bsky from '#/types/bsky' 13 14 14 15 const AVI_SIZE = 30 15 16 const AVI_SIZE_SMALL = 20 ··· 33 34 onLinkPress, 34 35 minimal, 35 36 }: { 36 - profile: AppBskyActorDefs.ProfileViewDetailed 37 + profile: bsky.profile.AnyProfileView 37 38 moderationOpts: ModerationOpts 38 39 onLinkPress?: LinkProps['onPress'] 39 40 minimal?: boolean ··· 77 78 onLinkPress, 78 79 minimal, 79 80 }: { 80 - profile: AppBskyActorDefs.ProfileViewDetailed 81 + profile: bsky.profile.AnyProfileView 81 82 moderationOpts: ModerationOpts 82 83 cachedKnownFollowers: AppBskyActorDefs.KnownFollowers 83 84 onLinkPress?: LinkProps['onPress']
+2 -2
src/components/ListCard.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 4 AppBskyGraphDefs, 6 5 AtUri, 7 6 moderateUserList, ··· 26 25 import {Link as InternalLink, LinkProps} from '#/components/Link' 27 26 import * as Hider from '#/components/moderation/Hider' 28 27 import {Text} from '#/components/Typography' 28 + import * as bsky from '#/types/bsky' 29 29 30 30 /* 31 31 * This component is based on `FeedCard` and is tightly coupled with that ··· 107 107 modUi, 108 108 }: { 109 109 title: string 110 - creator?: AppBskyActorDefs.ProfileViewBasic 110 + creator?: bsky.profile.AnyProfileView 111 111 purpose?: AppBskyGraphDefs.ListView['purpose'] 112 112 modUi?: ModerationUI 113 113 }) {
+22 -36
src/components/MediaPreview.tsx
··· 1 1 import React from 'react' 2 2 import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 3 import {Image} from 'expo-image' 4 - import { 5 - AppBskyEmbedExternal, 6 - AppBskyEmbedImages, 7 - AppBskyEmbedRecordWithMedia, 8 - AppBskyEmbedVideo, 9 - } from '@atproto/api' 4 + import {AppBskyFeedDefs} from '@atproto/api' 10 5 import {Trans} from '@lingui/macro' 11 6 12 - import {parseTenorGif} from '#/lib/strings/embed-player' 7 + import {isTenorGifUri} from '#/lib/strings/embed-player' 13 8 import {atoms as a, useTheme} from '#/alf' 14 9 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 15 10 import {Text} from '#/components/Typography' 16 11 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 12 + import * as bsky from '#/types/bsky' 17 13 18 14 /** 19 15 * Streamlined MediaPreview component which just handles images, gifs, and videos ··· 22 18 embed, 23 19 style, 24 20 }: { 25 - embed?: 26 - | AppBskyEmbedImages.View 27 - | AppBskyEmbedRecordWithMedia.View 28 - | AppBskyEmbedExternal.View 29 - | AppBskyEmbedVideo.View 30 - | {[k: string]: unknown} 21 + embed: AppBskyFeedDefs.PostView['embed'] 31 22 style?: StyleProp<ViewStyle> 32 23 }) { 33 - let media = AppBskyEmbedRecordWithMedia.isView(embed) ? embed.media : embed 24 + const e = bsky.post.parseEmbed(embed) 25 + 26 + if (!e) return null 34 27 35 - if (AppBskyEmbedImages.isView(media)) { 28 + if (e.type === 'images') { 36 29 return ( 37 30 <Outer style={style}> 38 - {media.images.map(image => ( 31 + {e.view.images.map(image => ( 39 32 <ImageItem 40 33 key={image.thumb} 41 34 thumbnail={image.thumb} ··· 44 37 ))} 45 38 </Outer> 46 39 ) 47 - } else if (AppBskyEmbedExternal.isView(media) && media.external.thumb) { 48 - let url: URL | undefined 49 - try { 50 - url = new URL(media.external.uri) 51 - } catch {} 52 - if (url) { 53 - const {success} = parseTenorGif(url) 54 - if (success) { 55 - return ( 56 - <Outer style={style}> 57 - <GifItem 58 - thumbnail={media.external.thumb} 59 - alt={media.external.title} 60 - /> 61 - </Outer> 62 - ) 63 - } 64 - } 65 - } else if (AppBskyEmbedVideo.isView(media)) { 40 + } else if (e.type === 'link') { 41 + if (!e.view.external.thumb) return null 42 + if (!isTenorGifUri(e.view.external.uri)) return null 43 + return ( 44 + <Outer style={style}> 45 + <GifItem 46 + thumbnail={e.view.external.thumb} 47 + alt={e.view.external.title} 48 + /> 49 + </Outer> 50 + ) 51 + } else if (e.type === 'video') { 66 52 return ( 67 53 <Outer style={style}> 68 - <VideoItem thumbnail={media.thumbnail} alt={media.alt} /> 54 + <VideoItem thumbnail={e.view.thumbnail} alt={e.view.alt} /> 69 55 </Outer> 70 56 ) 71 57 }
+11 -12
src/components/ProfileCard.tsx
··· 1 1 import React from 'react' 2 2 import {GestureResponderEvent, View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 4 moderateProfile, 6 5 ModerationOpts, 7 6 RichText as RichTextApi, ··· 25 24 import {Link as InternalLink, LinkProps} from '#/components/Link' 26 25 import {RichText} from '#/components/RichText' 27 26 import {Text} from '#/components/Typography' 27 + import * as bsky from '#/types/bsky' 28 28 29 29 export function Default({ 30 30 profile, 31 31 moderationOpts, 32 32 logContext = 'ProfileCard', 33 33 }: { 34 - profile: AppBskyActorDefs.ProfileViewDetailed 34 + profile: bsky.profile.AnyProfileView 35 35 moderationOpts: ModerationOpts 36 36 logContext?: 'ProfileCard' | 'StarterPackProfilesList' 37 37 }) { ··· 51 51 moderationOpts, 52 52 logContext = 'ProfileCard', 53 53 }: { 54 - profile: AppBskyActorDefs.ProfileViewDetailed 54 + profile: bsky.profile.AnyProfileView 55 55 moderationOpts: ModerationOpts 56 56 logContext?: 'ProfileCard' | 'StarterPackProfilesList' 57 57 }) { ··· 101 101 style, 102 102 ...rest 103 103 }: { 104 - profile: AppBskyActorDefs.ProfileViewDetailed 104 + profile: bsky.profile.AnyProfileView 105 105 } & Omit<LinkProps, 'to' | 'label'>) { 106 106 const {_} = useLingui() 107 107 return ( ··· 126 126 profile, 127 127 moderationOpts, 128 128 }: { 129 - profile: AppBskyActorDefs.ProfileViewDetailed 129 + profile: bsky.profile.AnyProfileView 130 130 moderationOpts: ModerationOpts 131 131 }) { 132 132 const moderation = moderateProfile(profile, moderationOpts) ··· 161 161 profile, 162 162 moderationOpts, 163 163 }: { 164 - profile: AppBskyActorDefs.ProfileViewDetailed 164 + profile: bsky.profile.AnyProfileView 165 165 moderationOpts: ModerationOpts 166 166 }) { 167 167 const t = useTheme() ··· 224 224 profile: profileUnshadowed, 225 225 numberOfLines = 3, 226 226 }: { 227 - profile: AppBskyActorDefs.ProfileViewDetailed 227 + profile: bsky.profile.AnyProfileView 228 228 numberOfLines?: number 229 229 }) { 230 230 const profile = useProfileShadow(profileUnshadowed) 231 - const {description} = profile 232 231 const rt = React.useMemo(() => { 233 - if (!description) return 234 - const rt = new RichTextApi({text: description || ''}) 232 + if (!('description' in profile)) return 233 + const rt = new RichTextApi({text: profile.description || ''}) 235 234 rt.detectFacetsWithoutResolution() 236 235 return rt 237 - }, [description]) 236 + }, [profile]) 238 237 if (!rt) return null 239 238 if ( 240 239 profile.viewer && ··· 281 280 } 282 281 283 282 export type FollowButtonProps = { 284 - profile: AppBskyActorDefs.ProfileViewBasic 283 + profile: bsky.profile.AnyProfileView 285 284 moderationOpts: ModerationOpts 286 285 logContext: LogEvents['profile:follow']['logContext'] & 287 286 LogEvents['profile:unfollow']['logContext']
+7 -1
src/components/StarterPack/QrCode.tsx
··· 13 13 import {atoms as a} from '#/alf' 14 14 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 15 15 import {Text} from '#/components/Typography' 16 + import * as bsky from '#/types/bsky' 16 17 17 18 const LazyViewShot = React.lazy( 18 19 // @ts-expect-error dynamic import ··· 30 31 ) { 31 32 const {record} = starterPack 32 33 33 - if (!AppBskyGraphStarterpack.isRecord(record)) { 34 + if ( 35 + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 36 + record, 37 + AppBskyGraphStarterpack.isRecord, 38 + ) 39 + ) { 34 40 return null 35 41 } 36 42
+7 -1
src/components/StarterPack/QrCodeDialog.tsx
··· 18 18 import {DialogControlProps} from '#/components/Dialog' 19 19 import {Loader} from '#/components/Loader' 20 20 import {QrCode} from '#/components/StarterPack/QrCode' 21 + import * as bsky from '#/types/bsky' 21 22 22 23 export function QrCodeDialog({ 23 24 starterPack, ··· 77 78 } else { 78 79 setIsProcessing(true) 79 80 80 - if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { 81 + if ( 82 + !bsky.validate( 83 + starterPack.record, 84 + AppBskyGraphStarterpack.validateRecord, 85 + ) 86 + ) { 81 87 return 82 88 } 83 89
+13 -7
src/components/StarterPack/StarterPackCard.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import {Image} from 'expo-image' 4 - import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api' 4 + import {AppBskyGraphStarterpack, AtUri} from '@atproto/api' 5 5 import {msg} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 import {useQueryClient} from '@tanstack/react-query' ··· 15 15 import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack' 16 16 import {Link as BaseLink, LinkProps as BaseLinkProps} from '#/components/Link' 17 17 import {Text} from '#/components/Typography' 18 + import * as bsky from '#/types/bsky' 18 19 19 20 export function Default({ 20 21 starterPack, 21 22 }: { 22 - starterPack?: AppBskyGraphDefs.StarterPackViewBasic 23 + starterPack?: bsky.starterPack.AnyStarterPackView 23 24 }) { 24 25 if (!starterPack) return null 25 26 return ( ··· 32 33 export function Notification({ 33 34 starterPack, 34 35 }: { 35 - starterPack?: AppBskyGraphDefs.StarterPackViewBasic 36 + starterPack?: bsky.starterPack.AnyStarterPackView 36 37 }) { 37 38 if (!starterPack) return null 38 39 return ( ··· 47 48 noIcon, 48 49 noDescription, 49 50 }: { 50 - starterPack: AppBskyGraphDefs.StarterPackViewBasic 51 + starterPack: bsky.starterPack.AnyStarterPackView 51 52 noIcon?: boolean 52 53 noDescription?: boolean 53 54 }) { ··· 57 58 const t = useTheme() 58 59 const {currentAccount} = useSession() 59 60 60 - if (!AppBskyGraphStarterpack.isRecord(record)) { 61 + if ( 62 + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 63 + record, 64 + AppBskyGraphStarterpack.isRecord, 65 + ) 66 + ) { 61 67 return null 62 68 } 63 69 ··· 100 106 starterPack, 101 107 children, 102 108 }: { 103 - starterPack: AppBskyGraphDefs.StarterPackViewBasic 109 + starterPack: bsky.starterPack.AnyStarterPackView 104 110 onPress?: () => void 105 111 children: BaseLinkProps['children'] 106 112 }) { ··· 139 145 export function Embed({ 140 146 starterPack, 141 147 }: { 142 - starterPack: AppBskyGraphDefs.StarterPackViewBasic 148 + starterPack: bsky.starterPack.AnyStarterPackView 143 149 }) { 144 150 const t = useTheme() 145 151 const imageUri = getStarterPackOgCard(starterPack)
+1 -1
src/components/StarterPack/Wizard/WizardEditListDialog.tsx
··· 38 38 state: WizardState 39 39 dispatch: (action: WizardAction) => void 40 40 moderationOpts: ModerationOpts 41 - profile: AppBskyActorDefs.ProfileViewBasic 41 + profile: AppBskyActorDefs.ProfileViewDetailed 42 42 }) { 43 43 const {_} = useLingui() 44 44 const t = useTheme()
+2 -1
src/components/StarterPack/Wizard/WizardListCard.tsx
··· 22 22 import * as Toggle from '#/components/forms/Toggle' 23 23 import {Checkbox} from '#/components/forms/Toggle' 24 24 import {Text} from '#/components/Typography' 25 + import * as bsky from '#/types/bsky' 25 26 26 27 function WizardListCard({ 27 28 type, ··· 123 124 btnType: 'checkbox' | 'remove' 124 125 state: WizardState 125 126 dispatch: (action: WizardAction) => void 126 - profile: AppBskyActorDefs.ProfileViewBasic 127 + profile: bsky.profile.AnyProfileView 127 128 moderationOpts: ModerationOpts 128 129 }) { 129 130 const {currentAccount} = useSession()
+7 -1
src/components/VideoPostCard.tsx
··· 27 27 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 28 28 import * as Hider from '#/components/moderation/Hider' 29 29 import {Text} from '#/components/Typography' 30 + import * as bsky from '#/types/bsky' 30 31 31 32 function getBlackColor(t: ReturnType<typeof useTheme>) { 32 33 return select(t.name, { ··· 78 79 if (!AppBskyEmbedVideo.isView(embed)) return null 79 80 80 81 const author = post.author 81 - const text = AppBskyFeedPost.isRecord(post.record) ? post.record?.text : '' 82 + const text = bsky.dangerousIsType<AppBskyFeedPost.Record>( 83 + post.record, 84 + AppBskyFeedPost.isRecord, 85 + ) 86 + ? post.record?.text 87 + : '' 82 88 const likeCount = post?.likeCount ?? 0 83 89 const repostCount = post?.repostCount ?? 0 84 90 const {thumbnail} = embed
+5 -1
src/components/WhoCanReply.tsx
··· 29 29 import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' 30 30 import {InlineLinkText} from '#/components/Link' 31 31 import {Text} from '#/components/Typography' 32 + import * as bsky from '#/types/bsky' 32 33 import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil' 33 34 34 35 interface WhoCanReplyProps { ··· 48 49 * unexpectedly, we should check to make sure it's for sure the root URI. 49 50 */ 50 51 const rootUri = 51 - AppBskyFeedPost.isRecord(post.record) && post.record.reply?.root 52 + bsky.dangerousIsType<AppBskyFeedPost.Record>( 53 + post.record, 54 + AppBskyFeedPost.isRecord, 55 + ) && post.record.reply?.root 52 56 ? post.record.reply.root.uri 53 57 : post.uri 54 58 const settings = React.useMemo(() => {
+4 -7
src/components/dms/ConvoMenu.tsx
··· 1 1 import React, {useCallback} from 'react' 2 2 import {Keyboard, Pressable, View} from 'react-native' 3 - import { 4 - AppBskyActorDefs, 5 - ChatBskyConvoDefs, 6 - ModerationCause, 7 - } from '@atproto/api' 3 + import {ChatBskyConvoDefs, ModerationCause} from '@atproto/api' 8 4 import {msg, Trans} from '@lingui/macro' 9 5 import {useLingui} from '@lingui/react' 10 6 import {useNavigation} from '@react-navigation/native' ··· 34 30 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 35 31 import * as Menu from '#/components/Menu' 36 32 import * as Prompt from '#/components/Prompt' 33 + import * as bsky from '#/types/bsky' 37 34 import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' 38 35 import {ReportDialog} from './ReportDialog' 39 36 ··· 49 46 style, 50 47 }: { 51 48 convo: ChatBskyConvoDefs.ConvoView 52 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 49 + profile: Shadow<bsky.profile.AnyProfileView> 53 50 control?: Menu.MenuControlProps 54 51 currentScreen: 'list' | 'conversation' 55 52 showMarkAsRead?: boolean ··· 148 145 blockedByListControl, 149 146 }: { 150 147 convo: ChatBskyConvoDefs.ConvoView 151 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 148 + profile: Shadow<bsky.profile.AnyProfileView> 152 149 showMarkAsRead?: boolean 153 150 blockInfo: { 154 151 listBlocks: ModerationCause[]
+1 -1
src/components/dms/MessageProfileButton.tsx
··· 19 19 export function MessageProfileButton({ 20 20 profile, 21 21 }: { 22 - profile: AppBskyActorDefs.ProfileView 22 + profile: AppBskyActorDefs.ProfileViewDetailed 23 23 }) { 24 24 const {_} = useLingui() 25 25 const t = useTheme()
+3 -2
src/components/dms/MessagesListBlockedFooter.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' 3 + import {ModerationDecision} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 14 14 import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 15 15 import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' 16 16 import {Text} from '#/components/Typography' 17 + import * as bsky from '#/types/bsky' 17 18 18 19 export function MessagesListBlockedFooter({ 19 20 recipient: initialRecipient, ··· 21 22 hasMessages, 22 23 moderation, 23 24 }: { 24 - recipient: AppBskyActorDefs.ProfileViewBasic 25 + recipient: bsky.profile.AnyProfileView 25 26 convoId: string 26 27 hasMessages: boolean 27 28 moderation: ModerationDecision
+6 -3
src/components/dms/MessagesListHeader.tsx
··· 17 17 import {isWeb} from '#/platform/detection' 18 18 import {Shadow} from '#/state/cache/profile-shadow' 19 19 import {isConvoActive, useConvo} from '#/state/messages/convo' 20 + import {ConvoItem} from '#/state/messages/convo/types' 20 21 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' 21 22 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 22 23 import {ConvoMenu} from '#/components/dms/ConvoMenu' ··· 31 32 profile, 32 33 moderation, 33 34 }: { 34 - profile?: Shadow<AppBskyActorDefs.ProfileViewBasic> 35 + profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> 35 36 moderation?: ModerationDecision 36 37 }): React.ReactNode => { 37 38 const t = useTheme() ··· 138 139 moderation, 139 140 blockInfo, 140 141 }: { 141 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 142 + profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> 142 143 moderation: ModerationDecision 143 144 blockInfo: { 144 145 listBlocks: ModerationCause[] ··· 157 158 moderation.ui('displayName'), 158 159 ) 159 160 161 + // @ts-ignore findLast is polyfilled - esb 160 162 const latestMessageFromOther = convoState.items.findLast( 161 - item => item.type === 'message' && item.message.sender.did === profile.did, 163 + (item: ConvoItem) => 164 + item.type === 'message' && item.message.sender.did === profile.did, 162 165 ) 163 166 164 167 const latestReportableMessage =
+9 -7
src/components/dms/ReportDialog.tsx
··· 1 1 import React, {memo, useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import { 4 + $Typed, 4 5 AppBskyActorDefs, 5 6 ChatBskyConvoDefs, 6 7 ComAtprotoModerationCreateReport, ··· 154 155 mutationFn: async () => { 155 156 if (params.type === 'convoMessage') { 156 157 const {convoId, message} = params 158 + const subject: $Typed<ChatBskyConvoDefs.MessageRef> = { 159 + $type: 'chat.bsky.convo.defs#messageRef', 160 + messageId: message.id, 161 + convoId, 162 + did: message.sender.did, 163 + } 157 164 158 165 const report = { 159 166 reasonType: reportOption.reason, 160 - subject: { 161 - $type: 'chat.bsky.convo.defs#messageRef', 162 - messageId: message.id, 163 - convoId, 164 - did: message.sender.did, 165 - } satisfies ChatBskyConvoDefs.MessageRef, 167 + subject, 166 168 reason: details, 167 169 } satisfies ComAtprotoModerationCreateReport.InputSchema 168 170 ··· 285 287 }: { 286 288 convoId: string 287 289 currentScreen: 'list' | 'conversation' 288 - profile: AppBskyActorDefs.ProfileViewBasic 290 + profile: AppBskyActorDefs.ProfileViewDetailed 289 291 }) { 290 292 const {_} = useLingui() 291 293 const navigation = useNavigation<NavigationProp>()
+4 -3
src/components/dms/dialogs/SearchablePeopleList.tsx
··· 6 6 useState, 7 7 } from 'react' 8 8 import {TextInput, View} from 'react-native' 9 - import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' 9 + import {moderateProfile, ModerationOpts} from '@atproto/api' 10 10 import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 ··· 28 28 import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' 29 29 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 30 30 import {Text} from '#/components/Typography' 31 + import * as bsky from '#/types/bsky' 31 32 32 33 type Item = 33 34 | { 34 35 type: 'profile' 35 36 key: string 36 37 enabled: boolean 37 - profile: AppBskyActorDefs.ProfileView 38 + profile: bsky.profile.AnyProfileView 38 39 } 39 40 | { 40 41 type: 'empty' ··· 330 331 onPress, 331 332 }: { 332 333 enabled: boolean 333 - profile: AppBskyActorDefs.ProfileView 334 + profile: bsky.profile.AnyProfileView 334 335 moderationOpts: ModerationOpts 335 336 onPress: (did: string) => void 336 337 }) {
+2 -2
src/components/dms/util.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import * as bsky from '#/types/bsky' 2 2 3 - export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) { 3 + export function canBeMessaged(profile: bsky.profile.AnyProfileView) { 4 4 switch (profile.associated?.chat?.allowIncoming) { 5 5 case 'none': 6 6 return false
+2 -2
src/components/hooks/useFollowMethods.ts
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs} from '@atproto/api' 3 2 import {msg} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 ··· 9 8 import {useProfileFollowMutationQueue} from '#/state/queries/profile' 10 9 import {useRequireAuth} from '#/state/session' 11 10 import * as Toast from '#/view/com/util/Toast' 11 + import * as bsky from '#/types/bsky' 12 12 13 13 export function useFollowMethods({ 14 14 profile, 15 15 logContext, 16 16 }: { 17 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 17 + profile: Shadow<bsky.profile.AnyProfileView> 18 18 logContext: LogEvents['profile:follow']['logContext'] & 19 19 LogEvents['profile:unfollow']['logContext'] 20 20 }) {
+7 -4
src/lib/api/feed-manip.ts
··· 6 6 AppBskyFeedPost, 7 7 } from '@atproto/api' 8 8 9 + import * as bsky from '#/types/bsky' 9 10 import {isPostInLanguage} from '../../locale/helpers' 10 11 import {FALLBACK_MARKER_POST} from './feed/home' 11 12 import {ReasonFeedSource} from './feed/types' ··· 57 58 } 58 59 this._feedPost = feedPost 59 60 this._reactKey = `slice-${post.uri}-${ 60 - feedPost.reason?.indexedAt || post.indexedAt 61 + feedPost.reason && 'indexedAt' in feedPost.reason 62 + ? feedPost.reason.indexedAt 63 + : post.indexedAt 61 64 }` 62 65 if (feedPost.post.uri === FALLBACK_MARKER_POST.post.uri) { 63 66 this.isFallbackMarker = true ··· 65 68 } 66 69 if ( 67 70 !AppBskyFeedPost.isRecord(post.record) || 68 - !AppBskyFeedPost.validateRecord(post.record).success 71 + !bsky.validate(post.record, AppBskyFeedPost.validateRecord) 69 72 ) { 70 73 return 71 74 } ··· 97 100 if ( 98 101 !AppBskyFeedDefs.isPostView(parent) || 99 102 !AppBskyFeedPost.isRecord(parent.record) || 100 - !AppBskyFeedPost.validateRecord(parent.record).success 103 + !bsky.validate(parent.record, AppBskyFeedPost.validateRecord) 101 104 ) { 102 105 this.isOrphan = true 103 106 return ··· 139 142 if ( 140 143 !AppBskyFeedDefs.isPostView(root) || 141 144 !AppBskyFeedPost.isRecord(root.record) || 142 - !AppBskyFeedPost.validateRecord(root.record).success 145 + !bsky.validate(root.record, AppBskyFeedPost.validateRecord) 143 146 ) { 144 147 this.isOrphan = true 145 148 return
+1
src/lib/api/feed/merge.ts
··· 311 311 ) 312 312 // attach source info 313 313 for (const post of res.data.feed) { 314 + // @ts-ignore 314 315 post.__source = this.sourceInfo 315 316 } 316 317 return res
-24
src/lib/api/hack-add-deleted-embed.ts
··· 1 - import { 2 - AppBskyFeedDefs, 3 - AppBskyFeedPost, 4 - ComAtprotoRepoStrongRef, 5 - } from '@atproto/api' 6 - 7 - /** 8 - * HACK 9 - * The server doesnt seem to be correctly giving the notFound view yet 10 - * so I'm adding it manually for now 11 - * -prf 12 - */ 13 - export function hackAddDeletedEmbed(post: AppBskyFeedDefs.PostView) { 14 - const record = post.record as AppBskyFeedPost.Record 15 - if (record.embed?.$type === 'app.bsky.embed.record' && !post.embed) { 16 - post.embed = { 17 - $type: 'app.bsky.embed.record#view', 18 - record: { 19 - $type: 'app.bsky.embed.record#viewNotFound', 20 - uri: (record.embed.record as ComAtprotoRepoStrongRef.Main).uri, 21 - }, 22 - } 23 - } 24 - }
+11 -10
src/lib/api/index.ts
··· 1 1 import { 2 + $Typed, 2 3 AppBskyEmbedExternal, 3 4 AppBskyEmbedImages, 4 5 AppBskyEmbedRecord, ··· 74 75 } 75 76 76 77 const did = agent.assertDid 77 - const writes: ComAtprotoRepoApplyWrites.Create[] = [] 78 + const writes: $Typed<ComAtprotoRepoApplyWrites.Create>[] = [] 78 79 const uris: string[] = [] 79 80 80 81 let now = new Date() ··· 91 92 draft, 92 93 opts.onStateChange, 93 94 ) 94 - let labels: ComAtprotoLabelDefs.SelfLabels | undefined 95 + let labels: $Typed<ComAtprotoLabelDefs.SelfLabels> | undefined 95 96 if (draft.labels.length) { 96 97 labels = { 97 98 $type: 'com.atproto.label.defs#selfLabels', ··· 230 231 draft: PostDraft, 231 232 onStateChange: ((state: string) => void) | undefined, 232 233 ): Promise< 233 - | AppBskyEmbedImages.Main 234 - | AppBskyEmbedVideo.Main 235 - | AppBskyEmbedExternal.Main 236 - | AppBskyEmbedRecord.Main 237 - | AppBskyEmbedRecordWithMedia.Main 234 + | $Typed<AppBskyEmbedImages.Main> 235 + | $Typed<AppBskyEmbedVideo.Main> 236 + | $Typed<AppBskyEmbedExternal.Main> 237 + | $Typed<AppBskyEmbedRecord.Main> 238 + | $Typed<AppBskyEmbedRecordWithMedia.Main> 238 239 | undefined 239 240 > { 240 241 if (draft.embed.quote) { ··· 288 289 embedDraft: EmbedDraft, 289 290 onStateChange: ((state: string) => void) | undefined, 290 291 ): Promise< 291 - | AppBskyEmbedExternal.Main 292 - | AppBskyEmbedImages.Main 293 - | AppBskyEmbedVideo.Main 292 + | $Typed<AppBskyEmbedExternal.Main> 293 + | $Typed<AppBskyEmbedImages.Main> 294 + | $Typed<AppBskyEmbedVideo.Main> 294 295 | undefined 295 296 > { 296 297 if (embedDraft.media?.type === 'images') {
-24
src/lib/embeds.ts
··· 1 - import { 2 - AppBskyEmbedRecord, 3 - AppBskyEmbedRecordWithMedia, 4 - AppBskyFeedDefs, 5 - } from '@atproto/api' 6 - 7 - export function isEmbedByEmbedder( 8 - embed: AppBskyFeedDefs.PostView['embed'], 9 - did: string, 10 - ): boolean { 11 - if (!embed) { 12 - return false 13 - } 14 - if (AppBskyEmbedRecord.isViewRecord(embed.record)) { 15 - return embed.record.author.did === did 16 - } 17 - if ( 18 - AppBskyEmbedRecordWithMedia.isView(embed) && 19 - AppBskyEmbedRecord.isViewRecord(embed.record.record) 20 - ) { 21 - return embed.record.record.author.did === did 22 - } 23 - return true 24 - }
+13 -4
src/lib/generate-starterpack.ts
··· 1 1 import { 2 + $Typed, 2 3 AppBskyActorDefs, 3 4 AppBskyGraphGetStarterPack, 4 5 BskyAgent, 6 + ComAtprotoRepoApplyWrites, 5 7 Facet, 6 8 } from '@atproto/api' 7 9 import {msg} from '@lingui/macro' ··· 13 15 import {sanitizeHandle} from '#/lib/strings/handles' 14 16 import {enforceLen} from '#/lib/strings/helpers' 15 17 import {useAgent} from '#/state/session' 18 + import * as bsky from '#/types/bsky' 16 19 17 20 export const createStarterPackList = async ({ 18 21 name, ··· 24 27 name: string 25 28 description?: string 26 29 descriptionFacets?: Facet[] 27 - profiles: AppBskyActorDefs.ProfileViewBasic[] 30 + profiles: bsky.profile.AnyProfileView[] 28 31 agent: BskyAgent 29 32 }): Promise<{uri: string; cid: string}> => { 30 33 if (profiles.length === 0) throw new Error('No profiles given') ··· 68 71 69 72 return useMutation<{uri: string; cid: string}, Error, void>({ 70 73 mutationFn: async () => { 71 - let profile: AppBskyActorDefs.ProfileViewBasic | undefined 72 - let profiles: AppBskyActorDefs.ProfileViewBasic[] | undefined 74 + let profile: AppBskyActorDefs.ProfileViewDetailed | undefined 75 + let profiles: AppBskyActorDefs.ProfileView[] | undefined 73 76 74 77 await Promise.all([ 75 78 (async () => { ··· 136 139 }) 137 140 } 138 141 139 - function createListItem({did, listUri}: {did: string; listUri: string}) { 142 + function createListItem({ 143 + did, 144 + listUri, 145 + }: { 146 + did: string 147 + listUri: string 148 + }): $Typed<ComAtprotoRepoApplyWrites.Create> { 140 149 return { 141 150 $type: 'com.atproto.repo.applyWrites#create', 142 151 collection: 'app.bsky.graph.listitem',
+3 -11
src/lib/moderation/blocked-and-muted.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import * as bsky from '#/types/bsky' 2 2 3 - export function isBlockedOrBlocking( 4 - profile: 5 - | AppBskyActorDefs.ProfileViewBasic 6 - | AppBskyActorDefs.ProfileViewDetailed, 7 - ) { 3 + export function isBlockedOrBlocking(profile: bsky.profile.AnyProfileView) { 8 4 return profile.viewer?.blockedBy || profile.viewer?.blocking 9 5 } 10 6 11 - export function isMuted( 12 - profile: 13 - | AppBskyActorDefs.ProfileViewBasic 14 - | AppBskyActorDefs.ProfileViewDetailed, 15 - ) { 7 + export function isMuted(profile: bsky.profile.AnyProfileView) { 16 8 return profile.viewer?.muted || profile.viewer?.mutedByList 17 9 }
+9
src/lib/strings/embed-player.ts
··· 568 568 dimensions, 569 569 } 570 570 } 571 + 572 + export function isTenorGifUri(url: URL | string) { 573 + try { 574 + return parseTenorGif(typeof url === 'string' ? new URL(url) : url).success 575 + } catch { 576 + // Invalid URL 577 + return false 578 + } 579 + }
+4 -2
src/lib/strings/starter-pack.ts
··· 1 - import {AppBskyGraphDefs, AtUri} from '@atproto/api' 1 + import {AtUri} from '@atproto/api' 2 + 3 + import * as bsky from '#/types/bsky' 2 4 3 5 export function createStarterPackLinkFromAndroidReferrer( 4 6 referrerQueryString: string, ··· 79 81 } 80 82 81 83 export function getStarterPackOgCard( 82 - didOrStarterPack: AppBskyGraphDefs.StarterPackView | string, 84 + didOrStarterPack: bsky.starterPack.AnyStarterPackView | string, 83 85 rkey?: string, 84 86 ) { 85 87 if (typeof didOrStarterPack === 'string') {
+1 -1
src/screens/Messages/Conversation.tsx
··· 165 165 setHasScrolled, 166 166 }: { 167 167 moderation: ModerationDecision 168 - recipient: Shadow<AppBskyActorDefs.ProfileViewBasic> 168 + recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> 169 169 hasScrolled: boolean 170 170 setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> 171 171 }) {
+2 -2
src/screens/Messages/components/ChatListItem.tsx
··· 1 1 import React, {useCallback, useMemo, useState} from 'react' 2 2 import {GestureResponderEvent, View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 4 AppBskyEmbedRecord, 6 5 ChatBskyConvoDefs, 7 6 moderateProfile, ··· 44 43 import {useMenuControl} from '#/components/Menu' 45 44 import {PostAlerts} from '#/components/moderation/PostAlerts' 46 45 import {Text} from '#/components/Typography' 46 + import * as bsky from '#/types/bsky' 47 47 48 48 export let ChatListItem = ({ 49 49 convo, ··· 78 78 moderationOpts, 79 79 }: { 80 80 convo: ChatBskyConvoDefs.ConvoView 81 - profile: AppBskyActorDefs.ProfileViewBasic 81 + profile: bsky.profile.AnyProfileView 82 82 moderationOpts: ModerationOpts 83 83 }) { 84 84 const t = useTheme()
+5 -2
src/screens/Messages/components/MessageInputEmbed.tsx
··· 30 30 import {PostAlerts} from '#/components/moderation/PostAlerts' 31 31 import {RichText} from '#/components/RichText' 32 32 import {Text} from '#/components/Typography' 33 + import * as bsky from '#/types/bsky' 33 34 34 35 export function useMessageEmbed() { 35 36 const route = ··· 113 114 const {rt, record} = useMemo(() => { 114 115 if ( 115 116 post && 116 - AppBskyFeedPost.isRecord(post.record) && 117 - AppBskyFeedPost.validateRecord(post.record).success 117 + bsky.dangerousIsType<AppBskyFeedPost.Record>( 118 + post.record, 119 + AppBskyFeedPost.isRecord, 120 + ) 118 121 ) { 119 122 return { 120 123 rt: new RichTextAPI({
+7 -2
src/screens/Messages/components/MessagesList.tsx
··· 10 10 } from 'react-native-reanimated' 11 11 import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' 12 12 import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 - import {AppBskyEmbedRecord, AppBskyRichtextFacet, RichText} from '@atproto/api' 13 + import { 14 + $Typed, 15 + AppBskyEmbedRecord, 16 + AppBskyRichtextFacet, 17 + RichText, 18 + } from '@atproto/api' 14 19 15 20 import {clamp} from '#/lib/numbers' 16 21 import {ScrollProvider} from '#/lib/ScrollContext' ··· 297 302 // we want to remove the post link from the text, re-trim, then detect facets 298 303 rt.detectFacetsWithoutResolution() 299 304 300 - let embed: AppBskyEmbedRecord.Main | undefined 305 + let embed: $Typed<AppBskyEmbedRecord.Main> | undefined 301 306 302 307 if (embedUri) { 303 308 try {
+21 -10
src/screens/Onboarding/StepFinished.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 3 + import { 4 + AppBskyActorProfile, 5 + AppBskyGraphDefs, 6 + AppBskyGraphStarterpack, 7 + Un$Typed, 8 + } from '@atproto/api' 4 9 import {SavedFeed} from '@atproto/api/dist/client/types/app/bsky/actor/defs' 5 10 import {TID} from '@atproto/common-web' 6 11 import {msg, Trans} from '@lingui/macro' ··· 44 49 import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' 45 50 import {Loader} from '#/components/Loader' 46 51 import {Text} from '#/components/Typography' 52 + import * as bsky from '#/types/bsky' 47 53 48 54 export function StepFinished() { 49 55 const {_} = useLingui() ··· 141 147 : undefined 142 148 143 149 await agent.upsertProfile(async existing => { 144 - existing = existing ?? {} 150 + let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} 145 151 146 152 if (blobPromise) { 147 153 const res = await blobPromise 148 154 if (res.data.blob) { 149 - existing.avatar = res.data.blob 155 + next.avatar = res.data.blob 150 156 } 151 157 } 152 158 153 159 if (starterPack) { 154 - existing.joinedViaStarterPack = { 160 + next.joinedViaStarterPack = { 155 161 uri: starterPack.uri, 156 162 cid: starterPack.cid, 157 163 } 158 164 } 159 165 160 - existing.displayName = '' 166 + next.displayName = '' 161 167 // HACKFIX 162 168 // creating a bunch of identical profile objects is breaking the relay 163 169 // tossing this unspecced field onto it to reduce the size of the problem 164 170 // -prf 165 - existing.createdAt = new Date().toISOString() 166 - return existing 171 + next.createdAt = new Date().toISOString() 172 + return next 167 173 }) 168 174 169 175 logEvent('onboarding:finished:avatarResult', { ··· 205 211 onboardDispatch({type: 'finish'}) 206 212 logEvent('onboarding:finished:nextPressed', { 207 213 usedStarterPack: Boolean(starterPack), 208 - starterPackName: AppBskyGraphStarterpack.isRecord(starterPack?.record) 209 - ? starterPack.record.name 210 - : undefined, 214 + starterPackName: 215 + starterPack && 216 + bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 217 + starterPack.record, 218 + AppBskyGraphStarterpack.isRecord, 219 + ) 220 + ? starterPack.record.name 221 + : undefined, 211 222 starterPackCreator: starterPack?.creator.did, 212 223 starterPackUri: starterPack?.uri, 213 224 profilesFollowed: listItems?.length ?? 0,
+10 -7
src/screens/Onboarding/util.ts
··· 1 1 import { 2 + $Typed, 2 3 AppBskyGraphFollow, 3 4 AppBskyGraphGetFollows, 4 5 BskyAgent, 6 + ComAtprotoRepoApplyWrites, 5 7 } from '@atproto/api' 6 8 import {TID} from '@atproto/common-web' 7 9 import chunk from 'lodash.chunk' ··· 15 17 throw new Error(`bulkWriteFollows failed: no session`) 16 18 } 17 19 18 - const followRecords: AppBskyGraphFollow.Record[] = dids.map(did => { 20 + const followRecords: $Typed<AppBskyGraphFollow.Record>[] = dids.map(did => { 19 21 return { 20 22 $type: 'app.bsky.graph.follow', 21 23 subject: did, ··· 23 25 } 24 26 }) 25 27 26 - const followWrites = followRecords.map(r => ({ 27 - $type: 'com.atproto.repo.applyWrites#create', 28 - collection: 'app.bsky.graph.follow', 29 - rkey: TID.nextStr(), 30 - value: r, 31 - })) 28 + const followWrites: $Typed<ComAtprotoRepoApplyWrites.Create>[] = 29 + followRecords.map(r => ({ 30 + $type: 'com.atproto.repo.applyWrites#create', 31 + collection: 'app.bsky.graph.follow', 32 + rkey: TID.nextStr(), 33 + value: r, 34 + })) 32 35 33 36 const chunks = chunk(followWrites, 50) 34 37 for (const chunk of chunks) {
+1 -1
src/screens/Profile/KnownFollowers.tsx
··· 21 21 item, 22 22 index, 23 23 }: { 24 - item: AppBskyActorDefs.ProfileViewBasic 24 + item: AppBskyActorDefs.ProfileView 25 25 index: number 26 26 }) { 27 27 return (
+13 -6
src/screens/Settings/components/PwiOptOut.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ComAtprotoLabelDefs} from '@atproto/api' 3 + import {$Typed, ComAtprotoLabelDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 12 12 import {atoms as a, useTheme} from '#/alf' 13 13 import * as Toggle from '#/components/forms/Toggle' 14 14 import {Text} from '#/components/Typography' 15 + import * as bsky from '#/types/bsky' 15 16 16 17 export function PwiOptOut() { 17 18 const t = useTheme() ··· 33 34 profile, 34 35 updates: existing => { 35 36 // create labels attr if needed 36 - existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) 37 + const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate( 38 + existing.labels, 39 + ComAtprotoLabelDefs.validateSelfLabels, 40 + ) 37 41 ? existing.labels 38 42 : { 39 43 $type: 'com.atproto.label.defs#selfLabels', ··· 41 45 } 42 46 43 47 // toggle the label 44 - const hasLabel = existing.labels.values.some( 48 + const hasLabel = labels.values.some( 45 49 l => l.val === '!no-unauthenticated', 46 50 ) 47 51 if (hasLabel) { 48 52 wasAdded = false 49 - existing.labels.values = existing.labels.values.filter( 53 + labels.values = labels.values.filter( 50 54 l => l.val !== '!no-unauthenticated', 51 55 ) 52 56 } else { 53 57 wasAdded = true 54 - existing.labels.values.push({val: '!no-unauthenticated'}) 58 + labels.values.push({val: '!no-unauthenticated'}) 55 59 } 56 60 57 61 // delete if no longer needed 58 - if (existing.labels.values.length === 0) { 62 + if (labels.values.length === 0) { 59 63 delete existing.labels 64 + } else { 65 + existing.labels = labels 60 66 } 67 + 61 68 return existing 62 69 }, 63 70 checkCommitted: res => {
+5 -1
src/screens/Signup/index.tsx
··· 26 26 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 27 27 import {InlineLinkText} from '#/components/Link' 28 28 import {Text} from '#/components/Typography' 29 + import * as bsky from '#/types/bsky' 29 30 30 31 export function Signup({onPressBack}: {onPressBack: () => void}) { 31 32 const {_} = useLingui() ··· 95 96 scrollable> 96 97 <View testID="createAccount" style={a.flex_1}> 97 98 {showStarterPackCard && 98 - AppBskyGraphStarterpack.isRecord(starterPack.record) ? ( 99 + bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 100 + starterPack.record, 101 + AppBskyGraphStarterpack.isRecord, 102 + ) ? ( 99 103 <Animated.View entering={!isFetchedAtMount ? FadeIn : undefined}> 100 104 <LinearGradientBackground 101 105 style={[a.mx_lg, a.p_lg, a.gap_sm, a.rounded_sm]}>
+7 -1
src/screens/StarterPack/StarterPackLandingScreen.tsx
··· 38 38 import * as Prompt from '#/components/Prompt' 39 39 import {RichText} from '#/components/RichText' 40 40 import {Text} from '#/components/Typography' 41 + import * as bsky from '#/types/bsky' 41 42 42 43 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 43 44 ··· 85 86 } 86 87 87 88 // Just for types, this cannot be hit 88 - if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { 89 + if ( 90 + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 91 + starterPack.record, 92 + AppBskyGraphStarterpack.isRecord, 93 + ) 94 + ) { 89 95 return null 90 96 } 91 97
+7 -1
src/screens/StarterPack/StarterPackScreen.tsx
··· 66 66 import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' 67 67 import {ShareDialog} from '#/components/StarterPack/ShareDialog' 68 68 import {Text} from '#/components/Typography' 69 + import * as bsky from '#/types/bsky' 69 70 70 71 type StarterPackScreeProps = NativeStackScreenProps< 71 72 CommonNavigatorParams, ··· 387 388 }) 388 389 } 389 390 390 - if (!AppBskyGraphStarterpack.isRecord(record)) { 391 + if ( 392 + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 393 + record, 394 + AppBskyGraphStarterpack.isRecord, 395 + ) 396 + ) { 391 397 return null 392 398 } 393 399
+8 -9
src/screens/StarterPack/Wizard/State.tsx
··· 1 1 import React from 'react' 2 - import { 3 - AppBskyActorDefs, 4 - AppBskyGraphDefs, 5 - AppBskyGraphStarterpack, 6 - } from '@atproto/api' 2 + import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 3 import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' 8 4 import {msg} from '@lingui/macro' 9 5 10 6 import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' 11 7 import {useSession} from '#/state/session' 12 8 import * as Toast from '#/view/com/util/Toast' 9 + import * as bsky from '#/types/bsky' 13 10 14 11 const steps = ['Details', 'Profiles', 'Feeds'] as const 15 12 type Step = (typeof steps)[number] ··· 20 17 | {type: 'SetCanNext'; canNext: boolean} 21 18 | {type: 'SetName'; name: string} 22 19 | {type: 'SetDescription'; description: string} 23 - | {type: 'AddProfile'; profile: AppBskyActorDefs.ProfileViewBasic} 20 + | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} 24 21 | {type: 'RemoveProfile'; profileDid: string} 25 22 | {type: 'AddFeed'; feed: GeneratorView} 26 23 | {type: 'RemoveFeed'; feedUri: string} ··· 32 29 currentStep: Step 33 30 name?: string 34 31 description?: string 35 - profiles: AppBskyActorDefs.ProfileViewBasic[] 32 + profiles: bsky.profile.AnyProfileView[] 36 33 feeds: GeneratorView[] 37 34 processing: boolean 38 35 error?: string ··· 113 110 return updatedState 114 111 } 115 112 116 - // TODO supply the initial state to this component 117 113 export function Provider({ 118 114 starterPack, 119 115 listItems, ··· 126 122 const {currentAccount} = useSession() 127 123 128 124 const createInitialState = (): State => { 129 - if (starterPack && AppBskyGraphStarterpack.isRecord(starterPack.record)) { 125 + if ( 126 + starterPack && 127 + bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) 128 + ) { 130 129 return { 131 130 canNext: true, 132 131 currentStep: 'Details',
+2 -1
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 16 16 import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' 17 17 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 18 18 import {Text} from '#/components/Typography' 19 + import * as bsky from '#/types/bsky' 19 20 20 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { 21 22 return item?.did ?? '' ··· 50 51 51 52 const renderItem = ({ 52 53 item, 53 - }: ListRenderItemInfo<AppBskyActorDefs.ProfileViewBasic>) => { 54 + }: ListRenderItemInfo<bsky.profile.AnyProfileView>) => { 54 55 return ( 55 56 <WizardProfileCard 56 57 profile={item}
+5 -4
src/screens/StarterPack/Wizard/index.tsx
··· 54 54 import {Loader} from '#/components/Loader' 55 55 import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' 56 56 import {Text} from '#/components/Typography' 57 + import * as bsky from '#/types/bsky' 57 58 import {Provider} from './State' 58 59 59 60 export function Wizard({ ··· 141 142 }: { 142 143 currentStarterPack?: AppBskyGraphDefs.StarterPackView 143 144 currentListItems?: AppBskyGraphDefs.ListItemView[] 144 - profile: AppBskyActorDefs.ProfileViewBasic 145 + profile: AppBskyActorDefs.ProfileViewDetailed 145 146 moderationOpts: ModerationOpts 146 147 }) { 147 148 const navigation = useNavigation<NavigationProp>() ··· 363 364 onNext: () => void 364 365 nextBtnText: string 365 366 moderationOpts: ModerationOpts 366 - profile: AppBskyActorDefs.ProfileViewBasic 367 + profile: AppBskyActorDefs.ProfileViewDetailed 367 368 }) { 368 369 const {_} = useLingui() 369 370 const t = useTheme() ··· 577 578 ) 578 579 } 579 580 580 - function getName(item: AppBskyActorDefs.ProfileViewBasic | GeneratorView) { 581 + function getName(item: bsky.profile.AnyProfileView | GeneratorView) { 581 582 if (typeof item.displayName === 'string') { 582 583 return enforceLen(sanitizeDisplayName(item.displayName), 28, true) 583 - } else if (typeof item.handle === 'string') { 584 + } else if ('handle' in item && typeof item.handle === 'string') { 584 585 return enforceLen(sanitizeHandle(item.handle), 28, true) 585 586 } 586 587 return ''
+7 -1
src/screens/VideoFeed/index.tsx
··· 90 90 import * as Hider from '#/components/moderation/Hider' 91 91 import {RichText} from '#/components/RichText' 92 92 import {Text} from '#/components/Typography' 93 + import * as bsky from '#/types/bsky' 93 94 import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' 94 95 95 96 function createThreeVideoPlayers( ··· 694 695 ) 695 696 696 697 const rkey = new AtUri(post.uri).rkey 697 - const record = AppBskyFeedPost.isRecord(post.record) ? post.record : undefined 698 + const record = bsky.dangerousIsType<AppBskyFeedPost.Record>( 699 + post.record, 700 + AppBskyFeedPost.isRecord, 701 + ) 702 + ? post.record 703 + : undefined 698 704 const richText = new RichTextAPI({ 699 705 text: record?.text || '', 700 706 facets: record?.facets,
+7 -6
src/state/cache/profile-shadow.ts
··· 1 1 import {useEffect, useMemo, useState} from 'react' 2 - import {AppBskyActorDefs} from '@atproto/api' 3 2 import {QueryClient} from '@tanstack/react-query' 4 3 import EventEmitter from 'eventemitter3' 5 4 6 5 import {batchedUpdates} from '#/lib/batchedUpdates' 6 + import * as bsky from '#/types/bsky' 7 7 import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search' 8 8 import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers' 9 9 import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members' ··· 20 20 import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '../queries/profile-follows' 21 21 import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '../queries/suggested-follows' 22 22 import {castAsShadow, Shadow} from './types' 23 + 23 24 export type {Shadow} from './types' 24 25 25 26 export interface ProfileShadow { ··· 29 30 } 30 31 31 32 const shadows: WeakMap< 32 - AppBskyActorDefs.ProfileView, 33 + bsky.profile.AnyProfileView, 33 34 Partial<ProfileShadow> 34 35 > = new WeakMap() 35 36 const emitter = new EventEmitter() 36 37 37 38 export function useProfileShadow< 38 - TProfileView extends AppBskyActorDefs.ProfileView, 39 + TProfileView extends bsky.profile.AnyProfileView, 39 40 >(profile: TProfileView): Shadow<TProfileView> { 40 41 const [shadow, setShadow] = useState(() => shadows.get(profile)) 41 42 const [prevPost, setPrevPost] = useState(profile) ··· 68 69 * This is useful for when the profile is not guaranteed to be loaded yet. 69 70 */ 70 71 export function useMaybeProfileShadow< 71 - TProfileView extends AppBskyActorDefs.ProfileView, 72 + TProfileView extends bsky.profile.AnyProfileView, 72 73 >(profile?: TProfileView): Shadow<TProfileView> | undefined { 73 74 const [shadow, setShadow] = useState(() => 74 75 profile ? shadows.get(profile) : undefined, ··· 115 116 }) 116 117 } 117 118 118 - function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>( 119 + function mergeShadow<TProfileView extends bsky.profile.AnyProfileView>( 119 120 profile: TProfileView, 120 121 shadow: Partial<ProfileShadow>, 121 122 ): Shadow<TProfileView> { ··· 137 138 function* findProfilesInCache( 138 139 queryClient: QueryClient, 139 140 did: string, 140 - ): Generator<AppBskyActorDefs.ProfileView, void> { 141 + ): Generator<bsky.profile.AnyProfileView, void> { 141 142 yield* findAllProfilesInListMembersQueryData(queryClient, did) 142 143 yield* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did) 143 144 yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did)
+1
src/state/cache/thread-mutes.tsx
··· 69 69 while (!cancelled) { 70 70 const threads = persisted.get('mutedThreads') 71 71 72 + // @ts-ignore findLast is polyfilled - esb 72 73 const root = threads.findLast(uri => uri.includes(currentAccount.did)) 73 74 74 75 if (!root) break
+13 -10
src/state/messages/convo/agent.ts
··· 1 1 import { 2 - AppBskyActorDefs, 3 2 BskyAgent, 3 + ChatBskyActorDefs, 4 4 ChatBskyConvoDefs, 5 5 ChatBskyConvoGetLog, 6 6 ChatBskyConvoSendMessage, ··· 80 80 81 81 convoId: string 82 82 convo: ChatBskyConvoDefs.ConvoView | undefined 83 - sender: AppBskyActorDefs.ProfileViewBasic | undefined 84 - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined 83 + sender: ChatBskyActorDefs.ProfileViewBasic | undefined 84 + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined 85 85 snapshot: ConvoState | undefined 86 86 87 87 constructor(params: ConvoParams) { ··· 463 463 throw new Error('Convo: could not find recipients in convo') 464 464 } 465 465 466 - const userIsDisabled = this.sender.chatDisabled as boolean 466 + const userIsDisabled = Boolean(this.sender.chatDisabled) 467 467 468 468 if (userIsDisabled) { 469 469 this.dispatch({event: ConvoDispatchEvent.Disable}) ··· 529 529 private pendingFetchConvo: 530 530 | Promise<{ 531 531 convo: ChatBskyConvoDefs.ConvoView 532 - sender: AppBskyActorDefs.ProfileViewBasic | undefined 533 - recipients: AppBskyActorDefs.ProfileViewBasic[] 532 + sender: ChatBskyActorDefs.ProfileViewBasic | undefined 533 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 534 534 }> 535 535 | undefined 536 536 async fetchConvo() { ··· 538 538 539 539 this.pendingFetchConvo = new Promise<{ 540 540 convo: ChatBskyConvoDefs.ConvoView 541 - sender: AppBskyActorDefs.ProfileViewBasic | undefined 542 - recipients: AppBskyActorDefs.ProfileViewBasic[] 541 + sender: ChatBskyActorDefs.ProfileViewBasic | undefined 542 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 543 543 }>(async (resolve, reject) => { 544 544 try { 545 545 const response = await networkRetry(2, () => { ··· 704 704 * If there's a rev, we should handle it. If there's not a rev, we don't 705 705 * know what it is. 706 706 */ 707 - if (typeof ev.rev === 'string') { 707 + if ('rev' in ev && typeof ev.rev === 'string') { 708 708 const isUninitialized = !this.latestRev 709 709 const isNewEvent = this.latestRev && ev.rev > this.latestRev 710 710 ··· 1049 1049 * `getItems` is only run in "active" status states, where 1050 1050 * `this.sender` is defined 1051 1051 */ 1052 - sender: this.sender!, 1052 + sender: { 1053 + $type: 'chat.bsky.convo.defs#messageViewSender', 1054 + did: this.sender!.did, 1055 + }, 1053 1056 }, 1054 1057 nextMessage: null, 1055 1058 prevMessage: null,
+13 -13
src/state/messages/convo/types.ts
··· 1 1 import { 2 - AppBskyActorDefs, 3 2 BskyAgent, 3 + ChatBskyActorDefs, 4 4 ChatBskyConvoDefs, 5 5 ChatBskyConvoSendMessage, 6 6 } from '@atproto/api' ··· 147 147 items: [] 148 148 convo: ChatBskyConvoDefs.ConvoView | undefined 149 149 error: undefined 150 - sender: AppBskyActorDefs.ProfileViewBasic | undefined 151 - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined 150 + sender: ChatBskyActorDefs.ProfileViewBasic | undefined 151 + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined 152 152 isFetchingHistory: false 153 153 deleteMessage: undefined 154 154 sendMessage: undefined ··· 159 159 items: [] 160 160 convo: ChatBskyConvoDefs.ConvoView | undefined 161 161 error: undefined 162 - sender: AppBskyActorDefs.ProfileViewBasic | undefined 163 - recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined 162 + sender: ChatBskyActorDefs.ProfileViewBasic | undefined 163 + recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined 164 164 isFetchingHistory: boolean 165 165 deleteMessage: undefined 166 166 sendMessage: undefined ··· 171 171 items: ConvoItem[] 172 172 convo: ChatBskyConvoDefs.ConvoView 173 173 error: undefined 174 - sender: AppBskyActorDefs.ProfileViewBasic 175 - recipients: AppBskyActorDefs.ProfileViewBasic[] 174 + sender: ChatBskyActorDefs.ProfileViewBasic 175 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 176 176 isFetchingHistory: boolean 177 177 deleteMessage: DeleteMessage 178 178 sendMessage: SendMessage ··· 183 183 items: ConvoItem[] 184 184 convo: ChatBskyConvoDefs.ConvoView 185 185 error: undefined 186 - sender: AppBskyActorDefs.ProfileViewBasic 187 - recipients: AppBskyActorDefs.ProfileViewBasic[] 186 + sender: ChatBskyActorDefs.ProfileViewBasic 187 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 188 188 isFetchingHistory: boolean 189 189 deleteMessage: DeleteMessage 190 190 sendMessage: SendMessage ··· 195 195 items: ConvoItem[] 196 196 convo: ChatBskyConvoDefs.ConvoView 197 197 error: undefined 198 - sender: AppBskyActorDefs.ProfileViewBasic 199 - recipients: AppBskyActorDefs.ProfileViewBasic[] 198 + sender: ChatBskyActorDefs.ProfileViewBasic 199 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 200 200 isFetchingHistory: boolean 201 201 deleteMessage: DeleteMessage 202 202 sendMessage: SendMessage ··· 219 219 items: ConvoItem[] 220 220 convo: ChatBskyConvoDefs.ConvoView 221 221 error: undefined 222 - sender: AppBskyActorDefs.ProfileViewBasic 223 - recipients: AppBskyActorDefs.ProfileViewBasic[] 222 + sender: ChatBskyActorDefs.ProfileViewBasic 223 + recipients: ChatBskyActorDefs.ProfileViewBasic[] 224 224 isFetchingHistory: boolean 225 225 deleteMessage: DeleteMessage 226 226 sendMessage: SendMessage
+2 -5
src/state/messages/events/agent.ts
··· 65 65 const handle = (event: MessagesEventBusEvent) => { 66 66 if (event.type === 'logs' && options.convoId) { 67 67 const filteredLogs = event.logs.filter(log => { 68 - if ( 69 - typeof log.convoId === 'string' && 70 - log.convoId === options.convoId 71 - ) { 68 + if ('convoId' in log && log.convoId === options.convoId) { 72 69 return log.convoId === options.convoId 73 70 } 74 71 return false ··· 355 352 * If there's a rev, we should handle it. If there's not a rev, we don't 356 353 * know what it is. 357 354 */ 358 - if (typeof ev.rev === 'string') { 355 + if ('rev' in ev && typeof ev.rev === 'string') { 359 356 /* 360 357 * We only care about new events 361 358 */
+7 -2
src/state/queries/list.ts
··· 1 1 import {Image as RNImage} from 'react-native-image-crop-picker' 2 2 import { 3 + $Typed, 3 4 AppBskyGraphDefs, 4 5 AppBskyGraphGetList, 5 6 AppBskyGraphList, 6 7 AtUri, 7 8 BskyAgent, 9 + ComAtprotoRepoApplyWrites, 8 10 Facet, 11 + Un$Typed, 9 12 } from '@atproto/api' 10 13 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 11 14 import chunk from 'lodash.chunk' ··· 68 71 ) { 69 72 throw new Error('Invalid list purpose: must be curatelist or modlist') 70 73 } 71 - const record: AppBskyGraphList.Record = { 74 + const record: Un$Typed<AppBskyGraphList.Record> = { 72 75 purpose, 73 76 name, 74 77 description, ··· 212 215 } 213 216 214 217 // batch delete the list and listitem records 215 - const createDel = (uri: string) => { 218 + const createDel = ( 219 + uri: string, 220 + ): $Typed<ComAtprotoRepoApplyWrites.Delete> => { 216 221 const urip = new AtUri(uri) 217 222 return { 218 223 $type: 'com.atproto.repo.applyWrites#delete',
-2
src/state/queries/messages/actor-declaration.ts
··· 69 69 return useMutation({ 70 70 mutationFn: async () => { 71 71 if (!currentAccount) throw new Error('Not signed in') 72 - // TODO(sam): remove validate: false once PDSes have the new lexicon 73 72 const result = await agent.api.com.atproto.repo.deleteRecord({ 74 73 repo: currentAccount.did, 75 74 collection: 'chat.bsky.actor.declaration', 76 75 rkey: 'self', 77 - validate: false, 78 76 }) 79 77 return result 80 78 },
+30 -20
src/state/queries/messages/list-conversations.tsx
··· 101 101 events => { 102 102 if (events.type !== 'logs') return 103 103 104 - events.logs.forEach(log => { 104 + for (const log of events.logs) { 105 105 if (ChatBskyConvoDefs.isLogBeginConvo(log)) { 106 106 debouncedRefetch() 107 107 } else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) { ··· 110 110 ) 111 111 } else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) { 112 112 queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => 113 - optimisticUpdate(log.convoId, old, convo => 114 - log.message.id === convo.lastMessage?.id 115 - ? { 116 - ...convo, 117 - rev: log.rev, 118 - lastMessage: log.message, 119 - } 120 - : convo, 121 - ), 113 + optimisticUpdate(log.convoId, old, convo => { 114 + if ( 115 + (ChatBskyConvoDefs.isDeletedMessageView(log.message) || 116 + ChatBskyConvoDefs.isMessageView(log.message)) && 117 + (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) || 118 + ChatBskyConvoDefs.isMessageView(convo.lastMessage)) 119 + ) { 120 + return log.message.id === convo.lastMessage.id 121 + ? { 122 + ...convo, 123 + rev: log.rev, 124 + lastMessage: log.message, 125 + } 126 + : convo 127 + } else { 128 + return convo 129 + } 130 + }), 122 131 ) 123 132 } else if (ChatBskyConvoDefs.isLogCreateMessage(log)) { 133 + // Store in a new var to avoid TS errors due to closures. 134 + const logRef: ChatBskyConvoDefs.LogCreateMessage = log 135 + 124 136 queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => { 125 137 if (!old) return old 126 138 127 139 function updateConvo(convo: ChatBskyConvoDefs.ConvoView) { 128 - if (!ChatBskyConvoDefs.isLogCreateMessage(log)) return convo 129 - 130 140 let unreadCount = convo.unreadCount 131 141 if (convo.id !== currentConvoId) { 132 142 if ( 133 - ChatBskyConvoDefs.isMessageView(log.message) || 134 - ChatBskyConvoDefs.isDeletedMessageView(log.message) 143 + ChatBskyConvoDefs.isMessageView(logRef.message) || 144 + ChatBskyConvoDefs.isDeletedMessageView(logRef.message) 135 145 ) { 136 - if (log.message.sender.did !== currentAccount?.did) { 146 + if (logRef.message.sender.did !== currentAccount?.did) { 137 147 unreadCount++ 138 148 } 139 149 } ··· 143 153 144 154 return { 145 155 ...convo, 146 - rev: log.rev, 147 - lastMessage: log.message, 156 + rev: logRef.rev, 157 + lastMessage: logRef.message, 148 158 unreadCount, 149 159 } 150 160 } ··· 152 162 function filterConvoFromPage( 153 163 convo: ChatBskyConvoDefs.ConvoView[], 154 164 ) { 155 - return convo.filter(c => c.id !== log.convoId) 165 + return convo.filter(c => c.id !== logRef.convoId) 156 166 } 157 167 158 - const existingConvo = getConvoFromQueryData(log.convoId, old) 168 + const existingConvo = getConvoFromQueryData(logRef.convoId, old) 159 169 160 170 if (existingConvo) { 161 171 return { ··· 186 196 } 187 197 }) 188 198 } 189 - }) 199 + } 190 200 }, 191 201 { 192 202 // get events for all chats
+11 -7
src/state/queries/notifications/feed.ts
··· 295 295 } 296 296 } 297 297 298 - const quotedPost = getEmbeddedPost(item.subject?.embed) 299 - if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { 300 - yield embedViewRecordToPostView(quotedPost!) 298 + if (AppBskyFeedDefs.isPostView(item.subject)) { 299 + const quotedPost = getEmbeddedPost(item.subject?.embed) 300 + if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { 301 + yield embedViewRecordToPostView(quotedPost!) 302 + } 301 303 } 302 304 } 303 305 } ··· 307 309 export function* findAllProfilesInQueryData( 308 310 queryClient: QueryClient, 309 311 did: string, 310 - ): Generator<AppBskyActorDefs.ProfileView, void> { 312 + ): Generator<AppBskyActorDefs.ProfileViewBasic, void> { 311 313 const queryDatas = queryClient.getQueriesData<InfiniteData<FeedPage>>({ 312 314 queryKey: [RQKEY_ROOT], 313 315 }) ··· 323 325 ) { 324 326 yield item.subject.author 325 327 } 326 - const quotedPost = getEmbeddedPost(item.subject?.embed) 327 - if (quotedPost?.author.did === did) { 328 - yield quotedPost.author 328 + if (AppBskyFeedDefs.isPostView(item.subject)) { 329 + const quotedPost = getEmbeddedPost(item.subject?.embed) 330 + if (quotedPost?.author.did === did) { 331 + yield quotedPost.author 332 + } 329 333 } 330 334 } 331 335 }
+11 -7
src/state/queries/notifications/util.ts
··· 14 14 import chunk from 'lodash.chunk' 15 15 16 16 import {labelIsHideableOffense} from '#/lib/moderation' 17 + import * as bsky from '#/types/bsky' 17 18 import {precacheProfile} from '../profile' 18 19 import {FeedNotification, FeedPage, NotificationType} from './types' 19 20 ··· 205 206 ), 206 207 ) 207 208 const postsMap = new Map<string, AppBskyFeedDefs.PostView>() 208 - const packsMap = new Map<string, AppBskyGraphDefs.StarterPackView>() 209 + const packsMap = new Map<string, AppBskyGraphDefs.StarterPackViewBasic>() 209 210 for (const post of postsChunks.flat()) { 210 - if ( 211 - AppBskyFeedPost.isRecord(post.record) && 212 - AppBskyFeedPost.validateRecord(post.record).success 213 - ) { 211 + if (AppBskyFeedPost.isRecord(post.record)) { 214 212 postsMap.set(post.uri, post) 215 213 } 216 214 } ··· 255 253 return notif.uri 256 254 } else if (type === 'post-like' || type === 'repost') { 257 255 if ( 258 - AppBskyFeedRepost.isRecord(notif.record) || 259 - AppBskyFeedLike.isRecord(notif.record) 256 + bsky.dangerousIsType<AppBskyFeedRepost.Record>( 257 + notif.record, 258 + AppBskyFeedRepost.isRecord, 259 + ) || 260 + bsky.dangerousIsType<AppBskyFeedLike.Record>( 261 + notif.record, 262 + AppBskyFeedLike.isRecord, 263 + ) 260 264 ) { 261 265 return typeof notif.record.subject?.uri === 'string' 262 266 ? notif.record.subject?.uri
+1 -1
src/state/queries/post-feed.ts
··· 547 547 export function* findAllProfilesInQueryData( 548 548 queryClient: QueryClient, 549 549 did: string, 550 - ): Generator<AppBskyActorDefs.ProfileView, undefined> { 550 + ): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> { 551 551 const queryDatas = queryClient.getQueriesData< 552 552 InfiniteData<FeedPageUnselected> 553 553 >({
+1 -1
src/state/queries/post-quotes.ts
··· 70 70 export function* findAllProfilesInQueryData( 71 71 queryClient: QueryClient, 72 72 did: string, 73 - ): Generator<AppBskyActorDefs.ProfileView, void> { 73 + ): Generator<AppBskyActorDefs.ProfileViewBasic, void> { 74 74 const queryDatas = queryClient.getQueriesData< 75 75 InfiniteData<AppBskyFeedGetQuotes.OutputSchema> 76 76 >({
+7 -4
src/state/queries/post-thread.ts
··· 18 18 findAllProfilesInQueryData as findAllProfilesInSearchQueryData, 19 19 } from '#/state/queries/search-posts' 20 20 import {useAgent} from '#/state/session' 21 + import * as bsky from '#/types/bsky' 21 22 import { 22 23 findAllPostsInQueryData as findAllPostsInNotifsQueryData, 23 24 findAllProfilesInQueryData as findAllProfilesInNotifsQueryData, ··· 332 333 ): ThreadNode { 333 334 if ( 334 335 AppBskyFeedDefs.isThreadViewPost(node) && 335 - AppBskyFeedPost.isRecord(node.post.record) && 336 - AppBskyFeedPost.validateRecord(node.post.record).success 336 + bsky.dangerousIsType<AppBskyFeedPost.Record>( 337 + node.post.record, 338 + AppBskyFeedPost.isRecord, 339 + ) 337 340 ) { 338 341 const post = node.post 339 342 // These should normally be present. They're missing only for ··· 364 367 depth, 365 368 isHighlightedPost: depth === 0, 366 369 hasMore: 367 - direction === 'down' && !node.replies?.length && !!node.replyCount, 370 + direction === 'down' && !node.replies?.length && !!post.replyCount, 368 371 isSelfThread: false, // populated `annotateSelfThread` 369 372 hasMoreSelfThread: false, // populated in `annotateSelfThread` 370 373 }, ··· 497 500 export function* findAllProfilesInQueryData( 498 501 queryClient: QueryClient, 499 502 did: string, 500 - ): Generator<AppBskyActorDefs.ProfileView, void> { 503 + ): Generator<AppBskyActorDefs.ProfileViewBasic, void> { 501 504 const queryDatas = queryClient.getQueriesData<PostThreadQueryData>({ 502 505 queryKey: [RQKEY_ROOT], 503 506 })
+5 -1
src/state/queries/postgate/index.ts
··· 21 21 POSTGATE_COLLECTION, 22 22 } from '#/state/queries/postgate/util' 23 23 import {useAgent} from '#/state/session' 24 + import * as bsky from '#/types/bsky' 24 25 25 26 export async function getPostgateRecord({ 26 27 agent, ··· 60 61 }), 61 62 ) 62 63 63 - if (data.value && AppBskyFeedPostgate.isRecord(data.value)) { 64 + if ( 65 + data.value && 66 + bsky.validate(data.value, AppBskyFeedPostgate.validateRecord) 67 + ) { 64 68 return data.value 65 69 } else { 66 70 return undefined
+8 -3
src/state/queries/postgate/util.ts
··· 1 1 import { 2 + $Typed, 2 3 AppBskyEmbedRecord, 3 4 AppBskyEmbedRecordWithMedia, 4 5 AppBskyFeedDefs, ··· 45 46 }) 46 47 } 47 48 48 - export function createEmbedViewDetachedRecord({uri}: {uri: string}) { 49 - const record: AppBskyEmbedRecord.ViewDetached = { 49 + export function createEmbedViewDetachedRecord({ 50 + uri, 51 + }: { 52 + uri: string 53 + }): $Typed<AppBskyEmbedRecord.View> { 54 + const record: $Typed<AppBskyEmbedRecord.ViewDetached> = { 50 55 $type: 'app.bsky.embed.record#viewDetached', 51 56 uri, 52 57 detached: true, ··· 95 100 96 101 export function createEmbedViewRecordFromPost( 97 102 post: AppBskyFeedDefs.PostView, 98 - ): AppBskyEmbedRecord.ViewRecord { 103 + ): $Typed<AppBskyEmbedRecord.ViewRecord> { 99 104 return { 100 105 $type: 'app.bsky.embed.record#viewRecord', 101 106 uri: post.uri,
+37 -37
src/state/queries/profile.ts
··· 8 8 AtUri, 9 9 BskyAgent, 10 10 ComAtprotoRepoUploadBlob, 11 + Un$Typed, 11 12 } from '@atproto/api' 12 13 import { 13 14 keepPreviousData, ··· 24 25 import {Shadow} from '#/state/cache/types' 25 26 import {STALE} from '#/state/queries' 26 27 import {resetProfilePostsQueries} from '#/state/queries/post-feed' 28 + import { 29 + unstableCacheProfileView, 30 + useUnstableProfileViewCache, 31 + } from '#/state/queries/unstable-profile-cache' 27 32 import * as userActionHistory from '#/state/userActionHistory' 33 + import * as bsky from '#/types/bsky' 28 34 import {updateProfileShadow} from '../cache/profile-shadow' 29 35 import {useAgent, useSession} from '../session' 30 36 import { ··· 35 41 import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' 36 42 import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' 37 43 44 + export * from '#/state/queries/unstable-profile-cache' 45 + /** 46 + * @deprecated use {@link unstableCacheProfileView} instead 47 + */ 48 + export const precacheProfile = unstableCacheProfileView 49 + 38 50 const RQKEY_ROOT = 'profile' 39 51 export const RQKEY = (did: string) => [RQKEY_ROOT, did] 40 52 ··· 44 56 handles, 45 57 ] 46 58 47 - const profileBasicQueryKeyRoot = 'profileBasic' 48 - export const profileBasicQueryKey = (didOrHandle: string) => [ 49 - profileBasicQueryKeyRoot, 50 - didOrHandle, 51 - ] 52 - 53 59 export function useProfileQuery({ 54 60 did, 55 61 staleTime = STALE.SECONDS.FIFTEEN, ··· 57 63 did: string | undefined 58 64 staleTime?: number 59 65 }) { 60 - const queryClient = useQueryClient() 61 66 const agent = useAgent() 67 + const {getUnstableProfile} = useUnstableProfileViewCache() 62 68 return useQuery<AppBskyActorDefs.ProfileViewDetailed>({ 63 69 // WARNING 64 70 // this staleTime is load-bearing ··· 73 79 }, 74 80 placeholderData: () => { 75 81 if (!did) return 76 - 77 - return queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>( 78 - profileBasicQueryKey(did), 79 - ) 82 + return getUnstableProfile(did) as AppBskyActorDefs.ProfileViewDetailed 80 83 }, 81 84 enabled: !!did, 82 85 }) ··· 121 124 } 122 125 123 126 interface ProfileUpdateParams { 124 - profile: AppBskyActorDefs.ProfileView 127 + profile: AppBskyActorDefs.ProfileViewDetailed 125 128 updates: 126 - | AppBskyActorProfile.Record 127 - | ((existing: AppBskyActorProfile.Record) => AppBskyActorProfile.Record) 129 + | Un$Typed<AppBskyActorProfile.Record> 130 + | (( 131 + existing: Un$Typed<AppBskyActorProfile.Record>, 132 + ) => Un$Typed<AppBskyActorProfile.Record>) 128 133 newUserAvatar?: RNImage | undefined | null 129 134 newUserBanner?: RNImage | undefined | null 130 135 checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean ··· 161 166 ) 162 167 } 163 168 await agent.upsertProfile(async existing => { 164 - existing = existing || {} 169 + let next: Un$Typed<AppBskyActorProfile.Record> = existing || {} 165 170 if (typeof updates === 'function') { 166 - existing = updates(existing) 171 + next = updates(next) 167 172 } else { 168 - existing.displayName = updates.displayName 169 - existing.description = updates.description 173 + next.displayName = updates.displayName 174 + next.description = updates.description 170 175 if ('pinnedPost' in updates) { 171 - existing.pinnedPost = updates.pinnedPost 176 + next.pinnedPost = updates.pinnedPost 172 177 } 173 178 } 174 179 if (newUserAvatarPromise) { 175 180 const res = await newUserAvatarPromise 176 - existing.avatar = res.data.blob 181 + next.avatar = res.data.blob 177 182 } else if (newUserAvatar === null) { 178 - existing.avatar = undefined 183 + next.avatar = undefined 179 184 } 180 185 if (newUserBannerPromise) { 181 186 const res = await newUserBannerPromise 182 - existing.banner = res.data.blob 187 + next.banner = res.data.blob 183 188 } else if (newUserBanner === null) { 184 - existing.banner = undefined 189 + next.banner = undefined 185 190 } 186 - return existing 191 + return next 187 192 }) 188 193 await whenAppViewReady( 189 194 agent, ··· 228 233 } 229 234 230 235 export function useProfileFollowMutationQueue( 231 - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, 236 + profile: Shadow<bsky.profile.AnyProfileView>, 232 237 logContext: LogEvents['profile:follow']['logContext'] & 233 238 LogEvents['profile:follow']['logContext'], 234 239 ) { ··· 302 307 303 308 function useProfileFollowMutation( 304 309 logContext: LogEvents['profile:follow']['logContext'], 305 - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, 310 + profile: Shadow<bsky.profile.AnyProfileView>, 306 311 ) { 307 312 const {currentAccount} = useSession() 308 313 const agent = useAgent() ··· 321 326 didBecomeMutual: profile.viewer 322 327 ? Boolean(profile.viewer.followedBy) 323 328 : undefined, 324 - followeeClout: toClout(profile.followersCount), 329 + followeeClout: 330 + 'followersCount' in profile 331 + ? toClout(profile.followersCount) 332 + : undefined, 325 333 followerClout: toClout(ownProfile?.followersCount), 326 334 }) 327 335 return await agent.follow(did) ··· 342 350 } 343 351 344 352 export function useProfileMuteMutationQueue( 345 - profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>, 353 + profile: Shadow<bsky.profile.AnyProfileView>, 346 354 ) { 347 355 const queryClient = useQueryClient() 348 356 const did = profile.did ··· 417 425 } 418 426 419 427 export function useProfileBlockMutationQueue( 420 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, 428 + profile: Shadow<bsky.profile.AnyProfileView>, 421 429 ) { 422 430 const queryClient = useQueryClient() 423 431 const did = profile.did ··· 511 519 resetProfilePostsQueries(queryClient, did, 1000) 512 520 }, 513 521 }) 514 - } 515 - 516 - export function precacheProfile( 517 - queryClient: QueryClient, 518 - profile: AppBskyActorDefs.ProfileViewBasic, 519 - ) { 520 - queryClient.setQueryData(profileBasicQueryKey(profile.handle), profile) 521 - queryClient.setQueryData(profileBasicQueryKey(profile.did), profile) 522 522 } 523 523 524 524 async function whenAppViewReady(
+5 -14
src/state/queries/resolve-uri.ts
··· 1 - import {AppBskyActorDefs, AtUri} from '@atproto/api' 2 - import { 3 - QueryClient, 4 - useQuery, 5 - useQueryClient, 6 - UseQueryResult, 7 - } from '@tanstack/react-query' 1 + import {AtUri} from '@atproto/api' 2 + import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query' 8 3 9 4 import {STALE} from '#/state/queries' 10 5 import {useAgent} from '#/state/session' 11 - import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from './profile' 6 + import {useUnstableProfileViewCache} from './profile' 12 7 13 8 const RQKEY_ROOT = 'resolved-did' 14 9 export const RQKEY = (didOrHandle: string) => [RQKEY_ROOT, didOrHandle] ··· 28 23 } 29 24 30 25 export function useResolveDidQuery(didOrHandle: string | undefined) { 31 - const queryClient = useQueryClient() 32 26 const agent = useAgent() 27 + const {getUnstableProfile} = useUnstableProfileViewCache() 33 28 34 29 return useQuery<string, Error>({ 35 30 staleTime: STALE.HOURS.ONE, ··· 45 40 initialData: () => { 46 41 // Return undefined if no did or handle 47 42 if (!didOrHandle) return 48 - 49 - const profile = 50 - queryClient.getQueryData<AppBskyActorDefs.ProfileViewBasic>( 51 - RQKEY_PROFILE_BASIC(didOrHandle), 52 - ) 43 + const profile = getUnstableProfile(didOrHandle) 53 44 return profile?.did 54 45 }, 55 46 enabled: !!didOrHandle,
+1 -1
src/state/queries/search-posts.ts
··· 174 174 export function* findAllProfilesInQueryData( 175 175 queryClient: QueryClient, 176 176 did: string, 177 - ): Generator<AppBskyActorDefs.ProfileView, undefined> { 177 + ): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> { 178 178 const queryDatas = queryClient.getQueriesData< 179 179 InfiniteData<AppBskyFeedSearchPosts.OutputSchema> 180 180 >({
+1
src/state/queries/service-config.ts
··· 19 19 const {data} = await agent.api.app.bsky.unspecced.getConfig() 20 20 return { 21 21 checkEmailConfirmed: Boolean(data.checkEmailConfirmed), 22 + // @ts-expect-error not included in types atm 22 23 topicsEnabled: Boolean(data.topicsEnabled), 23 24 } 24 25 } catch (e) {
+7 -4
src/state/queries/starter-packs.ts
··· 1 1 import { 2 - AppBskyActorDefs, 3 2 AppBskyFeedDefs, 4 3 AppBskyGraphDefs, 5 4 AppBskyGraphGetStarterPack, ··· 29 28 import {STALE} from '#/state/queries/index' 30 29 import {invalidateListMembersQuery} from '#/state/queries/list-members' 31 30 import {useAgent} from '#/state/session' 31 + import * as bsky from '#/types/bsky' 32 32 33 33 const RQKEY_ROOT = 'starter-pack' 34 34 const RQKEY = ({ ··· 93 93 interface UseCreateStarterPackMutationParams { 94 94 name: string 95 95 description?: string 96 - profiles: AppBskyActorDefs.ProfileViewBasic[] 96 + profiles: bsky.profile.AnyProfileView[] 97 97 feeds?: AppBskyFeedDefs.GeneratorView[] 98 98 } 99 99 ··· 131 131 132 132 return await agent.app.bsky.graph.starterpack.create( 133 133 { 134 - repo: agent.session?.did, 134 + repo: agent.assertDid, 135 135 }, 136 136 { 137 137 name, ··· 366 366 let starterPackView: AppBskyGraphDefs.StarterPackView | undefined 367 367 if (AppBskyGraphDefs.isStarterPackView(starterPack)) { 368 368 starterPackView = starterPack 369 - } else if (AppBskyGraphDefs.isStarterPackViewBasic(starterPack)) { 369 + } else if ( 370 + AppBskyGraphDefs.isStarterPackViewBasic(starterPack) && 371 + bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) 372 + ) { 370 373 const listView: AppBskyGraphDefs.ListViewBasic = { 371 374 uri: starterPack.record.list, 372 375 // This will be populated once the data from server is fetched
+5 -1
src/state/queries/threadgate/index.ts
··· 20 20 } from '#/state/queries/threadgate/util' 21 21 import {useAgent} from '#/state/session' 22 22 import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies' 23 + import * as bsky from '#/types/bsky' 23 24 24 25 export * from '#/state/queries/threadgate/types' 25 26 export * from '#/state/queries/threadgate/util' ··· 138 139 }), 139 140 ) 140 141 141 - if (data.value && AppBskyFeedThreadgate.isRecord(data.value)) { 142 + if ( 143 + data.value && 144 + bsky.validate(data.value, AppBskyFeedThreadgate.validateRecord) 145 + ) { 142 146 return data.value 143 147 } else { 144 148 return null
+1 -1
src/state/queries/threadgate/types.ts
··· 4 4 | {type: 'mention'} 5 5 | {type: 'following'} 6 6 | {type: 'followers'} 7 - | {type: 'list'; list: unknown} 7 + | {type: 'list'; list: string}
+9 -12
src/state/queries/threadgate/util.ts
··· 1 1 import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' 2 2 3 3 import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 4 + import * as bsky from '#/types/bsky' 4 5 5 6 export function threadgateViewToAllowUISetting( 6 7 threadgateView: AppBskyFeedDefs.ThreadgateView | undefined, 7 8 ): ThreadgateAllowUISetting[] { 9 + // Validate the record for clarity, since backwards compat code is a little confusing 8 10 const threadgate = 9 11 threadgateView && 10 - AppBskyFeedThreadgate.isRecord(threadgateView.record) && 11 - AppBskyFeedThreadgate.validateRecord(threadgateView.record).success 12 + bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord) 12 13 ? threadgateView.record 13 14 : undefined 14 15 return threadgateRecordToAllowUISetting(threadgate) ··· 39 40 const settings: ThreadgateAllowUISetting[] = threadgate.allow 40 41 .map(allow => { 41 42 let setting: ThreadgateAllowUISetting | undefined 42 - if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') { 43 + if (AppBskyFeedThreadgate.isMentionRule(allow)) { 43 44 setting = {type: 'mention'} 44 - } else if (allow.$type === 'app.bsky.feed.threadgate#followingRule') { 45 + } else if (AppBskyFeedThreadgate.isFollowingRule(allow)) { 45 46 setting = {type: 'following'} 46 - } else if (allow.$type === 'app.bsky.feed.threadgate#followerRule') { 47 + } else if (AppBskyFeedThreadgate.isListRule(allow)) { 48 + setting = {type: 'list', list: allow.list} 49 + } else if (AppBskyFeedThreadgate.isFollowerRule(allow)) { 47 50 setting = {type: 'followers'} 48 - } else if (allow.$type === 'app.bsky.feed.threadgate#listRule') { 49 - setting = {type: 'list', list: allow.list} 50 51 } 51 52 return setting 52 53 }) ··· 69 70 return undefined 70 71 } 71 72 72 - let allow: ( 73 - | AppBskyFeedThreadgate.MentionRule 74 - | AppBskyFeedThreadgate.FollowingRule 75 - | AppBskyFeedThreadgate.ListRule 76 - )[] = [] 73 + let allow: Exclude<AppBskyFeedThreadgate.Record['allow'], undefined> = [] 77 74 78 75 if (!threadgate.find(v => v.type === 'nobody')) { 79 76 for (const rule of threadgate) {
+51
src/state/queries/unstable-profile-cache.ts
··· 1 + import {useCallback} from 'react' 2 + import {QueryClient, useQueryClient} from '@tanstack/react-query' 3 + 4 + import * as bsky from '#/types/bsky' 5 + 6 + const unstableProfileViewCacheQueryKeyRoot = 'unstableProfileViewCache' 7 + export const unstableProfileViewCacheQueryKey = (didOrHandle: string) => [ 8 + unstableProfileViewCacheQueryKeyRoot, 9 + didOrHandle, 10 + ] 11 + 12 + /** 13 + * Used as a rough cache of profile views to make loading snappier. This method 14 + * accepts and stores any profile view type by both handle and DID. 15 + * 16 + * Access the cache via {@link useUnstableProfileViewCache}. 17 + */ 18 + export function unstableCacheProfileView( 19 + queryClient: QueryClient, 20 + profile: bsky.profile.AnyProfileView, 21 + ) { 22 + queryClient.setQueryData( 23 + unstableProfileViewCacheQueryKey(profile.handle), 24 + profile, 25 + ) 26 + queryClient.setQueryData( 27 + unstableProfileViewCacheQueryKey(profile.did), 28 + profile, 29 + ) 30 + } 31 + 32 + /** 33 + * Hook to access the unstable profile view cache. This cache can return ANY 34 + * profile view type, so if the object shape is important, you need to use the 35 + * identity validators shipped in the atproto SDK e.g. 36 + * `AppBskyActorDefs.isValidProfileViewBasic` to confirm before using. 37 + * 38 + * To cache a profile, use {@link unstableCacheProfileView}. 39 + */ 40 + export function useUnstableProfileViewCache() { 41 + const qc = useQueryClient() 42 + const getUnstableProfile = useCallback( 43 + (didOrHandle: string) => { 44 + return qc.getQueryData<bsky.profile.AnyProfileView>( 45 + unstableProfileViewCacheQueryKey(didOrHandle), 46 + ) 47 + }, 48 + [qc], 49 + ) 50 + return {getUnstableProfile} 51 + }
+11 -2
src/state/queries/util.ts
··· 8 8 } from '@atproto/api' 9 9 import {InfiniteData, QueryClient, QueryKey} from '@tanstack/react-query' 10 10 11 + import * as bsky from '#/types/bsky' 12 + 11 13 export async function truncateAndInvalidate<T = any>( 12 14 queryClient: QueryClient, 13 15 queryKey: QueryKey, ··· 44 46 export function getEmbeddedPost( 45 47 v: unknown, 46 48 ): AppBskyEmbedRecord.ViewRecord | undefined { 47 - if (AppBskyEmbedRecord.isView(v)) { 49 + if ( 50 + bsky.dangerousIsType<AppBskyEmbedRecord.View>(v, AppBskyEmbedRecord.isView) 51 + ) { 48 52 if ( 49 53 AppBskyEmbedRecord.isViewRecord(v.record) && 50 54 AppBskyFeedPost.isRecord(v.record.value) ··· 52 56 return v.record 53 57 } 54 58 } 55 - if (AppBskyEmbedRecordWithMedia.isView(v)) { 59 + if ( 60 + bsky.dangerousIsType<AppBskyEmbedRecordWithMedia.View>( 61 + v, 62 + AppBskyEmbedRecordWithMedia.isView, 63 + ) 64 + ) { 56 65 if ( 57 66 AppBskyEmbedRecord.isViewRecord(v.record.record) && 58 67 AppBskyFeedPost.isRecord(v.record.record.value)
+1 -2
src/state/shell/composer/index.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 AppBskyActorDefs, 4 - AppBskyEmbedRecord, 5 4 AppBskyFeedDefs, 6 5 ModerationDecision, 7 6 } from '@atproto/api' ··· 21 20 cid: string 22 21 text: string 23 22 author: AppBskyActorDefs.ProfileViewBasic 24 - embed?: AppBskyEmbedRecord.ViewRecord['embed'] 23 + embed?: AppBskyFeedDefs.PostView['embed'] 25 24 moderation?: ModerationDecision 26 25 } 27 26
+51
src/types/bsky/index.ts
··· 1 + import {ValidationResult} from '@atproto/lexicon' 2 + 3 + export * as post from '#/types/bsky/post' 4 + export * as profile from '#/types/bsky/profile' 5 + export * as starterPack from '#/types/bsky/starterPack' 6 + 7 + /** 8 + * Fast type checking without full schema validation, for use with data we 9 + * trust, or for non-critical path use cases. Why? Our SDK's `is*` identity 10 + * utils do not assert the type of the entire object, only the `$type` string. 11 + * 12 + * For full validation of the object schema, use the `validate` export from 13 + * this file. 14 + * 15 + * Usage: 16 + * ```ts 17 + * import * as bsky from '#/types/bsky' 18 + * 19 + * if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) { 20 + * // `item` has type `$Typed<AppBskyFeedPost.Record>` here 21 + * } 22 + * ``` 23 + */ 24 + export function dangerousIsType<R extends {$type?: string}>( 25 + record: unknown, 26 + identity: <V>(v: V) => v is V & {$type: NonNullable<R['$type']>}, 27 + ): record is R { 28 + return identity(record) 29 + } 30 + 31 + /** 32 + * Fully validates the object schema, which has a performance cost. 33 + * 34 + * For faster checks with data we trust, like that from our app view, use the 35 + * `dangerousIsType` export from this same file. 36 + * 37 + * Usage: 38 + * ```ts 39 + * import * as bsky from '#/types/bsky' 40 + * 41 + * if (bsky.validate(item, AppBskyFeedPost.validateRecord)) { 42 + * // `item` has type `$Typed<AppBskyFeedPost.Record>` here 43 + * } 44 + * ``` 45 + */ 46 + export function validate<R extends {$type?: string}>( 47 + record: unknown, 48 + validator: (v: unknown) => ValidationResult<R>, 49 + ): record is R { 50 + return validator(record).success 51 + }
+148
src/types/bsky/post.ts
··· 1 + import { 2 + AppBskyEmbedExternal, 3 + AppBskyEmbedImages, 4 + AppBskyEmbedRecord, 5 + AppBskyEmbedRecordWithMedia, 6 + AppBskyEmbedVideo, 7 + AppBskyFeedDefs, 8 + AppBskyGraphDefs, 9 + AppBskyLabelerDefs, 10 + } from '@atproto/api' 11 + 12 + export type Embed = 13 + | { 14 + type: 'post' 15 + view: AppBskyEmbedRecord.ViewRecord 16 + } 17 + | { 18 + type: 'post_not_found' 19 + view: AppBskyEmbedRecord.ViewNotFound 20 + } 21 + | { 22 + type: 'post_blocked' 23 + view: AppBskyEmbedRecord.ViewBlocked 24 + } 25 + | { 26 + type: 'post_detached' 27 + view: AppBskyEmbedRecord.ViewDetached 28 + } 29 + | { 30 + type: 'feed' 31 + view: AppBskyFeedDefs.GeneratorView 32 + } 33 + | { 34 + type: 'list' 35 + view: AppBskyGraphDefs.ListView 36 + } 37 + | { 38 + type: 'labeler' 39 + view: AppBskyLabelerDefs.LabelerView 40 + } 41 + | { 42 + type: 'starter_pack' 43 + view: AppBskyGraphDefs.StarterPackViewBasic 44 + } 45 + | { 46 + type: 'images' 47 + view: AppBskyEmbedImages.View 48 + } 49 + | { 50 + type: 'link' 51 + view: AppBskyEmbedExternal.View 52 + } 53 + | { 54 + type: 'video' 55 + view: AppBskyEmbedVideo.View 56 + } 57 + | { 58 + type: 'post_with_media' 59 + view: Embed 60 + media: Embed 61 + } 62 + | { 63 + type: 'unknown' 64 + view: null 65 + } 66 + 67 + export type EmbedType<T extends Embed['type']> = Extract<Embed, {type: T}> 68 + 69 + export function parseEmbedRecordView({record}: AppBskyEmbedRecord.View): Embed { 70 + if (AppBskyEmbedRecord.isViewRecord(record)) { 71 + return { 72 + type: 'post', 73 + view: record, 74 + } 75 + } else if (AppBskyEmbedRecord.isViewNotFound(record)) { 76 + return { 77 + type: 'post_not_found', 78 + view: record, 79 + } 80 + } else if (AppBskyEmbedRecord.isViewBlocked(record)) { 81 + return { 82 + type: 'post_blocked', 83 + view: record, 84 + } 85 + } else if (AppBskyEmbedRecord.isViewDetached(record)) { 86 + return { 87 + type: 'post_detached', 88 + view: record, 89 + } 90 + } else if (AppBskyFeedDefs.isGeneratorView(record)) { 91 + return { 92 + type: 'feed', 93 + view: record, 94 + } 95 + } else if (AppBskyGraphDefs.isListView(record)) { 96 + return { 97 + type: 'list', 98 + view: record, 99 + } 100 + } else if (AppBskyLabelerDefs.isLabelerView(record)) { 101 + return { 102 + type: 'labeler', 103 + view: record, 104 + } 105 + } else if (AppBskyGraphDefs.isStarterPackViewBasic(record)) { 106 + return { 107 + type: 'starter_pack', 108 + view: record, 109 + } 110 + } else { 111 + return { 112 + type: 'unknown', 113 + view: null, 114 + } 115 + } 116 + } 117 + 118 + export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed { 119 + if (AppBskyEmbedImages.isView(embed)) { 120 + return { 121 + type: 'images', 122 + view: embed, 123 + } 124 + } else if (AppBskyEmbedExternal.isView(embed)) { 125 + return { 126 + type: 'link', 127 + view: embed, 128 + } 129 + } else if (AppBskyEmbedVideo.isView(embed)) { 130 + return { 131 + type: 'video', 132 + view: embed, 133 + } 134 + } else if (AppBskyEmbedRecord.isView(embed)) { 135 + return parseEmbedRecordView(embed) 136 + } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { 137 + return { 138 + type: 'post_with_media', 139 + view: parseEmbedRecordView(embed.record), 140 + media: parseEmbed(embed.media), 141 + } 142 + } else { 143 + return { 144 + type: 'unknown', 145 + view: null, 146 + } 147 + } 148 + }
+10
src/types/bsky/profile.ts
··· 1 + import {AppBskyActorDefs, ChatBskyActorDefs} from '@atproto/api' 2 + 3 + /** 4 + * Matches any profile view exported by our SDK 5 + */ 6 + export type AnyProfileView = 7 + | AppBskyActorDefs.ProfileViewBasic 8 + | AppBskyActorDefs.ProfileView 9 + | AppBskyActorDefs.ProfileViewDetailed 10 + | ChatBskyActorDefs.ProfileViewBasic
+11
src/types/bsky/starterPack.ts
··· 1 + import {AppBskyGraphDefs} from '@atproto/api' 2 + 3 + export const isBasicView = AppBskyGraphDefs.isStarterPackViewBasic 4 + export const isView = AppBskyGraphDefs.isStarterPackView 5 + 6 + /** 7 + * Matches any starter pack view exported by our SDK 8 + */ 9 + export type AnyStarterPackView = 10 + | AppBskyGraphDefs.StarterPackViewBasic 11 + | AppBskyGraphDefs.StarterPackView
+4 -3
src/view/com/lists/ListMembers.tsx
··· 1 1 import React, {useCallback} from 'react' 2 2 import {Dimensions, StyleProp, View, ViewStyle} from 'react-native' 3 - import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' 3 + import {AppBskyGraphDefs} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 11 11 import {useListMembersQuery} from '#/state/queries/list-members' 12 12 import {useSession} from '#/state/session' 13 13 import {ListFooter} from '#/components/Lists' 14 + import * as bsky from '#/types/bsky' 14 15 import {ProfileCard} from '../profile/ProfileCard' 15 16 import {ErrorMessage} from '../util/error/ErrorMessage' 16 17 import {Button} from '../util/forms/Button' ··· 116 117 }, [fetchNextPage]) 117 118 118 119 const onPressEditMembership = React.useCallback( 119 - (profile: AppBskyActorDefs.ProfileViewBasic) => { 120 + (profile: bsky.profile.AnyProfileView) => { 120 121 openModal({ 121 122 name: 'user-add-remove-lists', 122 123 subject: profile.did, ··· 131 132 // = 132 133 133 134 const renderMemberButton = React.useCallback( 134 - (profile: AppBskyActorDefs.ProfileViewBasic) => { 135 + (profile: bsky.profile.AnyProfileView) => { 135 136 if (!isOwner) { 136 137 return null 137 138 }
+14 -4
src/view/com/notifications/NotificationFeedItem.tsx
··· 58 58 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 59 59 import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 60 60 import {SubtleWebHover} from '#/components/SubtleWebHover' 61 + import * as bsky from '#/types/bsky' 61 62 import {FeedSourceCard} from '../feeds/FeedSourceCard' 62 63 import {Post} from '../post/Post' 63 64 import {Link, TextLink} from '../util/Link' ··· 71 72 const EXPANDED_AUTHOR_EL_HEIGHT = 35 72 73 73 74 interface Author { 74 - profile: AppBskyActorDefs.ProfileViewBasic 75 + profile: AppBskyActorDefs.ProfileView 75 76 href: string 76 77 moderation: ModerationDecision 77 78 } ··· 264 265 265 266 if ( 266 267 item.notification.author.viewer?.following && 267 - AppBskyGraphFollow.isRecord(item.notification.record) 268 + bsky.dangerousIsType<AppBskyGraphFollow.Record>( 269 + item.notification.record, 270 + AppBskyGraphFollow.isRecord, 271 + ) 268 272 ) { 269 273 let followingTimestamp 270 274 try { ··· 521 525 } 522 526 } 523 527 524 - function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) { 528 + function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { 525 529 const {_} = useLingui() 526 530 const agent = useAgent() 527 531 const navigation = useNavigation<NavigationProp>() ··· 716 720 717 721 function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { 718 722 const pal = usePalette('default') 719 - if (post && AppBskyFeedPost.isRecord(post?.record)) { 723 + if ( 724 + post && 725 + bsky.dangerousIsType<AppBskyFeedPost.Record>( 726 + post?.record, 727 + AppBskyFeedPost.isRecord, 728 + ) 729 + ) { 720 730 const text = post.record.text 721 731 722 732 return (
+1 -1
src/view/com/post-thread/PostRepostedBy.tsx
··· 16 16 item, 17 17 index, 18 18 }: { 19 - item: ActorDefs.ProfileViewBasic 19 + item: ActorDefs.ProfileView 20 20 index: number 21 21 }) { 22 22 return (
+2 -3
src/view/com/post-thread/PostThreadFollowBtn.tsx
··· 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 7 import {logger} from '#/logger' 8 - import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow' 8 + import {useProfileShadow} from '#/state/cache/profile-shadow' 9 9 import { 10 10 useProfileFollowMutationQueue, 11 11 useProfileQuery, ··· 35 35 const navigation = useNavigation() 36 36 const {_} = useLingui() 37 37 const {gtMobile} = useBreakpoints() 38 - const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> = 39 - useProfileShadow(profileUnshadowed) 38 + const profile = useProfileShadow(profileUnshadowed) 40 39 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( 41 40 profile, 42 41 'PostThreadItem',
+5 -1
src/view/com/post-thread/PostThreadItem.tsx
··· 58 58 import {SubtleWebHover} from '#/components/SubtleWebHover' 59 59 import {Text} from '#/components/Typography' 60 60 import {WhoCanReply} from '#/components/WhoCanReply' 61 + import * as bsky from '#/types/bsky' 61 62 62 63 export function PostThreadItem({ 63 64 post, ··· 790 791 const control = Prompt.usePromptControl() 791 792 792 793 const indexedAt = new Date(post.indexedAt) 793 - const createdAt = AppBskyFeedPost.isRecord(post.record) 794 + const createdAt = bsky.dangerousIsType<AppBskyFeedPost.Record>( 795 + post.record, 796 + AppBskyFeedPost.isRecord, 797 + ) 794 798 ? new Date(post.record.createdAt) 795 799 : new Date(post.indexedAt) 796 800
+2 -2
src/view/com/post/Post.tsx
··· 28 28 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 29 29 import {RichText} from '#/components/RichText' 30 30 import {SubtleWebHover} from '#/components/SubtleWebHover' 31 + import * as bsky from '#/types/bsky' 31 32 import {ContentHider} from '../../../components/moderation/ContentHider' 32 33 import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' 33 34 import {PostAlerts} from '../../../components/moderation/PostAlerts' ··· 53 54 const moderationOpts = useModerationOpts() 54 55 const record = useMemo<AppBskyFeedPost.Record | undefined>( 55 56 () => 56 - AppBskyFeedPost.isRecord(post.record) && 57 - AppBskyFeedPost.validateRecord(post.record).success 57 + bsky.validate(post.record, AppBskyFeedPost.validateRecord) 58 58 ? post.record 59 59 : undefined, 60 60 [post],
+7 -2
src/view/com/posts/PostFeedItem.tsx
··· 47 47 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 48 48 import {RichText} from '#/components/RichText' 49 49 import {SubtleWebHover} from '#/components/SubtleWebHover' 50 + import * as bsky from '#/types/bsky' 50 51 import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link' 51 52 import {AviFollowButton} from './AviFollowButton' 52 53 ··· 232 233 * If `post[0]` in this slice is the actual root post (not an orphan thread), 233 234 * then we may have a threadgate record to reference 234 235 */ 235 - const threadgateRecord = AppBskyFeedThreadgate.isRecord( 236 + const threadgateRecord = bsky.dangerousIsType<AppBskyFeedThreadgate.Record>( 236 237 rootPost.threadgate?.record, 238 + AppBskyFeedThreadgate.isRecord, 237 239 ) 238 240 ? rootPost.threadgate.record 239 241 : undefined ··· 461 463 }) 462 464 const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => { 463 465 const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) 464 - const rootPostUri = AppBskyFeedPost.isRecord(post.record) 466 + const rootPostUri = bsky.dangerousIsType<AppBskyFeedPost.Record>( 467 + post.record, 468 + AppBskyFeedPost.isRecord, 469 + ) 465 470 ? post.record?.reply?.root?.uri || post.uri 466 471 : undefined 467 472 const isControlledByViewer =
+2 -2
src/view/com/profile/FollowButton.tsx
··· 1 1 import {StyleProp, TextStyle, View} from 'react-native' 2 - import {AppBskyActorDefs} from '@atproto/api' 3 2 import {msg} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 6 5 import {Shadow} from '#/state/cache/types' 7 6 import {useProfileFollowMutationQueue} from '#/state/queries/profile' 7 + import * as bsky from '#/types/bsky' 8 8 import {Button, ButtonType} from '../util/forms/Button' 9 9 import * as Toast from '../util/Toast' 10 10 ··· 18 18 }: { 19 19 unfollowedType?: ButtonType 20 20 followedType?: ButtonType 21 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 21 + profile: Shadow<bsky.profile.AnyProfileView> 22 22 labelStyle?: StyleProp<TextStyle> 23 23 logContext: 'ProfileCard' | 'StarterPackProfilesList' 24 24 onFollow?: () => void
+7 -5
src/view/com/profile/ProfileCard.tsx
··· 24 24 shouldShowKnownFollowers, 25 25 } from '#/components/KnownFollowers' 26 26 import * as Pills from '#/components/Pills' 27 + import * as bsky from '#/types/bsky' 27 28 import {Link} from '../util/Link' 28 29 import {Text} from '../util/text/Text' 29 30 import {PreviewableUserAvatar} from '../util/UserAvatar' ··· 41 42 showKnownFollowers, 42 43 }: { 43 44 testID?: string 44 - profile: AppBskyActorDefs.ProfileViewBasic 45 + profile: bsky.profile.AnyProfileView 45 46 noModFilter?: boolean 46 47 noBg?: boolean 47 48 noBorder?: boolean 48 49 renderButton?: ( 49 - profile: Shadow<AppBskyActorDefs.ProfileViewBasic>, 50 + profile: Shadow<bsky.profile.AnyProfileView>, 50 51 ) => React.ReactNode 51 52 onPress?: () => void 52 53 style?: StyleProp<ViewStyle> ··· 76 77 showKnownFollowers && 77 78 shouldShowKnownFollowers(profile.viewer?.knownFollowers) && 78 79 moderationOpts 80 + const hasDescription = 'description' in profile 79 81 80 82 return ( 81 83 <Link ··· 126 128 <View style={styles.layoutButton}>{renderButton(profile)}</View> 127 129 ) : undefined} 128 130 </View> 129 - {profile.description || knownFollowersVisible ? ( 131 + {hasDescription || knownFollowersVisible ? ( 130 132 <View style={styles.details}> 131 - {profile.description ? ( 133 + {hasDescription && profile.description ? ( 132 134 <Text emoji style={pal.text} numberOfLines={4}> 133 135 {profile.description as string} 134 136 </Text> ··· 139 141 a.flex_row, 140 142 a.align_center, 141 143 a.gap_sm, 142 - !!profile.description && a.mt_md, 144 + !!hasDescription && a.mt_md, 143 145 ]}> 144 146 <KnownFollowers 145 147 minimal
+1 -1
src/view/com/profile/ProfileFollowers.tsx
··· 17 17 item, 18 18 index, 19 19 }: { 20 - item: ActorDefs.ProfileViewBasic 20 + item: ActorDefs.ProfileView 21 21 index: number 22 22 }) { 23 23 return (
+1 -1
src/view/com/profile/ProfileFollows.tsx
··· 17 17 item, 18 18 index, 19 19 }: { 20 - item: ActorDefs.ProfileViewBasic 20 + item: ActorDefs.ProfileView 21 21 index: number 22 22 }) { 23 23 return (
+3 -2
src/view/com/util/UserAvatar.tsx
··· 2 2 import {Image, Pressable, StyleSheet, View} from 'react-native' 3 3 import {Image as RNImage} from 'react-native-image-crop-picker' 4 4 import Svg, {Circle, Path, Rect} from 'react-native-svg' 5 - import {AppBskyActorDefs, ModerationUI} from '@atproto/api' 5 + import {ModerationUI} from '@atproto/api' 6 6 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 7 7 import {msg, Trans} from '@lingui/macro' 8 8 import {useLingui} from '@lingui/react' ··· 31 31 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 32 32 import * as Menu from '#/components/Menu' 33 33 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 34 + import * as bsky from '#/types/bsky' 34 35 import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' 35 36 36 37 export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' ··· 55 56 56 57 interface PreviewableUserAvatarProps extends BaseUserAvatarProps { 57 58 moderation?: ModerationUI 58 - profile: AppBskyActorDefs.ProfileViewBasic 59 + profile: bsky.profile.AnyProfileView 59 60 disableHoverCard?: boolean 60 61 onBeforePress?: () => void 61 62 }
+9 -4
src/view/com/util/post-embeds/QuoteEmbed.tsx
··· 36 36 import {atoms as a, useTheme} from '#/alf' 37 37 import {RichText} from '#/components/RichText' 38 38 import {SubtleWebHover} from '#/components/SubtleWebHover' 39 + import * as bsky from '#/types/bsky' 39 40 import {ContentHider} from '../../../../components/moderation/ContentHider' 40 41 import {PostAlerts} from '../../../../components/moderation/PostAlerts' 41 42 import {Link} from '../Link' ··· 171 172 const itemTitle = `Post by ${quote.author.handle}` 172 173 173 174 const richText = React.useMemo(() => { 174 - const text = AppBskyFeedPost.isRecord(quote.record) ? quote.record.text : '' 175 - const facets = AppBskyFeedPost.isRecord(quote.record) 176 - ? quote.record.facets 177 - : undefined 175 + if ( 176 + !bsky.dangerousIsType<AppBskyFeedPost.Record>( 177 + quote.record, 178 + AppBskyFeedPost.isRecord, 179 + ) 180 + ) 181 + return undefined 182 + const {text, facets} = quote.record 178 183 return text.trim() 179 184 ? new RichTextAPI({text: text, facets: facets}) 180 185 : undefined
+2
src/view/screens/DebugMod.tsx
··· 133 133 }) 134 134 mockedProfile.did = did 135 135 mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' 136 + // @ts-expect-error ProfileViewBasic is close enough -esb 136 137 mockedProfile.banner = 137 138 'https://bsky.social/about/images/social-card-default-gradient.png' 138 139 return mockedProfile ··· 922 923 // @ts-ignore ProfileViewBasic is close enough -prf 923 924 profile={profile} 924 925 moderationOpts={moderationOpts} 926 + // @ts-ignore ProfileViewBasic is close enough -esb 925 927 descriptionRT={new RichText({text: profile.description as string})} 926 928 /> 927 929 </ScreenHider>
+7 -6
src/view/screens/Search/Search.tsx
··· 76 76 import * as Layout from '#/components/Layout' 77 77 import * as Menu from '#/components/Menu' 78 78 import {account, useStorage} from '#/storage' 79 + import * as bsky from '#/types/bsky' 79 80 80 81 function Loader() { 81 82 return ( ··· 656 657 ) 657 658 658 659 const updateProfileHistory = useCallback( 659 - async (item: AppBskyActorDefs.ProfileViewBasic) => { 660 + async (item: bsky.profile.AnyProfileView) => { 660 661 const newAccountHistory = [ 661 662 item.did, 662 663 ...accountHistory.filter(p => p !== item.did), ··· 673 674 [termHistory, setTermHistory], 674 675 ) 675 676 const deleteProfileHistoryItem = useCallback( 676 - async (item: AppBskyActorDefs.ProfileViewBasic) => { 677 + async (item: AppBskyActorDefs.ProfileViewDetailed) => { 677 678 setAccountHistory(accountHistory.filter(p => p !== item.did)) 678 679 }, 679 680 [accountHistory, setAccountHistory], ··· 766 767 ) 767 768 768 769 const handleProfileClick = React.useCallback( 769 - (profile: AppBskyActorDefs.ProfileViewBasic) => { 770 + (profile: bsky.profile.AnyProfileView) => { 770 771 // Slight delay to avoid updating during push nav animation. 771 772 setTimeout(() => { 772 773 updateProfileHistory(profile) ··· 1013 1014 onRemoveProfileClick, 1014 1015 }: { 1015 1016 searchHistory: string[] 1016 - selectedProfiles: AppBskyActorDefs.ProfileViewBasic[] 1017 + selectedProfiles: AppBskyActorDefs.ProfileViewDetailed[] 1017 1018 onItemClick: (item: string) => void 1018 - onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void 1019 + onProfileClick: (profile: AppBskyActorDefs.ProfileViewDetailed) => void 1019 1020 onRemoveItemClick: (item: string) => void 1020 - onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void 1021 + onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewDetailed) => void 1021 1022 }) { 1022 1023 const {isMobile} = useWebMediaQueries() 1023 1024 const pal = usePalette('default')
+1 -1
src/view/shell/desktop/LeftNav.tsx
··· 220 220 accounts: 221 221 | { 222 222 account: SessionAccount 223 - profile?: AppBskyActorDefs.ProfileView 223 + profile?: AppBskyActorDefs.ProfileViewDetailed 224 224 }[] 225 225 | undefined 226 226 signOutPromptControl: DialogControlProps
+218 -301
yarn.lock
··· 20 20 "@jridgewell/gen-mapping" "^0.3.0" 21 21 "@jridgewell/trace-mapping" "^0.3.9" 22 22 23 - "@atproto-labs/fetch-node@0.1.4": 24 - version "0.1.4" 25 - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.4.tgz#03859a39556eab936e2b3bec2d087585c6408cb3" 26 - integrity sha512-hwYx0XpgIl2zydRy13DtWvywruuHk1EX+yCjqjgUIezUm8fi35ZN4QvR6INEm0MpN2MD/kQsImPbd8ZftzZ3zw== 23 + "@atproto-labs/fetch-node@0.1.7": 24 + version "0.1.7" 25 + resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.7.tgz#b4538ee99bed6ca5843a9266004e1d7c1c0bf186" 26 + integrity sha512-vZ627PQqVGiBmPxulnviIGvvBPpTdzOcnfU1WcLeES3E0WjNxRGQqFaodBl5Zc4cj3QSPG/KC6wPcj/rjhbDrQ== 27 27 dependencies: 28 - "@atproto-labs/fetch" "0.1.2" 28 + "@atproto-labs/fetch" "0.2.1" 29 29 "@atproto-labs/pipe" "0.1.0" 30 30 ipaddr.js "^2.1.0" 31 31 psl "^1.9.0" 32 32 undici "^6.14.1" 33 33 34 - "@atproto-labs/fetch@0.1.2": 35 - version "0.1.2" 36 - resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.1.2.tgz#e1b9354205fb76f106ae3e1c6b56e7865a39600f" 37 - integrity sha512-7mQQIRtVenqtdBQKCqoLjyAhPS2aA56EGEjyz5zB3sramM3qkrvzyusr55GAzGDS0tvB6cy9cDEtSLmfK7LUnA== 34 + "@atproto-labs/fetch@0.2.1": 35 + version "0.2.1" 36 + resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.2.1.tgz#7e82eb6998d9694614fbe6cc9a68f0c217898a13" 37 + integrity sha512-V22/7C7r+FfIDZA/BVn5UeuK5JccDp7nOiRfp5JITpVw2OXQbVfd8kywN7voWvPXw4sjd4cHoIPgQa0wvQGenQ== 38 38 dependencies: 39 39 "@atproto-labs/pipe" "0.1.0" 40 40 optionalDependencies: ··· 45 45 resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.0.tgz#c8d86923b6d8e900d39efe6fdcdf0d897c434086" 46 46 integrity sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w== 47 47 48 - "@atproto-labs/simple-store-memory@0.1.1": 49 - version "0.1.1" 50 - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.1.tgz#54526a1f8ec978822be9fad75106ad8b78500dd3" 51 - integrity sha512-PCRqhnZ8NBNBvLku53O56T0lsVOtclfIrQU/rwLCc4+p45/SBPrRYNBi6YFq5rxZbK6Njos9MCmILV/KLQxrWA== 48 + "@atproto-labs/simple-store-memory@0.1.2": 49 + version "0.1.2" 50 + resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.2.tgz#234dbdb7162682795e09dfd7ea72cf448788ac8c" 51 + integrity sha512-q6wawjKKXuhUzr2MnkSlgr6zU6VimYkL8eNvLQvkroLnIDyMkoCKO4+EJ885ZD8lGwBo4pX9Lhrg9JJ+ncJI8g== 52 52 dependencies: 53 - "@atproto-labs/simple-store" "0.1.1" 53 + "@atproto-labs/simple-store" "0.1.2" 54 54 lru-cache "^10.2.0" 55 55 56 - "@atproto-labs/simple-store@0.1.1": 57 - version "0.1.1" 58 - resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.1.1.tgz#e743a2722b5d8732166f0a72aca8bd10e9bff106" 59 - integrity sha512-WKILW2b3QbAYKh+w5U2x6p5FqqLl0nAeLwGeDY+KjX01K4Dq3vQTR9b/qNp0jZm48CabPQVrqCv0PPU9LgRRRg== 56 + "@atproto-labs/simple-store@0.1.2": 57 + version "0.1.2" 58 + resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.1.2.tgz#39c1fa0326ae89204777e028886f79d6c22dc0ef" 59 + integrity sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ== 60 60 61 - "@atproto/api@^0.13.20": 62 - version "0.13.20" 63 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.20.tgz#5140db303c3b0981958dfe6a5fa6d7d1cd7bb3cc" 64 - integrity sha512-z/+CvG6BEttRHf856tKSe1AeUQNfrobRJldaHAthGmFk7O3wLZQyfcI9DUmBJQ9+4wAt0dZwvKWVGLZOV9eLHA== 61 + "@atproto-labs/xrpc-utils@0.0.7": 62 + version "0.0.7" 63 + resolved "https://registry.yarnpkg.com/@atproto-labs/xrpc-utils/-/xrpc-utils-0.0.7.tgz#351ddce177f2731383b2b8c62e0afe1de8112903" 64 + integrity sha512-mNev88mtNo79h4bkEQYuLoTlejc1zMl9lLwKbpKYfFaaU0IS9VdhiPdRTEcQ6JGYK915OZ5Lv7OJQNF0g9qq9w== 65 65 dependencies: 66 - "@atproto/common-web" "^0.3.1" 67 - "@atproto/lexicon" "^0.4.4" 68 - "@atproto/syntax" "^0.3.1" 69 - "@atproto/xrpc" "^0.6.5" 70 - await-lock "^2.2.2" 71 - multiformats "^9.9.0" 72 - tlds "^1.234.0" 73 - zod "^3.23.8" 66 + "@atproto/xrpc" "^0.6.9" 67 + "@atproto/xrpc-server" "^0.7.11" 74 68 75 - "@atproto/api@^0.13.35": 76 - version "0.13.35" 77 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.35.tgz#1e3a6c6e035c8e06302508983ed206effc92a7e8" 78 - integrity sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g== 69 + "@atproto/api@^0.14.0": 70 + version "0.14.0" 71 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.14.0.tgz#359debd4bc058fd24a2562dd674721e77a453d24" 72 + integrity sha512-KB+kMVdsDo7rW5S0vBpsPASepS717WPec8FAY04azhdCknlj7yh2FhMLYQu9dDb/uSJASAZGQEkDQUhumBk9fw== 79 73 dependencies: 80 74 "@atproto/common-web" "^0.4.0" 81 - "@atproto/lexicon" "^0.4.6" 82 - "@atproto/syntax" "^0.3.2" 83 - "@atproto/xrpc" "^0.6.8" 75 + "@atproto/lexicon" "^0.4.7" 76 + "@atproto/syntax" "^0.3.3" 77 + "@atproto/xrpc" "^0.6.9" 84 78 await-lock "^2.2.2" 85 79 multiformats "^9.9.0" 86 80 tlds "^1.234.0" 87 81 zod "^3.23.8" 88 82 89 - "@atproto/aws@^0.2.10": 90 - version "0.2.10" 91 - resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.10.tgz#e0b888fd50308cc24b7086cf3ec209587c13bbe4" 92 - integrity sha512-zQElKk6wGTQo5aKdXtmx/dINjkVgbJU9+C/xOVTs+M88I8IrrBxPvo1dASLJcMtRb9VjXh5snLJeAjgyx6qC6Q== 83 + "@atproto/aws@^0.2.15": 84 + version "0.2.15" 85 + resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.15.tgz#edc534a420b4da37e2f049d471bf40df93447a25" 86 + integrity sha512-4fR7wEnlGtkchfL7XdQ61yALNbIMpX1xL4H0XEq+o3LzM7/08lw2vhQCDFCqqjOJwWXxefQRsVXG5p7iyy3HPA== 93 87 dependencies: 94 - "@atproto/common" "^0.4.5" 95 - "@atproto/crypto" "^0.4.2" 96 - "@atproto/repo" "^0.6.0" 88 + "@atproto/common" "^0.4.8" 89 + "@atproto/crypto" "^0.4.4" 90 + "@atproto/repo" "^0.6.5" 97 91 "@aws-sdk/client-cloudfront" "^3.261.0" 98 92 "@aws-sdk/client-kms" "^3.196.0" 99 93 "@aws-sdk/client-s3" "^3.224.0" 100 94 "@aws-sdk/lib-storage" "^3.226.0" 101 - "@noble/curves" "^1.1.0" 95 + "@noble/curves" "^1.7.0" 102 96 key-encoder "^2.0.3" 103 97 multiformats "^9.9.0" 104 98 uint8arrays "3.0.0" 105 99 106 - "@atproto/bsky@^0.0.98": 107 - version "0.0.98" 108 - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.98.tgz#4c4746e588568df1878647ae80cf4b963bc95924" 109 - integrity sha512-Y+un2pD1W1H0s0IWdY6S4vLy8rgR8cpqThz9onn4wDppmGWvOBNXeD8AjNzIWC0iFlYcfR4rwCKSoccUXYzxNg== 100 + "@atproto/bsky@^0.0.117": 101 + version "0.0.117" 102 + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.117.tgz#869ac8f853cf43d893cba46a5a79f0f6a4a9f3f0" 103 + integrity sha512-C+KKNROLUgSkt5J7IlWMvqUKFRbZAoCg1vyuLiY/jf5+7NFkQ5YYghaJguMrQdpqvF8KLmVLI3clhyPwvlDIOg== 110 104 dependencies: 111 - "@atproto/api" "^0.13.20" 112 - "@atproto/common" "^0.4.5" 113 - "@atproto/crypto" "^0.4.2" 114 - "@atproto/identity" "^0.4.3" 115 - "@atproto/lexicon" "^0.4.4" 116 - "@atproto/repo" "^0.6.0" 117 - "@atproto/sync" "^0.1.7" 118 - "@atproto/syntax" "^0.3.1" 119 - "@atproto/xrpc-server" "^0.7.4" 105 + "@atproto-labs/fetch-node" "0.1.7" 106 + "@atproto-labs/xrpc-utils" "0.0.7" 107 + "@atproto/api" "^0.14.0" 108 + "@atproto/common" "^0.4.8" 109 + "@atproto/crypto" "^0.4.4" 110 + "@atproto/did" "^0.1.5" 111 + "@atproto/identity" "^0.4.6" 112 + "@atproto/lexicon" "^0.4.7" 113 + "@atproto/repo" "^0.6.5" 114 + "@atproto/sync" "^0.1.14" 115 + "@atproto/syntax" "^0.3.3" 116 + "@atproto/xrpc-server" "^0.7.11" 120 117 "@bufbuild/protobuf" "^1.5.0" 121 118 "@connectrpc/connect" "^1.1.4" 122 119 "@connectrpc/connect-express" "^1.1.4" 123 120 "@connectrpc/connect-node" "^1.1.4" 124 121 "@did-plc/lib" "^0.0.1" 122 + "@types/http-errors" "^2.0.1" 125 123 compression "^1.7.4" 126 124 cors "^2.8.5" 127 125 express "^4.17.2" ··· 142 140 structured-headers "^1.0.1" 143 141 typed-emitter "^2.1.0" 144 142 uint8arrays "3.0.0" 143 + undici "^6.19.8" 145 144 146 - "@atproto/bsync@^0.0.10": 147 - version "0.0.10" 148 - resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.10.tgz#fa16acfaf67112449b703778a20c785226c94189" 149 - integrity sha512-qviPMyYade/sqhX/9X9eTT4KaQ+FLvOyz+140LCDk/0vbZUCZPuYSEXZDCQkL5nlEXzScsQ3iyVeoYCGvV5kYw== 145 + "@atproto/bsync@^0.0.14": 146 + version "0.0.14" 147 + resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.14.tgz#ed25942e03e5c120cc89f3529143b2b197e4f3b1" 148 + integrity sha512-y6ioCJxmqnwQUc/MqBDCrNciJqrPanqSMjMneEU7mRSdbxXW27b1TblADSJeavkn8vbUGJUEmMWcqgWOrRClpw== 150 149 dependencies: 151 - "@atproto/common" "^0.4.5" 152 - "@atproto/syntax" "^0.3.1" 150 + "@atproto/common" "^0.4.8" 151 + "@atproto/syntax" "^0.3.3" 153 152 "@bufbuild/protobuf" "^1.5.0" 154 153 "@connectrpc/connect" "^1.1.4" 155 154 "@connectrpc/connect-node" "^1.1.4" ··· 159 158 pino-http "^8.2.1" 160 159 typed-emitter "^2.1.0" 161 160 162 - "@atproto/common-web@^0.3.1": 163 - version "0.3.1" 164 - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.1.tgz#86f8efb10a4b9073839cee914c6c08a664917cc4" 165 - integrity sha512-N7wiTnus5vAr+lT//0y8m/FaHHLJ9LpGuEwkwDAeV3LCiPif4m/FS8x/QOYrx1PdZQwKso95RAPzCGWQBH5j6Q== 166 - dependencies: 167 - graphemer "^1.4.0" 168 - multiformats "^9.9.0" 169 - uint8arrays "3.0.0" 170 - zod "^3.23.8" 171 - 172 161 "@atproto/common-web@^0.4.0": 173 162 version "0.4.0" 174 163 resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.0.tgz#b1407ae3f964f0ee23c2c3184f38041bac99d1f4" ··· 199 188 pino "^8.6.1" 200 189 zod "^3.14.2" 201 190 202 - "@atproto/common@^0.4.5": 203 - version "0.4.5" 204 - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.5.tgz#28fd176a9b5527c723828e725586bc0be9fa9516" 205 - integrity sha512-LFAGqHcxCI5+b31Xgk+VQQtZU258iGPpHJzNeHVcdh6teIKZi4C2l6YV+m+3CEz+yYcfP7jjUmgqesx7l9Arsg== 191 + "@atproto/common@^0.4.8": 192 + version "0.4.8" 193 + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.8.tgz#4ca61807448c672f19d17443b569fcdb81cc6df7" 194 + integrity sha512-/etCtnWQGLcfiGhIPwxAWrzgzoGB22nMWMeQcU6xZgRT4Cqrfg3A08jAMIHqve/AQpL+6D82lHYp36CG7a5G0w== 206 195 dependencies: 207 - "@atproto/common-web" "^0.3.1" 196 + "@atproto/common-web" "^0.4.0" 208 197 "@ipld/dag-cbor" "^7.0.3" 209 198 cbor-x "^1.5.1" 210 199 iso-datestring-validator "^2.2.2" ··· 222 211 one-webcrypto "^1.0.3" 223 212 uint8arrays "3.0.0" 224 213 225 - "@atproto/crypto@^0.4.2": 226 - version "0.4.2" 227 - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.2.tgz#07417887ddbd4baae5298d4b9499fc727f261a31" 228 - integrity sha512-aeOfPQYCDbhn2hV06oBF2KXrWjf/BK4yL8lfANJKSmKl3tKWCkiW/moi643rUXXxSE72KtWtQeqvNFYnnFJ0ig== 214 + "@atproto/crypto@^0.4.4": 215 + version "0.4.4" 216 + resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.4.tgz#3bd5066643d08e09da55bd59ac1f319d1fcff803" 217 + integrity sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA== 229 218 dependencies: 230 - "@noble/curves" "^1.1.0" 231 - "@noble/hashes" "^1.3.1" 219 + "@noble/curves" "^1.7.0" 220 + "@noble/hashes" "^1.6.1" 232 221 uint8arrays "3.0.0" 233 222 234 - "@atproto/dev-env@^0.3.67": 235 - version "0.3.67" 236 - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.67.tgz#4f6a20f0aafa8125ed9ec715abceedd11580882e" 237 - integrity sha512-7Ize4Y5vdjQjyrxTwjBPbkxKXQdE02KpE7AJLJt6Xpvowd2vbn8l8rDXfha+LtVi6t/613U4s+Slo5c1YD3x9A== 223 + "@atproto/dev-env@^0.3.87": 224 + version "0.3.87" 225 + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.87.tgz#dad1a7cdde1d38cbd5a88c75f5106261c6361b66" 226 + integrity sha512-xUeI94hqSnksjwdAi+Q0ML2qJlwRKYdqSD3kmR8LHIGeF6cWv+rjoSkK6+LVuV//LpC1EigoqkKQdX0UbDROOA== 238 227 dependencies: 239 - "@atproto/api" "^0.13.20" 240 - "@atproto/bsky" "^0.0.98" 241 - "@atproto/bsync" "^0.0.10" 242 - "@atproto/common-web" "^0.3.1" 243 - "@atproto/crypto" "^0.4.2" 244 - "@atproto/identity" "^0.4.3" 245 - "@atproto/lexicon" "^0.4.4" 246 - "@atproto/ozone" "^0.1.59" 247 - "@atproto/pds" "^0.4.76" 248 - "@atproto/sync" "^0.1.7" 249 - "@atproto/syntax" "^0.3.1" 250 - "@atproto/xrpc-server" "^0.7.4" 228 + "@atproto/api" "^0.14.0" 229 + "@atproto/bsky" "^0.0.117" 230 + "@atproto/bsync" "^0.0.14" 231 + "@atproto/common-web" "^0.4.0" 232 + "@atproto/crypto" "^0.4.4" 233 + "@atproto/identity" "^0.4.6" 234 + "@atproto/lexicon" "^0.4.7" 235 + "@atproto/ozone" "^0.1.78" 236 + "@atproto/pds" "^0.4.95" 237 + "@atproto/sync" "^0.1.14" 238 + "@atproto/syntax" "^0.3.3" 239 + "@atproto/xrpc-server" "^0.7.11" 251 240 "@did-plc/lib" "^0.0.1" 252 241 "@did-plc/server" "^0.0.1" 253 - axios "^0.27.2" 254 242 dotenv "^16.0.3" 255 243 express "^4.18.2" 256 244 get-port "^5.1.1" 257 245 multiformats "^9.9.0" 258 246 uint8arrays "3.0.0" 247 + undici "^6.14.1" 259 248 260 - "@atproto/identity@^0.4.3": 261 - version "0.4.3" 262 - resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.3.tgz#fd387d4f2dd68a514e3d0138009a4b9db7f489fd" 263 - integrity sha512-DLXMWh57dHvIeBl+IvC+q20z0IdDZT1awOn84vDyxacL9DfhbiTy/zCUPFEzHyvfrilNG1tDA4zQzURubdFqNg== 249 + "@atproto/did@^0.1.5": 250 + version "0.1.5" 251 + resolved "https://registry.yarnpkg.com/@atproto/did/-/did-0.1.5.tgz#5bfe73625d54c4c687c00ff370971ce01c39bd61" 252 + integrity sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ== 264 253 dependencies: 265 - "@atproto/common-web" "^0.3.1" 266 - "@atproto/crypto" "^0.4.2" 267 - axios "^0.27.2" 254 + zod "^3.23.8" 268 255 269 - "@atproto/jwk-jose@0.1.2": 270 - version "0.1.2" 271 - resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.2.tgz#236eadb740b498689d9a912d1254aa9ff58890a1" 272 - integrity sha512-lDwc/6lLn2aZ/JpyyggyjLFsJPMntrVzryyGUx5aNpuTS8SIuc4Ky0REhxqfLopQXJJZCuRRjagHG3uP05/moQ== 256 + "@atproto/identity@^0.4.6": 257 + version "0.4.6" 258 + resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.6.tgz#d2e7e3cd9b2af9ee2a82b7ffd8f6e2fcbd813a86" 259 + integrity sha512-fJq/cIp9MOgHxZfxuyki6mobk0QxRnbts53DstRixlvb5mOoxwttb9Gp6A8u9q49zBsfOmXNTHmP97I9iMHmTQ== 273 260 dependencies: 274 - "@atproto/jwk" "0.1.1" 275 - jose "^5.2.0" 261 + "@atproto/common-web" "^0.4.0" 262 + "@atproto/crypto" "^0.4.4" 276 263 277 - "@atproto/jwk@0.1.1": 278 - version "0.1.1" 279 - resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.1.1.tgz#15bcad4a1778eeb20c82108e0ec55fef45cd07b6" 280 - integrity sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og== 264 + "@atproto/jwk-jose@0.1.4": 265 + version "0.1.4" 266 + resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.4.tgz#c36c4332ce41d612a09492e9f6da479a6b7b2b9b" 267 + integrity sha512-JzLn1wUzuLfweznSECdTjSHTxQBEz7Q8oJ4XKjRNludqzyJW8etEH00l1WolLipFxoj1QCG9qy00JmlC59Y6Rw== 281 268 dependencies: 282 - multiformats "^9.9.0" 283 - zod "^3.23.8" 269 + "@atproto/jwk" "0.1.3" 270 + jose "^5.2.0" 284 271 285 - "@atproto/lexicon@^0.4.4": 286 - version "0.4.4" 287 - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.4.tgz#0d97314bb57b693b76f2495fa5e02872469dd93a" 288 - integrity sha512-QFEmr3rpj/RoAmfX9ALU/asBG/rsVtQZnw+9nOB1/AuIwoxXd+ZyndR6lVUc2+DL4GEjl6W2yvBru5xbQIZWyA== 272 + "@atproto/jwk@0.1.3": 273 + version "0.1.3" 274 + resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.1.3.tgz#c42feb53a39573cf84eeec73c62776d1d2497a55" 275 + integrity sha512-5rBgA8Fk4fg6MfNyEQvUnwq1MRn5xZOXYj4oxLuZ549XeNp2Rm2v+psuEkICD+o6pfIoMX4Hw7UTlXDrpsKKlQ== 289 276 dependencies: 290 - "@atproto/common-web" "^0.3.1" 291 - "@atproto/syntax" "^0.3.1" 292 - iso-datestring-validator "^2.2.2" 293 277 multiformats "^9.9.0" 294 278 zod "^3.23.8" 295 279 296 - "@atproto/lexicon@^0.4.6": 297 - version "0.4.6" 298 - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6.tgz#74b2a0f3e4c867b33f75430d4ccec70c47e41576" 299 - integrity sha512-RbiwXcnTuLp9vQrNoQ7xly8HyifKkovqCYtbfXVwqdylWYKPhmRsYkRfcPNv/lILhT9Lm0GVnxNwGGwvvgIsfA== 280 + "@atproto/lexicon@^0.4.7": 281 + version "0.4.7" 282 + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.7.tgz#f5d31615c21bcfd3e655f1e4f11a40a62fea9f86" 283 + integrity sha512-/x6h3tAiDNzSi4eXtC8ke65B7UzsagtlGRHmUD95698x5lBRpDnpizj0fZWTZVYed5qnOmz/ZEue+v3wDmO61g== 300 284 dependencies: 301 285 "@atproto/common-web" "^0.4.0" 302 - "@atproto/syntax" "^0.3.2" 286 + "@atproto/syntax" "^0.3.3" 303 287 iso-datestring-validator "^2.2.2" 304 288 multiformats "^9.9.0" 305 289 zod "^3.23.8" 306 290 307 - "@atproto/oauth-provider@^0.2.10": 308 - version "0.2.10" 309 - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901" 310 - integrity sha512-cF42lo0+Mj+Zq2RXwS2NxmobmtL7YL1vXlYcN6iKflZ8pQ5WvpR/cZKsKEZOT9cEBBTw5MARKTYxbr8CPDKlHg== 291 + "@atproto/oauth-provider@^0.2.17": 292 + version "0.2.17" 293 + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.17.tgz#4644d391eedbbbbe5825ecc0e8cc03f1c6433b95" 294 + integrity sha512-fvEbONJfjDRqQoIkB76n1cLz7y6f99Fhgs8h2u1LNZak1p95JZs3Tc5HsDhmUHo2Yk9h22CIwMRRjHImU/m1Nw== 311 295 dependencies: 312 - "@atproto-labs/fetch" "0.1.2" 313 - "@atproto-labs/fetch-node" "0.1.4" 296 + "@atproto-labs/fetch" "0.2.1" 297 + "@atproto-labs/fetch-node" "0.1.7" 314 298 "@atproto-labs/pipe" "0.1.0" 315 - "@atproto-labs/simple-store" "0.1.1" 316 - "@atproto-labs/simple-store-memory" "0.1.1" 317 - "@atproto/common" "^0.4.5" 318 - "@atproto/jwk" "0.1.1" 319 - "@atproto/jwk-jose" "0.1.2" 320 - "@atproto/oauth-types" "0.2.1" 299 + "@atproto-labs/simple-store" "0.1.2" 300 + "@atproto-labs/simple-store-memory" "0.1.2" 301 + "@atproto/common" "^0.4.8" 302 + "@atproto/jwk" "0.1.3" 303 + "@atproto/jwk-jose" "0.1.4" 304 + "@atproto/oauth-types" "0.2.3" 321 305 "@hapi/accept" "^6.0.3" 322 306 "@hapi/bourne" "^3.0.0" 323 307 "@hapi/content" "^6.0.0" ··· 329 313 psl "^1.9.0" 330 314 zod "^3.23.8" 331 315 332 - "@atproto/oauth-types@0.2.1": 333 - version "0.2.1" 334 - resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.2.1.tgz#a7ace557cc91817fcde6195f023e4e1838e4aef6" 335 - integrity sha512-hDisUXzcq5KU1HMuCYZ8Kcz7BePl7V11bFjjgZvND3mdSphiyBpJ8MCNn3QzAa6cXpFo0w9PDcYMAlCCRZHdVw== 316 + "@atproto/oauth-types@0.2.3": 317 + version "0.2.3" 318 + resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.2.3.tgz#a2e9470cbf48c6e7663906f8b43045077945e1f2" 319 + integrity sha512-M+0WW/alS2BfhKtwvdU3rSaLoycw6kTH1kGKeyDdmb/xN/8QjU7T6dkJe+wX4NC7F23xdKfti9DZhBpEtn+/kg== 336 320 dependencies: 337 - "@atproto/jwk" "0.1.1" 321 + "@atproto/jwk" "0.1.3" 338 322 zod "^3.23.8" 339 323 340 - "@atproto/ozone@^0.1.59": 341 - version "0.1.59" 342 - resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.59.tgz#219984a46617b0ac039f2f02767290eaa0b4cfc3" 343 - integrity sha512-AD03Ocb3fZW+grxO/VwMld5iNdCLgbahFzku6xh1qEw0tLOBKp3GXSfepVd9XWu5fb1yPhGPd2JgjApV5hbJvw== 324 + "@atproto/ozone@^0.1.78": 325 + version "0.1.78" 326 + resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.78.tgz#9ed4535967b79ba291c15560b4b598f863c5ded0" 327 + integrity sha512-l7T6d4+gieVbxscZ3ou/PSE2VtO9w3/C30gAhVhRPvqbUzAAzpvPLkQGyP7cUSnuXrb32QCyuOy2QoAJ84lR8Q== 344 328 dependencies: 345 - "@atproto/api" "^0.13.20" 346 - "@atproto/common" "^0.4.5" 347 - "@atproto/crypto" "^0.4.2" 348 - "@atproto/identity" "^0.4.3" 349 - "@atproto/lexicon" "^0.4.4" 350 - "@atproto/syntax" "^0.3.1" 351 - "@atproto/xrpc" "^0.6.5" 352 - "@atproto/xrpc-server" "^0.7.4" 329 + "@atproto/api" "^0.14.0" 330 + "@atproto/common" "^0.4.8" 331 + "@atproto/crypto" "^0.4.4" 332 + "@atproto/identity" "^0.4.6" 333 + "@atproto/lexicon" "^0.4.7" 334 + "@atproto/syntax" "^0.3.3" 335 + "@atproto/xrpc" "^0.6.9" 336 + "@atproto/xrpc-server" "^0.7.11" 353 337 "@did-plc/lib" "^0.0.1" 354 - axios "^1.6.7" 355 338 compression "^1.7.4" 356 339 cors "^2.8.5" 357 340 express "^4.17.2" ··· 365 348 structured-headers "^1.0.1" 366 349 typed-emitter "^2.1.0" 367 350 uint8arrays "3.0.0" 351 + undici "^6.14.1" 368 352 369 - "@atproto/pds@^0.4.76": 370 - version "0.4.76" 371 - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.76.tgz#cd7b3f13359a7c31dc9362a5e4309419512c4102" 372 - integrity sha512-+cFVpqlgpCS0BuGac5fCQPZUugpS1r7ghnSQLVdjnTnvQJCqLRA++BlJWYbGgRP6FJrumCY2jtuwG8t59Rjt8Q== 353 + "@atproto/pds@^0.4.95": 354 + version "0.4.95" 355 + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.95.tgz#a0881f7de2cfa900b82c3a465798a75c02302f2e" 356 + integrity sha512-HiOoWvBvU/hICgOslOddX6yIUN/e5um59Py0Ngl85xPkwt1TUb1/c57P2/wM1HGZt6wrORplDS4gzCP9wmGnRQ== 373 357 dependencies: 374 - "@atproto-labs/fetch-node" "0.1.4" 375 - "@atproto/api" "^0.13.20" 376 - "@atproto/aws" "^0.2.10" 377 - "@atproto/common" "^0.4.5" 378 - "@atproto/crypto" "^0.4.2" 379 - "@atproto/identity" "^0.4.3" 380 - "@atproto/lexicon" "^0.4.4" 381 - "@atproto/oauth-provider" "^0.2.10" 382 - "@atproto/repo" "^0.6.0" 383 - "@atproto/syntax" "^0.3.1" 384 - "@atproto/xrpc" "^0.6.5" 385 - "@atproto/xrpc-server" "^0.7.4" 358 + "@atproto-labs/fetch-node" "0.1.7" 359 + "@atproto-labs/xrpc-utils" "0.0.7" 360 + "@atproto/api" "^0.14.0" 361 + "@atproto/aws" "^0.2.15" 362 + "@atproto/common" "^0.4.8" 363 + "@atproto/crypto" "^0.4.4" 364 + "@atproto/identity" "^0.4.6" 365 + "@atproto/lexicon" "^0.4.7" 366 + "@atproto/oauth-provider" "^0.2.17" 367 + "@atproto/repo" "^0.6.5" 368 + "@atproto/syntax" "^0.3.3" 369 + "@atproto/xrpc" "^0.6.9" 370 + "@atproto/xrpc-server" "^0.7.11" 386 371 "@did-plc/lib" "^0.0.4" 387 372 "@hapi/address" "^5.1.1" 388 373 better-sqlite3 "^10.0.0" ··· 412 397 undici "^6.19.8" 413 398 zod "^3.23.8" 414 399 415 - "@atproto/repo@^0.6.0": 416 - version "0.6.0" 417 - resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.6.0.tgz#29e698731e6df63636b0f7c91ce106a9de50ad19" 418 - integrity sha512-6YGVhjiHKmqCW5Ce4oY49E3NCEfbvAGowJ5ETXX2sx2l4D2bOL7a2hn5zWqsPHYpWSLjrPfnj7PVpApK0kmL7A== 400 + "@atproto/repo@^0.6.5": 401 + version "0.6.5" 402 + resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.6.5.tgz#a45cb0df5b1e0ec078a535a4acb69e9364938020" 403 + integrity sha512-Sa95LaEMDtwL9M0kp3vuVQIcgEJI+6EssDLIiuPnJAi9SbEPESdUfEiIR5t2oFCkMwrS7OJQCLdCa7CMy+plUg== 419 404 dependencies: 420 - "@atproto/common" "^0.4.5" 421 - "@atproto/common-web" "^0.3.1" 422 - "@atproto/crypto" "^0.4.2" 423 - "@atproto/lexicon" "^0.4.4" 405 + "@atproto/common" "^0.4.8" 406 + "@atproto/common-web" "^0.4.0" 407 + "@atproto/crypto" "^0.4.4" 408 + "@atproto/lexicon" "^0.4.7" 424 409 "@ipld/car" "^3.2.3" 425 410 "@ipld/dag-cbor" "^7.0.0" 426 411 multiformats "^9.9.0" 427 412 uint8arrays "3.0.0" 428 413 zod "^3.23.8" 429 414 430 - "@atproto/sync@^0.1.7": 431 - version "0.1.7" 432 - resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.7.tgz#c7f78d99bb40eacf93ca13fdd04134a0985bf421" 433 - integrity sha512-liJH2EsD4AbWA8G0oRDURgbHW6Uq4NnM2rNfbrTlqgtj0kyGRY3FcVEyqeRcaQYfCuscChIg5DQKHqY421/7Mw== 415 + "@atproto/sync@^0.1.14": 416 + version "0.1.14" 417 + resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.14.tgz#e35ff18ab314eb26d3bc4d8f55e3c8530b8eed0a" 418 + integrity sha512-8+8o4aWnWVJiiNG63k9K/etFz7KZgwmYKIXgT13AO8hGRAKO4eZDTtM2GbEfKqja2Grs7iHSZ4EIKoTYaK4Daw== 434 419 dependencies: 435 - "@atproto/common" "^0.4.5" 436 - "@atproto/identity" "^0.4.3" 437 - "@atproto/lexicon" "^0.4.4" 438 - "@atproto/repo" "^0.6.0" 439 - "@atproto/syntax" "^0.3.1" 440 - "@atproto/xrpc-server" "^0.7.4" 420 + "@atproto/common" "^0.4.8" 421 + "@atproto/identity" "^0.4.6" 422 + "@atproto/lexicon" "^0.4.7" 423 + "@atproto/repo" "^0.6.5" 424 + "@atproto/syntax" "^0.3.3" 425 + "@atproto/xrpc-server" "^0.7.11" 441 426 multiformats "^9.9.0" 442 427 p-queue "^6.6.2" 443 428 ws "^8.12.0" 444 429 445 - "@atproto/syntax@^0.3.1": 446 - version "0.3.1" 447 - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.1.tgz#4346418728f9643d783d2ffcf7c77e132e1f53d4" 448 - integrity sha512-fzW0Mg1QUOVCWUD3RgEsDt6d1OZ6DdFmbKcDdbzUfh0t4rhtRAC05KbZYmxuMPWDAiJ4BbbQ5dkAc/mNypMXkw== 430 + "@atproto/syntax@^0.3.3": 431 + version "0.3.3" 432 + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.3.tgz#6debe8983985378104822172a128e429931bf3f7" 433 + integrity sha512-F1LZweesNYdBbZBXVa72N/cSvchG8Q1tG4/209ZXbIuM3FwQtkgn+zgmmV4P4ORmhOeXPBNXvMBpcqiwx/gEQQ== 449 434 450 - "@atproto/syntax@^0.3.2": 451 - version "0.3.2" 452 - resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.2.tgz#188f8dccba11e5ace1bf83cbff8ed9e1a3d2d66c" 453 - integrity sha512-JLMhTbXER1Im98RrozfsLAZARGIAzKCZEm+Inh1IF00XU6tHcoGKS+HOw0Uy4R2r04yvxoFs8fswmwAhmMpMdw== 454 - 455 - "@atproto/xrpc-server@^0.7.4": 456 - version "0.7.4" 457 - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.7.4.tgz#dfac8f7276c1c971a35eaba627eb6372088441c3" 458 - integrity sha512-MrAwxfJBQm/kCol3D8qc+vpQzBMzLqvtUbauSSfVVJ10PlGtxg4LlXqcjkAuhrjyrqp3dQH9LHuhDpgVQK+G3w== 435 + "@atproto/xrpc-server@^0.7.11": 436 + version "0.7.11" 437 + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.7.11.tgz#efadcfdaaaa0ff5576d1ee97e46dcbc6dafcb0b6" 438 + integrity sha512-kywMZMw2FbUFk0xBCtSI1mik+dc3uSvloNndI+N4X/+Qv1FGvoCRMi//9TqaSL13MFevTOynVoMVmaZbnaDG9A== 459 439 dependencies: 460 - "@atproto/common" "^0.4.5" 461 - "@atproto/crypto" "^0.4.2" 462 - "@atproto/lexicon" "^0.4.4" 463 - "@atproto/xrpc" "^0.6.5" 440 + "@atproto/common" "^0.4.8" 441 + "@atproto/crypto" "^0.4.4" 442 + "@atproto/lexicon" "^0.4.7" 443 + "@atproto/xrpc" "^0.6.9" 464 444 cbor-x "^1.5.1" 465 445 express "^4.17.2" 466 446 http-errors "^2.0.0" ··· 470 450 ws "^8.12.0" 471 451 zod "^3.23.8" 472 452 473 - "@atproto/xrpc@^0.6.5": 474 - version "0.6.5" 475 - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.5.tgz#8b180fc5f6b8374fd00c41b9e4cd7b24ead48e6b" 476 - integrity sha512-t6u8iPEVbWge5RhzKZDahSzNDYIAxUtop6Q/X/apAZY1rgreVU0/1sSvvRoRFH19d3UIKjYdLuwFqMi9w8nY3Q== 477 - dependencies: 478 - "@atproto/lexicon" "^0.4.4" 479 - zod "^3.23.8" 480 - 481 - "@atproto/xrpc@^0.6.8": 482 - version "0.6.8" 483 - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.8.tgz#cede54e17b6f8863f78e16f27f87c1966446eea6" 484 - integrity sha512-+KW0NcwdFyLziccYimX6tPkORiwwxlJPqlkVL9bJyj8nJ0aB8cyqo9HXkziMI+R6ansB1BuWQ0tfdPlLLwrUcA== 453 + "@atproto/xrpc@^0.6.9": 454 + version "0.6.9" 455 + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.9.tgz#6e1effc42cdab40741a73ead5c276183284887d2" 456 + integrity sha512-vQGA7++DYMNaHx3C7vEjT+2X6hYYLG7JNbBnDLWu0km1/1KYXgRkAz4h+FfYqg1mvzvIorHU7DAs5wevkJDDlw== 485 457 dependencies: 486 - "@atproto/lexicon" "^0.4.6" 458 + "@atproto/lexicon" "^0.4.7" 487 459 zod "^3.23.8" 488 460 489 461 "@aws-crypto/crc32@3.0.0": ··· 3295 3267 "@babel/parser" "^7.25.9" 3296 3268 "@babel/types" "^7.25.9" 3297 3269 3298 - "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": 3270 + "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": 3299 3271 version "7.25.9" 3300 3272 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" 3301 3273 integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== ··· 3353 3325 "@babel/helper-split-export-declaration" "^7.24.5" 3354 3326 "@babel/parser" "^7.24.5" 3355 3327 "@babel/types" "^7.24.5" 3356 - debug "^4.3.1" 3357 - globals "^11.1.0" 3358 - 3359 - "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": 3360 - version "7.25.9" 3361 - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" 3362 - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== 3363 - dependencies: 3364 - "@babel/code-frame" "^7.25.9" 3365 - "@babel/generator" "^7.25.9" 3366 - "@babel/parser" "^7.25.9" 3367 - "@babel/template" "^7.25.9" 3368 - "@babel/types" "^7.25.9" 3369 3328 debug "^4.3.1" 3370 3329 globals "^11.1.0" 3371 3330 ··· 5117 5076 dependencies: 5118 5077 eslint-scope "5.1.1" 5119 5078 5120 - "@noble/curves@^1.1.0": 5121 - version "1.1.0" 5122 - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" 5123 - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== 5079 + "@noble/curves@^1.7.0": 5080 + version "1.8.1" 5081 + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" 5082 + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== 5124 5083 dependencies: 5125 - "@noble/hashes" "1.3.1" 5084 + "@noble/hashes" "1.7.1" 5126 5085 5127 - "@noble/hashes@1.3.1", "@noble/hashes@^1.3.1": 5128 - version "1.3.1" 5129 - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" 5130 - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== 5086 + "@noble/hashes@1.7.1", "@noble/hashes@^1.6.1": 5087 + version "1.7.1" 5088 + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" 5089 + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== 5131 5090 5132 5091 "@noble/secp256k1@^1.7.0": 5133 5092 version "1.7.1" ··· 6947 6906 resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" 6948 6907 integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== 6949 6908 6909 + "@types/http-errors@^2.0.1": 6910 + version "2.0.4" 6911 + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" 6912 + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== 6913 + 6950 6914 "@types/http-proxy@^1.17.8": 6951 6915 version "1.17.11" 6952 6916 resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" ··· 7983 7947 resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" 7984 7948 integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== 7985 7949 7986 - axios@^0.27.2: 7987 - version "0.27.2" 7988 - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" 7989 - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== 7990 - dependencies: 7991 - follow-redirects "^1.14.9" 7992 - form-data "^4.0.0" 7993 - 7994 7950 axios@^1.3.4: 7995 7951 version "1.4.0" 7996 7952 resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" 7997 7953 integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== 7998 7954 dependencies: 7999 7955 follow-redirects "^1.15.0" 8000 - form-data "^4.0.0" 8001 - proxy-from-env "^1.1.0" 8002 - 8003 - axios@^1.6.7: 8004 - version "1.6.8" 8005 - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" 8006 - integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== 8007 - dependencies: 8008 - follow-redirects "^1.15.6" 8009 7956 form-data "^4.0.0" 8010 7957 proxy-from-env "^1.1.0" 8011 7958 ··· 11200 11147 resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.215.0.tgz#9b153fa27ab238bcc0bb1ff73b63bdb15d3f277d" 11201 11148 integrity sha512-8bjwzy8vi+fNDy8YoTBNtQUSZa53i7UWJJTunJojOtjab9cMNhOCwohionuMgDQUU0y21QTTtPOX6OQEOQT72A== 11202 11149 11203 - follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: 11150 + follow-redirects@^1.0.0, follow-redirects@^1.15.0: 11204 11151 version "1.15.2" 11205 11152 resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 11206 11153 integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== 11207 11154 11208 - follow-redirects@^1.15.6: 11209 - version "1.15.6" 11210 - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" 11211 - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== 11212 - 11213 11155 fontfaceobserver@^2.1.0: 11214 11156 version "2.3.0" 11215 11157 resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8" ··· 17657 17599 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" 17658 17600 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== 17659 17601 17660 - "string-width-cjs@npm:string-width@^4.2.0": 17661 - version "4.2.3" 17662 - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 17663 - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 17664 - dependencies: 17665 - emoji-regex "^8.0.0" 17666 - is-fullwidth-code-point "^3.0.0" 17667 - strip-ansi "^6.0.1" 17668 - 17669 - string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 17602 + "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 17670 17603 version "4.2.3" 17671 17604 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 17672 17605 integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== ··· 17766 17699 dependencies: 17767 17700 safe-buffer "~5.1.0" 17768 17701 17769 - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": 17702 + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: 17770 17703 version "6.0.1" 17771 17704 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 17772 17705 integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== ··· 17779 17712 integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 17780 17713 dependencies: 17781 17714 ansi-regex "^4.1.0" 17782 - 17783 - strip-ansi@^6.0.0, strip-ansi@^6.0.1: 17784 - version "6.0.1" 17785 - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 17786 - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 17787 - dependencies: 17788 - ansi-regex "^5.0.1" 17789 17715 17790 17716 strip-ansi@^7.0.1: 17791 17717 version "7.1.0" ··· 19061 18987 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 19062 18988 integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== 19063 18989 19064 - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": 18990 + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: 19065 18991 version "7.0.0" 19066 18992 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 19067 18993 integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== ··· 19074 19000 version "6.2.0" 19075 19001 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 19076 19002 integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 19077 - dependencies: 19078 - ansi-styles "^4.0.0" 19079 - string-width "^4.1.0" 19080 - strip-ansi "^6.0.0" 19081 - 19082 - wrap-ansi@^7.0.0: 19083 - version "7.0.0" 19084 - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 19085 - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 19086 19003 dependencies: 19087 19004 ansi-styles "^4.0.0" 19088 19005 string-width "^4.1.0"