Bluesky app fork with some witchin' additions 💫

use an env var for configurable proxy header (#8863)

authored by

hailey and committed by
GitHub
a1ed59e0 39c6984c

+108 -41
+3
.env.example
··· 16 16 # Enable debug logs for specific logger instances 17 17 EXPO_PUBLIC_LOG_DEBUG=session 18 18 19 + # Bluesky appview DID 20 + EXPO_PUBLIC_BLUESKY_PROXY_DID= 21 + 19 22 # Chat service DID 20 23 EXPO_PUBLIC_CHAT_PROXY_DID= 21 24
+6
src/env/common.ts
··· 63 63 export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || '' 64 64 65 65 /** 66 + * The DID of the Bluesky appview to proxy to 67 + */ 68 + export const BLUESKY_PROXY_DID: Did = 69 + process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app' 70 + 71 + /** 66 72 * The DID of the chat service to proxy to 67 73 */ 68 74 export const CHAT_PROXY_DID: Did =
+13
src/lib/constants.ts
··· 1 1 import {type Insets, Platform} from 'react-native' 2 2 import {type AppBskyActorDefs} from '@atproto/api' 3 3 4 + import {type ProxyHeaderValue} from '#/state/session/agent' 5 + import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' 6 + 4 7 export const LOCAL_DEV_SERVICE = 5 8 Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' 6 9 export const STAGING_SERVICE = 'https://staging.bsky.dev' ··· 210 213 export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev' 211 214 212 215 export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same 216 + 217 + export const BLUESKY_PROXY_HEADER: ProxyHeaderValue = `${BLUESKY_PROXY_DID}#bsky_appview` 218 + 219 + export const BLUESKY_SERVICE_HEADERS = { 220 + 'atproto-proxy': BLUESKY_PROXY_HEADER, 221 + } 222 + 223 + export const DM_SERVICE_HEADERS = { 224 + 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, 225 + } 213 226 214 227 export const webLinks = { 215 228 tos: `https://bsky.social/about/support/tos`,
+3 -3
src/screens/Login/ForgotPasswordForm.tsx
··· 1 1 import React, {useState} from 'react' 2 2 import {ActivityIndicator, Keyboard, View} from 'react-native' 3 - import {ComAtprotoServerDescribeServer} from '@atproto/api' 4 - import {BskyAgent} from '@atproto/api' 3 + import {type ComAtprotoServerDescribeServer} from '@atproto/api' 5 4 import {msg, Trans} from '@lingui/macro' 6 5 import {useLingui} from '@lingui/react' 7 6 import * as EmailValidator from 'email-validator' ··· 9 8 import {isNetworkError} from '#/lib/strings/errors' 10 9 import {cleanError} from '#/lib/strings/errors' 11 10 import {logger} from '#/logger' 11 + import {Agent} from '#/state/session/agent' 12 12 import {atoms as a, useTheme} from '#/alf' 13 13 import {Button, ButtonText} from '#/components/Button' 14 14 import {FormError} from '#/components/forms/FormError' ··· 55 55 setIsProcessing(true) 56 56 57 57 try { 58 - const agent = new BskyAgent({service: serviceUrl}) 58 + const agent = new Agent(null, {service: serviceUrl}) 59 59 await agent.com.atproto.server.requestPasswordReset({email}) 60 60 onEmailSent() 61 61 } catch (e: any) {
+2 -2
src/screens/Login/SetNewPasswordForm.tsx
··· 1 1 import {useState} from 'react' 2 2 import {ActivityIndicator, View} from 'react-native' 3 - import {BskyAgent} from '@atproto/api' 4 3 import {msg, Trans} from '@lingui/macro' 5 4 import {useLingui} from '@lingui/react' 6 5 ··· 9 8 import {cleanError} from '#/lib/strings/errors' 10 9 import {checkAndFormatResetCode} from '#/lib/strings/password' 11 10 import {logger} from '#/logger' 11 + import {Agent} from '#/state/session/agent' 12 12 import {atoms as a, useTheme} from '#/alf' 13 13 import {Button, ButtonText} from '#/components/Button' 14 14 import {FormError} from '#/components/forms/FormError' ··· 63 63 setIsProcessing(true) 64 64 65 65 try { 66 - const agent = new BskyAgent({service: serviceUrl}) 66 + const agent = new Agent(null, {service: serviceUrl}) 67 67 await agent.com.atproto.server.resetPassword({ 68 68 token: formattedCode, 69 69 password,
+1 -1
src/state/messages/convo/agent.ts
··· 10 10 import {nanoid} from 'nanoid/non-secure' 11 11 12 12 import {networkRetry} from '#/lib/async/retry' 13 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 13 14 import {isNetworkError} from '#/lib/strings/errors' 14 15 import {Logger} from '#/logger' 15 16 import {isNative} from '#/platform/detection' ··· 33 34 } from '#/state/messages/convo/types' 34 35 import {type MessagesEventBus} from '#/state/messages/events/agent' 35 36 import {type MessagesEventBusError} from '#/state/messages/events/types' 36 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 37 37 38 38 const logger = Logger.create(Logger.Context.ConversationAgent) 39 39
+1 -1
src/state/messages/events/agent.ts
··· 3 3 import {nanoid} from 'nanoid/non-secure' 4 4 5 5 import {networkRetry} from '#/lib/async/retry' 6 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 6 7 import {isNetworkError} from '#/lib/strings/errors' 7 8 import {Logger} from '#/logger' 8 9 import { ··· 17 18 type MessagesEventBusParams, 18 19 MessagesEventBusStatus, 19 20 } from '#/state/messages/events/types' 20 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 21 21 22 22 const logger = Logger.create(Logger.Context.DMsAgent) 23 23
+4 -3
src/state/queries/handle-availability.ts
··· 1 - import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api' 1 + import {ComAtprotoTempCheckHandleAvailability} from '@atproto/api' 2 2 import {useQuery} from '@tanstack/react-query' 3 3 4 4 import { ··· 10 10 import {logger} from '#/logger' 11 11 import {useDebouncedValue} from '#/components/live/utils' 12 12 import * as bsky from '#/types/bsky' 13 + import {Agent} from '../session/agent' 13 14 14 15 export const RQKEY_handleAvailability = ( 15 16 handle: string, ··· 74 75 }, 75 76 ) { 76 77 if (serviceDid === BSKY_SERVICE_DID) { 77 - const agent = new Agent({service: BSKY_SERVICE}) 78 + const agent = new Agent(null, {service: BSKY_SERVICE}) 78 79 // entryway has a special API for handle availability 79 80 const {data} = await agent.com.atproto.temp.checkHandleAvailability({ 80 81 handle, ··· 109 110 } 110 111 } else { 111 112 // 3rd party PDSes won't have this API so just try and resolve the handle 112 - const agent = new Agent({service: PUBLIC_BSKY_SERVICE}) 113 + const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE}) 113 114 try { 114 115 const res = await agent.resolveHandle({ 115 116 handle,
+5 -2
src/state/queries/messages/accept-conversation.ts
··· 1 - import {ChatBskyConvoAcceptConvo, ChatBskyConvoListConvos} from '@atproto/api' 1 + import { 2 + type ChatBskyConvoAcceptConvo, 3 + type ChatBskyConvoListConvos, 4 + } from '@atproto/api' 2 5 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 6 7 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 4 8 import {logger} from '#/logger' 5 9 import {useAgent} from '#/state/session' 6 - import {DM_SERVICE_HEADERS} from './const' 7 10 import { 8 11 RQKEY as CONVO_LIST_KEY, 9 12 RQKEY_ROOT as CONVO_LIST_ROOT_KEY,
-5
src/state/queries/messages/const.ts
··· 1 - import {CHAT_PROXY_DID} from '#/env' 2 - 3 - export const DM_SERVICE_HEADERS = { 4 - 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, 5 - }
+4 -4
src/state/queries/messages/conversation.ts
··· 1 - import {ChatBskyConvoDefs} from '@atproto/api' 1 + import {type ChatBskyConvoDefs} from '@atproto/api' 2 2 import { 3 - QueryClient, 3 + type QueryClient, 4 4 useMutation, 5 5 useQuery, 6 6 useQueryClient, 7 7 } from '@tanstack/react-query' 8 8 9 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 9 10 import {STALE} from '#/state/queries' 10 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 11 11 import {useOnMarkAsRead} from '#/state/queries/messages/list-conversations' 12 12 import {useAgent} from '#/state/session' 13 13 import { 14 - ConvoListQueryData, 14 + type ConvoListQueryData, 15 15 getConvoFromQueryData, 16 16 RQKEY_ROOT as LIST_CONVOS_KEY, 17 17 } from './list-conversations'
+1 -1
src/state/queries/messages/get-convo-availability.ts
··· 1 1 import {useQuery} from '@tanstack/react-query' 2 2 3 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 3 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 4 4 import {useAgent} from '#/state/session' 5 5 import {STALE} from '..' 6 6
+2 -2
src/state/queries/messages/get-convo-for-members.ts
··· 1 - import {ChatBskyConvoGetConvoForMembers} from '@atproto/api' 1 + import {type ChatBskyConvoGetConvoForMembers} from '@atproto/api' 2 2 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 3 4 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 4 5 import {logger} from '#/logger' 5 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 6 6 import {useAgent} from '#/state/session' 7 7 import {precacheConvoQuery} from './conversation' 8 8
+5 -2
src/state/queries/messages/leave-conversation.ts
··· 1 1 import {useMemo} from 'react' 2 - import {ChatBskyConvoLeaveConvo, ChatBskyConvoListConvos} from '@atproto/api' 2 + import { 3 + type ChatBskyConvoLeaveConvo, 4 + type ChatBskyConvoListConvos, 5 + } from '@atproto/api' 3 6 import { 4 7 useMutation, 5 8 useMutationState, 6 9 useQueryClient, 7 10 } from '@tanstack/react-query' 8 11 12 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 9 13 import {logger} from '#/logger' 10 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 11 14 import {useAgent} from '#/state/session' 12 15 import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations' 13 16
+1 -1
src/state/queries/messages/list-conversations.tsx
··· 13 13 } from '@tanstack/react-query' 14 14 import throttle from 'lodash.throttle' 15 15 16 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 16 17 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 17 18 import {useMessagesEventBus} from '#/state/messages/events' 18 19 import {useModerationOpts} from '#/state/preferences/moderation-opts' 19 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 20 20 import {useAgent, useSession} from '#/state/session' 21 21 import {useLeftConvos} from './leave-conversation' 22 22
+9 -5
src/state/queries/messages/mute-conversation.ts
··· 1 1 import { 2 - ChatBskyConvoDefs, 3 - ChatBskyConvoListConvos, 4 - ChatBskyConvoMuteConvo, 2 + type ChatBskyConvoDefs, 3 + type ChatBskyConvoListConvos, 4 + type ChatBskyConvoMuteConvo, 5 5 } from '@atproto/api' 6 - import {InfiniteData, useMutation, useQueryClient} from '@tanstack/react-query' 6 + import { 7 + type InfiniteData, 8 + useMutation, 9 + useQueryClient, 10 + } from '@tanstack/react-query' 7 11 8 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 12 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 9 13 import {useAgent} from '#/state/session' 10 14 import {RQKEY as CONVO_KEY} from './conversation' 11 15 import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
+2 -2
src/state/queries/messages/update-all-read.ts
··· 1 - import {ChatBskyConvoListConvos} from '@atproto/api' 1 + import {type ChatBskyConvoListConvos} from '@atproto/api' 2 2 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 3 4 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 4 5 import {logger} from '#/logger' 5 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 6 6 import {useAgent} from '#/state/session' 7 7 import {RQKEY as CONVO_LIST_KEY} from './list-conversations' 8 8
+3 -2
src/state/queries/service.ts
··· 1 - import {BskyAgent} from '@atproto/api' 2 1 import {useQuery} from '@tanstack/react-query' 2 + 3 + import {Agent} from '../session/agent' 3 4 4 5 const RQKEY_ROOT = 'service' 5 6 export const RQKEY = (serviceUrl: string) => [RQKEY_ROOT, serviceUrl] ··· 8 9 return useQuery({ 9 10 queryKey: RQKEY(serviceUrl), 10 11 queryFn: async () => { 11 - const agent = new BskyAgent({service: serviceUrl}) 12 + const agent = new Agent(null, {service: serviceUrl}) 12 13 const res = await agent.com.atproto.server.describeServer() 13 14 return res.data 14 15 },
+41 -3
src/state/session/agent.ts
··· 1 - import {AtpSessionData, AtpSessionEvent, BskyAgent} from '@atproto/api' 1 + import { 2 + Agent as BaseAgent, 3 + type AtprotoServiceType, 4 + type AtpSessionData, 5 + type AtpSessionEvent, 6 + BskyAgent, 7 + type Did, 8 + } from '@atproto/api' 9 + import {type FetchHandler} from '@atproto/api/dist/agent' 10 + import {type SessionManager} from '@atproto/api/dist/session-manager' 2 11 import {TID} from '@atproto/common-web' 12 + import {type FetchHandlerOptions} from '@atproto/xrpc' 3 13 4 14 import {networkRetry} from '#/lib/async/retry' 5 15 import { 16 + BLUESKY_PROXY_HEADER, 6 17 BSKY_SERVICE, 7 18 DISCOVER_SAVED_FEED, 8 19 IS_PROD_SERVICE, ··· 19 30 configureModerationForAccount, 20 31 configureModerationForGuest, 21 32 } from './moderation' 22 - import {SessionAccount} from './types' 33 + import {type SessionAccount} from './types' 23 34 import {isSessionExpired, isSignupQueued} from './util' 24 35 36 + export type ProxyHeaderValue = `${Did}#${AtprotoServiceType}` 37 + 25 38 export function createPublicAgent() { 26 39 configureModerationForGuest() // Side effect but only relevant for tests 27 - return new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) 40 + 41 + const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) 42 + agent.configureProxy(BLUESKY_PROXY_HEADER) 43 + return agent 28 44 } 29 45 30 46 export async function createAgentAndResume( ··· 61 77 } 62 78 } 63 79 80 + agent.configureProxy(BLUESKY_PROXY_HEADER) 81 + 64 82 return agent.prepare(gates, moderation, onSessionChange) 65 83 } 66 84 ··· 93 111 const account = agentToSessionAccountOrThrow(agent) 94 112 const gates = tryFetchGates(account.did, 'prefer-fresh-gates') 95 113 const moderation = configureModerationForAccount(agent, account) 114 + 115 + agent.configureProxy(BLUESKY_PROXY_HEADER) 116 + 96 117 return agent.prepare(gates, moderation, onSessionChange) 97 118 } 98 119 ··· 180 201 logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`}) 181 202 } 182 203 204 + agent.configureProxy(BLUESKY_PROXY_HEADER) 205 + 183 206 return agent.prepare(gates, moderation, onSessionChange) 184 207 } 185 208 ··· 234 257 } 235 258 } 236 259 260 + export class Agent extends BaseAgent { 261 + constructor( 262 + proxyHeader: ProxyHeaderValue | null, 263 + options: SessionManager | FetchHandler | FetchHandlerOptions, 264 + ) { 265 + super(options) 266 + if (proxyHeader) { 267 + this.configureProxy(proxyHeader) 268 + } 269 + } 270 + } 271 + 237 272 // Not exported. Use factories above to create it. 273 + // WARN: In the factories above, we _manually set a proxy header_ for the agent after we do whatever it is we are supposed to do. 274 + // Ideally, we wouldn't be doing this. However, since there is so much logic that requires making calls to the PDS right now, it 275 + // feels safer to just let those run as-is and set the header afterward. 238 276 let realFetch = globalThis.fetch 239 277 class BskyAppAgent extends BskyAgent { 240 278 persistSessionHandler: ((event: AtpSessionEvent) => void) | undefined =
+1 -1
src/view/com/modals/DeleteAccount.tsx
··· 10 10 import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 13 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 13 14 import {usePalette} from '#/lib/hooks/usePalette' 14 15 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 15 16 import {cleanError} from '#/lib/strings/errors' ··· 17 18 import {useTheme} from '#/lib/ThemeContext' 18 19 import {isAndroid, isWeb} from '#/platform/detection' 19 20 import {useModalControls} from '#/state/modals' 20 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 21 21 import {useAgent, useSession, useSessionApi} from '#/state/session' 22 22 import {atoms as a, useTheme as useNewTheme} from '#/alf' 23 23 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+1 -1
src/view/com/notifications/NotificationFeedItem.tsx
··· 31 31 import {useQueryClient} from '@tanstack/react-query' 32 32 33 33 import {MAX_POST_LINES} from '#/lib/constants' 34 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 34 35 import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' 35 36 import {usePalette} from '#/lib/hooks/usePalette' 36 37 import {makeProfileLink} from '#/lib/routes/links' ··· 41 42 import {niceDate} from '#/lib/strings/time' 42 43 import {s} from '#/lib/styles' 43 44 import {logger} from '#/logger' 44 - import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 45 45 import {type FeedNotification} from '#/state/queries/notifications/feed' 46 46 import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' 47 47 import {useAgent} from '#/state/session'