// Client-side AT Protocol OAuth. Production uses the hosted /client-metadata.json; // local dev uses a loopback client (requires the 127.0.0.1 origin, not localhost), // with the write scopes encoded in the loopback client_id. import { BrowserOAuthClient } from '@atproto/oauth-client-browser'; import { OAUTH_SCOPE } from './oauth-scope'; // The public AppView, not the bsky.social entryway: the entryway caches handle -> DID for months // after a handle moves to another account (seen 2026-09-25: anders.sorby.xyz still resolved to its // previous account there), and the OAuth client's bi-directional check then rejects the sign-in. const HANDLE_RESOLVER = 'https://public.api.bsky.app'; export async function createOAuthClient(): Promise { const origin = window.location.origin; const isLoopback = /^https?:\/\/(127\.0\.0\.1|\[?::1\]?)(:\d+)?$/.test(origin); if (isLoopback) { const clientId = `http://localhost?redirect_uri=${encodeURIComponent(origin + '/')}` + `&scope=${encodeURIComponent(OAUTH_SCOPE)}`; return BrowserOAuthClient.load({ clientId, handleResolver: HANDLE_RESOLVER }); } return BrowserOAuthClient.load({ clientId: `${origin}/client-metadata.json`, handleResolver: HANDLE_RESOLVER, }); }