A Deno-compatible AT Protocol OAuth client that serves as a drop-in replacement for @atproto/oauth-client-node
at main 162 lines 4.7 kB view raw
1/** 2 * @fileoverview Storage implementations for OAuth client session persistence 3 * @module 4 */ 5 6import type { OAuthStorage } from "./types.ts"; 7export type { OAuthStorage as Storage } from "./types.ts"; 8 9/** 10 * Simple in-memory storage implementation for OAuth sessions. 11 * 12 * Stores data in memory with optional TTL support. Data is lost when the 13 * process restarts. Good for development, testing, and temporary sessions. 14 * 15 * @example 16 * ```ts 17 * const storage = new MemoryStorage(); 18 * 19 * // Store with TTL 20 * await storage.set("session-123", sessionData, { ttl: 3600 }); // 1 hour 21 * 22 * // Retrieve 23 * const session = await storage.get("session-123"); 24 * 25 * // Clean up 26 * await storage.delete("session-123"); 27 * ``` 28 */ 29export class MemoryStorage implements OAuthStorage { 30 private data = new Map<string, { value: unknown; expiresAt?: number }>(); 31 32 async get<T = unknown>(key: string): Promise<T | null> { 33 await Promise.resolve(); // Satisfy require-await linting rule 34 const item = this.data.get(key); 35 if (!item) return null; 36 37 if (item.expiresAt && Date.now() > item.expiresAt) { 38 this.data.delete(key); 39 return null; 40 } 41 42 return item.value as T; 43 } 44 45 async set<T = unknown>(key: string, value: T, options?: { ttl?: number }): Promise<void> { 46 await Promise.resolve(); // Satisfy require-await linting rule 47 const expiresAt = options?.ttl ? Date.now() + (options.ttl * 1000) : undefined; 48 this.data.set(key, { value, ...(expiresAt ? { expiresAt } : {}) }); 49 } 50 51 async delete(key: string): Promise<void> { 52 await Promise.resolve(); // Satisfy require-await linting rule 53 this.data.delete(key); 54 } 55 56 // Utility method for cleanup in tests 57 clear(): void { 58 this.data.clear(); 59 } 60} 61 62/** 63 * Example SQLite storage implementation (for reference) 64 * Users can implement similar patterns for their storage backend 65 */ 66export class SQLiteStorage implements OAuthStorage { 67 constructor( 68 private sqlite: { 69 execute: ( 70 query: { sql: string; args: unknown[] }, 71 ) => Promise<{ columns: string[]; rows: unknown[][] }>; 72 }, 73 ) {} 74 75 async get<T = unknown>(key: string): Promise<T | null> { 76 const result = await this.sqlite.execute({ 77 sql: "SELECT value, expires_at FROM oauth_storage WHERE key = ?", 78 args: [key], 79 }); 80 81 if (result.rows.length === 0) return null; 82 83 const row = result.rows[0]; 84 if (!row || row.length < 2) return null; 85 86 const [value, expiresAt] = row; 87 88 // Validate types from database 89 if (typeof value !== "string") { 90 throw new Error("Invalid storage value: expected string"); 91 } 92 93 if (expiresAt !== null && typeof expiresAt === "number" && Date.now() > expiresAt) { 94 await this.delete(key); 95 return null; 96 } 97 98 return JSON.parse(value) as T; 99 } 100 101 async set<T = unknown>(key: string, value: T, options?: { ttl?: number }): Promise<void> { 102 const expiresAt = options?.ttl ? Date.now() + (options.ttl * 1000) : null; 103 104 // Ensure table exists 105 await this.sqlite.execute({ 106 sql: `CREATE TABLE IF NOT EXISTS oauth_storage ( 107 key TEXT PRIMARY KEY, 108 value TEXT NOT NULL, 109 expires_at INTEGER, 110 created_at INTEGER DEFAULT (unixepoch() * 1000) 111 )`, 112 args: [], 113 }); 114 115 await this.sqlite.execute({ 116 sql: "INSERT OR REPLACE INTO oauth_storage (key, value, expires_at) VALUES (?, ?, ?)", 117 args: [key, JSON.stringify(value), expiresAt], 118 }); 119 } 120 121 async delete(key: string): Promise<void> { 122 await this.sqlite.execute({ 123 sql: "DELETE FROM oauth_storage WHERE key = ?", 124 args: [key], 125 }); 126 } 127} 128 129/** 130 * Example localStorage-compatible storage (for browser/Deno environments with localStorage) 131 */ 132export class LocalStorage implements OAuthStorage { 133 async get<T = unknown>(key: string): Promise<T | null> { 134 await Promise.resolve(); // Satisfy require-await linting rule 135 try { 136 const item = localStorage.getItem(key); 137 if (!item) return null; 138 139 const parsed = JSON.parse(item); 140 if (parsed.expiresAt && Date.now() > parsed.expiresAt) { 141 localStorage.removeItem(key); 142 return null; 143 } 144 145 return parsed.value as T; 146 } catch { 147 return null; 148 } 149 } 150 151 async set<T = unknown>(key: string, value: T, options?: { ttl?: number }): Promise<void> { 152 await Promise.resolve(); // Satisfy require-await linting rule 153 const expiresAt = options?.ttl ? Date.now() + (options.ttl * 1000) : undefined; 154 const item = { value, expiresAt }; 155 localStorage.setItem(key, JSON.stringify(item)); 156 } 157 158 async delete(key: string): Promise<void> { 159 await Promise.resolve(); // Satisfy require-await linting rule 160 localStorage.removeItem(key); 161 } 162}