@gagandeep023/api-gateway 0.3.0 → 0.3.1

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/frontend/GatewayDashboard.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n apiKey?: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string, apiKey?: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n fetch(`${apiBaseUrl}${path}`, { headers })\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path, apiKey]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl, apiKey }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config', apiKey);\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20', apiKey);\n const eventSourceRef = useRef<EventSource | null>(null);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE', headers });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);\n eventSourceRef.current = es;\n\n es.onmessage = (event) => {\n const data: GatewayAnalytics = JSON.parse(event.data);\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n };\n\n return () => {\n es.close();\n };\n }, [apiBaseUrl]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n <th>Auth</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n <td>\n <span className={`gw-auth-badge ${log.authenticated ? 'gw-auth-yes' : 'gw-auth-no'}`}>\n {log.authenticated ? 'key' : 'none'}\n </span>\n </td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={7} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/frontend/GatewayDashboard.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n apiKey?: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string, apiKey?: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n fetch(`${apiBaseUrl}${path}`, { headers })\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path, apiKey]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl, apiKey }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config', apiKey);\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20', apiKey);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE', headers });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n let cancelled = false;\n\n async function connectSSE() {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n\n try {\n const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });\n if (!res.ok || !res.body) return;\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (!cancelled) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data: GatewayAnalytics = JSON.parse(line.slice(6));\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n }\n }\n\n reader.cancel();\n } catch {\n // connection closed or failed\n }\n }\n\n connectSSE();\n\n return () => {\n cancelled = true;\n };\n }, [apiBaseUrl, apiKey]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n <th>Auth</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n <td>\n <span className={`gw-auth-badge ${log.authenticated ? 'gw-auth-yes' : 'gw-auth-no'}`}>\n {log.authenticated ? 'key' : 'none'}\n </span>\n </td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={7} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -384,7 +384,6 @@ function createGatewayRoutes(options) {
384
384
  res.setHeader("Cache-Control", "no-cache");
385
385
  res.setHeader("Connection", "keep-alive");
386
386
  res.setHeader("X-Accel-Buffering", "no");
387
- res.setHeader("Access-Control-Allow-Origin", "*");
388
387
  res.flushHeaders();
389
388
  const send = () => {
390
389
  const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
@@ -462,7 +461,6 @@ function GatewayDashboard({ apiBaseUrl, apiKey }) {
462
461
  const [rpmHistory, setRpmHistory] = (0, import_react.useState)([]);
463
462
  const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config", apiKey);
464
463
  const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20", apiKey);
465
- const eventSourceRef = (0, import_react.useRef)(null);
466
464
  const [keyName, setKeyName] = (0, import_react.useState)("");
467
465
  const [keyTier, setKeyTier] = (0, import_react.useState)("free");
468
466
  const [createdKeys, setCreatedKeys] = (0, import_react.useState)([]);
@@ -516,23 +514,44 @@ function GatewayDashboard({ apiBaseUrl, apiKey }) {
516
514
  });
517
515
  };
518
516
  (0, import_react.useEffect)(() => {
519
- const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);
520
- eventSourceRef.current = es;
521
- es.onmessage = (event) => {
522
- const data = JSON.parse(event.data);
523
- setAnalytics(data);
524
- setRpmHistory((prev) => {
525
- const next = [
526
- ...prev,
527
- { time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
528
- ];
529
- return next.slice(-20);
530
- });
531
- };
517
+ let cancelled = false;
518
+ async function connectSSE() {
519
+ const headers = {};
520
+ if (apiKey) headers["X-API-Key"] = apiKey;
521
+ try {
522
+ const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });
523
+ if (!res.ok || !res.body) return;
524
+ const reader = res.body.getReader();
525
+ const decoder = new TextDecoder();
526
+ let buffer = "";
527
+ while (!cancelled) {
528
+ const { done, value } = await reader.read();
529
+ if (done) break;
530
+ buffer += decoder.decode(value, { stream: true });
531
+ const lines = buffer.split("\n");
532
+ buffer = lines.pop() || "";
533
+ for (const line of lines) {
534
+ if (!line.startsWith("data: ")) continue;
535
+ const data = JSON.parse(line.slice(6));
536
+ setAnalytics(data);
537
+ setRpmHistory((prev) => {
538
+ const next = [
539
+ ...prev,
540
+ { time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
541
+ ];
542
+ return next.slice(-20);
543
+ });
544
+ }
545
+ }
546
+ reader.cancel();
547
+ } catch {
548
+ }
549
+ }
550
+ connectSSE();
532
551
  return () => {
533
- es.close();
552
+ cancelled = true;
534
553
  };
535
- }, [apiBaseUrl]);
554
+ }, [apiBaseUrl, apiKey]);
536
555
  const getMethodClass = (method) => {
537
556
  switch (method) {
538
557
  case "GET":
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/backend/middleware/gateway.ts","../src/backend/services/RateLimiterService.ts","../src/backend/services/AnalyticsService.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/frontend/GatewayDashboard.tsx"],"sourcesContent":["// Backend exports\nexport { createGatewayMiddleware } from './backend/middleware/gateway';\nexport type { GatewayInstances } from './backend/middleware/gateway';\nexport { createGatewayRoutes } from './backend/routes/gateway';\nexport type { GatewayRoutesOptions } from './backend/routes/gateway';\nexport { RateLimiterService } from './backend/services/RateLimiterService';\nexport { AnalyticsService } from './backend/services/AnalyticsService';\nexport { createApiKeyAuth } from './backend/middleware/apiKeyAuth';\nexport { createIpFilter } from './backend/middleware/ipFilter';\nexport { createRateLimiter } from './backend/middleware/rateLimiter';\nexport { createRequestLogger } from './backend/middleware/requestLogger';\n\n// Frontend exports\nexport { GatewayDashboard } from './frontend/GatewayDashboard';\nexport type { GatewayDashboardProps } from './frontend/GatewayDashboard';\n\n// Type exports\nexport type {\n ApiKey,\n ApiKeysConfig,\n TierConfig,\n RateLimitConfig,\n IpRules,\n BucketState,\n SlidingWindowState,\n FixedWindowState,\n RequestLog,\n GatewayAnalytics,\n GatewayConfig,\n GatewayMiddlewareConfig,\n} from './types';\n\n// Config exports\nexport {\n DEFAULT_RATE_LIMIT_CONFIG,\n DEFAULT_IP_RULES,\n DEFAULT_API_KEYS,\n} from './config/defaults';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\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 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 };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\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 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';\n\nexport function createApiKeyAuth(getKeys: () => ApiKeysConfig) {\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 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 { ApiKeysConfig, 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.setHeader('Access-Control-Allow-Origin', '*');\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 { useState, useEffect, useRef } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n apiKey?: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string, apiKey?: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n fetch(`${apiBaseUrl}${path}`, { headers })\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path, apiKey]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl, apiKey }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config', apiKey);\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20', apiKey);\n const eventSourceRef = useRef<EventSource | null>(null);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE', headers });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);\n eventSourceRef.current = es;\n\n es.onmessage = (event) => {\n const data: GatewayAnalytics = JSON.parse(event.data);\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n };\n\n return () => {\n es.close();\n };\n }, [apiBaseUrl]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n <th>Auth</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n <td>\n <span className={`gw-auth-badge ${log.authenticated ? 'gw-auth-yes' : 'gw-auth-no'}`}>\n {log.authenticated ? 'key' : 'none'}\n </span>\n </td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={7} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["resetMs","import_express","crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/backend/middleware/gateway.ts","../src/backend/services/RateLimiterService.ts","../src/backend/services/AnalyticsService.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/frontend/GatewayDashboard.tsx"],"sourcesContent":["// Backend exports\nexport { createGatewayMiddleware } from './backend/middleware/gateway';\nexport type { GatewayInstances } from './backend/middleware/gateway';\nexport { createGatewayRoutes } from './backend/routes/gateway';\nexport type { GatewayRoutesOptions } from './backend/routes/gateway';\nexport { RateLimiterService } from './backend/services/RateLimiterService';\nexport { AnalyticsService } from './backend/services/AnalyticsService';\nexport { createApiKeyAuth } from './backend/middleware/apiKeyAuth';\nexport { createIpFilter } from './backend/middleware/ipFilter';\nexport { createRateLimiter } from './backend/middleware/rateLimiter';\nexport { createRequestLogger } from './backend/middleware/requestLogger';\n\n// Frontend exports\nexport { GatewayDashboard } from './frontend/GatewayDashboard';\nexport type { GatewayDashboardProps } from './frontend/GatewayDashboard';\n\n// Type exports\nexport type {\n ApiKey,\n ApiKeysConfig,\n TierConfig,\n RateLimitConfig,\n IpRules,\n BucketState,\n SlidingWindowState,\n FixedWindowState,\n RequestLog,\n GatewayAnalytics,\n GatewayConfig,\n GatewayMiddlewareConfig,\n} from './types';\n\n// Config exports\nexport {\n DEFAULT_RATE_LIMIT_CONFIG,\n DEFAULT_IP_RULES,\n DEFAULT_API_KEYS,\n} from './config/defaults';\n","import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\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 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 };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\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 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';\n\nexport function createApiKeyAuth(getKeys: () => ApiKeysConfig) {\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 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 { useState, useEffect } from 'react';\nimport { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';\nimport type { GatewayAnalytics, GatewayConfig, RequestLog, ApiKey } from '../types';\n\nexport interface GatewayDashboardProps {\n apiBaseUrl: string;\n apiKey?: string;\n}\n\ninterface LogsResponse {\n logs: RequestLog[];\n limit: number;\n offset: number;\n}\n\ninterface CreatedKey extends ApiKey {\n justCreated?: boolean;\n}\n\nfunction useGatewayApi<T>(apiBaseUrl: string, path: string, apiKey?: string): { data: T | null } {\n const [data, setData] = useState<T | null>(null);\n useEffect(() => {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n fetch(`${apiBaseUrl}${path}`, { headers })\n .then(r => r.json())\n .then(setData)\n .catch(() => {});\n }, [apiBaseUrl, path, apiKey]);\n return { data };\n}\n\nexport function GatewayDashboard({ apiBaseUrl, apiKey }: GatewayDashboardProps) {\n const [analytics, setAnalytics] = useState<GatewayAnalytics | null>(null);\n const [rpmHistory, setRpmHistory] = useState<{ time: string; rpm: number }[]>([]);\n const { data: config } = useGatewayApi<GatewayConfig>(apiBaseUrl, '/gateway/config', apiKey);\n const { data: logsData } = useGatewayApi<LogsResponse>(apiBaseUrl, '/gateway/logs?limit=20', apiKey);\n const [keyName, setKeyName] = useState('');\n const [keyTier, setKeyTier] = useState('free');\n const [createdKeys, setCreatedKeys] = useState<CreatedKey[]>([]);\n const [keyError, setKeyError] = useState('');\n const [keyLoading, setKeyLoading] = useState(false);\n const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null);\n\n const handleCreateKey = async () => {\n if (!keyName.trim()) {\n setKeyError('Name is required');\n return;\n }\n setKeyError('');\n setKeyLoading(true);\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ name: keyName.trim(), tier: keyTier }),\n });\n if (!res.ok) {\n const err = await res.json();\n setKeyError(err.error || 'Failed to create key');\n return;\n }\n const newKey: ApiKey = await res.json();\n setCreatedKeys(prev => [{ ...newKey, justCreated: true }, ...prev]);\n setKeyName('');\n } catch {\n setKeyError('Network error');\n } finally {\n setKeyLoading(false);\n }\n };\n\n const handleRevokeKey = async (keyId: string) => {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n const res = await fetch(`${apiBaseUrl}/gateway/keys/${keyId}`, { method: 'DELETE', headers });\n if (res.ok) {\n setCreatedKeys(prev => prev.map(k => k.id === keyId ? { ...k, active: false } : k));\n }\n } catch {}\n };\n\n const handleCopyKey = (key: string, keyId: string) => {\n navigator.clipboard.writeText(key).then(() => {\n setCopiedKeyId(keyId);\n setTimeout(() => setCopiedKeyId(null), 2000);\n });\n };\n\n useEffect(() => {\n let cancelled = false;\n\n async function connectSSE() {\n const headers: Record<string, string> = {};\n if (apiKey) headers['X-API-Key'] = apiKey;\n\n try {\n const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });\n if (!res.ok || !res.body) return;\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (!cancelled) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data: GatewayAnalytics = JSON.parse(line.slice(6));\n setAnalytics(data);\n setRpmHistory(prev => {\n const next = [\n ...prev,\n { time: new Date().toLocaleTimeString(), rpm: data.requestsPerMinute },\n ];\n return next.slice(-20);\n });\n }\n }\n\n reader.cancel();\n } catch {\n // connection closed or failed\n }\n }\n\n connectSSE();\n\n return () => {\n cancelled = true;\n };\n }, [apiBaseUrl, apiKey]);\n\n const getMethodClass = (method: string) => {\n switch (method) {\n case 'GET': return 'gw-method-get';\n case 'POST': return 'gw-method-post';\n case 'DELETE': return 'gw-method-delete';\n default: return 'gw-method-get';\n }\n };\n\n const getStatusClass = (code: number) => {\n if (code === 429) return 'gw-status-rate-limit';\n if (code >= 400) return 'gw-status-error';\n return 'gw-status-ok';\n };\n\n return (\n <div className=\"gw-dashboard\">\n <div className=\"gw-header\">\n <h1>API Gateway Dashboard</h1>\n <p>Real-time monitoring for the API gateway and rate limiter</p>\n <div className=\"gw-status-badge\">\n <span className=\"gw-status-dot\" />\n Live\n </div>\n </div>\n\n {/* Stats Grid */}\n <div className=\"gw-stats-grid\">\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Total Requests</div>\n <div className=\"gw-stat-value\">{analytics?.totalRequests ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Requests / Min</div>\n <div className=\"gw-stat-value gw-accent\">\n {analytics?.requestsPerMinute ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Error Rate</div>\n <div className={`gw-stat-value ${analytics && analytics.errorRate > 5 ? 'gw-danger' : ''}`}>\n {analytics?.errorRate ?? 0}%\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Avg Response Time</div>\n <div className=\"gw-stat-value\">{analytics?.avgResponseTime ?? 0}ms</div>\n </div>\n </div>\n\n {/* Second Row Stats */}\n <div className=\"gw-stats-grid\" style={{ marginBottom: 32 }}>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Hits</div>\n <div className=\"gw-stat-value gw-warning\">\n {analytics?.rateLimitHits ?? 0}\n </div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active IPs</div>\n <div className=\"gw-stat-value\">{analytics?.activeClients ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Active Key Sessions</div>\n <div className=\"gw-stat-value\">{analytics?.activeKeyUses ?? 0}</div>\n </div>\n <div className=\"gw-stat-card\">\n <div className=\"gw-stat-label\">Rate Limit Tiers</div>\n <div className=\"gw-stat-value\">\n {config ? Object.keys(config.rateLimits.tiers).length : 0}\n </div>\n </div>\n </div>\n\n {/* Charts Row */}\n <div className=\"gw-charts-row\">\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Requests Per Minute</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <LineChart data={rpmHistory}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis dataKey=\"time\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n labelStyle={{ color: 'var(--gw-text-muted, #888)' }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"rpm\"\n stroke=\"var(--gw-accent, #64ffda)\"\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--gw-accent, #64ffda)' }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n\n <div className=\"gw-chart-card\">\n <div className=\"gw-chart-title\">Top Endpoints</div>\n <ResponsiveContainer width=\"100%\" height={250}>\n <BarChart\n data={analytics?.topEndpoints ?? []}\n layout=\"vertical\"\n >\n <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--gw-border, #2a2a2a)\" />\n <XAxis type=\"number\" tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 11 }} />\n <YAxis\n dataKey=\"path\"\n type=\"category\"\n tick={{ fill: 'var(--gw-text-muted, #888)', fontSize: 10 }}\n width={120}\n />\n <Tooltip\n contentStyle={{ background: 'var(--gw-bg-card, #1a1a1a)', border: '1px solid var(--gw-border, #2a2a2a)', borderRadius: 8 }}\n />\n <Bar dataKey=\"count\" fill=\"var(--gw-accent, #64ffda)\" radius={[0, 4, 4, 0]} />\n </BarChart>\n </ResponsiveContainer>\n </div>\n </div>\n\n {/* Recent Logs */}\n <div className=\"gw-logs-section\">\n <div className=\"gw-logs-title\">Recent Requests</div>\n <table className=\"gw-logs-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Method</th>\n <th>Path</th>\n <th>Status</th>\n <th>Duration</th>\n <th>IP</th>\n <th>Auth</th>\n </tr>\n </thead>\n <tbody>\n {(logsData?.logs ?? []).map((log, i) => (\n <tr key={i}>\n <td>{new Date(log.timestamp).toLocaleTimeString()}</td>\n <td>\n <span className={`gw-method-badge ${getMethodClass(log.method)}`}>\n {log.method}\n </span>\n </td>\n <td>{log.path}</td>\n <td className={getStatusClass(log.statusCode)}>{log.statusCode}</td>\n <td>{log.responseTime}ms</td>\n <td>{log.ip}</td>\n <td>\n <span className={`gw-auth-badge ${log.authenticated ? 'gw-auth-yes' : 'gw-auth-no'}`}>\n {log.authenticated ? 'key' : 'none'}\n </span>\n </td>\n </tr>\n ))}\n {(!logsData?.logs || logsData.logs.length === 0) && (\n <tr>\n <td colSpan={7} style={{ textAlign: 'center', color: 'var(--gw-text-muted, #666)' }}>\n No requests logged yet. Make some API calls to see data here.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Config Section */}\n {config && (\n <div className=\"gw-config-section\">\n <div className=\"gw-config-card\">\n <h3>Rate Limit Tiers</h3>\n {Object.entries(config.rateLimits.tiers).map(([name, tier]) => (\n <div key={name} className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">{name}</span>\n <span className=\"gw-tier-detail\">\n {tier.algorithm === 'none'\n ? 'unlimited'\n : `${tier.maxRequests} req / ${(tier.windowMs || 60000) / 1000}s`}\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"gw-config-card\">\n <h3>IP Rules</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Mode</span>\n <span className=\"gw-tier-detail\">{config.ipRules.mode}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Allowlist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.allowlist.length === 0 ? 'empty' : config.ipRules.allowlist.length + ' IPs'}\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Blocklist</span>\n <span className=\"gw-tier-detail\">\n {config.ipRules.blocklist.length === 0 ? 'empty' : config.ipRules.blocklist.length + ' IPs'}\n </span>\n </div>\n </div>\n\n <div className=\"gw-config-card\">\n <h3>Global Limit</h3>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Max Requests</span>\n <span className=\"gw-tier-detail\">\n {config.rateLimits.globalLimit.maxRequests} / {config.rateLimits.globalLimit.windowMs / 1000}s\n </span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Default Tier</span>\n <span className=\"gw-tier-detail\">{config.rateLimits.defaultTier}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Keys</span>\n <span className=\"gw-tier-detail\">{config.activeKeys}</span>\n </div>\n <div className=\"gw-tier-item\">\n <span className=\"gw-tier-name\">Active Key Sessions</span>\n <span className=\"gw-tier-detail\">{config.activeKeyUses}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* API Key Management */}\n <div className=\"gw-keys-section\">\n <div className=\"gw-keys-header\">\n <h2>API Keys</h2>\n <p>Create keys to authenticate API requests. Each key is tied to a rate limit tier.</p>\n </div>\n\n <div className=\"gw-keys-create\">\n <div className=\"gw-keys-form\">\n <input\n type=\"text\"\n className=\"gw-keys-input\"\n placeholder=\"Key name (e.g. My App)\"\n value={keyName}\n onChange={e => setKeyName(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleCreateKey()}\n />\n <select\n className=\"gw-keys-select\"\n value={keyTier}\n onChange={e => setKeyTier(e.target.value)}\n >\n {config && Object.keys(config.rateLimits.tiers).map(tier => (\n <option key={tier} value={tier}>{tier}</option>\n ))}\n </select>\n <button\n className=\"gw-keys-btn\"\n onClick={handleCreateKey}\n disabled={keyLoading}\n >\n {keyLoading ? 'Creating...' : 'Create Key'}\n </button>\n </div>\n {keyError && <div className=\"gw-keys-error\">{keyError}</div>}\n </div>\n\n {createdKeys.length > 0 && (\n <div className=\"gw-keys-list\">\n {createdKeys.map(k => (\n <div key={k.id} className={`gw-key-card ${!k.active ? 'gw-key-revoked' : ''}`}>\n <div className=\"gw-key-top\">\n <span className=\"gw-key-name\">{k.name}</span>\n <div className=\"gw-key-badges\">\n <span className=\"gw-key-tier\">{k.tier}</span>\n <span className={`gw-key-status ${k.active ? 'gw-key-active' : 'gw-key-inactive'}`}>\n {k.active ? 'active' : 'revoked'}\n </span>\n </div>\n </div>\n <div className=\"gw-key-value\">\n <code>{k.key}</code>\n {k.active && (\n <button\n className=\"gw-key-copy\"\n onClick={() => handleCopyKey(k.key, k.id)}\n >\n {copiedKeyId === k.id ? 'Copied!' : 'Copy'}\n </button>\n )}\n </div>\n <div className=\"gw-key-bottom\">\n <span className=\"gw-key-id\">{k.id}</span>\n {k.active && (\n <button\n className=\"gw-key-revoke\"\n onClick={() => handleRevokeKey(k.id)}\n >\n Revoke\n </button>\n )}\n </div>\n {k.justCreated && (\n <div className=\"gw-key-usage\">\n <span className=\"gw-key-usage-label\">Usage:</span>\n <code>curl -H \"X-API-Key: {k.key}\" {apiBaseUrl}/health</code>\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["resetMs","import_express","crypto"]}
package/dist/index.mjs CHANGED
@@ -337,7 +337,6 @@ function createGatewayRoutes(options) {
337
337
  res.setHeader("Cache-Control", "no-cache");
338
338
  res.setHeader("Connection", "keep-alive");
339
339
  res.setHeader("X-Accel-Buffering", "no");
340
- res.setHeader("Access-Control-Allow-Origin", "*");
341
340
  res.flushHeaders();
342
341
  const send = () => {
343
342
  const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);
@@ -397,7 +396,7 @@ function createGatewayRoutes(options) {
397
396
  }
398
397
 
399
398
  // src/frontend/GatewayDashboard.tsx
400
- import { useState, useEffect, useRef } from "react";
399
+ import { useState, useEffect } from "react";
401
400
  import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
402
401
  import { jsx, jsxs } from "react/jsx-runtime";
403
402
  function useGatewayApi(apiBaseUrl, path, apiKey) {
@@ -415,7 +414,6 @@ function GatewayDashboard({ apiBaseUrl, apiKey }) {
415
414
  const [rpmHistory, setRpmHistory] = useState([]);
416
415
  const { data: config } = useGatewayApi(apiBaseUrl, "/gateway/config", apiKey);
417
416
  const { data: logsData } = useGatewayApi(apiBaseUrl, "/gateway/logs?limit=20", apiKey);
418
- const eventSourceRef = useRef(null);
419
417
  const [keyName, setKeyName] = useState("");
420
418
  const [keyTier, setKeyTier] = useState("free");
421
419
  const [createdKeys, setCreatedKeys] = useState([]);
@@ -469,23 +467,44 @@ function GatewayDashboard({ apiBaseUrl, apiKey }) {
469
467
  });
470
468
  };
471
469
  useEffect(() => {
472
- const es = new EventSource(`${apiBaseUrl}/gateway/analytics/live`);
473
- eventSourceRef.current = es;
474
- es.onmessage = (event) => {
475
- const data = JSON.parse(event.data);
476
- setAnalytics(data);
477
- setRpmHistory((prev) => {
478
- const next = [
479
- ...prev,
480
- { time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
481
- ];
482
- return next.slice(-20);
483
- });
484
- };
470
+ let cancelled = false;
471
+ async function connectSSE() {
472
+ const headers = {};
473
+ if (apiKey) headers["X-API-Key"] = apiKey;
474
+ try {
475
+ const res = await fetch(`${apiBaseUrl}/gateway/analytics/live`, { headers });
476
+ if (!res.ok || !res.body) return;
477
+ const reader = res.body.getReader();
478
+ const decoder = new TextDecoder();
479
+ let buffer = "";
480
+ while (!cancelled) {
481
+ const { done, value } = await reader.read();
482
+ if (done) break;
483
+ buffer += decoder.decode(value, { stream: true });
484
+ const lines = buffer.split("\n");
485
+ buffer = lines.pop() || "";
486
+ for (const line of lines) {
487
+ if (!line.startsWith("data: ")) continue;
488
+ const data = JSON.parse(line.slice(6));
489
+ setAnalytics(data);
490
+ setRpmHistory((prev) => {
491
+ const next = [
492
+ ...prev,
493
+ { time: (/* @__PURE__ */ new Date()).toLocaleTimeString(), rpm: data.requestsPerMinute }
494
+ ];
495
+ return next.slice(-20);
496
+ });
497
+ }
498
+ }
499
+ reader.cancel();
500
+ } catch {
501
+ }
502
+ }
503
+ connectSSE();
485
504
  return () => {
486
- es.close();
505
+ cancelled = true;
487
506
  };
488
- }, [apiBaseUrl]);
507
+ }, [apiBaseUrl, apiKey]);
489
508
  const getMethodClass = (method) => {
490
509
  switch (method) {
491
510
  case "GET":