@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/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.js +89 -4
- package/dist/express.js.map +1 -1
- package/dist/express.mjs +89 -4
- package/dist/express.mjs.map +1 -1
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +89 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -4
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs.d.mts +1 -1
- package/dist/nextjs.d.ts +1 -1
- package/dist/nextjs.js +89 -4
- package/dist/nextjs.js.map +1 -1
- package/dist/nextjs.mjs +89 -4
- package/dist/nextjs.mjs.map +1 -1
- package/dist/{tracker-BJBniOSu.d.mts → tracker-BXvPYi1R.d.mts} +7 -0
- package/dist/{tracker-BJBniOSu.d.ts → tracker-BXvPYi1R.d.ts} +7 -0
- package/package.json +1 -1
package/dist/nextjs.mjs
CHANGED
|
@@ -5,6 +5,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// src/tracker.ts
|
|
9
|
+
import http2 from "http";
|
|
10
|
+
import https2 from "https";
|
|
11
|
+
|
|
8
12
|
// src/config.ts
|
|
9
13
|
var DEFAULT_API_URL = "https://api.shieldpress.net";
|
|
10
14
|
function resolveConfig(config) {
|
|
@@ -81,8 +85,8 @@ function getPublicIpCached() {
|
|
|
81
85
|
if (Date.now() - publicIpLastFetch > 6e5) {
|
|
82
86
|
publicIpLastFetch = Date.now();
|
|
83
87
|
try {
|
|
84
|
-
const
|
|
85
|
-
const req =
|
|
88
|
+
const http3 = __require("https");
|
|
89
|
+
const req = http3.get("https://api.ipify.org", { timeout: 3e3 }, (res) => {
|
|
86
90
|
let data = "";
|
|
87
91
|
res.on("data", (chunk) => {
|
|
88
92
|
data += chunk;
|
|
@@ -1118,23 +1122,49 @@ function sanitizePayload(payload) {
|
|
|
1118
1122
|
}
|
|
1119
1123
|
return p;
|
|
1120
1124
|
}
|
|
1121
|
-
var
|
|
1125
|
+
var _Reporter = class _Reporter {
|
|
1122
1126
|
constructor(apiUrl, apiKey, logger) {
|
|
1123
1127
|
this.queue = [];
|
|
1124
1128
|
this.sending = false;
|
|
1129
|
+
this.consecutiveFailures = 0;
|
|
1130
|
+
this.backoffUntil = 0;
|
|
1125
1131
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
1126
1132
|
this.apiKey = apiKey;
|
|
1127
1133
|
this.logger = logger;
|
|
1128
1134
|
}
|
|
1129
1135
|
async send(payload) {
|
|
1130
1136
|
try {
|
|
1137
|
+
if (this.queue.length >= _Reporter.MAX_QUEUE_SIZE) {
|
|
1138
|
+
const dropped = this.queue.length - _Reporter.MAX_QUEUE_SIZE + 1;
|
|
1139
|
+
this.queue.splice(0, dropped);
|
|
1140
|
+
this.logger.warn(`Queue full, dropped ${dropped} oldest report(s)`);
|
|
1141
|
+
}
|
|
1131
1142
|
this.queue.push(payload);
|
|
1132
1143
|
if (this.sending) return true;
|
|
1144
|
+
const now = Date.now();
|
|
1145
|
+
if (now < this.backoffUntil) {
|
|
1146
|
+
this.logger.info(`Backoff active, will retry in ${Math.round((this.backoffUntil - now) / 1e3)}s`);
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1133
1149
|
this.sending = true;
|
|
1134
1150
|
try {
|
|
1135
1151
|
while (this.queue.length > 0) {
|
|
1152
|
+
if (Date.now() < this.backoffUntil) break;
|
|
1136
1153
|
const batch = this.queue.splice(0, 10);
|
|
1137
|
-
|
|
1154
|
+
try {
|
|
1155
|
+
await this.transmit(batch);
|
|
1156
|
+
this.consecutiveFailures = 0;
|
|
1157
|
+
this.backoffUntil = 0;
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
this.queue.unshift(...batch);
|
|
1160
|
+
this.consecutiveFailures++;
|
|
1161
|
+
const delay = Math.min(1e3 * Math.pow(2, this.consecutiveFailures - 1), _Reporter.MAX_BACKOFF_MS);
|
|
1162
|
+
this.backoffUntil = Date.now() + delay;
|
|
1163
|
+
this.logger.warn(
|
|
1164
|
+
`Transmit failed (attempt ${this.consecutiveFailures}), backing off ${Math.round(delay / 1e3)}s: ${err instanceof Error ? err.message : err}`
|
|
1165
|
+
);
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1138
1168
|
}
|
|
1139
1169
|
return true;
|
|
1140
1170
|
} finally {
|
|
@@ -1157,6 +1187,8 @@ var Reporter = class {
|
|
|
1157
1187
|
`Payload too large (${body.length} bytes), truncating arrays (key=${maskApiKey(this.apiKey)})`
|
|
1158
1188
|
);
|
|
1159
1189
|
for (const p of sanitized) {
|
|
1190
|
+
p._truncated = true;
|
|
1191
|
+
p._originalSize = body.length;
|
|
1160
1192
|
if (p.security) {
|
|
1161
1193
|
p.security.events = p.security.events.slice(0, 10);
|
|
1162
1194
|
}
|
|
@@ -1224,6 +1256,11 @@ var Reporter = class {
|
|
|
1224
1256
|
});
|
|
1225
1257
|
}
|
|
1226
1258
|
};
|
|
1259
|
+
/** Max queued reports before dropping oldest */
|
|
1260
|
+
_Reporter.MAX_QUEUE_SIZE = 100;
|
|
1261
|
+
/** Max backoff delay in ms (60 seconds) */
|
|
1262
|
+
_Reporter.MAX_BACKOFF_MS = 6e4;
|
|
1263
|
+
var Reporter = _Reporter;
|
|
1227
1264
|
|
|
1228
1265
|
// src/tracker.ts
|
|
1229
1266
|
var ShieldPressTracker = class {
|
|
@@ -1232,6 +1269,7 @@ var ShieldPressTracker = class {
|
|
|
1232
1269
|
this.lagTimer = null;
|
|
1233
1270
|
this.started = false;
|
|
1234
1271
|
this.headersChecked = false;
|
|
1272
|
+
this.autoHooked = false;
|
|
1235
1273
|
this.config = resolveConfig(userConfig);
|
|
1236
1274
|
this.logger = createLogger(this.config.debug);
|
|
1237
1275
|
this.reporter = new Reporter(this.config.apiUrl, this.config.apiKey, this.logger);
|
|
@@ -1259,6 +1297,9 @@ var ShieldPressTracker = class {
|
|
|
1259
1297
|
this.errorCollector.capture(err, { type: "unhandledRejection" });
|
|
1260
1298
|
});
|
|
1261
1299
|
}
|
|
1300
|
+
if (this.config.httpTracking) {
|
|
1301
|
+
this.autoHookHttpServers();
|
|
1302
|
+
}
|
|
1262
1303
|
if (this.config.systemMetrics) {
|
|
1263
1304
|
collectSystemMetrics();
|
|
1264
1305
|
}
|
|
@@ -1378,6 +1419,50 @@ var ShieldPressTracker = class {
|
|
|
1378
1419
|
}
|
|
1379
1420
|
return this.reporter.send(payload);
|
|
1380
1421
|
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Monkey-patch http.Server and https.Server so every incoming request
|
|
1424
|
+
* is automatically tracked without requiring the user to install middleware.
|
|
1425
|
+
* Safe to call multiple times — patches only once.
|
|
1426
|
+
*/
|
|
1427
|
+
autoHookHttpServers() {
|
|
1428
|
+
if (this.autoHooked) return;
|
|
1429
|
+
this.autoHooked = true;
|
|
1430
|
+
const tracker = this;
|
|
1431
|
+
const patchEmit = (ServerProto) => {
|
|
1432
|
+
const originalEmit = ServerProto.emit;
|
|
1433
|
+
ServerProto.emit = function(event, ...args) {
|
|
1434
|
+
if (event === "request") {
|
|
1435
|
+
const req = args[0];
|
|
1436
|
+
const res = args[1];
|
|
1437
|
+
const start = process.hrtime.bigint();
|
|
1438
|
+
const url = req.url || "/";
|
|
1439
|
+
res.on("finish", () => {
|
|
1440
|
+
try {
|
|
1441
|
+
const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
1442
|
+
const parsedPath = url.split("?")[0] || "/";
|
|
1443
|
+
tracker.recordRequest({
|
|
1444
|
+
path: parsedPath,
|
|
1445
|
+
method: req.method || "GET",
|
|
1446
|
+
statusCode: res.statusCode,
|
|
1447
|
+
durationMs: Math.round(durationMs * 100) / 100
|
|
1448
|
+
});
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
return originalEmit.apply(this, [event, ...args]);
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
try {
|
|
1457
|
+
patchEmit(http2.Server.prototype);
|
|
1458
|
+
patchEmit(https2.Server.prototype);
|
|
1459
|
+
this.logger.info("Auto-hooked http/https servers for request tracking");
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
this.logger.warn(
|
|
1462
|
+
`Failed to auto-hook HTTP servers: ${err instanceof Error ? err.message : err}`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1381
1466
|
shouldIgnorePath(path) {
|
|
1382
1467
|
return this.config.ignorePaths.some((pattern) => {
|
|
1383
1468
|
if (pattern.endsWith("/*")) {
|