@gagandeep023/api-gateway 0.4.2 → 0.5.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/backend/index.ts","../../src/backend/middleware/gateway.ts","../../src/backend/services/RateLimiterService.ts","../../src/backend/services/AnalyticsService.ts","../../src/backend/services/DeviceRegistryService.ts","../../src/backend/utils/totp.ts","../../src/config/defaults.ts","../../src/backend/middleware/apiKeyAuth.ts","../../src/backend/middleware/ipFilter.ts","../../src/backend/middleware/rateLimiter.ts","../../src/backend/middleware/requestLogger.ts","../../src/backend/routes/gateway.ts","../../src/backend/routes/deviceAuth.ts"],"sourcesContent":["export { createGatewayMiddleware } from './middleware/gateway';\nexport type { GatewayInstances } from './middleware/gateway';\nexport { createGatewayRoutes } from './routes/gateway';\nexport type { GatewayRoutesOptions } from './routes/gateway';\nexport { createDeviceAuthRoutes } from './routes/deviceAuth';\nexport type { DeviceAuthRoutesOptions } from './routes/deviceAuth';\nexport { DeviceRegistryService } from './services/DeviceRegistryService';\nexport { RateLimiterService } from './services/RateLimiterService';\nexport { AnalyticsService } from './services/AnalyticsService';\nexport { createApiKeyAuth } from './middleware/apiKeyAuth';\nexport { createIpFilter } from './middleware/ipFilter';\nexport { createRateLimiter } from './middleware/rateLimiter';\nexport { createRequestLogger } from './middleware/requestLogger';\nexport { generateTOTP, validateTOTP, formatKey, parseKey, generateSecret } from './utils/totp';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n deviceRegistry?: DeviceRegistryService;\n middleware: Router;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<GatewayMiddlewareConfig> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n deviceRegistryPath: userConfig?.deviceRegistryPath ?? '',\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n let deviceRegistry: DeviceRegistryService | undefined;\n if (config.deviceRegistryPath) {\n deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);\n }\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n deviceRegistry,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import type { RequestLog, GatewayAnalytics } from '../../types';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { generateSecret } from '../utils/totp';\nimport type { DeviceEntry, DevicesFile } from '../../types';\n\nconst ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\nconst DEBOUNCE_WRITE_MS = 2000;\nconst MAX_REGISTRATIONS_PER_IP_PER_MIN = 10;\nconst MAX_REGISTRATIONS_PER_IP_TOTAL = 30;\nconst RATE_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport class DeviceRegistryService {\n private devices: Map<string, DeviceEntry> = new Map();\n private filePath: string;\n private writeTimeout: ReturnType<typeof setTimeout> | null = null;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n // In-memory rate tracking: ip -> timestamps of recent registration attempts\n private registrationAttempts: Map<string, number[]> = new Map();\n\n constructor(filePath: string) {\n this.filePath = filePath;\n this.loadFromDisk();\n this.cleanupExpired();\n this.cleanupInterval = setInterval(() => this.cleanupExpired(), CLEANUP_INTERVAL_MS);\n }\n\n private loadFromDisk(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const data: DevicesFile = JSON.parse(raw);\n for (const device of data.devices) {\n this.devices.set(device.browserId, device);\n }\n console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);\n } else {\n // Create the directory and empty file\n const dir = path.dirname(this.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n this.saveToDiskSync();\n console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);\n }\n } catch (err) {\n console.error('[DeviceRegistry] Failed to load devices file:', err);\n }\n }\n\n private saveToDiskSync(): void {\n const data: DevicesFile = {\n devices: Array.from(this.devices.values()),\n };\n fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n\n private debouncedSave(): void {\n if (this.writeTimeout) clearTimeout(this.writeTimeout);\n this.writeTimeout = setTimeout(() => {\n this.saveToDiskSync();\n }, DEBOUNCE_WRITE_MS);\n }\n\n private cleanupExpired(): void {\n const now = Date.now();\n let removed = 0;\n for (const [id, device] of this.devices) {\n if (new Date(device.expiresAt).getTime() <= now) {\n this.devices.delete(id);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`[DeviceRegistry] Cleaned up ${removed} expired devices`);\n this.debouncedSave();\n }\n }\n\n private getActiveCountByIp(ip: string): number {\n const now = Date.now();\n let count = 0;\n for (const device of this.devices.values()) {\n if (device.ip === ip && device.active && new Date(device.expiresAt).getTime() > now) {\n count++;\n }\n }\n return count;\n }\n\n private checkRateLimit(ip: string): boolean {\n const now = Date.now();\n const attempts = this.registrationAttempts.get(ip) || [];\n // Remove attempts older than the rate window\n const recent = attempts.filter(t => now - t < RATE_WINDOW_MS);\n this.registrationAttempts.set(ip, recent);\n return recent.length < MAX_REGISTRATIONS_PER_IP_PER_MIN;\n }\n\n private recordAttempt(ip: string): void {\n const attempts = this.registrationAttempts.get(ip) || [];\n attempts.push(Date.now());\n this.registrationAttempts.set(ip, attempts);\n }\n\n registerDevice(\n browserId: string,\n ip: string,\n userAgent: string\n ): { success: true; device: DeviceEntry } | { success: false; error: string; status: number } {\n // Check rate limit: 10 per IP per minute\n if (!this.checkRateLimit(ip)) {\n return {\n success: false,\n error: 'Registration rate limit exceeded. Max 10 per minute per IP.',\n status: 429,\n };\n }\n\n this.recordAttempt(ip);\n\n // Check total cap: 30 active devices per IP\n if (this.getActiveCountByIp(ip) >= MAX_REGISTRATIONS_PER_IP_TOTAL) {\n return {\n success: false,\n error: 'Maximum device registrations reached for this IP. Max 30 per IP.',\n status: 403,\n };\n }\n\n // Check if this browserId is already registered and still valid\n const existing = this.devices.get(browserId);\n if (existing && existing.active && new Date(existing.expiresAt).getTime() > Date.now()) {\n // Re-registration: return existing secret, refresh expiry\n existing.expiresAt = new Date(Date.now() + ONE_WEEK_MS).toISOString();\n existing.lastSeen = new Date().toISOString();\n existing.lastIp = ip;\n this.debouncedSave();\n return { success: true, device: existing };\n }\n\n const now = new Date();\n const device: DeviceEntry = {\n browserId,\n sharedSecret: generateSecret(),\n ip,\n userAgent,\n registeredAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + ONE_WEEK_MS).toISOString(),\n lastSeen: now.toISOString(),\n lastIp: ip,\n active: true,\n };\n\n this.devices.set(browserId, device);\n this.debouncedSave();\n return { success: true, device };\n }\n\n getDevice(browserId: string): DeviceEntry | null {\n const device = this.devices.get(browserId) || null;\n if (!device) return null;\n\n // Check expiry\n if (new Date(device.expiresAt).getTime() <= Date.now()) {\n this.devices.delete(browserId);\n this.debouncedSave();\n return null;\n }\n\n if (!device.active) return null;\n return device;\n }\n\n updateLastSeen(browserId: string, ip: string): void {\n const device = this.devices.get(browserId);\n if (!device) return;\n device.lastSeen = new Date().toISOString();\n if (device.lastIp !== ip) {\n console.log(`[DeviceRegistry] IP change for ${browserId}: ${device.lastIp} -> ${ip}`);\n device.lastIp = ip;\n }\n this.debouncedSave();\n }\n\n revokeDevice(browserId: string): boolean {\n const device = this.devices.get(browserId);\n if (!device) return false;\n device.active = false;\n this.debouncedSave();\n return true;\n }\n\n getStats(): { total: number; active: number; expired: number } {\n const now = Date.now();\n let active = 0;\n let expired = 0;\n for (const device of this.devices.values()) {\n if (!device.active || new Date(device.expiresAt).getTime() <= now) {\n expired++;\n } else {\n active++;\n }\n }\n return { total: this.devices.size, active, expired };\n }\n\n destroy(): void {\n if (this.cleanupInterval) clearInterval(this.cleanupInterval);\n if (this.writeTimeout) {\n clearTimeout(this.writeTimeout);\n this.saveToDiskSync(); // Final save\n }\n }\n}\n","import crypto from 'crypto';\n\nconst TIME_WINDOW_MS = 3600 * 1000; // 1 hour\nconst CODE_LENGTH = 16; // truncated HMAC hex chars\n\nexport function generateTOTP(browserId: string, secret: string, timeOffset: number = 0): string {\n const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;\n const message = `${browserId}:${timeWindow}`;\n const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');\n return hmac.substring(0, CODE_LENGTH);\n}\n\nexport function validateTOTP(browserId: string, secret: string, providedCode: string): boolean {\n const currentCode = generateTOTP(browserId, secret, 0);\n const previousCode = generateTOTP(browserId, secret, -1);\n return timingSafeEqual(providedCode, currentCode) || timingSafeEqual(providedCode, previousCode);\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n return crypto.timingSafeEqual(bufA, bufB);\n}\n\nexport function formatKey(browserId: string, code: string): string {\n return `totp_${browserId}_${code}`;\n}\n\nexport function parseKey(key: string): { browserId: string; code: string } | null {\n if (!key.startsWith('totp_')) return null;\n const parts = key.slice(5).split('_');\n // UUID has 4 dashes, so browserId has 5 parts when split by _\n // Format: totp_<uuid>_<code> where uuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n // But UUID uses dashes, not underscores, so it's a single segment\n if (parts.length < 2) return null;\n const code = parts[parts.length - 1];\n const browserId = parts.slice(0, -1).join('_');\n if (!browserId || !code) return null;\n return { browserId, code };\n}\n\nexport function generateSecret(): string {\n return crypto.randomBytes(32).toString('hex');\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\nimport { parseKey, validateTOTP } from '../utils/totp';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport function createApiKeyAuth(\n getKeys: () => ApiKeysConfig,\n deviceRegistry?: DeviceRegistryService\n) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n // TOTP key: totp_<browserId>_<code>\n if (apiKey.startsWith('totp_') && deviceRegistry) {\n const parsed = parseKey(apiKey);\n if (!parsed) {\n res.status(401).json({ error: 'Malformed TOTP key' });\n return;\n }\n\n const device = deviceRegistry.getDevice(parsed.browserId);\n if (!device) {\n res.status(401).json({ error: 'Device not registered or expired' });\n return;\n }\n\n if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {\n res.status(401).json({ error: 'Invalid or expired TOTP code' });\n return;\n }\n\n // Valid TOTP - update tracking\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n deviceRegistry.updateLastSeen(parsed.browserId, ip);\n\n (req as any).clientId = parsed.browserId;\n (req as any).tier = 'free';\n (req as any).apiKeyValue = apiKey;\n next();\n return;\n }\n\n // Static key: gw_live_*\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n const apiKeyValue = (req as any).apiKeyValue || undefined;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: apiKeyValue,\n authenticated: !!apiKeyValue,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { Router, Request, Response } from 'express';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport interface DeviceAuthRoutesOptions {\n deviceRegistry: DeviceRegistryService;\n}\n\nexport function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router {\n const { deviceRegistry } = options;\n const router = Router();\n\n // POST /register - Register a browser device\n router.post('/register', (req: Request, res: Response) => {\n const { browserId } = req.body;\n\n if (!browserId || typeof browserId !== 'string') {\n res.status(400).json({ error: 'browserId is required' });\n return;\n }\n\n // Basic UUID format validation\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(browserId)) {\n res.status(400).json({ error: 'browserId must be a valid UUID' });\n return;\n }\n\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const userAgent = req.headers['user-agent'] || 'unknown';\n\n const result = deviceRegistry.registerDevice(browserId, ip, userAgent);\n\n if (!result.success) {\n res.status(result.status).json({ error: result.error });\n return;\n }\n\n res.status(201).json({\n browserId: result.device.browserId,\n sharedSecret: result.device.sharedSecret,\n expiresAt: result.device.expiresAt,\n });\n });\n\n // GET /status/:browserId - Check device registration status\n router.get('/status/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const device = deviceRegistry.getDevice(browserId);\n\n if (!device) {\n res.status(404).json({ registered: false });\n return;\n }\n\n res.json({\n registered: true,\n browserId: device.browserId,\n expiresAt: device.expiresAt,\n registeredAt: device.registeredAt,\n });\n });\n\n // DELETE /:browserId - Revoke a device (admin)\n router.delete('/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const revoked = deviceRegistry.revokeDevice(browserId);\n\n if (!revoked) {\n res.status(404).json({ error: 'Device not found' });\n return;\n }\n\n res.json({ message: 'Device revoked', browserId });\n });\n\n // GET /stats - Device registry stats\n router.get('/stats', (_req: Request, res: Response) => {\n res.json(deviceRegistry.getStats());\n });\n\n return router;\n}\n"],"mappings":"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","names":["resetMs","path","crypto","fs","path","import_express","import_crypto","crypto","import_express"]}
1
+ {"version":3,"sources":["../../src/backend/index.ts","../../src/backend/middleware/gateway.ts","../../src/backend/services/RateLimiterService.ts","../../src/backend/services/FileLogWriter.ts","../../src/config/defaults.ts","../../src/backend/services/AnalyticsService.ts","../../src/backend/services/DeviceRegistryService.ts","../../src/backend/utils/totp.ts","../../src/backend/middleware/apiKeyAuth.ts","../../src/backend/middleware/ipFilter.ts","../../src/backend/middleware/rateLimiter.ts","../../src/backend/middleware/requestLogger.ts","../../src/backend/routes/gateway.ts","../../src/backend/routes/deviceAuth.ts"],"sourcesContent":["export { createGatewayMiddleware } from './middleware/gateway';\nexport type { GatewayInstances } from './middleware/gateway';\nexport { createGatewayRoutes } from './routes/gateway';\nexport type { GatewayRoutesOptions } from './routes/gateway';\nexport { createDeviceAuthRoutes } from './routes/deviceAuth';\nexport type { DeviceAuthRoutesOptions } from './routes/deviceAuth';\nexport { DeviceRegistryService } from './services/DeviceRegistryService';\nexport { RateLimiterService } from './services/RateLimiterService';\nexport { AnalyticsService } from './services/AnalyticsService';\nexport { FileLogWriter } from './services/FileLogWriter';\nexport { createApiKeyAuth } from './middleware/apiKeyAuth';\nexport { createIpFilter } from './middleware/ipFilter';\nexport { createRateLimiter } from './middleware/rateLimiter';\nexport { createRequestLogger } from './middleware/requestLogger';\nexport { generateTOTP, validateTOTP, formatKey, parseKey, generateSecret } from './utils/totp';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n deviceRegistry?: DeviceRegistryService;\n middleware: Router;\n config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n deviceRegistryPath: userConfig?.deviceRegistryPath ?? '',\n logConfig: userConfig?.logConfig,\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService(userConfig?.logConfig);\n\n let deviceRegistry: DeviceRegistryService | undefined;\n if (config.deviceRegistryPath) {\n deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);\n }\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n deviceRegistry,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import { mkdirSync, readdirSync, appendFileSync } from 'fs';\nimport { join } from 'path';\nimport { randomUUID } from 'crypto';\nimport type { RequestLog, LogConfig, FileLogEntry } from '../../types';\nimport { DEFAULT_LOG_MAX_LINES } from '../../config/defaults';\n\nfunction deriveLevel(statusCode: number): FileLogEntry['level'] {\n if (statusCode < 400) return 'info';\n if (statusCode < 500) return 'warn';\n if (statusCode === 503) return 'fatal';\n return 'error';\n}\n\nfunction formatDate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction formatTime(date: Date): string {\n const h = String(date.getHours()).padStart(2, '0');\n const min = String(date.getMinutes()).padStart(2, '0');\n const s = String(date.getSeconds()).padStart(2, '0');\n return `${h}${min}${s}`;\n}\n\nexport class FileLogWriter {\n private logDir: string;\n private appName: string;\n private maxLines: number;\n private currentDate: string;\n private currentLines = 0;\n private fileIndex = 0;\n private currentFilePath: string | null = null;\n private destroyed = false;\n\n constructor(config: LogConfig) {\n this.logDir = config.logDir;\n this.appName = config.appName;\n this.maxLines = config.maxLinesPerFile ?? DEFAULT_LOG_MAX_LINES;\n this.currentDate = formatDate(new Date());\n\n try {\n mkdirSync(this.logDir, { recursive: true });\n } catch (err) {\n process.stderr.write(`[FileLogWriter] Failed to create log directory: ${err}\\n`);\n }\n\n this.fileIndex = this.scanExistingFiles(this.currentDate);\n this.rotateFile();\n }\n\n write(log: RequestLog): void {\n if (this.destroyed) return;\n\n const now = new Date(log.timestamp);\n const today = formatDate(now);\n\n // Date rollover\n if (today !== this.currentDate) {\n this.currentDate = today;\n this.fileIndex = 1;\n this.currentLines = 0;\n this.rotateFile();\n }\n\n // Line-limit rotation\n if (this.currentLines >= this.maxLines) {\n this.fileIndex++;\n this.currentLines = 0;\n this.rotateFile();\n }\n\n const entry: FileLogEntry = {\n timestamp: now.toISOString(),\n level: deriveLevel(log.statusCode),\n service: this.appName,\n method: log.method,\n path: log.path,\n statusCode: log.statusCode,\n responseTime: log.responseTime,\n requestId: randomUUID(),\n clientId: log.clientId,\n ip: log.ip,\n authenticated: log.authenticated,\n };\n\n try {\n appendFileSync(this.currentFilePath!, JSON.stringify(entry) + '\\n');\n this.currentLines++;\n } catch (err) {\n process.stderr.write(`[FileLogWriter] Write error: ${err}\\n`);\n }\n }\n\n destroy(): void {\n this.destroyed = true;\n this.currentFilePath = null;\n }\n\n /** Scan existing log files for a date to determine the next incremental number. */\n private scanExistingFiles(date: string): number {\n try {\n const prefix = `${this.appName}_${date}_`;\n const files = readdirSync(this.logDir).filter(\n f => f.startsWith(prefix) && f.endsWith('.log'),\n );\n if (files.length === 0) return 1;\n\n let maxIndex = 0;\n for (const f of files) {\n // filename: {appName}_{YYYY-MM-DD}_{HHmmss}_{NNN}.log\n const parts = f.replace('.log', '').split('_');\n const idx = parseInt(parts[parts.length - 1], 10);\n if (!isNaN(idx) && idx > maxIndex) maxIndex = idx;\n }\n return maxIndex + 1;\n } catch {\n return 1;\n }\n }\n\n private rotateFile(): void {\n const time = formatTime(new Date());\n const idx = String(this.fileIndex).padStart(3, '0');\n const filename = `${this.appName}_${this.currentDate}_${time}_${idx}.log`;\n this.currentFilePath = join(this.logDir, filename);\n }\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_LOG_MAX_LINES = 10000;\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import type { RequestLog, GatewayAnalytics, LogConfig } from '../../types';\nimport { FileLogWriter } from './FileLogWriter';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n private fileLogWriter: FileLogWriter | null = null;\n\n constructor(logConfig?: LogConfig) {\n if (logConfig) {\n this.fileLogWriter = new FileLogWriter(logConfig);\n }\n }\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n this.fileLogWriter?.write(log);\n }\n\n destroy(): void {\n this.fileLogWriter?.destroy();\n this.fileLogWriter = null;\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { generateSecret } from '../utils/totp';\nimport type { DeviceEntry, DevicesFile } from '../../types';\n\nconst ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\nconst DEBOUNCE_WRITE_MS = 2000;\nconst MAX_REGISTRATIONS_PER_IP_PER_MIN = 10;\nconst MAX_REGISTRATIONS_PER_IP_TOTAL = 30;\nconst RATE_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport class DeviceRegistryService {\n private devices: Map<string, DeviceEntry> = new Map();\n private filePath: string;\n private writeTimeout: ReturnType<typeof setTimeout> | null = null;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n // In-memory rate tracking: ip -> timestamps of recent registration attempts\n private registrationAttempts: Map<string, number[]> = new Map();\n\n constructor(filePath: string) {\n this.filePath = filePath;\n this.loadFromDisk();\n this.cleanupExpired();\n this.cleanupInterval = setInterval(() => this.cleanupExpired(), CLEANUP_INTERVAL_MS);\n }\n\n private loadFromDisk(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const data: DevicesFile = JSON.parse(raw);\n for (const device of data.devices) {\n this.devices.set(device.browserId, device);\n }\n console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);\n } else {\n // Create the directory and empty file\n const dir = path.dirname(this.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n this.saveToDiskSync();\n console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);\n }\n } catch (err) {\n console.error('[DeviceRegistry] Failed to load devices file:', err);\n }\n }\n\n private saveToDiskSync(): void {\n const data: DevicesFile = {\n devices: Array.from(this.devices.values()),\n };\n fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n\n private debouncedSave(): void {\n if (this.writeTimeout) clearTimeout(this.writeTimeout);\n this.writeTimeout = setTimeout(() => {\n this.saveToDiskSync();\n }, DEBOUNCE_WRITE_MS);\n }\n\n private cleanupExpired(): void {\n const now = Date.now();\n let removed = 0;\n for (const [id, device] of this.devices) {\n if (new Date(device.expiresAt).getTime() <= now) {\n this.devices.delete(id);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`[DeviceRegistry] Cleaned up ${removed} expired devices`);\n this.debouncedSave();\n }\n }\n\n private getActiveCountByIp(ip: string): number {\n const now = Date.now();\n let count = 0;\n for (const device of this.devices.values()) {\n if (device.ip === ip && device.active && new Date(device.expiresAt).getTime() > now) {\n count++;\n }\n }\n return count;\n }\n\n private checkRateLimit(ip: string): boolean {\n const now = Date.now();\n const attempts = this.registrationAttempts.get(ip) || [];\n // Remove attempts older than the rate window\n const recent = attempts.filter(t => now - t < RATE_WINDOW_MS);\n this.registrationAttempts.set(ip, recent);\n return recent.length < MAX_REGISTRATIONS_PER_IP_PER_MIN;\n }\n\n private recordAttempt(ip: string): void {\n const attempts = this.registrationAttempts.get(ip) || [];\n attempts.push(Date.now());\n this.registrationAttempts.set(ip, attempts);\n }\n\n registerDevice(\n browserId: string,\n ip: string,\n userAgent: string\n ): { success: true; device: DeviceEntry } | { success: false; error: string; status: number } {\n // Check rate limit: 10 per IP per minute\n if (!this.checkRateLimit(ip)) {\n return {\n success: false,\n error: 'Registration rate limit exceeded. Max 10 per minute per IP.',\n status: 429,\n };\n }\n\n this.recordAttempt(ip);\n\n // Check total cap: 30 active devices per IP\n if (this.getActiveCountByIp(ip) >= MAX_REGISTRATIONS_PER_IP_TOTAL) {\n return {\n success: false,\n error: 'Maximum device registrations reached for this IP. Max 30 per IP.',\n status: 403,\n };\n }\n\n // Check if this browserId is already registered and still valid\n const existing = this.devices.get(browserId);\n if (existing && existing.active && new Date(existing.expiresAt).getTime() > Date.now()) {\n // Re-registration: return existing secret, refresh expiry\n existing.expiresAt = new Date(Date.now() + ONE_WEEK_MS).toISOString();\n existing.lastSeen = new Date().toISOString();\n existing.lastIp = ip;\n this.debouncedSave();\n return { success: true, device: existing };\n }\n\n const now = new Date();\n const device: DeviceEntry = {\n browserId,\n sharedSecret: generateSecret(),\n ip,\n userAgent,\n registeredAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + ONE_WEEK_MS).toISOString(),\n lastSeen: now.toISOString(),\n lastIp: ip,\n active: true,\n };\n\n this.devices.set(browserId, device);\n this.debouncedSave();\n return { success: true, device };\n }\n\n getDevice(browserId: string): DeviceEntry | null {\n const device = this.devices.get(browserId) || null;\n if (!device) return null;\n\n // Check expiry\n if (new Date(device.expiresAt).getTime() <= Date.now()) {\n this.devices.delete(browserId);\n this.debouncedSave();\n return null;\n }\n\n if (!device.active) return null;\n return device;\n }\n\n updateLastSeen(browserId: string, ip: string): void {\n const device = this.devices.get(browserId);\n if (!device) return;\n device.lastSeen = new Date().toISOString();\n if (device.lastIp !== ip) {\n console.log(`[DeviceRegistry] IP change for ${browserId}: ${device.lastIp} -> ${ip}`);\n device.lastIp = ip;\n }\n this.debouncedSave();\n }\n\n revokeDevice(browserId: string): boolean {\n const device = this.devices.get(browserId);\n if (!device) return false;\n device.active = false;\n this.debouncedSave();\n return true;\n }\n\n getStats(): { total: number; active: number; expired: number } {\n const now = Date.now();\n let active = 0;\n let expired = 0;\n for (const device of this.devices.values()) {\n if (!device.active || new Date(device.expiresAt).getTime() <= now) {\n expired++;\n } else {\n active++;\n }\n }\n return { total: this.devices.size, active, expired };\n }\n\n destroy(): void {\n if (this.cleanupInterval) clearInterval(this.cleanupInterval);\n if (this.writeTimeout) {\n clearTimeout(this.writeTimeout);\n this.saveToDiskSync(); // Final save\n }\n }\n}\n","import crypto from 'crypto';\n\nconst TIME_WINDOW_MS = 3600 * 1000; // 1 hour\nconst CODE_LENGTH = 16; // truncated HMAC hex chars\n\nexport function generateTOTP(browserId: string, secret: string, timeOffset: number = 0): string {\n const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;\n const message = `${browserId}:${timeWindow}`;\n const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');\n return hmac.substring(0, CODE_LENGTH);\n}\n\nexport function validateTOTP(browserId: string, secret: string, providedCode: string): boolean {\n const currentCode = generateTOTP(browserId, secret, 0);\n const previousCode = generateTOTP(browserId, secret, -1);\n return timingSafeEqual(providedCode, currentCode) || timingSafeEqual(providedCode, previousCode);\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n return crypto.timingSafeEqual(bufA, bufB);\n}\n\nexport function formatKey(browserId: string, code: string): string {\n return `totp_${browserId}_${code}`;\n}\n\nexport function parseKey(key: string): { browserId: string; code: string } | null {\n if (!key.startsWith('totp_')) return null;\n const parts = key.slice(5).split('_');\n // UUID has 4 dashes, so browserId has 5 parts when split by _\n // Format: totp_<uuid>_<code> where uuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n // But UUID uses dashes, not underscores, so it's a single segment\n if (parts.length < 2) return null;\n const code = parts[parts.length - 1];\n const browserId = parts.slice(0, -1).join('_');\n if (!browserId || !code) return null;\n return { browserId, code };\n}\n\nexport function generateSecret(): string {\n return crypto.randomBytes(32).toString('hex');\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\nimport { parseKey, validateTOTP } from '../utils/totp';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport function createApiKeyAuth(\n getKeys: () => ApiKeysConfig,\n deviceRegistry?: DeviceRegistryService\n) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n // TOTP key: totp_<browserId>_<code>\n if (apiKey.startsWith('totp_') && deviceRegistry) {\n const parsed = parseKey(apiKey);\n if (!parsed) {\n res.status(401).json({ error: 'Malformed TOTP key' });\n return;\n }\n\n const device = deviceRegistry.getDevice(parsed.browserId);\n if (!device) {\n res.status(401).json({ error: 'Device not registered or expired' });\n return;\n }\n\n if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {\n res.status(401).json({ error: 'Invalid or expired TOTP code' });\n return;\n }\n\n // Valid TOTP - update tracking\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n deviceRegistry.updateLastSeen(parsed.browserId, ip);\n\n (req as any).clientId = parsed.browserId;\n (req as any).tier = 'free';\n (req as any).apiKeyValue = apiKey;\n next();\n return;\n }\n\n // Static key: gw_live_*\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n const apiKeyValue = (req as any).apiKeyValue || undefined;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: apiKeyValue,\n authenticated: !!apiKeyValue,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { Router, Request, Response } from 'express';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport interface DeviceAuthRoutesOptions {\n deviceRegistry: DeviceRegistryService;\n}\n\nexport function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router {\n const { deviceRegistry } = options;\n const router = Router();\n\n // POST /register - Register a browser device\n router.post('/register', (req: Request, res: Response) => {\n const { browserId } = req.body;\n\n if (!browserId || typeof browserId !== 'string') {\n res.status(400).json({ error: 'browserId is required' });\n return;\n }\n\n // Basic UUID format validation\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(browserId)) {\n res.status(400).json({ error: 'browserId must be a valid UUID' });\n return;\n }\n\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const userAgent = req.headers['user-agent'] || 'unknown';\n\n const result = deviceRegistry.registerDevice(browserId, ip, userAgent);\n\n if (!result.success) {\n res.status(result.status).json({ error: result.error });\n return;\n }\n\n res.status(201).json({\n browserId: result.device.browserId,\n sharedSecret: result.device.sharedSecret,\n expiresAt: result.device.expiresAt,\n });\n });\n\n // GET /status/:browserId - Check device registration status\n router.get('/status/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const device = deviceRegistry.getDevice(browserId);\n\n if (!device) {\n res.status(404).json({ registered: false });\n return;\n }\n\n res.json({\n registered: true,\n browserId: device.browserId,\n expiresAt: device.expiresAt,\n registeredAt: device.registeredAt,\n });\n });\n\n // DELETE /:browserId - Revoke a device (admin)\n router.delete('/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const revoked = deviceRegistry.revokeDevice(browserId);\n\n if (!revoked) {\n res.status(404).json({ error: 'Device not found' });\n return;\n }\n\n res.json({ message: 'Device revoked', browserId });\n });\n\n // GET /stats - Device registry stats\n router.get('/stats', (_req: Request, res: Response) => {\n res.json(deviceRegistry.getStats());\n });\n\n return router;\n}\n"],"mappings":"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","names":["resetMs","path","import_fs","import_path","import_crypto","crypto","fs","path","import_express","import_crypto","crypto","import_express"]}
@@ -126,6 +126,140 @@ var RateLimiterService = class {
126
126
  }
127
127
  };
