import type { Handlers, PageProps } from '$fresh/server.ts' import { getCookies } from '@std/http/cookie' type SessionEntry = { accessCount: number flash: boolean value: T } export interface SessionState { session: Session } export type SessionPageProps> = PageProps export type SessionHandlers> = Handlers export class SessionStore { static #store = new Map() static get maxAge() { const halfHour = 30 * 60 * 1_000 return Date.now() + halfHour } static createSession(): Session { return new Session(this.#store) } static getSession(uuid: string): Session | undefined { // Check session validity const halfHour = 30 * 60 * 1_000 const maxOld = Date.now() - halfHour const session = this.#store.get(uuid) if (session === undefined) { return undefined } if (session.timestamp < maxOld) { session.destroy() return undefined } return session } static getFromRequest(request: Request): Session | undefined { const sessionId = getCookies(request.headers)['__Secure-SESSION'] ?? '' return this.getSession(sessionId) } private constructor() {} } class Session { #db = new Map() #timestamp = Date.now() #uuid = crypto.randomUUID() #store: Map constructor(store: Map) { this.#store = store store.set(this.#uuid, this) //cleanup old sessions const halfHour = 30 * 60 * 1_000 const maxAge = this.#timestamp - halfHour for (const [uuid, session] of store.entries()) { if (session.#timestamp < maxAge) { store.delete(uuid) } } } #updateTimestamp() { this.#timestamp = Date.now() } get uuid() { return this.#uuid } get timestamp() { return this.#timestamp } get(key: string): T | undefined { this.#updateTimestamp() const entry = this.#db.get(key) // no entry if (entry === undefined) return // flash entry if (entry.flash) { this.#db.delete(key) return entry.value as T } // normal entry entry.accessCount++ this.#db.set(key, entry) return entry.value as T } set(key: string, value: T): void { this.#updateTimestamp() const entry = this.#db.get(key) as SessionEntry | undefined // update or create const newEntry: SessionEntry = { accessCount: entry?.accessCount ? entry.accessCount++ : 0, flash: entry?.flash ?? false, value, } this.#db.set(key, newEntry) } delete(key: string): boolean { this.#updateTimestamp() return this.#db.delete(key) } has(key: string): boolean { this.#updateTimestamp() return this.#db.has(key) } list(): [key: string, value: unknown][] { this.#updateTimestamp() const keys = [...this.#db.keys()] return keys.map((key) => ([key, this.get(key)] as [string, unknown])) } flash(key: string, value: T) { this.#updateTimestamp() const entry = this.#db.get(key) as SessionEntry | undefined // update or create const newEntry: SessionEntry = { accessCount: entry?.accessCount ? entry.accessCount++ : 0, flash: true, value, } this.#db.set(key, newEntry) } destroy() { this.#db.clear() this.#store.delete(this.uuid) } } export type { Session }