@metric-im/administrate 1.0.1 → 1.0.3
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/multisite.mjs +52 -3
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -10
package/multisite.mjs
CHANGED
|
@@ -19,6 +19,7 @@ export class MultiSite {
|
|
|
19
19
|
this.usedPorts = new Set();
|
|
20
20
|
this.healthCheckIntervals = new Map();
|
|
21
21
|
this.isShuttingDown = false;
|
|
22
|
+
this.errorLogTracker = new Map(); // Track error frequency for rate limiting
|
|
22
23
|
this.setupCleanup();
|
|
23
24
|
}
|
|
24
25
|
get spawnPort() {
|
|
@@ -176,9 +177,41 @@ export class MultiSite {
|
|
|
176
177
|
}
|
|
177
178
|
});
|
|
178
179
|
}
|
|
180
|
+
|
|
181
|
+
// Rate limit error logging to prevent spam
|
|
182
|
+
shouldLogError(target, clientIP) {
|
|
183
|
+
const key = `${clientIP}:${target}`;
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const logEntry = this.errorLogTracker.get(key);
|
|
186
|
+
|
|
187
|
+
if (!logEntry) {
|
|
188
|
+
this.errorLogTracker.set(key, { count: 1, firstSeen: now, lastLogged: now });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
logEntry.count++;
|
|
193
|
+
|
|
194
|
+
// Reset counter if it's been more than 5 minutes since first error
|
|
195
|
+
if (now - logEntry.firstSeen > 300000) {
|
|
196
|
+
logEntry.count = 1;
|
|
197
|
+
logEntry.firstSeen = now;
|
|
198
|
+
logEntry.lastLogged = now;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Log first error, then every 10th error, but not more than once per minute
|
|
203
|
+
if (logEntry.count === 1 ||
|
|
204
|
+
(logEntry.count % 10 === 0 && now - logEntry.lastLogged > 60000)) {
|
|
205
|
+
logEntry.lastLogged = now;
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
179
211
|
static async attach(app,options) {
|
|
180
212
|
const instance = new MultiSite(app,options);
|
|
181
213
|
instance.config = new Config();
|
|
214
|
+
|
|
182
215
|
// spawn declared sites
|
|
183
216
|
if (fs.existsSync(resolve('./sites'))) {
|
|
184
217
|
instance.sites = (fs.readdirSync(resolve('./sites'))).reduce((result,hostName)=>{
|
|
@@ -194,6 +227,7 @@ export class MultiSite {
|
|
|
194
227
|
app.use('/',instance.routes());
|
|
195
228
|
return instance;
|
|
196
229
|
}
|
|
230
|
+
|
|
197
231
|
routes() {
|
|
198
232
|
const router = express.Router();
|
|
199
233
|
|
|
@@ -205,6 +239,7 @@ export class MultiSite {
|
|
|
205
239
|
const method = req.method;
|
|
206
240
|
const isBodyMethod = ['POST', 'PUT', 'PATCH'].includes(method);
|
|
207
241
|
const payload = isBodyMethod ? req.body : null;
|
|
242
|
+
const clientIP = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for'];
|
|
208
243
|
|
|
209
244
|
// Debug logging for POST requests
|
|
210
245
|
if (method === 'POST') {
|
|
@@ -230,7 +265,7 @@ export class MultiSite {
|
|
|
230
265
|
url: target,
|
|
231
266
|
headers: headersToForward,
|
|
232
267
|
data: payload,
|
|
233
|
-
params
|
|
268
|
+
// Don't pass params - req.url already contains query string
|
|
234
269
|
validateStatus: () => true,
|
|
235
270
|
timeout: 30000,
|
|
236
271
|
responseType: 'stream'
|
|
@@ -252,7 +287,7 @@ export class MultiSite {
|
|
|
252
287
|
url: target,
|
|
253
288
|
headers: headersToForward,
|
|
254
289
|
data: payload,
|
|
255
|
-
params
|
|
290
|
+
// Don't pass params - req.url already contains query string
|
|
256
291
|
validateStatus: () => true,
|
|
257
292
|
timeout: 30000,
|
|
258
293
|
responseType: 'text'
|
|
@@ -267,7 +302,15 @@ export class MultiSite {
|
|
|
267
302
|
res.status(response.status).set(cleanHeaders).send(response.data);
|
|
268
303
|
}
|
|
269
304
|
} catch (error) {
|
|
270
|
-
|
|
305
|
+
// Rate limit error logging to prevent spam
|
|
306
|
+
if (this.shouldLogError(target, clientIP)) {
|
|
307
|
+
const logEntry = this.errorLogTracker.get(`${clientIP}:${target}`);
|
|
308
|
+
if (logEntry && logEntry.count > 1) {
|
|
309
|
+
console.error(`${clientIP}:E: [Proxy] Error connecting to ${target}: ${error.message} (${logEntry.count} times)`);
|
|
310
|
+
} else {
|
|
311
|
+
console.error(`${clientIP}:E: [Proxy] Error connecting to ${target}: ${error.message}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
271
314
|
|
|
272
315
|
// // Clean up dead processes
|
|
273
316
|
// this.removeDeadSites();
|
|
@@ -297,6 +340,12 @@ export class Site {
|
|
|
297
340
|
this.name = name;
|
|
298
341
|
this.options = options;
|
|
299
342
|
this.parent = parent;
|
|
343
|
+
try {
|
|
344
|
+
const envars = JSON.parse(process.env.SITE_ENV||{});
|
|
345
|
+
Object.assign(this.options.env,envars[this.name]||{});
|
|
346
|
+
} catch(e) {
|
|
347
|
+
console.log(`Unable to parse SITE_ENV... continuing: ${e.message}`);
|
|
348
|
+
}
|
|
300
349
|
}
|
|
301
350
|
static Spawn(name, options, parent) {
|
|
302
351
|
const instance = new Site(name, options, parent);
|
package/package.json
CHANGED