redesign light mode and speed up auth
4 unmodified lines5678991011121369 unmodified lines4 unmodified lines "scripts": { "test": "NODE_ENV=test bun test", "dev": "NODE_ENV=development bun run build && NODE_ENV=development bun run --watch src/index.ts", "start": "bun run src/index.ts", "build:css": "bunx @tailwindcss/cli -i public/styles/global.css -o dist/styles.css --minify", "build:css": "tailwindcss -i public/styles/global.css -o dist/styles.css --minify", "build": "bun run build.ts && bun run build:css", "check": "tsc --noEmit", "screenshot": "bun run scripts/screenshot-sites.ts" },69 unmodified lines138 unmodified linesExpand all13914014114214314314414514614714814914915015115215215315415515515615715815991 unmodified lines25125225325425525525625725825931 unmodified lines2912922932942952952962972982992993003013023032 unmodified lines30630730830931031131231331431531631031131231331431531631731831932095 unmodified lines138 unmodified linesExpand all
<section> <h2>How I Collect Information</h2> <p> wisp.place uses the AT Protocol for authentication. When you log in, you authenticate directly with your Personal Data Server (PDS) — I never see your password. What I receive and store is your <strong>Decentralized Identifier (DID)</strong>, a public pseudonymous identifier that is the foundation of your AT Protocol identity. I do not collect email addresses or phone numbers. wisp.place uses the AT Protocol for authentication. When you log in, you authenticate directly with your Personal Data Server (PDS): I never see your password. What I receive and store is your <strong>Decentralized Identifier (DID)</strong>, a public pseudonymous identifier that is the foundation of your AT Protocol identity. I do not collect email addresses or phone numbers. </p> <p> During the login process, your <strong>AT Protocol handle</strong> (e.g. <code>you.bsky.social</code>) may appear in application logs. It is not stored in the database. </p> <p> When you publish a site, I store metadata about it: the site name, display name, your wisp.place subdomain, any custom domains you configure, content hashes, timestamps, and your site settings. The actual files you upload live on your PDS as blobs — I hold a derived cache to serve them to visitors. When you publish a site, I store metadata about it: the site name, display name, your wisp.place subdomain, any custom domains you configure, content hashes, timestamps, and your site settings. The actual files you upload live on your PDS as blobs; I hold a derived cache to serve them to visitors. </p> <p> I retain server logs and collect non-personally-identifying operational data — request paths, HTTP methods, status codes, and response durations — to monitor service health and diagnose issues. <strong>I do not log IP addresses, User-Agent strings, or Referer headers.</strong> I retain server logs and collect non-personally-identifying operational data (request paths, HTTP methods, status codes, and response durations) to monitor service health and diagnose issues. <strong>I do not log IP addresses, User-Agent strings, or Referer headers.</strong> </p> <p> Request counts are aggregated into periodic hit totals per site. These aggregate statistics contain no personally identifying information — no IP addresses, no geographic data, no visitor identifiers of any kind — and are made available to site owners who support the project. Request counts are aggregated into periodic hit totals per site. These aggregate statistics contain no personally identifying information (no IP addresses, no geographic data, no visitor identifiers of any kind) and are made available to site owners who support the project. </p> <p> I use a signed session cookie to keep you logged into your account. Session records are stored in the database and expire after 30 days. </p>91 unmodified lines
<div class="card info"> <h2 style="margin-top: 0;">The AT Protocol and Public Data</h2> <p style="margin-bottom: 0;"> wisp.place is built on the AT Protocol. Your sites, their files, and the records describing them are public on your PDS. Anyone operating a compatible service can read your published site records and blobs — this is inherent to how the protocol works and is not specific to wisp.place. wisp.place is built on the AT Protocol. Your sites, their files, and the records describing them are public on your PDS. Anyone operating a compatible service can read your published site records and blobs. This is inherent to how the protocol works and is not specific to wisp.place. </p> </div>
<section>31 unmodified lines <td>Performance of a contract (Art. 6(1)(b))</td> </tr> <tr> <td>Application logs, error traces, and request metrics</td> <td>Legitimate interests (Art. 6(1)(f)) — operating and securing the service</td> <td>Legitimate interests (Art. 6(1)(f)): operating and securing the service</td> </tr> <tr> <td>VictoriaLogs / VictoriaMetrics on self-operated infrastructure</td> <td>Legitimate interests (Art. 6(1)(f)) — service monitoring on infrastructure I control</td> <td>Legitimate interests (Art. 6(1)(f)): service monitoring on infrastructure I control</td> </tr> </table> </div>
2 unmodified lines </p>
<p>You have the right to:</p> <ul> <li><span class="bullet accent">•</span><span><strong>Access</strong> — request a copy of the personal data I hold about you</span></li> <li><span class="bullet accent">•</span><span><strong>Rectification</strong> — ask me to correct inaccurate data</span></li> <li><span class="bullet accent">•</span><span><strong>Erasure</strong> — ask me to delete your data (see retention note above)</span></li> <li><span class="bullet accent">•</span><span><strong>Restriction</strong> — ask me to limit how I process your data</span></li> <li><span class="bullet accent">•</span><span><strong>Portability</strong> — receive your data in a structured, machine-readable format</span></li> <li><span class="bullet accent">•</span><span><strong>Object</strong> — object to processing based on legitimate interests</span></li> <li><span class="bullet accent">•</span><span><strong>Lodge a complaint</strong> — with your national data protection supervisory authority</span></li> <li><span class="bullet accent">•</span><span><strong>Access</strong>: request a copy of the personal data I hold about you</span></li> <li><span class="bullet accent">•</span><span><strong>Rectification</strong>: ask me to correct inaccurate data</span></li> <li><span class="bullet accent">•</span><span><strong>Erasure</strong>: ask me to delete your data (see retention note above)</span></li> <li><span class="bullet accent">•</span><span><strong>Restriction</strong>: ask me to limit how I process your data</span></li> <li><span class="bullet accent">•</span><span><strong>Portability</strong>: receive your data in a structured, machine-readable format</span></li> <li><span class="bullet accent">•</span><span><strong>Object</strong>: object to processing based on legitimate interests</span></li> <li><span class="bullet accent">•</span><span><strong>Lodge a complaint</strong>: with your national data protection supervisory authority</span></li> </ul>
<div class="alert"> <p>95 unmodified lines23 unmodified lines2425262728292829303132333432333435363738373839404142414243444546454647484950515253495051525354555657497 unmodified linesExpand all555556557558559559560561562563564564565566567568328 unmodified linesExpand all23 unmodified lines ========================================================================== */
:root { /* Background colors */ --wisp-bg: oklch(0.92 0.012 35); --wisp-bg-alt: oklch(0.88 0.01 35); --wisp-bg: oklch(0.986 0.008 345); --wisp-bg-alt: oklch(0.958 0.021 348);
/* Text colors */ --wisp-text: oklch(0.15 0.015 30); --wisp-text-muted: oklch(0.35 0.02 30); --wisp-text-subtle: oklch(0.50 0.02 30); --wisp-text: oklch(0.26 0.031 352); --wisp-text-muted: oklch(0.45 0.031 350); --wisp-text-subtle: oklch(0.49 0.028 350);
/* Border colors */ --wisp-border: oklch(0.30 0.025 35); --wisp-border-light: oklch(0.65 0.02 30); --wisp-border: oklch(0.50 0.160 9); --wisp-border-light: oklch(0.875 0.035 348);
/* Accent colors */ --wisp-accent: oklch(0.65 0.18 345); --wisp-accent-muted: oklch(0.75 0.14 345); --wisp-accent: oklch(0.50 0.160 9); --wisp-accent-muted: oklch(0.905 0.040 348);
/* CTA/Button colors */ --wisp-cta-bg: oklch(0.30 0.025 35); --wisp-cta-text: oklch(0.96 0.008 35); --wisp-cta-bg: oklch(0.50 0.160 9); --wisp-cta-text: oklch(0.996 0.003 345);
/* Semantic colors */ --wisp-code-bg: oklch(0.95 0.008 35); --wisp-success: oklch(0.65 0.20 145); --wisp-danger: oklch(0.60 0.20 25); --wisp-warning: oklch(0.70 0.15 85); --wisp-info: oklch(0.60 0.15 240); --wisp-code-bg: oklch(0.958 0.021 348); --wisp-success: oklch(0.473 0.098 153); --wisp-danger: oklch(0.513 0.165 21); --wisp-warning: oklch(0.508 0.105 72); --wisp-info: oklch(0.479 0.134 261);
/* Layout */ --wisp-max-width: 900px; --wisp-header-max-width: 1100px;497 unmodified linesExpand all}
.alert-warning { background: var(--wisp-code-bg); border-left: 4px solid var(--wisp-warning); border: 1px solid color-mix(in oklab, var(--wisp-warning) 45%, transparent);}
.alert-danger { background: var(--wisp-code-bg); border-left: 4px solid var(--wisp-danger); border: 1px solid color-mix(in oklab, var(--wisp-danger) 45%, transparent);}
/* ========================================================================== Lists328 unmodified linesExpand all20 unmodified lines212223242526272829303132333435252627282930313233343536373839404142434438394041424344454647481173 unmodified linesExpand all20 unmodified lines --wide-width: 68rem; --nav-height: 3.5rem; --noise-opacity: 0.18;
/* Light — warm beige */ --bg: oklch(0.92 0.012 35); --bg-alt: oklch(0.88 0.01 35); --bg-accent: oklch(0.86 0.012 35); --text: oklch(0.15 0.015 30); --text-muted: oklch(0.42 0.02 30); --text-subtle: oklch(0.58 0.02 30); --accent: oklch(0.65 0.18 345); --accent-high: oklch(0.48 0.22 345); --accent-low: oklch(0.9 0.06 345); --border: oklch(0.6 0.02 30); /* Light — rose/bone */ --bg: oklch(0.986 0.008 345); --bg-alt: oklch(0.958 0.021 348); --bg-accent: oklch(0.945 0.026 348); --text: oklch(0.26 0.031 352); --text-muted: oklch(0.45 0.031 350); --text-subtle: oklch(0.49 0.028 350); --accent: oklch(0.50 0.160 9); --accent-high: oklch(0.42 0.140 9); --accent-low: oklch(0.905 0.040 348); --border: oklch(0.875 0.035 348);
/* Terminal / code chrome */ --term-bg: #f6f8fa; --term-header: #eaeef2; --term-border: oklch(0.6 0.02 30); --term-title: #57606a; --term-text: #1f2328; --term-prompt: #6639ba; --term-muted: #656d76; --term-bg: #faf5f5; --term-header: #f1e7e7; --term-border: oklch(0.875 0.035 348); --term-title: #6a6a6a; --term-text: #333333; --term-prompt: #a6485c; --term-muted: #6a6a6a; --term-success: #1a7f37; --term-cyan: #0969da;
color-scheme: light;1173 unmodified linesExpand all13 unmodified lines1415161718192021222324252627282930313233181920212223242526272829303132333435363738393637383940414243251 unmodified linesExpand all295296297298299299300301302303282 unmodified linesExpand all13 unmodified lines --sl-font-mono: "JetBrains Mono", ui-monospace, monospace; --sl-content-width: 70rem; --noise-opacity: 0.18;
/* Light — warm beige */ --sl-color-bg: oklch(0.92 0.012 35); --sl-color-bg-sidebar: oklch(0.88 0.01 35); --sl-color-bg-nav: oklch(0.88 0.01 35); --sl-color-text: oklch(0.15 0.015 30); --sl-color-text-accent: oklch(0.65 0.18 345); --sl-color-accent: oklch(0.65 0.18 345); --sl-color-accent-low: oklch(0.90 0.06 345); --sl-color-accent-high: oklch(0.48 0.22 345); --sl-color-border: oklch(0.60 0.02 30); --sl-color-gray-1: oklch(0.42 0.02 30); --sl-color-gray-2: oklch(0.32 0.02 30); --sl-color-gray-3: oklch(0.25 0.02 30); --sl-color-gray-4: oklch(0.18 0.015 30); --sl-color-gray-5: oklch(0.58 0.02 30); --sl-color-bg-accent: oklch(0.86 0.012 35); /* Light — rose/bone */ --sl-color-bg: oklch(0.986 0.008 345); --sl-color-bg-sidebar: oklch(0.958 0.021 348); --sl-color-bg-nav: oklch(0.958 0.021 348); --sl-color-text: oklch(0.26 0.031 352); --sl-color-text-accent: oklch(0.50 0.160 9); --sl-color-accent: oklch(0.50 0.160 9); --sl-color-accent-low: oklch(0.905 0.040 348); --sl-color-accent-high: oklch(0.42 0.140 9); --sl-color-border: oklch(0.875 0.035 348); --sl-color-gray-1: oklch(0.45 0.031 350); --sl-color-gray-2: oklch(0.41 0.033 352); --sl-color-gray-3: oklch(0.35 0.035 352); --sl-color-gray-4: oklch(0.30 0.037 352); --sl-color-gray-5: oklch(0.49 0.028 350); --sl-color-bg-accent: oklch(0.945 0.026 348);
/* Terminal code block chrome (light) */ --term-bg: #f6f8fa; --term-header: #eaeef2; --term-border: oklch(0.60 0.02 30); --term-title: #57606a; --term-bg: #faf5f5; --term-header: #f1e7e7; --term-border: oklch(0.875 0.035 348); --term-title: #6a6a6a;}
/* Dark — slate violet */[data-theme="dark"] {251 unmodified linesExpand all font-size: 0.875em !important; background: var(--sl-color-bg-accent) !important; border: 1px solid var(--sl-color-border) !important; padding: 0.1em 0.35em !important; color: var(--sl-color-text-accent) !important; color: var(--sl-color-accent-high) !important;}
/* Lists */.sl-markdown-content ul {282 unmodified linesExpand all23 unmodified lines2425262728292829303132333432333435363738373839404142414243444546454647484950515253495051525354555657497 unmodified linesExpand all555556557558559559560561562563564564565566567568328 unmodified linesExpand all23 unmodified lines ========================================================================== */
:root { /* Background colors */ --wisp-bg: oklch(0.92 0.012 35); --wisp-bg-alt: oklch(0.88 0.01 35); --wisp-bg: oklch(0.986 0.008 345); --wisp-bg-alt: oklch(0.958 0.021 348);
/* Text colors */ --wisp-text: oklch(0.15 0.015 30); --wisp-text-muted: oklch(0.35 0.02 30); --wisp-text-subtle: oklch(0.50 0.02 30); --wisp-text: oklch(0.26 0.031 352); --wisp-text-muted: oklch(0.45 0.031 350); --wisp-text-subtle: oklch(0.49 0.028 350);
/* Border colors */ --wisp-border: oklch(0.30 0.025 35); --wisp-border-light: oklch(0.65 0.02 30); --wisp-border: oklch(0.50 0.160 9); --wisp-border-light: oklch(0.875 0.035 348);
/* Accent colors */ --wisp-accent: oklch(0.65 0.18 345); --wisp-accent-muted: oklch(0.75 0.14 345); --wisp-accent: oklch(0.50 0.160 9); --wisp-accent-muted: oklch(0.905 0.040 348);
/* CTA/Button colors */ --wisp-cta-bg: oklch(0.30 0.025 35); --wisp-cta-text: oklch(0.96 0.008 35); --wisp-cta-bg: oklch(0.50 0.160 9); --wisp-cta-text: oklch(0.996 0.003 345);
/* Semantic colors */ --wisp-code-bg: oklch(0.95 0.008 35); --wisp-success: oklch(0.65 0.20 145); --wisp-danger: oklch(0.60 0.20 25); --wisp-warning: oklch(0.70 0.15 85); --wisp-info: oklch(0.60 0.15 240); --wisp-code-bg: oklch(0.958 0.021 348); --wisp-success: oklch(0.473 0.098 153); --wisp-danger: oklch(0.513 0.165 21); --wisp-warning: oklch(0.508 0.105 72); --wisp-info: oklch(0.479 0.134 261);
/* Layout */ --wisp-max-width: 900px; --wisp-header-max-width: 1100px;497 unmodified linesExpand all}
.alert-warning { background: var(--wisp-code-bg); border-left: 4px solid var(--wisp-warning); border: 1px solid color-mix(in oklab, var(--wisp-warning) 45%, transparent);}
.alert-danger { background: var(--wisp-code-bg); border-left: 4px solid var(--wisp-danger); border: 1px solid color-mix(in oklab, var(--wisp-danger) 45%, transparent);}
/* ========================================================================== Lists328 unmodified linesExpand all714 unmodified linesExpand all715716717718719719720721722723315 unmodified linesExpand all714 unmodified linesExpand all No domains available. Add a custom domain or claim a wisp.place subdomain. </p> )}
<div className="p-3 bg-muted/20 rounded-lg border-l-4 border-blue-500/50 mt-4"> <div className="p-3 bg-primary/5 rounded-lg border border-primary/20 mt-4"> <p className="text-xs text-muted-foreground"> <strong>Note:</strong> If no domains are selected, the site will be accessible at:{' '} <span className="font-mono"> sites.wisp.place/{userInfo?.handle || '...'}/{configuringSite.rkey}315 unmodified linesExpand all4 unmodified lines567891011129101112141516131415161819171819212223202122232526272425262729303128293031333435323334353738394041363738394043444142434647484950444546474849505354555657585960515253545556575859606162211 unmodified linesExpand all27427527627728028128228328428528628728828929029127829327928028128228316 unmodified lines300301302303318319320321322323324325326327328329304331332333334335336337338339305306307308343344345346347348349350309310311312313122 unmodified linesExpand all4 unmodified lines
:root { color-scheme: light;
/* Warm beige background inspired by Sunset design */ --background: oklch(0.92 0.012 35); /* Very dark brown text for strong contrast */ --foreground: oklch(0.15 0.015 30); /* Rose/bone base */ --background: oklch(0.986 0.008 345); --foreground: oklch(0.26 0.031 352);
/* Slightly lighter card background for elevation */ --card: oklch(0.95 0.008 35); --card-foreground: oklch(0.15 0.015 30); /* Crisp card elevation */ --card: oklch(0.996 0.003 345); --card-foreground: oklch(0.26 0.031 352);
--popover: oklch(0.96 0.006 35); --popover-foreground: oklch(0.15 0.015 30); --popover: oklch(0.996 0.003 345); --popover-foreground: oklch(0.26 0.031 352);
/* Dark brown primary - darker for better contrast */ --primary: oklch(0.30 0.025 35); --primary-foreground: oklch(0.96 0.008 35); /* Berry primary actions */ --primary: oklch(0.50 0.160 9); --primary-foreground: oklch(0.996 0.003 345);
/* Bubblegum pink accent */ --accent: #ff5c8a; --accent-foreground: oklch(0.15 0.015 30); /* Berry accent - links, tints, focus */ --accent: oklch(0.50 0.160 9); --accent-foreground: oklch(0.996 0.003 345);
/* Darker taupe secondary for better contrast */ --secondary: oklch(0.85 0.012 30); --secondary-foreground: oklch(0.25 0.02 30); /* Blush secondary surfaces */ --secondary: oklch(0.958 0.021 348); --secondary-foreground: oklch(0.26 0.031 352);
/* Muted areas with better distinction */ --muted: oklch(0.88 0.01 35); --muted-foreground: oklch(0.35 0.02 30); /* Rose tinted muted areas */ --muted: oklch(0.945 0.026 348); --muted-foreground: oklch(0.45 0.031 350);
/* Significantly darker border for visibility */ --border: oklch(0.65 0.02 30); /* Input backgrounds - distinct from card background */ --input: oklch(0.82 0.01 35); --ring: #ff5c8a; /* Rose borders */ --border: oklch(0.875 0.035 348); --input: oklch(0.84 0.045 348); --ring: oklch(0.50 0.160 9);
--destructive: oklch(0.50 0.20 25); --destructive-foreground: oklch(0.98 0 0); --destructive: oklch(0.513 0.165 21); --destructive-foreground: oklch(0.996 0.003 345);
--chart-1: #ff5c8a; --chart-2: oklch(0.32 0.04 285); --chart-3: oklch(0.50 0.10 220); --chart-4: oklch(0.70 0.08 130); --chart-5: oklch(0.75 0.06 85); --chart-1: oklch(0.50 0.160 9); --chart-2: oklch(0.905 0.040 348); --chart-3: oklch(0.386 0.097 12); --chart-4: oklch(0.473 0.098 153); --chart-5: oklch(0.479 0.134 261);
--radius: 0.75rem; --sidebar: oklch(0.94 0.008 35); --sidebar-foreground: oklch(0.15 0.015 30); --sidebar-primary: oklch(0.30 0.025 35); --sidebar-primary-foreground: oklch(0.96 0.008 35); --sidebar-accent: oklch(0.90 0.01 35); --sidebar-accent-foreground: oklch(0.20 0.02 30); --sidebar-border: oklch(0.65 0.02 30); --sidebar-ring: #ff5c8a; --sidebar: oklch(0.958 0.021 348); --sidebar-foreground: oklch(0.26 0.031 352); --sidebar-primary: oklch(0.50 0.160 9); --sidebar-primary-foreground: oklch(0.996 0.003 345); --sidebar-accent: oklch(0.905 0.040 348); --sidebar-accent-foreground: oklch(0.42 0.140 9); --sidebar-border: oklch(0.875 0.035 348); --sidebar-ring: oklch(0.50 0.160 9);}
.dark { color-scheme: dark;211 unmodified linesExpand all @apply bg-background text-foreground; }}
@keyframes arrow-bounce {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(4px); }}
.arrow-animate { animation: arrow-bounce 1.5s ease-in-out infinite; transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);}
@keyframes shimmer { 100% {16 unmodified lines
/* ========== Landing Page Animations ========== */
/* Animated gradient for headline text */@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }}
.text-gradient-animate { background: linear-gradient(90deg, oklch(0.55 0.22 350), oklch(0.60 0.24 10), oklch(0.55 0.22 350)); background-size: 200% auto; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; animation: gradient-shift 4s ease-in-out infinite; color: var(--primary);}
.dark .text-gradient-animate { background: linear-gradient(90deg, oklch(0.75 0.12 295), oklch(0.85 0.10 5), oklch(0.75 0.12 295)); background-size: 200% auto; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: oklch(0.85 0.10 5);}
/* Floating/breathing animation for hero elements */@keyframes float {122 unmodified linesExpand all49 unmodified lines5051525354555455565758137 unmodified linesExpand all19619719819920020120220320420520620720820921021123 unmodified lines23523623723823423924023624124224324424524624753 unmodified lines49 unmodified lines const reserved = await lockDb.reserve() return await withReservedOAuthLock( { async acquire(): Promise<void> { await reserved`SET lock_timeout = '30s'` await reserved`SELECT pg_advisory_lock(${key})` await reserved.unsafe(`SET lock_timeout = '30s'; SELECT pg_advisory_lock(${key})`) }, async unlock(): Promise<void> { await reserved`SELECT pg_advisory_unlock(${key})` },137 unmodified linesExpand all ON CONFLICT (kid) DO UPDATE SET jwk = EXCLUDED.jwk, created_at = EXCLUDED.created_at ` keysCache = undefined}
// 60s ttl: keys rotate ~twice a year, so cross-instance staleness after a// rotation self-heals well before the next sign-in checks the new keyset.let keysCache: { keys: JoseKey[]; fetchedAtMs: number } | undefinedconst KEYS_CACHE_TTL_MS = 60_000
const loadPersistedKeys = async (): Promise<JoseKey[]> => { const rows = await db`SELECT kid, jwk, created_at FROM oauth_keys ORDER BY kid` const keys: JoseKey[] = [] for (const row of rows) {23 unmodified lines keys.sort((a, b) => (a.kid ?? '').localeCompare(b.kid ?? '')) return keys}
// Load keys from database every time (stateless - safe for horizontal scaling)export const getCurrentKeys = async (): Promise<JoseKey[]> => { if (keysCache && Date.now() - keysCache.fetchedAtMs < KEYS_CACHE_TTL_MS) return keysCache.keys return await loadPersistedKeys() const keys = await loadPersistedKeys() keysCache = { keys, fetchedAtMs: Date.now() } return keys}
// Key rotation - rotate keys older than 6 monthsconst KEY_MAX_AGE = 182.5 * 24 * 60 * 60 // ~6 months in seconds53 unmodified lines42 unmodified lines43444546474849505152535455565758596013 unmodified lines42 unmodified lines }) expect(await http.resolve('alice.example')).toBeNull() })
test('does not convert transient transport failures into cacheable null results', async () => { const resolver = new SlingshotHandleResolver(ENDPOINT, { resolver: async () => [PUBLIC_ADDRESS], transport: async () => { throw new Error('socket unavailable') }, }) await expect(resolver.resolve('alice.example')).rejects.toThrow('temporarily unavailable') })
test('propagates caller cancellation through the pinned request', async () => { const controller = new AbortController() const resolver = new SlingshotHandleResolver(ENDPOINT, { resolver: async () => [PUBLIC_ADDRESS],13 unmodified lines92 unmodified lines9394959697989997989910010110210310410510610710810911092 unmodified lines return did as AtprotoDid } catch (error) { if (options?.signal?.aborted) throw error if (error instanceof Error && error.name === 'AbortError') throw error // Do not log errors from a remote server: they may contain URLs, // response bodies, or credentials supplied by that server. logger.warn('[SlingshotHandleResolver] Handle resolution failed', { handle: logHandle }) // Do not turn a transient network failure into a cacheable “handle not // found” result. The OAuth client may safely cache authoritative nulls, // but a rejected lookup remains retryable on the next request. const transient = error instanceof Error && (error.message === 'Identity request failed' || error.message === 'Identity request timed out' || typeof (error as Error & { code?: unknown }).code === 'string') logger.warn('[SlingshotHandleResolver] Handle resolution failed', { handle: logHandle, transient }) if (transient) throw new Error('Handle resolver temporarily unavailable') return null } }}