128
128
 
129
+ // src/backend/services/FileLogWriter.ts
130
+ import { mkdirSync, readdirSync, appendFileSync } from "fs";
131
+ import { join } from "path";
132
+ import { randomUUID } from "crypto";
133
+
134
+ // src/config/defaults.ts
135
+ var DEFAULT_LOG_MAX_LINES = 1e4;
136
+ var DEFAULT_RATE_LIMIT_CONFIG = {
137
+ tiers: {
138
+ free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
139
+ pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
140
+ unlimited: { algorithm: "none" }
141
+ },
142
+ defaultTier: "free",
143
+ globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
144
+ };
145
+ var DEFAULT_IP_RULES = {
146
+ allowlist: [],
147
+ blocklist: [],
148
+ mode: "blocklist"
149
+ };
150
+ var DEFAULT_API_KEYS = {
151
+ keys: []
152
+ };
153
+
154
+ // src/backend/services/FileLogWriter.ts
155
+ function deriveLevel(statusCode) {
156
+ if (statusCode < 400) return "info";
157
+ if (statusCode < 500) return "warn";
158
+ if (statusCode === 503) return "fatal";
159
+ return "error";
160
+ }
161
+ function formatDate(date) {
162
+ const y = date.getFullYear();
163
+ const m = String(date.getMonth() + 1).padStart(2, "0");
164
+ const d = String(date.getDate()).padStart(2, "0");
165
+ return `${y}-${m}-${d}`;
166
+ }
167
+ function formatTime(date) {
168
+ const h = String(date.getHours()).padStart(2, "0");
169
+ const min = String(date.getMinutes()).padStart(2, "0");
170
+ const s = String(date.getSeconds()).padStart(2, "0");
171
+ return `${h}${min}${s}`;
172
+ }
173
+ var FileLogWriter = class {
174
+ logDir;
175
+ appName;
176
+ maxLines;
177
+ currentDate;
178
+ currentLines = 0;
179
+ fileIndex = 0;
180
+ currentFilePath = null;
181
+ destroyed = false;
182
+ constructor(config) {
183
+ this.logDir = config.logDir;
184
+ this.appName = config.appName;
185
+ this.maxLines = config.maxLinesPerFile ?? DEFAULT_LOG_MAX_LINES;
186
+ this.currentDate = formatDate(/* @__PURE__ */ new Date());
187
+ try {
188
+ mkdirSync(this.logDir, { recursive: true });
189
+ } catch (err) {
190
+ process.stderr.write(`[FileLogWriter] Failed to create log directory: ${err}
191
+ `);
192
+ }
193
+ this.fileIndex = this.scanExistingFiles(this.currentDate);
194
+ this.rotateFile();
195
+ }
196
+ write(log) {
197
+ if (this.destroyed) return;
198
+ const now = new Date(log.timestamp);
199
+ const today = formatDate(now);
200
+ if (today !== this.currentDate) {
201
+ this.currentDate = today;
202
+ this.fileIndex = 1;
203
+ this.currentLines = 0;
204
+ this.rotateFile();
205
+ }
206
+ if (this.currentLines >= this.maxLines) {
207
+ this.fileIndex++;
208
+ this.currentLines = 0;
209
+ this.rotateFile();
210
+ }
211
+ const entry = {
212
+ timestamp: now.toISOString(),
213
+ level: deriveLevel(log.statusCode),
214
+ service: this.appName,
215
+ method: log.method,
216
+ path: log.path,
217
+ statusCode: log.statusCode,
218
+ responseTime: log.responseTime,
219
+ requestId: randomUUID(),
220
+ clientId: log.clientId,
221
+ ip: log.ip,
222
+ authenticated: log.authenticated
223
+ };
224
+ try {
225
+ appendFileSync(this.currentFilePath, JSON.stringify(entry) + "\n");
226
+ this.currentLines++;
227
+ } catch (err) {
228
+ process.stderr.write(`[FileLogWriter] Write error: ${err}
229
+ `);
230
+ }
231
+ }
232
+ destroy() {
233
+ this.destroyed = true;
234
+ this.currentFilePath = null;
235
+ }
236
+ /** Scan existing log files for a date to determine the next incremental number. */
237
+ scanExistingFiles(date) {
238
+ try {
239
+ const prefix = `${this.appName}_${date}_`;
240
+ const files = readdirSync(this.logDir).filter(
241
+ (f) => f.startsWith(prefix) && f.endsWith(".log")
242
+ );
243
+ if (files.length === 0) return 1;
244
+ let maxIndex = 0;
245
+ for (const f of files) {
246
+ const parts = f.replace(".log", "").split("_");
247
+ const idx = parseInt(parts[parts.length - 1], 10);
248
+ if (!isNaN(idx) && idx > maxIndex) maxIndex = idx;
249
+ }
250
+ return maxIndex + 1;
251
+ } catch {
252
+ return 1;
253
+ }
254
+ }
255
+ rotateFile() {
256
+ const time = formatTime(/* @__PURE__ */ new Date());
257
+ const idx = String(this.fileIndex).padStart(3, "0");
258
+ const filename = `${this.appName}_${this.currentDate}_${time}_${idx}.log`;
259
+ this.currentFilePath = join(this.logDir, filename);
260
+ }
261
+ };
262
+
129
263
  // src/backend/services/AnalyticsService.ts
