Cleanup, feedback
This commit is contained in:
@@ -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}`
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
+56
-44
@@ -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,7 +619,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureModeration(agent: BskyAgent, account: SessionAccount) {
|
async function configureModeration(agent: BskyAgent, account?: SessionAccount) {
|
||||||
|
if (account) {
|
||||||
if (IS_TEST_USER(account.handle)) {
|
if (IS_TEST_USER(account.handle)) {
|
||||||
const did = (
|
const did = (
|
||||||
await agent
|
await agent
|
||||||
@@ -627,6 +633,8 @@ async function configureModeration(agent: BskyAgent, account: SessionAccount) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
|
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
|
||||||
|
|
||||||
|
if (account) {
|
||||||
const labelerDids = await readLabelers(account.did).catch(_ => {})
|
const labelerDids = await readLabelers(account.did).catch(_ => {})
|
||||||
if (labelerDids) {
|
if (labelerDids) {
|
||||||
agent.configureLabelersHeader(
|
agent.configureLabelersHeader(
|
||||||
@@ -635,6 +643,10 @@ async function configureModeration(agent: BskyAgent, account: SessionAccount) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useSession() {
|
export function useSession() {
|
||||||
return React.useContext(StateContext)
|
return React.useContext(StateContext)
|
||||||
|
|||||||
Reference in New Issue
Block a user