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 15, 2024
1 parent 4d32866 commit d481f72
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 22 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,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,
Expand All @@ -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 {
/**
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
}
}
40 changes: 20 additions & 20 deletions packages/oauth/oauth-client-browser/example/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 <p>{error || 'Loading...'}</p>
Expand Down
6 changes: 6 additions & 0 deletions packages/oauth/oauth-client-browser/example/src/oauth.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { BskyAgent } from '@atproto/api'
import { OAuthAuthorizeOptions, OAuthClient } from '@atproto/oauth-client'
import {
BrowserOAuthClientFactory,
LoginContinuedInParentWindowError,
} 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'

Expand Down Expand Up @@ -32,6 +34,8 @@ export function useOAuth() {
const [loading, setLoading] = useState(true)
const [state, setState] = useState<undefined | string>(undefined)

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

useEffect(() => {
if (client != null) {
localStorage.setItem(CURRENT_SESSION_ID_KEY, client.sessionId)
Expand All @@ -42,6 +46,7 @@ export function useOAuth() {

useEffect(() => {
let cleanup = false

setInitialized(false)
setClient(undefined)
setClients({})
Expand Down Expand Up @@ -124,6 +129,7 @@ export function useOAuth() {
initialized,
clients,
client: client ?? null,
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 d481f72

Please sign in to comment.