Something went wrong. Try again.
The logic for serving and storing associations between users and apps
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126import { DurableObject } from "cloudflare:workers";
export const TTL_MS = 7 * 24 * 60 * 60 * 1000;export const MIN_INTERVAL_MS = 2 * 60 * 1000;/** Max distinct DID+origin rows retained per browser session. */export const MAX_ACCOUNT_ROWS = 40;
export type Account = { did: string; origins: string[];};
type AccountRow = { did: string; origin: string; last_seen: number;};
export class SessionDO extends DurableObject<object> { constructor(ctx: DurableObjectState, env: object) { super(ctx, env); ctx.storage.sql.exec(` CREATE TABLE IF NOT EXISTS accounts ( did TEXT NOT NULL, origin TEXT NOT NULL, last_seen INTEGER NOT NULL, PRIMARY KEY (did, origin) ) `); }
async loggedIn(did: string, origin: string): Promise<{ ok: boolean }> { const now = Date.now(); const existing = this.ctx.storage.sql .exec<AccountRow>( "SELECT did, origin, last_seen FROM accounts WHERE did = ? AND origin = ?", did, origin, ) .toArray()[0];
if (existing && now - existing.last_seen < MIN_INTERVAL_MS) { return { ok: true }; }
if (!existing) { await this.enforceRowCap(); }
this.ctx.storage.sql.exec( `INSERT INTO accounts (did, origin, last_seen) VALUES (?, ?, ?) ON CONFLICT(did, origin) DO UPDATE SET last_seen = excluded.last_seen`, did, origin, now, ); await this.touchAlarm(); return { ok: true }; }
async logout(did: string, origin: string): Promise<{ ok: boolean }> { this.ctx.storage.sql.exec( "DELETE FROM accounts WHERE did = ? AND origin = ?", did, origin, ); return { ok: true }; }
async list(): Promise<{ accounts: Account[] }> { const cutoff = Date.now() - TTL_MS; const rows = this.ctx.storage.sql .exec<AccountRow>( "SELECT did, origin, last_seen FROM accounts WHERE last_seen >= ? ORDER BY last_seen DESC", cutoff, ) .toArray();
const byDid = new Map<string, string[]>(); for (const row of rows) { const origins = byDid.get(row.did) ?? []; if (!origins.includes(row.origin)) origins.push(row.origin); byDid.set(row.did, origins); }
return { accounts: [...byDid.entries()].map(([did, origins]) => ({ did, origins })), }; }
async alarm(): Promise<void> { const cutoff = Date.now() - TTL_MS; this.ctx.storage.sql.exec("DELETE FROM accounts WHERE last_seen < ?", cutoff); const next = this.ctx.storage.sql .exec<{ m: number | null }>("SELECT MIN(last_seen) AS m FROM accounts") .toArray()[0]; if (next?.m != null) { await this.ctx.storage.setAlarm(Number(next.m) + TTL_MS); } }
private async enforceRowCap(): Promise<void> { const cutoff = Date.now() - TTL_MS; this.ctx.storage.sql.exec("DELETE FROM accounts WHERE last_seen < ?", cutoff); const count = this.ctx.storage.sql .exec<{ n: number }>("SELECT COUNT(*) AS n FROM accounts") .toArray()[0]?.n; if (count == null || count < MAX_ACCOUNT_ROWS) return; const overflow = count - MAX_ACCOUNT_ROWS + 1; this.ctx.storage.sql.exec( `DELETE FROM accounts WHERE rowid IN ( SELECT rowid FROM accounts ORDER BY last_seen ASC LIMIT ? )`, overflow, ); }
private async touchAlarm(): Promise<void> { const current = await this.ctx.storage.getAlarm(); const when = Date.now() + TTL_MS; if (current == null || current > when) { await this.ctx.storage.setAlarm(when); } }}