Search code examples
typescriptauthenticationsveltesveltekitsvelte-store

Typed Svelte store with initial value from local storage


I'm trying to implement JWT authentication in my SvelteKit SPA. I'm new to TS (and thus Svelte[Kit]) and there is a lot to learn, so my problem might not strictly be with Svelte and its store system but also with TS in general.

My root +layout.svelte for all routes that require authentication looks like this:

<script lang="ts">
    import { goto } from '$app/navigation';
    import auth from '$lib/stores/auth';
</script>

{#await auth.init() then user}
    <slot />
{:catch}
    {goto('/login')}
{/await}

lib/stores/auth.ts:

import { writable, get } from 'svelte/store';
import { jwtDecode, type JwtPayload } from 'jwt-decode';

type User = {
    email: string;
    name: string;
};

interface Token extends JwtPayload {
    user: User;
}

function createStore() {
    const user = {};
    const { subscribe, update, set } = writable(user as User);

    return {
        subscribe,

        async init() {
            const tokenStr = localStorage.getItem('token')!;
            const token = jwtDecode<Token>(tokenStr);
            set(token.user);
        },

        setToken(tokenStr: string) {
            const token = jwtDecode<Token>(tokenStr);
            let storeValue = get(this);

            storeValue = token.user;

            update(() => storeValue);

            localStorage.setItem('token', tokenStr);
        }
    };
}

export default createStore();

My problem is that I can't use the user result object in my layout (e.g. to display the username in the navigation, etc.) It is undefined. Why is that?


Solution

  • So I ended up using svelte-persisted-store as also suggested by @José.

    lib/stores/auth.ts:

    import { derived, writable } from 'svelte/store';
    import { browser } from '$app/environment';
    import { jwtDecode, type JwtPayload } from 'jwt-decode';
    
    type User = {
        email: string;
        name: string;
    };
    
    interface Token extends JwtPayload {
        user: User;
    }
    
    export const token = (() => {
        const { set, subscribe, update } = writable(browser ? localStorage.getItem('token') : null);
        return {
            set: (value: string | null) => {
                value ? localStorage.setItem('token', value) : localStorage.removeItem('token');
                set(value);
            },
            subscribe,
            update
        };
    })();
    export const user = derived(token, (token) => (token ? jwtDecode<Token>(token).user : null));
    

    layout.svelte:

    <script lang="ts">
        import { goto } from '$app/navigation';
        import { user } from '$lib/stores/auth';
    </script>
    
    {#if $user}
        <slot />
    {:else}
        {goto('/login')}
    {/if}