+page.svelte
1 <script lang="ts"> 2 import { page } from '$app/state'; 3 import { goto } from '$app/navigation'; 4 import { resolve } from '$app/paths'; 5 import { friendkit } from '$lib/store.svelte'; 6 import { getAccessToken, verifyCredentials } from '$lib/mastodon'; 7 import { Loader, Button, Small, Panel, Padding } from 'svelte-akui'; 8 import CentredLayout from '$lib/components/ui/CentredLayout.svelte'; 9 10 let error = $state<string | null>(null); 11 12 import { untrack } from 'svelte'; 13 let hasAuthenticated = $state(false); 14 15 $effect(() => { 16 if (friendkit.isLoaded && !hasAuthenticated) { 17 untrack(() => { 18 hasAuthenticated = true; 19 handleAuthentication(); 20 }); 21 } 22 }); 23 24 async function handleAuthentication() { 25 const code = page.url.searchParams.get('code'); 26 27 if (!code) { 28 error = 'No code received from Mastodon.'; 29 return; 30 } 31 32 try { 33 if (!friendkit.app || !friendkit.instance) { 34 throw new Error('Application state lost. Please try logging in again.'); 35 } 36 37 const { access_token, scope } = await getAccessToken( 38 friendkit.instance, 39 friendkit.app.client_id, 40 friendkit.app.client_secret, 41 code, 42 window.location.origin + '/callback' 43 ); 44 45 friendkit.accessToken = access_token; 46 friendkit.accessTokenScopes = scope; 47 friendkit.currentUser = await verifyCredentials(); 48 49 const redirectPath = (await import('$lib/db').then((m) => m.db.get<string>('auth_redirect'))) || '/stats'; 50 import('$lib/db').then((m) => m.db.delete('auth_redirect')); 51 52 if (!friendkit.lastSync) { 53 void goto(resolve('/firstrun')); 54 } else { 55 void goto(resolve(redirectPath)); 56 } 57 } catch (e: unknown) { 58 error = e instanceof Error ? e.message : String(e); 59 } 60 } 61 </script> 62 63 <CentredLayout class="fk-callback"> 64 <Panel class="fk-callback__panel"> 65 <Padding size="xl"> 66 <div class="fk-callback__content"> 67 {#if error} 68 <div class="fk-callback__header"> 69 <h1>Authentication Error</h1> 70 <p>{error}</p> 71 </div> 72 <div class="fk-callback__actions"> 73 <Button variant="accent" onclick={() => void goto(resolve('/'))}>Try Again</Button> 74 </div> 75 {:else} 76 <div class="fk-callback__header"> 77 <h1>Authenticating…</h1> 78 <p>Establishing a secure connection with your Mastodon instance.</p> 79 </div> 80 <div class="fk-callback__loader-area"> 81 <Loader /> 82 <Small>Hang tight, this will only take a moment.</Small> 83 </div> 84 {/if} 85 </div> 86 </Padding> 87 </Panel> 88 </CentredLayout> 89 90 <style> 91 :global(.fk-callback__panel) { 92 width: 100%; 93 max-width: 500px; 94 min-height: 400px; 95 display: flex; 96 flex-direction: column; 97 } 98 99 .fk-callback__content { 100 display: flex; 101 flex-direction: column; 102 gap: 3rem; 103 text-align: center; 104 height: 100%; 105 justify-content: center; 106 } 107 108 .fk-callback__header { 109 display: flex; 110 flex-direction: column; 111 align-items: center; 112 gap: 1.5rem; 113 } 114 115 .fk-callback__header h1 { 116 margin: 0; 117 } 118 119 .fk-callback__header p { 120 margin: 0; 121 opacity: 0.7; 122 line-height: 1.5; 123 } 124 125 .fk-callback__loader-area { 126 display: flex; 127 flex-direction: column; 128 align-items: center; 129 gap: 1.5rem; 130 } 131 132 .fk-callback__actions { 133 display: flex; 134 justify-content: center; 135 } 136 </style>