@anomira/node-sdk 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -10,43 +10,60 @@ npm install @anomira/node-sdk
10
10
 
11
11
  ## Quick Start
12
12
 
13
- ```ts
13
+ ```js
14
14
  import { SentinelAPI } from "@anomira/node-sdk";
15
15
 
16
16
  const sentinel = new SentinelAPI({
17
- apiKey: process.env.ANOMIRA_API_KEY!,
18
- appId: process.env.ANOMIRA_APP_ID!,
17
+ apiKey: process.env.SENTINEL_API_KEY,
18
+ appId: process.env.SENTINEL_APP_ID,
19
+ ingestUrl: process.env.SENTINEL_INGEST_URL,
20
+ debug: true,
19
21
  });
20
22
  ```
21
23
 
22
24
  ## Express
23
25
 
24
- ```ts
26
+ ```js
25
27
  import express from "express";
26
28
  import { SentinelAPI } from "@anomira/node-sdk";
27
29
 
28
30
  const app = express();
31
+
29
32
  const sentinel = new SentinelAPI({
30
- apiKey: process.env.ANOMIRA_API_KEY!,
31
- appId: process.env.ANOMIRA_APP_ID!,
33
+ apiKey: process.env.SENTINEL_API_KEY,
34
+ appId: process.env.SENTINEL_APP_ID,
35
+ ingestUrl: process.env.SENTINEL_INGEST_URL,
36
+ debug: true,
37
+ captureConsole: true, // forwards console.log/warn/error to Logs dashboard
38
+ service: "my-api",
39
+ detect: {
40
+ bruteForce: true,
41
+ rateAbuse: true,
42
+ pathTraversal: true,
43
+ xss: true,
44
+ scanDetection: true,
45
+ geoVelocity: true,
46
+ },
32
47
  });
33
48
 
34
- // Add as global middleware — must come before your routes
35
- app.use(sentinel.express());
49
+ app.use(express.json());
50
+ app.use(sentinel.express()); // auto-instruments all routes
36
51
 
37
52
  app.listen(3000);
38
53
  ```
39
54
 
40
55
  ## Fastify
41
56
 
42
- ```ts
57
+ ```js
43
58
  import Fastify from "fastify";
44
59
  import { SentinelAPI } from "@anomira/node-sdk";
45
60
 
46
61
  const app = Fastify();
62
+
47
63
  const sentinel = new SentinelAPI({
48
- apiKey: process.env.ANOMIRA_API_KEY!,
49
- appId: process.env.ANOMIRA_APP_ID!,
64
+ apiKey: process.env.SENTINEL_API_KEY,
65
+ appId: process.env.SENTINEL_APP_ID,
66
+ ingestUrl: process.env.SENTINEL_INGEST_URL,
50
67
  });
51
68
 
52
69
  await app.register(sentinel.fastify());
@@ -56,7 +73,7 @@ app.listen({ port: 3000 });
56
73
 
57
74
  ## Manual Event Tracking
58
75
 
59
- ```ts
76
+ ```js
60
77
  // Track a failed OTP attempt
