···11+# Set to "production" for deployed environments.
22+# When not "production", OAuth uses the 127.0.0.1 loopback exception
33+# and does not require a hosted client-metadata.json.
44+NODE_ENV=development
55+66+# Required in production only. The full public URL where
77+# client-metadata.json is served (e.g., https://yourapp.com/client-metadata.json).
88+# Leave blank for local development.
99+PUBLIC_URL=
1010+1111+# Server port. In dev, this is used to construct the loopback client_id.
1212+PORT=5173
+9-44
src/auth.ts
···11-import { BrowserOAuthClient, type OAuthClientMetadataInput } from '@atproto/oauth-client-browser'
11+import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
22+import { oauthConfig } from './config'
2334// The metadata must match what is served at the client_id URL
44-const clientMetadata = (() => {
55- const isLocal = ['127.0.0.1', 'localhost'].includes(window.location.hostname)
66-77- if (isLocal) {
88- return {
99- client_id: 'http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A5173&scope=atproto%20transition%3Ageneric',
1010- client_name: 'Drydown App (Dev)',
1111- client_uri: 'http://127.0.0.1:5173',
1212- logo_uri: 'http://127.0.0.1:5173/vite.svg',
1313- tos_uri: 'http://127.0.0.1:5173/tos',
1414- policy_uri: 'http://127.0.0.1:5173/policy',
1515- redirect_uris: ['http://127.0.0.1:5173'],
1616- scope: 'atproto transition:generic',
1717- grant_types: ['authorization_code', 'refresh_token'],
1818- response_types: ['code'],
1919- token_endpoint_auth_method: 'none',
2020- application_type: 'web',
2121- dpop_bound_access_tokens: true,
2222- } satisfies OAuthClientMetadataInput
2323- }
2424-2525- return {
2626- client_id: 'https://drydown.social/client-metadata.json',
2727- client_name: 'Drydown App',
2828- client_uri: 'https://drydown.social',
2929- logo_uri: 'https://drydown.social/vite.svg',
3030- tos_uri: 'https://drydown.social/tos',
3131- policy_uri: 'https://drydown.social/policy',
3232- redirect_uris: [
3333- 'https://drydown.social/',
3434- ],
3535- scope: 'atproto transition:generic',
3636- grant_types: ['authorization_code', 'refresh_token'],
3737- response_types: ['code'],
3838- token_endpoint_auth_method: 'none',
3939- application_type: 'web',
4040- dpop_bound_access_tokens: true,
4141- } satisfies OAuthClientMetadataInput
4242-})()
55+// For local development, we use BrowserOAuthClient.load which handles loopback metadata generation
436447let client: BrowserOAuthClient | undefined
458469export async function getClient() {
4710 if (client) return client
1111+1212+ console.log('Environment:', { isDev: import.meta.env.DEV, clientId: oauthConfig.clientId })
48134914 try {
5050- client = new BrowserOAuthClient({
1515+ client = await BrowserOAuthClient.load({
1616+ clientId: oauthConfig.clientId,
5117 handleResolver: 'https://bsky.social',
5252- clientMetadata,
5318 fetch: window.fetch.bind(window), // Fix for "Illegal invocation" in Safari/Strict mode
5419 })
5520 return client
···6025}
61266227export async function initAuth() {
2828+ console.log('Initializing Auth...')
6329 const c = await getClient()
6430 const result = await c.init()
6531 return result
···6935 const c = await getClient()
70367137 // Explicitly use the first redirect_uri from the active configuration
7272- // This ensures consistency whether in Dev or Prod
7373- const redirectUri = clientMetadata.redirect_uris?.[0]
3838+ const redirectUri = oauthConfig.redirectUri
74397540 return await c.signIn(handle, {
7641 state: undefined,