···
import { ExpoOAuthClient } from '@atproto/oauth-client-expo';
2
-
import { config } from '@/constants/config';
2
+
import Constants from 'expo-constants';
5
+
* Get configuration value from Expo Constants
6
+
* Works in both Expo Go (expoConfig) and native builds (manifestExtra)
8
+
function getConfig(key: string): string {
9
+
// Try multiple sources in order:
10
+
// 1. expoConfig.extra (Expo Go, dev builds)
11
+
// 2. manifestExtra (native builds via EAS)
12
+
// 3. process.env (fallback)
14
+
Constants.expoConfig?.extra?.[key] ??
15
+
Constants.manifest2?.extra?.expoClient?.extra?.[key] ??
16
+
Constants.manifest?.extra?.[key] ??
21
+
`Missing required configuration: ${key}\n` +
22
+
`Please ensure it's set in app.config.js extra field.\n` +
23
+
`See .env.example for required variables.`
30
+
// Get OAuth configuration - will throw if not properly configured
31
+
const CLIENT_ID = getConfig('EXPO_PUBLIC_OAUTH_CLIENT_ID');
32
+
const CLIENT_URI = getConfig('EXPO_PUBLIC_OAUTH_CLIENT_URI');
33
+
const REDIRECT_URI = getConfig('EXPO_PUBLIC_OAUTH_REDIRECT_URI');
34
+
const CUSTOM_SCHEME = getConfig('EXPO_PUBLIC_CUSTOM_SCHEME');
36
+
// Build the custom scheme callback URI
37
+
const CUSTOM_SCHEME_CALLBACK = `${CUSTOM_SCHEME}:/oauth/callback`;
* Initialize the OAuth client
···
export const oauthClient = new ExpoOAuthClient({
// Client metadata - must match hosted client-metadata.json
11
-
client_id: config.clientMetadata.client_id,
12
-
client_name: config.clientMetadata.client_name,
13
-
client_uri: config.clientMetadata.client_uri,
14
-
redirect_uris: config.clientMetadata.redirect_uris,
15
-
scope: config.clientMetadata.scope,
16
-
grant_types: config.clientMetadata.grant_types,
17
-
response_types: config.clientMetadata.response_types,
18
-
application_type: config.clientMetadata.application_type,
19
-
token_endpoint_auth_method: config.clientMetadata.token_endpoint_auth_method,
20
-
dpop_bound_access_tokens: config.clientMetadata.dpop_bound_access_tokens,
46
+
client_id: CLIENT_ID,
47
+
client_name: 'Coves',
48
+
client_uri: CLIENT_URI,
50
+
REDIRECT_URI, // HTTPS redirect (works better on Android)
51
+
CUSTOM_SCHEME_CALLBACK, // Fallback custom scheme
53
+
scope: 'atproto transition:generic',
54
+
grant_types: ['authorization_code', 'refresh_token'],
55
+
response_types: ['code'],
56
+
application_type: 'native',
57
+
token_endpoint_auth_method: 'none', // Public client
58
+
dpop_bound_access_tokens: true,
// Handle resolver - resolves atProto handles to DID documents
···
export async function initializeOAuth(storedDid?: string | null) {
38
-
console.log('Initializing OAuth client...');
77
+
console.log('Initializing OAuth client...');
78
+
console.log('Client ID:', CLIENT_ID);
79
+
console.log('Redirect URI:', REDIRECT_URI);
// Only try to restore if we have a stored DID
42
-
console.log('No stored DID found, skipping session restore');
85
+
console.log('No stored DID found, skipping session restore');
46
-
console.log('Attempting to restore session for:', storedDid);
91
+
console.log('Attempting to restore session for:', storedDid);
const session = await oauthClient.restore(storedDid);
50
-
console.log('Successfully restored session for:', session.sub);
97
+
console.log('Successfully restored session for:', session.sub);
54
-
console.log('No valid session found');
103
+
console.log('No valid session found');
console.error('Failed to restore session:', error);
···
const result = await oauthClient.signIn(handle, {
signal: new AbortController().signal,
// Force HTTPS redirect URI (works better on Android than custom schemes)
75
-
redirect_uri: process.env.EXPO_PUBLIC_OAUTH_REDIRECT_URI!,
125
+
redirect_uri: REDIRECT_URI,
// Check the result status
···
export async function signOut(sub: string) {
await oauthClient.revoke(sub);
96
-
console.log('Signed out successfully');
147
+
console.log('Signed out successfully');
console.error('Sign out failed:', error);