Something went wrong. Try again.
Central platform for European atproto.<cc> country community websites atproto.eu
community atproto
Something went wrong. Try again.
4.1 kB · 113 lines
Astro
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114---import { localeHref } from '../i18n/paths';// Client-side sign in / out (AT Protocol OAuth). Restores the session on load (so users// stay signed in), exposes window.atn = { agent, session, did } + window.atnClient for the// /login page and logged-in actions, and fires atn:signedin / atn:signedout.//// The sign-in link is server-rendered and we optimistically paint the signed-in label from// a cached value, so the button never pops in after the async OAuth init (no navbar blink// on page-to-page navigation).import type { Lang } from '../i18n/ui';const { lang = 'en' } = Astro.props as { lang?: Lang };const loginHref = localeHref(lang, '/login');const signIn = lang === 'nl' ? 'Inloggen' : 'Sign in';const signOut = lang === 'nl' ? 'Uitloggen' : 'Sign out';---
<div id="atn-auth" class="auth-widget" data-login={loginHref} data-signin={signIn} data-signout={signOut}> <a class="icon-btn" href={loginHref}>{signIn}</a></div>
<script is:inline> // Synchronous optimistic paint: before first paint, swap the server-rendered "Sign in" // link for the last known signed-in label. Runs during parse (not deferred), so a // logged-in user never sees the "Sign in" -> "@handle" width jump on navigation. { const el = document.getElementById('atn-auth'); const label = el && localStorage.getItem('atn-label'); if (el && label) { const out = el.dataset.signout || 'Sign out'; el.innerHTML = `<span class="auth-who">${label}</span> <button class="icon-btn" type="button" data-signout>${out}</button>`; } }</script>
<script> import { Agent } from '@atproto/api'; import { createOAuthClient } from '../lib/oauth';
const el = document.getElementById('atn-auth'); let client: Awaited<ReturnType<typeof createOAuthClient>> | undefined; const LABEL_KEY = 'atn-label';
function renderLabel(label: string) { if (!el) return; const out = el.dataset.signout ?? 'Sign out'; el.innerHTML = `<span class="auth-who">${label}</span> <button class="icon-btn" type="button" data-signout>${out}</button>`; } function renderSignIn() { if (!el) return; const href = el.dataset.login ?? '/login'; const label = el.dataset.signin ?? 'Sign in'; el.innerHTML = `<a class="icon-btn" href="${href}">${label}</a>`; }
// Optimistic paint: show the last known signed-in label immediately (the sign-in link is // already server-rendered for signed-out visitors). OAuth init below confirms/corrects it. const cachedLabel = localStorage.getItem(LABEL_KEY); if (cachedLabel) renderLabel(cachedLabel);
async function showSignedIn(session: any) { const agent = new Agent(session); const did = agent.did ?? session.sub; (window as any).atn = { agent, session, did }; let label = did as string; try { const r = await fetch( `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`, ); if (r.ok) label = '@' + (await r.json()).handle; } catch { /* keep DID as the label */ } localStorage.setItem(LABEL_KEY, label); renderLabel(label); el?.querySelector('[data-signout]')?.addEventListener('click', async () => { try { await client?.revoke(session.sub); } finally { localStorage.removeItem(LABEL_KEY); location.reload(); } }); document.dispatchEvent(new CustomEvent('atn:signedin')); }
function showSignedOut() { (window as any).atn = null; localStorage.removeItem(LABEL_KEY); renderSignIn(); document.dispatchEvent(new CustomEvent('atn:signedout')); }
(async () => { if (!el) return; try { client = await createOAuthClient(); (window as any).atnClient = client; const result = await client.init(); if (result?.session) await showSignedIn(result.session); else showSignedOut(); } catch (e) { console.error('atproto OAuth init failed', e); // Leave whatever is shown (server sign-in link or cached label); never blank it. } })();</script>