Bluesky app fork with some witchin' additions 💫

revert pdsAgent commits & remove custom AppView support

Bye bye "fix: ensure `com.atproto.*` calls go through without proxying" "add deer custom-appview" "unproxy preferences" (ALSO removes the pdsAgent import added in "couple code error fixes" 3814f6b)

This reverts commits 69cf6f93b829d1d25e5fa37875d90070f68ef2ed, 6b305f2626cffa73b1989dec44a071ae7b4ba2b9, and 45584b9c28a5b657e3684b2d8f3cbea06668c116.

xan.lol cccfbd0b cb54cd87

verified
+99 -379
+16 -10
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 17 type AppBskyFeedThreadgate, 18 AtUri, 19 type BlobRef, 20 - isDid, 21 type RichText as RichTextAPI, 22 } from '@atproto/api' 23 import {msg} from '@lingui/macro' ··· 44 import {type Shadow} from '#/state/cache/post-shadow' 45 import {useProfileShadow} from '#/state/cache/profile-shadow' 46 import {useFeedFeedbackContext} from '#/state/feed-feedback' 47 - import {useLanguagePrefs} from '#/state/preferences' 48 - import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' 49 import {usePinnedPostMutation} from '#/state/queries/pinned-post' 50 import { 51 usePostDeleteMutation, ··· 58 useProfileMuteMutationQueue, 59 } from '#/state/queries/profile' 60 import {resolvePdsServiceUrl} from '#/state/queries/resolve-identity' 61 - import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' 62 import { 63 InvalidInteractionSettingsError, 64 MAX_HIDDEN_REPLIES, 65 MaxHiddenRepliesError, 66 } from '#/state/queries/threadgate' 67 import {useRequireAuth, useSession} from '#/state/session' 68 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' ··· 84 import {Eye_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/Eye' 85 import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 86 import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' 87 - import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 88 - import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 89 import {Pencil_Stroke2_Corner0_Rounded as Pen} from '#/components/icons/Pencil' 90 import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person' 91 import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 92 import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' 93 - import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 94 - import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 95 import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' 96 import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' 97 import {Loader} from '#/components/Loader' ··· 603 if (!videoEmbed) return 604 const did = post.author.did 605 const cid = videoEmbed.cid 606 - if (!isDid(did)) return 607 - const pdsUrl = await resolvePdsServiceUrl(did as `did:${string}:${string}`) 608 const uri = `${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}` 609 610 Toast.show(_(msg({message: 'Downloading video...', context: 'toast'})))
··· 17 type AppBskyFeedThreadgate, 18 AtUri, 19 type BlobRef, 20 type RichText as RichTextAPI, 21 } from '@atproto/api' 22 import {msg} from '@lingui/macro' ··· 43 import {type Shadow} from '#/state/cache/post-shadow' 44 import {useProfileShadow} from '#/state/cache/profile-shadow' 45 import {useFeedFeedbackContext} from '#/state/feed-feedback' 46 + import { 47 + useHiddenPosts, 48 + useHiddenPostsApi, 49 + useLanguagePrefs, 50 + } from '#/state/preferences' 51 import {usePinnedPostMutation} from '#/state/queries/pinned-post' 52 import { 53 usePostDeleteMutation, ··· 60 useProfileMuteMutationQueue, 61 } from '#/state/queries/profile' 62 import {resolvePdsServiceUrl} from '#/state/queries/resolve-identity' 63 import { 64 InvalidInteractionSettingsError, 65 MAX_HIDDEN_REPLIES, 66 MaxHiddenRepliesError, 67 + useToggleReplyVisibilityMutation, 68 } from '#/state/queries/threadgate' 69 import {useRequireAuth, useSession} from '#/state/session' 70 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' ··· 86 import {Eye_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/Eye' 87 import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 88 import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' 89 + import { 90 + Mute_Stroke2_Corner0_Rounded as Mute, 91 + Mute_Stroke2_Corner0_Rounded as MuteIcon, 92 + } from '#/components/icons/Mute' 93 import {Pencil_Stroke2_Corner0_Rounded as Pen} from '#/components/icons/Pencil' 94 import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person' 95 import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 96 import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' 97 + import { 98 + SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute, 99 + SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon, 100 + } from '#/components/icons/Speaker' 101 import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' 102 import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' 103 import {Loader} from '#/components/Loader' ··· 609 if (!videoEmbed) return 610 const did = post.author.did 611 const cid = videoEmbed.cid 612 + if (!did.startsWith('did:')) return 613 + const pdsUrl = await resolvePdsServiceUrl(did as `did:${string}`) 614 const uri = `${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}` 615 616 Toast.show(_(msg({message: 'Downloading video...', context: 'toast'})))
+1 -2
src/components/dialogs/EmailDialog/data/useConfirmEmail.ts
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent, useSession} from '#/state/session' 4 - import {pdsAgent} from '#/state/session/agent' 5 6 export function useConfirmEmail({ 7 onSuccess, ··· 16 throw new Error('No email found for the current account') 17 } 18 19 - await pdsAgent(agent).com.atproto.server.confirmEmail({ 20 email: currentAccount.email.trim(), 21 token: token.trim(), 22 })
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent, useSession} from '#/state/session' 4 5 export function useConfirmEmail({ 6 onSuccess, ··· 15 throw new Error('No email found for the current account') 16 } 17 18 + await agent.com.atproto.server.confirmEmail({ 19 email: currentAccount.email.trim(), 20 token: token.trim(), 21 })
+1 -2
src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent, useSession} from '#/state/session' 4 - import {pdsAgent} from '#/state/session/agent' 5 6 export function useManageEmail2FA() { 7 const agent = useAgent() ··· 18 throw new Error('No email found for the current account') 19 } 20 21 - await pdsAgent(agent).com.atproto.server.updateEmail({ 22 email: currentAccount.email, 23 emailAuthFactor: enabled, 24 token,
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent, useSession} from '#/state/session' 4 5 export function useManageEmail2FA() { 6 const agent = useAgent() ··· 17 throw new Error('No email found for the current account') 18 } 19 20 + await agent.com.atproto.server.updateEmail({ 21 email: currentAccount.email, 22 emailAuthFactor: enabled, 23 token,
+1 -3
src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 - import {pdsAgent} from '#/state/session/agent' 5 6 export function useRequestEmailUpdate() { 7 const agent = useAgent() 8 9 return useMutation({ 10 mutationFn: async () => { 11 - return (await pdsAgent(agent).com.atproto.server.requestEmailUpdate()) 12 - .data 13 }, 14 }) 15 }
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 5 export function useRequestEmailUpdate() { 6 const agent = useAgent() 7 8 return useMutation({ 9 mutationFn: async () => { 10 + return (await agent.com.atproto.server.requestEmailUpdate()).data 11 }, 12 }) 13 }
+1 -2
src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 - import {pdsAgent} from '#/state/session/agent' 5 6 export function useRequestEmailVerification() { 7 const agent = useAgent() 8 9 return useMutation({ 10 mutationFn: async () => { 11 - await pdsAgent(agent).com.atproto.server.requestEmailConfirmation() 12 }, 13 }) 14 }
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 5 export function useRequestEmailVerification() { 6 const agent = useAgent() 7 8 return useMutation({ 9 mutationFn: async () => { 10 + await agent.com.atproto.server.requestEmailConfirmation() 11 }, 12 }) 13 }
+1 -5
src/components/dialogs/EmailDialog/data/useUpdateEmail.ts
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 - import {pdsAgent} from '#/state/session/agent' 5 import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate' 6 7 async function updateEmailAndRefreshSession( ··· 9 email: string, 10 token?: string, 11 ) { 12 - await pdsAgent(agent).com.atproto.server.updateEmail({ 13 - email: email.trim(), 14 - token, 15 - }) 16 await agent.resumeSession(agent.session!) 17 } 18
··· 1 import {useMutation} from '@tanstack/react-query' 2 3 import {useAgent} from '#/state/session' 4 import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate' 5 6 async function updateEmailAndRefreshSession( ··· 8 email: string, 9 token?: string, 10 ) { 11 + await agent.com.atproto.server.updateEmail({email: email.trim(), token}) 12 await agent.resumeSession(agent.session!) 13 } 14
+1 -2
src/components/intents/VerifyEmailIntentDialog.tsx
··· 4 import {useLingui} from '@lingui/react' 5 6 import {useAgent, useSession} from '#/state/session' 7 - import {pdsAgent} from '#/state/session/agent' 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 import * as Dialog from '#/components/Dialog' ··· 52 53 const onPressResendEmail = async () => { 54 setSending(true) 55 - await pdsAgent(agent).com.atproto.server.requestEmailConfirmation() 56 setSending(false) 57 setStatus('resent') 58 }
··· 4 import {useLingui} from '@lingui/react' 5 6 import {useAgent, useSession} from '#/state/session' 7 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 8 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 9 import * as Dialog from '#/components/Dialog' ··· 51 52 const onPressResendEmail = async () => { 53 setSending(true) 54 + await agent.com.atproto.server.requestEmailConfirmation() 55 setSending(false) 56 setStatus('resent') 57 }
+3 -4
src/components/live/queries.ts
··· 16 import {updateProfileShadow} from '#/state/cache/profile-shadow' 17 import {useLiveNowConfig} from '#/state/service-config' 18 import {useAgent, useSession} from '#/state/session' 19 - import {pdsAgent} from '#/state/session/agent' 20 import * as Toast from '#/view/com/util/Toast' 21 import {useDialogContext} from '#/components/Dialog' 22 import {getLiveServiceNames} from '#/components/live/utils' ··· 110 const repo = currentAccount.did 111 const collection = 'app.bsky.actor.status' 112 113 - const existing = await pdsAgent(agent) 114 - .com.atproto.repo.getRecord({repo, collection, rkey: 'self'}) 115 .catch(_e => undefined) 116 117 - await pdsAgent(agent).com.atproto.repo.putRecord({ 118 repo, 119 collection, 120 rkey: 'self',
··· 16 import {updateProfileShadow} from '#/state/cache/profile-shadow' 17 import {useLiveNowConfig} from '#/state/service-config' 18 import {useAgent, useSession} from '#/state/session' 19 import * as Toast from '#/view/com/util/Toast' 20 import {useDialogContext} from '#/components/Dialog' 21 import {getLiveServiceNames} from '#/components/live/utils' ··· 109 const repo = currentAccount.did 110 const collection = 'app.bsky.actor.status' 111 112 + const existing = await agent.com.atproto.repo 113 + .getRecord({repo, collection, rkey: 'self'}) 114 .catch(_e => undefined) 115 116 + await agent.com.atproto.repo.putRecord({ 117 repo, 118 collection, 119 rkey: 'self',
-10
src/env/common.ts
··· 118 * URLs for the live-event config web worker. Can be a 119 * locally running server, see `env.example` for more. 120 */ 121 - export const LIVE_EVENTS_DEV_URL = process.env.LIVE_EVENTS_DEV_URL 122 - export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app` 123 - export const LIVE_EVENTS_URL = IS_DEV 124 - ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL) 125 - : LIVE_EVENTS_PROD_URL 126 - 127 - export const ENV_PUBLIC_BSKY_SERVICE: string | undefined = 128 - process.env.EXPO_PUBLIC_PUBLIC_BSKY_SERVICE 129 - export const ENV_APPVIEW_DID_PROXY: `did:${string}:${string}#bsky_appview` | undefined = 130 - process.env.EXPO_PUBLIC_APPVIEW_DID_PROXY
··· 118 * URLs for the live-event config web worker. Can be a 119 * locally running server, see `env.example` for more. 120 */
+2 -3
src/lib/api/feed/custom.ts
··· 5 jsonStringToLex, 6 } from '@atproto/api' 7 8 - import {PUBLIC_BSKY_SERVICE} from '#/lib/constants' 9 import { 10 getAppLanguageAsContentLanguage, 11 getContentLanguages, ··· 121 122 // manually construct fetch call so we can add the `lang` cache-busting param 123 let res = await fetch( 124 - `${PUBLIC_BSKY_SERVICE}/xrpc/app.bsky.feed.getFeed?feed=${feed}${ 125 cursor ? `&cursor=${cursor}` : '' 126 }&limit=${limit}&lang=${contentLangs}`, 127 { ··· 141 142 // no data, try again with language headers removed 143 res = await fetch( 144 - `${PUBLIC_BSKY_SERVICE}/xrpc/app.bsky.feed.getFeed?feed=${feed}${ 145 cursor ? `&cursor=${cursor}` : '' 146 }&limit=${limit}`, 147 {method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}},
··· 5 jsonStringToLex, 6 } from '@atproto/api' 7 8 import { 9 getAppLanguageAsContentLanguage, 10 getContentLanguages, ··· 120 121 // manually construct fetch call so we can add the `lang` cache-busting param 122 let res = await fetch( 123 + `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${ 124 cursor ? `&cursor=${cursor}` : '' 125 }&limit=${limit}&lang=${contentLangs}`, 126 { ··· 140 141 // no data, try again with language headers removed 142 res = await fetch( 143 + `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${ 144 cursor ? `&cursor=${cursor}` : '' 145 }&limit=${limit}`, 146 {method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}},
+12 -5
src/lib/api/index.ts
··· 23 import * as Hasher from 'multiformats/hashes/hasher' 24 25 import {isNetworkError} from '#/lib/strings/errors' 26 - import {parseMarkdownLinks,shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' 27 import {logger} from '#/logger' 28 import {compressImage} from '#/state/gallery' 29 import { ··· 34 createThreadgateRecord, 35 threadgateAllowUISettingToAllowRecordValue, 36 } from '#/state/queries/threadgate' 37 - import {pdsAgent} from '#/state/session/agent' 38 import { 39 type EmbedDraft, 40 type PostDraft, ··· 173 } 174 175 try { 176 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 177 repo: agent.assertDid, 178 writes: writes, 179 validate: true, ··· 370 371 const width = Math.round( 372 videoDraft.asset?.width || 373 - ('redraftDimensions' in videoDraft ? videoDraft.redraftDimensions.width : 1000) 374 ) 375 const height = Math.round( 376 videoDraft.asset?.height || 377 - ('redraftDimensions' in videoDraft ? videoDraft.redraftDimensions.height : 1000) 378 ) 379 380 // aspect ratio values must be >0 - better to leave as unset otherwise
··· 23 import * as Hasher from 'multiformats/hashes/hasher' 24 25 import {isNetworkError} from '#/lib/strings/errors' 26 + import { 27 + parseMarkdownLinks, 28 + shortenLinks, 29 + stripInvalidMentions, 30 + } from '#/lib/strings/rich-text-manip' 31 import {logger} from '#/logger' 32 import {compressImage} from '#/state/gallery' 33 import { ··· 38 createThreadgateRecord, 39 threadgateAllowUISettingToAllowRecordValue, 40 } from '#/state/queries/threadgate' 41 import { 42 type EmbedDraft, 43 type PostDraft, ··· 176 } 177 178 try { 179 + await agent.com.atproto.repo.applyWrites({ 180 repo: agent.assertDid, 181 writes: writes, 182 validate: true, ··· 373 374 const width = Math.round( 375 videoDraft.asset?.width || 376 + ('redraftDimensions' in videoDraft 377 + ? videoDraft.redraftDimensions.width 378 + : 1000), 379 ) 380 const height = Math.round( 381 videoDraft.asset?.height || 382 + ('redraftDimensions' in videoDraft 383 + ? videoDraft.redraftDimensions.height 384 + : 1000), 385 ) 386 387 // aspect ratio values must be >0 - better to leave as unset otherwise
+2 -4
src/lib/constants.ts
··· 3 4 import {type ProxyHeaderValue} from '#/state/session/agent' 5 import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' 6 - import {ENV_APPVIEW_DID_PROXY, ENV_PUBLIC_BSKY_SERVICE} from '#/env' 7 export const LOCAL_DEV_SERVICE = 8 Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' 9 export const STAGING_SERVICE = 'https://staging.bsky.dev' 10 export const BSKY_SERVICE = 'https://bsky.social' 11 export const BSKY_SERVICE_DID = 'did:web:bsky.social' 12 - export const PUBLIC_BSKY_SERVICE = 13 - ENV_PUBLIC_BSKY_SERVICE || 'https://public.api.bsky.app' 14 export const DEFAULT_SERVICE = BSKY_SERVICE 15 export const HELP_DESK_URL = `https://tangled.org/jollywhoppers.com/witchsky.app/` 16 export const EMBED_SERVICE = 'https://embed.bsky.app' 17 export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` 18 export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' 19 - export const APPVIEW_DID_PROXY = ENV_APPVIEW_DID_PROXY 20 export const STARTER_PACK_MAX_SIZE = 150 21 export const CARD_ASPECT_RATIO = 1200 / 630 22
··· 3 4 import {type ProxyHeaderValue} from '#/state/session/agent' 5 import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' 6 + 7 export const LOCAL_DEV_SERVICE = 8 Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' 9 export const STAGING_SERVICE = 'https://staging.bsky.dev' 10 export const BSKY_SERVICE = 'https://bsky.social' 11 export const BSKY_SERVICE_DID = 'did:web:bsky.social' 12 + export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app' 13 export const DEFAULT_SERVICE = BSKY_SERVICE 14 export const HELP_DESK_URL = `https://tangled.org/jollywhoppers.com/witchsky.app/` 15 export const EMBED_SERVICE = 'https://embed.bsky.app' 16 export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` 17 export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' 18 export const STARTER_PACK_MAX_SIZE = 150 19 export const CARD_ASPECT_RATIO = 1200 / 630 20
+1 -2
src/lib/generate-starterpack.ts
··· 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 import {enforceLen} from '#/lib/strings/helpers' 17 import {useAgent} from '#/state/session' 18 - import {pdsAgent} from '#/state/session/agent' 19 import type * as bsky from '#/types/bsky' 20 21 export const createStarterPackList = async ({ ··· 45 }, 46 ) 47 if (!list) throw new Error('List creation failed') 48 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 49 repo: agent.session!.did, 50 writes: profiles.map(p => createListItem({did: p.did, listUri: list.uri})), 51 })
··· 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 import {enforceLen} from '#/lib/strings/helpers' 17 import {useAgent} from '#/state/session' 18 import type * as bsky from '#/types/bsky' 19 20 export const createStarterPackList = async ({ ··· 44 }, 45 ) 46 if (!list) throw new Error('List creation failed') 47 + await agent.com.atproto.repo.applyWrites({ 48 repo: agent.session!.did, 49 writes: profiles.map(p => createListItem({did: p.did, listUri: list.uri})), 50 })
+1 -4
src/lib/media/video/upload.shared.ts
··· 5 import {VIDEO_SERVICE_DID} from '#/lib/constants' 6 import {UploadLimitError} from '#/lib/media/video/errors' 7 import {getServiceAuthAudFromUrl} from '#/lib/strings/url-helpers' 8 - import {pdsAgent} from '#/state/session/agent' 9 import {createVideoAgent} from './util' 10 11 export async function getServiceAuthToken({ ··· 23 if (!pdsAud) { 24 throw new Error('Agent does not have a PDS URL') 25 } 26 - const {data: serviceAuth} = await pdsAgent( 27 - agent, 28 - ).com.atproto.server.getServiceAuth({ 29 aud: aud ?? pdsAud, 30 lxm, 31 exp,
··· 5 import {VIDEO_SERVICE_DID} from '#/lib/constants' 6 import {UploadLimitError} from '#/lib/media/video/errors' 7 import {getServiceAuthAudFromUrl} from '#/lib/strings/url-helpers' 8 import {createVideoAgent} from './util' 9 10 export async function getServiceAuthToken({ ··· 22 if (!pdsAud) { 23 throw new Error('Agent does not have a PDS URL') 24 } 25 + const {data: serviceAuth} = await agent.com.atproto.server.getServiceAuth({ 26 aud: aud ?? pdsAud, 27 lxm, 28 exp,
+1 -2
src/lib/react-query.tsx
··· 11 12 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 13 import {IS_NATIVE, IS_WEB} from '#/env' 14 - import {PUBLIC_BSKY_SERVICE} from './constants' 15 16 declare global { 17 interface Window { ··· 29 setTimeout(() => { 30 controller.abort() 31 }, 15e3) 32 - const res = await fetch(`${PUBLIC_BSKY_SERVICE}/xrpc/_health`, { 33 cache: 'no-store', 34 signal: controller.signal, 35 })
··· 11 12 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 13 import {IS_NATIVE, IS_WEB} from '#/env' 14 15 declare global { 16 interface Window { ··· 28 setTimeout(() => { 29 controller.abort() 30 }, 15e3) 31 + const res = await fetch('https://public.api.bsky.app/xrpc/_health', { 32 cache: 'no-store', 33 signal: controller.signal, 34 })
+1 -2
src/screens/Deactivated.tsx
··· 13 useSession, 14 useSessionApi, 15 } from '#/state/session' 16 - import {pdsAgent} from '#/state/session/agent' 17 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 18 import {Logo} from '#/view/icons/Logo' 19 import {atoms as a, useTheme} from '#/alf' ··· 70 const handleActivate = React.useCallback(async () => { 71 try { 72 setPending(true) 73 - await pdsAgent(agent).com.atproto.server.activateAccount() 74 await queryClient.resetQueries() 75 await agent.resumeSession(agent.session!) 76 } catch (e: any) {
··· 13 useSession, 14 useSessionApi, 15 } from '#/state/session' 16 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 17 import {Logo} from '#/view/icons/Logo' 18 import {atoms as a, useTheme} from '#/alf' ··· 69 const handleActivate = React.useCallback(async () => { 70 try { 71 setPending(true) 72 + await agent.com.atproto.server.activateAccount() 73 await queryClient.resetQueries() 74 await agent.resumeSession(agent.session!) 75 } catch (e: any) {
+1 -2
src/screens/Onboarding/util.ts
··· 9 import chunk from 'lodash.chunk' 10 11 import {until} from '#/lib/async/until' 12 - import {pdsAgent} from '#/state/session/agent' 13 14 export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) { 15 const session = agent.session ··· 36 37 const chunks = chunk(followWrites, 50) 38 for (const chunk of chunks) { 39 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 40 repo: session.did, 41 writes: chunk, 42 })
··· 9 import chunk from 'lodash.chunk' 10 11 import {until} from '#/lib/async/until' 12 13 export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) { 14 const session = agent.session ··· 35 36 const chunks = chunk(followWrites, 50) 37 for (const chunk of chunks) { 38 + await agent.com.atproto.repo.applyWrites({ 39 repo: session.did, 40 writes: chunk, 41 })
+1 -157
src/screens/Settings/DeerSettings.tsx
··· 1 import {useState} from 'react' 2 import {View} from 'react-native' 3 - import {isDid} from '@atproto/api' 4 import {type ProfileViewBasic} from '@atproto/api/dist/client/types/app/bsky/actor/defs' 5 import {msg, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 8 9 - import {APPVIEW_DID_PROXY} from '#/lib/constants' 10 import {usePalette} from '#/lib/hooks/usePalette' 11 import {type CommonNavigatorParams} from '#/lib/routes/types' 12 import {type Gate} from '#/lib/statsig/gates' ··· 21 useConstellationInstance, 22 useSetConstellationInstance, 23 } from '#/state/preferences/constellation-instance' 24 - import {useCustomAppViewDid} from '#/state/preferences/custom-appview-did' 25 import { 26 useDeerVerificationEnabled, 27 useDeerVerificationTrusted, ··· 113 useShowLinkInHandle, 114 } from '#/state/preferences/show-link-in-handle.tsx' 115 import {useProfilesQuery} from '#/state/queries/profile' 116 - import {findService, useDidDocument} from '#/state/queries/resolve-identity' 117 - import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 118 import * as SettingsList from '#/screens/Settings/components/SettingsList' 119 import {atoms as a, useBreakpoints} from '#/alf' 120 import {Admonition} from '#/components/Admonition' ··· 133 import {Text} from '#/components/Typography' 134 import {IS_WEB} from '#/env' 135 import {SearchProfileCard} from '../Search/components/SearchProfileCard' 136 type Props = NativeStackScreenProps<CommonNavigatorParams> 137 138 const defaultGateValues = { ··· 229 ) 230 } 231 232 - function CustomAppViewDidDialog({ 233 - control, 234 - }: { 235 - control: Dialog.DialogControlProps 236 - }) { 237 - const pal = usePalette('default') 238 - const {_} = useLingui() 239 - 240 - const [did, setDid] = useState('') 241 - const [, setCustomAppViewDid] = useCustomAppViewDid() 242 - 243 - const doc = useDidDocument({did}) 244 - const bskyAppViewService = 245 - doc.data && findService(doc.data, '#bsky_appview', 'BskyAppView') 246 - 247 - const submit = () => { 248 - if (did.length === 0) { 249 - setCustomAppViewDid(undefined) 250 - control.close() 251 - return 252 - } 253 - if (!bskyAppViewService?.serviceEndpoint) return 254 - setCustomAppViewDid(did) 255 - control.close() 256 - } 257 - 258 - return ( 259 - <Dialog.Outer 260 - control={control} 261 - nativeOptions={{preventExpansion: true}} 262 - onClose={() => setDid('')}> 263 - <Dialog.Handle /> 264 - <Dialog.ScrollableInner label={_(msg`Custom AppView Proxy DID`)}> 265 - <View style={[a.gap_sm, a.pb_lg]}> 266 - <Text style={[a.text_2xl, a.font_bold]}> 267 - <Trans>Custom AppView Proxy DID</Trans> 268 - </Text> 269 - </View> 270 - 271 - <View style={a.gap_lg}> 272 - <Dialog.Input 273 - label="Text input field" 274 - autoFocus 275 - style={[styles.textInput, pal.border, pal.text]} 276 - onChangeText={value => { 277 - setDid(value) 278 - }} 279 - placeholder={ 280 - APPVIEW_DID_PROXY?.substring(0, APPVIEW_DID_PROXY.indexOf('#')) || 281 - `did:web:api.bsky.app` 282 - } 283 - placeholderTextColor={pal.colors.textLight} 284 - onSubmitEditing={submit} 285 - accessibilityHint={_( 286 - msg`Input the DID of the AppView to proxy requests through`, 287 - )} 288 - isInvalid={ 289 - !!did && !bskyAppViewService?.serviceEndpoint && !doc.isLoading 290 - } 291 - /> 292 - 293 - {did && !isDid(did) && ( 294 - <View> 295 - <ErrorMessage message={_(msg`must enter a DID`)} /> 296 - </View> 297 - )} 298 - 299 - {did && (did.includes('#') || did.includes('?')) && ( 300 - <View> 301 - <ErrorMessage message={_(msg`don't include the service id`)} /> 302 - </View> 303 - )} 304 - 305 - {doc.isError && ( 306 - <View> 307 - <ErrorMessage 308 - message={ 309 - doc.error.message || _(msg`document resolution failure`) 310 - } 311 - /> 312 - </View> 313 - )} 314 - 315 - {doc.data && 316 - !bskyAppViewService && 317 - (doc.data as {message?: string}).message && ( 318 - <View> 319 - <ErrorMessage 320 - message={(doc.data as {message: string}).message} 321 - /> 322 - </View> 323 - )} 324 - 325 - {doc.data && !bskyAppViewService && ( 326 - <View> 327 - <ErrorMessage 328 - message={_(msg`document doesn't contain #bsky_appview service`)} 329 - /> 330 - </View> 331 - )} 332 - 333 - {bskyAppViewService && ( 334 - <Text style={[a.text_sm, a.leading_snug]}> 335 - {JSON.stringify(bskyAppViewService, null, 2)} 336 - </Text> 337 - )} 338 - 339 - <View style={IS_WEB && [a.flex_row, a.justify_end]}> 340 - <Button 341 - label={_(msg`Save`)} 342 - size="large" 343 - onPress={submit} 344 - variant="solid" 345 - color={did.length > 0 ? 'primary' : 'secondary'} 346 - disabled={ 347 - did.length !== 0 && !bskyAppViewService?.serviceEndpoint 348 - }> 349 - <ButtonText> 350 - {did.length > 0 ? <Trans>Save</Trans> : <Trans>Reset</Trans>} 351 - </ButtonText> 352 - </Button> 353 - </View> 354 - </View> 355 - 356 - <Dialog.Close /> 357 - </Dialog.ScrollableInner> 358 - </Dialog.Outer> 359 - ) 360 - } 361 - 362 function TrustedVerifiersDialog({ 363 control, 364 }: { ··· 498 [gate]: value, 499 }) 500 } 501 - const [customAppViewDid] = useCustomAppViewDid() 502 - const setCustomAppViewDidControl = Dialog.useDialogControl() 503 504 return ( 505 <Layout.Screen> ··· 640 Constellation is used to supplement AppView responses for custom 641 verifications and nuclear block bypass, via backlinks. Current 642 instance: {constellationInstance} 643 - </Trans> 644 - </Admonition> 645 - </SettingsList.Item> 646 - 647 - <SettingsList.Item> 648 - <SettingsList.ItemIcon icon={StarIcon} /> 649 - <SettingsList.ItemText> 650 - <Trans>{`Custom AppView DID`}</Trans> 651 - </SettingsList.ItemText> 652 - <SettingsList.BadgeButton 653 - label={customAppViewDid ? _(msg`Set`) : _(msg`Change`)} 654 - onPress={() => setCustomAppViewDidControl.open()} 655 - /> 656 - </SettingsList.Item> 657 - <SettingsList.Item> 658 - <Admonition type="info" style={[a.flex_1]}> 659 - <Trans> 660 - Restart app after changing your AppView. 661 - {customAppViewDid && _(` Currently ${customAppViewDid}`)} 662 </Trans> 663 </Admonition> 664 </SettingsList.Item> ··· 1003 </SettingsList.Container> 1004 </Layout.Content> 1005 <ConstellationInstanceDialog control={setConstellationInstanceControl} /> 1006 - <CustomAppViewDidDialog control={setCustomAppViewDidControl} /> 1007 <TrustedVerifiersDialog control={setTrustedVerifiersDialogControl} /> 1008 </Layout.Screen> 1009 )
··· 1 import {useState} from 'react' 2 import {View} from 'react-native' 3 import {type ProfileViewBasic} from '@atproto/api/dist/client/types/app/bsky/actor/defs' 4 import {msg, Trans} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 7 8 import {usePalette} from '#/lib/hooks/usePalette' 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 import {type Gate} from '#/lib/statsig/gates' ··· 19 useConstellationInstance, 20 useSetConstellationInstance, 21 } from '#/state/preferences/constellation-instance' 22 import { 23 useDeerVerificationEnabled, 24 useDeerVerificationTrusted, ··· 110 useShowLinkInHandle, 111 } from '#/state/preferences/show-link-in-handle.tsx' 112 import {useProfilesQuery} from '#/state/queries/profile' 113 import * as SettingsList from '#/screens/Settings/components/SettingsList' 114 import {atoms as a, useBreakpoints} from '#/alf' 115 import {Admonition} from '#/components/Admonition' ··· 128 import {Text} from '#/components/Typography' 129 import {IS_WEB} from '#/env' 130 import {SearchProfileCard} from '../Search/components/SearchProfileCard' 131 + 132 type Props = NativeStackScreenProps<CommonNavigatorParams> 133 134 const defaultGateValues = { ··· 225 ) 226 } 227 228 function TrustedVerifiersDialog({ 229 control, 230 }: { ··· 364 [gate]: value, 365 }) 366 } 367 368 return ( 369 <Layout.Screen> ··· 504 Constellation is used to supplement AppView responses for custom 505 verifications and nuclear block bypass, via backlinks. Current 506 instance: {constellationInstance} 507 </Trans> 508 </Admonition> 509 </SettingsList.Item> ··· 848 </SettingsList.Container> 849 </Layout.Content> 850 <ConstellationInstanceDialog control={setConstellationInstanceControl} /> 851 <TrustedVerifiersDialog control={setTrustedVerifiersDialogControl} /> 852 </Layout.Screen> 853 )
+2 -3
src/screens/Settings/components/ChangePasswordDialog.tsx
··· 8 import {checkAndFormatResetCode} from '#/lib/strings/password' 9 import {logger} from '#/logger' 10 import {useAgent, useSession} from '#/state/session' 11 - import {pdsAgent} from '#/state/session/agent' 12 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 13 import {android, atoms as a, web} from '#/alf' 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 85 setError('') 86 setIsProcessing(true) 87 try { 88 - await pdsAgent(agent).com.atproto.server.requestPasswordReset({ 89 email: currentAccount.email, 90 }) 91 setStage(Stages.ChangePassword) ··· 129 setError('') 130 setIsProcessing(true) 131 try { 132 - await pdsAgent(agent).com.atproto.server.resetPassword({ 133 token: formattedCode, 134 password: newPassword, 135 })
··· 8 import {checkAndFormatResetCode} from '#/lib/strings/password' 9 import {logger} from '#/logger' 10 import {useAgent, useSession} from '#/state/session' 11 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 12 import {android, atoms as a, web} from '#/alf' 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 84 setError('') 85 setIsProcessing(true) 86 try { 87 + await agent.com.atproto.server.requestPasswordReset({ 88 email: currentAccount.email, 89 }) 90 setStage(Stages.ChangePassword) ··· 128 setError('') 129 setIsProcessing(true) 130 try { 131 + await agent.com.atproto.server.resetPassword({ 132 token: formattedCode, 133 password: newPassword, 134 })
+1 -2
src/screens/Settings/components/DeactivateAccountDialog.tsx
··· 5 6 import {logger} from '#/logger' 7 import {useAgent, useSessionApi} from '#/state/session' 8 - import {pdsAgent} from '#/state/session/agent' 9 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 10 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 import {type DialogOuterProps} from '#/components/Dialog' ··· 43 const handleDeactivate = React.useCallback(async () => { 44 try { 45 setPending(true) 46 - await pdsAgent(agent).com.atproto.server.deactivateAccount({}) 47 control.close(() => { 48 logoutCurrentAccount('Deactivated') 49 })
··· 5 6 import {logger} from '#/logger' 7 import {useAgent, useSessionApi} from '#/state/session' 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 import {type DialogOuterProps} from '#/components/Dialog' ··· 42 const handleDeactivate = React.useCallback(async () => { 43 try { 44 setPending(true) 45 + await agent.com.atproto.server.deactivateAccount({}) 46 control.close(() => { 47 logoutCurrentAccount('Deactivated') 48 })
+3 -4
src/screens/Settings/components/DisableEmail2FADialog.tsx
··· 5 6 import {cleanError} from '#/lib/strings/errors' 7 import {useAgent, useSession} from '#/state/session' 8 - import {pdsAgent} from '#/state/session/agent' 9 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 10 import * as Toast from '#/view/com/util/Toast' 11 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 42 setError('') 43 setIsProcessing(true) 44 try { 45 - await pdsAgent(agent).com.atproto.server.requestEmailUpdate() 46 setStage(Stages.ConfirmCode) 47 } catch (e) { 48 setError(cleanError(String(e))) ··· 56 setIsProcessing(true) 57 try { 58 if (currentAccount?.email) { 59 - await pdsAgent(agent).com.atproto.server.updateEmail({ 60 - email: currentAccount!.email, 61 token: confirmationCode.trim(), 62 emailAuthFactor: false, 63 })
··· 5 6 import {cleanError} from '#/lib/strings/errors' 7 import {useAgent, useSession} from '#/state/session' 8 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 9 import * as Toast from '#/view/com/util/Toast' 10 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 41 setError('') 42 setIsProcessing(true) 43 try { 44 + await agent.com.atproto.server.requestEmailUpdate() 45 setStage(Stages.ConfirmCode) 46 } catch (e) { 47 setError(cleanError(String(e))) ··· 55 setIsProcessing(true) 56 try { 57 if (currentAccount?.email) { 58 + await agent.com.atproto.server.updateEmail({ 59 + email: currentAccount.email, 60 token: confirmationCode.trim(), 61 emailAuthFactor: false, 62 })
+1 -2
src/screens/Settings/components/ExportCarDialog.tsx
··· 6 import {saveBytesToDisk} from '#/lib/media/manip' 7 import {logger} from '#/logger' 8 import {useAgent} from '#/state/session' 9 - import {pdsAgent} from '#/state/session/agent' 10 import {atoms as a, useTheme, web} from '#/alf' 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 12 import * as Dialog from '#/components/Dialog' ··· 33 try { 34 setLoading(true) 35 const did = agent.session.did 36 - const downloadRes = await pdsAgent(agent).com.atproto.sync.getRepo({did}) 37 const saveRes = await saveBytesToDisk( 38 'repo.car', 39 downloadRes.data,
··· 6 import {saveBytesToDisk} from '#/lib/media/manip' 7 import {logger} from '#/logger' 8 import {useAgent} from '#/state/session' 9 import {atoms as a, useTheme, web} from '#/alf' 10 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 import * as Dialog from '#/components/Dialog' ··· 32 try { 33 setLoading(true) 34 const did = agent.session.did 35 + const downloadRes = await agent.com.atproto.sync.getRepo({did}) 36 const saveRes = await saveBytesToDisk( 37 'repo.car', 38 downloadRes.data,
+1 -2
src/screens/SignupQueued.tsx
··· 7 8 import {logger} from '#/logger' 9 import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 10 - import {pdsAgent} from '#/state/session/agent' 11 import {useOnboardingDispatch} from '#/state/shell' 12 import {Logo} from '#/view/icons/Logo' 13 import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' ··· 38 const checkStatus = React.useCallback(async () => { 39 setProcessing(true) 40 try { 41 - const res = await pdsAgent(agent).com.atproto.temp.checkSignupQueue() 42 if (res.data.activated) { 43 // ready to go, exchange the access token for a usable one and kick off onboarding 44 await agent.sessionManager.refreshSession()
··· 7 8 import {logger} from '#/logger' 9 import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 10 import {useOnboardingDispatch} from '#/state/shell' 11 import {Logo} from '#/view/icons/Logo' 12 import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' ··· 37 const checkStatus = React.useCallback(async () => { 38 setProcessing(true) 39 try { 40 + const res = await agent.com.atproto.temp.checkSignupQueue() 41 if (res.data.activated) { 42 // ready to go, exchange the access token for a usable one and kick off onboarding 43 await agent.sessionManager.refreshSession()
-21
src/state/preferences/custom-appview-did.tsx
··· 1 - import {isDid} from '@atproto/api' 2 - 3 - import {device, useStorage} from '#/storage' 4 - 5 - export function useCustomAppViewDid() { 6 - const [customAppViewDid = undefined, setCustomAppViewDid] = useStorage( 7 - device, 8 - ['customAppViewDid'], 9 - ) 10 - 11 - return [customAppViewDid, setCustomAppViewDid] as const 12 - } 13 - 14 - export function readCustomAppViewDidUri() { 15 - const maybeDid = device.get(['customAppViewDid']) 16 - if (!maybeDid || !isDid(maybeDid)) { 17 - return undefined 18 - } 19 - 20 - return `${maybeDid}#bsky_appview` as `did:${string}:${string}#bsky_appview` 21 - }
···
+3 -4
src/state/queries/app-passwords.ts
··· 3 4 import {STALE} from '#/state/queries' 5 import {useAgent} from '../session' 6 - import {pdsAgent} from '../session/agent' 7 8 const RQKEY_ROOT = 'app-passwords' 9 export const RQKEY = () => [RQKEY_ROOT] ··· 14 staleTime: STALE.MINUTES.FIVE, 15 queryKey: RQKEY(), 16 queryFn: async () => { 17 - const res = await pdsAgent(agent).com.atproto.server.listAppPasswords({}) 18 return res.data.passwords 19 }, 20 }) ··· 30 >({ 31 mutationFn: async ({name, privileged}) => { 32 return ( 33 - await pdsAgent(agent).com.atproto.server.createAppPassword({ 34 name, 35 privileged, 36 }) ··· 49 const agent = useAgent() 50 return useMutation<void, Error, {name: string}>({ 51 mutationFn: async ({name}) => { 52 - await pdsAgent(agent).com.atproto.server.revokeAppPassword({ 53 name, 54 }) 55 },
··· 3 4 import {STALE} from '#/state/queries' 5 import {useAgent} from '../session' 6 7 const RQKEY_ROOT = 'app-passwords' 8 export const RQKEY = () => [RQKEY_ROOT] ··· 13 staleTime: STALE.MINUTES.FIVE, 14 queryKey: RQKEY(), 15 queryFn: async () => { 16 + const res = await agent.com.atproto.server.listAppPasswords({}) 17 return res.data.passwords 18 }, 19 }) ··· 29 >({ 30 mutationFn: async ({name, privileged}) => { 31 return ( 32 + await agent.com.atproto.server.createAppPassword({ 33 name, 34 privileged, 35 }) ··· 48 const agent = useAgent() 49 return useMutation<void, Error, {name: string}>({ 50 mutationFn: async ({name}) => { 51 + await agent.com.atproto.server.revokeAppPassword({ 52 name, 53 }) 54 },
+2 -3
src/state/queries/list.ts
··· 17 import {type ImageMeta} from '#/state/gallery' 18 import {STALE} from '#/state/queries' 19 import {useAgent, useSession} from '#/state/session' 20 - import {pdsAgent} from '#/state/session/agent' 21 import {invalidate as invalidateMyLists} from './my-lists' 22 import {RQKEY as PROFILE_LISTS_RQKEY} from './profile-lists' 23 ··· 153 record.avatar = undefined 154 } 155 const res = ( 156 - await pdsAgent(agent).com.atproto.repo.putRecord({ 157 repo: currentAccount.did, 158 collection: 'app.bsky.graph.list', 159 rkey, ··· 232 233 // apply in chunks 234 for (const writesChunk of chunk(writes, 10)) { 235 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 236 repo: currentAccount.did, 237 writes: writesChunk, 238 })
··· 17 import {type ImageMeta} from '#/state/gallery' 18 import {STALE} from '#/state/queries' 19 import {useAgent, useSession} from '#/state/session' 20 import {invalidate as invalidateMyLists} from './my-lists' 21 import {RQKEY as PROFILE_LISTS_RQKEY} from './profile-lists' 22 ··· 152 record.avatar = undefined 153 } 154 const res = ( 155 + await agent.com.atproto.repo.putRecord({ 156 repo: currentAccount.did, 157 collection: 'app.bsky.graph.list', 158 rkey, ··· 231 232 // apply in chunks 233 for (const writesChunk of chunk(writes, 10)) { 234 + await agent.com.atproto.repo.applyWrites({ 235 repo: currentAccount.did, 236 writes: writesChunk, 237 })
+2 -3
src/state/queries/messages/actor-declaration.ts
··· 3 4 import {logger} from '#/logger' 5 import {useAgent, useSession} from '#/state/session' 6 - import {pdsAgent} from '#/state/session/agent' 7 import {RQKEY as PROFILE_RKEY} from '../profile' 8 9 export function useUpdateActorDeclaration({ ··· 20 return useMutation({ 21 mutationFn: async (allowIncoming: 'all' | 'none' | 'following') => { 22 if (!currentAccount) throw new Error('Not signed in') 23 - const result = await pdsAgent(agent).com.atproto.repo.putRecord({ 24 repo: currentAccount.did, 25 collection: 'chat.bsky.actor.declaration', 26 rkey: 'self', ··· 70 return useMutation({ 71 mutationFn: async () => { 72 if (!currentAccount) throw new Error('Not signed in') 73 - const result = await pdsAgent(agent).com.atproto.repo.deleteRecord({ 74 repo: currentAccount.did, 75 collection: 'chat.bsky.actor.declaration', 76 rkey: 'self',
··· 3 4 import {logger} from '#/logger' 5 import {useAgent, useSession} from '#/state/session' 6 import {RQKEY as PROFILE_RKEY} from '../profile' 7 8 export function useUpdateActorDeclaration({ ··· 19 return useMutation({ 20 mutationFn: async (allowIncoming: 'all' | 'none' | 'following') => { 21 if (!currentAccount) throw new Error('Not signed in') 22 + const result = await agent.com.atproto.repo.putRecord({ 23 repo: currentAccount.did, 24 collection: 'chat.bsky.actor.declaration', 25 rkey: 'self', ··· 69 return useMutation({ 70 mutationFn: async () => { 71 if (!currentAccount) throw new Error('Not signed in') 72 + const result = await agent.api.com.atproto.repo.deleteRecord({ 73 repo: currentAccount.did, 74 collection: 'chat.bsky.actor.declaration', 75 rkey: 'self',
+1 -2
src/state/queries/postgate/index.ts
··· 21 POSTGATE_COLLECTION, 22 } from '#/state/queries/postgate/util' 23 import {useAgent} from '#/state/session' 24 - import {pdsAgent} from '#/state/session/agent' 25 import * as bsky from '#/types/bsky' 26 27 export async function getPostgateRecord({ ··· 97 const postUrip = new AtUri(postUri) 98 99 await networkRetry(2, () => 100 - pdsAgent(agent).com.atproto.repo.putRecord({ 101 repo: agent.session!.did, 102 collection: POSTGATE_COLLECTION, 103 rkey: postUrip.rkey,
··· 21 POSTGATE_COLLECTION, 22 } from '#/state/queries/postgate/util' 23 import {useAgent} from '#/state/session' 24 import * as bsky from '#/types/bsky' 25 26 export async function getPostgateRecord({ ··· 96 const postUrip = new AtUri(postUri) 97 98 await networkRetry(2, () => 99 + agent.api.com.atproto.repo.putRecord({ 100 repo: agent.session!.did, 101 collection: POSTGATE_COLLECTION, 102 rkey: postUrip.rkey,
+3 -4
src/state/queries/preferences/index.ts
··· 20 type ThreadViewPreferences, 21 type UsePreferencesQueryResponse, 22 } from '#/state/queries/preferences/types' 23 - import {useBlankPrefAuthedAgent as useAgent} from '#/state/session' 24 - import {pdsAgent} from '#/state/session/agent' 25 import {saveLabelers} from '#/state/session/agent-config' 26 import {useAgeAssurance} from '#/ageAssurance' 27 import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' ··· 46 if (!agent.did) { 47 return DEFAULT_LOGGED_OUT_PREFERENCES 48 } else { 49 - const res = await pdsAgent(agent).getPreferences() 50 51 // save to local storage to ensure there are labels on initial requests 52 saveLabelers( ··· 101 102 return useMutation({ 103 mutationFn: async () => { 104 - await pdsAgent(agent).app.bsky.actor.putPreferences({preferences: []}) 105 // triggers a refetch 106 await queryClient.invalidateQueries({ 107 queryKey: preferencesQueryKey,
··· 20 type ThreadViewPreferences, 21 type UsePreferencesQueryResponse, 22 } from '#/state/queries/preferences/types' 23 + import {useAgent} from '#/state/session' 24 import {saveLabelers} from '#/state/session/agent-config' 25 import {useAgeAssurance} from '#/ageAssurance' 26 import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' ··· 45 if (!agent.did) { 46 return DEFAULT_LOGGED_OUT_PREFERENCES 47 } else { 48 + const res = await agent.getPreferences() 49 50 // save to local storage to ensure there are labels on initial requests 51 saveLabelers( ··· 100 101 return useMutation({ 102 mutationFn: async () => { 103 + await agent.app.bsky.actor.putPreferences({preferences: []}) 104 // triggers a refetch 105 await queryClient.invalidateQueries({ 106 queryKey: preferencesQueryKey,
+15 -57
src/state/queries/resolve-identity.ts
··· 1 - import {type Did, isDid} from '@atproto/api' 2 - import {useQuery} from '@tanstack/react-query' 3 - 4 - import {STALE} from '.' 5 import {LRU} from './direct-fetch-record' 6 - const RQKEY_ROOT = 'resolve-identity' 7 - export const RQKEY = (did: string) => [RQKEY_ROOT, did] 8 9 - // this isn't trusted... 10 - export type DidDocument = { 11 - '@context'?: string[] 12 - id?: string 13 - alsoKnownAs?: string[] 14 - verificationMethod?: VerificationMethod[] 15 - service?: Service[] 16 - } 17 - 18 - export type VerificationMethod = { 19 - id?: string 20 - type?: string 21 - controller?: string 22 - publicKeyMultibase?: string 23 - } 24 - 25 - export type Service = { 26 - id?: string 27 - type?: string 28 - serviceEndpoint?: string 29 - } 30 31 - const serviceCache = new LRU<Did, DidDocument>() 32 - 33 - export async function resolveDidDocument(did: Did) { 34 return await serviceCache.getOrTryInsertWith(did, async () => { 35 const docUrl = did.startsWith('did:plc:') 36 ? `https://plc.directory/${did}` 37 : `https://${did.substring(8)}/.well-known/did.json` 38 39 - // TODO: we should probably validate this... 40 - return await (await fetch(docUrl)).json() 41 - }) 42 - } 43 - 44 - export function findService(doc: DidDocument, id: string, type?: string) { 45 - // probably not defensive enough, but we don't have atproto/did as a dep... 46 - if (!Array.isArray(doc?.service)) return 47 - return doc.service.find( 48 - s => s?.serviceEndpoint && s?.id === id && (!type || s?.type === type), 49 - ) 50 - } 51 - 52 - export async function resolvePdsServiceUrl(did: Did) { 53 - const doc = await resolveDidDocument(did) 54 - return findService(doc, '#atproto_pds', 'AtprotoPersonalDataServer') 55 - ?.serviceEndpoint 56 - } 57 58 - export function useDidDocument({did}: {did: string}) { 59 - return useQuery<DidDocument | undefined>({ 60 - staleTime: STALE.HOURS.ONE, 61 - queryKey: RQKEY(did || ''), 62 - async queryFn() { 63 - if (!isDid(did)) return undefined 64 - return await resolveDidDocument(did) 65 - }, 66 - enabled: isDid(did) && !(did.includes('#') || did.includes('?')), 67 }) 68 }
··· 1 import {LRU} from './direct-fetch-record' 2 3 + const serviceCache = new LRU<`did:${string}`, string>() 4 5 + export async function resolvePdsServiceUrl(did: `did:${string}`) { 6 return await serviceCache.getOrTryInsertWith(did, async () => { 7 const docUrl = did.startsWith('did:plc:') 8 ? `https://plc.directory/${did}` 9 : `https://${did.substring(8)}/.well-known/did.json` 10 11 + // TODO: validate! 12 + const doc: { 13 + service: { 14 + serviceEndpoint: string 15 + type: string 16 + }[] 17 + } = await (await fetch(docUrl)).json() 18 + const service = doc.service.find( 19 + s => s.type === 'AtprotoPersonalDataServer', 20 + )?.serviceEndpoint 21 22 + if (service === undefined) 23 + throw new Error(`could not find a service for ${did}`) 24 + return service 25 }) 26 }
+3 -4
src/state/queries/starter-packs.ts
··· 27 import {STALE} from '#/state/queries/index' 28 import {invalidateListMembersQuery} from '#/state/queries/list-members' 29 import {useAgent} from '#/state/session' 30 - import {pdsAgent} from '#/state/session/agent' 31 import * as bsky from '#/types/bsky' 32 33 const RQKEY_ROOT = 'starter-pack' ··· 204 if (removedItems.length !== 0) { 205 const chunks = chunk(removedItems, 50) 206 for (const chunk of chunks) { 207 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 208 repo: agent.session!.did, 209 writes: chunk.map(i => ({ 210 $type: 'com.atproto.repo.applyWrites#delete', ··· 221 if (addedProfiles.length > 0) { 222 const chunks = chunk(addedProfiles, 50) 223 for (const chunk of chunks) { 224 - await pdsAgent(agent).com.atproto.repo.applyWrites({ 225 repo: agent.session!.did, 226 writes: chunk.map(p => ({ 227 $type: 'com.atproto.repo.applyWrites#create', ··· 238 } 239 240 const rkey = parseStarterPackUri(currentStarterPack.uri)!.rkey 241 - await pdsAgent(agent).com.atproto.repo.putRecord({ 242 repo: agent.session!.did, 243 collection: 'app.bsky.graph.starterpack', 244 rkey,
··· 27 import {STALE} from '#/state/queries/index' 28 import {invalidateListMembersQuery} from '#/state/queries/list-members' 29 import {useAgent} from '#/state/session' 30 import * as bsky from '#/types/bsky' 31 32 const RQKEY_ROOT = 'starter-pack' ··· 203 if (removedItems.length !== 0) { 204 const chunks = chunk(removedItems, 50) 205 for (const chunk of chunks) { 206 + await agent.com.atproto.repo.applyWrites({ 207 repo: agent.session!.did, 208 writes: chunk.map(i => ({ 209 $type: 'com.atproto.repo.applyWrites#delete', ··· 220 if (addedProfiles.length > 0) { 221 const chunks = chunk(addedProfiles, 50) 222 for (const chunk of chunks) { 223 + await agent.com.atproto.repo.applyWrites({ 224 repo: agent.session!.did, 225 writes: chunk.map(p => ({ 226 $type: 'com.atproto.repo.applyWrites#create', ··· 237 } 238 239 const rkey = parseStarterPackUri(currentStarterPack.uri)!.rkey 240 + await agent.com.atproto.repo.putRecord({ 241 repo: agent.session!.did, 242 collection: 'app.bsky.graph.starterpack', 243 rkey,
+1 -2
src/state/queries/threadgate/index.ts
··· 18 } from '#/state/queries/threadgate/util' 19 import {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread' 20 import {useAgent} from '#/state/session' 21 - import {pdsAgent} from '#/state/session/agent' 22 import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies' 23 import * as bsky from '#/types/bsky' 24 ··· 163 }) 164 165 await networkRetry(2, () => 166 - pdsAgent(agent).com.atproto.repo.putRecord({ 167 repo: agent.session!.did, 168 collection: 'app.bsky.feed.threadgate', 169 rkey: postUrip.rkey,
··· 18 } from '#/state/queries/threadgate/util' 19 import {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread' 20 import {useAgent} from '#/state/session' 21 import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies' 22 import * as bsky from '#/types/bsky' 23 ··· 162 }) 163 164 await networkRetry(2, () => 165 + agent.api.com.atproto.repo.putRecord({ 166 repo: agent.session!.did, 167 collection: 'app.bsky.feed.threadgate', 168 rkey: postUrip.rkey,
+10 -27
src/state/session/agent.ts
··· 15 16 import {networkRetry} from '#/lib/async/retry' 17 import { 18 - APPVIEW_DID_PROXY, 19 BLUESKY_PROXY_HEADER, 20 BSKY_SERVICE, 21 DISCOVER_SAVED_FEED, ··· 34 setCreatedAtForDid, 35 } from '#/ageAssurance/data' 36 import {emitNetworkConfirmed, emitNetworkLost} from '../events' 37 - import {readCustomAppViewDidUri} from '../preferences/custom-appview-did' 38 import {addSessionErrorLog} from './logging' 39 import { 40 configureModerationForAccount, ··· 49 configureModerationForGuest() // Side effect but only relevant for tests 50 51 const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) 52 - const proxyDid = 53 - readCustomAppViewDidUri() || BLUESKY_PROXY_HEADER.get() || APPVIEW_DID_PROXY 54 - agent.configureProxy(proxyDid) 55 return agent 56 } 57 ··· 92 // after session is attached 93 const aa = prefetchAgeAssuranceData({agent}) 94 95 - const proxyDid = 96 - readCustomAppViewDidUri() || BLUESKY_PROXY_HEADER.get() || APPVIEW_DID_PROXY 97 - agent.configureProxy(proxyDid) 98 99 return agent.prepare({ 100 resolvers: [gates, moderation, aa], ··· 133 const moderation = configureModerationForAccount(agent, account) 134 const aa = prefetchAgeAssuranceData({agent}) 135 136 - const proxyDid = 137 - readCustomAppViewDidUri() || BLUESKY_PROXY_HEADER.get() || APPVIEW_DID_PROXY 138 - agent.configureProxy(proxyDid) 139 140 return agent.prepare({ 141 resolvers: [gates, moderation, aa], ··· 242 }), 243 getAge(birthDate) < 18 && 244 networkRetry(3, () => { 245 - return pdsAgent(agent).com.atproto.repo.putRecord({ 246 repo: account.did, 247 collection: 'chat.bsky.actor.declaration', 248 rkey: 'self', ··· 307 logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`}) 308 } 309 310 - const proxyDid = 311 - readCustomAppViewDidUri() || BLUESKY_PROXY_HEADER.get() || APPVIEW_DID_PROXY 312 - agent.configureProxy(proxyDid) 313 314 return agent.prepare({ 315 resolvers: [gates, moderation, aa], ··· 342 accessJwt: agent.session.accessJwt, 343 signupQueued: isSignupQueued(agent.session.accessJwt), 344 active: agent.session.active, 345 - status: agent.session.status as SessionAccount['status'], 346 pdsUrl: agent.pdsUrl?.toString(), 347 isSelfHosted: !agent.serviceUrl.toString().startsWith(BSKY_SERVICE), 348 } ··· 415 } 416 }, 417 }) 418 - const proxyDid = readCustomAppViewDidUri() || APPVIEW_DID_PROXY 419 - if (proxyDid) { 420 - this.configureProxy(proxyDid) 421 - } 422 } 423 424 async prepare({ ··· 451 this.sessionManager.session = undefined 452 this.persistSessionHandler = undefined 453 } 454 - 455 - cloneWithoutProxy(): BskyAgent { 456 - const cloned = new BskyAgent({service: this.serviceUrl.toString()}) 457 - cloned.sessionManager.session = this.sessionManager.session 458 - return cloned 459 - } 460 } 461 462 /** ··· 465 * other PDS-specific operations like preferences. 466 */ 467 export function pdsAgent<T extends BaseAgent>(agent: T): T { 468 - if ('cloneWithoutProxy' in agent && typeof agent.cloneWithoutProxy === 'function') { 469 return agent.cloneWithoutProxy() as T 470 } 471 const clone = agent.clone() as T
··· 15 16 import {networkRetry} from '#/lib/async/retry' 17 import { 18 BLUESKY_PROXY_HEADER, 19 BSKY_SERVICE, 20 DISCOVER_SAVED_FEED, ··· 33 setCreatedAtForDid, 34 } from '#/ageAssurance/data' 35 import {emitNetworkConfirmed, emitNetworkLost} from '../events' 36 import {addSessionErrorLog} from './logging' 37 import { 38 configureModerationForAccount, ··· 47 configureModerationForGuest() // Side effect but only relevant for tests 48 49 const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) 50 + agent.configureProxy(BLUESKY_PROXY_HEADER.get()) 51 return agent 52 } 53 ··· 88 // after session is attached 89 const aa = prefetchAgeAssuranceData({agent}) 90 91 + agent.configureProxy(BLUESKY_PROXY_HEADER.get()) 92 93 return agent.prepare({ 94 resolvers: [gates, moderation, aa], ··· 127 const moderation = configureModerationForAccount(agent, account) 128 const aa = prefetchAgeAssuranceData({agent}) 129 130 + agent.configureProxy(BLUESKY_PROXY_HEADER.get()) 131 132 return agent.prepare({ 133 resolvers: [gates, moderation, aa], ··· 234 }), 235 getAge(birthDate) < 18 && 236 networkRetry(3, () => { 237 + return agent.com.atproto.repo.putRecord({ 238 repo: account.did, 239 collection: 'chat.bsky.actor.declaration', 240 rkey: 'self', ··· 299 logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`}) 300 } 301 302 + agent.configureProxy(BLUESKY_PROXY_HEADER.get()) 303 304 return agent.prepare({ 305 resolvers: [gates, moderation, aa], ··· 332 accessJwt: agent.session.accessJwt, 333 signupQueued: isSignupQueued(agent.session.accessJwt), 334 active: agent.session.active, 335 + status: agent.session.status, 336 pdsUrl: agent.pdsUrl?.toString(), 337 isSelfHosted: !agent.serviceUrl.toString().startsWith(BSKY_SERVICE), 338 } ··· 405 } 406 }, 407 }) 408 } 409 410 async prepare({ ··· 437 this.sessionManager.session = undefined 438 this.persistSessionHandler = undefined 439 } 440 } 441 442 /** ··· 445 * other PDS-specific operations like preferences. 446 */ 447 export function pdsAgent<T extends BaseAgent>(agent: T): T { 448 + if ( 449 + 'cloneWithoutProxy' in agent && 450 + typeof agent.cloneWithoutProxy === 'function' 451 + ) { 452 return agent.cloneWithoutProxy() as T 453 } 454 const clone = agent.clone() as T
+2 -14
src/state/session/index.tsx
··· 1 - import React, {useMemo} from 'react' 2 import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' 3 4 import * as persisted from '#/state/persisted' ··· 12 createAgentAndCreateAccount, 13 createAgentAndLogin, 14 createAgentAndResume, 15 - pdsAgent, 16 sessionAccountToSession, 17 } from './agent' 18 import {type Action, getInitialState, reducer, type State} from './reducer' ··· 249 >(async () => { 250 const agent = state.currentAgentState.agent as BskyAppAgent 251 const signal = cancelPendingTask() 252 - const {data} = await pdsAgent(agent).com.atproto.server.getSession() 253 if (signal.aborted) return 254 store.dispatch({ 255 type: 'partial-refresh-session', ··· 411 } 412 return agent 413 } 414 - 415 - export function useBlankPrefAuthedAgent(): BskyAgent { 416 - const agent = React.useContext(AgentContext) 417 - if (!agent) { 418 - throw Error('useAgent() must be below <SessionProvider>.') 419 - } 420 - 421 - return useMemo(() => { 422 - return (agent as BskyAppAgent).cloneWithoutProxy() 423 - }, [agent]) 424 - }
··· 1 + import React from 'react' 2 import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' 3 4 import * as persisted from '#/state/persisted' ··· 12 createAgentAndCreateAccount, 13 createAgentAndLogin, 14 createAgentAndResume, 15 sessionAccountToSession, 16 } from './agent' 17 import {type Action, getInitialState, reducer, type State} from './reducer' ··· 248 >(async () => { 249 const agent = state.currentAgentState.agent as BskyAppAgent 250 const signal = cancelPendingTask() 251 + const {data} = await agent.com.atproto.server.getSession() 252 if (signal.aborted) return 253 store.dispatch({ 254 type: 'partial-refresh-session', ··· 410 } 411 return agent 412 }
-1
src/storage/schema.ts
··· 52 deerGateCache: string 53 activitySubscriptionsNudged?: boolean 54 threadgateNudged?: boolean 55 - customAppViewDid: string | undefined 56 57 /** 58 * Policy update overlays. New IDs are required for each new announcement.
··· 52 deerGateCache: string 53 activitySubscriptionsNudged?: boolean 54 threadgateNudged?: boolean 55 56 /** 57 * Policy update overlays. New IDs are required for each new announcement.
+2 -3
src/view/com/modals/DeleteAccount.tsx
··· 18 import {useTheme} from '#/lib/ThemeContext' 19 import {useModalControls} from '#/state/modals' 20 import {useAgent, useSession, useSessionApi} from '#/state/session' 21 - import {pdsAgent} from '#/state/session/agent' 22 import {atoms as a, useTheme as useNewTheme, utils} from '#/alf' 23 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 24 import {Text as NewText} from '#/components/Typography' ··· 50 setError('') 51 setIsProcessing(true) 52 try { 53 - await pdsAgent(agent).com.atproto.server.requestAccountDelete() 54 setIsEmailSent(true) 55 } catch (e: any) { 56 setError(cleanError(e)) ··· 77 if (!success) { 78 throw new Error('Failed to inform chat service of account deletion') 79 } 80 - await pdsAgent(agent).com.atproto.server.deleteAccount({ 81 did: currentAccount.did, 82 password, 83 token,
··· 18 import {useTheme} from '#/lib/ThemeContext' 19 import {useModalControls} from '#/state/modals' 20 import {useAgent, useSession, useSessionApi} from '#/state/session' 21 import {atoms as a, useTheme as useNewTheme, utils} from '#/alf' 22 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 23 import {Text as NewText} from '#/components/Typography' ··· 49 setError('') 50 setIsProcessing(true) 51 try { 52 + await agent.com.atproto.server.requestAccountDelete() 53 setIsEmailSent(true) 54 } catch (e: any) { 55 setError(cleanError(e)) ··· 76 if (!success) { 77 throw new Error('Failed to inform chat service of account deletion') 78 } 79 + await agent.com.atproto.server.deleteAccount({ 80 did: currentAccount.did, 81 password, 82 token,