Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at theme-changes 168 lines 4.6 kB view raw
1import {Agent, type AtpSessionData} from '@atproto/api' 2import {type OutputSchema} from '@atproto/api/dist/client/types/com/atproto/server/getSession' 3import {type OAuthSession} from '@atproto/oauth-client-browser' 4 5import {BLUESKY_PROXY_HEADER, BSKY_SERVICE} from '#/lib/constants' 6import {logger} from '#/logger' 7import {sessionAccountToSession} from './agent' 8import {configureModerationForAccount} from './moderation' 9import {restoreOAuthSession} from './oauth-client-adapter' 10import {type SessionAccount} from './types' 11 12export async function oauthCreateAgent(session: OAuthSession) { 13 const agent = new OauthBskyAppAgent(session) 14 const account = await oauthAgentAndSessionToSessionAccountOrThrow( 15 agent, 16 session, 17 ) 18 const gates = Promise.resolve() 19 const moderation = configureModerationForAccount(agent, account) 20 return agent.prepare(account, gates, moderation) 21} 22 23const OAUTH_RESTORE_TIMEOUT_MS = 10_000 24 25export async function oauthResumeSession(account: SessionAccount) { 26 let session: OAuthSession 27 try { 28 session = await Promise.race([ 29 restoreOAuthSession(account.did), 30 new Promise<never>((_, reject) => 31 setTimeout( 32 () => reject(new Error('OAuth session restore timed out')), 33 OAUTH_RESTORE_TIMEOUT_MS, 34 ), 35 ), 36 ]) 37 } catch (e) { 38 logger.error('oauthResumeSession: restore failed', { 39 did: account.did, 40 error: e instanceof Error ? e.message : String(e), 41 }) 42 throw e 43 } 44 return await oauthCreateAgent(session) 45} 46 47export async function oauthAgentAndSessionToSessionAccountOrThrow( 48 agent: Agent, 49 session: OAuthSession, 50): Promise<SessionAccount> { 51 const account = await oauthAgentAndSessionToSessionAccount(agent, session) 52 if (!account) { 53 throw Error('Expected an active session') 54 } 55 return account 56} 57 58export async function oauthAgentAndSessionToSessionAccount( 59 agent: Agent, 60 session: OAuthSession, 61): Promise<SessionAccount | undefined> { 62 let data: OutputSchema 63 try { 64 const res = await Promise.race([ 65 agent.com.atproto.server.getSession(), 66 new Promise<never>((_, reject) => 67 setTimeout( 68 () => reject(new Error('getSession timed out')), 69 OAUTH_RESTORE_TIMEOUT_MS, 70 ), 71 ), 72 ]) 73 data = res.data 74 } catch (e: any) { 75 logger.error('oauthAgentAndSessionToSessionAccount: getSession failed', e) 76 return undefined 77 } 78 let aud: string 79 try { 80 const tokenInfo = await Promise.race([ 81 session.getTokenInfo(false), 82 new Promise<never>((_, reject) => 83 setTimeout( 84 () => reject(new Error('getTokenInfo timed out')), 85 OAUTH_RESTORE_TIMEOUT_MS, 86 ), 87 ), 88 ]) 89 aud = tokenInfo.aud 90 } catch (e: any) { 91 logger.error('oauthAgentAndSessionToSessionAccount: getTokenInfo failed', e) 92 return undefined 93 } 94 return { 95 service: session.serverMetadata.issuer, 96 did: session.did, 97 handle: data.handle, 98 email: data.email, 99 emailConfirmed: data.emailConfirmed, 100 emailAuthFactor: data.emailAuthFactor, 101 active: data.active, 102 status: data.status, 103 pdsUrl: aud, 104 isSelfHosted: !session.server.issuer.startsWith(BSKY_SERVICE), 105 isOauthSession: true, 106 } 107} 108 109export class OauthBskyAppAgent extends Agent { 110 readonly sessionManager: OAuthSession 111 session?: AtpSessionData 112 private _serviceUrl: URL 113 private _pdsUrl?: URL 114 115 constructor(session: OAuthSession) { 116 super(session) 117 this.sessionManager = session 118 this._serviceUrl = new URL(session.serverMetadata.issuer) 119 } 120 121 clone(): this { 122 const cloned = this.copyInto(new OauthBskyAppAgent(this.sessionManager)) 123 cloned.session = this.session 124 cloned._serviceUrl = this._serviceUrl 125 cloned._pdsUrl = this._pdsUrl 126 return cloned as this 127 } 128 129 get serviceUrl() { 130 return this._serviceUrl 131 } 132 133 get pdsUrl() { 134 return this._pdsUrl 135 } 136 137 get dispatchUrl() { 138 return this.pdsUrl || this.serviceUrl 139 } 140 141 /** @deprecated use {@link serviceUrl} instead */ 142 get service() { 143 return this.serviceUrl 144 } 145 146 async prepare( 147 account: SessionAccount, 148 gates: Promise<void>, 149 moderation: Promise<void>, 150 ) { 151 this.session = sessionAccountToSession(account) 152 this._serviceUrl = new URL(account.service) 153 this._pdsUrl = account.pdsUrl ? new URL(account.pdsUrl) : undefined 154 this.configureProxy(BLUESKY_PROXY_HEADER.get()) 155 156 await Promise.all([gates, moderation]) 157 158 return {account, agent: this} 159 } 160 161 dispose() {} 162 163 cloneWithoutProxy(): OauthBskyAppAgent { 164 const cloned = this.clone() 165 cloned.configureProxy(null) 166 return cloned 167 } 168}