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