1/** 2 * Cloudflare Worker for Coves OAuth 3 * Handles client metadata and OAuth callbacks with Android Intent URL support 4 */ 5 6export default { 7 async fetch(request) { 8 const url = new URL(request.url); 9 10 // Serve client-metadata.json 11 if (url.pathname === '/client-metadata.json') { 12 return new Response(JSON.stringify({ 13 client_id: 'https://lingering-darkness-50a6.brettmay0212.workers.dev/client-metadata.json', 14 client_name: 'Coves', 15 client_uri: 'https://lingering-darkness-50a6.brettmay0212.workers.dev/client-metadata.json', 16 redirect_uris: [ 17 'https://lingering-darkness-50a6.brettmay0212.workers.dev/oauth/callback', 18 'dev.workers.brettmay0212.lingering-darkness-50a6:/oauth/callback' 19 ], 20 scope: 'atproto transition:generic', 21 grant_types: ['authorization_code', 'refresh_token'], 22 response_types: ['code'], 23 application_type: 'native', 24 token_endpoint_auth_method: 'none', 25 dpop_bound_access_tokens: true 26 }), { 27 headers: { 'Content-Type': 'application/json' } 28 }); 29 } 30 31 // Handle OAuth callback - redirect to app 32 if (url.pathname === '/oauth/callback') { 33 const params = url.search; // Preserve query params (e.g., ?state=xxx&code=xxx) 34 const userAgent = request.headers.get('User-Agent') || ''; 35 const isAndroid = /Android/i.test(userAgent); 36 37 // Build the appropriate deep link based on platform 38 let deepLink; 39 if (isAndroid) { 40 // Android: Use Intent URL format (works reliably on all browsers) 41 // Format: intent://path?query#Intent;scheme=SCHEME;package=PACKAGE;end 42 const pathAndQuery = `/oauth/callback${params}`; 43 deepLink = `intent:/${pathAndQuery}#Intent;scheme=dev.workers.brettmay0212.lingering-darkness-50a6;package=social.coves;end`; 44 } else { 45 // iOS: Use standard custom scheme 46 deepLink = `dev.workers.brettmay0212.lingering-darkness-50a6:/oauth/callback${params}`; 47 } 48 49 return new Response(` 50 <!DOCTYPE html> 51 <html> 52 <head> 53 <meta charset="utf-8"> 54 <meta name="viewport" content="width=device-width, initial-scale=1"> 55 <title>Authorization Successful</title> 56 <style> 57 body { 58 font-family: system-ui, -apple-system, sans-serif; 59 display: flex; 60 align-items: center; 61 justify-content: center; 62 min-height: 100vh; 63 margin: 0; 64 background: #f5f5f5; 65 } 66 .container { 67 text-align: center; 68 padding: 2rem; 69 background: white; 70 border-radius: 8px; 71 box-shadow: 0 2px 8px rgba(0,0,0,0.1); 72 max-width: 400px; 73 } 74 .success { color: #22c55e; font-size: 3rem; margin-bottom: 1rem; } 75 h1 { margin: 0 0 0.5rem; color: #1f2937; font-size: 1.5rem; } 76 p { color: #6b7280; margin: 0.5rem 0; } 77 a { 78 display: inline-block; 79 margin-top: 1rem; 80 padding: 0.75rem 1.5rem; 81 background: #3b82f6; 82 color: white; 83 text-decoration: none; 84 border-radius: 6px; 85 font-weight: 500; 86 } 87 a:hover { 88 background: #2563eb; 89 } 90 </style> 91 </head> 92 <body> 93 <div class="container"> 94 <div class="success">\u2713</div> 95 <h1>Authorization Successful!</h1> 96 <p id="status">Returning to Coves...</p> 97 <a href="${deepLink}" id="manualLink">Open Coves</a> 98 </div> 99 <script> 100 // Attempt automatic redirect 101 window.location.href = "${deepLink}"; 102 103 // Update status after 2 seconds if redirect didn't work 104 setTimeout(() => { 105 document.getElementById('status').textContent = 'Click the button above to continue'; 106 }, 2000); 107 </script> 108 </body> 109 </html> 110 `, { 111 headers: { 'Content-Type': 'text/html' } 112 }); 113 } 114 115 return new Response('Not found', { status: 404 }); 116 } 117};