@shieldpress/tracker 1.0.0 → 1.1.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.
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { T as TrackerConfig, S as ShieldPressTracker } from './tracker-BJBniOSu.mjs';
2
+ import { T as TrackerConfig, S as ShieldPressTracker } from './tracker-BXvPYi1R.mjs';
3
3
 
4
4
  /**
5
5
  * Express middleware that tracks HTTP requests, errors, and security threats.
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { T as TrackerConfig, S as ShieldPressTracker } from './tracker-BJBniOSu.js';
2
+ import { T as TrackerConfig, S as ShieldPressTracker } from './tracker-BXvPYi1R.js';
3
3
 
4
4
  /**
5
5
  * Express middleware that tracks HTTP requests, errors, and security threats.
package/dist/express.js CHANGED
@@ -35,6 +35,10 @@ __export(express_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(express_exports);
37
37
 
38
+ // src/tracker.ts
39
+ var import_http2 = __toESM(require("http"));
40
+ var import_https2 = __toESM(require("https"));
41
+
38
42
  // src/config.ts
39
43
  var DEFAULT_API_URL = "https://api.shieldpress.net";
40
44
  function resolveConfig(config) {
@@ -111,8 +115,8 @@ function getPublicIpCached() {
111
115
  if (Date.now() - publicIpLastFetch > 6e5) {
112
116
  publicIpLastFetch = Date.now();
113
117
  try {
114
- const http2 = require("https");
115
- const req = http2.get("https://api.ipify.org", { timeout: 3e3 }, (res) => {
118
+ const http3 = require("https");
119
+ const req = http3.get("https://api.ipify.org", { timeout: 3e3 }, (res) => {
116
120
  let data = "";
117
121
  res.on("data", (chunk) => {
118
122
  data += chunk;
@@ -1148,23 +1152,49 @@ function sanitizePayload(payload) {
1148
1152
  }
1149
1153
  return p;
1150
1154
  }
1151
- var Reporter = class {
1155
+ var _Reporter = class _Reporter {
1152
1156
  constructor(apiUrl, apiKey, logger) {
1153
1157
  this.queue = [];
1154
1158
  this.sending = false;
1159
+ this.consecutiveFailures = 0;
1160
+ this.backoffUntil = 0;
1155
1161
  this.apiUrl = apiUrl.replace(/\/$/, "");
1156
1162
  this.apiKey = apiKey;
1157
1163
  this.logger = logger;
1158
1164
  }
1159
1165
  async send(payload) {
1160
1166
  try {
1167
+ if (this.queue.length >= _Reporter.MAX_QUEUE_SIZE) {
1168
+ const dropped = this.queue.length - _Reporter.MAX_QUEUE_SIZE + 1;
1169
+ this.queue.splice(0, dropped);
1170
+ this.logger.warn(`Queue full, dropped ${dropped} oldest report(s)`);
1171
+ }
1161
1172
  this.queue.push(payload);
1162
1173
  if (this.sending) return true;
1174
+ const now = Date.now();
1175
+ if (now < this.backoffUntil) {
1176
+ this.logger.info(`Backoff active, will retry in ${Math.round((this.backoffUntil - now) / 1e3)}s`);
1177
+ return true;
1178
+ }
1163
1179
  this.sending = true;
1164
1180
  try {
1165
1181
  while (this.queue.length > 0) {
1182
+ if (Date.now() < this.backoffUntil) break;
1166
1183
  const batch = this.queue.splice(0, 10);
1167
- await this.transmit(batch);
1184
+ try {
1185
+ await this.transmit(batch);
1186
+ this.consecutiveFailures = 0;
1187
+ this.backoffUntil = 0;
1188
+ } catch (err) {
1189
+ this.queue.unshift(...batch);
1190
+ this.consecutiveFailures++;
1191
+ const delay = Math.min(1e3 * Math.pow(2, this.consecutiveFailures - 1), _Reporter.MAX_BACKOFF_MS);
1192
+ this.backoffUntil = Date.now() + delay;
1193
+ this.logger.warn(
1194
+ `Transmit failed (attempt ${this.consecutiveFailures}), backing off ${Math.round(delay / 1e3)}s: ${err instanceof Error ? err.message : err}`
1195
+ );
1196
+ break;
1197
+ }
1168
1198
  }
1169
1199
  return true;
1170
1200
  } finally {
@@ -1187,6 +1217,8 @@ var Reporter = class {
1187
1217
  `Payload too large (${body.length} bytes), truncating arrays (key=${maskApiKey(this.apiKey)})`
1188
1218
  );
1189
1219
  for (const p of sanitized) {
1220
+ p._truncated = true;
1221
+ p._originalSize = body.length;
1190
1222
  if (p.security) {
1191
1223
  p.security.events = p.security.events.slice(0, 10);
1192
1224
  }
@@ -1254,6 +1286,11 @@ var Reporter = class {
1254
1286
  });
1255
1287
  }
1256
1288
  };
1289
+ /** Max queued reports before dropping oldest */
1290
+ _Reporter.MAX_QUEUE_SIZE = 100;
1291
+ /** Max backoff delay in ms (60 seconds) */
1292
+ _Reporter.MAX_BACKOFF_MS = 6e4;
1293
+ var Reporter = _Reporter;
1257
1294
 
