Cleanup, feedback

This commit is contained in:
Eric Bailey
2024-04-11 16:25:00 -05:00
parent fd085fd437
commit 9721bbaeb3
5 changed files with 92 additions and 70 deletions
+1
View File
@@ -4,6 +4,7 @@ export const LOCAL_DEV_SERVICE =
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
export const STAGING_SERVICE = 'https://staging.bsky.dev' export const STAGING_SERVICE = 'https://staging.bsky.dev'
export const BSKY_SERVICE = 'https://bsky.social' export const BSKY_SERVICE = 'https://bsky.social'
export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
export const DEFAULT_SERVICE = BSKY_SERVICE export const DEFAULT_SERVICE = BSKY_SERVICE
const HELP_DESK_LANG = 'en-us' const HELP_DESK_LANG = 'en-us'
export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}` export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
+1 -5
View File
@@ -6,11 +6,7 @@ import {migrate} from '#/state/persisted/legacy'
import {defaults, Schema} from '#/state/persisted/schema' import {defaults, Schema} from '#/state/persisted/schema'
import * as store from '#/state/persisted/store' import * as store from '#/state/persisted/store'
export type { export type {PersistedAccount, Schema} from '#/state/persisted/schema'
PersistedAccount,
PersistedCurrentAccount,
Schema,
} from '#/state/persisted/schema'
export {defaults} from '#/state/persisted/schema' export {defaults} from '#/state/persisted/schema'
const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL') const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
+16 -5
View File
@@ -4,7 +4,10 @@ import {deviceLocales} from '#/platform/detection'
const externalEmbedOptions = ['show', 'hide'] as const const externalEmbedOptions = ['show', 'hide'] as const
// only data needed for rendering account page /**
* A account persisted to storage. Stored in the `accounts[]` array. Contains
* base account info and access tokens.
*/
const accountSchema = z.object({ const accountSchema = z.object({
service: z.string(), service: z.string(),
did: z.string(), did: z.string(),
@@ -17,17 +20,25 @@ const accountSchema = z.object({
}) })
export type PersistedAccount = z.infer<typeof accountSchema> export type PersistedAccount = z.infer<typeof accountSchema>
const currentAccountSchema = z.object({ /**
did: z.string(), * The current account. Stored in the `currentAccount` field.
*
* In previous versions, this included tokens and other info. Now, it's used
* only to reference the `did` field, and all other fields are marked as
* optional. They should be considered deprecated and not used, but are kept
* here for backwards compat.
*/
const currentAccountScheme = accountSchema.extend({
service: z.string().optional(),
handle: z.string().optional(),
}) })
export type PersistedCurrentAccount = z.infer<typeof currentAccountSchema>
export const schema = z.object({ export const schema = z.object({
colorMode: z.enum(['system', 'light', 'dark']), colorMode: z.enum(['system', 'light', 'dark']),
darkTheme: z.enum(['dim', 'dark']).optional(), darkTheme: z.enum(['dim', 'dark']).optional(),
session: z.object({ session: z.object({
accounts: z.array(accountSchema), accounts: z.array(accountSchema),
currentAccount: currentAccountSchema.optional(), currentAccount: currentAccountScheme.optional(),
}), }),
reminders: z.object({ reminders: z.object({
lastEmailConfirm: z.string().optional(), lastEmailConfirm: z.string().optional(),
+3 -1
View File
@@ -1,7 +1,9 @@
import {BskyAgent} from '@atproto/api' import {BskyAgent} from '@atproto/api'
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
export const PUBLIC_BSKY_AGENT = new BskyAgent({ export const PUBLIC_BSKY_AGENT = new BskyAgent({
service: 'https://public.api.bsky.app', service: PUBLIC_BSKY_SERVICE,
}) })
export const STALE = { export const STALE = {
+71 -59
View File
@@ -5,12 +5,12 @@ import {jwtDecode} from 'jwt-decode'
import {track} from '#/lib/analytics/analytics' import {track} from '#/lib/analytics/analytics'
import {networkRetry} from '#/lib/async/retry' import {networkRetry} from '#/lib/async/retry'
import {IS_TEST_USER} from '#/lib/constants' import {IS_TEST_USER} from '#/lib/constants'
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {logEvent, LogEvents} from '#/lib/statsig/statsig'
import {hasProp} from '#/lib/type-guards' import {hasProp} from '#/lib/type-guards'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {PUBLIC_BSKY_AGENT} from '#/state/queries'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util' import {useCloseAllActiveElements} from '#/state/util'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
@@ -18,10 +18,16 @@ import {IS_DEV} from '#/env'
import {emitSessionDropped} from '../events' import {emitSessionDropped} from '../events'
import {readLabelers} from './agent-config' import {readLabelers} from './agent-config'
/**
* Only used for the initial agent values in state and context. Replaced
* immediately, and should not be reused.
*/
const INITIAL_AGENT = new BskyAgent({service: PUBLIC_BSKY_SERVICE})
/** /**
* @deprecated use `agent` from `useSession` instead * @deprecated use `agent` from `useSession` instead
*/ */
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT let __globalAgent: BskyAgent = INITIAL_AGENT
/** /**
* NOTE * NOTE
@@ -36,6 +42,7 @@ export function getAgent() {
} }
export type SessionAccount = persisted.PersistedAccount export type SessionAccount = persisted.PersistedAccount
export type CurrentAccount = Omit<SessionAccount, 'accessJwt' | 'refreshJwt'>
export type StateContext = { export type StateContext = {
currentAgent: BskyAgent currentAgent: BskyAgent
@@ -44,10 +51,10 @@ export type StateContext = {
hasSession: boolean hasSession: boolean
accounts: SessionAccount[] accounts: SessionAccount[]
/** /**
* This value is derived from `BskyAgent.session` and should contain the full * Contains the full account object persisted to storage, minus access
* account object persisted to storage, minus the access tokens. * tokens.
*/ */
currentAccount: Omit<SessionAccount, 'accessJwt' | 'refreshJwt'> | undefined currentAccount: CurrentAccount | undefined
} }
export type ApiContext = { export type ApiContext = {
@@ -98,7 +105,7 @@ export type ApiContext = {
} }
const StateContext = React.createContext<StateContext>({ const StateContext = React.createContext<StateContext>({
currentAgent: PUBLIC_BSKY_AGENT, currentAgent: INITIAL_AGENT,
isInitialLoad: true, isInitialLoad: true,
isSwitchingAccounts: false, isSwitchingAccounts: false,
accounts: [], accounts: [],
@@ -133,15 +140,6 @@ function agentToSessionAccount(agent: BskyAgent): SessionAccount | undefined {
} }
} }
function agentToCurrentAccount(
agent: BskyAgent,
): StateContext['currentAccount'] {
const sessionAccount = agentToSessionAccount(agent)
delete sessionAccount?.accessJwt
delete sessionAccount?.refreshJwt
return sessionAccount
}
function sessionAccountToAgentSession( function sessionAccountToAgentSession(
account: SessionAccount, account: SessionAccount,
): BskyAgent['session'] { ): BskyAgent['session'] {
@@ -158,16 +156,20 @@ function sessionAccountToAgentSession(
export function Provider({children}: React.PropsWithChildren<{}>) { export function Provider({children}: React.PropsWithChildren<{}>) {
const isDirty = React.useRef(false) const isDirty = React.useRef(false)
const [currentAgent, setCurrentAgent] = const [currentAgent, setCurrentAgent] =
React.useState<BskyAgent>(PUBLIC_BSKY_AGENT) React.useState<BskyAgent>(INITIAL_AGENT)
const [accounts, setAccounts] = React.useState<SessionAccount[]>( const [accounts, setAccounts] = React.useState<SessionAccount[]>(
persisted.get('session').accounts, persisted.get('session').accounts,
) )
const [isInitialLoad, setIsInitialLoad] = React.useState(true) const [isInitialLoad, setIsInitialLoad] = React.useState(true)
const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false) const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false)
const currentAccount = React.useMemo( const currentAccountDid = React.useMemo(
() => agentToCurrentAccount(currentAgent), () => currentAgent.session?.did,
[currentAgent], [currentAgent],
) )
const currentAccount = React.useMemo(
() => accounts.find(a => a.did === currentAccountDid),
[accounts, currentAccountDid],
)
const persistNextUpdate = React.useCallback( const persistNextUpdate = React.useCallback(
() => (isDirty.current = true), () => (isDirty.current = true),
@@ -187,10 +189,16 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const clearCurrentAccount = React.useCallback(() => { const clearCurrentAccount = React.useCallback(() => {
logger.warn(`session: clear current account`) logger.warn(`session: clear current account`)
// immediate clear this so any pending requests don't use it
currentAgent.setPersistSessionHandler(() => {})
persistNextUpdate() persistNextUpdate()
setCurrentAgent(PUBLIC_BSKY_AGENT)
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]}) const newAgent = new BskyAgent({service: PUBLIC_BSKY_SERVICE})
}, [persistNextUpdate, setCurrentAgent]) setCurrentAgent(newAgent)
configureModeration(newAgent)
}, [currentAgent, persistNextUpdate, setCurrentAgent])
React.useMemo(() => { React.useMemo(() => {
currentAgent.setPersistSessionHandler(event => { currentAgent.setPersistSessionHandler(event => {
@@ -436,17 +444,20 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
>(async () => { >(async () => {
const {accounts: persistedAccounts} = persisted.get('session') const {accounts: persistedAccounts} = persisted.get('session')
const selectedAccount = persistedAccounts.find( const selectedAccount = persistedAccounts.find(
a => a.did === currentAccount?.did, a => a.did === currentAccountDid,
) )
if (!selectedAccount) return if (!selectedAccount) return
await currentAgent.resumeSession(
sessionAccountToAgentSession(selectedAccount)!, // update and swap agent to trigger render refresh
) const newAgent = currentAgent.clone()
await newAgent.resumeSession(sessionAccountToAgentSession(selectedAccount)!)
const refreshedAccount = agentToSessionAccount(newAgent)
persistNextUpdate() persistNextUpdate()
upsertAndPersistAccount(agentToSessionAccount(currentAgent)!) upsertAndPersistAccount(refreshedAccount!)
setCurrentAgent(currentAgent.clone()) setCurrentAgent(newAgent)
configureModeration(newAgent, refreshedAccount)
}, [ }, [
currentAccount, currentAccountDid,
currentAgent, currentAgent,
setCurrentAgent, setCurrentAgent,
persistNextUpdate, persistNextUpdate,
@@ -475,11 +486,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
isDirty.current = false isDirty.current = false
persisted.write('session', { persisted.write('session', {
accounts, accounts,
currentAccount: currentAccount currentAccount,
? {
did: currentAccount.did,
}
: undefined,
}) })
} }
}, [accounts, currentAccount]) }, [accounts, currentAccount])
@@ -502,17 +509,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
) )
if (selectedAccount && selectedAccount.refreshJwt) { if (selectedAccount && selectedAccount.refreshJwt) {
if (selectedAccount?.did !== currentAccount?.did) { if (selectedAccount?.did !== currentAccountDid) {
logger.debug( logger.debug(
`session: persisted onUpdate, switching accounts`, `session: persisted onUpdate, switching accounts`,
{ {
from: { from: {
did: currentAccount?.did, did: currentAccountDid,
handle: currentAccount?.handle,
}, },
to: { to: {
did: selectedAccount.did, did: selectedAccount.did,
handle: selectedAccount.handle,
}, },
}, },
logger.DebugContext.session, logger.DebugContext.session,
@@ -526,12 +531,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logger.DebugContext.session, logger.DebugContext.session,
) )
// console.log('UPDATE', { refreshJwt: selectedAccount.refreshJwt.slice(-10) }) // console.log('UPDATE', { refreshJwt: selectedAccount.refreshJwt.slice(-10) })
// updates silently, all subsequent calls will use the new session const newAgent = currentAgent.clone()
currentAgent.session = sessionAccountToAgentSession(selectedAccount) newAgent.session = sessionAccountToAgentSession(selectedAccount)
// replace agent to re-derive currentAccount and trigger rerender with fresh data configureModeration(newAgent, selectedAccount)
setCurrentAgent(currentAgent.clone()) setCurrentAgent(newAgent)
} }
} else if (!selectedAccount && currentAccount) { } else if (!selectedAccount && currentAccountDid) {
logger.debug( logger.debug(
`session: persisted onUpdate, logging out`, `session: persisted onUpdate, logging out`,
{}, {},
@@ -548,7 +553,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
} }
}) })
}, [ }, [
currentAccount, currentAccountDid,
setAccounts, setAccounts,
clearCurrentAccount, clearCurrentAccount,
initSession, initSession,
@@ -614,25 +619,32 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
) )
} }
async function configureModeration(agent: BskyAgent, account: SessionAccount) { async function configureModeration(agent: BskyAgent, account?: SessionAccount) {
if (IS_TEST_USER(account.handle)) { if (account) {
const did = ( if (IS_TEST_USER(account.handle)) {
await agent const did = (
.resolveHandle({handle: 'mod-authority.test'}) await agent
.catch(_ => undefined) .resolveHandle({handle: 'mod-authority.test'})
)?.data.did .catch(_ => undefined)
if (did) { )?.data.did
console.warn('USING TEST ENV MODERATION') if (did) {
BskyAgent.configure({appLabelers: [did]}) console.warn('USING TEST ENV MODERATION')
BskyAgent.configure({appLabelers: [did]})
}
} else {
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
if (account) {
const labelerDids = await readLabelers(account.did).catch(_ => {})
if (labelerDids) {
agent.configureLabelersHeader(
labelerDids.filter(did => did !== BSKY_LABELER_DID),
)
}
}
} }
} else { } else {
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]}) BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
const labelerDids = await readLabelers(account.did).catch(_ => {})
if (labelerDids) {
agent.configureLabelersHeader(
labelerDids.filter(did => did !== BSKY_LABELER_DID),
)
}
} }
} }