Releases: razshare/sveltekit-server-session
0.1.5
SvelteKit Server Session
This library provides an easy way to start, serve and modify server sessions.
Install with:
npm i -D sveltekit-server-session
Start a session
Use session.start()
to start a session.
It requires SvelteKits' Cookies interface.
import { session } from 'sveltekit-server-session'
export async function GET({ cookies }) {
const {data, response} await session.start({ cookies })
return response("hello world")
}
Note
The response()
function creates a Response
object and appends to it the headers required for session management.
An example
<!-- src/routes/+page.svelte -->
<script>
import { onMount } from 'svelte'
let text = ''
let ready = false
let sending = false
onMount(async function start() {
const response = await fetch('/session/quote/get')
text = await response.text()
ready = true
})
async function set() {
sending = true
await fetch('/session/quote/update', { method: 'PUT', body: text })
sending = false
}
</script>
{#if ready}
<div class="content">
<textarea bind:value={text}></textarea>
<br />
<button disabled={sending} on:mouseup={set}>
<span>Save</span>
</button>
</div>
{/if}
// src/routes/session/quote/get/+server.js
import { session } from 'sveltekit-server-session'
export async function GET({ cookies }) {
const {
error,
value: { data, response },
} = await session.start({ cookies })
if (error) {
return new Response(error.message, { status: 500 })
}
if (!data.has('quote')) {
data.set('quote', 'initial quote')
}
return response(data.get('quote'))
}
// src/routes/session/quote/update/+server.js
import { session } from 'sveltekit-server-session'
export async function PUT({ cookies, request }) {
const {
error,
value: { data, response },
} = await session.start({ cookies })
if (error) {
return new Response(error.message, { status: 500 })
}
data.set('quote', await request.text())
return response(data.get('quote'))
}
Lifetime
The only way to start a session is through
await session.start({ cookies })
Whenever you start a session you're actually trying to retrieve a KITSESSID
cookie from the client, which should hold a session id.
Note
Sessions are internally mapped with a Map<string, Session>
object.
This map's keys are the sessions id
s of your application.
If the client doesn't hold a session id cookie it means it has no session, so a new one is created.
If the client does have a session id but is expired, the relative session is immediately destroyed, then a new session is created.
This new session doesn't contain any of the old session's data.
Finally, if the client has a valid session id cookie, then the relative session is retrieved.
Starting a session should always succeed, wether it is by creating a new session or retrieving an existing one.
Destroy & Flush
As explained above, in the lifetime section, clients that present an expired session id have their sessions destroyed immediately.
But sometimes clients want to create new sessions regardless if the current one has expired or not.
Other times clients abandon their sessions and never awaken them again.
These use cases can be a problem.
Even though these "abandoned" sessions are not active, they will still use some memory,
so they must be destroyed one way or another.
You can use destroy()
// src/routes/session/destroy/+server.js
import { session } from 'sveltekit-server-session'
/**
*
* @param {number} milliseconds
* @returns {Promise<void>}
*/
function delay(milliseconds) {
return new Promise(function start(resolve) {
setTimeout(resolve, milliseconds)
})
}
export async function GET({ cookies }) {
const {
error,
value: { destroy }, // <=== Obtain the function here.
} = await session.start({ cookies })
if (error) {
return new Response(error.message, { status: 500 })
}
await destroy() // <=== Destroy the session here.
await delay(3000)
return new Response('Session destroyed.')
}
to destroy each session manually.
For example this may be useful to invoke when clicking a logout button.
However one problem still remains
Other times clients abandon their sessions and never awaken them again.
When a client simply stops using your application for days even, destroying a session can become a bit more convoluted,
because in those cases there's no client to click the hypothetical logout button.
So what do we do?
The answer is nothing, this library takes care of that.
Whenever you create a session, a destructor function is automatically dispatched to destroy the session when it expires.
This is simply achieved through the event loop, using a Timer
through setTimeout
.
Custom Behavior
You can customize your sessions behavior with session.setOperations()
.
All operations related to session management are defined by SessionInterface
.
Even though some operations may appear to act directly on an instance of a session, like .destroy()
, in reality they all use only operations defined by SessionInterface
.
This means you have total control over how sessions are retrieved, modified and validated.
Here's an example of how to set a custom set of operations for session management
import { ok } from 'sveltekit-unsafe' // peer dependency
import { session } from 'sveltekit-server-session'
/**
* @type {Map<string,import('$lib/types').Session>}
*/
const map = new Map()
session.setOperations({
async exists(id) {
return ok(map.has(id))
},
async isValid(id) {
const session = map.get(id)
if (!session) {
return ok(false)
}
return ok(session.getRemainingSeconds() > 0)
},
async has(id) {
return ok(map.has(id))
},
async get(id) {
return ok(map.get(id))
},
async set(id, session) {
map.set(id, session)
return ok()
},
async delete(id) {
map.delete(id)
return ok()
},
})
Note
As you may have figured out, since these function definitions have an implicit asynchronous nature
they might as well query a database instead of working with an in-memory map, for improved resilience.
Using SvelteKit Hooks
You can simplify your developer experience by moving the session management logic into your src/hooks.server.js
.
- First of all create a new
src/hooks.server.js
and move your session management logic in yourhandle
function
import { session } from 'sveltekit-server-session'; /** * @type {import("@sveltejs/kit").Handle} */ export async function handle({ event, resolve }) { const { error, value: sessionLocal } = await session.start({ cookies: event.cookies }); if (error) { return new Response(error.message, { status: 500 }); } event.locals.session = sessionLocal; // <=== Set session here. // You will get a type hinting error, this is normal. // See next step in order to fix this. const response = await resolve(event); for (const [key, value] of sessionLocal.response().headers) { response.headers.set(key, value); } return response; }
- Open your
src/app.d.ts
file and define your session key underinterface Locals
// See https://kit.svelte.dev/docs/types#app import type { Session } from 'sveltekit-server-session'; // for information about these interfaces declare global { namespace App { // interface Error {} interface Locals { session: Session; // <== Here. } // interface PageData {} // interface PageState {} // interface Platform {} } } export {};
And you're done, now all you have to do is destruct your session from your endpoints like so
// src/routes/session/quote/update/+server.js
export async function PUT({ locals, request }) {
const { data, response } = locals.session // <=== Here.
data.set('quote', await request.text())
return response(data.get('quote'))
}
Recommended Usage
So far the development process in the example above has involved using a fetch('/session/quote/get')
call to retrieve the initial value of the quote
, but that is not necessary, we can simplify things even further by building on top of the Using SvelteKit Hooks section.
Since the hook handler defined above populates our locals
prop, this means we now have access to the session from any +page.server.js
file, so the following is now possible
// src/routes/+page.server.js
/**
* @type {import("./$types").PageServerLoad}
*/
export function load({ locals }) {
const { data } = locals.session;
if (!data.has('quote')) {
data.set('quote', 'initial quote');
}
return {
text: data.get('quote')
};
}
There is no need for the src/routes/session/quote/get/+server.js
file an...