1258
1295
  // src/tracker.ts
1259
1296
  var ShieldPressTracker = class {
@@ -1262,6 +1299,7 @@ var ShieldPressTracker = class {
1262
1299
  this.lagTimer = null;
1263
1300
  this.started = false;
1264
1301
  this.headersChecked = false;
1302
+ this.autoHooked = false;
1265
1303
  this.config = resolveConfig(userConfig);
1266
1304
  this.logger = createLogger(this.config.debug);
1267
1305
  this.reporter = new Reporter(this.config.apiUrl, this.config.apiKey, this.logger);
@@ -1289,6 +1327,9 @@ var ShieldPressTracker = class {
1289
1327
  this.errorCollector.capture(err, { type: "unhandledRejection" });
1290
1328
  });
1291
1329
  }
1330
+ if (this.config.httpTracking) {
1331
+ this.autoHookHttpServers();
1332
+ }
1292
1333
  if (this.config.systemMetrics) {
1293
1334
  collectSystemMetrics();
1294
1335
  }
@@ -1408,6 +1449,50 @@ var ShieldPressTracker = class {
1408
1449
  }
1409
1450
  return this.reporter.send(payload);
1410
1451
  }
1452
+ /**
1453
+ * Monkey-patch http.Server and https.Server so every incoming request
1454
+ * is automatically tracked without requiring the user to install middleware.
1455
+ * Safe to call multiple times — patches only once.
1456
+ */
1457
+ autoHookHttpServers() {
1458
+ if (this.autoHooked) return;
1459
+ this.autoHooked = true;
1460
+ const tracker = this;
1461
+ const patchEmit = (ServerProto) => {
1462
+ const originalEmit = ServerProto.emit;
1463
+ ServerProto.emit = function(event, ...args) {
1464
+ if (event === "request") {
1465
+ const req = args[0];
1466
+ const res = args[1];
1467
+ const start = process.hrtime.bigint();
1468
+ const url = req.url || "/";
1469
+ res.on("finish", () => {
1470
+ try {
1471
+ const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
1472
+ const parsedPath = url.split("?")[0] || "/";
1473
+ tracker.recordRequest({
1474
+ path: parsedPath,
1475
+ method: req.method || "GET",
1476
+ statusCode: res.statusCode,
1477
+ durationMs: Math.round(durationMs * 100) / 100
1478
+ });
1479
+ } catch {
1480
+ }
1481
+ });
1482
+ }
1483
+ return originalEmit.apply(this, [event, ...args]);
1484
+ };
1485
+ };
1486
+ try {
1487
+ patchEmit(import_http2.default.Server.prototype);
1488
+ patchEmit(import_https2.default.Server.prototype);
1489
+ this.logger.info("Auto-hooked http/https servers for request tracking");
1490
+ } catch (err) {
1491
+ this.logger.warn(
1492
+ `Failed to auto-hook HTTP servers: ${err instanceof Error ? err.message : err}`
1493
+ );
1494
+ }
1495
+ }
1411
1496
  shouldIgnorePath(path) {
1412
1497
  return this.config.ignorePaths.some((pattern) => {
1413
1498
  if (pattern.endsWith("/*")) {