Spaces:
Paused
Paused
File size: 4,773 Bytes
fc05a6d 9f98007 fc05a6d 9f98007 fc05a6d 9f98007 fc05a6d 9f98007 fc05a6d 9f98007 fc05a6d 9f98007 fc05a6d 2aadb1f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
require('dotenv').config();
const app = express();
// --- 1. SECURITY: TRUST PROXY ---
// Required for Hugging Face to see the Real IP for rate limiting
app.set('trust proxy', 1);
// --- 2. SECURITY: RATE LIMITING ---
// Limit each IP to 100 requests per 15 minutes
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { error: "Too many requests, please try again later." }
});
app.use(limiter);
// --- 3. SECURITY: STRICT CORS ---
const allowedOrigins = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : [];
app.use(cors({
origin: function (origin, callback) {
// Open Mode: If list is empty or origin is null (tools/mobile), allow it.
if (!origin || allowedOrigins.length === 0) return callback(null, true);
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
app.use(express.json());
// --- 4. MULTI-INSTANCE CONFIGURATION ---
let INSTANCES = [];
try {
// Example Secret: [{"url":"https://my-flowise.com","key":"tr-123"}, {"url":"https://open-flowise.com","key":""}]
INSTANCES = JSON.parse(process.env.FLOWISE_INSTANCES || '[]');
} catch (e) {
console.error("CRITICAL ERROR: Could not parse FLOWISE_INSTANCES JSON", e);
}
// Master Cache: Maps "bot-name" -> { flowId, instanceUrl, apiKey }
let flowDirectory = {};
let lastCacheUpdate = 0;
// --- 5. DYNAMIC DISCOVERY ---
async function refreshFlowDirectory() {
// Cache valid for 60 seconds
if (Date.now() - lastCacheUpdate < 60000 && Object.keys(flowDirectory).length > 0) return;
console.log(`[System] Scanning ${INSTANCES.length} Flowise Instances...`);
const newDirectory = {};
// Run all fetches in parallel
const promises = INSTANCES.map(async (inst) => {
try {
// Smart Auth: Only add header if key exists
const headers = {};
if (inst.key && inst.key.length > 0) {
headers['Authorization'] = `Bearer ${inst.key}`;
}
const res = await fetch(`${inst.url}/api/v1/chatflows`, { headers });
if(!res.ok) throw new Error(`Status ${res.status}`);
const flows = await res.json();
flows.forEach(flow => {
const alias = flow.name.toLowerCase().replace(/\s+/g, '-');
newDirectory[alias] = {
id: flow.id,
host: inst.url,
key: inst.key
};
});
} catch (err) {
console.error(`[Error] Failed to fetch from ${inst.url}:`, err.message);
}
});
await Promise.allSettled(promises);
flowDirectory = newDirectory;
lastCacheUpdate = Date.now();
console.log(`[System] Directory Updated. Serving ${Object.keys(flowDirectory).length} bots.`);
}
// --- 6. THE ROUTE ---
app.post('/api/v1/prediction/:botName', async (req, res) => {
const botName = req.params.botName.toLowerCase();
await refreshFlowDirectory();
const target = flowDirectory[botName];
if (!target) {
lastCacheUpdate = 0;
await refreshFlowDirectory();
if (!flowDirectory[botName]) {
return res.status(404).json({ error: `Bot '${botName}' not found.` });
}
}
const finalTarget = flowDirectory[botName];
try {
// Construct Headers for forwarding
const forwardHeaders = {
'Content-Type': 'application/json',
'HTTP-Referer': req.headers.origin || 'https://huggingface.co',
'X-Title': 'FederatedProxy'
};
// Smart Auth: Only add Bearer if key exists
if (finalTarget.key && finalTarget.key.length > 0) {
forwardHeaders['Authorization'] = `Bearer ${finalTarget.key}`;
}
const flowiseResponse = await fetch(`${finalTarget.host}/api/v1/prediction/${finalTarget.id}`, {
method: 'POST',
headers: forwardHeaders,
body: JSON.stringify(req.body)
});
const data = await flowiseResponse.json();
res.status(flowiseResponse.status).json(data);
} catch (error) {
console.error("Proxy Forwarding Error:", error);
res.status(500).json({ error: 'Proxy forwarding failed.' });
}
});
app.get('/', (req, res) => res.send('Federated Proxy Active'));
app.listen(7860, '0.0.0.0', () => console.log('Federated Proxy running on port 7860')); |