Main coves client
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};