@bbloker/sdk 0.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.
@@ -0,0 +1,422 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;// src/data/default-rules.json
2
+ var default_rules_default = {
3
+ version: 1,
4
+ updatedAt: "2026-02-06",
5
+ blockedUAs: [
6
+ "GPTBot",
7
+ "ChatGPT-User",
8
+ "OAI-SearchBot",
9
+ "CCBot",
10
+ "anthropic-ai",
11
+ "ClaudeBot",
12
+ "Claude-Web",
13
+ "Meta-ExternalAgent",
14
+ "Meta-ExternalFetcher",
15
+ "FacebookBot",
16
+ "facebookexternalhit",
17
+ "PerplexityBot",
18
+ "Bytespider",
19
+ "Google-Extended",
20
+ "Applebot-Extended",
21
+ "cohere-ai",
22
+ "Diffbot",
23
+ "ImagesiftBot",
24
+ "Omgilibot",
25
+ "Omgili",
26
+ "YouBot",
27
+ "Amazonbot",
28
+ "AI2Bot",
29
+ "Ai2Bot-Dolma",
30
+ "Scrapy",
31
+ "PetalBot",
32
+ "Semrushbot",
33
+ "AhrefsBot",
34
+ "MJ12bot",
35
+ "DotBot",
36
+ "Seekport",
37
+ "BLEXBot",
38
+ "DataForSeoBot",
39
+ "magpie-crawler",
40
+ "Timpibot",
41
+ "Velenpublicwebcrawler",
42
+ "Webzio-Extended",
43
+ "iaskspider",
44
+ "Kangaroo Bot",
45
+ "img2dataset"
46
+ ],
47
+ blockedIPs: [
48
+ "20.15.240.0/20",
49
+ "20.171.206.0/23",
50
+ "40.83.2.0/23",
51
+ "52.230.152.0/21",
52
+ "20.171.207.0/24"
53
+ ],
54
+ headerPatterns: [
55
+ {
56
+ name: "accept",
57
+ pattern: "^\\*\\/\\*$",
58
+ weight: 0.3
59
+ },
60
+ {
61
+ name: "accept-language",
62
+ pattern: "^$",
63
+ weight: 0.5
64
+ },
65
+ {
66
+ name: "accept-encoding",
67
+ pattern: "^$",
68
+ weight: 0.4
69
+ }
70
+ ],
71
+ anomalyThreshold: 0.7
72
+ };
73
+
74
+ // src/core/logger.ts
75
+ var LEVELS = {
76
+ debug: 0,
77
+ info: 1,
78
+ warn: 2,
79
+ error: 3,
80
+ silent: 4
81
+ };
82
+ var Logger = (_class = class {constructor() { _class.prototype.__init.call(this); }
83
+ __init() {this.level = LEVELS.warn}
84
+ setLevel(level) {
85
+ this.level = LEVELS[level];
86
+ }
87
+ debug(...args) {
88
+ if (this.level <= LEVELS.debug) console.debug("[bbloker]", ...args);
89
+ }
90
+ info(...args) {
91
+ if (this.level <= LEVELS.info) console.info("[bbloker]", ...args);
92
+ }
93
+ warn(...args) {
94
+ if (this.level <= LEVELS.warn) console.warn("[bbloker]", ...args);
95
+ }
96
+ error(...args) {
97
+ if (this.level <= LEVELS.error) console.error("[bbloker]", ...args);
98
+ }
99
+ }, _class);
100
+ var logger = new Logger();
101
+
102
+ // src/core/rules.ts
103
+ var RuleManager = (_class2 = class {
104
+
105
+
106
+
107
+ __init2() {this.syncTimer = null}
108
+ // Pre-compiled UA patterns for fast matching
109
+ __init3() {this.uaPatterns = []}
110
+ constructor(config) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);
111
+ this.apiUrl = config.apiUrl;
112
+ this.apiKey = config.apiKey;
113
+ this.rules = default_rules_default;
114
+ this.compilePatterns();
115
+ this.sync();
116
+ this.syncTimer = setInterval(() => this.sync(), config.syncInterval);
117
+ if (_optionalChain([this, 'access', _ => _.syncTimer, 'optionalAccess', _2 => _2.unref])) {
118
+ this.syncTimer.unref();
119
+ }
120
+ }
121
+ compilePatterns() {
122
+ this.uaPatterns = this.rules.blockedUAs.map((ua) => ua.toLowerCase());
123
+ }
124
+ get current() {
125
+ return this.rules;
126
+ }
127
+ /** Check if a User-Agent matches any blocked pattern */
128
+ isBlockedUA(ua) {
129
+ const lower = ua.toLowerCase();
130
+ return this.uaPatterns.some((pattern) => lower.includes(pattern));
131
+ }
132
+ /** Check if an IP is in any blocked CIDR range */
133
+ isBlockedIP(ip) {
134
+ return this.rules.blockedIPs.some((cidr) => ipInCidr(ip, cidr));
135
+ }
136
+ /** Calculate header anomaly score (0-1) */
137
+ headerAnomalyScore(headers) {
138
+ let totalWeight = 0;
139
+ let matchWeight = 0;
140
+ for (const pattern of this.rules.headerPatterns) {
141
+ totalWeight += pattern.weight;
142
+ const value = _nullishCoalesce(headers[pattern.name.toLowerCase()], () => ( ""));
143
+ try {
144
+ if (new RegExp(pattern.pattern).test(value)) {
145
+ matchWeight += pattern.weight;
146
+ }
147
+ } catch (e) {
148
+ }
149
+ }
150
+ return totalWeight > 0 ? matchWeight / totalWeight : 0;
151
+ }
152
+ get anomalyThreshold() {
153
+ return this.rules.anomalyThreshold;
154
+ }
155
+ async sync() {
156
+ try {
157
+ const res = await fetch(`${this.apiUrl}/v1/rules`, {
158
+ headers: { Authorization: `Bearer ${this.apiKey}` },
159
+ signal: AbortSignal.timeout(5e3)
160
+ });
161
+ if (!res.ok) {
162
+ logger.warn(`bbloker: rule sync failed: ${res.status}`);
163
+ return;
164
+ }
165
+ const data = await res.json();
166
+ if (data.version > this.rules.version) {
167
+ this.rules = data;
168
+ this.compilePatterns();
169
+ logger.info(`bbloker: rules updated to v${data.version}`);
170
+ }
171
+ } catch (e2) {
172
+ logger.debug("bbloker: rule sync error (using cached rules)");
173
+ }
174
+ }
175
+ destroy() {
176
+ if (this.syncTimer) {
177
+ clearInterval(this.syncTimer);
178
+ this.syncTimer = null;
179
+ }
180
+ }
181
+ }, _class2);
182
+ function ipToLong(ip) {
183
+ const parts = ip.split(".");
184
+ if (parts.length !== 4) return 0;
185
+ return (parseInt(parts[0], 10) << 24 | parseInt(parts[1], 10) << 16 | parseInt(parts[2], 10) << 8 | parseInt(parts[3], 10)) >>> 0;
186
+ }
187
+ function ipInCidr(ip, cidr) {
188
+ if (ip.includes(":")) return false;
189
+ const [range, bits] = cidr.split("/");
190
+ if (!range || !bits) return false;
191
+ const mask = ~(2 ** (32 - parseInt(bits, 10)) - 1) >>> 0;
192
+ const ipLong = ipToLong(ip);
193
+ const rangeLong = ipToLong(range);
194
+ return (ipLong & mask) === (rangeLong & mask);
195
+ }
196
+
197
+ // src/core/rate-limiter.ts
198
+ var RateLimiter = (_class3 = class {
199
+ __init4() {this.windows = /* @__PURE__ */ new Map()}
200
+
201
+
202
+ __init5() {this.cleanupTimer = null}
203
+ constructor(maxRequests, windowMs) {;_class3.prototype.__init4.call(this);_class3.prototype.__init5.call(this);
204
+ this.maxRequests = maxRequests;
205
+ this.windowMs = windowMs;
206
+ this.cleanupTimer = setInterval(() => this.cleanup(), 6e4);
207
+ if (_optionalChain([this, 'access', _3 => _3.cleanupTimer, 'optionalAccess', _4 => _4.unref])) {
208
+ this.cleanupTimer.unref();
209
+ }
210
+ }
211
+ /** Returns true if the IP has exceeded the rate limit */
212
+ isExceeded(ip) {
213
+ const now = Date.now();
214
+ const window = this.windows.get(ip);
215
+ if (!window || now > window.resetAt) {
216
+ this.windows.set(ip, { count: 1, resetAt: now + this.windowMs });
217
+ return false;
218
+ }
219
+ window.count++;
220
+ return window.count > this.maxRequests;
221
+ }
222
+ cleanup() {
223
+ const now = Date.now();
224
+ for (const [ip, window] of this.windows) {
225
+ if (now > window.resetAt) {
226
+ this.windows.delete(ip);
227
+ }
228
+ }
229
+ }
230
+ destroy() {
231
+ if (this.cleanupTimer) {
232
+ clearInterval(this.cleanupTimer);
233
+ this.cleanupTimer = null;
234
+ }
235
+ this.windows.clear();
236
+ }
237
+ }, _class3);
238
+
239
+ // src/core/telemetry.ts
240
+ var Telemetry = (_class4 = class {
241
+ __init6() {this.buffer = []}
242
+
243
+
244
+
245
+ __init7() {this.flushTimer = null}
246
+
247
+ constructor(config) {;_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);
248
+ this.apiUrl = config.apiUrl;
249
+ this.apiKey = config.apiKey;
250
+ this.maxBuffer = config.bufferSize;
251
+ this.enabled = config.enabled;
252
+ if (!this.enabled) return;
253
+ this.flushTimer = setInterval(() => this.flush(), config.flushInterval);
254
+ if (_optionalChain([this, 'access', _5 => _5.flushTimer, 'optionalAccess', _6 => _6.unref])) {
255
+ this.flushTimer.unref();
256
+ }
257
+ }
258
+ push(fp) {
259
+ if (!this.enabled) return;
260
+ this.buffer.push(fp);
261
+ if (this.buffer.length >= this.maxBuffer) {
262
+ this.flush();
263
+ }
264
+ }
265
+ async flush() {
266
+ if (this.buffer.length === 0) return;
267
+ const batch = this.buffer.splice(0);
268
+ try {
269
+ const res = await fetch(`${this.apiUrl}/v1/fingerprints`, {
270
+ method: "POST",
271
+ headers: {
272
+ "Authorization": `Bearer ${this.apiKey}`,
273
+ "Content-Type": "application/json"
274
+ },
275
+ body: JSON.stringify({ events: batch }),
276
+ signal: AbortSignal.timeout(5e3)
277
+ });
278
+ if (!res.ok) {
279
+ logger.warn(`bbloker: telemetry flush failed: ${res.status}`);
280
+ } else {
281
+ logger.debug(`bbloker: flushed ${batch.length} fingerprints`);
282
+ }
283
+ } catch (e3) {
284
+ logger.debug(`bbloker: telemetry flush error (silent)`);
285
+ }
286
+ }
287
+ destroy() {
288
+ if (this.flushTimer) {
289
+ clearInterval(this.flushTimer);
290
+ this.flushTimer = null;
291
+ }
292
+ this.buffer = [];
293
+ }
294
+ }, _class4);
295
+
296
+ // src/core/engine.ts
297
+ var DEFAULTS = {
298
+ apiUrl: "https://api.bbloker.com",
299
+ syncInterval: 3e5,
300
+ flushInterval: 1e4,
301
+ bufferSize: 100,
302
+ telemetry: true,
303
+ rateLimit: 60,
304
+ rateLimitWindow: 6e4,
305
+ logLevel: "warn"
306
+ };
307
+ var Bbloker = class {
308
+
309
+
310
+
311
+
312
+ constructor(config) {
313
+ if (!config.apiKey) {
314
+ throw new Error("bbloker: apiKey is required");
315
+ }
316
+ const apiUrl = _nullishCoalesce(config.apiUrl, () => ( DEFAULTS.apiUrl));
317
+ const syncInterval = _nullishCoalesce(config.syncInterval, () => ( DEFAULTS.syncInterval));
318
+ const flushInterval = _nullishCoalesce(config.flushInterval, () => ( DEFAULTS.flushInterval));
319
+ const bufferSize = _nullishCoalesce(config.bufferSize, () => ( DEFAULTS.bufferSize));
320
+ const telemetryEnabled = _nullishCoalesce(config.telemetry, () => ( DEFAULTS.telemetry));
321
+ const rateLimit = _nullishCoalesce(config.rateLimit, () => ( DEFAULTS.rateLimit));
322
+ const rateLimitWindow = _nullishCoalesce(config.rateLimitWindow, () => ( DEFAULTS.rateLimitWindow));
323
+ const logLevel = _nullishCoalesce(config.logLevel, () => ( DEFAULTS.logLevel));
324
+ this.config = {
325
+ apiKey: config.apiKey,
326
+ apiUrl,
327
+ rateLimit,
328
+ rateLimitWindow,
329
+ logLevel,
330
+ onBlock: config.onBlock
331
+ };
332
+ logger.setLevel(logLevel);
333
+ this.rules = new RuleManager({
334
+ apiUrl,
335
+ apiKey: config.apiKey,
336
+ syncInterval
337
+ });
338
+ this.rateLimiter = new RateLimiter(rateLimit, rateLimitWindow);
339
+ this.telemetry = new Telemetry({
340
+ apiUrl,
341
+ apiKey: config.apiKey,
342
+ flushInterval,
343
+ bufferSize,
344
+ enabled: telemetryEnabled
345
+ });
346
+ logger.info("bbloker: initialized");
347
+ }
348
+ /**
349
+ * Analyze a normalized request and return a block/allow decision.
350
+ * This is the core method — adapters call this.
351
+ */
352
+ analyze(req) {
353
+ if (req.userAgent && this.rules.isBlockedUA(req.userAgent)) {
354
+ const decision2 = {
355
+ action: "block",
356
+ reason: "known_bot_ua",
357
+ confidence: 0.95
358
+ };
359
+ this.report(req, decision2);
360
+ return decision2;
361
+ }
362
+ if (req.ip && this.rules.isBlockedIP(req.ip)) {
363
+ const decision2 = {
364
+ action: "block",
365
+ reason: "known_bot_ip",
366
+ confidence: 0.9
367
+ };
368
+ this.report(req, decision2);
369
+ return decision2;
370
+ }
371
+ if (req.ip && this.rateLimiter.isExceeded(req.ip)) {
372
+ const decision2 = {
373
+ action: "block",
374
+ reason: "rate_limit",
375
+ confidence: 0.7
376
+ };
377
+ this.report(req, decision2);
378
+ return decision2;
379
+ }
380
+ const anomalyScore = this.rules.headerAnomalyScore(req.headers);
381
+ if (anomalyScore > this.rules.anomalyThreshold) {
382
+ const decision2 = {
383
+ action: "block",
384
+ reason: "header_anomaly",
385
+ confidence: anomalyScore
386
+ };
387
+ this.report(req, decision2);
388
+ return decision2;
389
+ }
390
+ const decision = { action: "allow" };
391
+ this.report(req, decision);
392
+ return decision;
393
+ }
394
+ report(req, decision) {
395
+ const fp = {
396
+ ip: req.ip,
397
+ userAgent: req.userAgent,
398
+ headerOrder: req.headerNames,
399
+ headers: req.headers,
400
+ path: req.path,
401
+ method: req.method,
402
+ ts: Date.now()
403
+ };
404
+ this.telemetry.push(fp);
405
+ if (decision.action === "block") {
406
+ logger.debug(
407
+ `bbloker: blocked ${req.ip} [${decision.reason}] UA="${req.userAgent.slice(0, 80)}"`
408
+ );
409
+ }
410
+ }
411
+ /** Clean up timers. Call when shutting down. */
412
+ destroy() {
413
+ this.rules.destroy();
414
+ this.rateLimiter.destroy();
415
+ this.telemetry.destroy();
416
+ logger.info("bbloker: destroyed");
417
+ }
418
+ };
419
+
420
+
421
+
422
+ exports.Bbloker = Bbloker;
package/dist/index.cjs ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkKT5GDIXHcjs = require('./chunk-KT5GDIXH.cjs');
4
+
5
+
6
+ exports.Bbloker = _chunkKT5GDIXHcjs.Bbloker;
@@ -0,0 +1,94 @@
1
+ interface BblokerConfig {
2
+ /** API key from bbloker dashboard (bb-sk-xxx) */
3
+ apiKey: string;
4
+ /** API endpoint. Default: https://api.bbloker.com */
5
+ apiUrl?: string;
6
+ /** Rule sync interval in ms. Default: 300_000 (5 min) */
7
+ syncInterval?: number;
8
+ /** Telemetry flush interval in ms. Default: 10_000 (10s) */
9
+ flushInterval?: number;
10
+ /** Max fingerprints to buffer before force flush. Default: 100 */
11
+ bufferSize?: number;
12
+ /** Enable telemetry reporting. Default: true */
13
+ telemetry?: boolean;
14
+ /** Rate limit: max requests per IP per window. Default: 60 */
15
+ rateLimit?: number;
16
+ /** Rate limit window in ms. Default: 60_000 (1 min) */
17
+ rateLimitWindow?: number;
18
+ /** Custom action on block. Default: return 403 */
19
+ onBlock?: (ctx: BlockContext) => void | Response | Promise<void | Response>;
20
+ /** Log level. Default: 'warn' */
21
+ logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent';
22
+ }
23
+ interface Fingerprint {
24
+ /** Client IP */
25
+ ip: string;
26
+ /** Raw User-Agent */
27
+ userAgent: string;
28
+ /** Ordered list of header names */
29
+ headerOrder: string[];
30
+ /** Key headers for anomaly detection */
31
+ headers: Record<string, string>;
32
+ /** Request path */
33
+ path: string;
34
+ /** HTTP method */
35
+ method: string;
36
+ /** Timestamp */
37
+ ts: number;
38
+ }
39
+ interface Decision {
40
+ action: 'allow' | 'block';
41
+ reason?: string;
42
+ /** Confidence 0-1 */
43
+ confidence?: number;
44
+ }
45
+ interface BlockContext {
46
+ fingerprint: Fingerprint;
47
+ decision: Decision;
48
+ }
49
+ interface RuleSet {
50
+ version: number;
51
+ updatedAt: string;
52
+ /** User-Agent substrings to block */
53
+ blockedUAs: string[];
54
+ /** CIDR ranges to block */
55
+ blockedIPs: string[];
56
+ /** Known bot header patterns */
57
+ headerPatterns: HeaderPattern[];
58
+ /** Header anomaly threshold (0-1). Above = block */
59
+ anomalyThreshold: number;
60
+ }
61
+ interface HeaderPattern {
62
+ /** Header name to check */
63
+ name: string;
64
+ /** Regex pattern that indicates bot */
65
+ pattern: string;
66
+ /** Weight for anomaly scoring */
67
+ weight: number;
68
+ }
69
+ interface NormalizedRequest {
70
+ ip: string;
71
+ userAgent: string;
72
+ headers: Record<string, string>;
73
+ headerNames: string[];
74
+ path: string;
75
+ method: string;
76
+ }
77
+
78
+ declare class Bbloker {
79
+ private rules;
80
+ private rateLimiter;
81
+ private telemetry;
82
+ private config;
83
+ constructor(config: BblokerConfig);
84
+ /**
85
+ * Analyze a normalized request and return a block/allow decision.
86
+ * This is the core method — adapters call this.
87
+ */
88
+ analyze(req: NormalizedRequest): Decision;
89
+ private report;
90
+ /** Clean up timers. Call when shutting down. */
91
+ destroy(): void;
92
+ }
93
+
94
+ export { Bbloker, type BblokerConfig, type BlockContext, type Decision, type Fingerprint, type NormalizedRequest, type RuleSet };
@@ -0,0 +1,94 @@
1
+ interface BblokerConfig {
2
+ /** API key from bbloker dashboard (bb-sk-xxx) */
3
+ apiKey: string;
4
+ /** API endpoint. Default: https://api.bbloker.com */
5
+ apiUrl?: string;
6
+ /** Rule sync interval in ms. Default: 300_000 (5 min) */
7
+ syncInterval?: number;
8
+ /** Telemetry flush interval in ms. Default: 10_000 (10s) */
9
+ flushInterval?: number;
10
+ /** Max fingerprints to buffer before force flush. Default: 100 */
11
+ bufferSize?: number;
12
+ /** Enable telemetry reporting. Default: true */
13
+ telemetry?: boolean;
14
+ /** Rate limit: max requests per IP per window. Default: 60 */
15
+ rateLimit?: number;
16
+ /** Rate limit window in ms. Default: 60_000 (1 min) */
17
+ rateLimitWindow?: number;
18
+ /** Custom action on block. Default: return 403 */
19
+ onBlock?: (ctx: BlockContext) => void | Response | Promise<void | Response>;
20
+ /** Log level. Default: 'warn' */
21
+ logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent';
22
+ }
23
+ interface Fingerprint {
24
+ /** Client IP */
25
+ ip: string;
26
+ /** Raw User-Agent */
27
+ userAgent: string;
28
+ /** Ordered list of header names */
29
+ headerOrder: string[];
30
+ /** Key headers for anomaly detection */
31
+ headers: Record<string, string>;
32
+ /** Request path */
33
+ path: string;
34
+ /** HTTP method */
35
+ method: string;
36
+ /** Timestamp */
37
+ ts: number;
38
+ }
39
+ interface Decision {
40
+ action: 'allow' | 'block';
41
+ reason?: string;
42
+ /** Confidence 0-1 */
43
+ confidence?: number;
44
+ }
45
+ interface BlockContext {
46
+ fingerprint: Fingerprint;
47
+ decision: Decision;
48
+ }
49
+ interface RuleSet {
50
+ version: number;
51
+ updatedAt: string;
52
+ /** User-Agent substrings to block */
53
+ blockedUAs: string[];
54
+ /** CIDR ranges to block */
55
+ blockedIPs: string[];
56
+ /** Known bot header patterns */
57
+ headerPatterns: HeaderPattern[];
58
+ /** Header anomaly threshold (0-1). Above = block */
59
+ anomalyThreshold: number;
60
+ }
61
+ interface HeaderPattern {
62
+ /** Header name to check */
63
+ name: string;
64
+ /** Regex pattern that indicates bot */
65
+ pattern: string;
66
+ /** Weight for anomaly scoring */
67
+ weight: number;
68
+ }
69
+ interface NormalizedRequest {
70
+ ip: string;
71
+ userAgent: string;
72
+ headers: Record<string, string>;
73
+ headerNames: string[];
74
+ path: string;
75
+ method: string;
76
+ }
77
+
78
+ declare class Bbloker {
79
+ private rules;
80
+ private rateLimiter;
81
+ private telemetry;
82
+ private config;
83
+ constructor(config: BblokerConfig);
84
+ /**
85
+ * Analyze a normalized request and return a block/allow decision.
86
+ * This is the core method — adapters call this.
87
+ */
88
+ analyze(req: NormalizedRequest): Decision;
89
+ private report;
90
+ /** Clean up timers. Call when shutting down. */
91
+ destroy(): void;
92
+ }
93
+
94
+ export { Bbloker, type BblokerConfig, type BlockContext, type Decision, type Fingerprint, type NormalizedRequest, type RuleSet };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ Bbloker
3
+ } from "./chunk-6P6B2RO7.js";
4
+ export {
5
+ Bbloker
6
+ };