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