Skip to content

Commit 5b55077

Browse files
rickyromboclaude
andcommitted
Update web upload example to new OAuth API (PKCE)
- Move redirectUri to SDK config (set once in sdk.ts) - Replace loginAsync/getRedirectResult with login/handleRedirect - Use hasRedirectResult() and isAuthenticated() as async methods - Remove sdk.oauth null-checks (oauth is always defined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d23812e commit 5b55077

4 files changed

Lines changed: 81 additions & 20 deletions

File tree

packages/web/examples/upload/README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,30 @@ A serverless Audius track upload example using SDK + OAuth PKCE entirely in the
44

55
## How it works
66

7-
1. User clicks "Sign in with Audius" — `sdk.oauth.loginAsync({ scope: 'write' })` opens a popup, runs the PKCE flow, and stores the access token internally in the SDK's `tokenStore`.
8-
2. User picks an audio file (and optional cover art), fills in title/genre/description.
9-
3. On upload:
7+
1. User clicks "Sign in with Audius" — `sdk.oauth.login({ scope: 'write', display: 'popup' })` opens a popup and runs the PKCE flow. The `redirectUri` is set once in the SDK config (see `src/sdk.ts`).
8+
2. The popup redirects to Audius, then back to `redirectUri` (this same app) with an authorization code in the URL.
9+
3. On the callback page (inside the popup), `sdk.oauth.handleRedirect()` detects `window.opener`, forwards the authorization code back to the parent window via `postMessage`, and closes the popup.
10+
4. The parent's `login()` promise resolves; call `sdk.oauth.getUser()` to retrieve the authenticated user's profile. The access token is stored internally in the SDK's `tokenStore`.
11+
5. User picks an audio file (and optional cover art), fills in title/genre/description.
12+
6. On upload:
1013
- `sdk.uploads.createAudioUpload({ file })` uploads audio to a storage node → returns `trackCid`, `origFileCid`, `duration`, etc.
1114
- `sdk.uploads.createImageUpload({ file })` uploads cover art → returns `coverArtSizes` CID.
1215
- `sdk.tracks.createTrack({ userId, metadata })` registers the track on-chain, authenticated via the stored OAuth access token.
1316

1417
## Setup
1518

19+
### 1. Register the redirect URI
20+
21+
In your developer app settings at **audius.co/settings → Developer Apps**, add the following redirect URI:
22+
23+
```
24+
http://localhost:5177
25+
```
26+
27+
This is required so the OAuth server will accept the callback URL when the popup redirects back to this app. For production deployments, register your deployed URL instead (e.g. `https://yourapp.com`).
28+
29+
### 2. Configure and run
30+
1631
```bash
1732
cp .env.example .env
1833
# Edit .env and set VITE_AUDIUS_API_KEY to your developer app API key

packages/web/examples/upload/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/web/examples/upload/src/App.tsx

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,69 @@ export default function App() {
5050
const audioInputRef = useRef<HTMLInputElement>(null)
5151
const coverInputRef = useRef<HTMLInputElement>(null)
5252

53-
// Re-initialize SDK on mount to pick up any stored OAuth state from a
54-
// previous page load (e.g. after a full-page redirect OAuth flow).
53+
// On mount: restore an existing session or handle an OAuth popup callback.
5554
useEffect(() => {
56-
getSDK()
55+
const sdk = getSDK()
56+
57+
// If the URL contains ?code=&state=, this page is returning from an OAuth
58+
// redirect. handleRedirect() handles both cases:
59+
// - popup: window.opener exists → forwards the code to the parent
60+
// and closes the popup. login() in the parent resolves.
61+
// - fullScreen: no opener → performs the PKCE exchange locally and
62+
// stores the tokens. Call getUser() afterwards to get the
63+
// profile.
64+
if (sdk.oauth.hasRedirectResult()) {
65+
setLoading(true)
66+
sdk.oauth
67+
.handleRedirect()
68+
.then(() => sdk.oauth.getUser())
69+
.then((user) => {
70+
setProfile(user)
71+
setScreen('signed-in')
72+
})
73+
.catch((e: unknown) => {
74+
setError(e instanceof Error ? e.message : 'Sign-in failed')
75+
})
76+
.finally(() => setLoading(false))
77+
return
78+
}
79+
80+
// Otherwise, if a session is already stored from a previous login, restore
81+
// the profile via getUser() so the user doesn't have to sign in again.
82+
sdk.oauth.isAuthenticated().then((authenticated) => {
83+
if (!authenticated) return
84+
setLoading(true)
85+
sdk.oauth
86+
.getUser()
87+
.then((user) => {
88+
setProfile(user)
89+
setScreen('signed-in')
90+
})
91+
.catch(() => {
92+
// Token may be expired — silently fall back to the sign-in screen.
93+
})
94+
.finally(() => setLoading(false))
95+
})
5796
}, [])
5897

5998
const handleSignIn = useCallback(async () => {
6099
setError(null)
61100
setLoading(true)
62101
try {
63102
const sdk = getSDK()
64-
if (!sdk.oauth) {
65-
setError('OAuth not available — make sure VITE_AUDIUS_API_KEY is set.')
66-
return
67-
}
68-
// loginAsync with scope='write' triggers the PKCE flow. The SDK opens
69-
// a popup, exchanges the auth code for tokens internally, and resolves
70-
// with the user profile. No server required.
71-
const { profile: p } = await sdk.oauth.loginAsync({
103+
// login opens a popup pointing to the Audius consent screen. The popup
104+
// redirects to redirectUri (set in SDK config) after the user approves.
105+
// handleRedirect() on that page detects window.opener, forwards the
106+
// auth code back to this window, and closes the popup.
107+
//
108+
// To use a full-page redirect instead, change display to 'fullScreen'.
109+
// login will navigate away; handleRedirect() on the next mount will
110+
// complete the exchange and restore the signed-in state.
111+
await sdk.oauth.login({
72112
scope: 'write',
73-
redirectUri: 'postMessage',
74113
display: 'popup'
75114
})
115+
const p = await sdk.oauth.getUser()
76116
setProfile(p)
77117
setScreen('signed-in')
78118
setResult(null)
@@ -85,7 +125,7 @@ export default function App() {
85125

86126
const handleSignOut = useCallback(async () => {
87127
const sdk = getSDK()
88-
await sdk.oauth?.logout().catch(() => {})
128+
await sdk.oauth.logout().catch(() => {})
89129
setProfile(null)
90130
setAudioFile(null)
91131
setCoverFile(null)

packages/web/examples/upload/src/sdk.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,25 @@ let sdkInstance: AudiusSdk | null = null
1010
/**
1111
* Returns a singleton SDK instance initialised with the developer app API key.
1212
* The API key enables PKCE-based OAuth for the write scope so that
13-
* sdk.oauth.loginAsync({ scope: 'write' }) stores an access token internally,
13+
* sdk.oauth.login({ scope: 'write' }) stores an access token internally,
1414
* allowing sdk.tracks.createTrack to be called directly from the browser
1515
* without a backend server.
1616
*/
1717
export function getSDK(): AudiusSdk {
1818
if (!sdkInstance) {
19+
// Use the current page as the redirect URI — the popup redirects here and
20+
// handleRedirect() detects window.opener, forwards the code to the parent,
21+
// and closes the popup.
22+
const redirectUri =
23+
window.location.origin + window.location.pathname
1924
sdkInstance = config.apiKey
2025
? sdk({
2126
appName: APP_NAME,
2227
apiKey: config.apiKey,
28+
redirectUri,
2329
environment: config.environment
2430
})
25-
: sdk({ appName: APP_NAME, environment: config.environment })
31+
: sdk({ appName: APP_NAME, redirectUri, environment: config.environment })
2632
}
2733
return sdkInstance
2834
}

0 commit comments

Comments
 (0)