feat: ✨ add server sessions
This commit is contained in:
parent
c971542ec8
commit
ca79b4a20d
142
src/session/mod.ts
Normal file
142
src/session/mod.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import { getCookies } from '@std/http/cookie'
|
||||
|
||||
type SessionEntry<T extends unknown = unknown> = {
|
||||
accessCount: number
|
||||
flash: boolean
|
||||
value: T
|
||||
}
|
||||
|
||||
export class SessionStore {
|
||||
static #store = new Map<string, Session>()
|
||||
static createSession(): Session {
|
||||
return new Session(this.#store)
|
||||
}
|
||||
|
||||
static getSession(uuid: string): Session | undefined {
|
||||
// Check session validity
|
||||
const halfHour = 30 * 60 * 1_000
|
||||
const maxAge = Date.now() - halfHour
|
||||
const session = this.#store.get(uuid)
|
||||
|
||||
if (session === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (session.timestamp < maxAge) {
|
||||
session.destroy()
|
||||
return undefined
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
static getFromRequest(request: Request): Session | undefined {
|
||||
const sessionId = getCookies(request.headers)['_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 extends unknown = 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 extends unknown = 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 extends unknown = 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 }
|
Loading…
Reference in a new issue