Skip to content

Commit

Permalink
feat(api): add support for oauth client
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieusieben committed May 13, 2024
1 parent e0fface commit 69632ad
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 25 deletions.
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 9 additions & 2 deletions packages/api/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OAuthClient } from '@atproto/oauth-client'
import { AtpClient } from './client'
import { BSKY_LABELER_DID } from './const'
import { SessionManager, isSessionManager } from './session/session-manager'
Expand All @@ -6,10 +7,14 @@ import {
StatelessSessionManagerOptions,
} from './session/stateless-session-handler'
import { AtpAgentGlobalOpts, AtprotoServiceType } from './types'
import { OAuthSessionManager } from './dispatcher/oauth-dispatcher'

const MAX_LABELERS = 10

export type AtpAgentOptions = SessionManager | StatelessSessionManagerOptions
export type AtpAgentOptions =
| SessionManager
| OAuthClient
| StatelessSessionManagerOptions

export class AtpAgent {
/**
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions packages/api/src/session/oauth-session-manager.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
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<URL> {
return new URL(this.client.serverMetadata.issuer)
}

async getDid(): Promise<string> {
const { sub } = await this.client.getTokenSet()
return sub
}
}
10 changes: 5 additions & 5 deletions packages/oauth-client-browser-example/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type AppState = {
function App() {
const {
initialized,
atpClient,
agent,
client,
signedIn,
signOut,
Expand All @@ -52,17 +52,17 @@ function App() {
const info = await client.getUserinfo()
console.log('info', info)

if (!atpClient) return
if (!agent) return

// A call that requires to be authenticated
console.log(
await atpClient.com.atproto.server.getServiceAuth({
await agent.com.atproto.server.getServiceAuth({
aud: info.sub,
}),
)

// This call does not require authentication
const profile = await atpClient.com.atproto.repo.getRecord({
const profile = await agent.com.atproto.repo.getRecord({
repo: info.sub,
collection: 'app.bsky.actor.profile',
rkey: 'self',
Expand All @@ -71,7 +71,7 @@ function App() {
console.log(profile)

setProfile(profile.data)
}, [client, atpClient])
}, [client, agent])

if (!initialized) {
return <p>{error || 'Loading...'}</p>
Expand Down
23 changes: 5 additions & 18 deletions packages/oauth-client-browser-example/src/oauth.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { AtpClient, schemas } from '@atproto/api'
import { BskyAgent } from '@atproto/api'
import { OAuthAuthorizeOptions, OAuthClient } from '@atproto/oauth-client'
import {
BrowserOAuthClientFactory,
LoginContinuedInParentWindowError,
} from '@atproto/oauth-client-browser'
import { XrpcAgent, XrpcClient } from '@atproto/xrpc'

import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

const CURRENT_SESSION_ID_KEY = 'CURRENT_SESSION_ID_KEY'

export function useOAuth(factory: BrowserOAuthClientFactory) {
const [initialized, setInitialized] = useState(false)
const [client, setClient] = useState<undefined | null | OAuthClient>(void 0)
const [clients, setClients] = useState<{ [_: string]: OAuthClient }>({})
const [atpClient, setAtpClient] = useState<AtpClient | null>(null)
const [error, setError] = useState<null | string>(null)
const [loading, setLoading] = useState(true)
const [state, setState] = useState<undefined | string>(undefined)

const agent = useMemo(() => (client ? new BskyAgent(client) : null), [client])

const semaphore = useRef(0)

useEffect(() => {
Expand All @@ -29,19 +29,6 @@ export function useOAuth(factory: BrowserOAuthClientFactory) {
}
}, [client])

useEffect(() => {
const atpClient = client
? new AtpClient(
new XrpcClient(
new XrpcAgent((url, init) => client.request(url, init)),
schemas,
),
)
: null

setAtpClient(atpClient)
}, [client])

useEffect(() => {
semaphore.current++

Expand Down Expand Up @@ -126,7 +113,7 @@ export function useOAuth(factory: BrowserOAuthClientFactory) {
initialized,
clients,
client: client ?? null,
atpClient,
agent,
state,
loading,
error,
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 69632ad

Please sign in to comment.