web-agent-bridge 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ar.md +506 -31
- package/README.md +574 -47
- package/bin/agent-runner.js +10 -1
- package/package.json +12 -4
- package/public/agent-workspace.html +347 -0
- package/public/browser.html +484 -0
- package/public/commander-dashboard.html +243 -0
- package/public/css/agent-workspace.css +1713 -0
- package/public/css/premium.css +317 -317
- package/public/demo.html +259 -259
- package/public/index.html +738 -644
- package/public/js/agent-workspace.js +1740 -0
- package/public/mesh-dashboard.html +309 -382
- package/public/premium-dashboard.html +2487 -2487
- package/public/premium.html +791 -791
- package/public/script/wab.min.js +124 -87
- package/script/ai-agent-bridge.js +154 -84
- package/sdk/agent-mesh.js +287 -171
- package/sdk/commander.js +262 -0
- package/sdk/index.d.ts +83 -0
- package/sdk/index.js +374 -260
- package/sdk/package.json +1 -1
- package/server/config/secrets.js +13 -5
- package/server/index.js +191 -5
- package/server/middleware/adminAuth.js +6 -1
- package/server/middleware/auth.js +11 -2
- package/server/middleware/rateLimits.js +78 -2
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -0
- package/server/models/db.js +121 -1
- package/server/routes/admin-premium.js +671 -671
- package/server/routes/admin.js +16 -2
- package/server/routes/ads.js +130 -0
- package/server/routes/agent-workspace.js +378 -0
- package/server/routes/api.js +21 -2
- package/server/routes/auth.js +26 -6
- package/server/routes/commander.js +316 -0
- package/server/routes/mesh.js +370 -201
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/sovereign.js +78 -0
- package/server/routes/universal.js +177 -0
- package/server/routes/wab-api.js +20 -5
- package/server/services/agent-chat.js +506 -0
- package/server/services/agent-learning.js +230 -77
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +260 -67
- package/server/services/agent-symphony.js +553 -517
- package/server/services/agent-tasks.js +1807 -0
- package/server/services/commander.js +738 -0
- package/server/services/edge-compute.js +440 -0
- package/server/services/fairness-engine.js +409 -0
- package/server/services/local-ai.js +389 -0
- package/server/services/plugins.js +771 -747
- package/server/services/price-intelligence.js +565 -0
- package/server/services/price-shield.js +1137 -0
- package/server/services/search-engine.js +357 -0
- package/server/services/security.js +513 -0
- package/server/services/self-healing.js +843 -843
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +661 -0
- package/server/services/vision.js +871 -871
- package/server/ws.js +61 -1
- package/public/admin/dashboard.html +0 -848
- package/public/admin/login.html +0 -84
- package/public/video/tutorial.mp4 +0 -0
package/server/index.js
CHANGED
|
@@ -11,7 +11,10 @@ const rateLimit = require('express-rate-limit');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const { setupWebSocket } = require('./ws');
|
|
13
13
|
const { runMigrations } = require('./utils/migrate');
|
|
14
|
-
const { maybeBootstrapAdmin } = require('./models/db');
|
|
14
|
+
const { maybeBootstrapAdmin, db } = require('./models/db');
|
|
15
|
+
const { initSearchEngine, search, getSuggestions, getTrendingSearches, getSearchStats, purgeOldCache } = require('./services/search-engine');
|
|
16
|
+
const { processMessage: agentChat } = require('./services/agent-chat');
|
|
17
|
+
const agentTasks = require('./services/agent-tasks');
|
|
15
18
|
|
|
16
19
|
const authRoutes = require('./routes/auth');
|
|
17
20
|
const apiRoutes = require('./routes/api');
|
|
@@ -20,6 +23,15 @@ const adminRoutes = require('./routes/admin');
|
|
|
20
23
|
const billingRoutes = require('./routes/billing');
|
|
21
24
|
const sovereignRoutes = require('./routes/sovereign');
|
|
22
25
|
const meshRoutes = require('./routes/mesh');
|
|
26
|
+
const commanderRoutes = require('./routes/commander');
|
|
27
|
+
const adsRoutes = require('./routes/ads');
|
|
28
|
+
const wabApiRoutes = require('./routes/wab-api');
|
|
29
|
+
const noscriptRoutes = require('./routes/noscript');
|
|
30
|
+
const discoveryRoutes = require('./routes/discovery');
|
|
31
|
+
const premiumRoutes = require('./routes/premium');
|
|
32
|
+
const adminPremiumRoutes = require('./routes/admin-premium');
|
|
33
|
+
const workspaceRoutes = require('./routes/agent-workspace');
|
|
34
|
+
const universalRoutes = require('./routes/universal');
|
|
23
35
|
const { handleWebhookRequest } = require('./services/stripe');
|
|
24
36
|
|
|
25
37
|
const app = express();
|
|
@@ -60,14 +72,16 @@ app.use(
|
|
|
60
72
|
directives: {
|
|
61
73
|
defaultSrc: ["'self'"],
|
|
62
74
|
scriptSrc,
|
|
63
|
-
|
|
75
|
+
scriptSrcAttr: scriptSrc,
|
|
76
|
+
styleSrc: [...styleSrc, 'https://fonts.googleapis.com'],
|
|
64
77
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
65
78
|
connectSrc: ["'self'", 'ws:', 'wss:'],
|
|
66
|
-
fontSrc: ["'self'", 'https:', 'data:'],
|
|
67
|
-
frameSrc: ["'
|
|
79
|
+
fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https:', 'data:'],
|
|
80
|
+
frameSrc: ["'self'", 'https:', 'http:'],
|
|
68
81
|
frameAncestors: ["'none'"],
|
|
69
82
|
objectSrc: ["'none'"],
|
|
70
|
-
baseUri: ["'self'"]
|
|
83
|
+
baseUri: ["'self'"],
|
|
84
|
+
formAction: ["'self'"]
|
|
71
85
|
}
|
|
72
86
|
},
|
|
73
87
|
crossOriginEmbedderPolicy: false
|
|
@@ -115,6 +129,52 @@ app.use('/api/admin', apiLimiter, adminRoutes);
|
|
|
115
129
|
app.use('/api/billing', apiLimiter, billingRoutes);
|
|
116
130
|
app.use('/api/sovereign', apiLimiter, sovereignRoutes);
|
|
117
131
|
app.use('/api/mesh', apiLimiter, meshRoutes);
|
|
132
|
+
app.use('/api/commander', apiLimiter, commanderRoutes);
|
|
133
|
+
app.use('/api/ads', apiLimiter, adsRoutes);
|
|
134
|
+
app.use('/api/wab', wabApiRoutes);
|
|
135
|
+
app.use('/api/noscript', apiLimiter, noscriptRoutes);
|
|
136
|
+
app.use('/api/discovery', apiLimiter, discoveryRoutes);
|
|
137
|
+
app.use('/api/premium', apiLimiter, premiumRoutes);
|
|
138
|
+
app.use('/api/admin/premium', apiLimiter, adminPremiumRoutes);
|
|
139
|
+
app.use('/api/workspace', apiLimiter, workspaceRoutes);
|
|
140
|
+
app.use('/api/universal', apiLimiter, universalRoutes);
|
|
141
|
+
|
|
142
|
+
// ─── WAB Search Engine ────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const searchLimiter = rateLimit({
|
|
145
|
+
windowMs: 60 * 1000,
|
|
146
|
+
max: 30,
|
|
147
|
+
standardHeaders: true,
|
|
148
|
+
legacyHeaders: false,
|
|
149
|
+
message: { error: 'Too many search requests, please slow down' }
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
app.get('/api/search', searchLimiter, async (req, res) => {
|
|
153
|
+
const q = (req.query.q || '').trim();
|
|
154
|
+
if (!q) return res.json({ results: [], cached: false });
|
|
155
|
+
if (q.length > 200) return res.status(400).json({ error: 'Query too long' });
|
|
156
|
+
const crypto = require('crypto');
|
|
157
|
+
const ipHash = crypto.createHash('sha256').update(req.ip || '').digest('hex').slice(0, 16);
|
|
158
|
+
const result = await search(q, ipHash);
|
|
159
|
+
res.json(result);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
app.get('/api/search/suggest', searchLimiter, (req, res) => {
|
|
163
|
+
const q = (req.query.q || '').trim();
|
|
164
|
+
if (!q) return res.json({ suggestions: [] });
|
|
165
|
+
const suggestions = getSuggestions(q, 8);
|
|
166
|
+
res.json({ suggestions });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
app.get('/api/search/trending', apiLimiter, (req, res) => {
|
|
170
|
+
const trending = getTrendingSearches(10);
|
|
171
|
+
res.json({ trending });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
app.get('/api/search/stats', apiLimiter, (req, res) => {
|
|
175
|
+
const stats = getSearchStats();
|
|
176
|
+
res.json(stats);
|
|
177
|
+
});
|
|
118
178
|
|
|
119
179
|
app.get('/dashboard', (req, res) => {
|
|
120
180
|
res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
|
|
@@ -122,6 +182,9 @@ app.get('/dashboard', (req, res) => {
|
|
|
122
182
|
app.get('/mesh-dashboard', (req, res) => {
|
|
123
183
|
res.sendFile(path.join(__dirname, '..', 'public', 'mesh-dashboard.html'));
|
|
124
184
|
});
|
|
185
|
+
app.get('/commander-dashboard', (req, res) => {
|
|
186
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'commander-dashboard.html'));
|
|
187
|
+
});
|
|
125
188
|
app.get('/docs', (req, res) => {
|
|
126
189
|
res.sendFile(path.join(__dirname, '..', 'public', 'docs.html'));
|
|
127
190
|
});
|
|
@@ -146,6 +209,125 @@ app.get('/terms', (req, res) => {
|
|
|
146
209
|
app.get('/cookies', (req, res) => {
|
|
147
210
|
res.sendFile(path.join(__dirname, '..', 'public', 'cookies.html'));
|
|
148
211
|
});
|
|
212
|
+
app.get('/browser', (req, res) => {
|
|
213
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'browser.html'));
|
|
214
|
+
});
|
|
215
|
+
app.get('/workspace', (req, res) => {
|
|
216
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'agent-workspace.html'));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Browser downloads
|
|
220
|
+
app.use('/downloads', express.static(path.join(__dirname, '..', 'downloads'), {
|
|
221
|
+
maxAge: '1d',
|
|
222
|
+
setHeaders: (res, filePath) => {
|
|
223
|
+
res.set('Content-Disposition', 'attachment');
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
// Agent chat endpoint for WAB Browser — Real AI Agent
|
|
228
|
+
const chatLimiter = rateLimit({
|
|
229
|
+
windowMs: 60 * 1000,
|
|
230
|
+
max: 20,
|
|
231
|
+
standardHeaders: true,
|
|
232
|
+
legacyHeaders: false,
|
|
233
|
+
message: { error: 'Too many messages, please slow down' }
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
app.post('/api/wab/agent-chat', chatLimiter, async (req, res) => {
|
|
237
|
+
const { message, context, sessionId, taskId, taskAction } = req.body || {};
|
|
238
|
+
if (!message || typeof message !== 'string') {
|
|
239
|
+
return res.status(400).json({ error: 'Message required' });
|
|
240
|
+
}
|
|
241
|
+
if (message.length > 3000) {
|
|
242
|
+
return res.status(400).json({ error: 'Message too long' });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const sid = sessionId || req.ip || 'anonymous';
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
// ── Task actions (user responding to an active task) ──
|
|
249
|
+
if (taskId && taskAction) {
|
|
250
|
+
if (taskAction === 'answer') {
|
|
251
|
+
const result = agentTasks.answerClarification(taskId, message);
|
|
252
|
+
if (result.status === 'planning') {
|
|
253
|
+
// Auto-execute after planning
|
|
254
|
+
const execResult = await agentTasks.executeTask(taskId);
|
|
255
|
+
return res.json({ ...execResult, type: 'task' });
|
|
256
|
+
}
|
|
257
|
+
return res.json({ ...result, type: 'task' });
|
|
258
|
+
}
|
|
259
|
+
if (taskAction === 'select') {
|
|
260
|
+
const idx = parseInt(message.replace(/\D/g, '')) - 1;
|
|
261
|
+
const result = agentTasks.selectOffer(taskId, idx);
|
|
262
|
+
return res.json({ ...result, type: 'task' });
|
|
263
|
+
}
|
|
264
|
+
if (taskAction === 'cancel') {
|
|
265
|
+
const result = agentTasks.cancelTask(taskId);
|
|
266
|
+
return res.json({ ...result, type: 'task' });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Check if user wants to select from existing offers ──
|
|
271
|
+
if (!taskId) {
|
|
272
|
+
const selectMatch = message.match(/(?:اختر|اخت(?:ا|ي)ر|select|choose|pick)\s*(\d+)/i);
|
|
273
|
+
if (selectMatch) {
|
|
274
|
+
const tasks = agentTasks.getSessionTasks(sid, 1);
|
|
275
|
+
if (tasks.length > 0 && tasks[0].status === 'presenting') {
|
|
276
|
+
const idx = parseInt(selectMatch[1]) - 1;
|
|
277
|
+
const result = agentTasks.selectOffer(tasks[0].id, idx);
|
|
278
|
+
return res.json({ ...result, type: 'task' });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Detect URL paste — create URL negotiation task ──
|
|
284
|
+
const urlData = agentTasks.parseBookingUrl(message);
|
|
285
|
+
if (urlData) {
|
|
286
|
+
const task = agentTasks.createUrlTask(sid, message, urlData);
|
|
287
|
+
const execResult = await agentTasks.executeUrlTask(task.taskId);
|
|
288
|
+
return res.json({ ...execResult, type: 'task', urlData });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Detect if this is a task-type request (booking, shopping, etc.) ──
|
|
292
|
+
const intent = agentTasks.detectIntent(message);
|
|
293
|
+
if (intent.confidence >= 0.7 && intent.intent !== 'general') {
|
|
294
|
+
const task = agentTasks.createTask(sid, message);
|
|
295
|
+
|
|
296
|
+
if (task.status === 'clarifying') {
|
|
297
|
+
return res.json({ ...task, type: 'task' });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// If requirements are complete, auto-execute
|
|
301
|
+
const execResult = await agentTasks.executeTask(task.taskId);
|
|
302
|
+
return res.json({ ...execResult, type: 'task' });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Regular chat (not a task) ──
|
|
306
|
+
const chatContext = {
|
|
307
|
+
url: context?.url || '',
|
|
308
|
+
platform: context?.platform || 'unknown',
|
|
309
|
+
sessionId: sid,
|
|
310
|
+
};
|
|
311
|
+
const result = await agentChat(message, chatContext);
|
|
312
|
+
res.json(result);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.error('[agent-chat] Error:', err.message);
|
|
315
|
+
res.json({ reply: '🤖 عذراً، حدث خطأ. حاول مرة أخرى.', type: 'text' });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Agent task status & history
|
|
320
|
+
app.get('/api/wab/agent-task/:id', chatLimiter, (req, res) => {
|
|
321
|
+
const state = agentTasks.getTaskState(req.params.id);
|
|
322
|
+
if (!state) return res.status(404).json({ error: 'Task not found' });
|
|
323
|
+
res.json(state);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
app.get('/api/wab/agent-tasks', chatLimiter, (req, res) => {
|
|
327
|
+
const sid = req.query.sessionId || req.ip || 'anonymous';
|
|
328
|
+
const tasks = agentTasks.getSessionTasks(sid, 20);
|
|
329
|
+
res.json({ tasks });
|
|
330
|
+
});
|
|
149
331
|
|
|
150
332
|
const pkg = require('../package.json');
|
|
151
333
|
app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
|
|
@@ -163,6 +345,10 @@ if (process.env.NODE_ENV !== 'test') {
|
|
|
163
345
|
console.log('Running database migrations...');
|
|
164
346
|
runMigrations();
|
|
165
347
|
maybeBootstrapAdmin();
|
|
348
|
+
initSearchEngine(db);
|
|
349
|
+
|
|
350
|
+
// Purge old search cache every hour
|
|
351
|
+
setInterval(purgeOldCache, 60 * 60 * 1000);
|
|
166
352
|
|
|
167
353
|
const server = http.createServer(app);
|
|
168
354
|
setupWebSocket(server);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const { signAdminToken, verifyAdminToken } = require('../config/secrets');
|
|
2
|
+
const { isJWTRevoked } = require('../services/security');
|
|
2
3
|
|
|
3
4
|
function generateAdminToken(admin) {
|
|
4
5
|
return signAdminToken(
|
|
5
6
|
{ id: admin.id, email: admin.email, name: admin.name, role: admin.role, isAdmin: true },
|
|
6
|
-
{ expiresIn: '
|
|
7
|
+
{ expiresIn: '4h' }
|
|
7
8
|
);
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -16,11 +17,15 @@ function authenticateAdmin(req, res, next) {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
try {
|
|
20
|
+
if (isJWTRevoked(token)) {
|
|
21
|
+
return res.status(403).json({ error: 'Token has been revoked' });
|
|
22
|
+
}
|
|
19
23
|
const decoded = verifyAdminToken(token);
|
|
20
24
|
if (!decoded.isAdmin) {
|
|
21
25
|
return res.status(403).json({ error: 'Admin privileges required' });
|
|
22
26
|
}
|
|
23
27
|
req.admin = decoded;
|
|
28
|
+
req._rawToken = token;
|
|
24
29
|
next();
|
|
25
30
|
} catch (err) {
|
|
26
31
|
return res.status(403).json({ error: 'Invalid or expired admin token' });
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const { signUserToken, verifyUserToken } = require('../config/secrets');
|
|
2
|
+
const { isJWTRevoked } = require('../services/security');
|
|
2
3
|
|
|
3
4
|
function generateToken(user) {
|
|
4
5
|
return signUserToken(
|
|
5
6
|
{ id: user.id, email: user.email, name: user.name },
|
|
6
|
-
{ expiresIn: '
|
|
7
|
+
{ expiresIn: '24h' }
|
|
7
8
|
);
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -16,8 +17,13 @@ function authenticateToken(req, res, next) {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
try {
|
|
20
|
+
// Check revocation list
|
|
21
|
+
if (isJWTRevoked(token)) {
|
|
22
|
+
return res.status(403).json({ error: 'Token has been revoked' });
|
|
23
|
+
}
|
|
19
24
|
const decoded = verifyUserToken(token);
|
|
20
25
|
req.user = decoded;
|
|
26
|
+
req._rawToken = token;
|
|
21
27
|
next();
|
|
22
28
|
} catch (err) {
|
|
23
29
|
return res.status(403).json({ error: 'Invalid or expired token' });
|
|
@@ -30,7 +36,10 @@ function optionalAuth(req, res, next) {
|
|
|
30
36
|
|
|
31
37
|
if (token) {
|
|
32
38
|
try {
|
|
33
|
-
|
|
39
|
+
if (!isJWTRevoked(token)) {
|
|
40
|
+
req.user = verifyUserToken(token);
|
|
41
|
+
req._rawToken = token;
|
|
42
|
+
}
|
|
34
43
|
} catch (e) {
|
|
35
44
|
// ignore invalid tokens for optional auth
|
|
36
45
|
}
|
|
@@ -1,9 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Comprehensive rate limits for all security-sensitive endpoints.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const rateLimit = require('express-rate-limit');
|
|
6
6
|
|
|
7
|
+
// ─── Auth endpoints ──────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const authLimiter = rateLimit({
|
|
10
|
+
windowMs: 15 * 60 * 1000,
|
|
11
|
+
max: 10,
|
|
12
|
+
standardHeaders: true,
|
|
13
|
+
legacyHeaders: false,
|
|
14
|
+
message: { error: 'Too many authentication attempts, please try again later' }
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const registerLimiter = rateLimit({
|
|
18
|
+
windowMs: 60 * 60 * 1000,
|
|
19
|
+
max: 5,
|
|
20
|
+
standardHeaders: true,
|
|
21
|
+
legacyHeaders: false,
|
|
22
|
+
message: { error: 'Too many registration attempts, please try again later' }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const adminLoginLimiter = rateLimit({
|
|
26
|
+
windowMs: 15 * 60 * 1000,
|
|
27
|
+
max: 5,
|
|
28
|
+
standardHeaders: true,
|
|
29
|
+
legacyHeaders: false,
|
|
30
|
+
message: { error: 'Too many admin login attempts, please try again later' }
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ─── WAB API endpoints ───────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const wabAuthenticateLimiter = rateLimit({
|
|
36
|
+
windowMs: 15 * 60 * 1000,
|
|
37
|
+
max: 20,
|
|
38
|
+
standardHeaders: true,
|
|
39
|
+
legacyHeaders: false,
|
|
40
|
+
keyGenerator: (req) => `${req.ip}:${req.body?.siteId || req.body?.apiKey || 'anon'}`,
|
|
41
|
+
message: { error: 'Too many WAB authentication attempts' }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const wabActionLimiter = rateLimit({
|
|
45
|
+
windowMs: 60 * 1000,
|
|
46
|
+
max: 60,
|
|
47
|
+
standardHeaders: true,
|
|
48
|
+
legacyHeaders: false,
|
|
49
|
+
keyGenerator: (req) => `${req.ip}:${req.wabSession?.siteId || 'anon'}`,
|
|
50
|
+
message: { error: 'Too many action requests, please slow down' }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── General API endpoints ───────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const apiLimiter = rateLimit({
|
|
56
|
+
windowMs: 60 * 1000,
|
|
57
|
+
max: 100,
|
|
58
|
+
standardHeaders: true,
|
|
59
|
+
legacyHeaders: false,
|
|
60
|
+
message: { error: 'Too many requests, please try again later' }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const searchLimiter = rateLimit({
|
|
64
|
+
windowMs: 60 * 1000,
|
|
65
|
+
max: 30,
|
|
66
|
+
standardHeaders: true,
|
|
67
|
+
legacyHeaders: false,
|
|
68
|
+
message: { error: 'Too many search requests' }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ─── License endpoints (existing) ────────────────────────────────────
|
|
72
|
+
|
|
7
73
|
const licenseTokenLimiter = rateLimit({
|
|
8
74
|
windowMs: 15 * 60 * 1000,
|
|
9
75
|
max: 30,
|
|
@@ -21,4 +87,14 @@ const licenseTrackLimiter = rateLimit({
|
|
|
21
87
|
message: { error: 'Too many track requests, please try again later' }
|
|
22
88
|
});
|
|
23
89
|
|
|
24
|
-
module.exports = {
|
|
90
|
+
module.exports = {
|
|
91
|
+
authLimiter,
|
|
92
|
+
registerLimiter,
|
|
93
|
+
adminLoginLimiter,
|
|
94
|
+
wabAuthenticateLimiter,
|
|
95
|
+
wabActionLimiter,
|
|
96
|
+
apiLimiter,
|
|
97
|
+
searchLimiter,
|
|
98
|
+
licenseTokenLimiter,
|
|
99
|
+
licenseTrackLimiter,
|
|
100
|
+
};
|