a a vibe-coded abomination experiment of a fragrance review platform built on the atmosphere. drydown.social
at main 310 lines 10 kB view raw view rendered
1# Adding AT Protocol Services to Drydown 2 3This guide explains how to add support for new AT Protocol services (like Bluesky, Blacksky, Northsky, etc.) to Drydown. 4 5## Overview 6 7Drydown uses a **configuration-driven approach** for AT Protocol service support. When you add a new service to the configuration file, all share buttons, profile links, and footer links automatically work with that service—no additional code changes required. 8 9## Quick Start 10 11To add a new service, you only need to edit **one file**: `src/config/services.ts` 12 13### Step 1: Add Service Configuration 14 15Open `src/config/services.ts` and add a new entry to the `KNOWN_SERVICES` array: 16 17```typescript 18export const KNOWN_SERVICES: ServiceConfig[] = [ 19 // ... existing services ... 20 { 21 id: 'yourservice', 22 name: 'YourService', 23 composeUrl: (text) => `https://yourservice.app/intent/compose?text=${encodeURIComponent(text)}`, 24 profileUrl: (handle) => `https://yourservice.app/profile/${handle}`, 25 issuerPatterns: ['yourservice.app', 'yourservice.com'], 26 pdsPatterns: ['yourservice.app', 'pds.yourservice.com'], 27 }, 28] 29``` 30 31### Step 2: Test 32 33That's it! No other code changes needed. The app will now: 34- Detect when users log in via your service 35- Generate correct share links for users on your service 36- Link to user profiles on your service 37- Show your service name in UI elements 38 39## ServiceConfig Reference 40 41Each service configuration has the following fields: 42 43### `id` (string) 44Unique identifier for the service. Use lowercase, no spaces. 45 46```typescript 47id: 'bluesky' 48``` 49 50### `name` (string) 51Display name shown to users in tooltips and UI. 52 53```typescript 54name: 'Bluesky' 55``` 56 57### `composeUrl` (function) 58Generates the URL for creating a new post on this service. Takes the post text as a parameter. 59 60**Important:** This should be a compose intent URL, not the main compose page. Users should be able to click "post" immediately. 61 62```typescript 63// Standard pattern for most AT Protocol services: 64composeUrl: (text) => `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}` 65 66// If the service uses a different parameter name: 67composeUrl: (text) => `https://example.app/share?content=${encodeURIComponent(text)}` 68``` 69 70**Note:** Always use `encodeURIComponent()` to ensure special characters are properly encoded. 71 72### `profileUrl` (function) 73Generates the URL for a user's profile page. Takes the AT Protocol handle as a parameter. 74 75```typescript 76// Standard pattern: 77profileUrl: (handle) => `https://bsky.app/profile/${handle}` 78 79// Some services might use @: 80profileUrl: (handle) => `https://example.app/@${handle}` 81``` 82 83### `issuerPatterns` (string[]) 84Array of domain patterns that appear in OAuth issuer URLs for this service. Used to detect which service a user logged in with. 85 86```typescript 87// Check the OAuth session's issuer field (session.server.serverMetadata.issuer) 88// Add all domains that might appear in that URL 89issuerPatterns: ['bsky.social', 'bsky.app'] 90 91// Example: Blacksky uses https://blacksky.app as issuer 92issuerPatterns: ['blacksky.app'] 93``` 94 95**How to find this:** 961. Log in to the service via OAuth 972. Open browser DevTools Console 983. Look for the log: `[ServiceContext] Detecting service for issuer: <url>` 994. Add the domain from that URL to `issuerPatterns` 100 101### `pdsPatterns` (string[]) 102Array of domain patterns for Personal Data Server URLs associated with this service. Used as a fallback when OAuth issuer detection doesn't match. 103 104```typescript 105// Common PDS domains for this service 106pdsPatterns: ['bsky.social', 'bsky.network'] 107``` 108 109**How to find this:** 1101. Visit `https://plc.directory/{did}` with a user's DID 1112. Look for the `service` array with `type: "AtprotoPersonalDataServer"` 1123. Add the domain from `serviceEndpoint` 113 114## Complete Example 115 116Here's a complete example adding support for "Northsky": 117 118```typescript 119{ 120 id: 'northsky', 121 name: 'Northsky', 122 composeUrl: (text) => `https://northsky.app/intent/compose?text=${encodeURIComponent(text)}`, 123 profileUrl: (handle) => `https://northsky.app/profile/${handle}`, 124 issuerPatterns: ['northsky.app'], 125 pdsPatterns: ['northsky.app'], 126} 127``` 128 129## How Service Detection Works 130 131Drydown detects services in the following order: 132 133### 1. OAuth Issuer Detection (Primary) 134When a user logs in, Drydown checks `session.server.serverMetadata.issuer` against all `issuerPatterns`: 135 136```typescript 137// Example issuer: "https://bsky.social" 138// Matches issuerPatterns: ['bsky.social', 'bsky.app'] 139``` 140 141### 2. PDS URL Detection (Fallback) 142If the issuer doesn't match any known service, Drydown fetches the user's DID document and checks their PDS URL against `pdsPatterns`. 143 144### 3. Default to Bluesky 145If no match is found, the app defaults to Bluesky (per product decision: unknown/custom PDS instances should work with Bluesky URLs). 146 147## Where Services Are Used 148 149Once configured, service information is automatically used in: 150 151### Share Buttons 152- **Location:** Review pages (`SingleReviewPage`, `EditReview`) 153- **Behavior:** Uses the **logged-in user's service** (not the review author's) 154- **Example:** User on Blacksky sees "Share on Blacksky" and links open Blacksky compose 155 156### Profile Links 157- **Location:** Profile page headers (`ProfilePage`, `ProfileHousesPage`) 158- **Behavior:** Detects the **profile owner's service** (each user can be on a different service) 159- **Example:** Viewing a Northsky user's profile shows a link to their Northsky profile 160 161### Footer Links 162- **Location:** App footer (`Footer`) 163- **Behavior:** Links to `@drydown.social` on the **logged-in user's service** 164- **Example:** User on Blacksky sees a link to Blacksky's drydown.social profile 165 166## Testing Your Service Configuration 167 168### Manual Testing Checklist 169 1701. **Login Detection** 171 - [ ] Log in with an account on your service 172 - [ ] Check browser console: `session.server.serverMetadata.issuer` should match your patterns 173 - [ ] Verify no errors in console about service detection 174 1752. **Share Buttons** 176 - [ ] Create or view a review 177 - [ ] Click a share button 178 - [ ] Verify it opens your service's compose page with pre-filled text 179 - [ ] Verify the URL structure matches your `composeUrl` pattern 180 1813. **Profile Links** 182 - [ ] Visit a profile page 183 - [ ] Check the external link icon next to the profile name 184 - [ ] Verify hovering shows "View on YourService" 185 - [ ] Click the link and verify it goes to the correct profile 186 1874. **Footer Link** 188 - [ ] Check the footer link to `@drydown.social` 189 - [ ] Verify it points to your service's drydown.social profile 190 191### Testing with Mock Issuers 192 193If you don't have access to an account on the service, you can temporarily mock the issuer for testing: 194 195```typescript 196// In src/contexts/ServiceContext.tsx, temporarily modify: 197useEffect(() => { 198 if (session) { 199 // Original: 200 // const issuer = session.server.serverMetadata.issuer 201 202 // Test mock: 203 const issuer = 'https://yourservice.app' // Your test issuer 204 205 const detected = detectService(issuer) 206 setUserService(detected) 207 } 208}, [session]) 209``` 210 211**Remember to remove the mock before committing!** 212 213## Edge Cases & Considerations 214 215### Multiple Domains 216Some services might use different domains for OAuth vs profiles: 217 218```typescript 219{ 220 id: 'example', 221 name: 'Example', 222 composeUrl: (text) => `https://app.example.com/compose?text=${encodeURIComponent(text)}`, 223 profileUrl: (handle) => `https://example.com/u/${handle}`, 224 issuerPatterns: ['auth.example.com', 'oauth.example.com'], 225 pdsPatterns: ['pds.example.com', 'data.example.com'], 226} 227``` 228 229### Services Without Compose Intents 230Some AT Protocol services might not have a `/intent/compose` endpoint yet. In this case: 231 2321. Use the regular compose page URL 2332. Document this limitation in comments 2343. Users will need to manually paste the text 235 236```typescript 237{ 238 id: 'example', 239 name: 'Example', 240 // No intent URL available, falls back to main compose 241 composeUrl: (text) => `https://example.app/compose`, 242 // ... rest of config 243} 244``` 245 246### Custom PDS Instances 247Individual users can run their own PDS. These will not match any known patterns and will default to Bluesky. This is intentional—custom PDS users are advanced users who understand Bluesky URLs will work. 248 249## Troubleshooting 250 251### Service Not Detected After Login 2521. Check `session.server.serverMetadata.issuer` in browser console 2532. Verify the issuer domain matches your `issuerPatterns` 2543. Check for typos in pattern strings (must be exact substring matches) 255 256### Profile Links Not Appearing 2571. Open browser devtools Network tab 2582. Look for requests to `https://plc.directory/did:plc:...` 2593. Check if the response includes a PDS service endpoint 2604. Verify that endpoint domain matches your `pdsPatterns` 261 262### Share Button Opens Wrong Service 2631. Verify you're checking the **logged-in user's service**, not the author's 2642. Check `useShareButton()` hook is imported and used correctly 2653. Confirm `ServiceProvider` wraps your component tree in `app.tsx` 266 267### TypeScript Errors After Adding Service 2681. Run `npx tsc -b` to check for type errors 2692. Ensure all required fields are present in your config 2703. Verify function signatures match `ServiceConfig` interface 271 272## Build Verification 273 274After adding a service, always verify the build: 275 276```bash 277# Type check 278npx tsc -b 279 280# Production build 281npm run build 282 283# Dev server (test manually) 284npm run dev 285``` 286 287All commands should complete without errors. 288 289## Contributing Service Configurations 290 291When contributing a new service configuration to Drydown: 292 2931. **Research**: Verify the service actually exists and has a public web interface 2942. **Test**: Log in with a real account if possible, or coordinate with service maintainers 2953. **Document**: Add a comment above your config explaining any quirks 2964. **Verify**: Test all three use cases (share, profile links, footer) 2975. **PR**: Submit with a clear description of which service you're adding 298 299## Future Enhancements 300 301Potential improvements to the service system: 302 303- **Service icons**: Add logos/icons for each service 304- **Service discovery**: Automatically detect services from a registry 305- **User preferences**: Let users override detected service 306- **Federation status**: Show which services support which AT Protocol features 307 308--- 309 310For questions or issues, please open an issue on the Drydown GitHub repository.