/ src / routes / callback / +page.svelte
+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>