website/src/session/mod.ts

159 lines
3.3 KiB
TypeScript

import type { Handlers, PageProps } from '$fresh/server.ts'
import { getCookies } from '@std/http/cookie'
type SessionEntry<T = unknown> = {
accessCount: number
flash: boolean
value: T
}
export interface SessionState {
session: Session
}
export type SessionPageProps<T = unknown, S = Record<string, unknown>> =
PageProps<T, S & SessionState>
export type SessionHandlers<T = unknown, S = Record<string, unknown>> =
Handlers<T, S & SessionState>
export class SessionStore {
static #store = new Map<string, Session>()
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<string, SessionEntry>()
#timestamp = Date.now()
#uuid = crypto.randomUUID()
#store: Map<string, Session>
constructor(store: Map<string, Session>) {
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<T = unknown>(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<T = unknown>(key: string, value: T): void {
this.#updateTimestamp()
const entry = this.#db.get(key) as SessionEntry<T> | undefined
// update or create
const newEntry: SessionEntry<T> = {
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<T = unknown>(key: string, value: T) {
this.#updateTimestamp()
const entry = this.#db.get(key) as SessionEntry<T> | undefined
// update or create
const newEntry: SessionEntry<T> = {
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 }