agentic-flow 1.9.4 → 1.10.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.
- package/CHANGELOG.md +246 -0
- package/dist/proxy/adaptive-proxy.js +224 -0
- package/dist/proxy/anthropic-to-gemini.js +2 -2
- package/dist/proxy/http2-proxy-optimized.js +191 -0
- package/dist/proxy/http2-proxy.js +381 -0
- package/dist/proxy/http3-proxy-old.js +331 -0
- package/dist/proxy/http3-proxy.js +51 -0
- package/dist/proxy/websocket-proxy.js +406 -0
- package/dist/utils/adaptive-pool-sizing.js +414 -0
- package/dist/utils/auth.js +52 -0
- package/dist/utils/circular-rate-limiter.js +391 -0
- package/dist/utils/compression-middleware.js +149 -0
- package/dist/utils/connection-pool.js +184 -0
- package/dist/utils/dynamic-compression.js +298 -0
- package/dist/utils/http2-multiplexing.js +319 -0
- package/dist/utils/lazy-auth.js +311 -0
- package/dist/utils/rate-limiter.js +48 -0
- package/dist/utils/response-cache.js +211 -0
- package/dist/utils/server-push.js +251 -0
- package/dist/utils/streaming-optimizer.js +141 -0
- package/dist/utils/zero-copy-buffer.js +286 -0
- package/docs/.claude-flow/metrics/performance.json +3 -3
- package/docs/.claude-flow/metrics/task-metrics.json +3 -3
- package/docs/DOCKER-VERIFICATION.md +207 -0
- package/docs/ISSUE-55-VALIDATION.md +171 -0
- package/docs/NPX_AGENTDB_SETUP.md +175 -0
- package/docs/OPTIMIZATIONS.md +460 -0
- package/docs/PHASE2-IMPLEMENTATION-SUMMARY.md +275 -0
- package/docs/PHASE2-PHASE3-COMPLETE-SUMMARY.md +453 -0
- package/docs/PHASE3-IMPLEMENTATION-SUMMARY.md +357 -0
- package/docs/PUBLISH_GUIDE.md +438 -0
- package/docs/README.md +217 -0
- package/docs/RELEASE-v1.10.0-COMPLETE.md +382 -0
- package/docs/archive/.agentdb-instructions.md +66 -0
- package/docs/archive/AGENT-BOOSTER-STATUS.md +292 -0
- package/docs/archive/CHANGELOG-v1.3.0.md +120 -0
- package/docs/archive/COMPLETION_REPORT_v1.7.1.md +335 -0
- package/docs/archive/IMPLEMENTATION_SUMMARY_v1.7.1.md +241 -0
- package/docs/archive/SUPABASE-INTEGRATION-COMPLETE.md +357 -0
- package/docs/archive/TESTING_QUICK_START.md +223 -0
- package/docs/archive/TOOL-EMULATION-INTEGRATION-ISSUE.md +669 -0
- package/docs/archive/VALIDATION_v1.7.1.md +234 -0
- package/docs/issues/ISSUE-xenova-transformers-dependency.md +380 -0
- package/docs/releases/PUBLISH_CHECKLIST_v1.10.0.md +396 -0
- package/docs/releases/PUBLISH_SUMMARY_v1.7.1.md +198 -0
- package/docs/releases/RELEASE_NOTES_v1.10.0.md +464 -0
- package/docs/releases/RELEASE_NOTES_v1.7.0.md +297 -0
- package/docs/releases/RELEASE_v1.7.1.md +327 -0
- package/package.json +1 -1
- package/scripts/claude +31 -0
- package/validation/docker-npm-validation.sh +170 -0
- package/validation/simple-npm-validation.sh +131 -0
- package/validation/test-gemini-exclusiveMinimum-fix.ts +142 -0
- package/validation/test-gemini-models.ts +200 -0
- package/validation/validate-v1.10.0-docker.sh +296 -0
- package/wasm/reasoningbank/reasoningbank_wasm_bg.js +2 -2
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
- package/docs/INDEX.md +0 -279
- package/docs/guides/.claude-flow/metrics/agent-metrics.json +0 -1
- package/docs/guides/.claude-flow/metrics/performance.json +0 -9
- package/docs/guides/.claude-flow/metrics/task-metrics.json +0 -10
- package/docs/router/.claude-flow/metrics/agent-metrics.json +0 -1
- package/docs/router/.claude-flow/metrics/performance.json +0 -9
- package/docs/router/.claude-flow/metrics/task-metrics.json +0 -10
- /package/docs/{TEST-V1.7.8.Dockerfile → docker-tests/TEST-V1.7.8.Dockerfile} +0 -0
- /package/docs/{TEST-V1.7.9-NODE20.Dockerfile → docker-tests/TEST-V1.7.9-NODE20.Dockerfile} +0 -0
- /package/docs/{TEST-V1.7.9.Dockerfile → docker-tests/TEST-V1.7.9.Dockerfile} +0 -0
- /package/docs/{v1.7.1-QUICK-START.md → guides/QUICK-START-v1.7.1.md} +0 -0
- /package/docs/{INTEGRATION-COMPLETE.md → integration-docs/INTEGRATION-COMPLETE.md} +0 -0
- /package/docs/{LANDING-PAGE-PROVIDER-CONTENT.md → providers/LANDING-PAGE-PROVIDER-CONTENT.md} +0 -0
- /package/docs/{PROVIDER-FALLBACK-GUIDE.md → providers/PROVIDER-FALLBACK-GUIDE.md} +0 -0
- /package/docs/{PROVIDER-FALLBACK-SUMMARY.md → providers/PROVIDER-FALLBACK-SUMMARY.md} +0 -0
- /package/docs/{QUIC_FINAL_STATUS.md → quic/QUIC_FINAL_STATUS.md} +0 -0
- /package/docs/{README_QUIC_PHASE1.md → quic/README_QUIC_PHASE1.md} +0 -0
- /package/docs/{AGENTDB_TESTING.md → testing/AGENTDB_TESTING.md} +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP/2 Multiplexing Optimization
|
|
3
|
+
* Stream prioritization and flow control for concurrent request optimization
|
|
4
|
+
* Phase 2 Optimization
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* HTTP/2 Multiplexing Manager
|
|
8
|
+
* Manages stream prioritization and concurrent request handling
|
|
9
|
+
*/
|
|
10
|
+
export class HTTP2MultiplexingManager {
|
|
11
|
+
config;
|
|
12
|
+
activeStreams = new Map();
|
|
13
|
+
priorityQueues = new Map();
|
|
14
|
+
stats;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = {
|
|
17
|
+
enabled: config.enabled,
|
|
18
|
+
maxConcurrentStreams: config.maxConcurrentStreams || 100,
|
|
19
|
+
defaultPriority: config.defaultPriority || 16,
|
|
20
|
+
enableFlowControl: config.enableFlowControl !== false,
|
|
21
|
+
initialWindowSize: config.initialWindowSize || 65535
|
|
22
|
+
};
|
|
23
|
+
this.stats = {
|
|
24
|
+
totalStreams: 0,
|
|
25
|
+
activeStreams: 0,
|
|
26
|
+
completedStreams: 0,
|
|
27
|
+
averageDuration: 0,
|
|
28
|
+
priorityChanges: 0
|
|
29
|
+
};
|
|
30
|
+
this.initializePriorityQueues();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize priority queues (1-256)
|
|
34
|
+
*/
|
|
35
|
+
initializePriorityQueues() {
|
|
36
|
+
for (let i = 1; i <= 256; i++) {
|
|
37
|
+
this.priorityQueues.set(i, new Set());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Register a new stream
|
|
42
|
+
*/
|
|
43
|
+
registerStream(stream, priority) {
|
|
44
|
+
if (!this.config.enabled)
|
|
45
|
+
return;
|
|
46
|
+
const streamId = stream.id || 0;
|
|
47
|
+
const streamPriority = priority?.weight || this.config.defaultPriority;
|
|
48
|
+
const info = {
|
|
49
|
+
stream,
|
|
50
|
+
priority: streamPriority,
|
|
51
|
+
bytesReceived: 0,
|
|
52
|
+
bytesSent: 0,
|
|
53
|
+
startTime: Date.now(),
|
|
54
|
+
state: 'open'
|
|
55
|
+
};
|
|
56
|
+
this.activeStreams.set(streamId, info);
|
|
57
|
+
this.priorityQueues.get(streamPriority)?.add(streamId);
|
|
58
|
+
this.stats.totalStreams++;
|
|
59
|
+
this.stats.activeStreams++;
|
|
60
|
+
// Set stream priority
|
|
61
|
+
if (priority) {
|
|
62
|
+
this.setPriority(stream, priority);
|
|
63
|
+
}
|
|
64
|
+
// Setup event handlers
|
|
65
|
+
this.setupStreamHandlers(stream, streamId);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Set stream priority
|
|
69
|
+
*/
|
|
70
|
+
setPriority(stream, priority) {
|
|
71
|
+
try {
|
|
72
|
+
stream.priority(priority);
|
|
73
|
+
const streamId = stream.id || 0;
|
|
74
|
+
const info = this.activeStreams.get(streamId);
|
|
75
|
+
if (info) {
|
|
76
|
+
// Move to new priority queue
|
|
77
|
+
const oldPriority = info.priority;
|
|
78
|
+
const newPriority = priority.weight;
|
|
79
|
+
this.priorityQueues.get(oldPriority)?.delete(streamId);
|
|
80
|
+
info.priority = newPriority;
|
|
81
|
+
this.priorityQueues.get(newPriority)?.add(streamId);
|
|
82
|
+
this.stats.priorityChanges++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Stream may be closed
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Adjust stream priority based on load
|
|
91
|
+
*/
|
|
92
|
+
adjustPriority(streamId, adjustment) {
|
|
93
|
+
const info = this.activeStreams.get(streamId);
|
|
94
|
+
if (!info)
|
|
95
|
+
return;
|
|
96
|
+
const newPriority = Math.max(1, Math.min(256, info.priority + adjustment));
|
|
97
|
+
this.setPriority(info.stream, {
|
|
98
|
+
weight: newPriority,
|
|
99
|
+
exclusive: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get next stream to process based on priority
|
|
104
|
+
*/
|
|
105
|
+
getNextStream() {
|
|
106
|
+
// Process highest priority streams first (256 is highest)
|
|
107
|
+
for (let priority = 256; priority >= 1; priority--) {
|
|
108
|
+
const queue = this.priorityQueues.get(priority);
|
|
109
|
+
if (queue && queue.size > 0) {
|
|
110
|
+
const streamId = queue.values().next().value;
|
|
111
|
+
if (streamId !== undefined) {
|
|
112
|
+
const info = this.activeStreams.get(streamId);
|
|
113
|
+
if (info && info.state === 'open') {
|
|
114
|
+
return info.stream;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Setup stream event handlers
|
|
123
|
+
*/
|
|
124
|
+
setupStreamHandlers(stream, streamId) {
|
|
125
|
+
stream.on('data', (chunk) => {
|
|
126
|
+
const info = this.activeStreams.get(streamId);
|
|
127
|
+
if (info) {
|
|
128
|
+
info.bytesReceived += chunk.length;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
stream.on('end', () => {
|
|
132
|
+
this.updateStreamState(streamId, 'half-closed');
|
|
133
|
+
});
|
|
134
|
+
stream.on('close', () => {
|
|
135
|
+
this.handleStreamClose(streamId);
|
|
136
|
+
});
|
|
137
|
+
stream.on('error', () => {
|
|
138
|
+
this.handleStreamClose(streamId);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Update stream state
|
|
143
|
+
*/
|
|
144
|
+
updateStreamState(streamId, state) {
|
|
145
|
+
const info = this.activeStreams.get(streamId);
|
|
146
|
+
if (info) {
|
|
147
|
+
info.state = state;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle stream closure
|
|
152
|
+
*/
|
|
153
|
+
handleStreamClose(streamId) {
|
|
154
|
+
const info = this.activeStreams.get(streamId);
|
|
155
|
+
if (!info)
|
|
156
|
+
return;
|
|
157
|
+
// Remove from priority queue
|
|
158
|
+
this.priorityQueues.get(info.priority)?.delete(streamId);
|
|
159
|
+
// Update statistics
|
|
160
|
+
const duration = Date.now() - info.startTime;
|
|
161
|
+
this.updateStats(duration);
|
|
162
|
+
// Remove from active streams
|
|
163
|
+
this.activeStreams.delete(streamId);
|
|
164
|
+
this.stats.activeStreams--;
|
|
165
|
+
this.stats.completedStreams++;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update statistics
|
|
169
|
+
*/
|
|
170
|
+
updateStats(duration) {
|
|
171
|
+
const currentAvg = this.stats.averageDuration;
|
|
172
|
+
const total = this.stats.completedStreams;
|
|
173
|
+
this.stats.averageDuration = (currentAvg * (total - 1) + duration) / total;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get stream statistics
|
|
177
|
+
*/
|
|
178
|
+
getStreamStats(streamId) {
|
|
179
|
+
const info = this.activeStreams.get(streamId);
|
|
180
|
+
if (!info)
|
|
181
|
+
return null;
|
|
182
|
+
return {
|
|
183
|
+
streamId,
|
|
184
|
+
priority: info.priority,
|
|
185
|
+
bytesReceived: info.bytesReceived,
|
|
186
|
+
bytesSent: info.bytesSent,
|
|
187
|
+
duration: Date.now() - info.startTime,
|
|
188
|
+
state: info.state
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get all statistics
|
|
193
|
+
*/
|
|
194
|
+
getStats() {
|
|
195
|
+
const priorityDistribution = new Map();
|
|
196
|
+
for (const [priority, queue] of this.priorityQueues) {
|
|
197
|
+
if (queue.size > 0) {
|
|
198
|
+
priorityDistribution.set(priority, queue.size);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
...this.stats,
|
|
203
|
+
priorityDistribution
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if can accept more streams
|
|
208
|
+
*/
|
|
209
|
+
canAcceptStream() {
|
|
210
|
+
return this.stats.activeStreams < this.config.maxConcurrentStreams;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get load percentage
|
|
214
|
+
*/
|
|
215
|
+
getLoad() {
|
|
216
|
+
return (this.stats.activeStreams / this.config.maxConcurrentStreams) * 100;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Flow Control Manager
|
|
221
|
+
* Manages HTTP/2 flow control for optimal throughput
|
|
222
|
+
*/
|
|
223
|
+
export class FlowControlManager {
|
|
224
|
+
windowSizes = new Map();
|
|
225
|
+
config;
|
|
226
|
+
constructor(config) {
|
|
227
|
+
this.config = {
|
|
228
|
+
initialWindowSize: config?.initialWindowSize || 65535,
|
|
229
|
+
maxWindowSize: config?.maxWindowSize || 16777215, // 16MB
|
|
230
|
+
minWindowSize: config?.minWindowSize || 16384 // 16KB
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Initialize window size for a stream
|
|
235
|
+
*/
|
|
236
|
+
initializeWindow(streamId) {
|
|
237
|
+
this.windowSizes.set(streamId, this.config.initialWindowSize);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Update window size
|
|
241
|
+
*/
|
|
242
|
+
updateWindow(streamId, delta) {
|
|
243
|
+
const current = this.windowSizes.get(streamId) || this.config.initialWindowSize;
|
|
244
|
+
const newSize = Math.max(this.config.minWindowSize, Math.min(this.config.maxWindowSize, current + delta));
|
|
245
|
+
this.windowSizes.set(streamId, newSize);
|
|
246
|
+
return newSize;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get current window size
|
|
250
|
+
*/
|
|
251
|
+
getWindowSize(streamId) {
|
|
252
|
+
return this.windowSizes.get(streamId) || this.config.initialWindowSize;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Calculate optimal window size based on throughput
|
|
256
|
+
*/
|
|
257
|
+
calculateOptimalWindow(throughputBps, rttMs) {
|
|
258
|
+
// Bandwidth-Delay Product
|
|
259
|
+
const bdp = (throughputBps / 8) * (rttMs / 1000);
|
|
260
|
+
return Math.max(this.config.minWindowSize, Math.min(this.config.maxWindowSize, Math.ceil(bdp * 2)));
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Clean up closed stream
|
|
264
|
+
*/
|
|
265
|
+
cleanup(streamId) {
|
|
266
|
+
this.windowSizes.delete(streamId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Priority scheduler for optimal stream processing
|
|
271
|
+
*/
|
|
272
|
+
export class PriorityScheduler {
|
|
273
|
+
queues = new Map();
|
|
274
|
+
/**
|
|
275
|
+
* Add stream to priority queue
|
|
276
|
+
*/
|
|
277
|
+
enqueue(streamId, priority) {
|
|
278
|
+
if (!this.queues.has(priority)) {
|
|
279
|
+
this.queues.set(priority, []);
|
|
280
|
+
}
|
|
281
|
+
this.queues.get(priority).push(streamId);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get next stream to process
|
|
285
|
+
*/
|
|
286
|
+
dequeue() {
|
|
287
|
+
// Process highest priority first
|
|
288
|
+
for (let priority = 256; priority >= 1; priority--) {
|
|
289
|
+
const queue = this.queues.get(priority);
|
|
290
|
+
if (queue && queue.length > 0) {
|
|
291
|
+
return queue.shift();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Remove stream from all queues
|
|
298
|
+
*/
|
|
299
|
+
remove(streamId) {
|
|
300
|
+
for (const queue of this.queues.values()) {
|
|
301
|
+
const index = queue.indexOf(streamId);
|
|
302
|
+
if (index !== -1) {
|
|
303
|
+
queue.splice(index, 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get queue sizes
|
|
309
|
+
*/
|
|
310
|
+
getStats() {
|
|
311
|
+
const stats = new Map();
|
|
312
|
+
for (const [priority, queue] of this.queues) {
|
|
313
|
+
if (queue.length > 0) {
|
|
314
|
+
stats.set(priority, queue.length);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return stats;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy Authentication with Session Caching
|
|
3
|
+
* Reduces auth overhead by 5-10% through session caching and lazy validation
|
|
4
|
+
* Phase 3 Optimization
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Lazy Authentication Manager
|
|
8
|
+
* Caches validated sessions to avoid repeated authentication overhead
|
|
9
|
+
*/
|
|
10
|
+
export class LazyAuthManager {
|
|
11
|
+
config;
|
|
12
|
+
sessions = new Map();
|
|
13
|
+
stats;
|
|
14
|
+
cleanupTimer;
|
|
15
|
+
validationQueue = new Set();
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = {
|
|
18
|
+
enabled: config.enabled,
|
|
19
|
+
ttl: config.ttl || 3600000, // 1 hour default
|
|
20
|
+
maxSessions: config.maxSessions || 1000,
|
|
21
|
+
checkInterval: config.checkInterval || 60000 // 1 minute
|
|
22
|
+
};
|
|
23
|
+
this.stats = {
|
|
24
|
+
totalValidations: 0,
|
|
25
|
+
cacheHits: 0,
|
|
26
|
+
cacheMisses: 0,
|
|
27
|
+
sessionsActive: 0,
|
|
28
|
+
sessionsCleaned: 0,
|
|
29
|
+
avgValidationTime: 0
|
|
30
|
+
};
|
|
31
|
+
if (this.config.enabled) {
|
|
32
|
+
this.startCleanupTimer();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Authenticate a token (lazy validation)
|
|
37
|
+
*/
|
|
38
|
+
async authenticate(token, validateFn) {
|
|
39
|
+
if (!this.config.enabled) {
|
|
40
|
+
// No caching, always validate
|
|
41
|
+
const userId = await validateFn(token);
|
|
42
|
+
return this.createSession(token, userId, true);
|
|
43
|
+
}
|
|
44
|
+
// Check cache first
|
|
45
|
+
const cached = this.sessions.get(token);
|
|
46
|
+
if (cached && !this.isExpired(cached)) {
|
|
47
|
+
this.stats.cacheHits++;
|
|
48
|
+
cached.lastAccessedAt = Date.now();
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
this.stats.cacheMisses++;
|
|
52
|
+
// Lazy validation: check if already being validated
|
|
53
|
+
if (this.validationQueue.has(token)) {
|
|
54
|
+
// Wait for ongoing validation
|
|
55
|
+
return this.waitForValidation(token);
|
|
56
|
+
}
|
|
57
|
+
// Perform validation
|
|
58
|
+
return this.validateAndCache(token, validateFn);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate token and cache the session
|
|
62
|
+
*/
|
|
63
|
+
async validateAndCache(token, validateFn) {
|
|
64
|
+
this.validationQueue.add(token);
|
|
65
|
+
try {
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
const userId = await validateFn(token);
|
|
68
|
+
const validationTime = Date.now() - startTime;
|
|
69
|
+
// Update average validation time
|
|
70
|
+
this.updateAvgValidationTime(validationTime);
|
|
71
|
+
this.stats.totalValidations++;
|
|
72
|
+
// Create and cache session
|
|
73
|
+
const session = this.createSession(token, userId, true);
|
|
74
|
+
this.cacheSession(session);
|
|
75
|
+
return session;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.validationQueue.delete(token);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait for ongoing validation
|
|
86
|
+
*/
|
|
87
|
+
async waitForValidation(token) {
|
|
88
|
+
// Poll for validation completion (max 5 seconds)
|
|
89
|
+
const maxAttempts = 50;
|
|
90
|
+
let attempts = 0;
|
|
91
|
+
while (this.validationQueue.has(token) && attempts < maxAttempts) {
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
93
|
+
attempts++;
|
|
94
|
+
}
|
|
95
|
+
// Check cache after validation
|
|
96
|
+
const session = this.sessions.get(token);
|
|
97
|
+
return session && !this.isExpired(session) ? session : null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a session object
|
|
101
|
+
*/
|
|
102
|
+
createSession(token, userId, validated) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
return {
|
|
105
|
+
token,
|
|
106
|
+
userId,
|
|
107
|
+
validated,
|
|
108
|
+
createdAt: now,
|
|
109
|
+
lastAccessedAt: now,
|
|
110
|
+
expiresAt: now + this.config.ttl
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Cache a session
|
|
115
|
+
*/
|
|
116
|
+
cacheSession(session) {
|
|
117
|
+
// Check max sessions limit
|
|
118
|
+
if (this.sessions.size >= this.config.maxSessions) {
|
|
119
|
+
this.evictOldest();
|
|
120
|
+
}
|
|
121
|
+
this.sessions.set(session.token, session);
|
|
122
|
+
this.stats.sessionsActive = this.sessions.size;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if session is expired
|
|
126
|
+
*/
|
|
127
|
+
isExpired(session) {
|
|
128
|
+
return Date.now() > session.expiresAt;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Evict oldest session (LRU)
|
|
132
|
+
*/
|
|
133
|
+
evictOldest() {
|
|
134
|
+
let oldest = null;
|
|
135
|
+
let oldestToken = null;
|
|
136
|
+
for (const [token, session] of this.sessions) {
|
|
137
|
+
if (!oldest || session.lastAccessedAt < oldest.lastAccessedAt) {
|
|
138
|
+
oldest = session;
|
|
139
|
+
oldestToken = token;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (oldestToken) {
|
|
143
|
+
this.sessions.delete(oldestToken);
|
|
144
|
+
this.stats.sessionsCleaned++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Update average validation time
|
|
149
|
+
*/
|
|
150
|
+
updateAvgValidationTime(newTime) {
|
|
151
|
+
const total = this.stats.totalValidations;
|
|
152
|
+
const currentAvg = this.stats.avgValidationTime;
|
|
153
|
+
this.stats.avgValidationTime = (currentAvg * total + newTime) / (total + 1);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Start cleanup timer for expired sessions
|
|
157
|
+
*/
|
|
158
|
+
startCleanupTimer() {
|
|
159
|
+
this.cleanupTimer = setInterval(() => {
|
|
160
|
+
this.cleanup();
|
|
161
|
+
}, this.config.checkInterval);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Clean up expired sessions
|
|
165
|
+
*/
|
|
166
|
+
cleanup() {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
let cleaned = 0;
|
|
169
|
+
for (const [token, session] of this.sessions) {
|
|
170
|
+
if (now > session.expiresAt) {
|
|
171
|
+
this.sessions.delete(token);
|
|
172
|
+
cleaned++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this.stats.sessionsCleaned += cleaned;
|
|
176
|
+
this.stats.sessionsActive = this.sessions.size;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Invalidate a session
|
|
180
|
+
*/
|
|
181
|
+
invalidate(token) {
|
|
182
|
+
return this.sessions.delete(token);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Invalidate all sessions for a user
|
|
186
|
+
*/
|
|
187
|
+
invalidateUser(userId) {
|
|
188
|
+
let count = 0;
|
|
189
|
+
for (const [token, session] of this.sessions) {
|
|
190
|
+
if (session.userId === userId) {
|
|
191
|
+
this.sessions.delete(token);
|
|
192
|
+
count++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return count;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get session if exists and valid
|
|
199
|
+
*/
|
|
200
|
+
getSession(token) {
|
|
201
|
+
const session = this.sessions.get(token);
|
|
202
|
+
if (!session || this.isExpired(session)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
session.lastAccessedAt = Date.now();
|
|
206
|
+
return session;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get statistics
|
|
210
|
+
*/
|
|
211
|
+
getStats() {
|
|
212
|
+
return { ...this.stats };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get cache hit rate
|
|
216
|
+
*/
|
|
217
|
+
getCacheHitRate() {
|
|
218
|
+
const total = this.stats.cacheHits + this.stats.cacheMisses;
|
|
219
|
+
return total > 0 ? (this.stats.cacheHits / total) * 100 : 0;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Reset statistics
|
|
223
|
+
*/
|
|
224
|
+
resetStats() {
|
|
225
|
+
this.stats = {
|
|
226
|
+
totalValidations: 0,
|
|
227
|
+
cacheHits: 0,
|
|
228
|
+
cacheMisses: 0,
|
|
229
|
+
sessionsActive: this.sessions.size,
|
|
230
|
+
sessionsCleaned: 0,
|
|
231
|
+
avgValidationTime: 0
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Clear all sessions
|
|
236
|
+
*/
|
|
237
|
+
clear() {
|
|
238
|
+
this.sessions.clear();
|
|
239
|
+
this.stats.sessionsActive = 0;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Destroy manager and cleanup
|
|
243
|
+
*/
|
|
244
|
+
destroy() {
|
|
245
|
+
if (this.cleanupTimer) {
|
|
246
|
+
clearInterval(this.cleanupTimer);
|
|
247
|
+
}
|
|
248
|
+
this.clear();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Token-based authentication helper
|
|
253
|
+
*/
|
|
254
|
+
export class TokenAuth {
|
|
255
|
+
authManager;
|
|
256
|
+
validateFn;
|
|
257
|
+
constructor(authManager, validateFn) {
|
|
258
|
+
this.authManager = authManager;
|
|
259
|
+
this.validateFn = validateFn;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Authenticate a request
|
|
263
|
+
*/
|
|
264
|
+
async authenticate(authHeader) {
|
|
265
|
+
// Extract token from header
|
|
266
|
+
const token = this.extractToken(authHeader);
|
|
267
|
+
if (!token) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return this.authManager.authenticate(token, this.validateFn);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Extract token from Authorization header
|
|
274
|
+
*/
|
|
275
|
+
extractToken(authHeader) {
|
|
276
|
+
if (!authHeader) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
// Support "Bearer <token>" and raw token
|
|
280
|
+
if (authHeader.startsWith('Bearer ')) {
|
|
281
|
+
return authHeader.substring(7);
|
|
282
|
+
}
|
|
283
|
+
return authHeader;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Invalidate token
|
|
287
|
+
*/
|
|
288
|
+
invalidate(token) {
|
|
289
|
+
return this.authManager.invalidate(token);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get statistics
|
|
293
|
+
*/
|
|
294
|
+
getStats() {
|
|
295
|
+
return this.authManager.getStats();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Calculate auth overhead reduction
|
|
300
|
+
*/
|
|
301
|
+
export function calculateAuthSavings(stats) {
|
|
302
|
+
const hitRate = stats.cacheHits / (stats.cacheHits + stats.cacheMisses);
|
|
303
|
+
const savingsPercentage = hitRate * 100;
|
|
304
|
+
const avgSavedTime = stats.avgValidationTime * hitRate;
|
|
305
|
+
const totalSavedTime = stats.avgValidationTime * stats.cacheHits;
|
|
306
|
+
return {
|
|
307
|
+
savingsPercentage,
|
|
308
|
+
avgSavedTime,
|
|
309
|
+
totalSavedTime
|
|
310
|
+
};
|
|
311
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory rate limiter for proxy protection
|
|
3
|
+
*/
|
|
4
|
+
export class RateLimiter {
|
|
5
|
+
config;
|
|
6
|
+
clients = new Map();
|
|
7
|
+
cleanupInterval;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
// Cleanup expired entries every minute
|
|
11
|
+
this.cleanupInterval = setInterval(() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, record] of this.clients.entries()) {
|
|
14
|
+
if (record.resetTime < now && (!record.blockedUntil || record.blockedUntil < now)) {
|
|
15
|
+
this.clients.delete(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}, 60000);
|
|
19
|
+
}
|
|
20
|
+
async consume(key) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const record = this.clients.get(key);
|
|
23
|
+
// Check if client is blocked
|
|
24
|
+
if (record?.blockedUntil && record.blockedUntil > now) {
|
|
25
|
+
const remainingMs = record.blockedUntil - now;
|
|
26
|
+
throw new Error(`Rate limit exceeded. Try again in ${Math.ceil(remainingMs / 1000)} seconds`);
|
|
27
|
+
}
|
|
28
|
+
// Initialize or reset record
|
|
29
|
+
if (!record || record.resetTime < now) {
|
|
30
|
+
this.clients.set(key, {
|
|
31
|
+
count: 1,
|
|
32
|
+
resetTime: now + this.config.duration * 1000
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Increment count
|
|
37
|
+
record.count++;
|
|
38
|
+
// Check if limit exceeded
|
|
39
|
+
if (record.count > this.config.points) {
|
|
40
|
+
record.blockedUntil = now + this.config.blockDuration * 1000;
|
|
41
|
+
throw new Error(`Rate limit exceeded (${this.config.points} requests per ${this.config.duration}s)`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
destroy() {
|
|
45
|
+
clearInterval(this.cleanupInterval);
|
|
46
|
+
this.clients.clear();
|
|
47
|
+
}
|
|
48
|
+
}
|