bluesky client without react native baggage written in sveltekit

fixedd authentication

+70 -14
+11 -1
src/lib/atproto.ts
··· 1 - import { Client, simpleFetchHandler } from '@atcute/client'; 1 + import { Client, ok, simpleFetchHandler } from '@atcute/client'; 2 + import { AppBskyActorDefs } from '@atcute/bluesky'; 2 3 import { 3 4 configureOAuth, 4 5 createAuthorizationUrl, ··· 78 79 /** Check if a session is active */ 79 80 export function isLoggedIn(): boolean { 80 81 return rpc !== null; 82 + } 83 + 84 + /** Fetch the logged-in user's profile */ 85 + export async function getProfile(): Promise<AppBskyActorDefs.ProfileViewDetailed | null> { 86 + if (!rpc || !currentSession) return null; 87 + const data = await ok(rpc.get('app.bsky.actor.getProfile', { 88 + params: { actor: currentSession.info.sub } 89 + })); 90 + return data; 81 91 } 82 92 83 93 function setSession(session: Session): void {
+6
src/lib/components/Avatar.svelte
··· 1 + <script lang="ts"> 2 + import { AppBskyActorDefs } from '@atcute/bluesky'; 3 + 4 + let { user }: { user: AppBskyActorDefs.ProfileViewDetailed } = $props() 5 + </script> 6 + <img class="ml-2.5 mr-2 w-10.5 h-10.5 rounded-lg" src={user.avatar} alt={`${user.displayName || user.handle}'s avatar'`}>
+3 -2
src/lib/components/Post.svelte
··· 1 1 <script lang="ts"> 2 2 import RichText from "./RichText.svelte"; 3 + import Avatar from "./Avatar.svelte"; 3 4 import type { PostView } from "@atcute/bluesky/types/app/feed/defs"; 4 5 5 6 let { post }: { post: PostView } = $props(); 6 7 </script> 7 8 8 9 <article class="flex pr-4 pl-2.5 pt-2 pb-2 border-post-border border"> 9 - <img class="ml-2.5 mr-2 w-10.5 h-10.5 rounded-lg" src={post.author.avatar} alt={`${post.author.displayName || post.author.handle}'s avatar'`}> 10 + <Avatar user={post.author} /> 10 11 <div> 11 12 <div class="mb-1"> 12 13 <a href="#" > ··· 30 31 </div> 31 32 </div> 32 33 </div> 33 - </article> 34 + </article>
+17
src/lib/context.ts
··· 1 + import { getContext, setContext } from 'svelte'; 2 + import type { AppBskyActorDefs } from '@atcute/bluesky'; 3 + 4 + export interface UserContext { 5 + loggedIn: boolean; 6 + profile: AppBskyActorDefs.ProfileViewDetailed | null; 7 + } 8 + 9 + const USER_KEY = Symbol('user'); 10 + 11 + export function setUserContext(ctx: UserContext) { 12 + return setContext(USER_KEY, ctx); 13 + } 14 + 15 + export function getUserContext(): UserContext { 16 + return getContext(USER_KEY); 17 + }
+21
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import './layout.css'; 3 3 import favicon from '$lib/assets/favicon.svg'; 4 + import { onMount } from 'svelte'; 5 + import { resumeSession, getProfile } from '$lib/atproto'; 6 + import { setUserContext } from '$lib/context'; 7 + import type { AppBskyActorDefs } from '@atcute/bluesky'; 4 8 5 9 let { children } = $props(); 10 + 11 + let loggedIn = $state(false); 12 + let profile = $state<AppBskyActorDefs.ProfileViewDetailed | null>(null); 13 + 14 + setUserContext({ 15 + get loggedIn() { return loggedIn; }, 16 + set loggedIn(v: boolean) { loggedIn = v; }, 17 + get profile() { return profile; }, 18 + set profile(v) { profile = v; } 19 + }); 20 + 21 + onMount(async () => { 22 + loggedIn = await resumeSession(); 23 + if (loggedIn) { 24 + profile = await getProfile(); 25 + } 26 + }); 6 27 </script> 7 28 8 29 <svelte:head><link rel="icon" href={favicon} /></svelte:head>
+10 -9
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import Post from '$lib/components/Post.svelte'; 3 - import { login, resumeSession } from '$lib/atproto'; 4 - import { onMount } from 'svelte'; 3 + import Avatar from '$lib/components/Avatar.svelte'; 4 + import { login } from '$lib/atproto'; 5 + import { getUserContext } from '$lib/context'; 5 6 6 7 let { data } = $props(); 7 - let loggedIn = $state(false); 8 + const user = getUserContext(); 8 9 let handle = $state(''); 9 10 let loggingIn = $state(false); 10 - 11 - onMount(async () => { 12 - loggedIn = await resumeSession(); 13 - }); 14 11 15 12 async function handleLogin() { 16 13 if (!handle.trim()) return; ··· 25 22 26 23 <div class="mx-auto flex max-h-screen max-w-290"> 27 24 <div class="sidebar p-4"> 28 - {#if loggedIn} 29 - <p>Logged in</p> 25 + {#if user.loggedIn} 26 + <p>Logged in</p> 27 + <div class="flex items-center gap-2"> 28 + <Avatar user={user.profile} /> 29 + {user.profile?.handle} 30 + </div> 30 31 {:else} 31 32 <form onsubmit={(e) => { e.preventDefault(); handleLogin(); }}> 32 33 <input
+2 -2
src/routes/oauth/callback/+page.svelte
··· 6 6 let error = $state(''); 7 7 8 8 onMount(async () => { 9 - const params = new URLSearchParams(location.hash.slice(1)); 10 - history.replaceState(null, '', location.pathname + location.search); 9 + const params = new URLSearchParams(location.hash.slice(1)); 10 + console.log(params) 11 11 12 12 try { 13 13 await handleCallback(params);