From e7f14f4573c28753d01b0eba27a1597e63fb8739 Mon Sep 17 00:00:00 2001 From: Freddy L Date: Tue, 26 Sep 2023 19:24:59 -0400 Subject: [PATCH] Added SessionProvider on sveltekit --- examples/sveltekit-mal-auth/package.json | 2 +- examples/sveltekit-mal-auth/src/app.d.ts | 10 +- .../sveltekit-mal-auth/src/hooks.server.ts | 22 ++- .../src/routes/+layout.server.ts | 6 + .../src/routes/+layout.svelte | 9 + .../src/routes/+page.svelte | 5 - .../animelist-auth-sveltekit/package.json | 11 +- .../src/client/SessionProvider.svelte | 11 ++ .../src/client/index.ts | 3 +- .../src/client/session.ts | 172 +++++++++--------- pnpm-lock.yaml | 59 ++++++ 11 files changed, 209 insertions(+), 101 deletions(-) create mode 100644 examples/sveltekit-mal-auth/src/routes/+layout.server.ts create mode 100644 examples/sveltekit-mal-auth/src/routes/+layout.svelte create mode 100644 packages/animelist-auth-sveltekit/src/client/SessionProvider.svelte diff --git a/examples/sveltekit-mal-auth/package.json b/examples/sveltekit-mal-auth/package.json index 123daaa..8ebc5e0 100644 --- a/examples/sveltekit-mal-auth/package.json +++ b/examples/sveltekit-mal-auth/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "private": true, "scripts": { - "dev": "vite dev", + "dev": "vite dev --port 5175", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/examples/sveltekit-mal-auth/src/app.d.ts b/examples/sveltekit-mal-auth/src/app.d.ts index f59b884..893ff9f 100644 --- a/examples/sveltekit-mal-auth/src/app.d.ts +++ b/examples/sveltekit-mal-auth/src/app.d.ts @@ -2,11 +2,17 @@ // for information about these interfaces declare global { namespace App { + interface Locals { + session?: { + user: User, + accessToken: string; + } | null; + } + // interface Error {} - // interface Locals {} // interface PageData {} // interface Platform {} } } -export {}; +export { }; diff --git a/examples/sveltekit-mal-auth/src/hooks.server.ts b/examples/sveltekit-mal-auth/src/hooks.server.ts index e68ec6c..52bc4d7 100644 --- a/examples/sveltekit-mal-auth/src/hooks.server.ts +++ b/examples/sveltekit-mal-auth/src/hooks.server.ts @@ -1,15 +1,25 @@ import type { Handle } from "@sveltejs/kit"; import { createMyAnimeListHandler } from "@animelist/auth-sveltekit/server"; +import { getServerSession } from "@animelist/auth/common"; +import { MALClient } from "@animelist/client"; -const handler = createMyAnimeListHandler({ - callbacks: { - onSession(session) { - console.log(session.user); +const handler = createMyAnimeListHandler(); + +export const handle: Handle = async ({ event, resolve }) => { + const session = await getServerSession(event.cookies); + + if (session) { + const { accessToken } = session; + const client = new MALClient({ accessToken }); + + try { + const user = await client.getMyUserInfo({}); + event.locals.session = { user, accessToken }; + } catch (err) { + console.error(err); } } -}); -export const handle: Handle = ({ event, resolve }) => { if (event.url.pathname.startsWith("/api/myanimelist")) { return handler({ event, resolve }); } diff --git a/examples/sveltekit-mal-auth/src/routes/+layout.server.ts b/examples/sveltekit-mal-auth/src/routes/+layout.server.ts new file mode 100644 index 0000000..7c14ea9 --- /dev/null +++ b/examples/sveltekit-mal-auth/src/routes/+layout.server.ts @@ -0,0 +1,6 @@ +import type { LayoutServerLoad } from "./$types"; + + +export const load: LayoutServerLoad = async ({ locals }) => { + return { session: locals.session } +}; \ No newline at end of file diff --git a/examples/sveltekit-mal-auth/src/routes/+layout.svelte b/examples/sveltekit-mal-auth/src/routes/+layout.svelte new file mode 100644 index 0000000..0fe0c73 --- /dev/null +++ b/examples/sveltekit-mal-auth/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + + + + + diff --git a/examples/sveltekit-mal-auth/src/routes/+page.svelte b/examples/sveltekit-mal-auth/src/routes/+page.svelte index 8f98e19..b262509 100644 --- a/examples/sveltekit-mal-auth/src/routes/+page.svelte +++ b/examples/sveltekit-mal-auth/src/routes/+page.svelte @@ -1,11 +1,6 @@ {#if $session.loading} diff --git a/packages/animelist-auth-sveltekit/package.json b/packages/animelist-auth-sveltekit/package.json index 2806a6e..6d34cc3 100644 --- a/packages/animelist-auth-sveltekit/package.json +++ b/packages/animelist-auth-sveltekit/package.json @@ -11,7 +11,8 @@ "entrypoints": "tsx entrypoints.script.mts", "build:types": "tsc --emitDeclarationOnly", "build:lib": "tsx esbuild.mts", - "build": "rimraf dist && pnpm build:types && pnpm build:lib && pnpm entrypoints" + "build:svelte": "svelte-package -i src/client -o dist/client", + "build": "rimraf dist && pnpm build:types && pnpm build:lib && pnpm run build:svelte && rimraf .svelte-kit && pnpm entrypoints" }, "keywords": [], "author": "", @@ -22,9 +23,14 @@ "@sveltejs/kit": "^1.25.0" }, "devDependencies": { + "@sveltejs/package": "^2.2.2", "eslint": "^8.49.0", + "svelte": "^4.0.5", "typescript": "^5.2.2" }, + "peerDependencies": { + "svelte": "^4.0.5" + }, "publishConfig": { "access": "public" }, @@ -34,6 +40,9 @@ "import": "./dist/client/index.mjs", "default": "./dist/client/index.mjs" }, + "./client/SessionProvider.svelte": { + "svelte": "./dist/client/SessionProvider.svelte" + }, "./server": { "import": "./dist/server/index.mjs", "default": "./dist/server/index.mjs" diff --git a/packages/animelist-auth-sveltekit/src/client/SessionProvider.svelte b/packages/animelist-auth-sveltekit/src/client/SessionProvider.svelte new file mode 100644 index 0000000..a36bc95 --- /dev/null +++ b/packages/animelist-auth-sveltekit/src/client/SessionProvider.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/animelist-auth-sveltekit/src/client/index.ts b/packages/animelist-auth-sveltekit/src/client/index.ts index de64132..b6bc88f 100644 --- a/packages/animelist-auth-sveltekit/src/client/index.ts +++ b/packages/animelist-auth-sveltekit/src/client/index.ts @@ -1 +1,2 @@ -export * from "./session"; \ No newline at end of file +export * from "./session"; +export { default as SessionProvider } from "./SessionProvider.svelte"; \ No newline at end of file diff --git a/packages/animelist-auth-sveltekit/src/client/session.ts b/packages/animelist-auth-sveltekit/src/client/session.ts index 1d36d02..8862432 100644 --- a/packages/animelist-auth-sveltekit/src/client/session.ts +++ b/packages/animelist-auth-sveltekit/src/client/session.ts @@ -1,107 +1,109 @@ -// import { dev } from "$app/environment"; -import { type User } from "@animelist/core"; -import { getSession } from "@animelist/auth/client"; -import { get, writable } from "svelte/store"; +import { type Session, getSession } from "@animelist/auth/client"; +import { get, writable, derived } from "svelte/store"; -let initialized = false; +/** + * @internal + */ +export const INITIALIZE_SESSION = Symbol("INITIALIZE_SESSION"); + +const DAY_MILLIS = 1000 * 60 * 60 * 24; export type SessionState = { - user: User | null; - accessToken: string | null; + session: Session | null, loading: boolean; } -const sessionStore = writable({ - user: null, - accessToken: null, - loading: false -}); - -type InitializeSession = Omit; - -function setUserSession(session: InitializeSession | null) { - if (session) { - initialized = true; - sessionStore.set({ - loading: false, - accessToken: session.accessToken, - user: session.user - }) - } else { - initialized = false; - sessionStore.set({ - loading: false, - accessToken: null, - user: null - }); - } -} +function createSession() { + const baseSessionStore = writable({ + session: null, + loading: false + }); -async function fetchUserSession() { - if (typeof window === 'undefined') { - return; - } + async function fetchUserSession() { + if (typeof window === 'undefined') { + return null; + } - initialized = true; + try { + // fetch the current user session + const session = await getSession(); - try { - // Set state to loading - const currentSession = get(sessionStore); - sessionStore.set({ - loading: true, - accessToken: currentSession.accessToken, - user: currentSession.user - }); + if (session == null) { + baseSessionStore.set({ loading: false, session }); + return null; + } - // fetch the current user session - const session = await getSession(); + baseSessionStore.set({ session, loading: false }); - if (session == null) { - return sessionStore.set({ loading: false, accessToken: null, user: null }); - } + // We use 1 day as a threshold because we don't expect an user to stay 24 hours + // without any interaction. in most cases this is not reached because the default session is 7 days + const expiresAt = new Date(session.expiresAt); - // if (dev) { - // console.log("🍥 User session loaded: ", JSON.stringify(session, null, 2)); - // } + if (expiresAt.getTime() < DAY_MILLIS) { + window.setTimeout( + fetchUserSession, + expiresAt.getTime() + ); + } - // Currently the expiration of the access token is 31 days, which is really long, - // so we don't have reason to refresh it, each time the user log in a new token will be created. - const { accessToken, user } = session; - sessionStore.set({ user, accessToken, loading: false }) - } - catch (err) { - console.error(err); - initialized = false; - sessionStore.set({ user: null, accessToken: null, loading: false }) - } -} + return session; + } + catch (err) { + console.error(err); + baseSessionStore.set({ session: null, loading: false }); + } -async function initialize(session?: InitializeSession | null) { - if (session && initialized) { - return; + return null; } - if (session !== undefined) { - setUserSession(session); - } - else { - await fetchUserSession(); + async function initialize(session?: Session | null) { + if (session === undefined) { + // Set state to loading + baseSessionStore.update(s => ({ ...s, loading: true })); + + // Fetch the current session + await fetchUserSession(); + } else { + baseSessionStore.set({ + session, + loading: false + }) + } } -} -function destroy() { - sessionStore.set({ - accessToken: null, - loading: false, - user: null, + const sessionStore = derived(baseSessionStore, ($store) => { + return { + /** + * Returns the current user. + */ + get user() { + return $store?.session?.user || null; + }, + + /** + * Returns the current user access token. + */ + get accessToken() { + return $store?.session?.accessToken || null + }, + + ...$store + } }) -} -export const session = { - initialize, - destroy, - subscribe: sessionStore.subscribe, - get current() { - return get(sessionStore); + return { + subscribe: sessionStore.subscribe, + + /** + * Returns `true` if the user is authenticated. + */ + get isAuthenticated() { + return get(baseSessionStore).session != null + }, + + // @internal + [INITIALIZE_SESSION]: initialize } } + +export const session = createSession(); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ab6fb6..c794719 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,9 +244,15 @@ importers: specifier: ^1.25.0 version: 1.25.0(svelte@4.2.1)(vite@4.4.9) devDependencies: + '@sveltejs/package': + specifier: ^2.2.2 + version: 2.2.2(svelte@4.2.1)(typescript@5.2.2) eslint: specifier: ^8.49.0 version: 8.49.0 + svelte: + specifier: ^4.0.5 + version: 4.2.1 typescript: specifier: ^5.2.2 version: 5.2.2 @@ -1405,6 +1411,23 @@ packages: transitivePeerDependencies: - supports-color + /@sveltejs/package@2.2.2(svelte@4.2.1)(typescript@5.2.2): + resolution: {integrity: sha512-rP3sVv6cAntcdcG4r4KspLU6nZYYUrHJBAX3Arrw0KJFdgxtlsi2iDwN0Jwr/vIkgjcU0ZPWM8kkT5kpZDlWAw==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 + dependencies: + chokidar: 3.5.3 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.5.4 + svelte: 4.2.1 + svelte2tsx: 0.6.22(svelte@4.2.1)(typescript@5.2.2) + transitivePeerDependencies: + - typescript + dev: true + /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.4.9): resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==} engines: {node: ^14.18.0 || >= 16} @@ -2682,6 +2705,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dev: true + /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -4732,6 +4759,12 @@ packages: get-func-name: 2.0.0 dev: true + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.6.2 + dev: true + /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} @@ -5117,6 +5150,13 @@ packages: - babel-plugin-macros dev: false + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.6.2 + dev: true + /node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} dev: true @@ -5678,6 +5718,13 @@ packages: parse-path: 7.0.0 dev: true + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + dev: true + /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -6841,6 +6888,18 @@ packages: typescript: 5.2.2 dev: true + /svelte2tsx@0.6.22(svelte@4.2.1)(typescript@5.2.2): + resolution: {integrity: sha512-eFCfz0juaWeanbwGeQV21kPMwH3LKhfrUYRy1PqRmlieuHvJs8VeK7CaoHJdpBZWCXba2cltHVdywJmwOGhbww==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 + typescript: ^4.9.4 || ^5.0.0 + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 4.2.1 + typescript: 5.2.2 + dev: true + /svelte@4.2.1: resolution: {integrity: sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==} engines: {node: '>=16'}