130
264
  var MAX_LOG_SIZE = 1e4;
131
265
  var ACTIVE_WINDOW_MS = 3e5;
@@ -133,6 +267,12 @@ var AnalyticsService = class {
133
267
  logs = [];
134
268
  head = 0;
135
269
  count = 0;
270
+ fileLogWriter = null;
271
+ constructor(logConfig) {
272
+ if (logConfig) {
273
+ this.fileLogWriter = new FileLogWriter(logConfig);
274
+ }
275
+ }
136
276
  addLog(log) {
137
277
  if (this.count < MAX_LOG_SIZE) {
138
278
  this.logs.push(log);
@@ -141,6 +281,11 @@ var AnalyticsService = class {
141
281
  this.logs[this.head] = log;
142
282
  this.head = (this.head + 1) % MAX_LOG_SIZE;
143
283
  }
284
+ this.fileLogWriter?.write(log);
285
+ }
286
+ destroy() {
287
+ this.fileLogWriter?.destroy();
288
+ this.fileLogWriter = null;
144
289
  }
145
290
  getRecentLogs(limit = 20, offset = 0) {
146
291
  const ordered = this.getOrderedLogs();
@@ -412,25 +557,6 @@ var DeviceRegistryService = class {
412
557
  }
413
558
  };
414
559
 
415
- // src/config/defaults.ts
416
- var DEFAULT_RATE_LIMIT_CONFIG = {
417
- tiers: {
418
- free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
419
- pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
420
- unlimited: { algorithm: "none" }
421
- },
422
- defaultTier: "free",
423
- globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
424
- };
425
- var DEFAULT_IP_RULES = {
426
- allowlist: [],
427
- blocklist: [],
428
- mode: "blocklist"
429
- };
430
- var DEFAULT_API_KEYS = {
431
- keys: []
432
- };
433
-
434
560
  // src/backend/middleware/apiKeyAuth.ts
435
561
  function createApiKeyAuth(getKeys, deviceRegistry) {
436
562
  return function apiKeyAuth(req, res, next) {
@@ -549,10 +675,11 @@ function createGatewayMiddleware(userConfig) {
549
675
  rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,
550
676
  ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
551
677
  apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,
552
- deviceRegistryPath: userConfig?.deviceRegistryPath ?? ""
678
+ deviceRegistryPath: userConfig?.deviceRegistryPath ?? "",
679
+ logConfig: userConfig?.logConfig
553
680
  };
554
681
  const rateLimiterService = new RateLimiterService(config.rateLimits);
555
- const analyticsService = new AnalyticsService();
682
+ const analyticsService = new AnalyticsService(userConfig?.logConfig);
556
683
  let deviceRegistry;
557
684
  if (config.deviceRegistryPath) {
558
685
  deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);
@@ -704,6 +831,7 @@ function createDeviceAuthRoutes(options) {
704
831
  export {
705
832
  AnalyticsService,
706
833
  DeviceRegistryService,
834
+ FileLogWriter,
707
835
  RateLimiterService,
708
836
  createApiKeyAuth,
709
837
  createDeviceAuthRoutes,