@rester159/blacktip 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.
- package/AGENTS.md +249 -0
- package/LICENSE +38 -0
- package/README.md +234 -0
- package/dist/behavioral/calibration.d.ts +145 -0
- package/dist/behavioral/calibration.d.ts.map +1 -0
- package/dist/behavioral/calibration.js +242 -0
- package/dist/behavioral/calibration.js.map +1 -0
- package/dist/behavioral-engine.d.ts +156 -0
- package/dist/behavioral-engine.d.ts.map +1 -0
- package/dist/behavioral-engine.js +521 -0
- package/dist/behavioral-engine.js.map +1 -0
- package/dist/blacktip.d.ts +289 -0
- package/dist/blacktip.d.ts.map +1 -0
- package/dist/blacktip.js +1574 -0
- package/dist/blacktip.js.map +1 -0
- package/dist/browser-core.d.ts +47 -0
- package/dist/browser-core.d.ts.map +1 -0
- package/dist/browser-core.js +375 -0
- package/dist/browser-core.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +226 -0
- package/dist/cli.js.map +1 -0
- package/dist/element-finder.d.ts +42 -0
- package/dist/element-finder.d.ts.map +1 -0
- package/dist/element-finder.js +240 -0
- package/dist/element-finder.js.map +1 -0
- package/dist/evasion.d.ts +39 -0
- package/dist/evasion.d.ts.map +1 -0
- package/dist/evasion.js +488 -0
- package/dist/evasion.js.map +1 -0
- package/dist/fingerprint.d.ts +19 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +171 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +13 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +42 -0
- package/dist/logging.js.map +1 -0
- package/dist/observability.d.ts +69 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +189 -0
- package/dist/observability.js.map +1 -0
- package/dist/proxy-pool.d.ts +101 -0
- package/dist/proxy-pool.d.ts.map +1 -0
- package/dist/proxy-pool.js +156 -0
- package/dist/proxy-pool.js.map +1 -0
- package/dist/snapshot.d.ts +59 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +91 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/examples/01-basic-navigate.ts +40 -0
- package/examples/02-login-with-mfa.ts +68 -0
- package/examples/03-agent-serve-mode.md +98 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { BlackTip, BlackTipFrame } from './blacktip.js';
|
|
2
|
+
export { BehavioralEngine, HUMAN_PROFILE, SCRAPER_PROFILE } from './behavioral-engine.js';
|
|
3
|
+
export type { MouseStep, KeystrokeStep, ScrollStep, ActionImportance } from './behavioral-engine.js';
|
|
4
|
+
export { DeviceProfileManager } from './fingerprint.js';
|
|
5
|
+
export { generateEvasionScripts } from './evasion.js';
|
|
6
|
+
export { ElementFinder } from './element-finder.js';
|
|
7
|
+
export { Logger } from './logging.js';
|
|
8
|
+
export { BrowserCore } from './browser-core.js';
|
|
9
|
+
export { fitDistribution, fitFittsLaw, fitMouseDynamics, fitTypingDynamics, fitFromSamples, deriveProfileConfig, } from './behavioral/calibration.js';
|
|
10
|
+
export type { MouseSample, MouseMovement, KeystrokeSample, TypingSession, DistributionFit, MouseFit, TypingFit, CalibratedProfile, } from './behavioral/calibration.js';
|
|
11
|
+
export { ProxyPool, ProxyProviders, proxyToUrl } from './proxy-pool.js';
|
|
12
|
+
export type { ProxyDescriptor, ProxyProtocol, PoolOptions } from './proxy-pool.js';
|
|
13
|
+
export { SnapshotManager } from './snapshot.js';
|
|
14
|
+
export type { SessionSnapshot } from './snapshot.js';
|
|
15
|
+
export { attachObservability, JsonlFileExporter, ConsoleExporter, newTraceId, } from './observability.js';
|
|
16
|
+
export type { StructuredEvent, EventExporter } from './observability.js';
|
|
17
|
+
export type { BlackTipConfig, ProfileConfig, DeviceProfile, PluginData, ActionResult, NavigateResult, ScreenshotResult, WaitResult, ActionEvent, BehavioralMetadata, ErrorEvent, RetryEvent, TabChangeEvent, LogEntry, TabInfo, FrameInfo, LogLevel, ClickOptions, TypeOptions, ScrollOptions, HoverOptions, SelectOptions, PressKeyOptions, UploadFileOptions, NavigateOptions, ScreenshotOptions, WaitForOptions, WaitForNavigationOptions, ExtractTextOptions, PageContentOptions, ErrorCode, RetryStrategy, Point, BoundingBox, } from './types.js';
|
|
18
|
+
export { ErrorCodes } from './types.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC1F,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,WAAW,EACX,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACR,SAAS,EACT,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEzE,YAAY,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,EACT,aAAa,EACb,KAAK,EACL,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { BlackTip, BlackTipFrame } from './blacktip.js';
|
|
2
|
+
export { BehavioralEngine, HUMAN_PROFILE, SCRAPER_PROFILE } from './behavioral-engine.js';
|
|
3
|
+
export { DeviceProfileManager } from './fingerprint.js';
|
|
4
|
+
export { generateEvasionScripts } from './evasion.js';
|
|
5
|
+
export { ElementFinder } from './element-finder.js';
|
|
6
|
+
export { Logger } from './logging.js';
|
|
7
|
+
export { BrowserCore } from './browser-core.js';
|
|
8
|
+
// v2 additions — Tier 2 calibration, Tier 3 infrastructure, observability.
|
|
9
|
+
export { fitDistribution, fitFittsLaw, fitMouseDynamics, fitTypingDynamics, fitFromSamples, deriveProfileConfig, } from './behavioral/calibration.js';
|
|
10
|
+
export { ProxyPool, ProxyProviders, proxyToUrl } from './proxy-pool.js';
|
|
11
|
+
export { SnapshotManager } from './snapshot.js';
|
|
12
|
+
export { attachObservability, JsonlFileExporter, ConsoleExporter, newTraceId, } from './observability.js';
|
|
13
|
+
export { ErrorCodes } from './types.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,2EAA2E;AAC3E,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAYrC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,oBAAoB,CAAC;AAwC5B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { LogLevel } from './types.js';
|
|
3
|
+
export declare class Logger extends EventEmitter {
|
|
4
|
+
private level;
|
|
5
|
+
constructor(level?: LogLevel);
|
|
6
|
+
setLevel(level: LogLevel): void;
|
|
7
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
8
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
9
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
10
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
11
|
+
private log;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,YAAY,CAAC;AASrD,qBAAa,MAAO,SAAQ,YAAY;IACtC,OAAO,CAAC,KAAK,CAAW;gBAEZ,KAAK,GAAE,QAAiB;IAKpC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D,OAAO,CAAC,GAAG;CAcZ"}
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
const LOG_LEVELS = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
7
|
+
};
|
|
8
|
+
export class Logger extends EventEmitter {
|
|
9
|
+
level;
|
|
10
|
+
constructor(level = 'info') {
|
|
11
|
+
super();
|
|
12
|
+
this.level = level;
|
|
13
|
+
}
|
|
14
|
+
setLevel(level) {
|
|
15
|
+
this.level = level;
|
|
16
|
+
}
|
|
17
|
+
debug(message, data) {
|
|
18
|
+
this.log('debug', message, data);
|
|
19
|
+
}
|
|
20
|
+
info(message, data) {
|
|
21
|
+
this.log('info', message, data);
|
|
22
|
+
}
|
|
23
|
+
warn(message, data) {
|
|
24
|
+
this.log('warn', message, data);
|
|
25
|
+
}
|
|
26
|
+
error(message, data) {
|
|
27
|
+
this.log('error', message, data);
|
|
28
|
+
}
|
|
29
|
+
log(level, message, data) {
|
|
30
|
+
if (LOG_LEVELS[level] < LOG_LEVELS[this.level]) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const entry = {
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
level,
|
|
36
|
+
message,
|
|
37
|
+
...(data !== undefined && { data }),
|
|
38
|
+
};
|
|
39
|
+
this.emit('log', entry);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,OAAO,MAAO,SAAQ,YAAY;IAC9B,KAAK,CAAW;IAExB,YAAY,QAAkB,MAAM;QAClC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,QAAQ,CAAC,KAAe;QACtB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC1E,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAa;YACtB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,OAAO;YACP,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;SACpC,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;CACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability — structured event schema and reference exporters.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* 1. Every BlackTip event (action, retry, error, tabChange, log) must
|
|
6
|
+
* be serializable into a shape that maps 1:1 onto OpenTelemetry
|
|
7
|
+
* span attributes, without requiring the OTel SDK as a dependency.
|
|
8
|
+
* 2. Callers who want OTel can bridge these events into their own
|
|
9
|
+
* SDK in ~20 lines.
|
|
10
|
+
* 3. Callers who just want tail-a-file observability can use the
|
|
11
|
+
* built-in JSONL file sink.
|
|
12
|
+
*
|
|
13
|
+
* We do NOT ship the OTel SDK because pulling it in would roughly
|
|
14
|
+
* double the install footprint and most users of BlackTip don't need
|
|
15
|
+
* it. Instead we define the event schema so bridging is trivial.
|
|
16
|
+
*/
|
|
17
|
+
import type { BlackTip } from './blacktip.js';
|
|
18
|
+
/**
|
|
19
|
+
* Structured event — the canonical shape every BlackTip event reduces
|
|
20
|
+
* to when exported. Mirrors OpenTelemetry's span data model loosely:
|
|
21
|
+
*
|
|
22
|
+
* - timestamp: ISO-8601, the event time
|
|
23
|
+
* - name: short event name, e.g. "action.click", "retry.strategy"
|
|
24
|
+
* - severity: info/warn/error for filtering
|
|
25
|
+
* - attributes: flat key-value pairs (values are strings, numbers,
|
|
26
|
+
* or booleans — no nested objects, matching OTel's attribute model)
|
|
27
|
+
* - traceId: stable per BlackTip session (useful for grouping events
|
|
28
|
+
* belonging to one agent flow)
|
|
29
|
+
* - spanId: stable per action (groups pre-action, retries, post-action)
|
|
30
|
+
*/
|
|
31
|
+
export interface StructuredEvent {
|
|
32
|
+
timestamp: string;
|
|
33
|
+
name: string;
|
|
34
|
+
severity: 'debug' | 'info' | 'warn' | 'error';
|
|
35
|
+
attributes: Record<string, string | number | boolean>;
|
|
36
|
+
traceId: string;
|
|
37
|
+
spanId?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 16-char hex trace ID (same format as OpenTelemetry trace IDs, half
|
|
41
|
+
* the width — enough entropy for a session without a crypto dep).
|
|
42
|
+
*/
|
|
43
|
+
export declare function newTraceId(): string;
|
|
44
|
+
export interface EventExporter {
|
|
45
|
+
export(event: StructuredEvent): void;
|
|
46
|
+
}
|
|
47
|
+
export declare class JsonlFileExporter implements EventExporter {
|
|
48
|
+
private path;
|
|
49
|
+
constructor(path: string);
|
|
50
|
+
export(event: StructuredEvent): void;
|
|
51
|
+
}
|
|
52
|
+
export declare class ConsoleExporter implements EventExporter {
|
|
53
|
+
export(event: StructuredEvent): void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wire a BlackTip instance up to one or more StructuredEvent exporters.
|
|
57
|
+
* Returns an unsubscribe function that removes all listeners added by
|
|
58
|
+
* this call.
|
|
59
|
+
*
|
|
60
|
+
* Usage:
|
|
61
|
+
* const stop = attachObservability(bt, [
|
|
62
|
+
* new JsonlFileExporter('./events.jsonl'),
|
|
63
|
+
* new ConsoleExporter(),
|
|
64
|
+
* ]);
|
|
65
|
+
* // ... drive BlackTip ...
|
|
66
|
+
* stop();
|
|
67
|
+
*/
|
|
68
|
+
export declare function attachObservability(bt: BlackTip, exporters: EventExporter[]): () => void;
|
|
69
|
+
//# sourceMappingURL=observability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAK9C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAuGD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CACtC;AAID,qBAAa,iBAAkB,YAAW,aAAa;IACzC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,MAAM;IAIhC,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;CAGrC;AAID,qBAAa,eAAgB,YAAW,aAAa;IACnD,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;CAIrC;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,IAAI,CAiCxF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability — structured event schema and reference exporters.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* 1. Every BlackTip event (action, retry, error, tabChange, log) must
|
|
6
|
+
* be serializable into a shape that maps 1:1 onto OpenTelemetry
|
|
7
|
+
* span attributes, without requiring the OTel SDK as a dependency.
|
|
8
|
+
* 2. Callers who want OTel can bridge these events into their own
|
|
9
|
+
* SDK in ~20 lines.
|
|
10
|
+
* 3. Callers who just want tail-a-file observability can use the
|
|
11
|
+
* built-in JSONL file sink.
|
|
12
|
+
*
|
|
13
|
+
* We do NOT ship the OTel SDK because pulling it in would roughly
|
|
14
|
+
* double the install footprint and most users of BlackTip don't need
|
|
15
|
+
* it. Instead we define the event schema so bridging is trivial.
|
|
16
|
+
*/
|
|
17
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
// ── Trace ID generator ──
|
|
20
|
+
/**
|
|
21
|
+
* 16-char hex trace ID (same format as OpenTelemetry trace IDs, half
|
|
22
|
+
* the width — enough entropy for a session without a crypto dep).
|
|
23
|
+
*/
|
|
24
|
+
export function newTraceId() {
|
|
25
|
+
let s = '';
|
|
26
|
+
for (let i = 0; i < 16; i++) {
|
|
27
|
+
s += Math.floor(Math.random() * 16).toString(16);
|
|
28
|
+
}
|
|
29
|
+
return s;
|
|
30
|
+
}
|
|
31
|
+
// ── Normalizers: BlackTip event → StructuredEvent ──
|
|
32
|
+
function actionToStructured(ev, traceId) {
|
|
33
|
+
return {
|
|
34
|
+
timestamp: ev.timestamp,
|
|
35
|
+
name: `action.${ev.action}`,
|
|
36
|
+
severity: ev.outcome === 'success' ? 'info' : 'error',
|
|
37
|
+
traceId,
|
|
38
|
+
attributes: {
|
|
39
|
+
'bt.action': ev.action,
|
|
40
|
+
'bt.target': ev.target,
|
|
41
|
+
'bt.outcome': ev.outcome,
|
|
42
|
+
'bt.duration_ms': ev.duration,
|
|
43
|
+
'bt.retries': ev.retries,
|
|
44
|
+
...(ev.error ? { 'bt.error': ev.error } : {}),
|
|
45
|
+
...(ev.value ? { 'bt.value_length': ev.value.length } : {}),
|
|
46
|
+
...(ev.behavioral?.mousePathLength ? { 'bt.behavioral.path_length_px': ev.behavioral.mousePathLength } : {}),
|
|
47
|
+
...(ev.behavioral?.mouseMoveDuration ? { 'bt.behavioral.move_duration_ms': ev.behavioral.mouseMoveDuration } : {}),
|
|
48
|
+
...(ev.behavioral?.preActionPause ? { 'bt.behavioral.pre_pause_ms': ev.behavioral.preActionPause } : {}),
|
|
49
|
+
...(ev.behavioral?.postActionPause ? { 'bt.behavioral.post_pause_ms': ev.behavioral.postActionPause } : {}),
|
|
50
|
+
...(ev.behavioral?.clickDwell ? { 'bt.behavioral.click_dwell_ms': ev.behavioral.clickDwell } : {}),
|
|
51
|
+
...(ev.behavioral?.typingDuration ? { 'bt.behavioral.typing_duration_ms': ev.behavioral.typingDuration } : {}),
|
|
52
|
+
...(ev.tabIndex !== undefined ? { 'bt.tab_index': ev.tabIndex } : {}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function retryToStructured(ev, traceId) {
|
|
57
|
+
return {
|
|
58
|
+
timestamp: ev.timestamp,
|
|
59
|
+
name: 'retry.strategy',
|
|
60
|
+
severity: 'warn',
|
|
61
|
+
traceId,
|
|
62
|
+
attributes: {
|
|
63
|
+
'bt.action': ev.action,
|
|
64
|
+
'bt.target': ev.target,
|
|
65
|
+
'bt.retry.attempt': ev.attempt,
|
|
66
|
+
'bt.retry.max_attempts': ev.maxAttempts,
|
|
67
|
+
'bt.retry.strategy': ev.strategy,
|
|
68
|
+
'bt.error': ev.error,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function errorToStructured(ev, traceId) {
|
|
73
|
+
return {
|
|
74
|
+
timestamp: ev.timestamp,
|
|
75
|
+
name: 'error.action',
|
|
76
|
+
severity: 'error',
|
|
77
|
+
traceId,
|
|
78
|
+
attributes: {
|
|
79
|
+
'bt.error.code': ev.code,
|
|
80
|
+
'bt.error.message': ev.message,
|
|
81
|
+
'bt.error.url': ev.url,
|
|
82
|
+
'bt.action': ev.action,
|
|
83
|
+
'bt.attempts': ev.attempts,
|
|
84
|
+
...(ev.screenshot ? { 'bt.error.has_screenshot': true, 'bt.error.screenshot_bytes': ev.screenshot.length } : {}),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function tabChangeToStructured(ev, traceId) {
|
|
89
|
+
return {
|
|
90
|
+
timestamp: ev.timestamp,
|
|
91
|
+
name: `tab.${ev.action}`,
|
|
92
|
+
severity: 'info',
|
|
93
|
+
traceId,
|
|
94
|
+
attributes: {
|
|
95
|
+
'bt.tab.index': ev.tabIndex,
|
|
96
|
+
'bt.tab.url': ev.url,
|
|
97
|
+
'bt.tab.action': ev.action,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function logToStructured(entry, traceId) {
|
|
102
|
+
const attrs = {
|
|
103
|
+
'bt.log.message': entry.message,
|
|
104
|
+
};
|
|
105
|
+
// Flatten data shallow (OTel attrs are flat).
|
|
106
|
+
if (entry.data) {
|
|
107
|
+
for (const [k, v] of Object.entries(entry.data)) {
|
|
108
|
+
if (v === null || v === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
|
111
|
+
attrs[`bt.log.${k}`] = v;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
attrs[`bt.log.${k}`] = JSON.stringify(v).slice(0, 200);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
timestamp: entry.timestamp,
|
|
120
|
+
name: 'log',
|
|
121
|
+
severity: entry.level,
|
|
122
|
+
traceId,
|
|
123
|
+
attributes: attrs,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// ── File sink (JSONL) ──
|
|
127
|
+
export class JsonlFileExporter {
|
|
128
|
+
path;
|
|
129
|
+
constructor(path) {
|
|
130
|
+
this.path = path;
|
|
131
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
export(event) {
|
|
134
|
+
appendFileSync(this.path, JSON.stringify(event) + '\n');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ── Console exporter (for development) ──
|
|
138
|
+
export class ConsoleExporter {
|
|
139
|
+
export(event) {
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.log(`[${event.severity}] ${event.name}`, event.attributes);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ── Attach exporters to a BlackTip instance ──
|
|
145
|
+
/**
|
|
146
|
+
* Wire a BlackTip instance up to one or more StructuredEvent exporters.
|
|
147
|
+
* Returns an unsubscribe function that removes all listeners added by
|
|
148
|
+
* this call.
|
|
149
|
+
*
|
|
150
|
+
* Usage:
|
|
151
|
+
* const stop = attachObservability(bt, [
|
|
152
|
+
* new JsonlFileExporter('./events.jsonl'),
|
|
153
|
+
* new ConsoleExporter(),
|
|
154
|
+
* ]);
|
|
155
|
+
* // ... drive BlackTip ...
|
|
156
|
+
* stop();
|
|
157
|
+
*/
|
|
158
|
+
export function attachObservability(bt, exporters) {
|
|
159
|
+
const traceId = newTraceId();
|
|
160
|
+
const fanOut = (event) => {
|
|
161
|
+
for (const exporter of exporters) {
|
|
162
|
+
try {
|
|
163
|
+
exporter.export(event);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Swallow exporter errors — observability must never crash the
|
|
167
|
+
// primary flow.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const onAction = (ev) => fanOut(actionToStructured(ev, traceId));
|
|
172
|
+
const onRetry = (ev) => fanOut(retryToStructured(ev, traceId));
|
|
173
|
+
const onError = (ev) => fanOut(errorToStructured(ev, traceId));
|
|
174
|
+
const onTab = (ev) => fanOut(tabChangeToStructured(ev, traceId));
|
|
175
|
+
const onLog = (entry) => fanOut(logToStructured(entry, traceId));
|
|
176
|
+
bt.on('action', onAction);
|
|
177
|
+
bt.on('retry', onRetry);
|
|
178
|
+
bt.on('error', onError);
|
|
179
|
+
bt.on('tabChange', onTab);
|
|
180
|
+
bt.on('log', onLog);
|
|
181
|
+
return () => {
|
|
182
|
+
bt.off('action', onAction);
|
|
183
|
+
bt.off('retry', onRetry);
|
|
184
|
+
bt.off('error', onError);
|
|
185
|
+
bt.off('tabChange', onTab);
|
|
186
|
+
bt.off('log', onLog);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=observability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.js","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy pool abstraction.
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency reference implementation. The caller supplies proxy
|
|
5
|
+
* credentials; this module handles:
|
|
6
|
+
*
|
|
7
|
+
* - Round-robin or domain-sticky selection.
|
|
8
|
+
* - In-memory reputation tracking (which proxies got banned on which
|
|
9
|
+
* domains, decay window, avoid-list).
|
|
10
|
+
* - URL formatting for the common residential providers (BrightData,
|
|
11
|
+
* Oxylabs, Smartproxy, NetNut) — caller passes the provider name
|
|
12
|
+
* and credentials, we build the right URL string.
|
|
13
|
+
*
|
|
14
|
+
* This is a scaffold — the actual integration with BlackTip's
|
|
15
|
+
* `BlackTipConfig.proxy` happens when `selectForDomain` is called and
|
|
16
|
+
* the result is set on the config before `bt.launch()`.
|
|
17
|
+
*
|
|
18
|
+
* Residential proxy bandwidth costs money. For the zero-budget path,
|
|
19
|
+
* callers can instantiate an empty pool and use BlackTip without any
|
|
20
|
+
* proxy (the default), or plug in a single free datacenter proxy they
|
|
21
|
+
* already have access to.
|
|
22
|
+
*/
|
|
23
|
+
export type ProxyProtocol = 'http' | 'https' | 'socks5';
|
|
24
|
+
export interface ProxyDescriptor {
|
|
25
|
+
/** Human-readable ID for logging and reputation tracking. */
|
|
26
|
+
id: string;
|
|
27
|
+
protocol: ProxyProtocol;
|
|
28
|
+
host: string;
|
|
29
|
+
port: number;
|
|
30
|
+
username?: string;
|
|
31
|
+
password?: string;
|
|
32
|
+
/** Optional: the exit country for this proxy, used for locale/timezone
|
|
33
|
+
* consistency checks. */
|
|
34
|
+
exitCountry?: string;
|
|
35
|
+
/** Optional: tags like 'residential', 'datacenter', 'mobile'. */
|
|
36
|
+
tags?: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Provider-specific URL formatters. The caller provides the API key or
|
|
40
|
+
* username/password and we produce a ProxyDescriptor ready to use.
|
|
41
|
+
*/
|
|
42
|
+
export declare const ProxyProviders: {
|
|
43
|
+
readonly brightData: (username: string, password: string, zone: string) => ProxyDescriptor;
|
|
44
|
+
readonly oxylabs: (username: string, password: string) => ProxyDescriptor;
|
|
45
|
+
readonly smartproxy: (username: string, password: string) => ProxyDescriptor;
|
|
46
|
+
readonly custom: (descriptor: ProxyDescriptor) => ProxyDescriptor;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Format a ProxyDescriptor into the URL string BlackTipConfig.proxy expects.
|
|
50
|
+
*/
|
|
51
|
+
export declare function proxyToUrl(p: ProxyDescriptor): string;
|
|
52
|
+
interface BanRecord {
|
|
53
|
+
proxyId: string;
|
|
54
|
+
domain: string;
|
|
55
|
+
bannedAt: number;
|
|
56
|
+
reason?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface PoolOptions {
|
|
59
|
+
/** How long to remember a ban before retrying the same proxy on the
|
|
60
|
+
* same domain. Default: 24 hours. */
|
|
61
|
+
banDecayMs?: number;
|
|
62
|
+
/** Strategy for picking the next proxy. */
|
|
63
|
+
strategy?: 'round-robin' | 'random' | 'least-used';
|
|
64
|
+
}
|
|
65
|
+
export declare class ProxyPool {
|
|
66
|
+
private proxies;
|
|
67
|
+
private bans;
|
|
68
|
+
private usageCount;
|
|
69
|
+
private lastIndexByDomain;
|
|
70
|
+
private readonly banDecayMs;
|
|
71
|
+
private readonly strategy;
|
|
72
|
+
constructor(proxies?: ProxyDescriptor[], options?: PoolOptions);
|
|
73
|
+
add(proxy: ProxyDescriptor): void;
|
|
74
|
+
remove(proxyId: string): void;
|
|
75
|
+
size(): number;
|
|
76
|
+
/**
|
|
77
|
+
* Select the next proxy for a given domain, skipping any that are
|
|
78
|
+
* currently banned on that domain.
|
|
79
|
+
*/
|
|
80
|
+
selectForDomain(domain: string): ProxyDescriptor | null;
|
|
81
|
+
/**
|
|
82
|
+
* Record that a proxy got banned on a domain. Future calls to
|
|
83
|
+
* `selectForDomain(domain)` will skip this proxy until the ban decays.
|
|
84
|
+
*/
|
|
85
|
+
reportBan(proxyId: string, domain: string, reason?: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Clear expired bans from the in-memory list. Optional hygiene — not
|
|
88
|
+
* required for correctness but keeps memory bounded.
|
|
89
|
+
*/
|
|
90
|
+
pruneExpiredBans(): number;
|
|
91
|
+
/**
|
|
92
|
+
* Return a copy of the reputation state for observability.
|
|
93
|
+
*/
|
|
94
|
+
getReputationSnapshot(): {
|
|
95
|
+
proxies: ProxyDescriptor[];
|
|
96
|
+
activeBans: BanRecord[];
|
|
97
|
+
usage: Record<string, number>;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=proxy-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-pool.d.ts","sourceRoot":"","sources":["../src/proxy-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;8BAC0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc;oCACF,MAAM,YAAY,MAAM,QAAQ,MAAM,KAAG,eAAe;iCAS3D,MAAM,YAAY,MAAM,KAAG,eAAe;oCASvC,MAAM,YAAY,MAAM,KAAG,eAAe;kCAS5C,eAAe,KAAG,eAAe;CAC9C,CAAC;AAEX;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAGrD;AAID,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B;0CACsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,aAAa,GAAG,QAAQ,GAAG,YAAY,CAAC;CACpD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;gBAEpD,OAAO,GAAE,eAAe,EAAO,EAAE,OAAO,GAAE,WAAgB;IAMtE,GAAG,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAMjC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI7B,IAAI,IAAI,MAAM;IAId;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAqCvD;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IASjE;;;OAGG;IACH,gBAAgB,IAAI,MAAM;IAO1B;;OAEG;IACH,qBAAqB,IAAI;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;CAQhH"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy pool abstraction.
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency reference implementation. The caller supplies proxy
|
|
5
|
+
* credentials; this module handles:
|
|
6
|
+
*
|
|
7
|
+
* - Round-robin or domain-sticky selection.
|
|
8
|
+
* - In-memory reputation tracking (which proxies got banned on which
|
|
9
|
+
* domains, decay window, avoid-list).
|
|
10
|
+
* - URL formatting for the common residential providers (BrightData,
|
|
11
|
+
* Oxylabs, Smartproxy, NetNut) — caller passes the provider name
|
|
12
|
+
* and credentials, we build the right URL string.
|
|
13
|
+
*
|
|
14
|
+
* This is a scaffold — the actual integration with BlackTip's
|
|
15
|
+
* `BlackTipConfig.proxy` happens when `selectForDomain` is called and
|
|
16
|
+
* the result is set on the config before `bt.launch()`.
|
|
17
|
+
*
|
|
18
|
+
* Residential proxy bandwidth costs money. For the zero-budget path,
|
|
19
|
+
* callers can instantiate an empty pool and use BlackTip without any
|
|
20
|
+
* proxy (the default), or plug in a single free datacenter proxy they
|
|
21
|
+
* already have access to.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Provider-specific URL formatters. The caller provides the API key or
|
|
25
|
+
* username/password and we produce a ProxyDescriptor ready to use.
|
|
26
|
+
*/
|
|
27
|
+
export const ProxyProviders = {
|
|
28
|
+
brightData: (username, password, zone) => ({
|
|
29
|
+
id: `brightdata-${zone}`,
|
|
30
|
+
protocol: 'http',
|
|
31
|
+
host: 'brd.superproxy.io',
|
|
32
|
+
port: 22225,
|
|
33
|
+
username: `brd-customer-${username}-zone-${zone}`,
|
|
34
|
+
password,
|
|
35
|
+
tags: ['residential', 'brightdata'],
|
|
36
|
+
}),
|
|
37
|
+
oxylabs: (username, password) => ({
|
|
38
|
+
id: 'oxylabs',
|
|
39
|
+
protocol: 'http',
|
|
40
|
+
host: 'pr.oxylabs.io',
|
|
41
|
+
port: 7777,
|
|
42
|
+
username,
|
|
43
|
+
password,
|
|
44
|
+
tags: ['residential', 'oxylabs'],
|
|
45
|
+
}),
|
|
46
|
+
smartproxy: (username, password) => ({
|
|
47
|
+
id: 'smartproxy',
|
|
48
|
+
protocol: 'http',
|
|
49
|
+
host: 'gate.smartproxy.com',
|
|
50
|
+
port: 10000,
|
|
51
|
+
username,
|
|
52
|
+
password,
|
|
53
|
+
tags: ['residential', 'smartproxy'],
|
|
54
|
+
}),
|
|
55
|
+
custom: (descriptor) => descriptor,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Format a ProxyDescriptor into the URL string BlackTipConfig.proxy expects.
|
|
59
|
+
*/
|
|
60
|
+
export function proxyToUrl(p) {
|
|
61
|
+
const auth = p.username && p.password ? `${encodeURIComponent(p.username)}:${encodeURIComponent(p.password)}@` : '';
|
|
62
|
+
return `${p.protocol}://${auth}${p.host}:${p.port}`;
|
|
63
|
+
}
|
|
64
|
+
export class ProxyPool {
|
|
65
|
+
proxies = [];
|
|
66
|
+
bans = [];
|
|
67
|
+
usageCount = new Map();
|
|
68
|
+
lastIndexByDomain = new Map();
|
|
69
|
+
banDecayMs;
|
|
70
|
+
strategy;
|
|
71
|
+
constructor(proxies = [], options = {}) {
|
|
72
|
+
this.proxies = [...proxies];
|
|
73
|
+
this.banDecayMs = options.banDecayMs ?? 24 * 60 * 60 * 1000;
|
|
74
|
+
this.strategy = options.strategy ?? 'round-robin';
|
|
75
|
+
}
|
|
76
|
+
add(proxy) {
|
|
77
|
+
if (!this.proxies.find((p) => p.id === proxy.id)) {
|
|
78
|
+
this.proxies.push(proxy);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
remove(proxyId) {
|
|
82
|
+
this.proxies = this.proxies.filter((p) => p.id !== proxyId);
|
|
83
|
+
}
|
|
84
|
+
size() {
|
|
85
|
+
return this.proxies.length;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Select the next proxy for a given domain, skipping any that are
|
|
89
|
+
* currently banned on that domain.
|
|
90
|
+
*/
|
|
91
|
+
selectForDomain(domain) {
|
|
92
|
+
if (this.proxies.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
// Filter out proxies banned on this domain (after decay window).
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const banned = new Set(this.bans
|
|
97
|
+
.filter((b) => b.domain === domain && now - b.bannedAt < this.banDecayMs)
|
|
98
|
+
.map((b) => b.proxyId));
|
|
99
|
+
const eligible = this.proxies.filter((p) => !banned.has(p.id));
|
|
100
|
+
if (eligible.length === 0)
|
|
101
|
+
return null;
|
|
102
|
+
let chosen;
|
|
103
|
+
switch (this.strategy) {
|
|
104
|
+
case 'random':
|
|
105
|
+
chosen = eligible[Math.floor(Math.random() * eligible.length)];
|
|
106
|
+
break;
|
|
107
|
+
case 'least-used':
|
|
108
|
+
chosen = eligible.reduce((best, p) => (this.usageCount.get(p.id) ?? 0) < (this.usageCount.get(best.id) ?? 0) ? p : best);
|
|
109
|
+
break;
|
|
110
|
+
case 'round-robin':
|
|
111
|
+
default: {
|
|
112
|
+
const lastIdx = this.lastIndexByDomain.get(domain) ?? -1;
|
|
113
|
+
const nextIdx = (lastIdx + 1) % eligible.length;
|
|
114
|
+
this.lastIndexByDomain.set(domain, nextIdx);
|
|
115
|
+
chosen = eligible[nextIdx];
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.usageCount.set(chosen.id, (this.usageCount.get(chosen.id) ?? 0) + 1);
|
|
120
|
+
return chosen;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Record that a proxy got banned on a domain. Future calls to
|
|
124
|
+
* `selectForDomain(domain)` will skip this proxy until the ban decays.
|
|
125
|
+
*/
|
|
126
|
+
reportBan(proxyId, domain, reason) {
|
|
127
|
+
this.bans.push({
|
|
128
|
+
proxyId,
|
|
129
|
+
domain,
|
|
130
|
+
bannedAt: Date.now(),
|
|
131
|
+
reason,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Clear expired bans from the in-memory list. Optional hygiene — not
|
|
136
|
+
* required for correctness but keeps memory bounded.
|
|
137
|
+
*/
|
|
138
|
+
pruneExpiredBans() {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const before = this.bans.length;
|
|
141
|
+
this.bans = this.bans.filter((b) => now - b.bannedAt < this.banDecayMs);
|
|
142
|
+
return before - this.bans.length;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Return a copy of the reputation state for observability.
|
|
146
|
+
*/
|
|
147
|
+
getReputationSnapshot() {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
return {
|
|
150
|
+
proxies: [...this.proxies],
|
|
151
|
+
activeBans: this.bans.filter((b) => now - b.bannedAt < this.banDecayMs),
|
|
152
|
+
usage: Object.fromEntries(this.usageCount.entries()),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=proxy-pool.js.map
|