@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 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: req.query,
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: req.query,
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
- console.error(`[Proxy] Error connecting to ${target}:`, error.message);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metric-im/administrate",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Tools for site administration",
5
5
  "homepage": "https://github.com/metric-im/administrate#readme",
6
6
  "bugs": {
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Read(//home/msprague/workspace/sitewell/**)",
5
- "Read(//home/msprague/workspace/rootz/data-wallet-demos/**)"
6
- ],
7
- "deny": [],
8
- "ask": []
9
- }
10
- }