From d481f72487797eb908aab01ef134f8f54493b4af Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Fri, 26 Apr 2024 10:57:55 +0200 Subject: [PATCH] feat(api): add support for oauth client --- packages/api/package.json | 1 + packages/api/src/agent.ts | 11 ++++- .../api/src/session/oauth-session-manager.ts | 33 +++++++++++++++ .../oauth-client-browser/example/src/app.tsx | 40 +++++++++---------- .../oauth-client-browser/example/src/oauth.ts | 6 +++ pnpm-lock.yaml | 3 ++ 6 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 packages/api/src/session/oauth-session-manager.ts diff --git a/packages/api/package.json b/packages/api/package.json index f4731d5faf1..c411ab30cd7 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -24,6 +24,7 @@ "dependencies": { "@atproto/common-web": "workspace:^", "@atproto/lexicon": "workspace:^", + "@atproto/oauth-client": "workspace:^", "@atproto/syntax": "workspace:^", "@atproto/xrpc": "workspace:^", "multiformats": "^9.9.0", diff --git a/packages/api/src/agent.ts b/packages/api/src/agent.ts index 63f33ea33f4..75923183fe5 100644 --- a/packages/api/src/agent.ts +++ b/packages/api/src/agent.ts @@ -1,5 +1,7 @@ +import { OAuthClient } from '@atproto/oauth-client' import { AtpClient } from './client' import { BSKY_LABELER_DID } from './const' +import { OAuthSessionManager } from './session/oauth-session-manager' import { SessionManager, isSessionManager } from './session/session-manager' import { StatelessSessionManager, @@ -9,7 +11,10 @@ import { AtpAgentGlobalOpts, AtprotoServiceType } from './types' const MAX_LABELERS = 10 -export type AtpAgentOptions = SessionManager | StatelessSessionManagerOptions +export type AtpAgentOptions = + | SessionManager + | OAuthClient + | StatelessSessionManagerOptions export class AtpAgent { /** @@ -39,7 +44,9 @@ export class AtpAgent { constructor(options: AtpAgentOptions) { this.sessionManager = isSessionManager(options) ? options - : new StatelessSessionManager(options) + : options instanceof OAuthClient + ? new OAuthSessionManager(options) + : new StatelessSessionManager(options) this.api = new AtpClient((...args) => // The function needs to be "bound" to the right context diff --git a/packages/api/src/session/oauth-session-manager.ts b/packages/api/src/session/oauth-session-manager.ts new file mode 100644 index 00000000000..f9dee5804b0 --- /dev/null +++ b/packages/api/src/session/oauth-session-manager.ts @@ -0,0 +1,33 @@ +import { FetchError, OAuthClient } from '@atproto/oauth-client' +import { XRPCError } from '@atproto/xrpc' +import { SessionManager } from './session-manager' + +export class OAuthSessionManager implements SessionManager { + constructor(readonly client: OAuthClient) {} + + async fetchHandler(url: string, init: RequestInit): Promise { + try { + return await this.client.request(url, init) + } catch (cause) { + if (cause instanceof FetchError) { + throw new XRPCError( + cause.statusCode, + undefined, + cause.message, + undefined, + { cause }, + ) + } + throw cause + } + } + + async getServiceUrl(): Promise { + return new URL(this.client.serverMetadata.issuer) + } + + async getDid(): Promise { + const { sub } = await this.client.getTokenSet() + return sub + } +} diff --git a/packages/oauth/oauth-client-browser/example/src/app.tsx b/packages/oauth/oauth-client-browser/example/src/app.tsx index 884418de487..c3f95668aef 100644 --- a/packages/oauth/oauth-client-browser/example/src/app.tsx +++ b/packages/oauth/oauth-client-browser/example/src/app.tsx @@ -19,8 +19,16 @@ export type AppState = { */ function App() { - const { initialized, client, signedIn, signOut, error, loading, signIn } = - useOAuth() + const { + initialized, + agent, + client, + signedIn, + signOut, + error, + loading, + signIn, + } = useOAuth() const [profile, setProfile] = useState<{ value: { displayName?: string } } | null>(null) @@ -31,34 +39,26 @@ function App() { const info = await client.getUserinfo() console.log('info', info) - if (!client) return + if (!agent) return // A call that requires to be authenticated console.log( - await client - .request( - '/xrpc/com.atproto.server.getServiceAuth?' + - new URLSearchParams({ aud: info.sub }).toString(), - ) - .then((r) => r.json()), + await agent.com.atproto.server.getServiceAuth({ + aud: info.sub, + }), ) // This call does not require authentication - const profile = await client - .request( - '/xrpc/com.atproto.repo.getRecord?' + - new URLSearchParams({ - repo: info.sub, - collection: 'app.bsky.actor.profile', - rkey: 'self', - }).toString(), - ) - .then((r) => r.json()) + const profile = await agent.com.atproto.repo.getRecord({ + repo: info.sub, + collection: 'app.bsky.actor.profile', + rkey: 'self', + }) console.log(profile) setProfile(profile.data) - }, [client]) + }, [client, agent]) if (!initialized) { return

{error || 'Loading...'}

diff --git a/packages/oauth/oauth-client-browser/example/src/oauth.ts b/packages/oauth/oauth-client-browser/example/src/oauth.ts index fc8770d4cc7..22496609cfb 100644 --- a/packages/oauth/oauth-client-browser/example/src/oauth.ts +++ b/packages/oauth/oauth-client-browser/example/src/oauth.ts @@ -1,3 +1,4 @@ +import { BskyAgent } from '@atproto/api' import { OAuthAuthorizeOptions, OAuthClient } from '@atproto/oauth-client' import { BrowserOAuthClientFactory, @@ -5,6 +6,7 @@ import { } from '@atproto/oauth-client-browser' import { oauthClientMetadataSchema } from '@atproto/oauth-types' import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' const CURRENT_SESSION_ID_KEY = 'CURRENT_SESSION_ID_KEY' @@ -32,6 +34,8 @@ export function useOAuth() { const [loading, setLoading] = useState(true) const [state, setState] = useState(undefined) + const agent = useMemo(() => (client ? new BskyAgent(client) : null), [client]) + useEffect(() => { if (client != null) { localStorage.setItem(CURRENT_SESSION_ID_KEY, client.sessionId) @@ -42,6 +46,7 @@ export function useOAuth() { useEffect(() => { let cleanup = false + setInitialized(false) setClient(undefined) setClients({}) @@ -124,6 +129,7 @@ export function useOAuth() { initialized, clients, client: client ?? null, + agent, state, loading, error, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f18d743ff99..1ea3e1e6588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: '@atproto/lexicon': specifier: workspace:^ version: link:../lexicon + '@atproto/oauth-client': + specifier: workspace:^ + version: link:../oauth/oauth-client '@atproto/syntax': specifier: workspace:^ version: link:../syntax