diff --git a/migrations/0003_remove_origin.sql b/migrations/0003_remove_origin.sql new file mode 100644 index 0000000..4476060 --- /dev/null +++ b/migrations/0003_remove_origin.sql @@ -0,0 +1,29 @@ +-- Migration: Remove origin column from offers table +-- This simplifies offer lookup to only use offer codes +-- Origin-based bucketing is no longer needed + +-- Create new offers table without origin column +CREATE TABLE offers_new ( + code TEXT PRIMARY KEY, + 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 +); + +-- Copy data from old table +INSERT INTO offers_new (code, peer_id, offer, answer, offer_candidates, answer_candidates, created_at, expires_at) +SELECT code, peer_id, offer, answer, offer_candidates, answer_candidates, created_at, expires_at +FROM offers; + +-- Drop old table +DROP TABLE offers; + +-- Rename new table +ALTER TABLE offers_new RENAME TO offers; + +-- Recreate index +CREATE INDEX IF NOT EXISTS idx_offers_expires_at ON offers(expires_at); diff --git a/src/app.ts b/src/app.ts index 6eaeb88..fd8dfad 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,19 +9,6 @@ export interface AppConfig { version?: string; } -/** - * 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. - */ -function getOrigin(c: Context): string { - const globalHeader = c.req.header('X-Rondevu-Global'); - if (globalHeader === 'true') { - return 'https://ronde.vu'; - } - return c.req.header('Origin') || c.req.header('origin') || 'unknown'; -} - /** * Creates the Hono application with WebRTC signaling endpoints */ @@ -43,7 +30,7 @@ export function createApp(storage: Storage, config: AppConfig) { return config.corsOrigins[0]; }, allowMethods: ['GET', 'POST', 'OPTIONS'], - allowHeaders: ['Content-Type', 'Origin', 'X-Rondevu-Global'], + allowHeaders: ['Content-Type', 'Origin'], exposeHeaders: ['Content-Type'], maxAge: 600, credentials: true, @@ -78,7 +65,6 @@ export function createApp(storage: Storage, config: AppConfig) { */ app.post('/offer', async (c) => { try { - const origin = getOrigin(c); const body = await c.req.json(); const { peerId, offer, code: customCode } = body; @@ -95,7 +81,7 @@ export function createApp(storage: Storage, config: AppConfig) { } const expiresAt = Date.now() + config.offerTimeout; - const code = await storage.createOffer(origin, peerId, offer, expiresAt, customCode); + const code = await storage.createOffer(peerId, offer, expiresAt, customCode); return c.json({ code }, 200); } catch (err) { @@ -117,7 +103,6 @@ export function createApp(storage: Storage, config: AppConfig) { */ app.post('/answer', async (c) => { try { - const origin = getOrigin(c); const body = await c.req.json(); const { code, answer, candidate, side } = body; @@ -137,23 +122,23 @@ export function createApp(storage: Storage, config: AppConfig) { return c.json({ error: 'Cannot provide both answer and candidate' }, 400); } - const offer = await storage.getOffer(code, origin); + const offer = await storage.getOffer(code); if (!offer) { - return c.json({ error: 'Offer not found, expired, or origin mismatch' }, 404); + return c.json({ error: 'Offer not found or expired' }, 404); } if (answer) { - await storage.updateOffer(code, origin, { answer }); + await storage.updateOffer(code, { answer }); } if (candidate) { if (side === 'offerer') { const updatedCandidates = [...offer.offerCandidates, candidate]; - await storage.updateOffer(code, origin, { offerCandidates: updatedCandidates }); + await storage.updateOffer(code, { offerCandidates: updatedCandidates }); } else { const updatedCandidates = [...offer.answerCandidates, candidate]; - await storage.updateOffer(code, origin, { answerCandidates: updatedCandidates }); + await storage.updateOffer(code, { answerCandidates: updatedCandidates }); } } @@ -171,7 +156,6 @@ export function createApp(storage: Storage, config: AppConfig) { */ app.post('/poll', async (c) => { try { - const origin = getOrigin(c); const body = await c.req.json(); const { code, side } = body; @@ -183,10 +167,10 @@ export function createApp(storage: Storage, config: AppConfig) { return c.json({ error: 'Invalid or missing parameter: side (must be "offerer" or "answerer")' }, 400); } - const offer = await storage.getOffer(code, origin); + const offer = await storage.getOffer(code); if (!offer) { - return c.json({ error: 'Offer not found, expired, or origin mismatch' }, 404); + return c.json({ error: 'Offer not found or expired' }, 404); } if (side === 'offerer') { diff --git a/src/storage/d1.ts b/src/storage/d1.ts index 063d204..40aa81e 100644 --- a/src/storage/d1.ts +++ b/src/storage/d1.ts @@ -27,7 +27,6 @@ export class D1Storage implements Storage { await this.db.exec(` CREATE TABLE IF NOT EXISTS 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, @@ -38,12 +37,10 @@ export class D1Storage implements Storage { ); 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 createOffer( - origin: string, peerId: string, offer: string, expiresAt: number, @@ -64,9 +61,9 @@ export class D1Storage implements Storage { try { await this.db.prepare(` - INSERT INTO offers (code, origin, peer_id, offer, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?) - `).bind(code, origin, peerId, offer, Date.now(), expiresAt).run(); + INSERT INTO offers (code, peer_id, offer, created_at, expires_at) + VALUES (?, ?, ?, ?, ?) + `).bind(code, peerId, offer, Date.now(), expiresAt).run(); break; } catch (err: any) { @@ -85,12 +82,12 @@ export class D1Storage implements Storage { return code; } - async getOffer(code: string, origin: string): Promise { + async getOffer(code: string): Promise { try { const result = await this.db.prepare(` SELECT * FROM offers - WHERE code = ? AND origin = ? AND expires_at > ? - `).bind(code, origin, Date.now()).first(); + WHERE code = ? AND expires_at > ? + `).bind(code, Date.now()).first(); if (!result) { return null; @@ -100,7 +97,6 @@ export class D1Storage implements Storage { return { code: row.code, - origin: row.origin, peerId: row.peer_id, offer: row.offer, answer: row.answer || undefined, @@ -115,12 +111,12 @@ export class D1Storage implements Storage { } } - async updateOffer(code: string, origin: string, update: Partial): Promise { - // Verify offer exists and origin matches - const current = await this.getOffer(code, origin); + async updateOffer(code: string, update: Partial): Promise { + // Verify offer exists + const current = await this.getOffer(code); if (!current) { - throw new Error('Offer not found or origin mismatch'); + throw new Error('Offer not found'); } // Build update query dynamically based on what fields are being updated @@ -147,13 +143,13 @@ export class D1Storage implements Storage { } // Add WHERE clause values - values.push(code, origin); + values.push(code); // D1 provides strong consistency, so this update is atomic and immediately visible const query = ` UPDATE offers SET ${updates.join(', ')} - WHERE code = ? AND origin = ? + WHERE code = ? `; await this.db.prepare(query).bind(...values).run(); diff --git a/src/storage/sqlite.ts b/src/storage/sqlite.ts index 6629ad6..39f06b7 100644 --- a/src/storage/sqlite.ts +++ b/src/storage/sqlite.ts @@ -26,7 +26,6 @@ export class SQLiteStorage implements Storage { this.db.exec(` CREATE TABLE IF NOT EXISTS 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, @@ -37,7 +36,6 @@ export class SQLiteStorage implements Storage { ); CREATE INDEX IF NOT EXISTS idx_offers_expires_at ON offers(expires_at); - CREATE INDEX IF NOT EXISTS idx_offers_origin ON offers(origin); `); } @@ -60,7 +58,7 @@ export class SQLiteStorage implements Storage { return randomUUID(); } - async createOffer(origin: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise { + async createOffer(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'); @@ -81,11 +79,11 @@ export class SQLiteStorage implements Storage { try { const stmt = this.db.prepare(` - INSERT INTO offers (code, origin, peer_id, offer, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO offers (code, peer_id, offer, created_at, expires_at) + VALUES (?, ?, ?, ?, ?) `); - stmt.run(code, origin, peerId, offer, Date.now(), expiresAt); + stmt.run(code, peerId, offer, Date.now(), expiresAt); break; } catch (err: any) { // If unique constraint failed with custom code, throw error @@ -103,12 +101,12 @@ export class SQLiteStorage implements Storage { return code; } - async getOffer(code: string, origin: string): Promise { + async getOffer(code: string): Promise { const stmt = this.db.prepare(` - SELECT * FROM offers WHERE code = ? AND origin = ? AND expires_at > ? + SELECT * FROM offers WHERE code = ? AND expires_at > ? `); - const row = stmt.get(code, origin, Date.now()) as any; + const row = stmt.get(code, Date.now()) as any; if (!row) { return null; @@ -116,7 +114,6 @@ export class SQLiteStorage implements Storage { return { code: row.code, - origin: row.origin, peerId: row.peer_id, offer: row.offer, answer: row.answer || undefined, @@ -127,11 +124,11 @@ export class SQLiteStorage implements Storage { }; } - async updateOffer(code: string, origin: string, update: Partial): Promise { - const current = await this.getOffer(code, origin); + async updateOffer(code: string, update: Partial): Promise { + const current = await this.getOffer(code); if (!current) { - throw new Error('Offer not found or origin mismatch'); + throw new Error('Offer not found'); } const updates: string[] = []; @@ -157,10 +154,9 @@ export class SQLiteStorage implements Storage { } values.push(code); - values.push(origin); const stmt = this.db.prepare(` - UPDATE offers SET ${updates.join(', ')} WHERE code = ? AND origin = ? + UPDATE offers SET ${updates.join(', ')} WHERE code = ? `); stmt.run(...values); diff --git a/src/storage/types.ts b/src/storage/types.ts index 5cf09ac..34d02d7 100644 --- a/src/storage/types.ts +++ b/src/storage/types.ts @@ -3,7 +3,6 @@ */ export interface Offer { code: string; - origin: string; peerId: string; offer: string; answer?: string; @@ -20,30 +19,27 @@ export interface Offer { export interface Storage { /** * Creates a new offer - * @param origin The Origin header from the request * @param peerId Peer identifier string (max 1024 chars) * @param offer The WebRTC SDP offer message * @param expiresAt Unix timestamp when the offer should expire * @param customCode Optional custom code (if not provided, generates UUID) * @returns The unique offer code */ - createOffer(origin: string, peerId: string, offer: string, expiresAt: number, customCode?: string): Promise; + createOffer(peerId: string, offer: string, expiresAt: number, customCode?: string): Promise; /** * Retrieves an offer by its code * @param code The offer code - * @param origin The Origin header from the request (for validation) * @returns The offer if found, null otherwise */ - getOffer(code: string, origin: string): Promise; + getOffer(code: string): Promise; /** * 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 offer data to update */ - updateOffer(code: string, origin: string, update: Partial): Promise; + updateOffer(code: string, update: Partial): Promise; /** * Deletes an offer