refactor
This commit is contained in:
@@ -6,3 +6,4 @@ EXPO_PUBLIC_LOG_LEVEL=debug
|
|||||||
EXPO_PUBLIC_LOG_DEBUG=
|
EXPO_PUBLIC_LOG_DEBUG=
|
||||||
EXPO_PUBLIC_BUNDLE_IDENTIFIER=
|
EXPO_PUBLIC_BUNDLE_IDENTIFIER=
|
||||||
EXPO_PUBLIC_BUNDLE_DATE=0
|
EXPO_PUBLIC_BUNDLE_DATE=0
|
||||||
|
EXPO_PUBLIC_USE_OAUTH="false"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"redirect_uris": [
|
"redirect_uris": [
|
||||||
"https://bsky.hailey.at/auth/web/callback"
|
"https://bsky.hailey.at/auth/web/callback"
|
||||||
],
|
],
|
||||||
"scope": "atproto transition:generic transition:email",
|
"scope": "atproto transition:generic transition:email transition:chat.bsky",
|
||||||
"token_endpoint_auth_method": "none",
|
"token_endpoint_auth_method": "none",
|
||||||
"response_types": [
|
"response_types": [
|
||||||
"code"
|
"code"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"redirect_uris": [
|
"redirect_uris": [
|
||||||
"at.hailey.bsky:/auth/native/callback"
|
"at.hailey.bsky:/auth/native/callback"
|
||||||
],
|
],
|
||||||
"scope": "atproto transition:generic transition:email",
|
"scope": "atproto transition:generic transition:email transition:chat.bsky",
|
||||||
"token_endpoint_auth_method": "none",
|
"token_endpoint_auth_method": "none",
|
||||||
"response_types": [
|
"response_types": [
|
||||||
"code"
|
"code"
|
||||||
|
|||||||
+3
-7
@@ -93,7 +93,7 @@ beginResolveGeolocation()
|
|||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const [isReady, setIsReady] = React.useState(false)
|
const [isReady, setIsReady] = React.useState(false)
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {resumeSession, resumeSessionOauth} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const theme = useColorModeTheme()
|
const theme = useColorModeTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const hasCheckedReferrer = useStarterPackEntry()
|
const hasCheckedReferrer = useStarterPackEntry()
|
||||||
@@ -103,11 +103,7 @@ function InnerApp() {
|
|||||||
async function onLaunch(account?: SessionAccount) {
|
async function onLaunch(account?: SessionAccount) {
|
||||||
try {
|
try {
|
||||||
if (account) {
|
if (account) {
|
||||||
if (true) {
|
await resumeSession(account)
|
||||||
await resumeSessionOauth(account)
|
|
||||||
} else {
|
|
||||||
await resumeSession(account)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await tryFetchGates(undefined, 'prefer-fresh-gates')
|
await tryFetchGates(undefined, 'prefer-fresh-gates')
|
||||||
}
|
}
|
||||||
@@ -119,7 +115,7 @@ function InnerApp() {
|
|||||||
}
|
}
|
||||||
const account = readLastActiveAccount()
|
const account = readLastActiveAccount()
|
||||||
onLaunch(account)
|
onLaunch(account)
|
||||||
}, [resumeSession, resumeSessionOauth])
|
}, [resumeSession])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return listenSessionDropped(() => {
|
return listenSessionDropped(() => {
|
||||||
|
|||||||
+3
-7
@@ -72,7 +72,7 @@ beginResolveGeolocation()
|
|||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const [isReady, setIsReady] = React.useState(false)
|
const [isReady, setIsReady] = React.useState(false)
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {resumeSession, resumeSessionOauth} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const theme = useColorModeTheme()
|
const theme = useColorModeTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const hasCheckedReferrer = useStarterPackEntry()
|
const hasCheckedReferrer = useStarterPackEntry()
|
||||||
@@ -82,11 +82,7 @@ function InnerApp() {
|
|||||||
async function onLaunch(account?: SessionAccount) {
|
async function onLaunch(account?: SessionAccount) {
|
||||||
try {
|
try {
|
||||||
if (account) {
|
if (account) {
|
||||||
if (true) {
|
await resumeSession(account)
|
||||||
await resumeSessionOauth(account)
|
|
||||||
} else {
|
|
||||||
await resumeSession(account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`session: resumeSession failed`, {message: e})
|
logger.error(`session: resumeSession failed`, {message: e})
|
||||||
@@ -96,7 +92,7 @@ function InnerApp() {
|
|||||||
}
|
}
|
||||||
const account = readLastActiveAccount()
|
const account = readLastActiveAccount()
|
||||||
onLaunch(account)
|
onLaunch(account)
|
||||||
}, [resumeSession, resumeSessionOauth])
|
}, [resumeSession])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return listenSessionDropped(() => {
|
return listenSessionDropped(() => {
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import {isWeb} from '#/platform/detection'
|
|||||||
import {type SessionAccount, useSessionApi} from '#/state/session'
|
import {type SessionAccount, useSessionApi} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
import {USE_OAUTH} from '../app-info'
|
||||||
import {logEvent} from '../statsig/statsig'
|
import {logEvent} from '../statsig/statsig'
|
||||||
import {type LogEvents} from '../statsig/statsig'
|
import {type LogEvents} from '../statsig/statsig'
|
||||||
|
|
||||||
export function useAccountSwitcher() {
|
export function useAccountSwitcher() {
|
||||||
const [pendingDid, setPendingDid] = useState<string | null>(null)
|
const [pendingDid, setPendingDid] = useState<string | null>(null)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {resumeSession, resumeSessionOauth} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
|
|
||||||
const onPressSwitchAccount = useCallback(
|
const onPressSwitchAccount = useCallback(
|
||||||
@@ -28,7 +29,7 @@ export function useAccountSwitcher() {
|
|||||||
try {
|
try {
|
||||||
setPendingDid(account.did)
|
setPendingDid(account.did)
|
||||||
// TODO: this should be checking if it is an oauth session
|
// TODO: this should be checking if it is an oauth session
|
||||||
if (true || account.accessJwt) {
|
if (USE_OAUTH || account.accessJwt) {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
// We're switching accounts, which remounts the entire app.
|
// We're switching accounts, which remounts the entire app.
|
||||||
// On mobile, this gets us Home, but on the web we also need reset the URL.
|
// On mobile, this gets us Home, but on the web we also need reset the URL.
|
||||||
@@ -37,11 +38,7 @@ export function useAccountSwitcher() {
|
|||||||
// So we change the URL ourselves. The navigator will pick it up on remount.
|
// So we change the URL ourselves. The navigator will pick it up on remount.
|
||||||
history.pushState(null, '', '/')
|
history.pushState(null, '', '/')
|
||||||
}
|
}
|
||||||
if (true) {
|
await resumeSession(account)
|
||||||
await resumeSessionOauth(account)
|
|
||||||
} else {
|
|
||||||
await resumeSession(account)
|
|
||||||
}
|
|
||||||
logEvent('account:loggedIn', {logContext, withPassword: false})
|
logEvent('account:loggedIn', {logContext, withPassword: false})
|
||||||
Toast.show(_(msg`Signed in as @${account.handle}`))
|
Toast.show(_(msg`Signed in as @${account.handle}`))
|
||||||
} else {
|
} else {
|
||||||
@@ -64,7 +61,7 @@ export function useAccountSwitcher() {
|
|||||||
setPendingDid(null)
|
setPendingDid(null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[_, resumeSession, resumeSessionOauth, requestSwitchToAccount, pendingDid],
|
[_, resumeSession, requestSwitchToAccount, pendingDid],
|
||||||
)
|
)
|
||||||
|
|
||||||
return {onPressSwitchAccount, pendingDid}
|
return {onPressSwitchAccount, pendingDid}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {useSessionApi} from '#/state/session'
|
|||||||
import {getWebOAuthClient} from '#/state/session/oauth-web-client'
|
import {getWebOAuthClient} from '#/state/session/oauth-web-client'
|
||||||
|
|
||||||
export function AuthCallback() {
|
export function AuthCallback() {
|
||||||
const {loginOauth} = useSessionApi()
|
const {login} = useSessionApi()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
// TODO: handle errors, loading state, etc...
|
// TODO: handle errors, loading state, etc...
|
||||||
@@ -18,10 +18,13 @@ export function AuthCallback() {
|
|||||||
;(async () => {
|
;(async () => {
|
||||||
const client = getWebOAuthClient()
|
const client = getWebOAuthClient()
|
||||||
const res = await client.callback(params)
|
const res = await client.callback(params)
|
||||||
await loginOauth(res.session, 'LoginForm') // TODO: right context?
|
await login(
|
||||||
navigation.navigate('Home')
|
{service: '', identifier: '', password: '', oauthSession: res.session},
|
||||||
|
'LoginForm',
|
||||||
|
)
|
||||||
|
navigation.replace('Home')
|
||||||
})()
|
})()
|
||||||
}, [loginOauth, navigation])
|
}, [login, navigation])
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {View} from 'react-native'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {USE_OAUTH} from '#/lib/app-info'
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||||
@@ -24,7 +25,7 @@ export const ChooseAccountForm = ({
|
|||||||
const [pendingDid, setPendingDid] = React.useState<string | null>(null)
|
const [pendingDid, setPendingDid] = React.useState<string | null>(null)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {resumeSession, resumeSessionOauth} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
|
||||||
const onSelect = React.useCallback(
|
const onSelect = React.useCallback(
|
||||||
@@ -34,7 +35,7 @@ export const ChooseAccountForm = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: this should be checking if it is an oauth session
|
// TODO: this should be checking if it is an oauth session
|
||||||
if (!true || !account.accessJwt) {
|
if (!USE_OAUTH && !account.accessJwt) {
|
||||||
// Move to login form.
|
// Move to login form.
|
||||||
onSelectAccount(account)
|
onSelectAccount(account)
|
||||||
return
|
return
|
||||||
@@ -46,11 +47,7 @@ export const ChooseAccountForm = ({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setPendingDid(account.did)
|
setPendingDid(account.did)
|
||||||
if (true) {
|
await resumeSession(account)
|
||||||
await resumeSessionOauth(account)
|
|
||||||
} else {
|
|
||||||
await resumeSession(account)
|
|
||||||
}
|
|
||||||
logEvent('account:loggedIn', {
|
logEvent('account:loggedIn', {
|
||||||
logContext: 'ChooseAccountForm',
|
logContext: 'ChooseAccountForm',
|
||||||
withPassword: false,
|
withPassword: false,
|
||||||
@@ -69,7 +66,6 @@ export const ChooseAccountForm = ({
|
|||||||
[
|
[
|
||||||
currentAccount,
|
currentAccount,
|
||||||
resumeSession,
|
resumeSession,
|
||||||
resumeSessionOauth,
|
|
||||||
pendingDid,
|
pendingDid,
|
||||||
onSelectAccount,
|
onSelectAccount,
|
||||||
setShowLoggedOut,
|
setShowLoggedOut,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {USE_OAUTH} from '#/lib/app-info'
|
||||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
@@ -53,14 +54,14 @@ interface LoginFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LoginForm(props: LoginFormProps) {
|
export function LoginForm(props: LoginFormProps) {
|
||||||
if (true) {
|
if (USE_OAUTH) {
|
||||||
return <OAuthLoginForm {...props} />
|
return <OAuthLoginFormInner {...props} />
|
||||||
} else {
|
} else {
|
||||||
return <LoginFormInner {...props} />
|
return <LoginFormInner {...props} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function OAuthLoginForm({
|
function OAuthLoginFormInner({
|
||||||
error,
|
error,
|
||||||
initialHandle,
|
initialHandle,
|
||||||
onPressBack,
|
onPressBack,
|
||||||
@@ -70,7 +71,7 @@ function OAuthLoginForm({
|
|||||||
const [isProcessing, setIsProcessing] = React.useState(false)
|
const [isProcessing, setIsProcessing] = React.useState(false)
|
||||||
const identifierValueRef = useRef<string>(initialHandle || '')
|
const identifierValueRef = useRef<string>(initialHandle || '')
|
||||||
|
|
||||||
const {loginOauth} = useSessionApi()
|
const {login} = useSessionApi()
|
||||||
|
|
||||||
const onPressNext = async () => {
|
const onPressNext = async () => {
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
@@ -86,7 +87,15 @@ function OAuthLoginForm({
|
|||||||
const client = getNativeOAuthClient()
|
const client = getNativeOAuthClient()
|
||||||
const res = await client.signIn(identifierValueRef.current)
|
const res = await client.signIn(identifierValueRef.current)
|
||||||
if (res.status === 'success') {
|
if (res.status === 'success') {
|
||||||
await loginOauth(res.session, 'LoginForm')
|
await login(
|
||||||
|
{
|
||||||
|
service: '',
|
||||||
|
identifier: '',
|
||||||
|
password: '',
|
||||||
|
oauthSession: res.session,
|
||||||
|
},
|
||||||
|
'LoginForm',
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
logger.error(`Invalid OAuth status: ${res.status}`)
|
logger.error(`Invalid OAuth status: ${res.status}`)
|
||||||
setError(_(msg`An error occurred during authentication.`))
|
setError(_(msg`An error occurred during authentication.`))
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Agent,
|
|
||||||
type AtpSessionData,
|
type AtpSessionData,
|
||||||
type AtpSessionEvent,
|
type AtpSessionEvent,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {type OutputSchema} from '@atproto/api/dist/client/types/com/atproto/server/getSession'
|
|
||||||
import {TID} from '@atproto/common-web'
|
import {TID} from '@atproto/common-web'
|
||||||
import {type OAuthSession} from '@atproto/oauth-client-browser'
|
|
||||||
|
|
||||||
import {networkRetry} from '#/lib/async/retry'
|
import {networkRetry} from '#/lib/async/retry'
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +23,6 @@ import {
|
|||||||
configureModerationForAccount,
|
configureModerationForAccount,
|
||||||
configureModerationForGuest,
|
configureModerationForGuest,
|
||||||
} from './moderation'
|
} from './moderation'
|
||||||
import {BSKY_OAUTH_CLIENT} from './oauth-web-client'
|
|
||||||
import {type SessionAccount} from './types'
|
import {type SessionAccount} from './types'
|
||||||
import {isSessionExpired, isSignupQueued} from './util'
|
import {isSessionExpired, isSignupQueued} from './util'
|
||||||
|
|
||||||
@@ -72,22 +68,6 @@ export async function createAgentAndResume(
|
|||||||
return agent.prepare(gates, moderation, onSessionChange)
|
return agent.prepare(gates, moderation, onSessionChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAgentOauth(session: OAuthSession) {
|
|
||||||
const agent = new Agent(session)
|
|
||||||
const account = await oauthAgentAndSessionToSessionAccountOrThrow(
|
|
||||||
agent,
|
|
||||||
session,
|
|
||||||
)
|
|
||||||
tryFetchGates(account.did, 'prefer-fresh-gates')
|
|
||||||
configureModerationForAccount(agent, account)
|
|
||||||
return {agent, account}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resumeAgentOauth(account: SessionAccount) {
|
|
||||||
const session = await BSKY_OAUTH_CLIENT.restore(account.did)
|
|
||||||
return await createAgentOauth(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createAgentAndLogin(
|
export async function createAgentAndLogin(
|
||||||
{
|
{
|
||||||
service,
|
service,
|
||||||
@@ -207,17 +187,6 @@ export async function createAgentAndCreateAccount(
|
|||||||
return agent.prepare(gates, moderation, onSessionChange)
|
return agent.prepare(gates, moderation, onSessionChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function oauthAgentAndSessionToSessionAccountOrThrow(
|
|
||||||
agent: Agent,
|
|
||||||
session: OAuthSession,
|
|
||||||
): Promise<SessionAccount> {
|
|
||||||
const account = await oauthAgentAndSessionToSessionAccount(agent, session)
|
|
||||||
if (!account) {
|
|
||||||
throw Error('Expected an active session')
|
|
||||||
}
|
|
||||||
return account
|
|
||||||
}
|
|
||||||
|
|
||||||
export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
|
export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
|
||||||
const account = agentToSessionAccount(agent)
|
const account = agentToSessionAccount(agent)
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@@ -226,32 +195,6 @@ export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
|
|||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function oauthAgentAndSessionToSessionAccount(
|
|
||||||
agent: Agent,
|
|
||||||
session: OAuthSession,
|
|
||||||
): Promise<SessionAccount | undefined> {
|
|
||||||
let data: OutputSchema
|
|
||||||
try {
|
|
||||||
const res = await agent.com.atproto.server.getSession()
|
|
||||||
data = res.data
|
|
||||||
} catch (e: any) {
|
|
||||||
logger.error(e)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
service: session.serverMetadata.issuer,
|
|
||||||
did: session.did,
|
|
||||||
handle: data.handle,
|
|
||||||
email: data.email,
|
|
||||||
emailConfirmed: data.emailConfirmed,
|
|
||||||
emailAuthFactor: data.emailAuthFactor,
|
|
||||||
active: data.active,
|
|
||||||
status: data.status,
|
|
||||||
pdsUrl: session.serverMetadata.issuer,
|
|
||||||
isSelfHosted: !session.server.issuer.startsWith(BSKY_SERVICE), // TODO: is this entryway?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function agentToSessionAccount(
|
export function agentToSessionAccount(
|
||||||
agent: BskyAgent,
|
agent: BskyAgent,
|
||||||
): SessionAccount | undefined {
|
): SessionAccount | undefined {
|
||||||
|
|||||||
+41
-64
@@ -12,8 +12,6 @@ import {
|
|||||||
createAgentAndCreateAccount,
|
createAgentAndCreateAccount,
|
||||||
createAgentAndLogin,
|
createAgentAndLogin,
|
||||||
createAgentAndResume,
|
createAgentAndResume,
|
||||||
createAgentOauth,
|
|
||||||
resumeAgentOauth,
|
|
||||||
sessionAccountToSession,
|
sessionAccountToSession,
|
||||||
} from './agent'
|
} from './agent'
|
||||||
import {getInitialState, reducer} from './reducer'
|
import {getInitialState, reducer} from './reducer'
|
||||||
@@ -21,11 +19,18 @@ import {getInitialState, reducer} from './reducer'
|
|||||||
export {isSignupQueued} from './util'
|
export {isSignupQueued} from './util'
|
||||||
import {addSessionDebugLog} from './logging'
|
import {addSessionDebugLog} from './logging'
|
||||||
export type {SessionAccount} from '#/state/session/types'
|
export type {SessionAccount} from '#/state/session/types'
|
||||||
|
import {USE_OAUTH} from '#/lib/app-info'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
|
type SessionAccount,
|
||||||
type SessionApiContext,
|
type SessionApiContext,
|
||||||
type SessionStateContext,
|
type SessionStateContext,
|
||||||
} from '#/state/session/types'
|
} from '#/state/session/types'
|
||||||
|
import {
|
||||||
|
type OauthBskyAppAgent,
|
||||||
|
oauthCreateAgent,
|
||||||
|
oauthResumeSession,
|
||||||
|
} from './oauth-agent'
|
||||||
|
|
||||||
const StateContext = React.createContext<SessionStateContext>({
|
const StateContext = React.createContext<SessionStateContext>({
|
||||||
accounts: [],
|
accounts: [],
|
||||||
@@ -38,11 +43,9 @@ const AgentContext = React.createContext<BskyAgent | null>(null)
|
|||||||
const ApiContext = React.createContext<SessionApiContext>({
|
const ApiContext = React.createContext<SessionApiContext>({
|
||||||
createAccount: async () => {},
|
createAccount: async () => {},
|
||||||
login: async () => {},
|
login: async () => {},
|
||||||
loginOauth: async () => {},
|
|
||||||
logoutCurrentAccount: async () => {},
|
logoutCurrentAccount: async () => {},
|
||||||
logoutEveryAccount: async () => {},
|
logoutEveryAccount: async () => {},
|
||||||
resumeSession: async () => {},
|
resumeSession: async () => {},
|
||||||
resumeSessionOauth: async () => {},
|
|
||||||
removeAccount: () => {},
|
removeAccount: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -98,15 +101,25 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
const login = React.useCallback<SessionApiContext['login']>(
|
const login = React.useCallback<SessionApiContext['login']>(
|
||||||
async (params, logContext) => {
|
async (params, logContext) => {
|
||||||
addSessionDebugLog({type: 'method:start', method: 'login'})
|
addSessionDebugLog({type: 'method:start', method: 'login'})
|
||||||
|
let agentAccount: {
|
||||||
|
agent: OauthBskyAppAgent | BskyAppAgent
|
||||||
|
account: SessionAccount
|
||||||
|
}
|
||||||
|
|
||||||
const signal = cancelPendingTask()
|
const signal = cancelPendingTask()
|
||||||
const {agent, account} = await createAgentAndLogin(
|
|
||||||
params,
|
if (params.oauthSession) {
|
||||||
onAgentSessionChange,
|
agentAccount = await oauthCreateAgent(params.oauthSession)
|
||||||
)
|
} else {
|
||||||
|
agentAccount = await createAgentAndLogin(params, onAgentSessionChange)
|
||||||
|
}
|
||||||
|
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {agent, account} = agentAccount
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'switched-to-account',
|
type: 'switched-to-account',
|
||||||
newAgent: agent,
|
newAgent: agent,
|
||||||
@@ -122,28 +135,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const loginOauth = React.useCallback<SessionApiContext['loginOauth']>(
|
|
||||||
async (session, logContext) => {
|
|
||||||
const signal = cancelPendingTask()
|
|
||||||
const {agent, account} = await createAgentOauth(session)
|
|
||||||
if (signal.aborted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dispatch({
|
|
||||||
type: 'switched-to-account',
|
|
||||||
newAgent: agent,
|
|
||||||
newAccount: account,
|
|
||||||
})
|
|
||||||
logger.metric(
|
|
||||||
'account:loggedIn',
|
|
||||||
{logContext, withPassword: true},
|
|
||||||
{statsig: true},
|
|
||||||
)
|
|
||||||
addSessionDebugLog({type: 'method:end', method: 'login', account})
|
|
||||||
},
|
|
||||||
[cancelPendingTask],
|
|
||||||
)
|
|
||||||
|
|
||||||
const logoutCurrentAccount = React.useCallback<
|
const logoutCurrentAccount = React.useCallback<
|
||||||
SessionApiContext['logoutEveryAccount']
|
SessionApiContext['logoutEveryAccount']
|
||||||
>(
|
>(
|
||||||
@@ -190,14 +181,27 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
account: storedAccount,
|
account: storedAccount,
|
||||||
})
|
})
|
||||||
const signal = cancelPendingTask()
|
const signal = cancelPendingTask()
|
||||||
const {agent, account} = await createAgentAndResume(
|
|
||||||
storedAccount,
|
let agentAccount: {
|
||||||
onAgentSessionChange,
|
agent: OauthBskyAppAgent | BskyAppAgent
|
||||||
)
|
account: SessionAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USE_OAUTH) {
|
||||||
|
agentAccount = await oauthResumeSession(storedAccount)
|
||||||
|
} else {
|
||||||
|
agentAccount = await createAgentAndResume(
|
||||||
|
storedAccount,
|
||||||
|
onAgentSessionChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {agent, account} = agentAccount
|
||||||
|
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'switched-to-account',
|
type: 'switched-to-account',
|
||||||
newAgent: agent,
|
newAgent: agent,
|
||||||
@@ -208,25 +212,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const resumeSessionOauth = React.useCallback<
|
|
||||||
SessionApiContext['resumeSessionOauth']
|
|
||||||
>(
|
|
||||||
async storedAccount => {
|
|
||||||
const signal = cancelPendingTask()
|
|
||||||
const {agent, account} = await resumeAgentOauth(storedAccount)
|
|
||||||
if (signal.aborted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dispatch({
|
|
||||||
type: 'switched-to-account',
|
|
||||||
newAgent: agent,
|
|
||||||
newAccount: account,
|
|
||||||
})
|
|
||||||
addSessionDebugLog({type: 'method:end', method: 'resumeSession', account})
|
|
||||||
},
|
|
||||||
[cancelPendingTask],
|
|
||||||
)
|
|
||||||
|
|
||||||
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
||||||
account => {
|
account => {
|
||||||
addSessionDebugLog({
|
addSessionDebugLog({
|
||||||
@@ -271,13 +256,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
a => a.did === synced.currentAccount?.did,
|
a => a.did === synced.currentAccount?.did,
|
||||||
)
|
)
|
||||||
// TODO: this should be checking if it is an oauth session
|
// TODO: this should be checking if it is an oauth session
|
||||||
if (syncedAccount && (true || syncedAccount?.refreshJwt)) {
|
if (syncedAccount && (USE_OAUTH || syncedAccount?.refreshJwt)) {
|
||||||
if (syncedAccount.did !== state.currentAgentState.did) {
|
if (syncedAccount.did !== state.currentAgentState.did) {
|
||||||
if (true) {
|
resumeSession(syncedAccount)
|
||||||
resumeSessionOauth(syncedAccount)
|
|
||||||
} else {
|
|
||||||
resumeSession(syncedAccount)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const agent = state.currentAgentState.agent as BskyAgent
|
const agent = state.currentAgentState.agent as BskyAgent
|
||||||
const prevSession = agent.session
|
const prevSession = agent.session
|
||||||
@@ -291,7 +272,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [state, resumeSession, resumeSessionOauth])
|
}, [state, resumeSession])
|
||||||
|
|
||||||
const stateContext = React.useMemo(
|
const stateContext = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -308,21 +289,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
() => ({
|
() => ({
|
||||||
createAccount,
|
createAccount,
|
||||||
login,
|
login,
|
||||||
loginOauth,
|
|
||||||
logoutCurrentAccount,
|
logoutCurrentAccount,
|
||||||
logoutEveryAccount,
|
logoutEveryAccount,
|
||||||
resumeSession,
|
resumeSession,
|
||||||
resumeSessionOauth,
|
|
||||||
removeAccount,
|
removeAccount,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
createAccount,
|
createAccount,
|
||||||
login,
|
login,
|
||||||
loginOauth,
|
|
||||||
logoutCurrentAccount,
|
logoutCurrentAccount,
|
||||||
logoutEveryAccount,
|
logoutEveryAccount,
|
||||||
resumeSession,
|
resumeSession,
|
||||||
resumeSessionOauth,
|
|
||||||
removeAccount,
|
removeAccount,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {type Agent, BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
|
|||||||
import {IS_TEST_USER} from '#/lib/constants'
|
import {IS_TEST_USER} from '#/lib/constants'
|
||||||
import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities'
|
import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities'
|
||||||
import {readLabelers} from './agent-config'
|
import {readLabelers} from './agent-config'
|
||||||
import {type SessionAccount} from './types'
|
|
||||||
|
|
||||||
export function configureModerationForGuest() {
|
export function configureModerationForGuest() {
|
||||||
// This global mutation is *only* OK because this code is only relevant for testing.
|
// This global mutation is *only* OK because this code is only relevant for testing.
|
||||||
@@ -14,7 +13,7 @@ export function configureModerationForGuest() {
|
|||||||
|
|
||||||
export async function configureModerationForAccount(
|
export async function configureModerationForAccount(
|
||||||
agent: Agent | BskyAgent,
|
agent: Agent | BskyAgent,
|
||||||
account: SessionAccount,
|
account: {did: string; handle?: string},
|
||||||
) {
|
) {
|
||||||
// This global mutation is *only* OK because this code is only relevant for testing.
|
// This global mutation is *only* OK because this code is only relevant for testing.
|
||||||
// Don't add any other global behavior here!
|
// Don't add any other global behavior here!
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import {Agent} from '@atproto/api'
|
||||||
|
import {type OutputSchema} from '@atproto/api/dist/client/types/com/atproto/server/getSession'
|
||||||
|
import {type OAuthSession} from '@atproto/oauth-client-browser'
|
||||||
|
|
||||||
|
import {BSKY_SERVICE} from '#/lib/constants'
|
||||||
|
import {tryFetchGates} from '#/lib/statsig/statsig'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {configureModerationForAccount} from './moderation'
|
||||||
|
import {BSKY_OAUTH_CLIENT} from './oauth-web-client'
|
||||||
|
import {type SessionAccount} from './types'
|
||||||
|
|
||||||
|
export async function oauthCreateAgent(session: OAuthSession) {
|
||||||
|
const agent = new OauthBskyAppAgent(session)
|
||||||
|
const gates = tryFetchGates(session.did, 'prefer-fresh-gates')
|
||||||
|
const moderation = configureModerationForAccount(agent, session)
|
||||||
|
return agent.prepare(gates, moderation)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function oauthResumeSession(account: SessionAccount) {
|
||||||
|
const session = await BSKY_OAUTH_CLIENT.restore(account.did)
|
||||||
|
return await oauthCreateAgent(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function oauthAgentAndSessionToSessionAccountOrThrow(
|
||||||
|
agent: Agent,
|
||||||
|
session: OAuthSession,
|
||||||
|
): Promise<SessionAccount> {
|
||||||
|
const account = await oauthAgentAndSessionToSessionAccount(agent, session)
|
||||||
|
if (!account) {
|
||||||
|
throw Error('Expected an active session')
|
||||||
|
}
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function oauthAgentAndSessionToSessionAccount(
|
||||||
|
agent: Agent,
|
||||||
|
session: OAuthSession,
|
||||||
|
): Promise<SessionAccount | undefined> {
|
||||||
|
let data: OutputSchema
|
||||||
|
try {
|
||||||
|
const res = await agent.com.atproto.server.getSession()
|
||||||
|
data = res.data
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(e)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
service: session.serverMetadata.issuer,
|
||||||
|
did: session.did,
|
||||||
|
handle: data.handle,
|
||||||
|
email: data.email,
|
||||||
|
emailConfirmed: data.emailConfirmed,
|
||||||
|
emailAuthFactor: data.emailAuthFactor,
|
||||||
|
active: data.active,
|
||||||
|
status: data.status,
|
||||||
|
pdsUrl: session.serverMetadata.issuer,
|
||||||
|
isSelfHosted: !session.server.issuer.startsWith(BSKY_SERVICE), // TODO: is this entryway?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OauthBskyAppAgent extends Agent {
|
||||||
|
#session: OAuthSession
|
||||||
|
#account?: SessionAccount
|
||||||
|
|
||||||
|
constructor(session: OAuthSession) {
|
||||||
|
super(session)
|
||||||
|
|
||||||
|
this.#session = session
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare(gates: Promise<void>, moderation: Promise<void>) {
|
||||||
|
// we have to await account fetching, since we need a separate call to getSession. this doesn't get returned in the
|
||||||
|
// OAuthSession itself, unlike the old agent
|
||||||
|
const account = await oauthAgentAndSessionToSessionAccountOrThrow(
|
||||||
|
this,
|
||||||
|
this.#session,
|
||||||
|
)
|
||||||
|
this.#account = account
|
||||||
|
|
||||||
|
await Promise.all([gates, moderation])
|
||||||
|
|
||||||
|
return {account, agent: this}
|
||||||
|
}
|
||||||
|
|
||||||
|
// does nothing, but aligning with BskyAppAgent
|
||||||
|
dispose() {}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export function createNativeOAuthClient() {
|
|||||||
client_name: 'Bluesky Native App (Hailey Demo)',
|
client_name: 'Bluesky Native App (Hailey Demo)',
|
||||||
client_uri: 'https://bsky.hailey.at',
|
client_uri: 'https://bsky.hailey.at',
|
||||||
redirect_uris: ['at.hailey.bsky:/auth/native/callback'],
|
redirect_uris: ['at.hailey.bsky:/auth/native/callback'],
|
||||||
scope: 'atproto transition:generic transition:email',
|
scope: 'atproto transition:generic transition:email transition:chat.bsky',
|
||||||
token_endpoint_auth_method: 'none',
|
token_endpoint_auth_method: 'none',
|
||||||
response_types: ['code'],
|
response_types: ['code'],
|
||||||
grant_types: ['authorization_code', 'refresh_token'],
|
grant_types: ['authorization_code', 'refresh_token'],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function createWebOAuthClient() {
|
|||||||
client_name: 'Bluesky (Hailey Demo)',
|
client_name: 'Bluesky (Hailey Demo)',
|
||||||
client_uri: 'https://bsky.hailey.at',
|
client_uri: 'https://bsky.hailey.at',
|
||||||
redirect_uris: ['https://bsky.hailey.at/auth/web/callback'],
|
redirect_uris: ['https://bsky.hailey.at/auth/web/callback'],
|
||||||
scope: 'atproto transition:generic transition:email',
|
scope: 'atproto transition:generic transition:email transition:chat.bsky',
|
||||||
token_endpoint_auth_method: 'none',
|
token_endpoint_auth_method: 'none',
|
||||||
response_types: ['code'],
|
response_types: ['code'],
|
||||||
grant_types: ['authorization_code', 'refresh_token'],
|
grant_types: ['authorization_code', 'refresh_token'],
|
||||||
|
|||||||
@@ -31,13 +31,10 @@ export type SessionApiContext = {
|
|||||||
identifier: string
|
identifier: string
|
||||||
password: string
|
password: string
|
||||||
authFactorToken?: string | undefined
|
authFactorToken?: string | undefined
|
||||||
|
oauthSession?: OAuthSession
|
||||||
},
|
},
|
||||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
loginOauth: (
|
|
||||||
session: OAuthSession,
|
|
||||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
|
||||||
) => Promise<void>
|
|
||||||
logoutCurrentAccount: (
|
logoutCurrentAccount: (
|
||||||
logContext: LogEvents['account:loggedOut']['logContext'],
|
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||||
) => void
|
) => void
|
||||||
@@ -45,6 +42,5 @@ export type SessionApiContext = {
|
|||||||
logContext: LogEvents['account:loggedOut']['logContext'],
|
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||||
) => void
|
) => void
|
||||||
resumeSession: (account: SessionAccount) => Promise<void>
|
resumeSession: (account: SessionAccount) => Promise<void>
|
||||||
resumeSessionOauth: (account: SessionAccount) => Promise<void>
|
|
||||||
removeAccount: (account: SessionAccount) => void
|
removeAccount: (account: SessionAccount) => void
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user