159 lines
3.3 KiB
TypeScript
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 }
|