@xtr-dev/rondevu-server 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3018,7 +3018,7 @@ function createApp(storage, config) {
3018
3018
  }
3019
3019
 
3020
3020
  // src/config.ts
3021
- var BUILD_VERSION = true ? "0.5.10" : "unknown";
3021
+ var BUILD_VERSION = true ? "0.5.11" : "unknown";
3022
3022
  function loadConfig() {
3023
3023
  let masterEncryptionKey = process.env.MASTER_ENCRYPTION_KEY;
3024
3024
  if (!masterEncryptionKey) {
@@ -3073,8 +3073,8 @@ function loadConfig() {
3073
3073
  // Min 1 second
3074
3074
  masterEncryptionKey,
3075
3075
  // Resource limits
3076
- maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, "20", "MAX_OFFERS_PER_USER", 1),
3077
- maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, "10000", "MAX_TOTAL_OFFERS", 1),
3076
+ maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, "1000", "MAX_OFFERS_PER_USER", 1),
3077
+ maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, "100000", "MAX_TOTAL_OFFERS", 1),
3078
3078
  maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, "50000", "MAX_TOTAL_CREDENTIALS", 1),
3079
3079
  maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, "50", "MAX_ICE_CANDIDATES_PER_OFFER", 1),
3080
3080
  credentialsPerIpPerSecond: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_SECOND, "5", "CREDENTIALS_PER_IP_PER_SECOND", 1),
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/crypto.ts", "../src/storage/hash-id.ts", "../src/storage/memory.ts", "../src/storage/sqlite.ts", "../src/storage/mysql.ts", "../src/storage/postgres.ts", "../src/index.ts", "../src/app.ts", "../src/rpc.ts", "../src/config.ts", "../src/storage/factory.ts"],
4
- "sourcesContent": ["/**\n * Crypto utilities for credential generation and validation\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n */\n\nimport { Buffer } from 'node:buffer';\n\n// Username validation\n// Rules: 4-32 chars, lowercase alphanumeric + dashes + periods, must start/end with alphanumeric\nconst USERNAME_REGEX = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;\nconst USERNAME_MIN_LENGTH = 4;\nconst USERNAME_MAX_LENGTH = 32;\n\n/**\n * Generates a random credential name\n * Format: {adjective}-{noun}-{random}\n * Example: \"brave-tiger-7a3f2b1c9d8e\", \"quick-river-9b2e4c1a5f3d\"\n */\nexport function generateCredentialName(): string {\n const adjectives = [\n 'brave', 'calm', 'eager', 'fancy', 'gentle', 'happy', 'jolly', 'kind',\n 'lively', 'merry', 'nice', 'proud', 'quiet', 'swift', 'witty', 'young',\n 'bright', 'clever', 'daring', 'fair', 'grand', 'humble', 'noble', 'quick'\n ];\n\n const nouns = [\n 'tiger', 'eagle', 'river', 'mountain', 'ocean', 'forest', 'desert', 'valley',\n 'thunder', 'wind', 'fire', 'stone', 'cloud', 'star', 'moon', 'sun',\n 'wolf', 'bear', 'hawk', 'lion', 'fox', 'deer', 'owl', 'swan'\n ];\n\n const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];\n const noun = nouns[Math.floor(Math.random() * nouns.length)];\n\n // Generate 16-character hex suffix for uniqueness (8 bytes = 2^64 combinations)\n // With 576 adjective-noun pairs, total space: 576 \u00D7 2^64 \u2248 1.06 \u00D7 10^22 names\n // Birthday paradox collision at ~4.3 billion credentials (extremely safe for large deployments)\n // Increased from 6 bytes to 8 bytes for maximum collision resistance\n const random = crypto.getRandomValues(new Uint8Array(8));\n const hex = Array.from(random).map(b => b.toString(16).padStart(2, '0')).join('');\n\n return `${adjective}-${noun}-${hex}`;\n}\n\n/**\n * Generates a random secret (API key style)\n * Format: 64-character hex string (256 bits of entropy)\n * 256 bits provides optimal security for HMAC-SHA256 and future-proofs against brute force\n */\nexport function generateSecret(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32)); // 32 bytes = 256 bits\n const secret = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n // Validation: Ensure output is exactly 64 characters and valid hex\n if (secret.length !== 64) {\n throw new Error('Secret generation failed: invalid length');\n }\n\n // Validate all characters are valid hex digits (0-9, a-f)\n for (let i = 0; i < secret.length; i++) {\n const c = secret[i];\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error(`Secret generation failed: invalid hex character at position ${i}: '${c}'`);\n }\n }\n\n return secret;\n}\n\n// ===== Secret Encryption/Decryption (Database Storage) =====\n\n/**\n * Convert hex string to byte array with validation\n * @param hex Hex string (must be even length)\n * @returns Uint8Array of bytes\n */\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) {\n throw new Error('Hex string must have even length');\n }\n\n // Pre-validate that all characters are valid hex digits (0-9, a-f, A-F)\n // This prevents parseInt from silently truncating invalid input like \"0z\" -> 0\n for (let i = 0; i < hex.length; i++) {\n const c = hex[i].toLowerCase();\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error(`Invalid hex character at position ${i}: '${hex[i]}'`);\n }\n }\n\n const match = hex.match(/.{1,2}/g);\n if (!match) {\n throw new Error('Invalid hex string format');\n }\n\n return new Uint8Array(match.map(byte => {\n const parsed = parseInt(byte, 16);\n if (isNaN(parsed)) {\n throw new Error(`Invalid hex byte: ${byte}`);\n }\n return parsed;\n }));\n}\n\n/**\n * Encrypt a secret using AES-256-GCM with master key\n * Format: iv:ciphertext (all hex-encoded, auth tag included in ciphertext)\n *\n * @param secret The plaintext secret to encrypt\n * @param masterKeyHex The master encryption key (64-char hex = 32 bytes)\n * @returns Encrypted secret in format \"iv:ciphertext\"\n */\nexport async function encryptSecret(secret: string, masterKeyHex: string): Promise<string> {\n // Validate master key\n if (!masterKeyHex || masterKeyHex.length !== 64) {\n throw new Error('Master key must be 64-character hex string (32 bytes)');\n }\n\n // Convert master key from hex to bytes (with validation)\n const keyBytes = hexToBytes(masterKeyHex);\n\n // Import master key\n const key = await crypto.subtle.importKey(\n 'raw',\n keyBytes,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt']\n );\n\n // Generate random IV (12 bytes for AES-GCM)\n const iv = crypto.getRandomValues(new Uint8Array(12));\n\n // Encrypt secret\n const encoder = new TextEncoder();\n const secretBytes = encoder.encode(secret);\n\n // AES-GCM returns ciphertext with auth tag already appended (no manual splitting needed)\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv, tagLength: 128 },\n key,\n secretBytes\n );\n\n // Convert to hex: iv:ciphertext (ciphertext includes 16-byte auth tag at end)\n const ivHex = Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('');\n const ciphertextHex = Array.from(new Uint8Array(ciphertext))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n return `${ivHex}:${ciphertextHex}`;\n}\n\n/**\n * Decrypt a secret using AES-256-GCM with master key\n *\n * @param encryptedSecret Encrypted secret in format \"iv:ciphertext\" (ciphertext includes auth tag)\n * @param masterKeyHex The master encryption key (64-char hex = 32 bytes)\n * @returns Decrypted plaintext secret\n */\nexport async function decryptSecret(encryptedSecret: string, masterKeyHex: string): Promise<string> {\n // Validate master key\n if (!masterKeyHex || masterKeyHex.length !== 64) {\n throw new Error('Master key must be 64-character hex string (32 bytes)');\n }\n\n // Parse encrypted format: iv:ciphertext\n const parts = encryptedSecret.split(':');\n if (parts.length !== 2) {\n throw new Error('Invalid encrypted secret format (expected iv:ciphertext)');\n }\n\n const [ivHex, ciphertextHex] = parts;\n\n // Validate IV length (must be 12 bytes = 24 hex characters for AES-GCM)\n if (ivHex.length !== 24) {\n throw new Error('Invalid IV length (expected 12 bytes = 24 hex characters)');\n }\n\n // Validate ciphertext length (must include at least 16-byte auth tag)\n // Minimum: 16 bytes for auth tag = 32 hex characters\n if (ciphertextHex.length < 32) {\n throw new Error('Invalid ciphertext length (must include 16-byte auth tag)');\n }\n\n // Convert from hex to bytes (with validation)\n const iv = hexToBytes(ivHex);\n const ciphertext = hexToBytes(ciphertextHex);\n\n // Convert master key from hex to bytes (with validation)\n const keyBytes = hexToBytes(masterKeyHex);\n\n // Import master key\n const key = await crypto.subtle.importKey(\n 'raw',\n keyBytes,\n { name: 'AES-GCM', length: 256 },\n false,\n ['decrypt']\n );\n\n // Decrypt (ciphertext already includes 16-byte auth tag at end)\n const decryptedBytes = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv, tagLength: 128 },\n key,\n ciphertext\n );\n\n // Convert to string\n const decoder = new TextDecoder();\n return decoder.decode(decryptedBytes);\n}\n\n// ===== HMAC Signature Generation and Verification =====\n\n/**\n * Generate HMAC-SHA256 signature for request authentication\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n *\n * @param secret The credential secret (hex string)\n * @param message The message to sign (typically: timestamp + method + params)\n * @returns Promise<string> Base64-encoded signature\n */\nexport async function generateSignature(secret: string, message: string): Promise<string> {\n // Convert secret from hex to bytes (with validation)\n const secretBytes = hexToBytes(secret);\n\n // Import secret as HMAC key\n const key = await crypto.subtle.importKey(\n 'raw',\n secretBytes,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n\n // Convert message to bytes\n const encoder = new TextEncoder();\n const messageBytes = encoder.encode(message);\n\n // Generate HMAC signature\n const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);\n\n // Convert to base64\n return Buffer.from(signatureBytes).toString('base64');\n}\n\n/**\n * Verify HMAC-SHA256 signature for request authentication\n * Uses crypto.subtle.verify() for constant-time comparison\n *\n * @param secret The credential secret (hex string)\n * @param message The message that was signed\n * @param signature The signature to verify (base64)\n * @returns Promise<boolean> True if signature is valid\n */\nexport async function verifySignature(secret: string, message: string, signature: string): Promise<boolean> {\n try {\n // Convert secret from hex to bytes (with validation)\n const secretBytes = hexToBytes(secret);\n\n // Import secret as HMAC key for verification\n const key = await crypto.subtle.importKey(\n 'raw',\n secretBytes,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify']\n );\n\n // Convert message to bytes\n const encoder = new TextEncoder();\n const messageBytes = encoder.encode(message);\n\n // Convert signature from base64 to bytes\n const signatureBytes = Buffer.from(signature, 'base64');\n\n // Use Web Crypto API's verify() for constant-time comparison\n // This is cryptographically secure and resistant to timing attacks\n return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);\n } catch (error) {\n // Log error for debugging (helps identify implementation bugs)\n console.error('Signature verification error:', error);\n return false;\n }\n}\n\n/**\n * Canonical JSON serialization with sorted keys\n * Ensures deterministic output regardless of property insertion order\n * Must match client's canonicalJSON implementation exactly\n */\nfunction canonicalJSON(obj: any, depth: number = 0): string {\n const MAX_DEPTH = 100;\n\n if (depth > MAX_DEPTH) {\n throw new Error('Object nesting too deep for canonicalization');\n }\n\n if (obj === null) return 'null';\n if (obj === undefined) return JSON.stringify(undefined);\n\n const type = typeof obj;\n\n if (type === 'function') throw new Error('Functions are not supported in RPC parameters');\n if (type === 'symbol' || type === 'bigint') throw new Error(`${type} is not supported in RPC parameters`);\n if (type === 'number' && !Number.isFinite(obj)) throw new Error('NaN and Infinity are not supported in RPC parameters');\n\n if (type !== 'object') return JSON.stringify(obj);\n\n if (Array.isArray(obj)) {\n return '[' + obj.map(item => canonicalJSON(item, depth + 1)).join(',') + ']';\n }\n\n const sortedKeys = Object.keys(obj).sort();\n const pairs = sortedKeys.map(key => JSON.stringify(key) + ':' + canonicalJSON(obj[key], depth + 1));\n return '{' + pairs.join(',') + '}';\n}\n\n/**\n * Build the message string for signing\n * Format: timestamp:nonce:method:canonicalJSON(params || {})\n * Uses colons as delimiters to prevent collision attacks\n * Includes nonce to prevent signature reuse within timestamp window\n * Uses canonical JSON (sorted keys) for deterministic serialization\n *\n * @param timestamp Unix timestamp in milliseconds\n * @param nonce Cryptographic nonce (UUID v4) to prevent replay attacks\n * @param method RPC method name\n * @param params RPC method parameters (optional)\n * @returns String to be signed\n */\nexport function buildSignatureMessage(timestamp: number, nonce: string, method: string, params?: any): string {\n // Validate nonce is UUID v4 format to prevent colon injection attacks\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (8-4-4-4-12 hex digits with dashes)\n // Use simple format checks instead of regex to avoid any timing or ReDoS concerns\n\n // Check total length (36 characters for UUID v4)\n if (nonce.length !== 36) {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check dash positions (indices 8, 13, 18, 23)\n if (nonce[8] !== '-' || nonce[13] !== '-' || nonce[18] !== '-' || nonce[23] !== '-') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check version (character at index 14 must be '4')\n if (nonce[14] !== '4') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check variant (character at index 19 must be 8, 9, a, or b)\n const variant = nonce[19].toLowerCase();\n if (variant !== '8' && variant !== '9' && variant !== 'a' && variant !== 'b') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Validate all other characters are hex digits (0-9, a-f)\n const hexChars = nonce.replace(/-/g, ''); // Remove dashes\n for (let i = 0; i < hexChars.length; i++) {\n const c = hexChars[i].toLowerCase();\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n }\n\n // Use canonical JSON (sorted keys) to match client's signature\n const paramsStr = canonicalJSON(params || {});\n // Use delimiters to prevent collision: timestamp=12,method=\"34\" vs timestamp=1,method=\"234\"\n // Include nonce to make each request unique (prevents signature reuse in same millisecond)\n return `${timestamp}:${nonce}:${method}:${paramsStr}`;\n}\n\n// ===== Username Validation =====\n\n/**\n * Validates username format\n * Rules: 4-32 chars, lowercase alphanumeric + dashes + periods, must start/end with alphanumeric\n */\nexport function validateUsername(username: string): { valid: boolean; error?: string } {\n if (typeof username !== 'string') {\n return { valid: false, error: 'Username must be a string' };\n }\n\n if (username.length < USERNAME_MIN_LENGTH) {\n return { valid: false, error: `Username must be at least ${USERNAME_MIN_LENGTH} characters` };\n }\n\n if (username.length > USERNAME_MAX_LENGTH) {\n return { valid: false, error: `Username must be at most ${USERNAME_MAX_LENGTH} characters` };\n }\n\n if (!USERNAME_REGEX.test(username)) {\n return { valid: false, error: 'Username must be lowercase alphanumeric with optional dashes/periods, and start/end with alphanumeric' };\n }\n\n return { valid: true };\n}\n\n// ===== Tag Validation =====\n\n// Tag validation constants\nconst TAG_MIN_LENGTH = 1;\nconst TAG_MAX_LENGTH = 64;\nconst TAG_REGEX = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;\n\n/**\n * Validates a single tag format\n * Rules: 1-64 chars, lowercase alphanumeric with optional dots/dashes\n * Must start and end with alphanumeric character\n *\n * Valid examples: \"chat\", \"video-call\", \"com.example.service\", \"v2\"\n * Invalid examples: \"\", \"UPPERCASE\", \"-starts-dash\", \"ends-dash-\"\n */\nexport function validateTag(tag: string): { valid: boolean; error?: string } {\n if (typeof tag !== 'string') {\n return { valid: false, error: 'Tag must be a string' };\n }\n\n if (tag.length < TAG_MIN_LENGTH) {\n return { valid: false, error: `Tag must be at least ${TAG_MIN_LENGTH} character` };\n }\n\n if (tag.length > TAG_MAX_LENGTH) {\n return { valid: false, error: `Tag must be at most ${TAG_MAX_LENGTH} characters` };\n }\n\n // Single character tags just need to be alphanumeric\n if (tag.length === 1) {\n if (!/^[a-z0-9]$/.test(tag)) {\n return { valid: false, error: 'Tag must be lowercase alphanumeric' };\n }\n return { valid: true };\n }\n\n // Multi-character tags must match the pattern\n if (!TAG_REGEX.test(tag)) {\n return { valid: false, error: 'Tag must be lowercase alphanumeric with optional dots/dashes, and start/end with alphanumeric' };\n }\n\n return { valid: true };\n}\n\n/**\n * Validates an array of tags\n * @param tags Array of tags to validate\n * @param maxTags Maximum number of tags allowed (default: 20)\n */\nexport function validateTags(tags: string[], maxTags: number = 20): { valid: boolean; error?: string } {\n if (!Array.isArray(tags)) {\n return { valid: false, error: 'Tags must be an array' };\n }\n\n if (tags.length === 0) {\n return { valid: false, error: 'At least one tag is required' };\n }\n\n if (tags.length > maxTags) {\n return { valid: false, error: `Maximum ${maxTags} tags allowed` };\n }\n\n // Validate each tag\n for (let i = 0; i < tags.length; i++) {\n const result = validateTag(tags[i]);\n if (!result.valid) {\n return { valid: false, error: `Tag ${i + 1}: ${result.error}` };\n }\n }\n\n // Check for duplicates\n const uniqueTags = new Set(tags);\n if (uniqueTags.size !== tags.length) {\n return { valid: false, error: 'Duplicate tags are not allowed' };\n }\n\n return { valid: true };\n}\n", "/**\n * Generates a unique offer ID using SHA-256 hash\n * Combines SDP content with timestamp and random bytes for uniqueness\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n *\n * @param sdp - The WebRTC SDP offer\n * @returns Unique SHA-256 hash ID\n */\nexport async function generateOfferHash(sdp: string): Promise<string> {\n // Generate random bytes for uniqueness (8 bytes = 64 bits of randomness)\n const randomBytes = crypto.getRandomValues(new Uint8Array(8));\n const randomHex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n // Include SDP, timestamp, and random bytes for uniqueness\n const hashInput = {\n sdp,\n timestamp: Date.now(),\n nonce: randomHex\n };\n\n // Create non-prettified JSON string\n const jsonString = JSON.stringify(hashInput);\n\n // Convert string to Uint8Array for hashing\n const encoder = new TextEncoder();\n const data = encoder.encode(jsonString);\n\n // Generate SHA-256 hash\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\n // Convert hash to hex string\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n\n return hashHex;\n}\n", "import {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\ninterface RateLimit {\n count: number;\n resetTime: number;\n}\n\ninterface NonceEntry {\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter for rondevu signaling system\n * Data is not persisted - all data is lost on server restart\n * Best for development, testing, or ephemeral deployments\n */\nexport class MemoryStorage implements Storage {\n private masterEncryptionKey: string;\n\n // Primary storage\n private credentials = new Map<string, Credential>();\n private offers = new Map<string, Offer>();\n private iceCandidates = new Map<string, IceCandidate[]>(); // offerId \u2192 candidates\n private rateLimits = new Map<string, RateLimit>();\n private nonces = new Map<string, NonceEntry>();\n\n // Secondary indexes for efficient lookups\n private offersByUsername = new Map<string, Set<string>>(); // username \u2192 offer IDs\n private offersByTag = new Map<string, Set<string>>(); // tag \u2192 offer IDs\n private offersByAnswerer = new Map<string, Set<string>>(); // answerer username \u2192 offer IDs\n\n // Auto-increment counter for ICE candidates\n private iceCandidateIdCounter = 0;\n\n constructor(masterEncryptionKey: string) {\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n const created: Offer[] = [];\n const now = Date.now();\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n const offer: Offer = {\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n };\n\n // Store offer\n this.offers.set(id, offer);\n\n // Update username index\n if (!this.offersByUsername.has(request.username)) {\n this.offersByUsername.set(request.username, new Set());\n }\n this.offersByUsername.get(request.username)!.add(id);\n\n // Update tag indexes\n for (const tag of request.tags) {\n if (!this.offersByTag.has(tag)) {\n this.offersByTag.set(tag, new Set());\n }\n this.offersByTag.get(tag)!.add(id);\n }\n\n created.push(offer);\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByUsername.get(username);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => b.lastSeen - a.lastSeen);\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const offer = this.offers.get(offerId);\n if (!offer || offer.expiresAt <= Date.now()) {\n return null;\n }\n return offer;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const offer = this.offers.get(offerId);\n if (!offer || offer.username !== ownerUsername) {\n return false;\n }\n\n this.removeOfferFromIndexes(offer);\n this.offers.delete(offerId);\n this.iceCandidates.delete(offerId);\n\n return true;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n let count = 0;\n\n for (const [id, offer] of this.offers) {\n if (offer.expiresAt < now) {\n this.removeOfferFromIndexes(offer);\n this.offers.delete(id);\n this.iceCandidates.delete(id);\n count++;\n }\n }\n\n return count;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n // Update offer with answer\n const now = Date.now();\n offer.answererUsername = answererUsername;\n offer.answerSdp = answerSdp;\n offer.answeredAt = now;\n\n // Update answerer index\n if (!this.offersByAnswerer.has(answererUsername)) {\n this.offersByAnswerer.set(answererUsername, new Set());\n }\n this.offersByAnswerer.get(answererUsername)!.add(offerId);\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByUsername.get(offererUsername);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.answererUsername && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByAnswerer.get(answererUsername);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n const now = Date.now();\n const matchingOfferIds = new Set<string>();\n\n // Find all offers matching any tag (OR logic)\n for (const tag of tags) {\n const offerIds = this.offersByTag.get(tag);\n if (offerIds) {\n for (const id of offerIds) {\n matchingOfferIds.add(id);\n }\n }\n }\n\n // Filter and collect matching offers\n const offers: Offer[] = [];\n for (const id of matchingOfferIds) {\n const offer = this.offers.get(id);\n if (\n offer &&\n offer.expiresAt > now &&\n !offer.answererUsername &&\n (!excludeUsername || offer.username !== excludeUsername)\n ) {\n offers.push(offer);\n }\n }\n\n // Sort by created_at descending and apply pagination\n offers.sort((a, b) => b.createdAt - a.createdAt);\n return offers.slice(offset, offset + limit);\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n const now = Date.now();\n const matchingOffers: Offer[] = [];\n\n // Find all offers matching any tag (OR logic)\n const matchingOfferIds = new Set<string>();\n for (const tag of tags) {\n const offerIds = this.offersByTag.get(tag);\n if (offerIds) {\n for (const id of offerIds) {\n matchingOfferIds.add(id);\n }\n }\n }\n\n // Collect matching offers\n for (const id of matchingOfferIds) {\n const offer = this.offers.get(id);\n if (\n offer &&\n offer.expiresAt > now &&\n !offer.answererUsername &&\n (!excludeUsername || offer.username !== excludeUsername)\n ) {\n matchingOffers.push(offer);\n }\n }\n\n if (matchingOffers.length === 0) return null;\n\n // Return random offer\n const randomIndex = Math.floor(Math.random() * matchingOffers.length);\n return matchingOffers[randomIndex];\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n const baseTimestamp = Date.now();\n\n if (!this.iceCandidates.has(offerId)) {\n this.iceCandidates.set(offerId, []);\n }\n\n const candidateList = this.iceCandidates.get(offerId)!;\n\n for (let i = 0; i < candidates.length; i++) {\n const candidate: IceCandidate = {\n id: ++this.iceCandidateIdCounter,\n offerId,\n username,\n role,\n candidate: candidates[i],\n createdAt: baseTimestamp + i,\n };\n candidateList.push(candidate);\n }\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n const candidates = this.iceCandidates.get(offerId) || [];\n\n return candidates\n .filter(c => c.role === targetRole && (since === undefined || c.createdAt > since))\n .sort((a, b) => a.createdAt - b.createdAt);\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return result;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n for (const offerId of offerIds) {\n const offer = this.offers.get(offerId);\n if (!offer) continue;\n\n const candidates = this.iceCandidates.get(offerId) || [];\n\n // Determine which role's candidates to return\n // If user is offerer, return answerer candidates and vice versa\n const isOfferer = offer.username === username;\n const isAnswerer = offer.answererUsername === username;\n\n if (!isOfferer && !isAnswerer) continue;\n\n const targetRole = isOfferer ? 'answerer' : 'offerer';\n\n const filteredCandidates = candidates\n .filter(c => c.role === targetRole && (since === undefined || c.createdAt > since))\n .sort((a, b) => a.createdAt - b.createdAt);\n\n if (filteredCandidates.length > 0) {\n result.set(offerId, filteredCandidates);\n }\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n if (this.credentials.has(request.name)) {\n throw new Error('Username already taken');\n }\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n if (!this.credentials.has(name)) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n\n // Encrypt secret before storing\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n const credential: Credential = {\n name: name!,\n secret: encryptedSecret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n\n this.credentials.set(name!, credential);\n\n // Return plaintext secret to user\n return {\n ...credential,\n secret, // Return plaintext, not encrypted\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const credential = this.credentials.get(name);\n if (!credential || credential.expiresAt <= Date.now()) {\n return null;\n }\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(credential.secret, this.masterEncryptionKey);\n\n return {\n ...credential,\n secret: decryptedSecret,\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n const credential = this.credentials.get(name);\n if (credential) {\n credential.lastUsed = lastUsed;\n credential.expiresAt = expiresAt;\n }\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n let count = 0;\n for (const [name, credential] of this.credentials) {\n if (credential.expiresAt < now) {\n this.credentials.delete(name);\n count++;\n }\n }\n return count;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const existing = this.rateLimits.get(identifier);\n\n if (!existing || existing.resetTime < now) {\n // New window or expired - reset count\n this.rateLimits.set(identifier, {\n count: 1,\n resetTime: now + windowMs,\n });\n return true;\n }\n\n // Increment count in existing window\n existing.count++;\n return existing.count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n let count = 0;\n for (const [identifier, rateLimit] of this.rateLimits) {\n if (rateLimit.resetTime < now) {\n this.rateLimits.delete(identifier);\n count++;\n }\n }\n return count;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n if (this.nonces.has(nonceKey)) {\n return false; // Nonce already used - replay attack\n }\n\n this.nonces.set(nonceKey, { expiresAt });\n return true; // Nonce is new - allowed\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n let count = 0;\n for (const [key, entry] of this.nonces) {\n if (entry.expiresAt < now) {\n this.nonces.delete(key);\n count++;\n }\n }\n return count;\n }\n\n async close(): Promise<void> {\n // Clear all data\n this.credentials.clear();\n this.offers.clear();\n this.iceCandidates.clear();\n this.rateLimits.clear();\n this.nonces.clear();\n this.offersByUsername.clear();\n this.offersByTag.clear();\n this.offersByAnswerer.clear();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n return this.offers.size;\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const offerIds = this.offersByUsername.get(username);\n return offerIds ? offerIds.size : 0;\n }\n\n async getCredentialCount(): Promise<number> {\n return this.credentials.size;\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const candidates = this.iceCandidates.get(offerId);\n return candidates ? candidates.length : 0;\n }\n\n // ===== Helper Methods =====\n\n private removeOfferFromIndexes(offer: Offer): void {\n // Remove from username index\n const usernameOffers = this.offersByUsername.get(offer.username);\n if (usernameOffers) {\n usernameOffers.delete(offer.id);\n if (usernameOffers.size === 0) {\n this.offersByUsername.delete(offer.username);\n }\n }\n\n // Remove from tag indexes\n for (const tag of offer.tags) {\n const tagOffers = this.offersByTag.get(tag);\n if (tagOffers) {\n tagOffers.delete(offer.id);\n if (tagOffers.size === 0) {\n this.offersByTag.delete(tag);\n }\n }\n }\n\n // Remove from answerer index\n if (offer.answererUsername) {\n const answererOffers = this.offersByAnswerer.get(offer.answererUsername);\n if (answererOffers) {\n answererOffers.delete(offer.id);\n if (answererOffers.size === 0) {\n this.offersByAnswerer.delete(offer.answererUsername);\n }\n }\n }\n }\n}\n", "import Database from 'better-sqlite3';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000; // 365 days\n\n/**\n * SQLite storage adapter for rondevu signaling system\n * Supports both file-based and in-memory databases\n */\nexport class SQLiteStorage implements Storage {\n private db: Database.Database;\n private masterEncryptionKey: string;\n\n /**\n * Creates a new SQLite storage instance\n * @param path Path to SQLite database file, or ':memory:' for in-memory database\n * @param masterEncryptionKey 64-char hex string for encrypting secrets (32 bytes)\n */\n constructor(path: string = ':memory:', masterEncryptionKey: string) {\n this.db = new Database(path);\n this.masterEncryptionKey = masterEncryptionKey;\n this.initializeDatabase();\n }\n\n /**\n * Initializes database schema with tags-based offers\n */\n private initializeDatabase(): void {\n this.db.exec(`\n -- WebRTC signaling offers with tags\n CREATE TABLE IF NOT EXISTS offers (\n id TEXT PRIMARY KEY,\n username TEXT NOT NULL,\n tags TEXT NOT NULL,\n sdp TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n answerer_username TEXT,\n answer_sdp TEXT,\n answered_at INTEGER\n );\n\n CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username);\n CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);\n CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);\n CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username);\n\n -- ICE candidates table\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n offer_id TEXT NOT NULL,\n username TEXT NOT NULL,\n role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),\n candidate TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE\n );\n\n CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id);\n CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username);\n CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at);\n\n -- Credentials table (replaces usernames with simpler name + secret auth)\n CREATE TABLE IF NOT EXISTS credentials (\n name TEXT PRIMARY KEY,\n secret TEXT NOT NULL UNIQUE,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n last_used INTEGER NOT NULL,\n CHECK(length(name) >= 3 AND length(name) <= 32)\n );\n\n CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at);\n CREATE INDEX IF NOT EXISTS idx_credentials_secret ON credentials(secret);\n\n -- Rate limits table (for distributed rate limiting)\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier TEXT PRIMARY KEY,\n count INTEGER NOT NULL,\n reset_time INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_rate_limits_reset ON rate_limits(reset_time);\n\n -- Nonces table (for replay attack prevention)\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key TEXT PRIMARY KEY,\n expires_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_nonces_expires ON nonces(expires_at);\n `);\n\n // Enable foreign keys\n this.db.pragma('foreign_keys = ON');\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n const created: Offer[] = [];\n\n // Generate hash-based IDs for all offers first\n const offersWithIds = await Promise.all(\n offers.map(async (offer) => ({\n ...offer,\n id: offer.id || await generateOfferHash(offer.sdp),\n }))\n );\n\n // Use transaction for atomic creation\n const transaction = this.db.transaction((offersWithIds: (CreateOfferRequest & { id: string })[]) => {\n const offerStmt = this.db.prepare(`\n INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n for (const offer of offersWithIds) {\n const now = Date.now();\n\n // Insert offer with JSON-serialized tags\n offerStmt.run(\n offer.id,\n offer.username,\n JSON.stringify(offer.tags),\n offer.sdp,\n now,\n offer.expiresAt,\n now\n );\n\n created.push({\n id: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: now,\n expiresAt: offer.expiresAt,\n lastSeen: now,\n });\n }\n });\n\n transaction(offersWithIds);\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE username = ? AND expires_at > ?\n ORDER BY last_seen DESC\n `);\n\n const rows = stmt.all(username, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE id = ? AND expires_at > ?\n `);\n\n const row = stmt.get(offerId, Date.now()) as any;\n\n if (!row) {\n return null;\n }\n\n return this.rowToOffer(row);\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const stmt = this.db.prepare(`\n DELETE FROM offers\n WHERE id = ? AND username = ?\n `);\n\n const result = stmt.run(offerId, ownerUsername);\n return result.changes > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM offers WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n // Check if offer exists and is not expired\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return {\n success: false,\n error: 'Offer not found or expired'\n };\n }\n\n // Check if offer already has an answerer\n if (offer.answererUsername) {\n return {\n success: false,\n error: 'Offer already answered'\n };\n }\n\n // Update offer with answer\n const stmt = this.db.prepare(`\n UPDATE offers\n SET answerer_username = ?, answer_sdp = ?, answered_at = ?\n WHERE id = ? AND answerer_username IS NULL\n `);\n\n const result = stmt.run(answererUsername, answerSdp, Date.now(), offerId);\n\n if (result.changes === 0) {\n return {\n success: false,\n error: 'Offer already answered (race condition)'\n };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?\n ORDER BY answered_at DESC\n `);\n\n const rows = stmt.all(offererUsername, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE answerer_username = ? AND expires_at > ?\n ORDER BY answered_at DESC\n `);\n\n const rows = stmt.all(answererUsername, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) {\n return [];\n }\n\n // Build query with JSON tag matching (OR logic)\n // SQLite: Use json_each() to expand tags array and check if any tag matches\n const placeholders = tags.map(() => '?').join(',');\n\n let query = `\n SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t\n WHERE t.value IN (${placeholders})\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n\n const params: any[] = [...tags, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';\n params.push(limit, offset);\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) {\n return null;\n }\n\n // Build query with JSON tag matching (OR logic)\n const placeholders = tags.map(() => '?').join(',');\n\n let query = `\n SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t\n WHERE t.value IN (${placeholders})\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n\n const params: any[] = [...tags, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RANDOM() LIMIT 1';\n\n const stmt = this.db.prepare(query);\n const row = stmt.get(...params) as any;\n\n return row ? this.rowToOffer(row) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n const stmt = this.db.prepare(`\n INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n const baseTimestamp = Date.now();\n const transaction = this.db.transaction((candidates: any[]) => {\n for (let i = 0; i < candidates.length; i++) {\n stmt.run(\n offerId,\n username,\n role,\n JSON.stringify(candidates[i]),\n baseTimestamp + i\n );\n }\n });\n\n transaction(candidates);\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `\n SELECT * FROM ice_candidates\n WHERE offer_id = ? AND role = ?\n `;\n\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n\n return rows.map(row => ({\n id: row.id,\n offerId: row.offer_id,\n username: row.username,\n role: row.role,\n candidate: JSON.parse(row.candidate),\n createdAt: row.created_at,\n }));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n // Return empty map if no offer IDs provided\n if (offerIds.length === 0) {\n return result;\n }\n\n // Validate array contains only strings\n if (!Array.isArray(offerIds) || !offerIds.every(id => typeof id === 'string')) {\n throw new Error('Invalid offer IDs: must be array of strings');\n }\n\n // Prevent DoS attacks from extremely large IN clauses\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n // Build query that fetches candidates from the OTHER peer only\n // For each offer, determine if user is offerer or answerer and get opposite role\n const placeholders = offerIds.map(() => '?').join(',');\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id IN (${placeholders})\n AND (\n (o.username = ? AND ic.role = 'answerer')\n OR (o.answerer_username = ? AND ic.role = 'offerer')\n )\n `;\n\n const params: any[] = [...offerIds, username, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n\n // Group candidates by offer_id\n for (const row of rows) {\n const candidate: IceCandidate = {\n id: row.id,\n offerId: row.offer_id,\n username: row.username,\n role: row.role,\n candidate: JSON.parse(row.candidate),\n createdAt: row.created_at,\n };\n\n if (!result.has(row.offer_id)) {\n result.set(row.offer_id, []);\n }\n result.get(row.offer_id)!.push(candidate);\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n // User requested specific username - check if available\n const existing = this.db.prepare(`\n SELECT name FROM credentials WHERE name = ?\n `).get(request.name);\n\n if (existing) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n // Generate random name - retry until unique\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const existing = this.db.prepare(`\n SELECT name FROM credentials WHERE name = ?\n `).get(name);\n\n if (!existing) {\n break;\n }\n\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n\n // Encrypt secret before storing (AES-256-GCM)\n const { encryptSecret } = await import('../crypto.ts');\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n // Insert credential with encrypted secret\n const stmt = this.db.prepare(`\n INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n stmt.run(name!, encryptedSecret, now, expiresAt, now);\n\n // Return plaintext secret to user (only time they'll see it)\n return {\n name: name!,\n secret, // Return plaintext secret, not encrypted\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const stmt = this.db.prepare(`\n SELECT * FROM credentials\n WHERE name = ? AND expires_at > ?\n `);\n\n const row = stmt.get(name, Date.now()) as any;\n\n if (!row) {\n return null;\n }\n\n // Decrypt secret before returning\n // If decryption fails (e.g., master key rotated), treat as credential not found\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(row.secret, this.masterEncryptionKey);\n\n return {\n name: row.name,\n secret: decryptedSecret, // Return decrypted secret\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n lastUsed: row.last_used,\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null; // Treat as credential not found (fail-safe behavior)\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n const stmt = this.db.prepare(`\n UPDATE credentials\n SET last_used = ?, expires_at = ?\n WHERE name = ?\n `);\n\n stmt.run(lastUsed, expiresAt, name);\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM credentials WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Atomic UPSERT: Insert or increment count, reset if expired\n // This prevents TOCTOU race conditions by doing check+increment in single operation\n const result = this.db.prepare(`\n INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES (?, 1, ?)\n ON CONFLICT(identifier) DO UPDATE SET\n count = CASE\n WHEN reset_time < ? THEN 1\n ELSE count + 1\n END,\n reset_time = CASE\n WHEN reset_time < ? THEN ?\n ELSE reset_time\n END\n RETURNING count\n `).get(identifier, resetTime, now, now, resetTime) as { count: number };\n\n // Check if limit exceeded\n return result.count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM rate_limits WHERE reset_time < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n // Atomic INSERT - if nonce already exists, this will fail with UNIQUE constraint\n // This prevents replay attacks by ensuring each nonce is only used once\n const stmt = this.db.prepare(`\n INSERT INTO nonces (nonce_key, expires_at)\n VALUES (?, ?)\n `);\n stmt.run(nonceKey, expiresAt);\n return true; // Nonce is new, request allowed\n } catch (error: any) {\n // SQLITE_CONSTRAINT error code for UNIQUE constraint violation\n if (error?.code === 'SQLITE_CONSTRAINT') {\n return false; // Nonce already used, replay attack detected\n }\n throw error; // Other errors should propagate\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM nonces WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM offers').get() as { count: number };\n return result.count;\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM offers WHERE username = ?').get(username) as { count: number };\n return result.count;\n }\n\n async getCredentialCount(): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM credentials').get() as { count: number };\n return result.count;\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?').get(offerId) as { count: number };\n return result.count;\n }\n\n // ===== Helper Methods =====\n\n /**\n * Helper method to convert database row to Offer object\n */\n private rowToOffer(row: any): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: JSON.parse(row.tags),\n sdp: row.sdp,\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n lastSeen: row.last_seen,\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at || undefined,\n };\n }\n}\n", "import mysql, { Pool, PoolConnection, RowDataPacket, ResultSetHeader } from 'mysql2/promise';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\n/**\n * MySQL storage adapter for rondevu signaling system\n * Uses connection pooling for efficient resource management\n */\nexport class MySQLStorage implements Storage {\n private pool: Pool;\n private masterEncryptionKey: string;\n\n private constructor(pool: Pool, masterEncryptionKey: string) {\n this.pool = pool;\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n /**\n * Creates a new MySQL storage instance with connection pooling\n * @param connectionString MySQL connection URL\n * @param masterEncryptionKey 64-char hex string for encrypting secrets\n * @param poolSize Maximum number of connections in the pool\n */\n static async create(\n connectionString: string,\n masterEncryptionKey: string,\n poolSize: number = 10\n ): Promise<MySQLStorage> {\n const pool = mysql.createPool({\n uri: connectionString,\n waitForConnections: true,\n connectionLimit: poolSize,\n queueLimit: 0,\n enableKeepAlive: true,\n keepAliveInitialDelay: 10000,\n });\n\n const storage = new MySQLStorage(pool, masterEncryptionKey);\n await storage.initializeDatabase();\n return storage;\n }\n\n private async initializeDatabase(): Promise<void> {\n const conn = await this.pool.getConnection();\n try {\n await conn.query(`\n CREATE TABLE IF NOT EXISTS offers (\n id VARCHAR(64) PRIMARY KEY,\n username VARCHAR(32) NOT NULL,\n tags JSON NOT NULL,\n sdp MEDIUMTEXT NOT NULL,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_seen BIGINT NOT NULL,\n answerer_username VARCHAR(32),\n answer_sdp MEDIUMTEXT,\n answered_at BIGINT,\n INDEX idx_offers_username (username),\n INDEX idx_offers_expires (expires_at),\n INDEX idx_offers_last_seen (last_seen),\n INDEX idx_offers_answerer (answerer_username)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id BIGINT AUTO_INCREMENT PRIMARY KEY,\n offer_id VARCHAR(64) NOT NULL,\n username VARCHAR(32) NOT NULL,\n role ENUM('offerer', 'answerer') NOT NULL,\n candidate JSON NOT NULL,\n created_at BIGINT NOT NULL,\n INDEX idx_ice_offer (offer_id),\n INDEX idx_ice_username (username),\n INDEX idx_ice_created (created_at),\n FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS credentials (\n name VARCHAR(32) PRIMARY KEY,\n secret VARCHAR(512) NOT NULL UNIQUE,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_used BIGINT NOT NULL,\n INDEX idx_credentials_expires (expires_at)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier VARCHAR(255) PRIMARY KEY,\n count INT NOT NULL,\n reset_time BIGINT NOT NULL,\n INDEX idx_rate_limits_reset (reset_time)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key VARCHAR(255) PRIMARY KEY,\n expires_at BIGINT NOT NULL,\n INDEX idx_nonces_expires (expires_at)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n } finally {\n conn.release();\n }\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n if (offers.length === 0) return [];\n\n const created: Offer[] = [];\n const now = Date.now();\n\n const conn = await this.pool.getConnection();\n try {\n await conn.beginTransaction();\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n await conn.query(\n `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]\n );\n\n created.push({\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n });\n }\n\n await conn.commit();\n } catch (error) {\n await conn.rollback();\n throw error;\n } finally {\n conn.release();\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers WHERE username = ? AND expires_at > ? ORDER BY last_seen DESC`,\n [username, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers WHERE id = ? AND expires_at > ?`,\n [offerId, Date.now()]\n );\n return rows.length > 0 ? this.rowToOffer(rows[0]) : null;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM offers WHERE id = ? AND username = ?`,\n [offerId, ownerUsername]\n );\n return result.affectedRows > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM offers WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n const [result] = await this.pool.query<ResultSetHeader>(\n `UPDATE offers SET answerer_username = ?, answer_sdp = ?, answered_at = ?\n WHERE id = ? AND answerer_username IS NULL`,\n [answererUsername, answerSdp, Date.now(), offerId]\n );\n\n if (result.affectedRows === 0) {\n return { success: false, error: 'Offer already answered (race condition)' };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers\n WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?\n ORDER BY answered_at DESC`,\n [offererUsername, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers\n WHERE answerer_username = ? AND expires_at > ?\n ORDER BY answered_at DESC`,\n [answererUsername, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n // Use JSON_OVERLAPS for efficient tag matching (MySQL 8.0.17+)\n // Falls back to JSON_CONTAINS for each tag with OR logic\n const tagArray = JSON.stringify(tags);\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE JSON_OVERLAPS(o.tags, ?)\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tagArray, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';\n params.push(limit, offset);\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n const tagArray = JSON.stringify(tags);\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE JSON_OVERLAPS(o.tags, ?)\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tagArray, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RAND() LIMIT 1';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.length > 0 ? this.rowToOffer(rows[0]) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n if (candidates.length === 0) return 0;\n\n const baseTimestamp = Date.now();\n const values = candidates.map((c, i) => [\n offerId,\n username,\n role,\n JSON.stringify(c),\n baseTimestamp + i,\n ]);\n\n await this.pool.query(\n `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES ?`,\n [values]\n );\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `SELECT * FROM ice_candidates WHERE offer_id = ? AND role = ?`;\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.map(row => this.rowToIceCandidate(row));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return result;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n const placeholders = offerIds.map(() => '?').join(',');\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id IN (${placeholders})\n AND (\n (o.username = ? AND ic.role = 'answerer')\n OR (o.answerer_username = ? AND ic.role = 'offerer')\n )\n `;\n const params: any[] = [...offerIds, username, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n\n for (const row of rows) {\n const candidate = this.rowToIceCandidate(row);\n if (!result.has(row.offer_id)) {\n result.set(row.offer_id, []);\n }\n result.get(row.offer_id)!.push(candidate);\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n const [existing] = await this.pool.query<RowDataPacket[]>(\n `SELECT name FROM credentials WHERE name = ?`,\n [request.name]\n );\n\n if (existing.length > 0) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const [existing] = await this.pool.query<RowDataPacket[]>(\n `SELECT name FROM credentials WHERE name = ?`,\n [name]\n );\n\n if (existing.length === 0) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n await this.pool.query(\n `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES (?, ?, ?, ?, ?)`,\n [name!, encryptedSecret, now, expiresAt, now]\n );\n\n return {\n name: name!,\n secret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM credentials WHERE name = ? AND expires_at > ?`,\n [name, Date.now()]\n );\n\n if (rows.length === 0) return null;\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(rows[0].secret, this.masterEncryptionKey);\n\n return {\n name: rows[0].name,\n secret: decryptedSecret,\n createdAt: Number(rows[0].created_at),\n expiresAt: Number(rows[0].expires_at),\n lastUsed: Number(rows[0].last_used),\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n await this.pool.query(\n `UPDATE credentials SET last_used = ?, expires_at = ? WHERE name = ?`,\n [lastUsed, expiresAt, name]\n );\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM credentials WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Use INSERT ... ON DUPLICATE KEY UPDATE for atomic upsert\n await this.pool.query(\n `INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES (?, 1, ?)\n ON DUPLICATE KEY UPDATE\n count = IF(reset_time < ?, 1, count + 1),\n reset_time = IF(reset_time < ?, ?, reset_time)`,\n [identifier, resetTime, now, now, resetTime]\n );\n\n // Get current count\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT count FROM rate_limits WHERE identifier = ?`,\n [identifier]\n );\n\n return rows.length > 0 && rows[0].count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM rate_limits WHERE reset_time < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n await this.pool.query(\n `INSERT INTO nonces (nonce_key, expires_at) VALUES (?, ?)`,\n [nonceKey, expiresAt]\n );\n return true;\n } catch (error: any) {\n // MySQL duplicate key error code\n if (error.code === 'ER_DUP_ENTRY') {\n return false;\n }\n throw error;\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM nonces WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM offers');\n return Number(rows[0].count);\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n 'SELECT COUNT(*) as count FROM offers WHERE username = ?',\n [username]\n );\n return Number(rows[0].count);\n }\n\n async getCredentialCount(): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM credentials');\n return Number(rows[0].count);\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n 'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?',\n [offerId]\n );\n return Number(rows[0].count);\n }\n\n // ===== Helper Methods =====\n\n private rowToOffer(row: RowDataPacket): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags,\n sdp: row.sdp,\n createdAt: Number(row.created_at),\n expiresAt: Number(row.expires_at),\n lastSeen: Number(row.last_seen),\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at ? Number(row.answered_at) : undefined,\n };\n }\n\n private rowToIceCandidate(row: RowDataPacket): IceCandidate {\n return {\n id: Number(row.id),\n offerId: row.offer_id,\n username: row.username,\n role: row.role as 'offerer' | 'answerer',\n candidate: typeof row.candidate === 'string' ? JSON.parse(row.candidate) : row.candidate,\n createdAt: Number(row.created_at),\n };\n }\n}\n", "import { Pool, QueryResult } from 'pg';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\n/**\n * PostgreSQL storage adapter for rondevu signaling system\n * Uses connection pooling for efficient resource management\n */\nexport class PostgreSQLStorage implements Storage {\n private pool: Pool;\n private masterEncryptionKey: string;\n\n private constructor(pool: Pool, masterEncryptionKey: string) {\n this.pool = pool;\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n /**\n * Creates a new PostgreSQL storage instance with connection pooling\n * @param connectionString PostgreSQL connection URL\n * @param masterEncryptionKey 64-char hex string for encrypting secrets\n * @param poolSize Maximum number of connections in the pool\n */\n static async create(\n connectionString: string,\n masterEncryptionKey: string,\n poolSize: number = 10\n ): Promise<PostgreSQLStorage> {\n const pool = new Pool({\n connectionString,\n max: poolSize,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: 5000,\n });\n\n const storage = new PostgreSQLStorage(pool, masterEncryptionKey);\n await storage.initializeDatabase();\n return storage;\n }\n\n private async initializeDatabase(): Promise<void> {\n const client = await this.pool.connect();\n try {\n await client.query(`\n CREATE TABLE IF NOT EXISTS offers (\n id VARCHAR(64) PRIMARY KEY,\n username VARCHAR(32) NOT NULL,\n tags JSONB NOT NULL,\n sdp TEXT NOT NULL,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_seen BIGINT NOT NULL,\n answerer_username VARCHAR(32),\n answer_sdp TEXT,\n answered_at BIGINT\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_tags ON offers USING GIN(tags)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id BIGSERIAL PRIMARY KEY,\n offer_id VARCHAR(64) NOT NULL REFERENCES offers(id) ON DELETE CASCADE,\n username VARCHAR(32) NOT NULL,\n role VARCHAR(8) NOT NULL CHECK (role IN ('offerer', 'answerer')),\n candidate JSONB NOT NULL,\n created_at BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS credentials (\n name VARCHAR(32) PRIMARY KEY,\n secret VARCHAR(512) NOT NULL UNIQUE,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_used BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier VARCHAR(255) PRIMARY KEY,\n count INT NOT NULL,\n reset_time BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_rate_limits_reset ON rate_limits(reset_time)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key VARCHAR(255) PRIMARY KEY,\n expires_at BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_nonces_expires ON nonces(expires_at)`);\n } finally {\n client.release();\n }\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n if (offers.length === 0) return [];\n\n const created: Offer[] = [];\n const now = Date.now();\n\n const client = await this.pool.connect();\n try {\n await client.query('BEGIN');\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n await client.query(\n `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]\n );\n\n created.push({\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n });\n }\n\n await client.query('COMMIT');\n } catch (error) {\n await client.query('ROLLBACK');\n throw error;\n } finally {\n client.release();\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers WHERE username = $1 AND expires_at > $2 ORDER BY last_seen DESC`,\n [username, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const result = await this.pool.query(\n `SELECT * FROM offers WHERE id = $1 AND expires_at > $2`,\n [offerId, Date.now()]\n );\n return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const result = await this.pool.query(\n `DELETE FROM offers WHERE id = $1 AND username = $2`,\n [offerId, ownerUsername]\n );\n return (result.rowCount ?? 0) > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM offers WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n const result = await this.pool.query(\n `UPDATE offers SET answerer_username = $1, answer_sdp = $2, answered_at = $3\n WHERE id = $4 AND answerer_username IS NULL`,\n [answererUsername, answerSdp, Date.now(), offerId]\n );\n\n if ((result.rowCount ?? 0) === 0) {\n return { success: false, error: 'Offer already answered (race condition)' };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers\n WHERE username = $1 AND answerer_username IS NOT NULL AND expires_at > $2\n ORDER BY answered_at DESC`,\n [offererUsername, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers\n WHERE answerer_username = $1 AND expires_at > $2\n ORDER BY answered_at DESC`,\n [answererUsername, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n // Use PostgreSQL's ?| operator for JSONB array overlap\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE o.tags ?| $1\n AND o.expires_at > $2\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tags, Date.now()];\n let paramIndex = 3;\n\n if (excludeUsername) {\n query += ` AND o.username != $${paramIndex}`;\n params.push(excludeUsername);\n paramIndex++;\n }\n\n query += ` ORDER BY o.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;\n params.push(limit, offset);\n\n const result = await this.pool.query(query, params);\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE o.tags ?| $1\n AND o.expires_at > $2\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tags, Date.now()];\n let paramIndex = 3;\n\n if (excludeUsername) {\n query += ` AND o.username != $${paramIndex}`;\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RANDOM() LIMIT 1';\n\n const result = await this.pool.query(query, params);\n return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n if (candidates.length === 0) return 0;\n\n const baseTimestamp = Date.now();\n const client = await this.pool.connect();\n\n try {\n await client.query('BEGIN');\n\n for (let i = 0; i < candidates.length; i++) {\n await client.query(\n `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES ($1, $2, $3, $4, $5)`,\n [offerId, username, role, JSON.stringify(candidates[i]), baseTimestamp + i]\n );\n }\n\n await client.query('COMMIT');\n } catch (error) {\n await client.query('ROLLBACK');\n throw error;\n } finally {\n client.release();\n }\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `SELECT * FROM ice_candidates WHERE offer_id = $1 AND role = $2`;\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > $3';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const result = await this.pool.query(query, params);\n return result.rows.map(row => this.rowToIceCandidate(row));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const resultMap = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return resultMap;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id = ANY($1)\n AND (\n (o.username = $2 AND ic.role = 'answerer')\n OR (o.answerer_username = $2 AND ic.role = 'offerer')\n )\n `;\n const params: any[] = [offerIds, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > $3';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const result = await this.pool.query(query, params);\n\n for (const row of result.rows) {\n const candidate = this.rowToIceCandidate(row);\n if (!resultMap.has(row.offer_id)) {\n resultMap.set(row.offer_id, []);\n }\n resultMap.get(row.offer_id)!.push(candidate);\n }\n\n return resultMap;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n const existing = await this.pool.query(\n `SELECT name FROM credentials WHERE name = $1`,\n [request.name]\n );\n\n if (existing.rows.length > 0) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const existing = await this.pool.query(\n `SELECT name FROM credentials WHERE name = $1`,\n [name]\n );\n\n if (existing.rows.length === 0) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n await this.pool.query(\n `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES ($1, $2, $3, $4, $5)`,\n [name!, encryptedSecret, now, expiresAt, now]\n );\n\n return {\n name: name!,\n secret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const result = await this.pool.query(\n `SELECT * FROM credentials WHERE name = $1 AND expires_at > $2`,\n [name, Date.now()]\n );\n\n if (result.rows.length === 0) return null;\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(result.rows[0].secret, this.masterEncryptionKey);\n\n return {\n name: result.rows[0].name,\n secret: decryptedSecret,\n createdAt: Number(result.rows[0].created_at),\n expiresAt: Number(result.rows[0].expires_at),\n lastUsed: Number(result.rows[0].last_used),\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n await this.pool.query(\n `UPDATE credentials SET last_used = $1, expires_at = $2 WHERE name = $3`,\n [lastUsed, expiresAt, name]\n );\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM credentials WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Use INSERT ... ON CONFLICT for atomic upsert\n const result = await this.pool.query(\n `INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES ($1, 1, $2)\n ON CONFLICT (identifier) DO UPDATE SET\n count = CASE\n WHEN rate_limits.reset_time < $3 THEN 1\n ELSE rate_limits.count + 1\n END,\n reset_time = CASE\n WHEN rate_limits.reset_time < $3 THEN $2\n ELSE rate_limits.reset_time\n END\n RETURNING count`,\n [identifier, resetTime, now]\n );\n\n return result.rows[0].count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM rate_limits WHERE reset_time < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n await this.pool.query(\n `INSERT INTO nonces (nonce_key, expires_at) VALUES ($1, $2)`,\n [nonceKey, expiresAt]\n );\n return true;\n } catch (error: any) {\n // PostgreSQL unique violation error code\n if (error.code === '23505') {\n return false;\n }\n throw error;\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM nonces WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const result = await this.pool.query('SELECT COUNT(*) as count FROM offers');\n return Number(result.rows[0].count);\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const result = await this.pool.query(\n 'SELECT COUNT(*) as count FROM offers WHERE username = $1',\n [username]\n );\n return Number(result.rows[0].count);\n }\n\n async getCredentialCount(): Promise<number> {\n const result = await this.pool.query('SELECT COUNT(*) as count FROM credentials');\n return Number(result.rows[0].count);\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const result = await this.pool.query(\n 'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = $1',\n [offerId]\n );\n return Number(result.rows[0].count);\n }\n\n // ===== Helper Methods =====\n\n private rowToOffer(row: any): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags,\n sdp: row.sdp,\n createdAt: Number(row.created_at),\n expiresAt: Number(row.expires_at),\n lastSeen: Number(row.last_seen),\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at ? Number(row.answered_at) : undefined,\n };\n }\n\n private rowToIceCandidate(row: any): IceCandidate {\n return {\n id: Number(row.id),\n offerId: row.offer_id,\n username: row.username,\n role: row.role as 'offerer' | 'answerer',\n candidate: typeof row.candidate === 'string' ? JSON.parse(row.candidate) : row.candidate,\n createdAt: Number(row.created_at),\n };\n }\n}\n", "import { serve } from '@hono/node-server';\nimport { createApp } from './app.ts';\nimport { loadConfig, runCleanup } from './config.ts';\nimport { createStorage } from './storage/factory.ts';\nimport { Storage } from './storage/types.ts';\n\nasync function main() {\n const config = loadConfig();\n\n console.log('Starting Rondevu server...');\n console.log('Configuration:', {\n port: config.port,\n storageType: config.storageType,\n storagePath: config.storageType === 'sqlite' ? config.storagePath : undefined,\n databaseUrl: config.databaseUrl ? '[configured]' : undefined,\n dbPoolSize: ['mysql', 'postgres'].includes(config.storageType) ? config.dbPoolSize : undefined,\n offerDefaultTtl: `${config.offerDefaultTtl}ms`,\n cleanupInterval: `${config.cleanupInterval}ms`,\n version: config.version,\n });\n\n const storage: Storage = await createStorage({\n type: config.storageType,\n masterEncryptionKey: config.masterEncryptionKey,\n sqlitePath: config.storagePath,\n connectionString: config.databaseUrl,\n poolSize: config.dbPoolSize,\n });\n console.log(`Using ${config.storageType} storage`);\n\n // Periodic cleanup\n const cleanupTimer = setInterval(async () => {\n try {\n const result = await runCleanup(storage, Date.now());\n const total = result.offers + result.credentials + result.rateLimits + result.nonces;\n if (total > 0) {\n console.log(`Cleanup: ${result.offers} offers, ${result.credentials} credentials, ${result.rateLimits} rate limits, ${result.nonces} nonces`);\n }\n } catch (err) {\n console.error('Cleanup error:', err);\n }\n }, config.cleanupInterval);\n\n const app = createApp(storage, config);\n\n const server = serve({\n fetch: app.fetch,\n port: config.port,\n });\n\n console.log(`Server running on http://localhost:${config.port}`);\n console.log('Ready to accept connections');\n\n // Graceful shutdown handler\n const shutdown = async () => {\n console.log('\\nShutting down gracefully...');\n clearInterval(cleanupTimer);\n await storage.close();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n", "import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Storage } from './storage/types.ts';\nimport { Config } from './config.ts';\nimport { handleRpc, RpcRequest } from './rpc.ts';\n\n/**\n * Creates the Hono application with RPC interface\n */\nexport function createApp(storage: Storage, config: Config) {\n const app = new Hono();\n\n // Enable CORS\n app.use('/*', cors({\n origin: (origin) => {\n if (config.corsOrigins.length === 1 && config.corsOrigins[0] === '*') {\n return origin;\n }\n if (config.corsOrigins.includes(origin)) {\n return origin;\n }\n return config.corsOrigins[0];\n },\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Origin', 'X-Name', 'X-Timestamp', 'X-Nonce', 'X-Signature'],\n exposeHeaders: ['Content-Type'],\n credentials: false,\n maxAge: 86400,\n }));\n\n // Root endpoint - server info\n app.get('/', (c) => {\n return c.json({\n version: config.version,\n name: 'Rondevu',\n description: 'WebRTC signaling with RPC interface and HMAC signature-based authentication',\n }, 200);\n });\n\n // Health check\n app.get('/health', (c) => {\n return c.json({\n status: 'ok',\n timestamp: Date.now(),\n version: config.version,\n }, 200);\n });\n\n /**\n * POST /rpc\n * RPC endpoint - accepts batch method calls only\n */\n app.post('/rpc', async (c) => {\n try {\n const body = await c.req.json();\n\n // Only accept batch arrays\n if (!Array.isArray(body)) {\n return c.json([{\n success: false,\n error: 'Request must be an array of RPC calls',\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n\n const requests: RpcRequest[] = body;\n\n // Validate requests\n if (requests.length === 0) {\n return c.json([{\n success: false,\n error: 'Empty request array',\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n\n if (requests.length > config.maxBatchSize) {\n return c.json([{\n success: false,\n error: `Too many requests in batch (max ${config.maxBatchSize})`,\n errorCode: 'BATCH_TOO_LARGE'\n }], 413); // 413 Payload Too Large\n }\n\n // Handle RPC (pass context for auth headers)\n const responses = await handleRpc(requests, c, storage, config);\n\n // Always return array\n return c.json(responses, 200);\n } catch (err) {\n console.error('RPC error:', err);\n\n // Distinguish between JSON parse errors and validation errors\n const errorMsg = err instanceof SyntaxError\n ? 'Invalid JSON in request body'\n : 'Request must be valid JSON array';\n\n return c.json([{\n success: false,\n error: errorMsg,\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n });\n\n // 404 for all other routes\n app.all('*', (c) => {\n return c.json({\n error: 'Not found. Use POST /rpc for all API calls.',\n }, 404);\n });\n\n return app;\n}\n", "import { Context } from 'hono';\nimport { Storage } from './storage/types.ts';\nimport { Config } from './config.ts';\nimport {\n validateTags,\n validateUsername,\n verifySignature,\n buildSignatureMessage,\n} from './crypto.ts';\n\n// Constants (non-configurable)\nconst MAX_PAGE_SIZE = 100;\n\n// NOTE: MAX_SDP_SIZE, MAX_CANDIDATE_SIZE, MAX_CANDIDATE_DEPTH, and MAX_CANDIDATES_PER_REQUEST\n// are now configurable via environment variables (see config.ts)\n\n// ===== Rate Limiting =====\n\n// Rate limiting windows (these are fixed, limits come from config)\n// NOTE: Uses fixed-window rate limiting with full window reset on expiry\n// - Window starts on first request and expires after window duration\n// - When window expires, counter resets to 0 and new window starts\n// - This is simpler than sliding windows but may allow bursts at window boundaries\nconst CREDENTIAL_RATE_WINDOW = 1000; // 1 second in milliseconds\nconst REQUEST_RATE_WINDOW = 1000; // 1 second in milliseconds\n\n/**\n * Check JSON object depth to prevent stack overflow from deeply nested objects\n * CRITICAL: Checks depth BEFORE recursing to prevent stack overflow\n * @param obj Object to check\n * @param maxDepth Maximum allowed depth\n * @param currentDepth Current recursion depth\n * @returns Actual depth of the object (returns maxDepth + 1 if exceeded)\n */\nfunction getJsonDepth(obj: any, maxDepth: number, currentDepth = 0): number {\n // Check for primitives/null first\n if (obj === null || typeof obj !== 'object') {\n return currentDepth;\n }\n\n // CRITICAL: Check depth BEFORE recursing to prevent stack overflow\n // If we're already at max depth, don't recurse further\n if (currentDepth >= maxDepth) {\n return currentDepth + 1; // Indicate exceeded\n }\n\n let maxChildDepth = currentDepth;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const childDepth = getJsonDepth(obj[key], maxDepth, currentDepth + 1);\n maxChildDepth = Math.max(maxChildDepth, childDepth);\n\n // Early exit if exceeded\n if (maxChildDepth > maxDepth) {\n return maxChildDepth;\n }\n }\n }\n\n return maxChildDepth;\n}\n\n/**\n * Validate parameter is a non-empty string\n * Prevents type coercion issues and injection attacks\n */\nfunction validateStringParam(value: any, paramName: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `${paramName} must be a non-empty string`);\n }\n}\n\n/**\n * Standard error codes for RPC responses\n */\nexport const ErrorCodes = {\n // Authentication errors\n AUTH_REQUIRED: 'AUTH_REQUIRED',\n INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',\n\n // Validation errors\n INVALID_NAME: 'INVALID_NAME',\n INVALID_TAG: 'INVALID_TAG',\n INVALID_SDP: 'INVALID_SDP',\n INVALID_PARAMS: 'INVALID_PARAMS',\n MISSING_PARAMS: 'MISSING_PARAMS',\n\n // Resource errors\n OFFER_NOT_FOUND: 'OFFER_NOT_FOUND',\n OFFER_ALREADY_ANSWERED: 'OFFER_ALREADY_ANSWERED',\n OFFER_NOT_ANSWERED: 'OFFER_NOT_ANSWERED',\n NO_AVAILABLE_OFFERS: 'NO_AVAILABLE_OFFERS',\n\n // Authorization errors\n NOT_AUTHORIZED: 'NOT_AUTHORIZED',\n OWNERSHIP_MISMATCH: 'OWNERSHIP_MISMATCH',\n\n // Limit errors\n TOO_MANY_OFFERS: 'TOO_MANY_OFFERS',\n SDP_TOO_LARGE: 'SDP_TOO_LARGE',\n BATCH_TOO_LARGE: 'BATCH_TOO_LARGE',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n TOO_MANY_OFFERS_PER_USER: 'TOO_MANY_OFFERS_PER_USER',\n STORAGE_FULL: 'STORAGE_FULL',\n TOO_MANY_ICE_CANDIDATES: 'TOO_MANY_ICE_CANDIDATES',\n\n // Generic errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n UNKNOWN_METHOD: 'UNKNOWN_METHOD',\n} as const;\n\n/**\n * Custom error class with error code support\n */\nexport class RpcError extends Error {\n constructor(\n public errorCode: string,\n message: string\n ) {\n super(message);\n this.name = 'RpcError';\n }\n}\n\n/**\n * RPC request format (body only - auth in headers)\n */\nexport interface RpcRequest {\n method: string;\n params?: any;\n clientIp?: string;\n}\n\n/**\n * RPC response format\n */\nexport interface RpcResponse {\n success: boolean;\n result?: any;\n error?: string;\n errorCode?: string;\n}\n\n/**\n * RPC Method Parameter Interfaces\n */\nexport interface GenerateCredentialsParams {\n name?: string; // Optional: claim specific username (4-32 chars, alphanumeric + dashes + periods)\n expiresAt?: number;\n}\n\nexport interface DiscoverParams {\n tags: string[];\n limit?: number;\n offset?: number;\n}\n\nexport interface PublishOfferParams {\n tags: string[];\n offers: Array<{ sdp: string }>;\n ttl?: number;\n}\n\nexport interface DeleteOfferParams {\n offerId: string;\n}\n\nexport interface AnswerOfferParams {\n offerId: string;\n sdp: string;\n}\n\nexport interface GetOfferAnswerParams {\n offerId: string;\n}\n\nexport interface PollParams {\n since?: number;\n}\n\nexport interface AddIceCandidatesParams {\n offerId: string;\n candidates: any[];\n}\n\nexport interface GetIceCandidatesParams {\n offerId: string;\n since?: number;\n}\n\n/**\n * RPC method handler\n * Generic type parameter allows individual handlers to specify their param types\n */\ntype RpcHandler<TParams = any> = (\n params: TParams,\n name: string,\n timestamp: number,\n signature: string,\n storage: Storage,\n config: Config,\n request: RpcRequest\n) => Promise<any>;\n\n/**\n * Validate timestamp for replay attack prevention\n * Throws RpcError if timestamp is invalid\n */\nfunction validateTimestamp(timestamp: number, config: Config): void {\n const now = Date.now();\n\n // Check if timestamp is too old (replay attack)\n if (now - timestamp > config.timestampMaxAge) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Timestamp too old');\n }\n\n // Check if timestamp is too far in future (clock skew)\n if (timestamp - now > config.timestampMaxFuture) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Timestamp too far in future');\n }\n}\n\n/**\n * Verify request signature for authentication\n * Throws RpcError on authentication failure\n */\nasync function verifyRequestSignature(\n name: string,\n timestamp: number,\n nonce: string,\n signature: string,\n method: string,\n params: any,\n storage: Storage,\n config: Config\n): Promise<void> {\n // Validate timestamp first\n validateTimestamp(timestamp, config);\n\n // Get credential to retrieve secret\n const credential = await storage.getCredential(name);\n if (!credential) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Invalid credentials');\n }\n\n // Build message and verify signature (includes nonce to prevent signature reuse)\n const message = buildSignatureMessage(timestamp, nonce, method, params);\n const isValid = await verifySignature(credential.secret, message, signature);\n\n if (!isValid) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Invalid signature');\n }\n\n // Check nonce uniqueness AFTER successful signature verification\n // This prevents DoS where invalid signatures burn nonces\n // Only valid authenticated requests can mark nonces as used\n const nonceKey = `nonce:${name}:${nonce}`;\n const nonceExpiresAt = timestamp + config.timestampMaxAge;\n const nonceIsNew = await storage.checkAndMarkNonce(nonceKey, nonceExpiresAt);\n\n if (!nonceIsNew) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Nonce already used (replay attack detected)');\n }\n\n // Update last used timestamp\n const now = Date.now();\n const credentialExpiresAt = now + (365 * 24 * 60 * 60 * 1000); // 1 year\n await storage.updateCredentialUsage(name, now, credentialExpiresAt);\n}\n\n/**\n * RPC Method Handlers\n */\n\nconst handlers: Record<string, RpcHandler> = {\n /**\n * Generate new credentials (name + secret pair)\n * No authentication required - this is how users get started\n * SECURITY: Rate limited per IP to prevent abuse (database-backed for multi-instance support)\n */\n async generateCredentials(params: GenerateCredentialsParams, name, timestamp, signature, storage, config, request: RpcRequest & { clientIp?: string }) {\n // Check total credentials limit\n const credentialCount = await storage.getCredentialCount();\n if (credentialCount >= config.maxTotalCredentials) {\n throw new RpcError(\n ErrorCodes.STORAGE_FULL,\n `Server credential limit reached (${config.maxTotalCredentials}). Try again later.`\n );\n }\n\n // Rate limiting check (IP-based, stored in database)\n // SECURITY: Use stricter global rate limit for requests without identifiable IP\n let rateLimitKey: string;\n let rateLimit: number;\n\n if (!request.clientIp) {\n // Warn about missing IP (suggests proxy misconfiguration)\n console.warn('\u26A0\uFE0F WARNING: Unable to determine client IP for credential generation. Using global rate limit.');\n // Use global rate limit with much stricter limit (prevents DoS while allowing basic function)\n rateLimitKey = 'cred_gen:global_unknown';\n rateLimit = 2; // Only 2 credentials per second globally for all unknown IPs combined\n } else {\n rateLimitKey = `cred_gen:${request.clientIp}`;\n rateLimit = config.credentialsPerIpPerSecond;\n }\n\n const allowed = await storage.checkRateLimit(\n rateLimitKey,\n rateLimit,\n CREDENTIAL_RATE_WINDOW\n );\n\n if (!allowed) {\n throw new RpcError(\n ErrorCodes.RATE_LIMIT_EXCEEDED,\n `Rate limit exceeded. Maximum ${rateLimit} credentials per second${request.clientIp ? ' per IP' : ' (global limit for unidentified IPs)'}.`\n );\n }\n\n // Validate username if provided\n if (params.name !== undefined) {\n if (typeof params.name !== 'string') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'name must be a string');\n }\n const usernameValidation = validateUsername(params.name);\n if (!usernameValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, usernameValidation.error || 'Invalid username');\n }\n }\n\n // Validate expiresAt if provided\n if (params.expiresAt !== undefined) {\n if (typeof params.expiresAt !== 'number' || isNaN(params.expiresAt) || !Number.isFinite(params.expiresAt)) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt must be a valid timestamp');\n }\n // Prevent setting expiry in the past (with 1 minute tolerance for clock skew)\n const now = Date.now();\n if (params.expiresAt < now - 60000) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt cannot be in the past');\n }\n // Prevent unreasonably far future expiry (max 10 years)\n const maxFuture = now + (10 * 365 * 24 * 60 * 60 * 1000);\n if (params.expiresAt > maxFuture) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt cannot be more than 10 years in the future');\n }\n }\n\n try {\n const credential = await storage.generateCredentials({\n name: params.name,\n expiresAt: params.expiresAt,\n });\n\n return {\n name: credential.name,\n secret: credential.secret,\n createdAt: credential.createdAt,\n expiresAt: credential.expiresAt,\n };\n } catch (error: any) {\n if (error.message === 'Username already taken') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Username already taken');\n }\n throw error;\n }\n },\n\n /**\n * Discover offers by tags - Supports 2 modes:\n * 1. Paginated discovery: tags array with limit/offset\n * 2. Random discovery: tags array without limit (returns single random offer)\n */\n async discover(params: DiscoverParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { tags, limit, offset } = params;\n\n // Validate tags\n const tagsValidation = validateTags(tags);\n if (!tagsValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_TAG, tagsValidation.error || 'Invalid tags');\n }\n\n // Mode 1: Paginated discovery\n if (limit !== undefined) {\n // Validate numeric parameters\n if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'limit must be a non-negative integer');\n }\n if (offset !== undefined && (typeof offset !== 'number' || !Number.isInteger(offset) || offset < 0)) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'offset must be a non-negative integer');\n }\n\n const pageLimit = Math.min(Math.max(1, limit), MAX_PAGE_SIZE);\n const pageOffset = Math.max(0, offset || 0);\n\n // Exclude self if authenticated\n const excludeUsername = name || null;\n\n const offers = await storage.discoverOffers(\n tags,\n excludeUsername,\n pageLimit,\n pageOffset\n );\n\n return {\n offers: offers.map(offer => ({\n offerId: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n })),\n count: offers.length,\n limit: pageLimit,\n offset: pageOffset,\n };\n }\n\n // Mode 2: Random discovery (no limit provided)\n // Exclude self if authenticated\n const excludeUsername = name || null;\n\n const offer = await storage.getRandomOffer(tags, excludeUsername);\n\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'No offers found matching tags');\n }\n\n return {\n offerId: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n };\n },\n\n /**\n * Publish offers with tags\n */\n async publishOffer(params: PublishOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { tags, offers, ttl } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required for offer publishing');\n }\n\n // Validate tags\n const tagsValidation = validateTags(tags);\n if (!tagsValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_TAG, tagsValidation.error || 'Invalid tags');\n }\n\n // Validate offers\n if (!offers || !Array.isArray(offers) || offers.length === 0) {\n throw new RpcError(ErrorCodes.MISSING_PARAMS, 'Must provide at least one offer');\n }\n\n if (offers.length > config.maxOffersPerRequest) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_OFFERS,\n `Too many offers (max ${config.maxOffersPerRequest})`\n );\n }\n\n // Check per-user offer limit\n const userOfferCount = await storage.getOfferCountByUsername(name);\n if (userOfferCount + offers.length > config.maxOffersPerUser) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_OFFERS_PER_USER,\n `User offer limit exceeded. You have ${userOfferCount} offers, limit is ${config.maxOffersPerUser}.`\n );\n }\n\n // Check total offers limit\n const totalOfferCount = await storage.getOfferCount();\n if (totalOfferCount + offers.length > config.maxTotalOffers) {\n throw new RpcError(\n ErrorCodes.STORAGE_FULL,\n `Server offer limit reached (${config.maxTotalOffers}). Try again later.`\n );\n }\n\n // Validate each offer has valid SDP\n offers.forEach((offer, index) => {\n if (!offer || typeof offer !== 'object') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Invalid offer at index ${index}: must be an object`);\n }\n if (!offer.sdp || typeof offer.sdp !== 'string') {\n throw new RpcError(ErrorCodes.INVALID_SDP, `Invalid offer at index ${index}: missing or invalid SDP`);\n }\n if (!offer.sdp.trim()) {\n throw new RpcError(ErrorCodes.INVALID_SDP, `Invalid offer at index ${index}: SDP cannot be empty`);\n }\n if (offer.sdp.length > config.maxSdpSize) {\n throw new RpcError(ErrorCodes.SDP_TOO_LARGE, `SDP too large at index ${index} (max ${config.maxSdpSize} bytes)`);\n }\n });\n\n // Validate TTL if provided\n if (ttl !== undefined) {\n if (typeof ttl !== 'number' || isNaN(ttl) || ttl < 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'TTL must be a non-negative number');\n }\n }\n\n // Create offers with tags\n const now = Date.now();\n const offerTtl =\n ttl !== undefined\n ? Math.min(\n Math.max(ttl, config.offerMinTtl),\n config.offerMaxTtl\n )\n : config.offerDefaultTtl;\n const expiresAt = now + offerTtl;\n\n // Prepare offer requests with tags\n const offerRequests = offers.map(offer => ({\n username: name,\n tags,\n sdp: offer.sdp,\n expiresAt,\n }));\n\n const createdOffers = await storage.createOffers(offerRequests);\n\n return {\n username: name,\n tags,\n offers: createdOffers.map(offer => ({\n offerId: offer.id,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n })),\n createdAt: now,\n expiresAt,\n };\n },\n\n /**\n * Delete an offer by ID\n */\n async deleteOffer(params: DeleteOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n validateStringParam(offerId, 'offerId');\n\n const deleted = await storage.deleteOffer(offerId, name);\n if (!deleted) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Offer not found or not owned by this name');\n }\n\n return { success: true };\n },\n\n /**\n * Answer an offer\n */\n async answerOffer(params: AnswerOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, sdp } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n if (!sdp || typeof sdp !== 'string' || sdp.length === 0) {\n throw new RpcError(ErrorCodes.INVALID_SDP, 'Invalid SDP');\n }\n\n if (sdp.length > config.maxSdpSize) {\n throw new RpcError(ErrorCodes.SDP_TOO_LARGE, `SDP too large (max ${config.maxSdpSize} bytes)`);\n }\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n if (offer.answererUsername) {\n throw new RpcError(ErrorCodes.OFFER_ALREADY_ANSWERED, 'Offer already answered');\n }\n\n await storage.answerOffer(offerId, name, sdp);\n\n return { success: true, offerId };\n },\n\n /**\n * Get answer for an offer\n */\n async getOfferAnswer(params: GetOfferAnswerParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n if (offer.username !== name) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Not authorized to access this offer');\n }\n\n if (!offer.answererUsername || !offer.answerSdp) {\n throw new RpcError(ErrorCodes.OFFER_NOT_ANSWERED, 'Offer not yet answered');\n }\n\n return {\n sdp: offer.answerSdp,\n offerId: offer.id,\n answererId: offer.answererUsername,\n answeredAt: offer.answeredAt,\n };\n },\n\n /**\n * Combined polling for answers and ICE candidates\n */\n async poll(params: PollParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { since } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n // Validate since parameter\n if (since !== undefined && (typeof since !== 'number' || since < 0 || !Number.isFinite(since))) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Invalid since parameter: must be a non-negative number');\n }\n const sinceTimestamp = since !== undefined ? since : 0;\n\n // Get all answered offers (where user is the offerer)\n const answeredOffers = await storage.getAnsweredOffers(name);\n const filteredAnswers = answeredOffers.filter(\n (offer) => offer.answeredAt && offer.answeredAt > sinceTimestamp\n );\n\n // Get all user's offers (where user is offerer)\n const ownedOffers = await storage.getOffersByUsername(name);\n\n // Get all offers the user has answered (where user is answerer)\n const answeredByUser = await storage.getOffersAnsweredBy(name);\n\n // Combine offer IDs from both sources for ICE candidate fetching\n // The storage method handles filtering by role automatically\n const allOfferIds = [\n ...ownedOffers.map(offer => offer.id),\n ...answeredByUser.map(offer => offer.id),\n ];\n // Remove duplicates (shouldn't happen, but defensive)\n const offerIds = [...new Set(allOfferIds)];\n\n // Batch fetch ICE candidates for all offers using JOIN to avoid N+1 query problem\n // Server filters by role - offerers get answerer candidates, answerers get offerer candidates\n const iceCandidatesMap = await storage.getIceCandidatesForMultipleOffers(\n offerIds,\n name,\n sinceTimestamp\n );\n\n // Convert Map to Record for response\n const iceCandidatesByOffer: Record<string, any[]> = {};\n for (const [offerId, candidates] of iceCandidatesMap.entries()) {\n iceCandidatesByOffer[offerId] = candidates;\n }\n\n return {\n answers: filteredAnswers.map((offer) => ({\n offerId: offer.id,\n answererId: offer.answererUsername,\n sdp: offer.answerSdp,\n answeredAt: offer.answeredAt,\n })),\n iceCandidates: iceCandidatesByOffer,\n };\n },\n\n /**\n * Add ICE candidates\n */\n async addIceCandidates(params: AddIceCandidatesParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, candidates } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n if (!Array.isArray(candidates) || candidates.length === 0) {\n throw new RpcError(ErrorCodes.MISSING_PARAMS, 'Missing or invalid required parameter: candidates');\n }\n\n if (candidates.length > config.maxCandidatesPerRequest) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Too many candidates (max ${config.maxCandidatesPerRequest})`\n );\n }\n\n // Validate each candidate is an object (don't enforce structure per CLAUDE.md)\n candidates.forEach((candidate, index) => {\n if (!candidate || typeof candidate !== 'object') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Invalid candidate at index ${index}: must be an object`);\n }\n\n // Check JSON depth to prevent stack overflow from deeply nested objects\n const depth = getJsonDepth(candidate, config.maxCandidateDepth + 1);\n if (depth > config.maxCandidateDepth) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Candidate at index ${index} too deeply nested (max depth ${config.maxCandidateDepth})`\n );\n }\n\n // Ensure candidate is serializable and check size (will be stored as JSON)\n let candidateJson: string;\n try {\n candidateJson = JSON.stringify(candidate);\n } catch (e) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Candidate at index ${index} is not serializable`);\n }\n\n // Validate candidate size to prevent abuse\n if (candidateJson.length > config.maxCandidateSize) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Candidate at index ${index} too large (max ${config.maxCandidateSize} bytes)`\n );\n }\n });\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n // Check ICE candidates limit per offer\n const currentCandidateCount = await storage.getIceCandidateCount(offerId);\n if (currentCandidateCount + candidates.length > config.maxIceCandidatesPerOffer) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_ICE_CANDIDATES,\n `ICE candidate limit exceeded for offer. Current: ${currentCandidateCount}, limit: ${config.maxIceCandidatesPerOffer}.`\n );\n }\n\n const role = offer.username === name ? 'offerer' : 'answerer';\n const count = await storage.addIceCandidates(\n offerId,\n name,\n role,\n candidates\n );\n\n return { count, offerId };\n },\n\n /**\n * Get ICE candidates\n */\n async getIceCandidates(params: GetIceCandidatesParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, since } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n // Validate since parameter\n if (since !== undefined && (typeof since !== 'number' || since < 0 || !Number.isFinite(since))) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Invalid since parameter: must be a non-negative number');\n }\n const sinceTimestamp = since !== undefined ? since : 0;\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n // Validate that user is authorized to access this offer's candidates\n // Only the offerer and answerer can access ICE candidates\n const isOfferer = offer.username === name;\n const isAnswerer = offer.answererUsername === name;\n\n if (!isOfferer && !isAnswerer) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Not authorized to access ICE candidates for this offer');\n }\n\n const role = isOfferer ? 'answerer' : 'offerer';\n\n const candidates = await storage.getIceCandidates(\n offerId,\n role,\n sinceTimestamp\n );\n\n return {\n candidates: candidates.map((c: any) => ({\n candidate: c.candidate,\n createdAt: c.createdAt,\n })),\n offerId,\n };\n },\n};\n\n// Methods that don't require authentication\nconst UNAUTHENTICATED_METHODS = new Set(['generateCredentials', 'discover']);\n\n/**\n * Handle RPC batch request with header-based authentication\n */\nexport async function handleRpc(\n requests: RpcRequest[],\n ctx: Context,\n storage: Storage,\n config: Config\n): Promise<RpcResponse[]> {\n const responses: RpcResponse[] = [];\n\n // Extract client IP for rate limiting\n // Try multiple headers for proxy compatibility\n const clientIp =\n ctx.req.header('cf-connecting-ip') || // Cloudflare\n ctx.req.header('x-real-ip') || // Nginx\n ctx.req.header('x-forwarded-for')?.split(',')[0].trim() ||\n undefined; // Don't use fallback - let handlers decide how to handle missing IP\n\n // General request rate limiting (per IP per second)\n if (clientIp) {\n const rateLimitKey = `req:${clientIp}`;\n const allowed = await storage.checkRateLimit(\n rateLimitKey,\n config.requestsPerIpPerSecond,\n REQUEST_RATE_WINDOW\n );\n\n if (!allowed) {\n // Return error for all requests in the batch\n return requests.map(() => ({\n success: false,\n error: `Rate limit exceeded. Maximum ${config.requestsPerIpPerSecond} requests per second per IP.`,\n errorCode: ErrorCodes.RATE_LIMIT_EXCEEDED,\n }));\n }\n }\n\n // Read auth headers (same for all requests in batch)\n const name = ctx.req.header('X-Name');\n const timestampHeader = ctx.req.header('X-Timestamp');\n const nonce = ctx.req.header('X-Nonce');\n const signature = ctx.req.header('X-Signature');\n\n // Parse timestamp if present\n const timestamp = timestampHeader ? parseInt(timestampHeader, 10) : 0;\n\n // CRITICAL: Pre-calculate total operations BEFORE processing any requests\n // This prevents DoS where first N requests complete before limit triggers\n // Example attack prevented: 100 publishOffer \u00D7 100 offers = 10,000 operations\n let totalOperations = 0;\n\n // Count all operations across all requests first\n for (const request of requests) {\n const { method, params } = request;\n if (method === 'publishOffer' && params?.offers && Array.isArray(params.offers)) {\n totalOperations += params.offers.length;\n } else if (method === 'addIceCandidates' && params?.candidates && Array.isArray(params.candidates)) {\n totalOperations += params.candidates.length;\n } else {\n totalOperations += 1; // Single operation\n }\n }\n\n // Reject entire batch if total operations exceed limit\n // This happens BEFORE processing any requests\n // Return error for EACH request to maintain response array alignment\n if (totalOperations > config.maxTotalOperations) {\n return requests.map(() => ({\n success: false,\n error: `Total operations across batch exceed limit: ${totalOperations} > ${config.maxTotalOperations}`,\n errorCode: ErrorCodes.BATCH_TOO_LARGE,\n }));\n }\n\n // Process all requests\n for (const request of requests) {\n try {\n const { method, params } = request;\n\n // Validate request\n if (!method || typeof method !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid method',\n errorCode: ErrorCodes.INVALID_PARAMS,\n });\n continue;\n }\n\n // Get handler\n const handler = handlers[method];\n if (!handler) {\n responses.push({\n success: false,\n error: `Unknown method: ${method}`,\n errorCode: ErrorCodes.UNKNOWN_METHOD,\n });\n continue;\n }\n\n // Validate auth headers only for methods that require authentication\n const requiresAuth = !UNAUTHENTICATED_METHODS.has(method);\n\n if (requiresAuth) {\n if (!name || typeof name !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Name header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!timestampHeader || typeof timestampHeader !== 'string' || isNaN(timestamp)) {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Timestamp header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!nonce || typeof nonce !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Nonce header (use crypto.randomUUID())',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!signature || typeof signature !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Signature header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n // Verify signature (validates timestamp, nonce, and signature)\n await verifyRequestSignature(\n name,\n timestamp,\n nonce,\n signature,\n method,\n params,\n storage,\n config\n );\n\n // Execute handler with auth\n const result = await handler(\n params || {},\n name,\n timestamp,\n signature,\n storage,\n config,\n { ...request, clientIp }\n );\n\n responses.push({\n success: true,\n result,\n });\n } else {\n // Execute handler without strict auth requirement\n const result = await handler(\n params || {},\n name || '',\n 0, // timestamp\n '', // signature\n storage,\n config,\n { ...request, clientIp }\n );\n\n responses.push({\n success: true,\n result,\n });\n }\n } catch (err) {\n if (err instanceof RpcError) {\n responses.push({\n success: false,\n error: err.message,\n errorCode: err.errorCode,\n });\n } else {\n // Generic error - don't leak internal details\n // Log the actual error for debugging\n console.error('Unexpected RPC error:', err);\n responses.push({\n success: false,\n error: 'Internal server error',\n errorCode: ErrorCodes.INTERNAL_ERROR,\n });\n }\n }\n }\n\n return responses;\n}\n", "import { Storage } from './storage/types.ts';\nimport { StorageType } from './storage/factory.ts';\n\n// Version is injected at build time via esbuild define\ndeclare const RONDEVU_VERSION: string;\nconst BUILD_VERSION = typeof RONDEVU_VERSION !== 'undefined' ? RONDEVU_VERSION : 'unknown';\n\n/**\n * Application configuration\n * Reads from environment variables with sensible defaults\n */\nexport interface Config {\n port: number;\n storageType: StorageType;\n storagePath: string;\n databaseUrl: string;\n dbPoolSize: number;\n corsOrigins: string[];\n version: string;\n offerDefaultTtl: number;\n offerMaxTtl: number;\n offerMinTtl: number;\n cleanupInterval: number;\n maxOffersPerRequest: number;\n maxBatchSize: number;\n maxSdpSize: number;\n maxCandidateSize: number;\n maxCandidateDepth: number;\n maxCandidatesPerRequest: number;\n maxTotalOperations: number;\n timestampMaxAge: number; // Max age for timestamps (replay protection)\n timestampMaxFuture: number; // Max future tolerance for timestamps (clock skew)\n masterEncryptionKey: string; // 64-char hex string for encrypting secrets (32 bytes)\n // Resource limits (for abuse prevention)\n maxOffersPerUser: number; // Max concurrent offers per user\n maxTotalOffers: number; // Max total offers in storage\n maxTotalCredentials: number; // Max total credentials in storage\n maxIceCandidatesPerOffer: number; // Max ICE candidates per offer\n credentialsPerIpPerSecond: number; // Rate limit: credentials per IP per second\n requestsPerIpPerSecond: number; // Rate limit: requests per IP per second\n}\n\n/**\n * Loads configuration from environment variables\n */\nexport function loadConfig(): Config {\n // Master encryption key for secret storage\n // CRITICAL: Set MASTER_ENCRYPTION_KEY in production to a secure random value\n let masterEncryptionKey = process.env.MASTER_ENCRYPTION_KEY;\n\n if (!masterEncryptionKey) {\n // SECURITY: Fail fast unless explicitly in development mode\n // Default to production-safe behavior if NODE_ENV is unset\n const isDevelopment = process.env.NODE_ENV === 'development';\n\n if (!isDevelopment) {\n throw new Error(\n 'MASTER_ENCRYPTION_KEY environment variable must be set. ' +\n 'Generate with: openssl rand -hex 32\\n' +\n 'For development only, set NODE_ENV=development to use insecure dev key.'\n );\n }\n\n // Use deterministic key ONLY in explicit development mode\n // WARNING: DO NOT USE THIS IN PRODUCTION - only for local development\n console.error('\u26A0\uFE0F WARNING: Using insecure deterministic development key');\n console.error('\u26A0\uFE0F ONLY use NODE_ENV=development for local development');\n console.error('\u26A0\uFE0F Generate production key with: openssl rand -hex 32');\n // Random-looking dev key (not ASCII-readable to prevent accidental production use)\n masterEncryptionKey = 'a3f8b9c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0';\n }\n\n // Validate master encryption key format\n // NOTE: Using regex here is safe since this runs at startup, not during request processing\n if (masterEncryptionKey.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(masterEncryptionKey)) {\n throw new Error('MASTER_ENCRYPTION_KEY must be 64-character hex string (32 bytes). Generate with: openssl rand -hex 32');\n }\n\n // Helper to safely parse and validate integer config values\n function parsePositiveInt(value: string | undefined, defaultValue: string, name: string, min = 1): number {\n const parsed = parseInt(value || defaultValue, 10);\n if (isNaN(parsed)) {\n throw new Error(`${name} must be a valid integer (got: ${value})`);\n }\n if (parsed < min) {\n throw new Error(`${name} must be >= ${min} (got: ${parsed})`);\n }\n return parsed;\n }\n\n const config = {\n port: parsePositiveInt(process.env.PORT, '3000', 'PORT', 1),\n storageType: (process.env.STORAGE_TYPE || 'memory') as StorageType,\n storagePath: process.env.STORAGE_PATH || ':memory:',\n databaseUrl: process.env.DATABASE_URL || '',\n dbPoolSize: parsePositiveInt(process.env.DB_POOL_SIZE, '10', 'DB_POOL_SIZE', 1),\n corsOrigins: process.env.CORS_ORIGINS\n ? process.env.CORS_ORIGINS.split(',').map(o => o.trim())\n : ['*'],\n version: process.env.VERSION || BUILD_VERSION,\n offerDefaultTtl: parsePositiveInt(process.env.OFFER_DEFAULT_TTL, '60000', 'OFFER_DEFAULT_TTL', 1000),\n offerMaxTtl: parsePositiveInt(process.env.OFFER_MAX_TTL, '86400000', 'OFFER_MAX_TTL', 1000),\n offerMinTtl: parsePositiveInt(process.env.OFFER_MIN_TTL, '60000', 'OFFER_MIN_TTL', 1000),\n cleanupInterval: parsePositiveInt(process.env.CLEANUP_INTERVAL, '60000', 'CLEANUP_INTERVAL', 1000),\n maxOffersPerRequest: parsePositiveInt(process.env.MAX_OFFERS_PER_REQUEST, '100', 'MAX_OFFERS_PER_REQUEST', 1),\n maxBatchSize: parsePositiveInt(process.env.MAX_BATCH_SIZE, '100', 'MAX_BATCH_SIZE', 1),\n maxSdpSize: parsePositiveInt(process.env.MAX_SDP_SIZE, String(64 * 1024), 'MAX_SDP_SIZE', 1024), // Min 1KB\n maxCandidateSize: parsePositiveInt(process.env.MAX_CANDIDATE_SIZE, String(4 * 1024), 'MAX_CANDIDATE_SIZE', 256), // Min 256 bytes\n maxCandidateDepth: parsePositiveInt(process.env.MAX_CANDIDATE_DEPTH, '10', 'MAX_CANDIDATE_DEPTH', 1),\n maxCandidatesPerRequest: parsePositiveInt(process.env.MAX_CANDIDATES_PER_REQUEST, '100', 'MAX_CANDIDATES_PER_REQUEST', 1),\n maxTotalOperations: parsePositiveInt(process.env.MAX_TOTAL_OPERATIONS, '1000', 'MAX_TOTAL_OPERATIONS', 1),\n timestampMaxAge: parsePositiveInt(process.env.TIMESTAMP_MAX_AGE, '60000', 'TIMESTAMP_MAX_AGE', 1000), // Min 1 second\n timestampMaxFuture: parsePositiveInt(process.env.TIMESTAMP_MAX_FUTURE, '60000', 'TIMESTAMP_MAX_FUTURE', 1000), // Min 1 second\n masterEncryptionKey,\n // Resource limits\n maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, '20', 'MAX_OFFERS_PER_USER', 1),\n maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, '10000', 'MAX_TOTAL_OFFERS', 1),\n maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, '50000', 'MAX_TOTAL_CREDENTIALS', 1),\n maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, '50', 'MAX_ICE_CANDIDATES_PER_OFFER', 1),\n credentialsPerIpPerSecond: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_SECOND, '5', 'CREDENTIALS_PER_IP_PER_SECOND', 1),\n requestsPerIpPerSecond: parsePositiveInt(process.env.REQUESTS_PER_IP_PER_SECOND, '50', 'REQUESTS_PER_IP_PER_SECOND', 1),\n };\n\n return config;\n}\n\n/**\n * Default config values (shared between Node and Workers)\n */\nexport const CONFIG_DEFAULTS = {\n offerDefaultTtl: 60000,\n offerMaxTtl: 86400000,\n offerMinTtl: 60000,\n cleanupInterval: 60000,\n maxOffersPerRequest: 100,\n maxBatchSize: 100,\n maxSdpSize: 64 * 1024,\n maxCandidateSize: 4 * 1024,\n maxCandidateDepth: 10,\n maxCandidatesPerRequest: 100,\n maxTotalOperations: 1000,\n timestampMaxAge: 60000,\n timestampMaxFuture: 60000,\n // Resource limits\n maxOffersPerUser: 1000,\n maxTotalOffers: 100000,\n maxTotalCredentials: 50000,\n maxIceCandidatesPerOffer: 50,\n credentialsPerIpPerSecond: 5,\n requestsPerIpPerSecond: 50,\n} as const;\n\n/**\n * Build config for Cloudflare Workers from env vars\n */\nexport function buildWorkerConfig(env: {\n MASTER_ENCRYPTION_KEY: string;\n OFFER_DEFAULT_TTL?: string;\n OFFER_MAX_TTL?: string;\n OFFER_MIN_TTL?: string;\n MAX_OFFERS_PER_REQUEST?: string;\n MAX_BATCH_SIZE?: string;\n CORS_ORIGINS?: string;\n VERSION?: string;\n}): Config {\n return {\n port: 0, // Not used in Workers\n storageType: 'sqlite', // D1 is SQLite-compatible\n storagePath: '', // Not used with D1\n databaseUrl: '', // Not used with D1\n dbPoolSize: 10, // Not used with D1\n corsOrigins: env.CORS_ORIGINS?.split(',').map(o => o.trim()) ?? ['*'],\n version: env.VERSION ?? 'unknown',\n offerDefaultTtl: env.OFFER_DEFAULT_TTL ? parseInt(env.OFFER_DEFAULT_TTL, 10) : CONFIG_DEFAULTS.offerDefaultTtl,\n offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : CONFIG_DEFAULTS.offerMaxTtl,\n offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : CONFIG_DEFAULTS.offerMinTtl,\n cleanupInterval: CONFIG_DEFAULTS.cleanupInterval,\n maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : CONFIG_DEFAULTS.maxOffersPerRequest,\n maxBatchSize: env.MAX_BATCH_SIZE ? parseInt(env.MAX_BATCH_SIZE, 10) : CONFIG_DEFAULTS.maxBatchSize,\n maxSdpSize: CONFIG_DEFAULTS.maxSdpSize,\n maxCandidateSize: CONFIG_DEFAULTS.maxCandidateSize,\n maxCandidateDepth: CONFIG_DEFAULTS.maxCandidateDepth,\n maxCandidatesPerRequest: CONFIG_DEFAULTS.maxCandidatesPerRequest,\n maxTotalOperations: CONFIG_DEFAULTS.maxTotalOperations,\n timestampMaxAge: CONFIG_DEFAULTS.timestampMaxAge,\n timestampMaxFuture: CONFIG_DEFAULTS.timestampMaxFuture,\n masterEncryptionKey: env.MASTER_ENCRYPTION_KEY,\n // Resource limits\n maxOffersPerUser: CONFIG_DEFAULTS.maxOffersPerUser,\n maxTotalOffers: CONFIG_DEFAULTS.maxTotalOffers,\n maxTotalCredentials: CONFIG_DEFAULTS.maxTotalCredentials,\n maxIceCandidatesPerOffer: CONFIG_DEFAULTS.maxIceCandidatesPerOffer,\n credentialsPerIpPerSecond: CONFIG_DEFAULTS.credentialsPerIpPerSecond,\n requestsPerIpPerSecond: CONFIG_DEFAULTS.requestsPerIpPerSecond,\n };\n}\n\n/**\n * Run cleanup of expired entries (shared between Node and Workers)\n * @returns Object with counts of deleted items\n */\nexport async function runCleanup(storage: Storage, now: number): Promise<{\n offers: number;\n credentials: number;\n rateLimits: number;\n nonces: number;\n}> {\n const offers = await storage.deleteExpiredOffers(now);\n const credentials = await storage.deleteExpiredCredentials(now);\n const rateLimits = await storage.deleteExpiredRateLimits(now);\n const nonces = await storage.deleteExpiredNonces(now);\n\n return { offers, credentials, rateLimits, nonces };\n}\n", "import { Storage } from './types.ts';\n\n/**\n * Supported storage backend types\n */\nexport type StorageType = 'memory' | 'sqlite' | 'mysql' | 'postgres';\n\n/**\n * Configuration for creating a storage backend\n */\nexport interface StorageConfig {\n type: StorageType;\n /** Master encryption key for secrets (64-char hex string) */\n masterEncryptionKey: string;\n /** SQLite database path (default: ':memory:') */\n sqlitePath?: string;\n /** Connection string for MySQL/PostgreSQL */\n connectionString?: string;\n /** Connection pool size for MySQL/PostgreSQL (default: 10) */\n poolSize?: number;\n}\n\n/**\n * Creates a storage backend based on configuration\n * Uses dynamic imports to avoid loading unused dependencies\n */\nexport async function createStorage(config: StorageConfig): Promise<Storage> {\n switch (config.type) {\n case 'memory': {\n const { MemoryStorage } = await import('./memory.ts');\n return new MemoryStorage(config.masterEncryptionKey);\n }\n\n case 'sqlite': {\n const { SQLiteStorage } = await import('./sqlite.ts');\n return new SQLiteStorage(\n config.sqlitePath || ':memory:',\n config.masterEncryptionKey\n );\n }\n\n case 'mysql': {\n if (!config.connectionString) {\n throw new Error('MySQL storage requires DATABASE_URL connection string');\n }\n const { MySQLStorage } = await import('./mysql.ts');\n return MySQLStorage.create(\n config.connectionString,\n config.masterEncryptionKey,\n config.poolSize || 10\n );\n }\n\n case 'postgres': {\n if (!config.connectionString) {\n throw new Error('PostgreSQL storage requires DATABASE_URL connection string');\n }\n const { PostgreSQLStorage } = await import('./postgres.ts');\n return PostgreSQLStorage.create(\n config.connectionString,\n config.masterEncryptionKey,\n config.poolSize || 10\n );\n }\n\n default:\n throw new Error(`Unsupported storage type: ${config.type}`);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Crypto utilities for credential generation and validation\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n */\n\nimport { Buffer } from 'node:buffer';\n\n// Username validation\n// Rules: 4-32 chars, lowercase alphanumeric + dashes + periods, must start/end with alphanumeric\nconst USERNAME_REGEX = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;\nconst USERNAME_MIN_LENGTH = 4;\nconst USERNAME_MAX_LENGTH = 32;\n\n/**\n * Generates a random credential name\n * Format: {adjective}-{noun}-{random}\n * Example: \"brave-tiger-7a3f2b1c9d8e\", \"quick-river-9b2e4c1a5f3d\"\n */\nexport function generateCredentialName(): string {\n const adjectives = [\n 'brave', 'calm', 'eager', 'fancy', 'gentle', 'happy', 'jolly', 'kind',\n 'lively', 'merry', 'nice', 'proud', 'quiet', 'swift', 'witty', 'young',\n 'bright', 'clever', 'daring', 'fair', 'grand', 'humble', 'noble', 'quick'\n ];\n\n const nouns = [\n 'tiger', 'eagle', 'river', 'mountain', 'ocean', 'forest', 'desert', 'valley',\n 'thunder', 'wind', 'fire', 'stone', 'cloud', 'star', 'moon', 'sun',\n 'wolf', 'bear', 'hawk', 'lion', 'fox', 'deer', 'owl', 'swan'\n ];\n\n const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];\n const noun = nouns[Math.floor(Math.random() * nouns.length)];\n\n // Generate 16-character hex suffix for uniqueness (8 bytes = 2^64 combinations)\n // With 576 adjective-noun pairs, total space: 576 \u00D7 2^64 \u2248 1.06 \u00D7 10^22 names\n // Birthday paradox collision at ~4.3 billion credentials (extremely safe for large deployments)\n // Increased from 6 bytes to 8 bytes for maximum collision resistance\n const random = crypto.getRandomValues(new Uint8Array(8));\n const hex = Array.from(random).map(b => b.toString(16).padStart(2, '0')).join('');\n\n return `${adjective}-${noun}-${hex}`;\n}\n\n/**\n * Generates a random secret (API key style)\n * Format: 64-character hex string (256 bits of entropy)\n * 256 bits provides optimal security for HMAC-SHA256 and future-proofs against brute force\n */\nexport function generateSecret(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32)); // 32 bytes = 256 bits\n const secret = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n // Validation: Ensure output is exactly 64 characters and valid hex\n if (secret.length !== 64) {\n throw new Error('Secret generation failed: invalid length');\n }\n\n // Validate all characters are valid hex digits (0-9, a-f)\n for (let i = 0; i < secret.length; i++) {\n const c = secret[i];\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error(`Secret generation failed: invalid hex character at position ${i}: '${c}'`);\n }\n }\n\n return secret;\n}\n\n// ===== Secret Encryption/Decryption (Database Storage) =====\n\n/**\n * Convert hex string to byte array with validation\n * @param hex Hex string (must be even length)\n * @returns Uint8Array of bytes\n */\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) {\n throw new Error('Hex string must have even length');\n }\n\n // Pre-validate that all characters are valid hex digits (0-9, a-f, A-F)\n // This prevents parseInt from silently truncating invalid input like \"0z\" -> 0\n for (let i = 0; i < hex.length; i++) {\n const c = hex[i].toLowerCase();\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error(`Invalid hex character at position ${i}: '${hex[i]}'`);\n }\n }\n\n const match = hex.match(/.{1,2}/g);\n if (!match) {\n throw new Error('Invalid hex string format');\n }\n\n return new Uint8Array(match.map(byte => {\n const parsed = parseInt(byte, 16);\n if (isNaN(parsed)) {\n throw new Error(`Invalid hex byte: ${byte}`);\n }\n return parsed;\n }));\n}\n\n/**\n * Encrypt a secret using AES-256-GCM with master key\n * Format: iv:ciphertext (all hex-encoded, auth tag included in ciphertext)\n *\n * @param secret The plaintext secret to encrypt\n * @param masterKeyHex The master encryption key (64-char hex = 32 bytes)\n * @returns Encrypted secret in format \"iv:ciphertext\"\n */\nexport async function encryptSecret(secret: string, masterKeyHex: string): Promise<string> {\n // Validate master key\n if (!masterKeyHex || masterKeyHex.length !== 64) {\n throw new Error('Master key must be 64-character hex string (32 bytes)');\n }\n\n // Convert master key from hex to bytes (with validation)\n const keyBytes = hexToBytes(masterKeyHex);\n\n // Import master key\n const key = await crypto.subtle.importKey(\n 'raw',\n keyBytes,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt']\n );\n\n // Generate random IV (12 bytes for AES-GCM)\n const iv = crypto.getRandomValues(new Uint8Array(12));\n\n // Encrypt secret\n const encoder = new TextEncoder();\n const secretBytes = encoder.encode(secret);\n\n // AES-GCM returns ciphertext with auth tag already appended (no manual splitting needed)\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv, tagLength: 128 },\n key,\n secretBytes\n );\n\n // Convert to hex: iv:ciphertext (ciphertext includes 16-byte auth tag at end)\n const ivHex = Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('');\n const ciphertextHex = Array.from(new Uint8Array(ciphertext))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n return `${ivHex}:${ciphertextHex}`;\n}\n\n/**\n * Decrypt a secret using AES-256-GCM with master key\n *\n * @param encryptedSecret Encrypted secret in format \"iv:ciphertext\" (ciphertext includes auth tag)\n * @param masterKeyHex The master encryption key (64-char hex = 32 bytes)\n * @returns Decrypted plaintext secret\n */\nexport async function decryptSecret(encryptedSecret: string, masterKeyHex: string): Promise<string> {\n // Validate master key\n if (!masterKeyHex || masterKeyHex.length !== 64) {\n throw new Error('Master key must be 64-character hex string (32 bytes)');\n }\n\n // Parse encrypted format: iv:ciphertext\n const parts = encryptedSecret.split(':');\n if (parts.length !== 2) {\n throw new Error('Invalid encrypted secret format (expected iv:ciphertext)');\n }\n\n const [ivHex, ciphertextHex] = parts;\n\n // Validate IV length (must be 12 bytes = 24 hex characters for AES-GCM)\n if (ivHex.length !== 24) {\n throw new Error('Invalid IV length (expected 12 bytes = 24 hex characters)');\n }\n\n // Validate ciphertext length (must include at least 16-byte auth tag)\n // Minimum: 16 bytes for auth tag = 32 hex characters\n if (ciphertextHex.length < 32) {\n throw new Error('Invalid ciphertext length (must include 16-byte auth tag)');\n }\n\n // Convert from hex to bytes (with validation)\n const iv = hexToBytes(ivHex);\n const ciphertext = hexToBytes(ciphertextHex);\n\n // Convert master key from hex to bytes (with validation)\n const keyBytes = hexToBytes(masterKeyHex);\n\n // Import master key\n const key = await crypto.subtle.importKey(\n 'raw',\n keyBytes,\n { name: 'AES-GCM', length: 256 },\n false,\n ['decrypt']\n );\n\n // Decrypt (ciphertext already includes 16-byte auth tag at end)\n const decryptedBytes = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv, tagLength: 128 },\n key,\n ciphertext\n );\n\n // Convert to string\n const decoder = new TextDecoder();\n return decoder.decode(decryptedBytes);\n}\n\n// ===== HMAC Signature Generation and Verification =====\n\n/**\n * Generate HMAC-SHA256 signature for request authentication\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n *\n * @param secret The credential secret (hex string)\n * @param message The message to sign (typically: timestamp + method + params)\n * @returns Promise<string> Base64-encoded signature\n */\nexport async function generateSignature(secret: string, message: string): Promise<string> {\n // Convert secret from hex to bytes (with validation)\n const secretBytes = hexToBytes(secret);\n\n // Import secret as HMAC key\n const key = await crypto.subtle.importKey(\n 'raw',\n secretBytes,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n\n // Convert message to bytes\n const encoder = new TextEncoder();\n const messageBytes = encoder.encode(message);\n\n // Generate HMAC signature\n const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);\n\n // Convert to base64\n return Buffer.from(signatureBytes).toString('base64');\n}\n\n/**\n * Verify HMAC-SHA256 signature for request authentication\n * Uses crypto.subtle.verify() for constant-time comparison\n *\n * @param secret The credential secret (hex string)\n * @param message The message that was signed\n * @param signature The signature to verify (base64)\n * @returns Promise<boolean> True if signature is valid\n */\nexport async function verifySignature(secret: string, message: string, signature: string): Promise<boolean> {\n try {\n // Convert secret from hex to bytes (with validation)\n const secretBytes = hexToBytes(secret);\n\n // Import secret as HMAC key for verification\n const key = await crypto.subtle.importKey(\n 'raw',\n secretBytes,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify']\n );\n\n // Convert message to bytes\n const encoder = new TextEncoder();\n const messageBytes = encoder.encode(message);\n\n // Convert signature from base64 to bytes\n const signatureBytes = Buffer.from(signature, 'base64');\n\n // Use Web Crypto API's verify() for constant-time comparison\n // This is cryptographically secure and resistant to timing attacks\n return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);\n } catch (error) {\n // Log error for debugging (helps identify implementation bugs)\n console.error('Signature verification error:', error);\n return false;\n }\n}\n\n/**\n * Canonical JSON serialization with sorted keys\n * Ensures deterministic output regardless of property insertion order\n * Must match client's canonicalJSON implementation exactly\n */\nfunction canonicalJSON(obj: any, depth: number = 0): string {\n const MAX_DEPTH = 100;\n\n if (depth > MAX_DEPTH) {\n throw new Error('Object nesting too deep for canonicalization');\n }\n\n if (obj === null) return 'null';\n if (obj === undefined) return JSON.stringify(undefined);\n\n const type = typeof obj;\n\n if (type === 'function') throw new Error('Functions are not supported in RPC parameters');\n if (type === 'symbol' || type === 'bigint') throw new Error(`${type} is not supported in RPC parameters`);\n if (type === 'number' && !Number.isFinite(obj)) throw new Error('NaN and Infinity are not supported in RPC parameters');\n\n if (type !== 'object') return JSON.stringify(obj);\n\n if (Array.isArray(obj)) {\n return '[' + obj.map(item => canonicalJSON(item, depth + 1)).join(',') + ']';\n }\n\n const sortedKeys = Object.keys(obj).sort();\n const pairs = sortedKeys.map(key => JSON.stringify(key) + ':' + canonicalJSON(obj[key], depth + 1));\n return '{' + pairs.join(',') + '}';\n}\n\n/**\n * Build the message string for signing\n * Format: timestamp:nonce:method:canonicalJSON(params || {})\n * Uses colons as delimiters to prevent collision attacks\n * Includes nonce to prevent signature reuse within timestamp window\n * Uses canonical JSON (sorted keys) for deterministic serialization\n *\n * @param timestamp Unix timestamp in milliseconds\n * @param nonce Cryptographic nonce (UUID v4) to prevent replay attacks\n * @param method RPC method name\n * @param params RPC method parameters (optional)\n * @returns String to be signed\n */\nexport function buildSignatureMessage(timestamp: number, nonce: string, method: string, params?: any): string {\n // Validate nonce is UUID v4 format to prevent colon injection attacks\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (8-4-4-4-12 hex digits with dashes)\n // Use simple format checks instead of regex to avoid any timing or ReDoS concerns\n\n // Check total length (36 characters for UUID v4)\n if (nonce.length !== 36) {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check dash positions (indices 8, 13, 18, 23)\n if (nonce[8] !== '-' || nonce[13] !== '-' || nonce[18] !== '-' || nonce[23] !== '-') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check version (character at index 14 must be '4')\n if (nonce[14] !== '4') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Check variant (character at index 19 must be 8, 9, a, or b)\n const variant = nonce[19].toLowerCase();\n if (variant !== '8' && variant !== '9' && variant !== 'a' && variant !== 'b') {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n\n // Validate all other characters are hex digits (0-9, a-f)\n const hexChars = nonce.replace(/-/g, ''); // Remove dashes\n for (let i = 0; i < hexChars.length; i++) {\n const c = hexChars[i].toLowerCase();\n if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) {\n throw new Error('Nonce must be a valid UUID v4 (use crypto.randomUUID())');\n }\n }\n\n // Use canonical JSON (sorted keys) to match client's signature\n const paramsStr = canonicalJSON(params || {});\n // Use delimiters to prevent collision: timestamp=12,method=\"34\" vs timestamp=1,method=\"234\"\n // Include nonce to make each request unique (prevents signature reuse in same millisecond)\n return `${timestamp}:${nonce}:${method}:${paramsStr}`;\n}\n\n// ===== Username Validation =====\n\n/**\n * Validates username format\n * Rules: 4-32 chars, lowercase alphanumeric + dashes + periods, must start/end with alphanumeric\n */\nexport function validateUsername(username: string): { valid: boolean; error?: string } {\n if (typeof username !== 'string') {\n return { valid: false, error: 'Username must be a string' };\n }\n\n if (username.length < USERNAME_MIN_LENGTH) {\n return { valid: false, error: `Username must be at least ${USERNAME_MIN_LENGTH} characters` };\n }\n\n if (username.length > USERNAME_MAX_LENGTH) {\n return { valid: false, error: `Username must be at most ${USERNAME_MAX_LENGTH} characters` };\n }\n\n if (!USERNAME_REGEX.test(username)) {\n return { valid: false, error: 'Username must be lowercase alphanumeric with optional dashes/periods, and start/end with alphanumeric' };\n }\n\n return { valid: true };\n}\n\n// ===== Tag Validation =====\n\n// Tag validation constants\nconst TAG_MIN_LENGTH = 1;\nconst TAG_MAX_LENGTH = 64;\nconst TAG_REGEX = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;\n\n/**\n * Validates a single tag format\n * Rules: 1-64 chars, lowercase alphanumeric with optional dots/dashes\n * Must start and end with alphanumeric character\n *\n * Valid examples: \"chat\", \"video-call\", \"com.example.service\", \"v2\"\n * Invalid examples: \"\", \"UPPERCASE\", \"-starts-dash\", \"ends-dash-\"\n */\nexport function validateTag(tag: string): { valid: boolean; error?: string } {\n if (typeof tag !== 'string') {\n return { valid: false, error: 'Tag must be a string' };\n }\n\n if (tag.length < TAG_MIN_LENGTH) {\n return { valid: false, error: `Tag must be at least ${TAG_MIN_LENGTH} character` };\n }\n\n if (tag.length > TAG_MAX_LENGTH) {\n return { valid: false, error: `Tag must be at most ${TAG_MAX_LENGTH} characters` };\n }\n\n // Single character tags just need to be alphanumeric\n if (tag.length === 1) {\n if (!/^[a-z0-9]$/.test(tag)) {\n return { valid: false, error: 'Tag must be lowercase alphanumeric' };\n }\n return { valid: true };\n }\n\n // Multi-character tags must match the pattern\n if (!TAG_REGEX.test(tag)) {\n return { valid: false, error: 'Tag must be lowercase alphanumeric with optional dots/dashes, and start/end with alphanumeric' };\n }\n\n return { valid: true };\n}\n\n/**\n * Validates an array of tags\n * @param tags Array of tags to validate\n * @param maxTags Maximum number of tags allowed (default: 20)\n */\nexport function validateTags(tags: string[], maxTags: number = 20): { valid: boolean; error?: string } {\n if (!Array.isArray(tags)) {\n return { valid: false, error: 'Tags must be an array' };\n }\n\n if (tags.length === 0) {\n return { valid: false, error: 'At least one tag is required' };\n }\n\n if (tags.length > maxTags) {\n return { valid: false, error: `Maximum ${maxTags} tags allowed` };\n }\n\n // Validate each tag\n for (let i = 0; i < tags.length; i++) {\n const result = validateTag(tags[i]);\n if (!result.valid) {\n return { valid: false, error: `Tag ${i + 1}: ${result.error}` };\n }\n }\n\n // Check for duplicates\n const uniqueTags = new Set(tags);\n if (uniqueTags.size !== tags.length) {\n return { valid: false, error: 'Duplicate tags are not allowed' };\n }\n\n return { valid: true };\n}\n", "/**\n * Generates a unique offer ID using SHA-256 hash\n * Combines SDP content with timestamp and random bytes for uniqueness\n * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers\n *\n * @param sdp - The WebRTC SDP offer\n * @returns Unique SHA-256 hash ID\n */\nexport async function generateOfferHash(sdp: string): Promise<string> {\n // Generate random bytes for uniqueness (8 bytes = 64 bits of randomness)\n const randomBytes = crypto.getRandomValues(new Uint8Array(8));\n const randomHex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n // Include SDP, timestamp, and random bytes for uniqueness\n const hashInput = {\n sdp,\n timestamp: Date.now(),\n nonce: randomHex\n };\n\n // Create non-prettified JSON string\n const jsonString = JSON.stringify(hashInput);\n\n // Convert string to Uint8Array for hashing\n const encoder = new TextEncoder();\n const data = encoder.encode(jsonString);\n\n // Generate SHA-256 hash\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\n // Convert hash to hex string\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n\n return hashHex;\n}\n", "import {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\ninterface RateLimit {\n count: number;\n resetTime: number;\n}\n\ninterface NonceEntry {\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter for rondevu signaling system\n * Data is not persisted - all data is lost on server restart\n * Best for development, testing, or ephemeral deployments\n */\nexport class MemoryStorage implements Storage {\n private masterEncryptionKey: string;\n\n // Primary storage\n private credentials = new Map<string, Credential>();\n private offers = new Map<string, Offer>();\n private iceCandidates = new Map<string, IceCandidate[]>(); // offerId \u2192 candidates\n private rateLimits = new Map<string, RateLimit>();\n private nonces = new Map<string, NonceEntry>();\n\n // Secondary indexes for efficient lookups\n private offersByUsername = new Map<string, Set<string>>(); // username \u2192 offer IDs\n private offersByTag = new Map<string, Set<string>>(); // tag \u2192 offer IDs\n private offersByAnswerer = new Map<string, Set<string>>(); // answerer username \u2192 offer IDs\n\n // Auto-increment counter for ICE candidates\n private iceCandidateIdCounter = 0;\n\n constructor(masterEncryptionKey: string) {\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n const created: Offer[] = [];\n const now = Date.now();\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n const offer: Offer = {\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n };\n\n // Store offer\n this.offers.set(id, offer);\n\n // Update username index\n if (!this.offersByUsername.has(request.username)) {\n this.offersByUsername.set(request.username, new Set());\n }\n this.offersByUsername.get(request.username)!.add(id);\n\n // Update tag indexes\n for (const tag of request.tags) {\n if (!this.offersByTag.has(tag)) {\n this.offersByTag.set(tag, new Set());\n }\n this.offersByTag.get(tag)!.add(id);\n }\n\n created.push(offer);\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByUsername.get(username);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => b.lastSeen - a.lastSeen);\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const offer = this.offers.get(offerId);\n if (!offer || offer.expiresAt <= Date.now()) {\n return null;\n }\n return offer;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const offer = this.offers.get(offerId);\n if (!offer || offer.username !== ownerUsername) {\n return false;\n }\n\n this.removeOfferFromIndexes(offer);\n this.offers.delete(offerId);\n this.iceCandidates.delete(offerId);\n\n return true;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n let count = 0;\n\n for (const [id, offer] of this.offers) {\n if (offer.expiresAt < now) {\n this.removeOfferFromIndexes(offer);\n this.offers.delete(id);\n this.iceCandidates.delete(id);\n count++;\n }\n }\n\n return count;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n // Update offer with answer\n const now = Date.now();\n offer.answererUsername = answererUsername;\n offer.answerSdp = answerSdp;\n offer.answeredAt = now;\n\n // Update answerer index\n if (!this.offersByAnswerer.has(answererUsername)) {\n this.offersByAnswerer.set(answererUsername, new Set());\n }\n this.offersByAnswerer.get(answererUsername)!.add(offerId);\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByUsername.get(offererUsername);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.answererUsername && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const now = Date.now();\n const offerIds = this.offersByAnswerer.get(answererUsername);\n if (!offerIds) return [];\n\n const offers: Offer[] = [];\n for (const id of offerIds) {\n const offer = this.offers.get(id);\n if (offer && offer.expiresAt > now) {\n offers.push(offer);\n }\n }\n\n return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n const now = Date.now();\n const matchingOfferIds = new Set<string>();\n\n // Find all offers matching any tag (OR logic)\n for (const tag of tags) {\n const offerIds = this.offersByTag.get(tag);\n if (offerIds) {\n for (const id of offerIds) {\n matchingOfferIds.add(id);\n }\n }\n }\n\n // Filter and collect matching offers\n const offers: Offer[] = [];\n for (const id of matchingOfferIds) {\n const offer = this.offers.get(id);\n if (\n offer &&\n offer.expiresAt > now &&\n !offer.answererUsername &&\n (!excludeUsername || offer.username !== excludeUsername)\n ) {\n offers.push(offer);\n }\n }\n\n // Sort by created_at descending and apply pagination\n offers.sort((a, b) => b.createdAt - a.createdAt);\n return offers.slice(offset, offset + limit);\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n const now = Date.now();\n const matchingOffers: Offer[] = [];\n\n // Find all offers matching any tag (OR logic)\n const matchingOfferIds = new Set<string>();\n for (const tag of tags) {\n const offerIds = this.offersByTag.get(tag);\n if (offerIds) {\n for (const id of offerIds) {\n matchingOfferIds.add(id);\n }\n }\n }\n\n // Collect matching offers\n for (const id of matchingOfferIds) {\n const offer = this.offers.get(id);\n if (\n offer &&\n offer.expiresAt > now &&\n !offer.answererUsername &&\n (!excludeUsername || offer.username !== excludeUsername)\n ) {\n matchingOffers.push(offer);\n }\n }\n\n if (matchingOffers.length === 0) return null;\n\n // Return random offer\n const randomIndex = Math.floor(Math.random() * matchingOffers.length);\n return matchingOffers[randomIndex];\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n const baseTimestamp = Date.now();\n\n if (!this.iceCandidates.has(offerId)) {\n this.iceCandidates.set(offerId, []);\n }\n\n const candidateList = this.iceCandidates.get(offerId)!;\n\n for (let i = 0; i < candidates.length; i++) {\n const candidate: IceCandidate = {\n id: ++this.iceCandidateIdCounter,\n offerId,\n username,\n role,\n candidate: candidates[i],\n createdAt: baseTimestamp + i,\n };\n candidateList.push(candidate);\n }\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n const candidates = this.iceCandidates.get(offerId) || [];\n\n return candidates\n .filter(c => c.role === targetRole && (since === undefined || c.createdAt > since))\n .sort((a, b) => a.createdAt - b.createdAt);\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return result;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n for (const offerId of offerIds) {\n const offer = this.offers.get(offerId);\n if (!offer) continue;\n\n const candidates = this.iceCandidates.get(offerId) || [];\n\n // Determine which role's candidates to return\n // If user is offerer, return answerer candidates and vice versa\n const isOfferer = offer.username === username;\n const isAnswerer = offer.answererUsername === username;\n\n if (!isOfferer && !isAnswerer) continue;\n\n const targetRole = isOfferer ? 'answerer' : 'offerer';\n\n const filteredCandidates = candidates\n .filter(c => c.role === targetRole && (since === undefined || c.createdAt > since))\n .sort((a, b) => a.createdAt - b.createdAt);\n\n if (filteredCandidates.length > 0) {\n result.set(offerId, filteredCandidates);\n }\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n if (this.credentials.has(request.name)) {\n throw new Error('Username already taken');\n }\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n if (!this.credentials.has(name)) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n\n // Encrypt secret before storing\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n const credential: Credential = {\n name: name!,\n secret: encryptedSecret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n\n this.credentials.set(name!, credential);\n\n // Return plaintext secret to user\n return {\n ...credential,\n secret, // Return plaintext, not encrypted\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const credential = this.credentials.get(name);\n if (!credential || credential.expiresAt <= Date.now()) {\n return null;\n }\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(credential.secret, this.masterEncryptionKey);\n\n return {\n ...credential,\n secret: decryptedSecret,\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n const credential = this.credentials.get(name);\n if (credential) {\n credential.lastUsed = lastUsed;\n credential.expiresAt = expiresAt;\n }\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n let count = 0;\n for (const [name, credential] of this.credentials) {\n if (credential.expiresAt < now) {\n this.credentials.delete(name);\n count++;\n }\n }\n return count;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const existing = this.rateLimits.get(identifier);\n\n if (!existing || existing.resetTime < now) {\n // New window or expired - reset count\n this.rateLimits.set(identifier, {\n count: 1,\n resetTime: now + windowMs,\n });\n return true;\n }\n\n // Increment count in existing window\n existing.count++;\n return existing.count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n let count = 0;\n for (const [identifier, rateLimit] of this.rateLimits) {\n if (rateLimit.resetTime < now) {\n this.rateLimits.delete(identifier);\n count++;\n }\n }\n return count;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n if (this.nonces.has(nonceKey)) {\n return false; // Nonce already used - replay attack\n }\n\n this.nonces.set(nonceKey, { expiresAt });\n return true; // Nonce is new - allowed\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n let count = 0;\n for (const [key, entry] of this.nonces) {\n if (entry.expiresAt < now) {\n this.nonces.delete(key);\n count++;\n }\n }\n return count;\n }\n\n async close(): Promise<void> {\n // Clear all data\n this.credentials.clear();\n this.offers.clear();\n this.iceCandidates.clear();\n this.rateLimits.clear();\n this.nonces.clear();\n this.offersByUsername.clear();\n this.offersByTag.clear();\n this.offersByAnswerer.clear();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n return this.offers.size;\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const offerIds = this.offersByUsername.get(username);\n return offerIds ? offerIds.size : 0;\n }\n\n async getCredentialCount(): Promise<number> {\n return this.credentials.size;\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const candidates = this.iceCandidates.get(offerId);\n return candidates ? candidates.length : 0;\n }\n\n // ===== Helper Methods =====\n\n private removeOfferFromIndexes(offer: Offer): void {\n // Remove from username index\n const usernameOffers = this.offersByUsername.get(offer.username);\n if (usernameOffers) {\n usernameOffers.delete(offer.id);\n if (usernameOffers.size === 0) {\n this.offersByUsername.delete(offer.username);\n }\n }\n\n // Remove from tag indexes\n for (const tag of offer.tags) {\n const tagOffers = this.offersByTag.get(tag);\n if (tagOffers) {\n tagOffers.delete(offer.id);\n if (tagOffers.size === 0) {\n this.offersByTag.delete(tag);\n }\n }\n }\n\n // Remove from answerer index\n if (offer.answererUsername) {\n const answererOffers = this.offersByAnswerer.get(offer.answererUsername);\n if (answererOffers) {\n answererOffers.delete(offer.id);\n if (answererOffers.size === 0) {\n this.offersByAnswerer.delete(offer.answererUsername);\n }\n }\n }\n }\n}\n", "import Database from 'better-sqlite3';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000; // 365 days\n\n/**\n * SQLite storage adapter for rondevu signaling system\n * Supports both file-based and in-memory databases\n */\nexport class SQLiteStorage implements Storage {\n private db: Database.Database;\n private masterEncryptionKey: string;\n\n /**\n * Creates a new SQLite storage instance\n * @param path Path to SQLite database file, or ':memory:' for in-memory database\n * @param masterEncryptionKey 64-char hex string for encrypting secrets (32 bytes)\n */\n constructor(path: string = ':memory:', masterEncryptionKey: string) {\n this.db = new Database(path);\n this.masterEncryptionKey = masterEncryptionKey;\n this.initializeDatabase();\n }\n\n /**\n * Initializes database schema with tags-based offers\n */\n private initializeDatabase(): void {\n this.db.exec(`\n -- WebRTC signaling offers with tags\n CREATE TABLE IF NOT EXISTS offers (\n id TEXT PRIMARY KEY,\n username TEXT NOT NULL,\n tags TEXT NOT NULL,\n sdp TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n answerer_username TEXT,\n answer_sdp TEXT,\n answered_at INTEGER\n );\n\n CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username);\n CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);\n CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);\n CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username);\n\n -- ICE candidates table\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n offer_id TEXT NOT NULL,\n username TEXT NOT NULL,\n role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),\n candidate TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE\n );\n\n CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id);\n CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username);\n CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at);\n\n -- Credentials table (replaces usernames with simpler name + secret auth)\n CREATE TABLE IF NOT EXISTS credentials (\n name TEXT PRIMARY KEY,\n secret TEXT NOT NULL UNIQUE,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n last_used INTEGER NOT NULL,\n CHECK(length(name) >= 3 AND length(name) <= 32)\n );\n\n CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at);\n CREATE INDEX IF NOT EXISTS idx_credentials_secret ON credentials(secret);\n\n -- Rate limits table (for distributed rate limiting)\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier TEXT PRIMARY KEY,\n count INTEGER NOT NULL,\n reset_time INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_rate_limits_reset ON rate_limits(reset_time);\n\n -- Nonces table (for replay attack prevention)\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key TEXT PRIMARY KEY,\n expires_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_nonces_expires ON nonces(expires_at);\n `);\n\n // Enable foreign keys\n this.db.pragma('foreign_keys = ON');\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n const created: Offer[] = [];\n\n // Generate hash-based IDs for all offers first\n const offersWithIds = await Promise.all(\n offers.map(async (offer) => ({\n ...offer,\n id: offer.id || await generateOfferHash(offer.sdp),\n }))\n );\n\n // Use transaction for atomic creation\n const transaction = this.db.transaction((offersWithIds: (CreateOfferRequest & { id: string })[]) => {\n const offerStmt = this.db.prepare(`\n INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n for (const offer of offersWithIds) {\n const now = Date.now();\n\n // Insert offer with JSON-serialized tags\n offerStmt.run(\n offer.id,\n offer.username,\n JSON.stringify(offer.tags),\n offer.sdp,\n now,\n offer.expiresAt,\n now\n );\n\n created.push({\n id: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: now,\n expiresAt: offer.expiresAt,\n lastSeen: now,\n });\n }\n });\n\n transaction(offersWithIds);\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE username = ? AND expires_at > ?\n ORDER BY last_seen DESC\n `);\n\n const rows = stmt.all(username, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE id = ? AND expires_at > ?\n `);\n\n const row = stmt.get(offerId, Date.now()) as any;\n\n if (!row) {\n return null;\n }\n\n return this.rowToOffer(row);\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const stmt = this.db.prepare(`\n DELETE FROM offers\n WHERE id = ? AND username = ?\n `);\n\n const result = stmt.run(offerId, ownerUsername);\n return result.changes > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM offers WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n // Check if offer exists and is not expired\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return {\n success: false,\n error: 'Offer not found or expired'\n };\n }\n\n // Check if offer already has an answerer\n if (offer.answererUsername) {\n return {\n success: false,\n error: 'Offer already answered'\n };\n }\n\n // Update offer with answer\n const stmt = this.db.prepare(`\n UPDATE offers\n SET answerer_username = ?, answer_sdp = ?, answered_at = ?\n WHERE id = ? AND answerer_username IS NULL\n `);\n\n const result = stmt.run(answererUsername, answerSdp, Date.now(), offerId);\n\n if (result.changes === 0) {\n return {\n success: false,\n error: 'Offer already answered (race condition)'\n };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?\n ORDER BY answered_at DESC\n `);\n\n const rows = stmt.all(offererUsername, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const stmt = this.db.prepare(`\n SELECT * FROM offers\n WHERE answerer_username = ? AND expires_at > ?\n ORDER BY answered_at DESC\n `);\n\n const rows = stmt.all(answererUsername, Date.now()) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) {\n return [];\n }\n\n // Build query with JSON tag matching (OR logic)\n // SQLite: Use json_each() to expand tags array and check if any tag matches\n const placeholders = tags.map(() => '?').join(',');\n\n let query = `\n SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t\n WHERE t.value IN (${placeholders})\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n\n const params: any[] = [...tags, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';\n params.push(limit, offset);\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) {\n return null;\n }\n\n // Build query with JSON tag matching (OR logic)\n const placeholders = tags.map(() => '?').join(',');\n\n let query = `\n SELECT DISTINCT o.* FROM offers o, json_each(o.tags) as t\n WHERE t.value IN (${placeholders})\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n\n const params: any[] = [...tags, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RANDOM() LIMIT 1';\n\n const stmt = this.db.prepare(query);\n const row = stmt.get(...params) as any;\n\n return row ? this.rowToOffer(row) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n const stmt = this.db.prepare(`\n INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n const baseTimestamp = Date.now();\n const transaction = this.db.transaction((candidates: any[]) => {\n for (let i = 0; i < candidates.length; i++) {\n stmt.run(\n offerId,\n username,\n role,\n JSON.stringify(candidates[i]),\n baseTimestamp + i\n );\n }\n });\n\n transaction(candidates);\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `\n SELECT * FROM ice_candidates\n WHERE offer_id = ? AND role = ?\n `;\n\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n\n return rows.map(row => ({\n id: row.id,\n offerId: row.offer_id,\n username: row.username,\n role: row.role,\n candidate: JSON.parse(row.candidate),\n createdAt: row.created_at,\n }));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n // Return empty map if no offer IDs provided\n if (offerIds.length === 0) {\n return result;\n }\n\n // Validate array contains only strings\n if (!Array.isArray(offerIds) || !offerIds.every(id => typeof id === 'string')) {\n throw new Error('Invalid offer IDs: must be array of strings');\n }\n\n // Prevent DoS attacks from extremely large IN clauses\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n // Build query that fetches candidates from the OTHER peer only\n // For each offer, determine if user is offerer or answerer and get opposite role\n const placeholders = offerIds.map(() => '?').join(',');\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id IN (${placeholders})\n AND (\n (o.username = ? AND ic.role = 'answerer')\n OR (o.answerer_username = ? AND ic.role = 'offerer')\n )\n `;\n\n const params: any[] = [...offerIds, username, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const stmt = this.db.prepare(query);\n const rows = stmt.all(...params) as any[];\n\n // Group candidates by offer_id\n for (const row of rows) {\n const candidate: IceCandidate = {\n id: row.id,\n offerId: row.offer_id,\n username: row.username,\n role: row.role,\n candidate: JSON.parse(row.candidate),\n createdAt: row.created_at,\n };\n\n if (!result.has(row.offer_id)) {\n result.set(row.offer_id, []);\n }\n result.get(row.offer_id)!.push(candidate);\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n // User requested specific username - check if available\n const existing = this.db.prepare(`\n SELECT name FROM credentials WHERE name = ?\n `).get(request.name);\n\n if (existing) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n // Generate random name - retry until unique\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const existing = this.db.prepare(`\n SELECT name FROM credentials WHERE name = ?\n `).get(name);\n\n if (!existing) {\n break;\n }\n\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n\n // Encrypt secret before storing (AES-256-GCM)\n const { encryptSecret } = await import('../crypto.ts');\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n // Insert credential with encrypted secret\n const stmt = this.db.prepare(`\n INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n stmt.run(name!, encryptedSecret, now, expiresAt, now);\n\n // Return plaintext secret to user (only time they'll see it)\n return {\n name: name!,\n secret, // Return plaintext secret, not encrypted\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const stmt = this.db.prepare(`\n SELECT * FROM credentials\n WHERE name = ? AND expires_at > ?\n `);\n\n const row = stmt.get(name, Date.now()) as any;\n\n if (!row) {\n return null;\n }\n\n // Decrypt secret before returning\n // If decryption fails (e.g., master key rotated), treat as credential not found\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(row.secret, this.masterEncryptionKey);\n\n return {\n name: row.name,\n secret: decryptedSecret, // Return decrypted secret\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n lastUsed: row.last_used,\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null; // Treat as credential not found (fail-safe behavior)\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n const stmt = this.db.prepare(`\n UPDATE credentials\n SET last_used = ?, expires_at = ?\n WHERE name = ?\n `);\n\n stmt.run(lastUsed, expiresAt, name);\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM credentials WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Atomic UPSERT: Insert or increment count, reset if expired\n // This prevents TOCTOU race conditions by doing check+increment in single operation\n const result = this.db.prepare(`\n INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES (?, 1, ?)\n ON CONFLICT(identifier) DO UPDATE SET\n count = CASE\n WHEN reset_time < ? THEN 1\n ELSE count + 1\n END,\n reset_time = CASE\n WHEN reset_time < ? THEN ?\n ELSE reset_time\n END\n RETURNING count\n `).get(identifier, resetTime, now, now, resetTime) as { count: number };\n\n // Check if limit exceeded\n return result.count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM rate_limits WHERE reset_time < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n // Atomic INSERT - if nonce already exists, this will fail with UNIQUE constraint\n // This prevents replay attacks by ensuring each nonce is only used once\n const stmt = this.db.prepare(`\n INSERT INTO nonces (nonce_key, expires_at)\n VALUES (?, ?)\n `);\n stmt.run(nonceKey, expiresAt);\n return true; // Nonce is new, request allowed\n } catch (error: any) {\n // SQLITE_CONSTRAINT error code for UNIQUE constraint violation\n if (error?.code === 'SQLITE_CONSTRAINT') {\n return false; // Nonce already used, replay attack detected\n }\n throw error; // Other errors should propagate\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const stmt = this.db.prepare('DELETE FROM nonces WHERE expires_at < ?');\n const result = stmt.run(now);\n return result.changes;\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM offers').get() as { count: number };\n return result.count;\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM offers WHERE username = ?').get(username) as { count: number };\n return result.count;\n }\n\n async getCredentialCount(): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM credentials').get() as { count: number };\n return result.count;\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const result = this.db.prepare('SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?').get(offerId) as { count: number };\n return result.count;\n }\n\n // ===== Helper Methods =====\n\n /**\n * Helper method to convert database row to Offer object\n */\n private rowToOffer(row: any): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: JSON.parse(row.tags),\n sdp: row.sdp,\n createdAt: row.created_at,\n expiresAt: row.expires_at,\n lastSeen: row.last_seen,\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at || undefined,\n };\n }\n}\n", "import mysql, { Pool, PoolConnection, RowDataPacket, ResultSetHeader } from 'mysql2/promise';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\n/**\n * MySQL storage adapter for rondevu signaling system\n * Uses connection pooling for efficient resource management\n */\nexport class MySQLStorage implements Storage {\n private pool: Pool;\n private masterEncryptionKey: string;\n\n private constructor(pool: Pool, masterEncryptionKey: string) {\n this.pool = pool;\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n /**\n * Creates a new MySQL storage instance with connection pooling\n * @param connectionString MySQL connection URL\n * @param masterEncryptionKey 64-char hex string for encrypting secrets\n * @param poolSize Maximum number of connections in the pool\n */\n static async create(\n connectionString: string,\n masterEncryptionKey: string,\n poolSize: number = 10\n ): Promise<MySQLStorage> {\n const pool = mysql.createPool({\n uri: connectionString,\n waitForConnections: true,\n connectionLimit: poolSize,\n queueLimit: 0,\n enableKeepAlive: true,\n keepAliveInitialDelay: 10000,\n });\n\n const storage = new MySQLStorage(pool, masterEncryptionKey);\n await storage.initializeDatabase();\n return storage;\n }\n\n private async initializeDatabase(): Promise<void> {\n const conn = await this.pool.getConnection();\n try {\n await conn.query(`\n CREATE TABLE IF NOT EXISTS offers (\n id VARCHAR(64) PRIMARY KEY,\n username VARCHAR(32) NOT NULL,\n tags JSON NOT NULL,\n sdp MEDIUMTEXT NOT NULL,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_seen BIGINT NOT NULL,\n answerer_username VARCHAR(32),\n answer_sdp MEDIUMTEXT,\n answered_at BIGINT,\n INDEX idx_offers_username (username),\n INDEX idx_offers_expires (expires_at),\n INDEX idx_offers_last_seen (last_seen),\n INDEX idx_offers_answerer (answerer_username)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id BIGINT AUTO_INCREMENT PRIMARY KEY,\n offer_id VARCHAR(64) NOT NULL,\n username VARCHAR(32) NOT NULL,\n role ENUM('offerer', 'answerer') NOT NULL,\n candidate JSON NOT NULL,\n created_at BIGINT NOT NULL,\n INDEX idx_ice_offer (offer_id),\n INDEX idx_ice_username (username),\n INDEX idx_ice_created (created_at),\n FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS credentials (\n name VARCHAR(32) PRIMARY KEY,\n secret VARCHAR(512) NOT NULL UNIQUE,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_used BIGINT NOT NULL,\n INDEX idx_credentials_expires (expires_at)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier VARCHAR(255) PRIMARY KEY,\n count INT NOT NULL,\n reset_time BIGINT NOT NULL,\n INDEX idx_rate_limits_reset (reset_time)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n\n await conn.query(`\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key VARCHAR(255) PRIMARY KEY,\n expires_at BIGINT NOT NULL,\n INDEX idx_nonces_expires (expires_at)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci\n `);\n } finally {\n conn.release();\n }\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n if (offers.length === 0) return [];\n\n const created: Offer[] = [];\n const now = Date.now();\n\n const conn = await this.pool.getConnection();\n try {\n await conn.beginTransaction();\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n await conn.query(\n `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]\n );\n\n created.push({\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n });\n }\n\n await conn.commit();\n } catch (error) {\n await conn.rollback();\n throw error;\n } finally {\n conn.release();\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers WHERE username = ? AND expires_at > ? ORDER BY last_seen DESC`,\n [username, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers WHERE id = ? AND expires_at > ?`,\n [offerId, Date.now()]\n );\n return rows.length > 0 ? this.rowToOffer(rows[0]) : null;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM offers WHERE id = ? AND username = ?`,\n [offerId, ownerUsername]\n );\n return result.affectedRows > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM offers WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n const [result] = await this.pool.query<ResultSetHeader>(\n `UPDATE offers SET answerer_username = ?, answer_sdp = ?, answered_at = ?\n WHERE id = ? AND answerer_username IS NULL`,\n [answererUsername, answerSdp, Date.now(), offerId]\n );\n\n if (result.affectedRows === 0) {\n return { success: false, error: 'Offer already answered (race condition)' };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers\n WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?\n ORDER BY answered_at DESC`,\n [offererUsername, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM offers\n WHERE answerer_username = ? AND expires_at > ?\n ORDER BY answered_at DESC`,\n [answererUsername, Date.now()]\n );\n return rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n // Use JSON_OVERLAPS for efficient tag matching (MySQL 8.0.17+)\n // Falls back to JSON_CONTAINS for each tag with OR logic\n const tagArray = JSON.stringify(tags);\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE JSON_OVERLAPS(o.tags, ?)\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tagArray, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';\n params.push(limit, offset);\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n const tagArray = JSON.stringify(tags);\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE JSON_OVERLAPS(o.tags, ?)\n AND o.expires_at > ?\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tagArray, Date.now()];\n\n if (excludeUsername) {\n query += ' AND o.username != ?';\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RAND() LIMIT 1';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.length > 0 ? this.rowToOffer(rows[0]) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n if (candidates.length === 0) return 0;\n\n const baseTimestamp = Date.now();\n const values = candidates.map((c, i) => [\n offerId,\n username,\n role,\n JSON.stringify(c),\n baseTimestamp + i,\n ]);\n\n await this.pool.query(\n `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES ?`,\n [values]\n );\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `SELECT * FROM ice_candidates WHERE offer_id = ? AND role = ?`;\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n return rows.map(row => this.rowToIceCandidate(row));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const result = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return result;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n const placeholders = offerIds.map(() => '?').join(',');\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id IN (${placeholders})\n AND (\n (o.username = ? AND ic.role = 'answerer')\n OR (o.answerer_username = ? AND ic.role = 'offerer')\n )\n `;\n const params: any[] = [...offerIds, username, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > ?';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const [rows] = await this.pool.query<RowDataPacket[]>(query, params);\n\n for (const row of rows) {\n const candidate = this.rowToIceCandidate(row);\n if (!result.has(row.offer_id)) {\n result.set(row.offer_id, []);\n }\n result.get(row.offer_id)!.push(candidate);\n }\n\n return result;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n const [existing] = await this.pool.query<RowDataPacket[]>(\n `SELECT name FROM credentials WHERE name = ?`,\n [request.name]\n );\n\n if (existing.length > 0) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const [existing] = await this.pool.query<RowDataPacket[]>(\n `SELECT name FROM credentials WHERE name = ?`,\n [name]\n );\n\n if (existing.length === 0) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n await this.pool.query(\n `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES (?, ?, ?, ?, ?)`,\n [name!, encryptedSecret, now, expiresAt, now]\n );\n\n return {\n name: name!,\n secret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT * FROM credentials WHERE name = ? AND expires_at > ?`,\n [name, Date.now()]\n );\n\n if (rows.length === 0) return null;\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(rows[0].secret, this.masterEncryptionKey);\n\n return {\n name: rows[0].name,\n secret: decryptedSecret,\n createdAt: Number(rows[0].created_at),\n expiresAt: Number(rows[0].expires_at),\n lastUsed: Number(rows[0].last_used),\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n await this.pool.query(\n `UPDATE credentials SET last_used = ?, expires_at = ? WHERE name = ?`,\n [lastUsed, expiresAt, name]\n );\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM credentials WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Use INSERT ... ON DUPLICATE KEY UPDATE for atomic upsert\n await this.pool.query(\n `INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES (?, 1, ?)\n ON DUPLICATE KEY UPDATE\n count = IF(reset_time < ?, 1, count + 1),\n reset_time = IF(reset_time < ?, ?, reset_time)`,\n [identifier, resetTime, now, now, resetTime]\n );\n\n // Get current count\n const [rows] = await this.pool.query<RowDataPacket[]>(\n `SELECT count FROM rate_limits WHERE identifier = ?`,\n [identifier]\n );\n\n return rows.length > 0 && rows[0].count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM rate_limits WHERE reset_time < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n await this.pool.query(\n `INSERT INTO nonces (nonce_key, expires_at) VALUES (?, ?)`,\n [nonceKey, expiresAt]\n );\n return true;\n } catch (error: any) {\n // MySQL duplicate key error code\n if (error.code === 'ER_DUP_ENTRY') {\n return false;\n }\n throw error;\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const [result] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM nonces WHERE expires_at < ?`,\n [now]\n );\n return result.affectedRows;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM offers');\n return Number(rows[0].count);\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n 'SELECT COUNT(*) as count FROM offers WHERE username = ?',\n [username]\n );\n return Number(rows[0].count);\n }\n\n async getCredentialCount(): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM credentials');\n return Number(rows[0].count);\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const [rows] = await this.pool.query<RowDataPacket[]>(\n 'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?',\n [offerId]\n );\n return Number(rows[0].count);\n }\n\n // ===== Helper Methods =====\n\n private rowToOffer(row: RowDataPacket): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags,\n sdp: row.sdp,\n createdAt: Number(row.created_at),\n expiresAt: Number(row.expires_at),\n lastSeen: Number(row.last_seen),\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at ? Number(row.answered_at) : undefined,\n };\n }\n\n private rowToIceCandidate(row: RowDataPacket): IceCandidate {\n return {\n id: Number(row.id),\n offerId: row.offer_id,\n username: row.username,\n role: row.role as 'offerer' | 'answerer',\n candidate: typeof row.candidate === 'string' ? JSON.parse(row.candidate) : row.candidate,\n createdAt: Number(row.created_at),\n };\n }\n}\n", "import { Pool, QueryResult } from 'pg';\nimport {\n Storage,\n Offer,\n IceCandidate,\n CreateOfferRequest,\n Credential,\n GenerateCredentialsRequest,\n} from './types.ts';\nimport { generateOfferHash } from './hash-id.ts';\n\nconst YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;\n\n/**\n * PostgreSQL storage adapter for rondevu signaling system\n * Uses connection pooling for efficient resource management\n */\nexport class PostgreSQLStorage implements Storage {\n private pool: Pool;\n private masterEncryptionKey: string;\n\n private constructor(pool: Pool, masterEncryptionKey: string) {\n this.pool = pool;\n this.masterEncryptionKey = masterEncryptionKey;\n }\n\n /**\n * Creates a new PostgreSQL storage instance with connection pooling\n * @param connectionString PostgreSQL connection URL\n * @param masterEncryptionKey 64-char hex string for encrypting secrets\n * @param poolSize Maximum number of connections in the pool\n */\n static async create(\n connectionString: string,\n masterEncryptionKey: string,\n poolSize: number = 10\n ): Promise<PostgreSQLStorage> {\n const pool = new Pool({\n connectionString,\n max: poolSize,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: 5000,\n });\n\n const storage = new PostgreSQLStorage(pool, masterEncryptionKey);\n await storage.initializeDatabase();\n return storage;\n }\n\n private async initializeDatabase(): Promise<void> {\n const client = await this.pool.connect();\n try {\n await client.query(`\n CREATE TABLE IF NOT EXISTS offers (\n id VARCHAR(64) PRIMARY KEY,\n username VARCHAR(32) NOT NULL,\n tags JSONB NOT NULL,\n sdp TEXT NOT NULL,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_seen BIGINT NOT NULL,\n answerer_username VARCHAR(32),\n answer_sdp TEXT,\n answered_at BIGINT\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_offers_tags ON offers USING GIN(tags)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS ice_candidates (\n id BIGSERIAL PRIMARY KEY,\n offer_id VARCHAR(64) NOT NULL REFERENCES offers(id) ON DELETE CASCADE,\n username VARCHAR(32) NOT NULL,\n role VARCHAR(8) NOT NULL CHECK (role IN ('offerer', 'answerer')),\n candidate JSONB NOT NULL,\n created_at BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username)`);\n await client.query(`CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS credentials (\n name VARCHAR(32) PRIMARY KEY,\n secret VARCHAR(512) NOT NULL UNIQUE,\n created_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL,\n last_used BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_credentials_expires ON credentials(expires_at)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n identifier VARCHAR(255) PRIMARY KEY,\n count INT NOT NULL,\n reset_time BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_rate_limits_reset ON rate_limits(reset_time)`);\n\n await client.query(`\n CREATE TABLE IF NOT EXISTS nonces (\n nonce_key VARCHAR(255) PRIMARY KEY,\n expires_at BIGINT NOT NULL\n )\n `);\n\n await client.query(`CREATE INDEX IF NOT EXISTS idx_nonces_expires ON nonces(expires_at)`);\n } finally {\n client.release();\n }\n }\n\n // ===== Offer Management =====\n\n async createOffers(offers: CreateOfferRequest[]): Promise<Offer[]> {\n if (offers.length === 0) return [];\n\n const created: Offer[] = [];\n const now = Date.now();\n\n const client = await this.pool.connect();\n try {\n await client.query('BEGIN');\n\n for (const request of offers) {\n const id = request.id || await generateOfferHash(request.sdp);\n\n await client.query(\n `INSERT INTO offers (id, username, tags, sdp, created_at, expires_at, last_seen)\n VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n [id, request.username, JSON.stringify(request.tags), request.sdp, now, request.expiresAt, now]\n );\n\n created.push({\n id,\n username: request.username,\n tags: request.tags,\n sdp: request.sdp,\n createdAt: now,\n expiresAt: request.expiresAt,\n lastSeen: now,\n });\n }\n\n await client.query('COMMIT');\n } catch (error) {\n await client.query('ROLLBACK');\n throw error;\n } finally {\n client.release();\n }\n\n return created;\n }\n\n async getOffersByUsername(username: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers WHERE username = $1 AND expires_at > $2 ORDER BY last_seen DESC`,\n [username, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getOfferById(offerId: string): Promise<Offer | null> {\n const result = await this.pool.query(\n `SELECT * FROM offers WHERE id = $1 AND expires_at > $2`,\n [offerId, Date.now()]\n );\n return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;\n }\n\n async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {\n const result = await this.pool.query(\n `DELETE FROM offers WHERE id = $1 AND username = $2`,\n [offerId, ownerUsername]\n );\n return (result.rowCount ?? 0) > 0;\n }\n\n async deleteExpiredOffers(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM offers WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n async answerOffer(\n offerId: string,\n answererUsername: string,\n answerSdp: string\n ): Promise<{ success: boolean; error?: string }> {\n const offer = await this.getOfferById(offerId);\n\n if (!offer) {\n return { success: false, error: 'Offer not found or expired' };\n }\n\n if (offer.answererUsername) {\n return { success: false, error: 'Offer already answered' };\n }\n\n const result = await this.pool.query(\n `UPDATE offers SET answerer_username = $1, answer_sdp = $2, answered_at = $3\n WHERE id = $4 AND answerer_username IS NULL`,\n [answererUsername, answerSdp, Date.now(), offerId]\n );\n\n if ((result.rowCount ?? 0) === 0) {\n return { success: false, error: 'Offer already answered (race condition)' };\n }\n\n return { success: true };\n }\n\n async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers\n WHERE username = $1 AND answerer_username IS NOT NULL AND expires_at > $2\n ORDER BY answered_at DESC`,\n [offererUsername, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getOffersAnsweredBy(answererUsername: string): Promise<Offer[]> {\n const result = await this.pool.query(\n `SELECT * FROM offers\n WHERE answerer_username = $1 AND expires_at > $2\n ORDER BY answered_at DESC`,\n [answererUsername, Date.now()]\n );\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n // ===== Discovery =====\n\n async discoverOffers(\n tags: string[],\n excludeUsername: string | null,\n limit: number,\n offset: number\n ): Promise<Offer[]> {\n if (tags.length === 0) return [];\n\n // Use PostgreSQL's ?| operator for JSONB array overlap\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE o.tags ?| $1\n AND o.expires_at > $2\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tags, Date.now()];\n let paramIndex = 3;\n\n if (excludeUsername) {\n query += ` AND o.username != $${paramIndex}`;\n params.push(excludeUsername);\n paramIndex++;\n }\n\n query += ` ORDER BY o.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;\n params.push(limit, offset);\n\n const result = await this.pool.query(query, params);\n return result.rows.map(row => this.rowToOffer(row));\n }\n\n async getRandomOffer(\n tags: string[],\n excludeUsername: string | null\n ): Promise<Offer | null> {\n if (tags.length === 0) return null;\n\n let query = `\n SELECT DISTINCT o.* FROM offers o\n WHERE o.tags ?| $1\n AND o.expires_at > $2\n AND o.answerer_username IS NULL\n `;\n const params: any[] = [tags, Date.now()];\n let paramIndex = 3;\n\n if (excludeUsername) {\n query += ` AND o.username != $${paramIndex}`;\n params.push(excludeUsername);\n }\n\n query += ' ORDER BY RANDOM() LIMIT 1';\n\n const result = await this.pool.query(query, params);\n return result.rows.length > 0 ? this.rowToOffer(result.rows[0]) : null;\n }\n\n // ===== ICE Candidate Management =====\n\n async addIceCandidates(\n offerId: string,\n username: string,\n role: 'offerer' | 'answerer',\n candidates: any[]\n ): Promise<number> {\n if (candidates.length === 0) return 0;\n\n const baseTimestamp = Date.now();\n const client = await this.pool.connect();\n\n try {\n await client.query('BEGIN');\n\n for (let i = 0; i < candidates.length; i++) {\n await client.query(\n `INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)\n VALUES ($1, $2, $3, $4, $5)`,\n [offerId, username, role, JSON.stringify(candidates[i]), baseTimestamp + i]\n );\n }\n\n await client.query('COMMIT');\n } catch (error) {\n await client.query('ROLLBACK');\n throw error;\n } finally {\n client.release();\n }\n\n return candidates.length;\n }\n\n async getIceCandidates(\n offerId: string,\n targetRole: 'offerer' | 'answerer',\n since?: number\n ): Promise<IceCandidate[]> {\n let query = `SELECT * FROM ice_candidates WHERE offer_id = $1 AND role = $2`;\n const params: any[] = [offerId, targetRole];\n\n if (since !== undefined) {\n query += ' AND created_at > $3';\n params.push(since);\n }\n\n query += ' ORDER BY created_at ASC';\n\n const result = await this.pool.query(query, params);\n return result.rows.map(row => this.rowToIceCandidate(row));\n }\n\n async getIceCandidatesForMultipleOffers(\n offerIds: string[],\n username: string,\n since?: number\n ): Promise<Map<string, IceCandidate[]>> {\n const resultMap = new Map<string, IceCandidate[]>();\n\n if (offerIds.length === 0) return resultMap;\n if (offerIds.length > 1000) {\n throw new Error('Too many offer IDs (max 1000)');\n }\n\n let query = `\n SELECT ic.*, o.username as offer_username\n FROM ice_candidates ic\n INNER JOIN offers o ON o.id = ic.offer_id\n WHERE ic.offer_id = ANY($1)\n AND (\n (o.username = $2 AND ic.role = 'answerer')\n OR (o.answerer_username = $2 AND ic.role = 'offerer')\n )\n `;\n const params: any[] = [offerIds, username];\n\n if (since !== undefined) {\n query += ' AND ic.created_at > $3';\n params.push(since);\n }\n\n query += ' ORDER BY ic.created_at ASC';\n\n const result = await this.pool.query(query, params);\n\n for (const row of result.rows) {\n const candidate = this.rowToIceCandidate(row);\n if (!resultMap.has(row.offer_id)) {\n resultMap.set(row.offer_id, []);\n }\n resultMap.get(row.offer_id)!.push(candidate);\n }\n\n return resultMap;\n }\n\n // ===== Credential Management =====\n\n async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {\n const now = Date.now();\n const expiresAt = request.expiresAt || (now + YEAR_IN_MS);\n\n const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');\n\n let name: string;\n\n if (request.name) {\n const existing = await this.pool.query(\n `SELECT name FROM credentials WHERE name = $1`,\n [request.name]\n );\n\n if (existing.rows.length > 0) {\n throw new Error('Username already taken');\n }\n\n name = request.name;\n } else {\n let attempts = 0;\n const maxAttempts = 100;\n\n while (attempts < maxAttempts) {\n name = generateCredentialName();\n\n const existing = await this.pool.query(\n `SELECT name FROM credentials WHERE name = $1`,\n [name]\n );\n\n if (existing.rows.length === 0) break;\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);\n }\n }\n\n const secret = generateSecret();\n const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);\n\n await this.pool.query(\n `INSERT INTO credentials (name, secret, created_at, expires_at, last_used)\n VALUES ($1, $2, $3, $4, $5)`,\n [name!, encryptedSecret, now, expiresAt, now]\n );\n\n return {\n name: name!,\n secret,\n createdAt: now,\n expiresAt,\n lastUsed: now,\n };\n }\n\n async getCredential(name: string): Promise<Credential | null> {\n const result = await this.pool.query(\n `SELECT * FROM credentials WHERE name = $1 AND expires_at > $2`,\n [name, Date.now()]\n );\n\n if (result.rows.length === 0) return null;\n\n try {\n const { decryptSecret } = await import('../crypto.ts');\n const decryptedSecret = await decryptSecret(result.rows[0].secret, this.masterEncryptionKey);\n\n return {\n name: result.rows[0].name,\n secret: decryptedSecret,\n createdAt: Number(result.rows[0].created_at),\n expiresAt: Number(result.rows[0].expires_at),\n lastUsed: Number(result.rows[0].last_used),\n };\n } catch (error) {\n console.error(`Failed to decrypt secret for credential '${name}':`, error);\n return null;\n }\n }\n\n async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {\n await this.pool.query(\n `UPDATE credentials SET last_used = $1, expires_at = $2 WHERE name = $3`,\n [lastUsed, expiresAt, name]\n );\n }\n\n async deleteExpiredCredentials(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM credentials WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n // ===== Rate Limiting =====\n\n async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {\n const now = Date.now();\n const resetTime = now + windowMs;\n\n // Use INSERT ... ON CONFLICT for atomic upsert\n const result = await this.pool.query(\n `INSERT INTO rate_limits (identifier, count, reset_time)\n VALUES ($1, 1, $2)\n ON CONFLICT (identifier) DO UPDATE SET\n count = CASE\n WHEN rate_limits.reset_time < $3 THEN 1\n ELSE rate_limits.count + 1\n END,\n reset_time = CASE\n WHEN rate_limits.reset_time < $3 THEN $2\n ELSE rate_limits.reset_time\n END\n RETURNING count`,\n [identifier, resetTime, now]\n );\n\n return result.rows[0].count <= limit;\n }\n\n async deleteExpiredRateLimits(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM rate_limits WHERE reset_time < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n // ===== Nonce Tracking (Replay Protection) =====\n\n async checkAndMarkNonce(nonceKey: string, expiresAt: number): Promise<boolean> {\n try {\n await this.pool.query(\n `INSERT INTO nonces (nonce_key, expires_at) VALUES ($1, $2)`,\n [nonceKey, expiresAt]\n );\n return true;\n } catch (error: any) {\n // PostgreSQL unique violation error code\n if (error.code === '23505') {\n return false;\n }\n throw error;\n }\n }\n\n async deleteExpiredNonces(now: number): Promise<number> {\n const result = await this.pool.query(\n `DELETE FROM nonces WHERE expires_at < $1`,\n [now]\n );\n return result.rowCount ?? 0;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n // ===== Count Methods (for resource limits) =====\n\n async getOfferCount(): Promise<number> {\n const result = await this.pool.query('SELECT COUNT(*) as count FROM offers');\n return Number(result.rows[0].count);\n }\n\n async getOfferCountByUsername(username: string): Promise<number> {\n const result = await this.pool.query(\n 'SELECT COUNT(*) as count FROM offers WHERE username = $1',\n [username]\n );\n return Number(result.rows[0].count);\n }\n\n async getCredentialCount(): Promise<number> {\n const result = await this.pool.query('SELECT COUNT(*) as count FROM credentials');\n return Number(result.rows[0].count);\n }\n\n async getIceCandidateCount(offerId: string): Promise<number> {\n const result = await this.pool.query(\n 'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = $1',\n [offerId]\n );\n return Number(result.rows[0].count);\n }\n\n // ===== Helper Methods =====\n\n private rowToOffer(row: any): Offer {\n return {\n id: row.id,\n username: row.username,\n tags: typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags,\n sdp: row.sdp,\n createdAt: Number(row.created_at),\n expiresAt: Number(row.expires_at),\n lastSeen: Number(row.last_seen),\n answererUsername: row.answerer_username || undefined,\n answerSdp: row.answer_sdp || undefined,\n answeredAt: row.answered_at ? Number(row.answered_at) : undefined,\n };\n }\n\n private rowToIceCandidate(row: any): IceCandidate {\n return {\n id: Number(row.id),\n offerId: row.offer_id,\n username: row.username,\n role: row.role as 'offerer' | 'answerer',\n candidate: typeof row.candidate === 'string' ? JSON.parse(row.candidate) : row.candidate,\n createdAt: Number(row.created_at),\n };\n }\n}\n", "import { serve } from '@hono/node-server';\nimport { createApp } from './app.ts';\nimport { loadConfig, runCleanup } from './config.ts';\nimport { createStorage } from './storage/factory.ts';\nimport { Storage } from './storage/types.ts';\n\nasync function main() {\n const config = loadConfig();\n\n console.log('Starting Rondevu server...');\n console.log('Configuration:', {\n port: config.port,\n storageType: config.storageType,\n storagePath: config.storageType === 'sqlite' ? config.storagePath : undefined,\n databaseUrl: config.databaseUrl ? '[configured]' : undefined,\n dbPoolSize: ['mysql', 'postgres'].includes(config.storageType) ? config.dbPoolSize : undefined,\n offerDefaultTtl: `${config.offerDefaultTtl}ms`,\n cleanupInterval: `${config.cleanupInterval}ms`,\n version: config.version,\n });\n\n const storage: Storage = await createStorage({\n type: config.storageType,\n masterEncryptionKey: config.masterEncryptionKey,\n sqlitePath: config.storagePath,\n connectionString: config.databaseUrl,\n poolSize: config.dbPoolSize,\n });\n console.log(`Using ${config.storageType} storage`);\n\n // Periodic cleanup\n const cleanupTimer = setInterval(async () => {\n try {\n const result = await runCleanup(storage, Date.now());\n const total = result.offers + result.credentials + result.rateLimits + result.nonces;\n if (total > 0) {\n console.log(`Cleanup: ${result.offers} offers, ${result.credentials} credentials, ${result.rateLimits} rate limits, ${result.nonces} nonces`);\n }\n } catch (err) {\n console.error('Cleanup error:', err);\n }\n }, config.cleanupInterval);\n\n const app = createApp(storage, config);\n\n const server = serve({\n fetch: app.fetch,\n port: config.port,\n });\n\n console.log(`Server running on http://localhost:${config.port}`);\n console.log('Ready to accept connections');\n\n // Graceful shutdown handler\n const shutdown = async () => {\n console.log('\\nShutting down gracefully...');\n clearInterval(cleanupTimer);\n await storage.close();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n", "import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Storage } from './storage/types.ts';\nimport { Config } from './config.ts';\nimport { handleRpc, RpcRequest } from './rpc.ts';\n\n/**\n * Creates the Hono application with RPC interface\n */\nexport function createApp(storage: Storage, config: Config) {\n const app = new Hono();\n\n // Enable CORS\n app.use('/*', cors({\n origin: (origin) => {\n if (config.corsOrigins.length === 1 && config.corsOrigins[0] === '*') {\n return origin;\n }\n if (config.corsOrigins.includes(origin)) {\n return origin;\n }\n return config.corsOrigins[0];\n },\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Origin', 'X-Name', 'X-Timestamp', 'X-Nonce', 'X-Signature'],\n exposeHeaders: ['Content-Type'],\n credentials: false,\n maxAge: 86400,\n }));\n\n // Root endpoint - server info\n app.get('/', (c) => {\n return c.json({\n version: config.version,\n name: 'Rondevu',\n description: 'WebRTC signaling with RPC interface and HMAC signature-based authentication',\n }, 200);\n });\n\n // Health check\n app.get('/health', (c) => {\n return c.json({\n status: 'ok',\n timestamp: Date.now(),\n version: config.version,\n }, 200);\n });\n\n /**\n * POST /rpc\n * RPC endpoint - accepts batch method calls only\n */\n app.post('/rpc', async (c) => {\n try {\n const body = await c.req.json();\n\n // Only accept batch arrays\n if (!Array.isArray(body)) {\n return c.json([{\n success: false,\n error: 'Request must be an array of RPC calls',\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n\n const requests: RpcRequest[] = body;\n\n // Validate requests\n if (requests.length === 0) {\n return c.json([{\n success: false,\n error: 'Empty request array',\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n\n if (requests.length > config.maxBatchSize) {\n return c.json([{\n success: false,\n error: `Too many requests in batch (max ${config.maxBatchSize})`,\n errorCode: 'BATCH_TOO_LARGE'\n }], 413); // 413 Payload Too Large\n }\n\n // Handle RPC (pass context for auth headers)\n const responses = await handleRpc(requests, c, storage, config);\n\n // Always return array\n return c.json(responses, 200);\n } catch (err) {\n console.error('RPC error:', err);\n\n // Distinguish between JSON parse errors and validation errors\n const errorMsg = err instanceof SyntaxError\n ? 'Invalid JSON in request body'\n : 'Request must be valid JSON array';\n\n return c.json([{\n success: false,\n error: errorMsg,\n errorCode: 'INVALID_PARAMS'\n }], 400);\n }\n });\n\n // 404 for all other routes\n app.all('*', (c) => {\n return c.json({\n error: 'Not found. Use POST /rpc for all API calls.',\n }, 404);\n });\n\n return app;\n}\n", "import { Context } from 'hono';\nimport { Storage } from './storage/types.ts';\nimport { Config } from './config.ts';\nimport {\n validateTags,\n validateUsername,\n verifySignature,\n buildSignatureMessage,\n} from './crypto.ts';\n\n// Constants (non-configurable)\nconst MAX_PAGE_SIZE = 100;\n\n// NOTE: MAX_SDP_SIZE, MAX_CANDIDATE_SIZE, MAX_CANDIDATE_DEPTH, and MAX_CANDIDATES_PER_REQUEST\n// are now configurable via environment variables (see config.ts)\n\n// ===== Rate Limiting =====\n\n// Rate limiting windows (these are fixed, limits come from config)\n// NOTE: Uses fixed-window rate limiting with full window reset on expiry\n// - Window starts on first request and expires after window duration\n// - When window expires, counter resets to 0 and new window starts\n// - This is simpler than sliding windows but may allow bursts at window boundaries\nconst CREDENTIAL_RATE_WINDOW = 1000; // 1 second in milliseconds\nconst REQUEST_RATE_WINDOW = 1000; // 1 second in milliseconds\n\n/**\n * Check JSON object depth to prevent stack overflow from deeply nested objects\n * CRITICAL: Checks depth BEFORE recursing to prevent stack overflow\n * @param obj Object to check\n * @param maxDepth Maximum allowed depth\n * @param currentDepth Current recursion depth\n * @returns Actual depth of the object (returns maxDepth + 1 if exceeded)\n */\nfunction getJsonDepth(obj: any, maxDepth: number, currentDepth = 0): number {\n // Check for primitives/null first\n if (obj === null || typeof obj !== 'object') {\n return currentDepth;\n }\n\n // CRITICAL: Check depth BEFORE recursing to prevent stack overflow\n // If we're already at max depth, don't recurse further\n if (currentDepth >= maxDepth) {\n return currentDepth + 1; // Indicate exceeded\n }\n\n let maxChildDepth = currentDepth;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const childDepth = getJsonDepth(obj[key], maxDepth, currentDepth + 1);\n maxChildDepth = Math.max(maxChildDepth, childDepth);\n\n // Early exit if exceeded\n if (maxChildDepth > maxDepth) {\n return maxChildDepth;\n }\n }\n }\n\n return maxChildDepth;\n}\n\n/**\n * Validate parameter is a non-empty string\n * Prevents type coercion issues and injection attacks\n */\nfunction validateStringParam(value: any, paramName: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `${paramName} must be a non-empty string`);\n }\n}\n\n/**\n * Standard error codes for RPC responses\n */\nexport const ErrorCodes = {\n // Authentication errors\n AUTH_REQUIRED: 'AUTH_REQUIRED',\n INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',\n\n // Validation errors\n INVALID_NAME: 'INVALID_NAME',\n INVALID_TAG: 'INVALID_TAG',\n INVALID_SDP: 'INVALID_SDP',\n INVALID_PARAMS: 'INVALID_PARAMS',\n MISSING_PARAMS: 'MISSING_PARAMS',\n\n // Resource errors\n OFFER_NOT_FOUND: 'OFFER_NOT_FOUND',\n OFFER_ALREADY_ANSWERED: 'OFFER_ALREADY_ANSWERED',\n OFFER_NOT_ANSWERED: 'OFFER_NOT_ANSWERED',\n NO_AVAILABLE_OFFERS: 'NO_AVAILABLE_OFFERS',\n\n // Authorization errors\n NOT_AUTHORIZED: 'NOT_AUTHORIZED',\n OWNERSHIP_MISMATCH: 'OWNERSHIP_MISMATCH',\n\n // Limit errors\n TOO_MANY_OFFERS: 'TOO_MANY_OFFERS',\n SDP_TOO_LARGE: 'SDP_TOO_LARGE',\n BATCH_TOO_LARGE: 'BATCH_TOO_LARGE',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n TOO_MANY_OFFERS_PER_USER: 'TOO_MANY_OFFERS_PER_USER',\n STORAGE_FULL: 'STORAGE_FULL',\n TOO_MANY_ICE_CANDIDATES: 'TOO_MANY_ICE_CANDIDATES',\n\n // Generic errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n UNKNOWN_METHOD: 'UNKNOWN_METHOD',\n} as const;\n\n/**\n * Custom error class with error code support\n */\nexport class RpcError extends Error {\n constructor(\n public errorCode: string,\n message: string\n ) {\n super(message);\n this.name = 'RpcError';\n }\n}\n\n/**\n * RPC request format (body only - auth in headers)\n */\nexport interface RpcRequest {\n method: string;\n params?: any;\n clientIp?: string;\n}\n\n/**\n * RPC response format\n */\nexport interface RpcResponse {\n success: boolean;\n result?: any;\n error?: string;\n errorCode?: string;\n}\n\n/**\n * RPC Method Parameter Interfaces\n */\nexport interface GenerateCredentialsParams {\n name?: string; // Optional: claim specific username (4-32 chars, alphanumeric + dashes + periods)\n expiresAt?: number;\n}\n\nexport interface DiscoverParams {\n tags: string[];\n limit?: number;\n offset?: number;\n}\n\nexport interface PublishOfferParams {\n tags: string[];\n offers: Array<{ sdp: string }>;\n ttl?: number;\n}\n\nexport interface DeleteOfferParams {\n offerId: string;\n}\n\nexport interface AnswerOfferParams {\n offerId: string;\n sdp: string;\n}\n\nexport interface GetOfferAnswerParams {\n offerId: string;\n}\n\nexport interface PollParams {\n since?: number;\n}\n\nexport interface AddIceCandidatesParams {\n offerId: string;\n candidates: any[];\n}\n\nexport interface GetIceCandidatesParams {\n offerId: string;\n since?: number;\n}\n\n/**\n * RPC method handler\n * Generic type parameter allows individual handlers to specify their param types\n */\ntype RpcHandler<TParams = any> = (\n params: TParams,\n name: string,\n timestamp: number,\n signature: string,\n storage: Storage,\n config: Config,\n request: RpcRequest\n) => Promise<any>;\n\n/**\n * Validate timestamp for replay attack prevention\n * Throws RpcError if timestamp is invalid\n */\nfunction validateTimestamp(timestamp: number, config: Config): void {\n const now = Date.now();\n\n // Check if timestamp is too old (replay attack)\n if (now - timestamp > config.timestampMaxAge) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Timestamp too old');\n }\n\n // Check if timestamp is too far in future (clock skew)\n if (timestamp - now > config.timestampMaxFuture) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Timestamp too far in future');\n }\n}\n\n/**\n * Verify request signature for authentication\n * Throws RpcError on authentication failure\n */\nasync function verifyRequestSignature(\n name: string,\n timestamp: number,\n nonce: string,\n signature: string,\n method: string,\n params: any,\n storage: Storage,\n config: Config\n): Promise<void> {\n // Validate timestamp first\n validateTimestamp(timestamp, config);\n\n // Get credential to retrieve secret\n const credential = await storage.getCredential(name);\n if (!credential) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Invalid credentials');\n }\n\n // Build message and verify signature (includes nonce to prevent signature reuse)\n const message = buildSignatureMessage(timestamp, nonce, method, params);\n const isValid = await verifySignature(credential.secret, message, signature);\n\n if (!isValid) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Invalid signature');\n }\n\n // Check nonce uniqueness AFTER successful signature verification\n // This prevents DoS where invalid signatures burn nonces\n // Only valid authenticated requests can mark nonces as used\n const nonceKey = `nonce:${name}:${nonce}`;\n const nonceExpiresAt = timestamp + config.timestampMaxAge;\n const nonceIsNew = await storage.checkAndMarkNonce(nonceKey, nonceExpiresAt);\n\n if (!nonceIsNew) {\n throw new RpcError(ErrorCodes.INVALID_CREDENTIALS, 'Nonce already used (replay attack detected)');\n }\n\n // Update last used timestamp\n const now = Date.now();\n const credentialExpiresAt = now + (365 * 24 * 60 * 60 * 1000); // 1 year\n await storage.updateCredentialUsage(name, now, credentialExpiresAt);\n}\n\n/**\n * RPC Method Handlers\n */\n\nconst handlers: Record<string, RpcHandler> = {\n /**\n * Generate new credentials (name + secret pair)\n * No authentication required - this is how users get started\n * SECURITY: Rate limited per IP to prevent abuse (database-backed for multi-instance support)\n */\n async generateCredentials(params: GenerateCredentialsParams, name, timestamp, signature, storage, config, request: RpcRequest & { clientIp?: string }) {\n // Check total credentials limit\n const credentialCount = await storage.getCredentialCount();\n if (credentialCount >= config.maxTotalCredentials) {\n throw new RpcError(\n ErrorCodes.STORAGE_FULL,\n `Server credential limit reached (${config.maxTotalCredentials}). Try again later.`\n );\n }\n\n // Rate limiting check (IP-based, stored in database)\n // SECURITY: Use stricter global rate limit for requests without identifiable IP\n let rateLimitKey: string;\n let rateLimit: number;\n\n if (!request.clientIp) {\n // Warn about missing IP (suggests proxy misconfiguration)\n console.warn('\u26A0\uFE0F WARNING: Unable to determine client IP for credential generation. Using global rate limit.');\n // Use global rate limit with much stricter limit (prevents DoS while allowing basic function)\n rateLimitKey = 'cred_gen:global_unknown';\n rateLimit = 2; // Only 2 credentials per second globally for all unknown IPs combined\n } else {\n rateLimitKey = `cred_gen:${request.clientIp}`;\n rateLimit = config.credentialsPerIpPerSecond;\n }\n\n const allowed = await storage.checkRateLimit(\n rateLimitKey,\n rateLimit,\n CREDENTIAL_RATE_WINDOW\n );\n\n if (!allowed) {\n throw new RpcError(\n ErrorCodes.RATE_LIMIT_EXCEEDED,\n `Rate limit exceeded. Maximum ${rateLimit} credentials per second${request.clientIp ? ' per IP' : ' (global limit for unidentified IPs)'}.`\n );\n }\n\n // Validate username if provided\n if (params.name !== undefined) {\n if (typeof params.name !== 'string') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'name must be a string');\n }\n const usernameValidation = validateUsername(params.name);\n if (!usernameValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, usernameValidation.error || 'Invalid username');\n }\n }\n\n // Validate expiresAt if provided\n if (params.expiresAt !== undefined) {\n if (typeof params.expiresAt !== 'number' || isNaN(params.expiresAt) || !Number.isFinite(params.expiresAt)) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt must be a valid timestamp');\n }\n // Prevent setting expiry in the past (with 1 minute tolerance for clock skew)\n const now = Date.now();\n if (params.expiresAt < now - 60000) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt cannot be in the past');\n }\n // Prevent unreasonably far future expiry (max 10 years)\n const maxFuture = now + (10 * 365 * 24 * 60 * 60 * 1000);\n if (params.expiresAt > maxFuture) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'expiresAt cannot be more than 10 years in the future');\n }\n }\n\n try {\n const credential = await storage.generateCredentials({\n name: params.name,\n expiresAt: params.expiresAt,\n });\n\n return {\n name: credential.name,\n secret: credential.secret,\n createdAt: credential.createdAt,\n expiresAt: credential.expiresAt,\n };\n } catch (error: any) {\n if (error.message === 'Username already taken') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Username already taken');\n }\n throw error;\n }\n },\n\n /**\n * Discover offers by tags - Supports 2 modes:\n * 1. Paginated discovery: tags array with limit/offset\n * 2. Random discovery: tags array without limit (returns single random offer)\n */\n async discover(params: DiscoverParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { tags, limit, offset } = params;\n\n // Validate tags\n const tagsValidation = validateTags(tags);\n if (!tagsValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_TAG, tagsValidation.error || 'Invalid tags');\n }\n\n // Mode 1: Paginated discovery\n if (limit !== undefined) {\n // Validate numeric parameters\n if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'limit must be a non-negative integer');\n }\n if (offset !== undefined && (typeof offset !== 'number' || !Number.isInteger(offset) || offset < 0)) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'offset must be a non-negative integer');\n }\n\n const pageLimit = Math.min(Math.max(1, limit), MAX_PAGE_SIZE);\n const pageOffset = Math.max(0, offset || 0);\n\n // Exclude self if authenticated\n const excludeUsername = name || null;\n\n const offers = await storage.discoverOffers(\n tags,\n excludeUsername,\n pageLimit,\n pageOffset\n );\n\n return {\n offers: offers.map(offer => ({\n offerId: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n })),\n count: offers.length,\n limit: pageLimit,\n offset: pageOffset,\n };\n }\n\n // Mode 2: Random discovery (no limit provided)\n // Exclude self if authenticated\n const excludeUsername = name || null;\n\n const offer = await storage.getRandomOffer(tags, excludeUsername);\n\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'No offers found matching tags');\n }\n\n return {\n offerId: offer.id,\n username: offer.username,\n tags: offer.tags,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n };\n },\n\n /**\n * Publish offers with tags\n */\n async publishOffer(params: PublishOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { tags, offers, ttl } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required for offer publishing');\n }\n\n // Validate tags\n const tagsValidation = validateTags(tags);\n if (!tagsValidation.valid) {\n throw new RpcError(ErrorCodes.INVALID_TAG, tagsValidation.error || 'Invalid tags');\n }\n\n // Validate offers\n if (!offers || !Array.isArray(offers) || offers.length === 0) {\n throw new RpcError(ErrorCodes.MISSING_PARAMS, 'Must provide at least one offer');\n }\n\n if (offers.length > config.maxOffersPerRequest) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_OFFERS,\n `Too many offers (max ${config.maxOffersPerRequest})`\n );\n }\n\n // Check per-user offer limit\n const userOfferCount = await storage.getOfferCountByUsername(name);\n if (userOfferCount + offers.length > config.maxOffersPerUser) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_OFFERS_PER_USER,\n `User offer limit exceeded. You have ${userOfferCount} offers, limit is ${config.maxOffersPerUser}.`\n );\n }\n\n // Check total offers limit\n const totalOfferCount = await storage.getOfferCount();\n if (totalOfferCount + offers.length > config.maxTotalOffers) {\n throw new RpcError(\n ErrorCodes.STORAGE_FULL,\n `Server offer limit reached (${config.maxTotalOffers}). Try again later.`\n );\n }\n\n // Validate each offer has valid SDP\n offers.forEach((offer, index) => {\n if (!offer || typeof offer !== 'object') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Invalid offer at index ${index}: must be an object`);\n }\n if (!offer.sdp || typeof offer.sdp !== 'string') {\n throw new RpcError(ErrorCodes.INVALID_SDP, `Invalid offer at index ${index}: missing or invalid SDP`);\n }\n if (!offer.sdp.trim()) {\n throw new RpcError(ErrorCodes.INVALID_SDP, `Invalid offer at index ${index}: SDP cannot be empty`);\n }\n if (offer.sdp.length > config.maxSdpSize) {\n throw new RpcError(ErrorCodes.SDP_TOO_LARGE, `SDP too large at index ${index} (max ${config.maxSdpSize} bytes)`);\n }\n });\n\n // Validate TTL if provided\n if (ttl !== undefined) {\n if (typeof ttl !== 'number' || isNaN(ttl) || ttl < 0) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'TTL must be a non-negative number');\n }\n }\n\n // Create offers with tags\n const now = Date.now();\n const offerTtl =\n ttl !== undefined\n ? Math.min(\n Math.max(ttl, config.offerMinTtl),\n config.offerMaxTtl\n )\n : config.offerDefaultTtl;\n const expiresAt = now + offerTtl;\n\n // Prepare offer requests with tags\n const offerRequests = offers.map(offer => ({\n username: name,\n tags,\n sdp: offer.sdp,\n expiresAt,\n }));\n\n const createdOffers = await storage.createOffers(offerRequests);\n\n return {\n username: name,\n tags,\n offers: createdOffers.map(offer => ({\n offerId: offer.id,\n sdp: offer.sdp,\n createdAt: offer.createdAt,\n expiresAt: offer.expiresAt,\n })),\n createdAt: now,\n expiresAt,\n };\n },\n\n /**\n * Delete an offer by ID\n */\n async deleteOffer(params: DeleteOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n validateStringParam(offerId, 'offerId');\n\n const deleted = await storage.deleteOffer(offerId, name);\n if (!deleted) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Offer not found or not owned by this name');\n }\n\n return { success: true };\n },\n\n /**\n * Answer an offer\n */\n async answerOffer(params: AnswerOfferParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, sdp } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n if (!sdp || typeof sdp !== 'string' || sdp.length === 0) {\n throw new RpcError(ErrorCodes.INVALID_SDP, 'Invalid SDP');\n }\n\n if (sdp.length > config.maxSdpSize) {\n throw new RpcError(ErrorCodes.SDP_TOO_LARGE, `SDP too large (max ${config.maxSdpSize} bytes)`);\n }\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n if (offer.answererUsername) {\n throw new RpcError(ErrorCodes.OFFER_ALREADY_ANSWERED, 'Offer already answered');\n }\n\n await storage.answerOffer(offerId, name, sdp);\n\n return { success: true, offerId };\n },\n\n /**\n * Get answer for an offer\n */\n async getOfferAnswer(params: GetOfferAnswerParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n if (offer.username !== name) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Not authorized to access this offer');\n }\n\n if (!offer.answererUsername || !offer.answerSdp) {\n throw new RpcError(ErrorCodes.OFFER_NOT_ANSWERED, 'Offer not yet answered');\n }\n\n return {\n sdp: offer.answerSdp,\n offerId: offer.id,\n answererId: offer.answererUsername,\n answeredAt: offer.answeredAt,\n };\n },\n\n /**\n * Combined polling for answers and ICE candidates\n */\n async poll(params: PollParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { since } = params;\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n // Validate since parameter\n if (since !== undefined && (typeof since !== 'number' || since < 0 || !Number.isFinite(since))) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Invalid since parameter: must be a non-negative number');\n }\n const sinceTimestamp = since !== undefined ? since : 0;\n\n // Get all answered offers (where user is the offerer)\n const answeredOffers = await storage.getAnsweredOffers(name);\n const filteredAnswers = answeredOffers.filter(\n (offer) => offer.answeredAt && offer.answeredAt > sinceTimestamp\n );\n\n // Get all user's offers (where user is offerer)\n const ownedOffers = await storage.getOffersByUsername(name);\n\n // Get all offers the user has answered (where user is answerer)\n const answeredByUser = await storage.getOffersAnsweredBy(name);\n\n // Combine offer IDs from both sources for ICE candidate fetching\n // The storage method handles filtering by role automatically\n const allOfferIds = [\n ...ownedOffers.map(offer => offer.id),\n ...answeredByUser.map(offer => offer.id),\n ];\n // Remove duplicates (shouldn't happen, but defensive)\n const offerIds = [...new Set(allOfferIds)];\n\n // Batch fetch ICE candidates for all offers using JOIN to avoid N+1 query problem\n // Server filters by role - offerers get answerer candidates, answerers get offerer candidates\n const iceCandidatesMap = await storage.getIceCandidatesForMultipleOffers(\n offerIds,\n name,\n sinceTimestamp\n );\n\n // Convert Map to Record for response\n const iceCandidatesByOffer: Record<string, any[]> = {};\n for (const [offerId, candidates] of iceCandidatesMap.entries()) {\n iceCandidatesByOffer[offerId] = candidates;\n }\n\n return {\n answers: filteredAnswers.map((offer) => ({\n offerId: offer.id,\n answererId: offer.answererUsername,\n sdp: offer.answerSdp,\n answeredAt: offer.answeredAt,\n })),\n iceCandidates: iceCandidatesByOffer,\n };\n },\n\n /**\n * Add ICE candidates\n */\n async addIceCandidates(params: AddIceCandidatesParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, candidates } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n if (!Array.isArray(candidates) || candidates.length === 0) {\n throw new RpcError(ErrorCodes.MISSING_PARAMS, 'Missing or invalid required parameter: candidates');\n }\n\n if (candidates.length > config.maxCandidatesPerRequest) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Too many candidates (max ${config.maxCandidatesPerRequest})`\n );\n }\n\n // Validate each candidate is an object (don't enforce structure per CLAUDE.md)\n candidates.forEach((candidate, index) => {\n if (!candidate || typeof candidate !== 'object') {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Invalid candidate at index ${index}: must be an object`);\n }\n\n // Check JSON depth to prevent stack overflow from deeply nested objects\n const depth = getJsonDepth(candidate, config.maxCandidateDepth + 1);\n if (depth > config.maxCandidateDepth) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Candidate at index ${index} too deeply nested (max depth ${config.maxCandidateDepth})`\n );\n }\n\n // Ensure candidate is serializable and check size (will be stored as JSON)\n let candidateJson: string;\n try {\n candidateJson = JSON.stringify(candidate);\n } catch (e) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, `Candidate at index ${index} is not serializable`);\n }\n\n // Validate candidate size to prevent abuse\n if (candidateJson.length > config.maxCandidateSize) {\n throw new RpcError(\n ErrorCodes.INVALID_PARAMS,\n `Candidate at index ${index} too large (max ${config.maxCandidateSize} bytes)`\n );\n }\n });\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n // Check ICE candidates limit per offer\n const currentCandidateCount = await storage.getIceCandidateCount(offerId);\n if (currentCandidateCount + candidates.length > config.maxIceCandidatesPerOffer) {\n throw new RpcError(\n ErrorCodes.TOO_MANY_ICE_CANDIDATES,\n `ICE candidate limit exceeded for offer. Current: ${currentCandidateCount}, limit: ${config.maxIceCandidatesPerOffer}.`\n );\n }\n\n const role = offer.username === name ? 'offerer' : 'answerer';\n const count = await storage.addIceCandidates(\n offerId,\n name,\n role,\n candidates\n );\n\n return { count, offerId };\n },\n\n /**\n * Get ICE candidates\n */\n async getIceCandidates(params: GetIceCandidatesParams, name, timestamp, signature, storage, config, request: RpcRequest) {\n const { offerId, since } = params;\n\n // Validate input parameters\n validateStringParam(offerId, 'offerId');\n\n if (!name) {\n throw new RpcError(ErrorCodes.AUTH_REQUIRED, 'Name required');\n }\n\n // Validate since parameter\n if (since !== undefined && (typeof since !== 'number' || since < 0 || !Number.isFinite(since))) {\n throw new RpcError(ErrorCodes.INVALID_PARAMS, 'Invalid since parameter: must be a non-negative number');\n }\n const sinceTimestamp = since !== undefined ? since : 0;\n\n const offer = await storage.getOfferById(offerId);\n if (!offer) {\n throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');\n }\n\n // Validate that user is authorized to access this offer's candidates\n // Only the offerer and answerer can access ICE candidates\n const isOfferer = offer.username === name;\n const isAnswerer = offer.answererUsername === name;\n\n if (!isOfferer && !isAnswerer) {\n throw new RpcError(ErrorCodes.NOT_AUTHORIZED, 'Not authorized to access ICE candidates for this offer');\n }\n\n const role = isOfferer ? 'answerer' : 'offerer';\n\n const candidates = await storage.getIceCandidates(\n offerId,\n role,\n sinceTimestamp\n );\n\n return {\n candidates: candidates.map((c: any) => ({\n candidate: c.candidate,\n createdAt: c.createdAt,\n })),\n offerId,\n };\n },\n};\n\n// Methods that don't require authentication\nconst UNAUTHENTICATED_METHODS = new Set(['generateCredentials', 'discover']);\n\n/**\n * Handle RPC batch request with header-based authentication\n */\nexport async function handleRpc(\n requests: RpcRequest[],\n ctx: Context,\n storage: Storage,\n config: Config\n): Promise<RpcResponse[]> {\n const responses: RpcResponse[] = [];\n\n // Extract client IP for rate limiting\n // Try multiple headers for proxy compatibility\n const clientIp =\n ctx.req.header('cf-connecting-ip') || // Cloudflare\n ctx.req.header('x-real-ip') || // Nginx\n ctx.req.header('x-forwarded-for')?.split(',')[0].trim() ||\n undefined; // Don't use fallback - let handlers decide how to handle missing IP\n\n // General request rate limiting (per IP per second)\n if (clientIp) {\n const rateLimitKey = `req:${clientIp}`;\n const allowed = await storage.checkRateLimit(\n rateLimitKey,\n config.requestsPerIpPerSecond,\n REQUEST_RATE_WINDOW\n );\n\n if (!allowed) {\n // Return error for all requests in the batch\n return requests.map(() => ({\n success: false,\n error: `Rate limit exceeded. Maximum ${config.requestsPerIpPerSecond} requests per second per IP.`,\n errorCode: ErrorCodes.RATE_LIMIT_EXCEEDED,\n }));\n }\n }\n\n // Read auth headers (same for all requests in batch)\n const name = ctx.req.header('X-Name');\n const timestampHeader = ctx.req.header('X-Timestamp');\n const nonce = ctx.req.header('X-Nonce');\n const signature = ctx.req.header('X-Signature');\n\n // Parse timestamp if present\n const timestamp = timestampHeader ? parseInt(timestampHeader, 10) : 0;\n\n // CRITICAL: Pre-calculate total operations BEFORE processing any requests\n // This prevents DoS where first N requests complete before limit triggers\n // Example attack prevented: 100 publishOffer \u00D7 100 offers = 10,000 operations\n let totalOperations = 0;\n\n // Count all operations across all requests first\n for (const request of requests) {\n const { method, params } = request;\n if (method === 'publishOffer' && params?.offers && Array.isArray(params.offers)) {\n totalOperations += params.offers.length;\n } else if (method === 'addIceCandidates' && params?.candidates && Array.isArray(params.candidates)) {\n totalOperations += params.candidates.length;\n } else {\n totalOperations += 1; // Single operation\n }\n }\n\n // Reject entire batch if total operations exceed limit\n // This happens BEFORE processing any requests\n // Return error for EACH request to maintain response array alignment\n if (totalOperations > config.maxTotalOperations) {\n return requests.map(() => ({\n success: false,\n error: `Total operations across batch exceed limit: ${totalOperations} > ${config.maxTotalOperations}`,\n errorCode: ErrorCodes.BATCH_TOO_LARGE,\n }));\n }\n\n // Process all requests\n for (const request of requests) {\n try {\n const { method, params } = request;\n\n // Validate request\n if (!method || typeof method !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid method',\n errorCode: ErrorCodes.INVALID_PARAMS,\n });\n continue;\n }\n\n // Get handler\n const handler = handlers[method];\n if (!handler) {\n responses.push({\n success: false,\n error: `Unknown method: ${method}`,\n errorCode: ErrorCodes.UNKNOWN_METHOD,\n });\n continue;\n }\n\n // Validate auth headers only for methods that require authentication\n const requiresAuth = !UNAUTHENTICATED_METHODS.has(method);\n\n if (requiresAuth) {\n if (!name || typeof name !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Name header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!timestampHeader || typeof timestampHeader !== 'string' || isNaN(timestamp)) {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Timestamp header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!nonce || typeof nonce !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Nonce header (use crypto.randomUUID())',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n if (!signature || typeof signature !== 'string') {\n responses.push({\n success: false,\n error: 'Missing or invalid X-Signature header',\n errorCode: ErrorCodes.AUTH_REQUIRED,\n });\n continue;\n }\n\n // Verify signature (validates timestamp, nonce, and signature)\n await verifyRequestSignature(\n name,\n timestamp,\n nonce,\n signature,\n method,\n params,\n storage,\n config\n );\n\n // Execute handler with auth\n const result = await handler(\n params || {},\n name,\n timestamp,\n signature,\n storage,\n config,\n { ...request, clientIp }\n );\n\n responses.push({\n success: true,\n result,\n });\n } else {\n // Execute handler without strict auth requirement\n const result = await handler(\n params || {},\n name || '',\n 0, // timestamp\n '', // signature\n storage,\n config,\n { ...request, clientIp }\n );\n\n responses.push({\n success: true,\n result,\n });\n }\n } catch (err) {\n if (err instanceof RpcError) {\n responses.push({\n success: false,\n error: err.message,\n errorCode: err.errorCode,\n });\n } else {\n // Generic error - don't leak internal details\n // Log the actual error for debugging\n console.error('Unexpected RPC error:', err);\n responses.push({\n success: false,\n error: 'Internal server error',\n errorCode: ErrorCodes.INTERNAL_ERROR,\n });\n }\n }\n }\n\n return responses;\n}\n", "import { Storage } from './storage/types.ts';\nimport { StorageType } from './storage/factory.ts';\n\n// Version is injected at build time via esbuild define\ndeclare const RONDEVU_VERSION: string;\nconst BUILD_VERSION = typeof RONDEVU_VERSION !== 'undefined' ? RONDEVU_VERSION : 'unknown';\n\n/**\n * Application configuration\n * Reads from environment variables with sensible defaults\n */\nexport interface Config {\n port: number;\n storageType: StorageType;\n storagePath: string;\n databaseUrl: string;\n dbPoolSize: number;\n corsOrigins: string[];\n version: string;\n offerDefaultTtl: number;\n offerMaxTtl: number;\n offerMinTtl: number;\n cleanupInterval: number;\n maxOffersPerRequest: number;\n maxBatchSize: number;\n maxSdpSize: number;\n maxCandidateSize: number;\n maxCandidateDepth: number;\n maxCandidatesPerRequest: number;\n maxTotalOperations: number;\n timestampMaxAge: number; // Max age for timestamps (replay protection)\n timestampMaxFuture: number; // Max future tolerance for timestamps (clock skew)\n masterEncryptionKey: string; // 64-char hex string for encrypting secrets (32 bytes)\n // Resource limits (for abuse prevention)\n maxOffersPerUser: number; // Max concurrent offers per user\n maxTotalOffers: number; // Max total offers in storage\n maxTotalCredentials: number; // Max total credentials in storage\n maxIceCandidatesPerOffer: number; // Max ICE candidates per offer\n credentialsPerIpPerSecond: number; // Rate limit: credentials per IP per second\n requestsPerIpPerSecond: number; // Rate limit: requests per IP per second\n}\n\n/**\n * Loads configuration from environment variables\n */\nexport function loadConfig(): Config {\n // Master encryption key for secret storage\n // CRITICAL: Set MASTER_ENCRYPTION_KEY in production to a secure random value\n let masterEncryptionKey = process.env.MASTER_ENCRYPTION_KEY;\n\n if (!masterEncryptionKey) {\n // SECURITY: Fail fast unless explicitly in development mode\n // Default to production-safe behavior if NODE_ENV is unset\n const isDevelopment = process.env.NODE_ENV === 'development';\n\n if (!isDevelopment) {\n throw new Error(\n 'MASTER_ENCRYPTION_KEY environment variable must be set. ' +\n 'Generate with: openssl rand -hex 32\\n' +\n 'For development only, set NODE_ENV=development to use insecure dev key.'\n );\n }\n\n // Use deterministic key ONLY in explicit development mode\n // WARNING: DO NOT USE THIS IN PRODUCTION - only for local development\n console.error('\u26A0\uFE0F WARNING: Using insecure deterministic development key');\n console.error('\u26A0\uFE0F ONLY use NODE_ENV=development for local development');\n console.error('\u26A0\uFE0F Generate production key with: openssl rand -hex 32');\n // Random-looking dev key (not ASCII-readable to prevent accidental production use)\n masterEncryptionKey = 'a3f8b9c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0';\n }\n\n // Validate master encryption key format\n // NOTE: Using regex here is safe since this runs at startup, not during request processing\n if (masterEncryptionKey.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(masterEncryptionKey)) {\n throw new Error('MASTER_ENCRYPTION_KEY must be 64-character hex string (32 bytes). Generate with: openssl rand -hex 32');\n }\n\n // Helper to safely parse and validate integer config values\n function parsePositiveInt(value: string | undefined, defaultValue: string, name: string, min = 1): number {\n const parsed = parseInt(value || defaultValue, 10);\n if (isNaN(parsed)) {\n throw new Error(`${name} must be a valid integer (got: ${value})`);\n }\n if (parsed < min) {\n throw new Error(`${name} must be >= ${min} (got: ${parsed})`);\n }\n return parsed;\n }\n\n const config = {\n port: parsePositiveInt(process.env.PORT, '3000', 'PORT', 1),\n storageType: (process.env.STORAGE_TYPE || 'memory') as StorageType,\n storagePath: process.env.STORAGE_PATH || ':memory:',\n databaseUrl: process.env.DATABASE_URL || '',\n dbPoolSize: parsePositiveInt(process.env.DB_POOL_SIZE, '10', 'DB_POOL_SIZE', 1),\n corsOrigins: process.env.CORS_ORIGINS\n ? process.env.CORS_ORIGINS.split(',').map(o => o.trim())\n : ['*'],\n version: process.env.VERSION || BUILD_VERSION,\n offerDefaultTtl: parsePositiveInt(process.env.OFFER_DEFAULT_TTL, '60000', 'OFFER_DEFAULT_TTL', 1000),\n offerMaxTtl: parsePositiveInt(process.env.OFFER_MAX_TTL, '86400000', 'OFFER_MAX_TTL', 1000),\n offerMinTtl: parsePositiveInt(process.env.OFFER_MIN_TTL, '60000', 'OFFER_MIN_TTL', 1000),\n cleanupInterval: parsePositiveInt(process.env.CLEANUP_INTERVAL, '60000', 'CLEANUP_INTERVAL', 1000),\n maxOffersPerRequest: parsePositiveInt(process.env.MAX_OFFERS_PER_REQUEST, '100', 'MAX_OFFERS_PER_REQUEST', 1),\n maxBatchSize: parsePositiveInt(process.env.MAX_BATCH_SIZE, '100', 'MAX_BATCH_SIZE', 1),\n maxSdpSize: parsePositiveInt(process.env.MAX_SDP_SIZE, String(64 * 1024), 'MAX_SDP_SIZE', 1024), // Min 1KB\n maxCandidateSize: parsePositiveInt(process.env.MAX_CANDIDATE_SIZE, String(4 * 1024), 'MAX_CANDIDATE_SIZE', 256), // Min 256 bytes\n maxCandidateDepth: parsePositiveInt(process.env.MAX_CANDIDATE_DEPTH, '10', 'MAX_CANDIDATE_DEPTH', 1),\n maxCandidatesPerRequest: parsePositiveInt(process.env.MAX_CANDIDATES_PER_REQUEST, '100', 'MAX_CANDIDATES_PER_REQUEST', 1),\n maxTotalOperations: parsePositiveInt(process.env.MAX_TOTAL_OPERATIONS, '1000', 'MAX_TOTAL_OPERATIONS', 1),\n timestampMaxAge: parsePositiveInt(process.env.TIMESTAMP_MAX_AGE, '60000', 'TIMESTAMP_MAX_AGE', 1000), // Min 1 second\n timestampMaxFuture: parsePositiveInt(process.env.TIMESTAMP_MAX_FUTURE, '60000', 'TIMESTAMP_MAX_FUTURE', 1000), // Min 1 second\n masterEncryptionKey,\n // Resource limits\n maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, '1000', 'MAX_OFFERS_PER_USER', 1),\n maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, '100000', 'MAX_TOTAL_OFFERS', 1),\n maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, '50000', 'MAX_TOTAL_CREDENTIALS', 1),\n maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, '50', 'MAX_ICE_CANDIDATES_PER_OFFER', 1),\n credentialsPerIpPerSecond: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_SECOND, '5', 'CREDENTIALS_PER_IP_PER_SECOND', 1),\n requestsPerIpPerSecond: parsePositiveInt(process.env.REQUESTS_PER_IP_PER_SECOND, '50', 'REQUESTS_PER_IP_PER_SECOND', 1),\n };\n\n return config;\n}\n\n/**\n * Default config values (shared between Node and Workers)\n */\nexport const CONFIG_DEFAULTS = {\n offerDefaultTtl: 60000,\n offerMaxTtl: 86400000,\n offerMinTtl: 60000,\n cleanupInterval: 60000,\n maxOffersPerRequest: 100,\n maxBatchSize: 100,\n maxSdpSize: 64 * 1024,\n maxCandidateSize: 4 * 1024,\n maxCandidateDepth: 10,\n maxCandidatesPerRequest: 100,\n maxTotalOperations: 1000,\n timestampMaxAge: 60000,\n timestampMaxFuture: 60000,\n // Resource limits\n maxOffersPerUser: 1000,\n maxTotalOffers: 100000,\n maxTotalCredentials: 50000,\n maxIceCandidatesPerOffer: 50,\n credentialsPerIpPerSecond: 5,\n requestsPerIpPerSecond: 50,\n} as const;\n\n/**\n * Build config for Cloudflare Workers from env vars\n */\nexport function buildWorkerConfig(env: {\n MASTER_ENCRYPTION_KEY: string;\n OFFER_DEFAULT_TTL?: string;\n OFFER_MAX_TTL?: string;\n OFFER_MIN_TTL?: string;\n MAX_OFFERS_PER_REQUEST?: string;\n MAX_BATCH_SIZE?: string;\n CORS_ORIGINS?: string;\n VERSION?: string;\n}): Config {\n return {\n port: 0, // Not used in Workers\n storageType: 'sqlite', // D1 is SQLite-compatible\n storagePath: '', // Not used with D1\n databaseUrl: '', // Not used with D1\n dbPoolSize: 10, // Not used with D1\n corsOrigins: env.CORS_ORIGINS?.split(',').map(o => o.trim()) ?? ['*'],\n version: env.VERSION ?? 'unknown',\n offerDefaultTtl: env.OFFER_DEFAULT_TTL ? parseInt(env.OFFER_DEFAULT_TTL, 10) : CONFIG_DEFAULTS.offerDefaultTtl,\n offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : CONFIG_DEFAULTS.offerMaxTtl,\n offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : CONFIG_DEFAULTS.offerMinTtl,\n cleanupInterval: CONFIG_DEFAULTS.cleanupInterval,\n maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : CONFIG_DEFAULTS.maxOffersPerRequest,\n maxBatchSize: env.MAX_BATCH_SIZE ? parseInt(env.MAX_BATCH_SIZE, 10) : CONFIG_DEFAULTS.maxBatchSize,\n maxSdpSize: CONFIG_DEFAULTS.maxSdpSize,\n maxCandidateSize: CONFIG_DEFAULTS.maxCandidateSize,\n maxCandidateDepth: CONFIG_DEFAULTS.maxCandidateDepth,\n maxCandidatesPerRequest: CONFIG_DEFAULTS.maxCandidatesPerRequest,\n maxTotalOperations: CONFIG_DEFAULTS.maxTotalOperations,\n timestampMaxAge: CONFIG_DEFAULTS.timestampMaxAge,\n timestampMaxFuture: CONFIG_DEFAULTS.timestampMaxFuture,\n masterEncryptionKey: env.MASTER_ENCRYPTION_KEY,\n // Resource limits\n maxOffersPerUser: CONFIG_DEFAULTS.maxOffersPerUser,\n maxTotalOffers: CONFIG_DEFAULTS.maxTotalOffers,\n maxTotalCredentials: CONFIG_DEFAULTS.maxTotalCredentials,\n maxIceCandidatesPerOffer: CONFIG_DEFAULTS.maxIceCandidatesPerOffer,\n credentialsPerIpPerSecond: CONFIG_DEFAULTS.credentialsPerIpPerSecond,\n requestsPerIpPerSecond: CONFIG_DEFAULTS.requestsPerIpPerSecond,\n };\n}\n\n/**\n * Run cleanup of expired entries (shared between Node and Workers)\n * @returns Object with counts of deleted items\n */\nexport async function runCleanup(storage: Storage, now: number): Promise<{\n offers: number;\n credentials: number;\n rateLimits: number;\n nonces: number;\n}> {\n const offers = await storage.deleteExpiredOffers(now);\n const credentials = await storage.deleteExpiredCredentials(now);\n const rateLimits = await storage.deleteExpiredRateLimits(now);\n const nonces = await storage.deleteExpiredNonces(now);\n\n return { offers, credentials, rateLimits, nonces };\n}\n", "import { Storage } from './types.ts';\n\n/**\n * Supported storage backend types\n */\nexport type StorageType = 'memory' | 'sqlite' | 'mysql' | 'postgres';\n\n/**\n * Configuration for creating a storage backend\n */\nexport interface StorageConfig {\n type: StorageType;\n /** Master encryption key for secrets (64-char hex string) */\n masterEncryptionKey: string;\n /** SQLite database path (default: ':memory:') */\n sqlitePath?: string;\n /** Connection string for MySQL/PostgreSQL */\n connectionString?: string;\n /** Connection pool size for MySQL/PostgreSQL (default: 10) */\n poolSize?: number;\n}\n\n/**\n * Creates a storage backend based on configuration\n * Uses dynamic imports to avoid loading unused dependencies\n */\nexport async function createStorage(config: StorageConfig): Promise<Storage> {\n switch (config.type) {\n case 'memory': {\n const { MemoryStorage } = await import('./memory.ts');\n return new MemoryStorage(config.masterEncryptionKey);\n }\n\n case 'sqlite': {\n const { SQLiteStorage } = await import('./sqlite.ts');\n return new SQLiteStorage(\n config.sqlitePath || ':memory:',\n config.masterEncryptionKey\n );\n }\n\n case 'mysql': {\n if (!config.connectionString) {\n throw new Error('MySQL storage requires DATABASE_URL connection string');\n }\n const { MySQLStorage } = await import('./mysql.ts');\n return MySQLStorage.create(\n config.connectionString,\n config.masterEncryptionKey,\n config.poolSize || 10\n );\n }\n\n case 'postgres': {\n if (!config.connectionString) {\n throw new Error('PostgreSQL storage requires DATABASE_URL connection string');\n }\n const { PostgreSQLStorage } = await import('./postgres.ts');\n return PostgreSQLStorage.create(\n config.connectionString,\n config.masterEncryptionKey,\n config.poolSize || 10\n );\n }\n\n default:\n throw new Error(`Unsupported storage type: ${config.type}`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["generateCredentialName", "generateSecret", "encryptSecret", "decryptSecret", "YEAR_IN_MS", "Database", "offersWithIds", "candidates", "generateCredentialName", "generateSecret", "encryptSecret", "decryptSecret", "YEAR_IN_MS", "mysql", "generateCredentialName", "generateSecret", "encryptSecret", "decryptSecret", "YEAR_IN_MS", "generateCredentialName", "generateSecret", "encryptSecret", "decryptSecret", "excludeUsername", "offer", "MemoryStorage", "SQLiteStorage", "MySQLStorage", "PostgreSQLStorage"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-server",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "DNS-like WebRTC signaling server with credential-based authentication and service discovery",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/config.ts CHANGED
@@ -113,8 +113,8 @@ export function loadConfig(): Config {
113
113
  timestampMaxFuture: parsePositiveInt(process.env.TIMESTAMP_MAX_FUTURE, '60000', 'TIMESTAMP_MAX_FUTURE', 1000), // Min 1 second
114
114
  masterEncryptionKey,
115
115
  // Resource limits
116
- maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, '20', 'MAX_OFFERS_PER_USER', 1),
117
- maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, '10000', 'MAX_TOTAL_OFFERS', 1),
116
+ maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, '1000', 'MAX_OFFERS_PER_USER', 1),
117
+ maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, '100000', 'MAX_TOTAL_OFFERS', 1),
118
118
  maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, '50000', 'MAX_TOTAL_CREDENTIALS', 1),
119
119
  maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, '50', 'MAX_ICE_CANDIDATES_PER_OFFER', 1),
120
120
  credentialsPerIpPerSecond: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_SECOND, '5', 'CREDENTIALS_PER_IP_PER_SECOND', 1),