diff --git a/migrations/0002_remove_topics.sql b/migrations/0002_remove_topics.sql new file mode 100644 index 0000000..8fe4492 --- /dev/null +++ b/migrations/0002_remove_topics.sql @@ -0,0 +1,22 @@ +-- Remove topics and rename sessions to offers +-- This is a breaking change requiring a fresh database + +-- Drop old sessions table +DROP TABLE IF EXISTS sessions; + +-- Create offers table (without topic) +CREATE TABLE offers ( + code TEXT PRIMARY KEY, + origin TEXT NOT NULL, + peer_id TEXT NOT NULL CHECK(length(peer_id) <= 1024), + offer TEXT NOT NULL, + answer TEXT, + offer_candidates TEXT NOT NULL DEFAULT '[]', + answer_candidates TEXT NOT NULL DEFAULT '[]', + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL +); + +-- Create indexes for efficient queries +CREATE INDEX idx_offers_expires_at ON offers(expires_at); +CREATE INDEX idx_offers_origin ON offers(origin); diff --git a/src/app.ts b/src/app.ts index 9a778af..6eaeb88 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,13 +4,13 @@ import { Storage } from './storage/types.ts'; import type { Context } from 'hono'; export interface AppConfig { - sessionTimeout: number; + offerTimeout: number; corsOrigins: string[]; version?: string; } /** - * Determines the origin for session isolation. + * Determines the origin for offer isolation. * If X-Rondevu-Global header is set to 'true', returns the global origin (https://ronde.vu). * Otherwise, returns the request's Origin header. */ @@ -60,80 +60,28 @@ export function createApp(storage: Storage, config: AppConfig) { }); /** - * GET /topics - * Lists all topics with their unanswered session counts (paginated) - * Query params: page (default: 1), limit (default: 100, max: 1000) + * GET /health + * Health check endpoint with version */ - app.get('/topics', async (c) => { - try { - const origin = getOrigin(c); - const page = parseInt(c.req.query('page') || '1', 10); - const limit = parseInt(c.req.query('limit') || '100', 10); - - const result = await storage.listTopics(origin, page, limit); - - return c.json(result); - } catch (err) { - console.error('Error listing topics:', err); - return c.json({ error: 'Internal server error' }, 500); - } + app.get('/health', (c) => { + return c.json({ + status: 'ok', + timestamp: Date.now(), + version: config.version || 'unknown' + }); }); /** - * GET /:topic/sessions - * Lists all unanswered sessions for a topic + * POST /offer + * Creates a new offer and returns a unique code + * Body: { peerId: string, offer: string, code?: string } */ - app.get('/:topic/sessions', async (c) => { + app.post('/offer', async (c) => { try { const origin = getOrigin(c); - const topic = c.req.param('topic'); - - if (!topic) { - return c.json({ error: 'Missing required parameter: topic' }, 400); - } - - if (topic.length > 1024) { - return c.json({ error: 'Topic string must be 1024 characters or less' }, 400); - } - - const sessions = await storage.listSessionsByTopic(origin, topic); - - return c.json({ - sessions: sessions.map(s => ({ - code: s.code, - peerId: s.peerId, - offer: s.offer, - offerCandidates: s.offerCandidates, - createdAt: s.createdAt, - expiresAt: s.expiresAt, - })), - }); - } catch (err) { - console.error('Error listing sessions:', err); - return c.json({ error: 'Internal server error' }, 500); - } - }); - - /** - * POST /:topic/offer - * Creates a new offer and returns a unique session code - * Body: { peerId: string, offer: string } - */ - app.post('/:topic/offer', async (c) => { - try { - const origin = getOrigin(c); - const topic = c.req.param('topic'); const body = await c.req.json(); const { peerId, offer, code: customCode } = body; - if (!topic || typeof topic !== 'string') { - return c.json({ error: 'Missing or invalid required parameter: topic' }, 400); - } - - if (topic.length > 1024) { - return c.json({ error: 'Topic string must be 1024 characters or less' }, 400); - } - if (!peerId || typeof peerId !== 'string') { return c.json({ error: 'Missing or invalid required parameter: peerId' }, 400); } @@ -146,14 +94,14 @@ export function createApp(storage: Storage, config: AppConfig) { return c.json({ error: 'Missing or invalid required parameter: offer' }, 400); } - const expiresAt = Date.now() + config.sessionTimeout; - const code = await storage.createSession(origin, topic, peerId, offer, expiresAt, customCode); + const expiresAt = Date.now() + config.offerTimeout; + const code = await storage.createOffer(origin, peerId, offer, expiresAt, customCode); return c.json({ code }, 200); } catch (err) { console.error('Error creating offer:', err); - // Check if it's a session code clash error + // Check if it's a code clash error if (err instanceof Error && err.message.includes('already exists')) { return c.json({ error: err.message }, 409); } @@ -189,23 +137,23 @@ export function createApp(storage: Storage, config: AppConfig) { return c.json({ error: 'Cannot provide both answer and candidate' }, 400); } - const session = await storage.getSession(code, origin); + const offer = await storage.getOffer(code, origin); - if (!session) { - return c.json({ error: 'Session not found, expired, or origin mismatch' }, 404); + if (!offer) { + return c.json({ error: 'Offer not found, expired, or origin mismatch' }, 404); } if (answer) { - await storage.updateSession(code, origin, { answer }); + await storage.updateOffer(code, origin, { answer }); } if (candidate) { if (side === 'offerer') { - const updatedCandidates = [...session.offerCandidates, candidate]; - await storage.updateSession(code, origin, { offerCandidates: updatedCandidates }); + const updatedCandidates = [...offer.offerCandidates, candidate]; + await storage.updateOffer(code, origin, { offerCandidates: updatedCandidates }); } else { - const updatedCandidates = [...session.answerCandidates, candidate]; - await storage.updateSession(code, origin, { answerCandidates: updatedCandidates }); + const updatedCandidates = [...offer.answerCandidates, candidate]; + await storage.updateOffer(code, origin, { answerCandidates: updatedCandidates }); } } @@ -218,7 +166,7 @@ export function createApp(storage: Storage, config: AppConfig) { /** * POST /poll - * Polls for session data (offer, answer, ICE candidates) + * Polls for offer data (offer, answer, ICE candidates) * Body: { code: string, side: 'offerer' | 'answerer' } */ app.post('/poll', async (c) => { @@ -235,36 +183,28 @@ export function createApp(storage: Storage, config: AppConfig) { return c.json({ error: 'Invalid or missing parameter: side (must be "offerer" or "answerer")' }, 400); } - const session = await storage.getSession(code, origin); + const offer = await storage.getOffer(code, origin); - if (!session) { - return c.json({ error: 'Session not found, expired, or origin mismatch' }, 404); + if (!offer) { + return c.json({ error: 'Offer not found, expired, or origin mismatch' }, 404); } if (side === 'offerer') { return c.json({ - answer: session.answer || null, - answerCandidates: session.answerCandidates, + answer: offer.answer || null, + answerCandidates: offer.answerCandidates, }); } else { return c.json({ - offer: session.offer, - offerCandidates: session.offerCandidates, + offer: offer.offer, + offerCandidates: offer.offerCandidates, }); } } catch (err) { - console.error('Error polling session:', err); + console.error('Error polling offer:', err); return c.json({ error: 'Internal server error' }, 500); } }); - /** - * GET /health - * Health check endpoint - */ - app.get('/health', (c) => { - return c.json({ status: 'ok', timestamp: Date.now() }); - }); - return app; } diff --git a/src/config.ts b/src/config.ts index ec0478f..0a778f6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,8 +6,9 @@ export interface Config { port: number; storageType: 'sqlite' | 'memory'; storagePath: string; - sessionTimeout: number; + offerTimeout: number; corsOrigins: string[]; + version: string; } /** @@ -18,9 +19,10 @@ export function loadConfig(): Config { port: parseInt(process.env.PORT || '3000', 10), storageType: (process.env.STORAGE_TYPE || 'sqlite') as 'sqlite' | 'memory', storagePath: process.env.STORAGE_PATH || ':memory:', - sessionTimeout: parseInt(process.env.SESSION_TIMEOUT || '300000', 10), + offerTimeout: parseInt(process.env.OFFER_TIMEOUT || '60000', 10), corsOrigins: process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(',').map(o => o.trim()) : ['*'], + version: process.env.VERSION || 'unknown', }; } diff --git a/src/index.ts b/src/index.ts index 15b9d11..ccb207f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,9 @@ async function main() { port: config.port, storageType: config.storageType, storagePath: config.storagePath, - sessionTimeout: `${config.sessionTimeout}ms`, + offerTimeout: `${config.offerTimeout}ms`, corsOrigins: config.corsOrigins, + version: config.version, }); let storage: Storage; @@ -29,9 +30,9 @@ async function main() { } const app = createApp(storage, { - sessionTimeout: config.sessionTimeout, + offerTimeout: config.offerTimeout, corsOrigins: config.corsOrigins, - version: process.env.RONDEVU_VERSION || 'unknown', + version: config.version, }); const server = serve({ diff --git a/src/storage/d1.ts b/src/storage/d1.ts index a5897b8..063d204 100644 --- a/src/storage/d1.ts +++ b/src/storage/d1.ts @@ -1,4 +1,4 @@ -import { Storage, Session } from './types.ts'; +import { Storage, Offer } from './types.ts'; // Generate a UUID v4 function generateUUID(): string { @@ -6,7 +6,7 @@ function generateUUID(): string { } /** - * D1 storage adapter for session management using Cloudflare D1 + * D1 storage adapter for offer management using Cloudflare D1 */ export class D1Storage implements Storage { private db: D1Database; @@ -25,10 +25,9 @@ export class D1Storage implements Storage { */ async initializeDatabase(): Promise { await this.db.exec(` - CREATE TABLE IF NOT EXISTS sessions ( + CREATE TABLE IF NOT EXISTS offers ( code TEXT PRIMARY KEY, origin TEXT NOT NULL, - topic TEXT NOT NULL, peer_id TEXT NOT NULL CHECK(length(peer_id) <= 1024), offer TEXT NOT NULL, answer TEXT, @@ -38,113 +37,13 @@ export class D1Storage implements Storage { expires_at INTEGER NOT NULL ); - CREATE INDEX IF NOT EXISTS idx_expires_at ON sessions(expires_at); - CREATE INDEX IF NOT EXISTS idx_origin_topic ON sessions(origin, topic); - CREATE INDEX IF NOT EXISTS idx_origin_topic_expires ON sessions(origin, topic, expires_at); + CREATE INDEX IF NOT EXISTS idx_offers_expires_at ON offers(expires_at); + CREATE INDEX IF NOT EXISTS idx_offers_origin ON offers(origin); `); } - async listTopics(origin: string, page: number = 1, limit: number = 100): Promise<{ - topics: Array<{ topic: string; count: number }>; - pagination: { - page: number; - limit: number; - total: number; - hasMore: boolean; - }; - }> { - // Clamp limit to maximum of 1000 - const effectiveLimit = Math.min(limit, 1000); - const offset = (page - 1) * effectiveLimit; - - try { - // Get total count of topics for this origin - const countResult = await this.db.prepare(` - SELECT COUNT(DISTINCT topic) as total - FROM sessions - WHERE origin = ? AND expires_at > ? AND answer IS NULL - `).bind(origin, Date.now()).first(); - - const total = countResult ? Number(countResult.total) : 0; - - // Get paginated topics - const result = await this.db.prepare(` - SELECT topic, COUNT(*) as count - FROM sessions - WHERE origin = ? AND expires_at > ? AND answer IS NULL - GROUP BY topic - ORDER BY topic ASC - LIMIT ? OFFSET ? - `).bind(origin, Date.now(), effectiveLimit, offset).all(); - - // D1 returns results in the results array, or empty array if no results - if (!result.results) { - console.error('[D1] listTopics: No results property in response:', result); - return { - topics: [], - pagination: { - page, - limit: effectiveLimit, - total: 0, - hasMore: false, - }, - }; - } - - const topics = result.results.map((row: any) => ({ - topic: row.topic, - count: Number(row.count), - })); - - return { - topics, - pagination: { - page, - limit: effectiveLimit, - total, - hasMore: offset + topics.length < total, - }, - }; - } catch (error) { - console.error('[D1] listTopics error:', error); - throw error; - } - } - - async listSessionsByTopic(origin: string, topic: string): Promise { - try { - const result = await this.db.prepare(` - SELECT * FROM sessions - WHERE origin = ? AND topic = ? AND expires_at > ? AND answer IS NULL - ORDER BY created_at DESC - `).bind(origin, topic, Date.now()).all(); - - if (!result.results) { - console.error('[D1] listSessionsByTopic: No results property in response:', result); - return []; - } - - return result.results.map((row: any) => ({ - code: row.code, - origin: row.origin, - topic: row.topic, - peerId: row.peer_id, - offer: row.offer, - answer: row.answer || undefined, - offerCandidates: JSON.parse(row.offer_candidates), - answerCandidates: JSON.parse(row.answer_candidates), - createdAt: row.created_at, - expiresAt: row.expires_at, - })); - } catch (error) { - console.error('[D1] listSessionsByTopic error:', error); - throw error; - } - } - - async createSession( + async createOffer( origin: string, - topic: string, peerId: string, offer: string, expiresAt: number, @@ -160,21 +59,21 @@ export class D1Storage implements Storage { attempts++; if (attempts > maxAttempts) { - throw new Error('Failed to generate unique session code'); + throw new Error('Failed to generate unique offer code'); } try { await this.db.prepare(` - INSERT INTO sessions (code, origin, topic, peer_id, offer, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?, ?) - `).bind(code, origin, topic, peerId, offer, Date.now(), expiresAt).run(); + INSERT INTO offers (code, origin, peer_id, offer, created_at, expires_at) + VALUES (?, ?, ?, ?, ?, ?) + `).bind(code, origin, peerId, offer, Date.now(), expiresAt).run(); break; } catch (err: any) { // If unique constraint failed with custom code, throw error if (err.message?.includes('UNIQUE constraint failed')) { if (customCode) { - throw new Error(`Session code '${customCode}' already exists`); + throw new Error(`Offer code '${customCode}' already exists`); } // Try again with new generated code continue; @@ -186,10 +85,10 @@ export class D1Storage implements Storage { return code; } - async getSession(code: string, origin: string): Promise { + async getOffer(code: string, origin: string): Promise { try { const result = await this.db.prepare(` - SELECT * FROM sessions + SELECT * FROM offers WHERE code = ? AND origin = ? AND expires_at > ? `).bind(code, origin, Date.now()).first(); @@ -202,7 +101,6 @@ export class D1Storage implements Storage { return { code: row.code, origin: row.origin, - topic: row.topic, peerId: row.peer_id, offer: row.offer, answer: row.answer || undefined, @@ -212,17 +110,17 @@ export class D1Storage implements Storage { expiresAt: row.expires_at, }; } catch (error) { - console.error('[D1] getSession error:', error); + console.error('[D1] getOffer error:', error); throw error; } } - async updateSession(code: string, origin: string, update: Partial): Promise { - // Verify session exists and origin matches - const current = await this.getSession(code, origin); + async updateOffer(code: string, origin: string, update: Partial): Promise { + // Verify offer exists and origin matches + const current = await this.getOffer(code, origin); if (!current) { - throw new Error('Session not found or origin mismatch'); + throw new Error('Offer not found or origin mismatch'); } // Build update query dynamically based on what fields are being updated @@ -253,7 +151,7 @@ export class D1Storage implements Storage { // D1 provides strong consistency, so this update is atomic and immediately visible const query = ` - UPDATE sessions + UPDATE offers SET ${updates.join(', ')} WHERE code = ? AND origin = ? `; @@ -261,22 +159,22 @@ export class D1Storage implements Storage { await this.db.prepare(query).bind(...values).run(); } - async deleteSession(code: string): Promise { + async deleteOffer(code: string): Promise { await this.db.prepare(` - DELETE FROM sessions WHERE code = ? + DELETE FROM offers WHERE code = ? `).bind(code).run(); } - async cleanupExpiredSessions(): Promise { + async cleanupExpiredOffers(): Promise { const result = await this.db.prepare(` - DELETE FROM sessions WHERE expires_at <= ? + DELETE FROM offers WHERE expires_at <= ? `).bind(Date.now()).run(); return result.meta.changes || 0; } async cleanup(): Promise { - await this.cleanupExpiredSessions(); + await this.cleanupExpiredOffers(); } async close(): Promise { diff --git a/src/storage/sqlite.ts b/src/storage/sqlite.ts index f21a195..6629ad6 100644 --- a/src/storage/sqlite.ts +++ b/src/storage/sqlite.ts @@ -1,9 +1,9 @@ import Database from 'better-sqlite3'; import { randomUUID } from 'crypto'; -import { Storage, Session } from './types.ts'; +import { Storage, Offer } from './types.ts'; /** - * SQLite storage adapter for session management + * SQLite storage adapter for offer management * Supports both file-based and in-memory databases */ export class SQLiteStorage implements Storage { @@ -24,10 +24,9 @@ export class SQLiteStorage implements Storage { */ private initializeDatabase(): void { this.db.exec(` - CREATE TABLE IF NOT EXISTS sessions ( + CREATE TABLE IF NOT EXISTS offers ( code TEXT PRIMARY KEY, origin TEXT NOT NULL, - topic TEXT NOT NULL, peer_id TEXT NOT NULL CHECK(length(peer_id) <= 1024), offer TEXT NOT NULL, answer TEXT, @@ -37,14 +36,13 @@ export class SQLiteStorage implements Storage { expires_at INTEGER NOT NULL ); - CREATE INDEX IF NOT EXISTS idx_expires_at ON sessions(expires_at); - CREATE INDEX IF NOT EXISTS idx_origin_topic ON sessions(origin, topic); - CREATE INDEX IF NOT EXISTS idx_origin_topic_expires ON sessions(origin, topic, expires_at); + CREATE INDEX IF NOT EXISTS idx_offers_expires_at ON offers(expires_at); + CREATE INDEX IF NOT EXISTS idx_offers_origin ON offers(origin); `); } /** - * Starts periodic cleanup of expired sessions + * Starts periodic cleanup of expired offers */ private startCleanupInterval(): void { // Run cleanup every minute @@ -62,7 +60,7 @@ export class SQLiteStorage implements Storage { return randomUUID(); } - async createSession(origin: string, topic: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise { + async createOffer(origin: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise { // Validate peerId length if (peerId.length > 1024) { throw new Error('PeerId string must be 1024 characters or less'); @@ -78,22 +76,22 @@ export class SQLiteStorage implements Storage { attempts++; if (attempts > maxAttempts) { - throw new Error('Failed to generate unique session code'); + throw new Error('Failed to generate unique offer code'); } try { const stmt = this.db.prepare(` - INSERT INTO sessions (code, origin, topic, peer_id, offer, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO offers (code, origin, peer_id, offer, created_at, expires_at) + VALUES (?, ?, ?, ?, ?, ?) `); - stmt.run(code, origin, topic, peerId, offer, Date.now(), expiresAt); + stmt.run(code, origin, peerId, offer, Date.now(), expiresAt); break; } catch (err: any) { // If unique constraint failed with custom code, throw error if (err.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') { if (customCode) { - throw new Error(`Session code '${customCode}' already exists`); + throw new Error(`Offer code '${customCode}' already exists`); } // Try again with new generated code continue; @@ -105,82 +103,9 @@ export class SQLiteStorage implements Storage { return code; } - async listSessionsByTopic(origin: string, topic: string): Promise { + async getOffer(code: string, origin: string): Promise { const stmt = this.db.prepare(` - SELECT * FROM sessions - WHERE origin = ? AND topic = ? AND expires_at > ? AND answer IS NULL - ORDER BY created_at DESC - `); - - const rows = stmt.all(origin, topic, Date.now()) as any[]; - - return rows.map(row => ({ - code: row.code, - origin: row.origin, - topic: row.topic, - peerId: row.peer_id, - offer: row.offer, - answer: row.answer || undefined, - offerCandidates: JSON.parse(row.offer_candidates), - answerCandidates: JSON.parse(row.answer_candidates), - createdAt: row.created_at, - expiresAt: row.expires_at, - })); - } - - async listTopics(origin: string, page: number, limit: number): Promise<{ - topics: Array<{ topic: string; count: number }>; - pagination: { - page: number; - limit: number; - total: number; - hasMore: boolean; - }; - }> { - // Ensure limit doesn't exceed 1000 - const safeLimit = Math.min(Math.max(1, limit), 1000); - const safePage = Math.max(1, page); - const offset = (safePage - 1) * safeLimit; - - // Get total count of topics - const countStmt = this.db.prepare(` - SELECT COUNT(DISTINCT topic) as total - FROM sessions - WHERE origin = ? AND expires_at > ? AND answer IS NULL - `); - const { total } = countStmt.get(origin, Date.now()) as any; - - // Get paginated topics - const stmt = this.db.prepare(` - SELECT topic, COUNT(*) as count - FROM sessions - WHERE origin = ? AND expires_at > ? AND answer IS NULL - GROUP BY topic - ORDER BY topic ASC - LIMIT ? OFFSET ? - `); - - const rows = stmt.all(origin, Date.now(), safeLimit, offset) as any[]; - - const topics = rows.map(row => ({ - topic: row.topic, - count: row.count, - })); - - return { - topics, - pagination: { - page: safePage, - limit: safeLimit, - total, - hasMore: offset + topics.length < total, - }, - }; - } - - async getSession(code: string, origin: string): Promise { - const stmt = this.db.prepare(` - SELECT * FROM sessions WHERE code = ? AND origin = ? AND expires_at > ? + SELECT * FROM offers WHERE code = ? AND origin = ? AND expires_at > ? `); const row = stmt.get(code, origin, Date.now()) as any; @@ -192,7 +117,6 @@ export class SQLiteStorage implements Storage { return { code: row.code, origin: row.origin, - topic: row.topic, peerId: row.peer_id, offer: row.offer, answer: row.answer || undefined, @@ -203,11 +127,11 @@ export class SQLiteStorage implements Storage { }; } - async updateSession(code: string, origin: string, update: Partial): Promise { - const current = await this.getSession(code, origin); + async updateOffer(code: string, origin: string, update: Partial): Promise { + const current = await this.getOffer(code, origin); if (!current) { - throw new Error('Session not found or origin mismatch'); + throw new Error('Offer not found or origin mismatch'); } const updates: string[] = []; @@ -236,23 +160,23 @@ export class SQLiteStorage implements Storage { values.push(origin); const stmt = this.db.prepare(` - UPDATE sessions SET ${updates.join(', ')} WHERE code = ? AND origin = ? + UPDATE offers SET ${updates.join(', ')} WHERE code = ? AND origin = ? `); stmt.run(...values); } - async deleteSession(code: string): Promise { - const stmt = this.db.prepare('DELETE FROM sessions WHERE code = ?'); + async deleteOffer(code: string): Promise { + const stmt = this.db.prepare('DELETE FROM offers WHERE code = ?'); stmt.run(code); } async cleanup(): Promise { - const stmt = this.db.prepare('DELETE FROM sessions WHERE expires_at <= ?'); + const stmt = this.db.prepare('DELETE FROM offers WHERE expires_at <= ?'); const result = stmt.run(Date.now()); if (result.changes > 0) { - console.log(`Cleaned up ${result.changes} expired session(s)`); + console.log(`Cleaned up ${result.changes} expired offer(s)`); } } diff --git a/src/storage/types.ts b/src/storage/types.ts index 1cb9ab3..5cf09ac 100644 --- a/src/storage/types.ts +++ b/src/storage/types.ts @@ -1,10 +1,9 @@ /** - * Represents a WebRTC signaling session + * Represents a WebRTC signaling offer */ -export interface Session { +export interface Offer { code: string; origin: string; - topic: string; peerId: string; offer: string; answer?: string; @@ -15,71 +14,45 @@ export interface Session { } /** - * Storage interface for session management - * Implementations can use different backends (SQLite, Redis, Memory, etc.) + * Storage interface for offer management + * Implementations can use different backends (SQLite, D1, Memory, etc.) */ export interface Storage { /** - * Creates a new session with the given offer + * Creates a new offer * @param origin The Origin header from the request - * @param topic The topic to post the offer to * @param peerId Peer identifier string (max 1024 chars) * @param offer The WebRTC SDP offer message - * @param expiresAt Unix timestamp when the session should expire + * @param expiresAt Unix timestamp when the offer should expire * @param customCode Optional custom code (if not provided, generates UUID) - * @returns The unique session code + * @returns The unique offer code */ - createSession(origin: string, topic: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise; + createOffer(origin: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise; /** - * Lists all unanswered sessions for a given origin and topic - * @param origin The Origin header from the request - * @param topic The topic to list offers for - * @returns Array of sessions that haven't been answered yet - */ - listSessionsByTopic(origin: string, topic: string): Promise; - - /** - * Lists all topics for a given origin with their session counts - * @param origin The Origin header from the request - * @param page Page number (starting from 1) - * @param limit Number of results per page (max 1000) - * @returns Object with topics array and pagination metadata - */ - listTopics(origin: string, page: number, limit: number): Promise<{ - topics: Array<{ topic: string; count: number }>; - pagination: { - page: number; - limit: number; - total: number; - hasMore: boolean; - }; - }>; - - /** - * Retrieves a session by its code - * @param code The session code + * Retrieves an offer by its code + * @param code The offer code * @param origin The Origin header from the request (for validation) - * @returns The session if found, null otherwise + * @returns The offer if found, null otherwise */ - getSession(code: string, origin: string): Promise; + getOffer(code: string, origin: string): Promise; /** - * Updates an existing session with new data - * @param code The session code + * Updates an existing offer with new data + * @param code The offer code * @param origin The Origin header from the request (for validation) - * @param update Partial session data to update + * @param update Partial offer data to update */ - updateSession(code: string, origin: string, update: Partial): Promise; + updateOffer(code: string, origin: string, update: Partial): Promise; /** - * Deletes a session - * @param code The session code + * Deletes an offer + * @param code The offer code */ - deleteSession(code: string): Promise; + deleteOffer(code: string): Promise; /** - * Removes expired sessions + * Removes expired offers * Should be called periodically to clean up old data */ cleanup(): Promise; diff --git a/src/worker.ts b/src/worker.ts index c280140..9189a25 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -6,7 +6,7 @@ import { D1Storage } from './storage/d1.ts'; */ export interface Env { DB: D1Database; - SESSION_TIMEOUT?: string; + OFFER_TIMEOUT?: string; CORS_ORIGINS?: string; VERSION?: string; } @@ -20,9 +20,9 @@ export default { const storage = new D1Storage(env.DB); // Parse configuration - const sessionTimeout = env.SESSION_TIMEOUT - ? parseInt(env.SESSION_TIMEOUT, 10) - : 300000; // 5 minutes default + const offerTimeout = env.OFFER_TIMEOUT + ? parseInt(env.OFFER_TIMEOUT, 10) + : 60000; // 1 minute default const corsOrigins = env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',').map(o => o.trim()) @@ -30,7 +30,7 @@ export default { // Create Hono app const app = createApp(storage, { - sessionTimeout, + offerTimeout, corsOrigins, version: env.VERSION || 'unknown', }); @@ -41,19 +41,19 @@ export default { /** * Scheduled handler for cron triggers - * Runs every 5 minutes to clean up expired sessions + * Runs every minute to clean up expired offers */ async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise { const storage = new D1Storage(env.DB); const now = Date.now(); try { - // Delete expired sessions using the storage method - const deletedCount = await storage.cleanupExpiredSessions(); + // Delete expired offers using the storage method + const deletedCount = await storage.cleanupExpiredOffers(); - console.log(`Cleaned up ${deletedCount} expired sessions at ${new Date(now).toISOString()}`); + console.log(`Cleaned up ${deletedCount} expired offers at ${new Date(now).toISOString()}`); } catch (error) { - console.error('Error cleaning up sessions:', error); + console.error('Error cleaning up offers:', error); } }, }; diff --git a/wrangler.toml b/wrangler.toml index ff833f9..5df1989 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -5,14 +5,14 @@ compatibility_date = "2024-01-01" # D1 Database binding [[d1_databases]] binding = "DB" -database_name = "rondevu-sessions" +database_name = "rondevu-offers" database_id = "b94e3f71-816d-455b-a89d-927fa49532d0" # Environment variables [vars] -SESSION_TIMEOUT = "60000" # 1 minute in milliseconds +OFFER_TIMEOUT = "60000" # 1 minute in milliseconds CORS_ORIGINS = "*" # Comma-separated list of allowed origins -VERSION = "unknown" # Set to git commit hash before deploying +VERSION = "0.0.1" # Semantic version # Build configuration [build]