61
78
  sentinel.track("auth.otp.failed", {
62
79
  ip: req.ip,
@@ -73,17 +90,17 @@ sentinel.trackPhoneAuth({ ip: req.ip, userId: user.id, phone: user.phone });
73
90
 
74
91
  ## Structured Logging
75
92
 
76
- ```ts
77
- sentinel.log("info", "User registered", { userId: user.id });
78
- sentinel.log("warn", "Slow DB query", { queryMs: 1240 });
79
- sentinel.log("error", "Payment failed", { reason: err.message });
93
+ ```js
94
+ sentinel.log("info", "User registered", { userId: user.id });
95
+ sentinel.log("warn", "Slow DB query", { queryMs: 1240 });
96
+ sentinel.log("error", "Payment failed", { reason: err.message });
80
97
  ```
81
98
 
82
99
  ## Blocklist & Firewall
83
100
 
84
- The SDK syncs your blocklist and firewall rules from the dashboard every 60 seconds. In your own middleware you can check:
101
+ The SDK syncs your blocklist and firewall rules from the dashboard every 60 seconds. Check them in your own middleware:
85
102
 
86
- ```ts
103
+ ```js
87
104
  if (sentinel.isBlocked(req.ip)) {
88
105
  return res.status(403).json({ error: "Forbidden" });
89
106
  }
@@ -91,7 +108,7 @@ if (sentinel.isBlocked(req.ip)) {
91
108
  const match = sentinel.matchFirewallRule({
92
109
  url: req.url,
93
110
  body: req.body,
94
- headers: req.headers as Record<string, string>,
111
+ headers: req.headers,
95
112
  ip: req.ip,
96
113
  });
97
114
  if (match?.rule.action === "block") {
@@ -109,13 +126,21 @@ npx anomira scan ./src
109
126
 
110
127
  Exit code `1` if violations are found — CI/CD compatible.
111
128
 
129
+ ## Environment Variables
130
+
131
+ | Variable | Description |
132
+ |---|---|
133
+ | `SENTINEL_API_KEY` | Your Anomira API key |
134
+ | `SENTINEL_APP_ID` | Your Anomira app ID |
135
+ | `SENTINEL_INGEST_URL` | Ingest endpoint (from your dashboard) |
136
+
112
137
  ## Configuration
113
138
 
114
139
  | Option | Type | Default | Description |
115
140
  |---|---|---|---|
116
141
  | `apiKey` | `string` | — | Your Anomira API key (required) |
117
142
  | `appId` | `string` | — | Your Anomira app ID (required) |
118
- | `ingestUrl` | `string` | Anomira cloud | Custom ingest endpoint |
143
+ | `ingestUrl` | `string` | Anomira cloud | Ingest endpoint URL |
119
144
  | `debug` | `boolean` | `false` | Log SDK activity to console |
120
145
  | `captureConsole` | `boolean` | `false` | Forward `console.*` calls to the Logs dashboard |
121
146
  | `service` | `string` | `"app"` | Service name tag for logs |
@@ -126,6 +151,17 @@ Exit code `1` if violations are found — CI/CD compatible.
126
151
  | `detect.scanDetection` | `boolean` | `true` | Detect scanner/bot probing |
127
152
  | `detect.geoVelocity` | `boolean` | `true` | Detect impossible travel between logins |
128
153
 
154
+ ## Graceful Shutdown
155
+
156
+ Always flush pending events before your process exits:
157
+
158
+ ```js
159
+ process.on("SIGTERM", async () => {
160
+ await sentinel.flush();
161
+ process.exit(0);
162
+ });
163
+ ```
164
+
129
165
  ## License
130
166
 
131
167
  MIT
package/dist/index.cjs CHANGED
@@ -297,6 +297,8 @@ var SentinelClient = class {
297
297
  this.logFlushTimer = null;
298
298
  this.blocklistTimer = null;
299
299
  this.firewallTimer = null;
300
+ /** True when credentials are missing — all operations become no-ops. */
301
+ this.disabled = false;
300
302
  /** In-process cache of blocked IPs — refreshed every 60 s from the ingest server. */
301
303
  this.blockedIpCache = /* @__PURE__ */ new Set();
302
304
  /** In-process cache of firewall rules with pre-compiled regex — refreshed every 60 s. */
@@ -305,8 +307,28 @@ var SentinelClient = class {
305
307
  this._origLog = console.log.bind(console);
306
308
  this._origWarn = console.warn.bind(console);
307
309
  this._origError = console.error.bind(console);
308
- if (!config.apiKey) throw new Error("[SentinelAPI] apiKey is required");
309
- if (!config.appId) throw new Error("[SentinelAPI] appId is required");
310
+ if (!config.apiKey || !config.appId) {
311
+ const missing = [!config.apiKey && "apiKey", !config.appId && "appId"].filter(Boolean).join(", ");
312
+ console.warn(`[Anomira] SDK disabled \u2014 missing config: ${missing}. Set SENTINEL_API_KEY and SENTINEL_APP_ID to enable monitoring.`);
313
+ this.disabled = true;
314
+ this.config = {
315
+ apiKey: "",
316
+ appId: "",
317
+ ingestUrl: DEFAULT_INGEST_URL,
318
+ geoLookupUrl: "",
319
+ maxBatchSize: DEFAULT_BATCH_SIZE,
320
+ flushIntervalMs: DEFAULT_FLUSH_MS,
321
+ maxRetries: DEFAULT_MAX_RETRIES,
322
+ debug: false,
323
+ captureConsole: false,
324
+ service: "app",
325
+ getUserId: defaultGetUserId,
326
+ getIp: defaultGetIp,
327
+ detect: { bruteForce: true, rateAbuse: true, pathTraversal: true, xss: true, scanDetection: true, geoVelocity: true }
328
+ };
329
+ this.buffer = new EventBuffer({ appId: "", apiKey: "", ingestUrl: DEFAULT_INGEST_URL, maxBatchSize: 0, flushIntervalMs: 999999999, maxRetries: 0, debug: false });
330
+ return;
331
+ }
310
332
  this.config = {
311
333
  apiKey: config.apiKey,
312
334
  appId: config.appId,
@@ -525,6 +547,7 @@ var SentinelClient = class {
525
547
  */
526
548
  /** Returns true if the IP is in the current blocked list. Synchronous — no network call. */
527
549
  isBlocked(ip) {
550
+ if (this.disabled) return false;
528
551
  return this.blockedIpCache.has(ip);
529
552
  }
530
553
  /** Evaluate firewall rules against a request. Returns the matched rule or null. Synchronous. */
@@ -532,6 +555,7 @@ var SentinelClient = class {
532
555
  return this.#matchFirewallRule(req);
533
556
  }
534
557
  track(eventName, data) {
558
+ if (this.disabled) return;
535
559
  const event = {
536
560
  name: eventName,
537
561
  ts: Date.now(),
@@ -554,6 +578,7 @@ var SentinelClient = class {
554
578
  * ```
555
579
  */
556
580
  async trackLogin(data) {
581
+ if (this.disabled) return;
557
582
  const tsMs = Date.now();
558
583
  this.track(EventName.LOGIN_SUCCESS, { ...data, meta: { ...data.meta } });
559
584
  if (!this.config.detect.geoVelocity) return;
@@ -593,6 +618,7 @@ var SentinelClient = class {
593
618
  * ```
594
619
  */
595
620
  trackPhoneAuth(data) {
621
+ if (this.disabled) return;
596
622
  this.track(EventName.PHONE_AUTH, {
597
623
  ip: data.ip,
598
624
  userId: data.userId,
@@ -609,6 +635,7 @@ var SentinelClient = class {
609
635
  * ```
610
636
  */
611
637
  log(level, message, meta) {
638
+ if (this.disabled) return;
612
639
  const { service, ...rest } = meta ?? {};
613
640
  const leaks = scanForLeaks(message);
614
641
  if (leaks.length > 0) {
@@ -630,6 +657,7 @@ var SentinelClient = class {
630
657
  * Useful before a graceful shutdown outside of the process lifecycle hooks.
631
658
  */
632
659
  async flush() {
660
+ if (this.disabled) return;
633
661
  await Promise.all([this.buffer.flush(), this.#flushLogs()]);
634
662
  }
635
663
  /**