unknownfriend00007 commited on
Commit
1321522
·
verified ·
1 Parent(s): 94093e6

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +106 -91
server.js CHANGED
@@ -7,6 +7,39 @@ require('dotenv').config();
7
 
8
  const app = express();
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  app.use(helmet({
11
  contentSecurityPolicy: false,
12
  crossOriginEmbedderPolicy: false
@@ -24,16 +57,14 @@ function authenticateRequest(req) {
24
  const origin = req.headers.origin;
25
  const apiKey = req.headers['x-api-key'];
26
 
27
- // CASE 1: Browser request (has Origin)
28
  if (origin) {
29
  if (allowedOrigins.length === 0) return { valid: true, source: 'open-mode' };
30
  return {
31
  valid: allowedOrigins.includes(origin),
32
- source: origin
33
  };
34
  }
35
 
36
- // CASE 2: Backend request (no Origin) - MUST have API key
37
  if (apiKey) {
38
  if (API_KEYS.length === 0) return { valid: true, source: 'no-keys-configured' };
39
  return {
@@ -42,11 +73,10 @@ function authenticateRequest(req) {
42
  };
43
  }
44
 
45
- // CASE 3: No Origin, No API Key - BLOCKED
46
  return { valid: false, source: 'unauthorized' };
47
  }
48
 
49
- // --- CORS (For browsers) ---
50
  const allowedOrigins = process.env.ALLOWED_ORIGINS
51
  ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
52
  : [];
@@ -60,7 +90,7 @@ app.use(cors({
60
  if (allowedOrigins.includes(origin)) {
61
  callback(null, true);
62
  } else {
63
- console.log(`[Security] Blocked origin: ${origin}`);
64
  callback(new Error('Not allowed by CORS'));
65
  }
66
  },
@@ -83,14 +113,14 @@ app.use((req, res, next) => {
83
  const auth = authenticateRequest(req);
84
 
85
  if (!auth.valid) {
86
- console.log(`[Security] Blocked unauthorized request from ${req.ip} (${auth.source})`);
87
  return res.status(401).json({
88
  error: 'Unauthorized',
89
  message: 'Valid origin or API key required'
90
  });
91
  }
92
 
93
- console.log(`[Auth] Request authorized from: ${auth.source}`);
94
  next();
95
  });
96
 
@@ -106,12 +136,14 @@ const limiter = rateLimit({
106
  });
107
  app.use(limiter);
108
 
109
- // --- REQUEST LOGGING ---
110
  app.use((req, res, next) => {
111
- const ip = (req.ip || 'unknown').replace(/:\d+[^:]*$/, '');
112
- const origin = req.headers.origin || 'no-origin';
113
  const apiKey = req.headers['x-api-key'] ? '***' : 'none';
114
- console.log(`[${new Date().toISOString()}] ${ip} -> ${req.method} ${req.path} | Origin: ${origin} | Key: ${apiKey}`);
 
 
115
  next();
116
  });
117
 
@@ -124,7 +156,7 @@ function checkDailyReset() {
124
  if (today !== lastResetDate) {
125
  dailyUsage.clear();
126
  lastResetDate = today;
127
- console.log('[System] Daily usage counters reset');
128
  }
129
  }
130
 
@@ -147,7 +179,7 @@ app.use((req, res, next) => {
147
  dailyUsage.set(ip, count + 1);
148
 
149
  if (dailyUsage.size > 10000) {
150
- console.warn('[System] Daily usage map too large, clearing oldest entries');
151
  const entries = Array.from(dailyUsage.entries()).slice(0, 1000);
152
  entries.forEach(([key]) => dailyUsage.delete(key));
153
  }
@@ -172,7 +204,7 @@ app.use((req, res, next) => {
172
  const isBot = suspiciousBots.some(bot => userAgent.includes(bot));
173
 
174
  if (isBot) {
175
- console.log(`[Security] Blocked bot: ${userAgent.substring(0, 50)} from ${req.ip}`);
176
  return res.status(403).json({
177
  error: 'Automated access detected',
178
  message: 'This service is for web browsers only.'
@@ -185,12 +217,12 @@ app.use((req, res, next) => {
185
  let INSTANCES = [];
186
  try {
187
  INSTANCES = JSON.parse(process.env.FLOWISE_INSTANCES || '[]');
188
- console.log(`[System] Loaded ${INSTANCES.length} instances`);
189
  if (!Array.isArray(INSTANCES) || INSTANCES.length === 0) {
190
- console.error('ERROR: FLOWISE_INSTANCES must be a non-empty array');
191
  }
192
  } catch (e) {
193
- console.error("CRITICAL ERROR: Could not parse FLOWISE_INSTANCES JSON", e);
194
  }
195
 
196
  // --- CACHE WITH AUTO-CLEANUP ---
@@ -229,7 +261,7 @@ async function resolveChatflowId(instanceNum, botName) {
229
  }
230
 
231
  const instance = INSTANCES[instanceNum - 1];
232
- console.log(`[System] Looking up '${botName}' in instance ${instanceNum}...`);
233
 
234
  const headers = {};
235
  if (instance.key && instance.key.length > 0) {
@@ -260,35 +292,34 @@ async function resolveChatflowId(instanceNum, botName) {
260
  timestamp: Date.now()
261
  });
262
 
263
- console.log(`[System] Found '${botName}' -> ${match.id}`);
264
 
265
  return { id: match.id, instance };
266
  }
267
 
268
- // --- IMPROVED STREAMING HANDLER WITH TIMEOUT ---
269
  async function handleStreamingResponse(flowiseResponse, clientRes) {
270
  clientRes.setHeader('Content-Type', 'text/event-stream');
271
  clientRes.setHeader('Cache-Control', 'no-cache');
272
  clientRes.setHeader('Connection', 'keep-alive');
273
  clientRes.setHeader('X-Accel-Buffering', 'no');
274
 
275
- console.log('[Streaming] Forwarding SSE stream...');
276
 
277
  let streamStarted = false;
278
  let dataReceived = false;
279
  let lastDataTime = Date.now();
280
  let totalBytes = 0;
281
 
282
- // Check for stream timeout every 5 seconds
283
  const timeoutCheck = setInterval(() => {
284
  const timeSinceData = Date.now() - lastDataTime;
285
 
286
- if (timeSinceData > 45000) { // 45 seconds without data
287
- console.error(`[Streaming] Timeout - no data for ${(timeSinceData/1000).toFixed(1)}s`);
288
  clearInterval(timeoutCheck);
289
 
290
  if (!dataReceived) {
291
- console.error('[Streaming] Stream completed with NO data received!');
292
  if (!streamStarted) {
293
  clientRes.status(504).json({
294
  error: 'Gateway timeout',
@@ -309,7 +340,7 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
309
  lastDataTime = Date.now();
310
  totalBytes += chunk.length;
311
 
312
- console.log(`[Streaming] Received chunk: ${chunk.length} bytes (total: ${totalBytes})`);
313
  clientRes.write(chunk);
314
  });
315
 
@@ -317,9 +348,9 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
317
  clearInterval(timeoutCheck);
318
 
319
  if (dataReceived) {
320
- console.log(`[Streaming] Stream completed successfully - ${totalBytes} bytes total`);
321
  } else {
322
- console.warn('[Streaming] Stream completed but NO data was received!');
323
  }
324
 
325
  clientRes.end();
@@ -327,7 +358,7 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
327
 
328
  flowiseResponse.body.on('error', (err) => {
329
  clearInterval(timeoutCheck);
330
- console.error('[Streaming Error]', err.message);
331
 
332
  if (streamStarted && dataReceived) {
333
  clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
@@ -338,7 +369,7 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
338
  });
339
  }
340
 
341
- // --- ROUTE 1: PREDICTION (WITH IMPROVED TIMEOUT) ---
342
  app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
343
  try {
344
  const instanceNum = parseInt(req.params.instanceNum);
@@ -365,9 +396,8 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
365
  headers['Authorization'] = `Bearer ${instance.key}`;
366
  }
367
 
368
- // TIMING: Track how long Flowise takes
369
  const startTime = Date.now();
370
- console.log(`[Timing] Calling Flowise at ${new Date().toISOString()}`);
371
 
372
  const response = await fetchWithTimeout(
373
  `${instance.url}/api/v1/prediction/${id}`,
@@ -376,15 +406,15 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
376
  headers,
377
  body: JSON.stringify(req.body)
378
  },
379
- 60000 // ← INCREASED TO 60 SECONDS
380
  );
381
 
382
  const duration = Date.now() - startTime;
383
- console.log(`[Timing] Flowise responded in ${duration}ms (${(duration/1000).toFixed(1)}s)`);
384
 
385
  if (!response.ok) {
386
  const errorText = await response.text();
387
- console.error(`[Error] Instance returned ${response.status}: ${errorText.substring(0, 200)}`);
388
  return res.status(response.status).json({
389
  error: 'Flowise instance error',
390
  message: 'The chatbot instance returned an error.'
@@ -394,23 +424,23 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
394
  const contentType = response.headers.get('content-type') || '';
395
 
396
  if (contentType.includes('text/event-stream')) {
397
- console.log('[Streaming] Detected SSE response');
398
  return handleStreamingResponse(response, res);
399
  }
400
 
401
- console.log('[Non-streaming] Parsing JSON response');
402
  const text = await response.text();
403
 
404
  try {
405
  const data = JSON.parse(text);
406
  res.status(200).json(data);
407
  } catch (e) {
408
- console.error('[Error] Invalid JSON:', text.substring(0, 200));
409
  res.status(500).json({ error: 'Invalid response from Flowise' });
410
  }
411
 
412
  } catch (error) {
413
- console.error('[Error]', error.message);
414
  res.status(500).json({
415
  error: 'Request failed',
416
  message: error.message
@@ -418,7 +448,7 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
418
  }
419
  });
420
 
421
- // --- ROUTE 2: CHATBOT CONFIG ---
422
  app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
423
  try {
424
  const instanceNum = parseInt(req.params.instanceNum);
@@ -445,12 +475,12 @@ app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) =
445
  res.status(200).json(data);
446
 
447
  } catch (error) {
448
- console.error('[Error Config]', error.message);
449
  res.status(404).json({ error: error.message });
450
  }
451
  });
452
 
453
- // --- ROUTE 3: STREAMING CHECK ---
454
  app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
455
  try {
456
  const instanceNum = parseInt(req.params.instanceNum);
@@ -477,46 +507,30 @@ app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) =>
477
  res.status(200).json(data);
478
 
479
  } catch (error) {
480
- console.error('[Error Streaming]', error.message);
481
  res.status(200).json({ isStreaming: false });
482
  }
483
  });
484
 
485
- // --- TEST ENDPOINT: Stream Test ---
486
- app.get('/test-stream', (req, res) => {
487
- res.setHeader('Content-Type', 'text/event-stream');
488
- res.setHeader('Cache-Control', 'no-cache');
489
- res.setHeader('Connection', 'keep-alive');
490
-
491
- let count = 0;
492
- const interval = setInterval(() => {
493
- count++;
494
- res.write(`data: {"message": "Test chunk ${count}", "timestamp": "${new Date().toISOString()}"}\n\n`);
495
-
496
- if (count >= 5) {
497
- clearInterval(interval);
498
- res.write('data: {"message": "Test complete"}\n\n');
499
- res.end();
500
- }
501
- }, 500);
502
- });
503
-
504
- // --- TEST ENDPOINT: Delay Test ---
505
- app.post('/test-delay', async (req, res) => {
506
- const start = Date.now();
507
- const delaySeconds = req.body.delay || 35;
508
-
509
- console.log(`[Test] Starting ${delaySeconds}s delay test...`);
510
- await new Promise(resolve => setTimeout(resolve, delaySeconds * 1000));
511
-
512
- const duration = Date.now() - start;
513
- res.json({
514
- message: 'Test completed',
515
- duration_ms: duration,
516
- duration_sec: (duration/1000).toFixed(1),
517
- requested_delay: delaySeconds
518
  });
519
- });
520
 
521
  // --- HEALTH CHECK ---
522
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
@@ -528,7 +542,7 @@ app.get('/health', (req, res) => {
528
  cached_bots: flowCache.size,
529
  daily_active_ips: dailyUsage.size,
530
  uptime: process.uptime(),
531
- memory: process.memoryUsage()
532
  });
533
  });
534
 
@@ -539,32 +553,33 @@ app.use((req, res) => {
539
 
540
  // --- GLOBAL ERROR HANDLER ---
541
  app.use((err, req, res, next) => {
542
- console.error('[Error] Unhandled error:', err);
543
  res.status(500).json({ error: 'Internal server error' });
544
  });
545
 
546
- // --- GRACEFUL SHUTDOWN ---
547
  const server = app.listen(7860, '0.0.0.0', () => {
548
- console.log('===== Federated Proxy Started =====');
549
- console.log('Port: 7860');
550
- console.log(`Instances: ${INSTANCES.length}`);
551
- console.log(`Allowed Origins: ${allowedOrigins.length || 'Open mode'}`);
552
- console.log(`API Keys: ${API_KEYS.length || 'None'}`);
553
- console.log('====================================');
 
554
  });
555
 
556
  process.on('SIGTERM', () => {
557
- console.log('[System] SIGTERM received, shutting down gracefully...');
558
  server.close(() => {
559
- console.log('[System] Server closed');
560
  process.exit(0);
561
  });
562
  });
563
 
564
  process.on('SIGINT', () => {
565
- console.log('[System] SIGINT received, shutting down gracefully...');
566
  server.close(() => {
567
- console.log('[System] Server closed');
568
  process.exit(0);
569
  });
570
  });
 
7
 
8
  const app = express();
9
 
10
+ // --- PRODUCTION MODE ---
11
+ const PRODUCTION_MODE = process.env.PRODUCTION_MODE === 'true';
12
+
13
+ function log(message, level = 'info') {
14
+ if (PRODUCTION_MODE && level === 'debug') return;
15
+ console.log(message);
16
+ }
17
+
18
+ function logSensitive(message) {
19
+ if (!PRODUCTION_MODE) console.log(message);
20
+ }
21
+
22
+ // Mask sensitive data
23
+ function maskIP(ip) {
24
+ if (PRODUCTION_MODE) {
25
+ const parts = ip.split('.');
26
+ return parts.length === 4 ? `${parts[0]}.${parts[1]}.***.**` : 'masked';
27
+ }
28
+ return ip;
29
+ }
30
+
31
+ function maskOrigin(origin) {
32
+ if (PRODUCTION_MODE && origin && origin !== 'no-origin') {
33
+ try {
34
+ const url = new URL(origin);
35
+ return `${url.protocol}//${url.hostname.substring(0, 3)}***`;
36
+ } catch {
37
+ return 'masked';
38
+ }
39
+ }
40
+ return origin;
41
+ }
42
+
43
  app.use(helmet({
44
  contentSecurityPolicy: false,
45
  crossOriginEmbedderPolicy: false
 
57
  const origin = req.headers.origin;
58
  const apiKey = req.headers['x-api-key'];
59
 
 
60
  if (origin) {
61
  if (allowedOrigins.length === 0) return { valid: true, source: 'open-mode' };
62
  return {
63
  valid: allowedOrigins.includes(origin),
64
+ source: PRODUCTION_MODE ? 'authorized-origin' : origin
65
  };
66
  }
67
 
 
68
  if (apiKey) {
69
  if (API_KEYS.length === 0) return { valid: true, source: 'no-keys-configured' };
70
  return {
 
73
  };
74
  }
75
 
 
76
  return { valid: false, source: 'unauthorized' };
77
  }
78
 
79
+ // --- CORS ---
80
  const allowedOrigins = process.env.ALLOWED_ORIGINS
81
  ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
82
  : [];
 
90
  if (allowedOrigins.includes(origin)) {
91
  callback(null, true);
92
  } else {
93
+ log(`[Security] Blocked origin: ${maskOrigin(origin)}`);
94
  callback(new Error('Not allowed by CORS'));
95
  }
96
  },
 
113
  const auth = authenticateRequest(req);
114
 
115
  if (!auth.valid) {
116
+ log(`[Security] Blocked unauthorized request from ${maskIP(req.ip)}`);
117
  return res.status(401).json({
118
  error: 'Unauthorized',
119
  message: 'Valid origin or API key required'
120
  });
121
  }
122
 
123
+ log(`[Auth] Request authorized from: ${auth.source}`);
124
  next();
125
  });
126
 
 
136
  });
137
  app.use(limiter);
138
 
139
+ // --- REQUEST LOGGING (SAFE) ---
140
  app.use((req, res, next) => {
141
+ const ip = maskIP((req.ip || 'unknown').replace(/:\d+[^:]*$/, ''));
142
+ const origin = maskOrigin(req.headers.origin || 'no-origin');
143
  const apiKey = req.headers['x-api-key'] ? '***' : 'none';
144
+ const path = PRODUCTION_MODE ? req.path.split('/').slice(0, 4).join('/') + '/***' : req.path;
145
+
146
+ log(`[${new Date().toISOString()}] ${ip} -> ${req.method} ${path} | Origin: ${origin} | Key: ${apiKey}`);
147
  next();
148
  });
149
 
 
156
  if (today !== lastResetDate) {
157
  dailyUsage.clear();
158
  lastResetDate = today;
159
+ log('[System] Daily usage counters reset');
160
  }
161
  }
162
 
 
179
  dailyUsage.set(ip, count + 1);
180
 
181
  if (dailyUsage.size > 10000) {
182
+ log('[System] Daily usage map too large, clearing oldest entries', 'debug');
183
  const entries = Array.from(dailyUsage.entries()).slice(0, 1000);
184
  entries.forEach(([key]) => dailyUsage.delete(key));
185
  }
 
204
  const isBot = suspiciousBots.some(bot => userAgent.includes(bot));
205
 
206
  if (isBot) {
207
+ log(`[Security] Blocked bot from ${maskIP(req.ip)}`);
208
  return res.status(403).json({
209
  error: 'Automated access detected',
210
  message: 'This service is for web browsers only.'
 
217
  let INSTANCES = [];
218
  try {
219
  INSTANCES = JSON.parse(process.env.FLOWISE_INSTANCES || '[]');
220
+ log(`[System] Loaded ${INSTANCES.length} instances`);
221
  if (!Array.isArray(INSTANCES) || INSTANCES.length === 0) {
222
+ log('ERROR: FLOWISE_INSTANCES must be a non-empty array');
223
  }
224
  } catch (e) {
225
+ log("CRITICAL ERROR: Could not parse FLOWISE_INSTANCES JSON");
226
  }
227
 
228
  // --- CACHE WITH AUTO-CLEANUP ---
 
261
  }
262
 
263
  const instance = INSTANCES[instanceNum - 1];
264
+ logSensitive(`[System] Looking up '${botName}' in instance ${instanceNum}...`);
265
 
266
  const headers = {};
267
  if (instance.key && instance.key.length > 0) {
 
292
  timestamp: Date.now()
293
  });
294
 
295
+ logSensitive(`[System] Found '${botName}' -> ${match.id}`);
296
 
297
  return { id: match.id, instance };
298
  }
299
 
300
+ // --- STREAMING HANDLER ---
301
  async function handleStreamingResponse(flowiseResponse, clientRes) {
302
  clientRes.setHeader('Content-Type', 'text/event-stream');
303
  clientRes.setHeader('Cache-Control', 'no-cache');
304
  clientRes.setHeader('Connection', 'keep-alive');
305
  clientRes.setHeader('X-Accel-Buffering', 'no');
306
 
307
+ log('[Streaming] Forwarding SSE stream...');
308
 
309
  let streamStarted = false;
310
  let dataReceived = false;
311
  let lastDataTime = Date.now();
312
  let totalBytes = 0;
313
 
 
314
  const timeoutCheck = setInterval(() => {
315
  const timeSinceData = Date.now() - lastDataTime;
316
 
317
+ if (timeSinceData > 45000) {
318
+ log(`[Streaming] Timeout - no data for ${(timeSinceData/1000).toFixed(1)}s`);
319
  clearInterval(timeoutCheck);
320
 
321
  if (!dataReceived) {
322
+ log('[Streaming] Stream completed with NO data received!');
323
  if (!streamStarted) {
324
  clientRes.status(504).json({
325
  error: 'Gateway timeout',
 
340
  lastDataTime = Date.now();
341
  totalBytes += chunk.length;
342
 
343
+ logSensitive(`[Streaming] Received chunk: ${chunk.length} bytes (total: ${totalBytes})`);
344
  clientRes.write(chunk);
345
  });
346
 
 
348
  clearInterval(timeoutCheck);
349
 
350
  if (dataReceived) {
351
+ log(`[Streaming] Stream completed - ${totalBytes} bytes`);
352
  } else {
353
+ log('[Streaming] Stream completed but NO data received!');
354
  }
355
 
356
  clientRes.end();
 
358
 
359
  flowiseResponse.body.on('error', (err) => {
360
  clearInterval(timeoutCheck);
361
+ log('[Streaming Error]');
362
 
363
  if (streamStarted && dataReceived) {
364
  clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
 
369
  });
370
  }
371
 
372
+ // --- PREDICTION ROUTE ---
373
  app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
374
  try {
375
  const instanceNum = parseInt(req.params.instanceNum);
 
396
  headers['Authorization'] = `Bearer ${instance.key}`;
397
  }
398
 
 
399
  const startTime = Date.now();
400
+ logSensitive(`[Timing] Calling Flowise at ${new Date().toISOString()}`);
401
 
402
  const response = await fetchWithTimeout(
403
  `${instance.url}/api/v1/prediction/${id}`,
 
406
  headers,
407
  body: JSON.stringify(req.body)
408
  },
409
+ 60000
410
  );
411
 
412
  const duration = Date.now() - startTime;
413
+ log(`[Timing] Response received in ${(duration/1000).toFixed(1)}s`);
414
 
415
  if (!response.ok) {
416
  const errorText = await response.text();
417
+ logSensitive(`[Error] Instance returned ${response.status}: ${errorText.substring(0, 100)}`);
418
  return res.status(response.status).json({
419
  error: 'Flowise instance error',
420
  message: 'The chatbot instance returned an error.'
 
424
  const contentType = response.headers.get('content-type') || '';
425
 
426
  if (contentType.includes('text/event-stream')) {
427
+ log('[Streaming] Detected SSE response');
428
  return handleStreamingResponse(response, res);
429
  }
430
 
431
+ log('[Non-streaming] Parsing JSON response');
432
  const text = await response.text();
433
 
434
  try {
435
  const data = JSON.parse(text);
436
  res.status(200).json(data);
437
  } catch (e) {
438
+ log('[Error] Invalid JSON response');
439
  res.status(500).json({ error: 'Invalid response from Flowise' });
440
  }
441
 
442
  } catch (error) {
443
+ log(`[Error] ${error.message}`);
444
  res.status(500).json({
445
  error: 'Request failed',
446
  message: error.message
 
448
  }
449
  });
450
 
451
+ // --- CONFIG ROUTE ---
452
  app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
453
  try {
454
  const instanceNum = parseInt(req.params.instanceNum);
 
475
  res.status(200).json(data);
476
 
477
  } catch (error) {
478
+ log('[Error] Config request failed');
479
  res.status(404).json({ error: error.message });
480
  }
481
  });
482
 
483
+ // --- STREAMING CHECK ROUTE ---
484
  app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
485
  try {
486
  const instanceNum = parseInt(req.params.instanceNum);
 
507
  res.status(200).json(data);
508
 
509
  } catch (error) {
510
+ log('[Error] Streaming check failed', 'debug');
511
  res.status(200).json({ isStreaming: false });
512
  }
513
  });
514
 
515
+ // --- TEST ENDPOINTS (DISABLED IN PRODUCTION) ---
516
+ if (!PRODUCTION_MODE) {
517
+ app.get('/test-stream', (req, res) => {
518
+ res.setHeader('Content-Type', 'text/event-stream');
519
+ res.setHeader('Cache-Control', 'no-cache');
520
+ res.setHeader('Connection', 'keep-alive');
521
+
522
+ let count = 0;
523
+ const interval = setInterval(() => {
524
+ count++;
525
+ res.write(`data: {"message": "Test ${count}"}\n\n`);
526
+
527
+ if (count >= 5) {
528
+ clearInterval(interval);
529
+ res.end();
530
+ }
531
+ }, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  });
533
+ }
534
 
535
  // --- HEALTH CHECK ---
536
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
 
542
  cached_bots: flowCache.size,
543
  daily_active_ips: dailyUsage.size,
544
  uptime: process.uptime(),
545
+ production_mode: PRODUCTION_MODE
546
  });
547
  });
548
 
 
553
 
554
  // --- GLOBAL ERROR HANDLER ---
555
  app.use((err, req, res, next) => {
556
+ log('[Error] Unhandled error');
557
  res.status(500).json({ error: 'Internal server error' });
558
  });
559
 
560
+ // --- SERVER START ---
561
  const server = app.listen(7860, '0.0.0.0', () => {
562
+ log('===== Federated Proxy Started =====');
563
+ log(`Port: 7860`);
564
+ log(`Mode: ${PRODUCTION_MODE ? 'PRODUCTION' : 'DEVELOPMENT'}`);
565
+ log(`Instances: ${INSTANCES.length}`);
566
+ log(`Allowed Origins: ${allowedOrigins.length || 'Open'}`);
567
+ log(`API Keys: ${API_KEYS.length || 'None'}`);
568
+ log('====================================');
569
  });
570
 
571
  process.on('SIGTERM', () => {
572
+ log('[System] Shutting down gracefully...');
573
  server.close(() => {
574
+ log('[System] Server closed');
575
  process.exit(0);
576
  });
577
  });
578
 
579
  process.on('SIGINT', () => {
580
+ log('[System] Shutting down gracefully...');
581
  server.close(() => {
582
+ log('[System] Server closed');
583
  process.exit(0);
584
  });
585
  });