@rester159/blacktip 0.1.0 → 0.4.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/CHANGELOG.md +190 -0
- package/README.md +95 -0
- package/dist/behavioral/parsers.d.ts +89 -0
- package/dist/behavioral/parsers.d.ts.map +1 -0
- package/dist/behavioral/parsers.js +223 -0
- package/dist/behavioral/parsers.js.map +1 -0
- package/dist/blacktip.d.ts +86 -0
- package/dist/blacktip.d.ts.map +1 -1
- package/dist/blacktip.js +193 -0
- package/dist/blacktip.js.map +1 -1
- package/dist/browser-core.d.ts.map +1 -1
- package/dist/browser-core.js +125 -33
- package/dist/browser-core.js.map +1 -1
- package/dist/diagnostics.d.ts +150 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +389 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/identity-pool.d.ts +160 -0
- package/dist/identity-pool.d.ts.map +1 -0
- package/dist/identity-pool.js +288 -0
- package/dist/identity-pool.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/tls-side-channel.d.ts +82 -0
- package/dist/tls-side-channel.d.ts.map +1 -0
- package/dist/tls-side-channel.js +241 -0
- package/dist/tls-side-channel.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/akamai-bypass.md +257 -0
- package/docs/anti-bot-validation.md +84 -0
- package/docs/calibration-validation.md +93 -0
- package/docs/identity-pool.md +176 -0
- package/docs/tls-side-channel.md +83 -0
- package/native/tls-client/go.mod +21 -0
- package/native/tls-client/go.sum +36 -0
- package/native/tls-client/main.go +216 -0
- package/package.json +8 -2
- package/scripts/fit-cmu-keystroke.mjs +186 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity pool — long-running session and identity rotation.
|
|
3
|
+
*
|
|
4
|
+
* An "identity" is the union of everything that makes a browser session
|
|
5
|
+
* look like one specific human: cookies, localStorage, proxy, device
|
|
6
|
+
* profile, behavior profile, locale, timezone. v0.4.0's answer to the
|
|
7
|
+
* question "how do I rotate identities cleanly across many requests
|
|
8
|
+
* without my whole flow looking like one bot retried under different IPs?"
|
|
9
|
+
*
|
|
10
|
+
* The pool persists to a JSON file at a caller-supplied path so identities
|
|
11
|
+
* survive process restarts. Each identity has its own burn-list (per
|
|
12
|
+
* domain), so an identity that got blocked on opentable.com is still
|
|
13
|
+
* eligible for amazon.com.
|
|
14
|
+
*
|
|
15
|
+
* Composition:
|
|
16
|
+
* - Snapshots (cookies + storage) come from `SnapshotManager`.
|
|
17
|
+
* - Proxies come from `ProxyPool` and are bound to an identity at
|
|
18
|
+
* creation time.
|
|
19
|
+
* - Device + behavior profiles are part of the identity itself.
|
|
20
|
+
*
|
|
21
|
+
* Lifecycle:
|
|
22
|
+
* 1. `pool.add(...)` — create an identity (no snapshot yet).
|
|
23
|
+
* 2. `pool.acquire(domain)` — pick one not burned for this domain.
|
|
24
|
+
* 3. `pool.applyToConfig(identity)` — produces a `BlackTipConfig` to
|
|
25
|
+
* pass to `new BlackTip(...)`.
|
|
26
|
+
* 4. After flow, `pool.captureFromBlackTip(bt, identity)` — save the
|
|
27
|
+
* session state back into the identity for next time.
|
|
28
|
+
* 5. `pool.markBurned(id, reason, domain?)` — when something blocks.
|
|
29
|
+
*
|
|
30
|
+
* The pool is in-memory + file-backed. There's no SQL, no LRU cache,
|
|
31
|
+
* no daemon. It's a single class with two file-IO methods.
|
|
32
|
+
*/
|
|
33
|
+
import type { BlackTip } from './blacktip.js';
|
|
34
|
+
import type { BlackTipConfig, ProfileConfig } from './types.js';
|
|
35
|
+
import type { SessionSnapshot } from './snapshot.js';
|
|
36
|
+
/** Device profile names BlackTip ships with. */
|
|
37
|
+
export type DeviceProfileName = 'desktop-windows' | 'desktop-macos' | 'desktop-linux';
|
|
38
|
+
import type { ProxyDescriptor, ProxyPool } from './proxy-pool.js';
|
|
39
|
+
export interface Identity {
|
|
40
|
+
/** UUID. Generated on `add()` if not supplied. */
|
|
41
|
+
id: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
/** ISO timestamp of last `acquire()` call, or null if never used. */
|
|
45
|
+
lastUsedAt: string | null;
|
|
46
|
+
/** Number of times this identity has been acquired. */
|
|
47
|
+
useCount: number;
|
|
48
|
+
/** ISO timestamp of when the identity was fully burned (across all domains). */
|
|
49
|
+
burnedAt: string | null;
|
|
50
|
+
/** Reason from the most recent burn. */
|
|
51
|
+
burnedReason: string | null;
|
|
52
|
+
/** Domains on which this identity is burned. Per-domain so an identity
|
|
53
|
+
* blocked on opentable can still be used elsewhere. */
|
|
54
|
+
burnedDomains: string[];
|
|
55
|
+
/** Cookies + localStorage. Null until the first capture. */
|
|
56
|
+
snapshot: SessionSnapshot | null;
|
|
57
|
+
/** The proxy bound to this identity, if any. */
|
|
58
|
+
proxy: ProxyDescriptor | null;
|
|
59
|
+
/** Behavior profile — either a built-in name or a full ProfileConfig
|
|
60
|
+
* (e.g. one fitted via `fitFromSamples`). */
|
|
61
|
+
behaviorProfile: ProfileConfig | 'human' | 'scraper';
|
|
62
|
+
/** Device profile. */
|
|
63
|
+
deviceProfile: DeviceProfileName;
|
|
64
|
+
locale: string;
|
|
65
|
+
timezone: string;
|
|
66
|
+
}
|
|
67
|
+
export interface RotationPolicy {
|
|
68
|
+
/** Burn an identity after this many uses. Default: never. */
|
|
69
|
+
maxUses?: number;
|
|
70
|
+
/** Burn an identity if its first use was longer than this ago. Default: never. */
|
|
71
|
+
maxAgeMs?: number;
|
|
72
|
+
/** When acquiring, prefer the least-recently-used identity (otherwise
|
|
73
|
+
* uses round-robin order). Default: true. */
|
|
74
|
+
preferLeastRecentlyUsed?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface IdentityPoolOptions {
|
|
77
|
+
/** Path to a JSON file that backs the pool. Will be created if missing. */
|
|
78
|
+
storePath: string;
|
|
79
|
+
/** Optional ProxyPool to draw new identities' proxies from when
|
|
80
|
+
* `add()` is called without an explicit proxy. */
|
|
81
|
+
proxyPool?: ProxyPool;
|
|
82
|
+
/** Rotation policy. Defaults to no automatic burning. */
|
|
83
|
+
rotationPolicy?: RotationPolicy;
|
|
84
|
+
}
|
|
85
|
+
export declare class IdentityPool {
|
|
86
|
+
private storePath;
|
|
87
|
+
private proxyPool;
|
|
88
|
+
private rotationPolicy;
|
|
89
|
+
private identities;
|
|
90
|
+
private acquireCursor;
|
|
91
|
+
constructor(options: IdentityPoolOptions);
|
|
92
|
+
/** Load identities from the store file. Called automatically by the
|
|
93
|
+
* constructor; safe to call again to refresh from disk. */
|
|
94
|
+
load(): void;
|
|
95
|
+
/** Persist the in-memory state to the store file. Called automatically
|
|
96
|
+
* after every mutation. */
|
|
97
|
+
save(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Create a new identity. Required: deviceProfile. Optional: everything
|
|
100
|
+
* else (sane defaults are filled in). If a `proxyPool` was supplied to
|
|
101
|
+
* the IdentityPool and `proxy` is omitted, the pool draws one.
|
|
102
|
+
*/
|
|
103
|
+
add(init: {
|
|
104
|
+
label?: string;
|
|
105
|
+
deviceProfile: DeviceProfileName;
|
|
106
|
+
behaviorProfile?: Identity['behaviorProfile'];
|
|
107
|
+
locale?: string;
|
|
108
|
+
timezone?: string;
|
|
109
|
+
proxy?: ProxyDescriptor | null;
|
|
110
|
+
}): Identity;
|
|
111
|
+
remove(id: string): boolean;
|
|
112
|
+
list(): Identity[];
|
|
113
|
+
size(): number;
|
|
114
|
+
/** Identities not fully burned (i.e. eligible for at least some domains). */
|
|
115
|
+
available(): Identity[];
|
|
116
|
+
/**
|
|
117
|
+
* Pick an identity for use. If `domain` is supplied, the result is
|
|
118
|
+
* guaranteed not to be in that identity's `burnedDomains` list. Applies
|
|
119
|
+
* the rotation policy: identities exceeding `maxUses` or `maxAgeMs`
|
|
120
|
+
* are auto-burned and skipped.
|
|
121
|
+
*
|
|
122
|
+
* Returns null if no eligible identity exists.
|
|
123
|
+
*/
|
|
124
|
+
acquire(domain?: string): Identity | null;
|
|
125
|
+
/**
|
|
126
|
+
* Mark an identity as burned, either fully (omit `domain`) or only on
|
|
127
|
+
* one specific domain. Burned identities are skipped by `acquire()`.
|
|
128
|
+
* Domain-specific burns also report the proxy ban back to the
|
|
129
|
+
* `ProxyPool` if one is wired up — that's the feedback loop that
|
|
130
|
+
* keeps the pool clean.
|
|
131
|
+
*/
|
|
132
|
+
markBurned(id: string, reason: string, domain?: string): boolean;
|
|
133
|
+
/** Clear a burn (full or per-domain). Useful for manually unbanning. */
|
|
134
|
+
clearBurn(id: string, domain?: string): boolean;
|
|
135
|
+
private applyRotationPolicy;
|
|
136
|
+
/**
|
|
137
|
+
* Build a `BlackTipConfig` for the given identity. The caller passes
|
|
138
|
+
* this to `new BlackTip(config)`. The result includes:
|
|
139
|
+
* - deviceProfile, behaviorProfile, locale, timezone from the identity
|
|
140
|
+
* - proxy URL if the identity has one bound
|
|
141
|
+
*
|
|
142
|
+
* Cookies and localStorage from the identity's snapshot are NOT applied
|
|
143
|
+
* here (they need a launched browser). Call `restoreSnapshot(bt, identity)`
|
|
144
|
+
* after `bt.launch()` to apply them.
|
|
145
|
+
*/
|
|
146
|
+
applyToConfig(identity: Identity, baseConfig?: Partial<BlackTipConfig>): BlackTipConfig;
|
|
147
|
+
/**
|
|
148
|
+
* After `bt.launch()`, apply the identity's snapshot (cookies +
|
|
149
|
+
* localStorage) to the running browser. No-op if the identity has
|
|
150
|
+
* no snapshot yet.
|
|
151
|
+
*/
|
|
152
|
+
restoreSnapshot(bt: BlackTip, identity: Identity): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Capture the current BlackTip session state into the identity. Call
|
|
155
|
+
* after a successful flow so the next acquire of this identity starts
|
|
156
|
+
* from a known-good logged-in state.
|
|
157
|
+
*/
|
|
158
|
+
captureSnapshot(bt: BlackTip, identity: Identity): Promise<void>;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=identity-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-pool.d.ts","sourceRoot":"","sources":["../src/identity-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity pool — long-running session and identity rotation.
|
|
3
|
+
*
|
|
4
|
+
* An "identity" is the union of everything that makes a browser session
|
|
5
|
+
* look like one specific human: cookies, localStorage, proxy, device
|
|
6
|
+
* profile, behavior profile, locale, timezone. v0.4.0's answer to the
|
|
7
|
+
* question "how do I rotate identities cleanly across many requests
|
|
8
|
+
* without my whole flow looking like one bot retried under different IPs?"
|
|
9
|
+
*
|
|
10
|
+
* The pool persists to a JSON file at a caller-supplied path so identities
|
|
11
|
+
* survive process restarts. Each identity has its own burn-list (per
|
|
12
|
+
* domain), so an identity that got blocked on opentable.com is still
|
|
13
|
+
* eligible for amazon.com.
|
|
14
|
+
*
|
|
15
|
+
* Composition:
|
|
16
|
+
* - Snapshots (cookies + storage) come from `SnapshotManager`.
|
|
17
|
+
* - Proxies come from `ProxyPool` and are bound to an identity at
|
|
18
|
+
* creation time.
|
|
19
|
+
* - Device + behavior profiles are part of the identity itself.
|
|
20
|
+
*
|
|
21
|
+
* Lifecycle:
|
|
22
|
+
* 1. `pool.add(...)` — create an identity (no snapshot yet).
|
|
23
|
+
* 2. `pool.acquire(domain)` — pick one not burned for this domain.
|
|
24
|
+
* 3. `pool.applyToConfig(identity)` — produces a `BlackTipConfig` to
|
|
25
|
+
* pass to `new BlackTip(...)`.
|
|
26
|
+
* 4. After flow, `pool.captureFromBlackTip(bt, identity)` — save the
|
|
27
|
+
* session state back into the identity for next time.
|
|
28
|
+
* 5. `pool.markBurned(id, reason, domain?)` — when something blocks.
|
|
29
|
+
*
|
|
30
|
+
* The pool is in-memory + file-backed. There's no SQL, no LRU cache,
|
|
31
|
+
* no daemon. It's a single class with two file-IO methods.
|
|
32
|
+
*/
|
|
33
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
34
|
+
import { dirname } from 'node:path';
|
|
35
|
+
import { randomUUID } from 'node:crypto';
|
|
36
|
+
import { SnapshotManager } from './snapshot.js';
|
|
37
|
+
import { proxyToUrl } from './proxy-pool.js';
|
|
38
|
+
export class IdentityPool {
|
|
39
|
+
storePath;
|
|
40
|
+
proxyPool;
|
|
41
|
+
rotationPolicy;
|
|
42
|
+
identities = [];
|
|
43
|
+
acquireCursor = 0;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.storePath = options.storePath;
|
|
46
|
+
this.proxyPool = options.proxyPool;
|
|
47
|
+
this.rotationPolicy = {
|
|
48
|
+
maxUses: options.rotationPolicy?.maxUses ?? Infinity,
|
|
49
|
+
maxAgeMs: options.rotationPolicy?.maxAgeMs ?? Infinity,
|
|
50
|
+
preferLeastRecentlyUsed: options.rotationPolicy?.preferLeastRecentlyUsed ?? true,
|
|
51
|
+
};
|
|
52
|
+
this.load();
|
|
53
|
+
}
|
|
54
|
+
// ── File I/O ──
|
|
55
|
+
/** Load identities from the store file. Called automatically by the
|
|
56
|
+
* constructor; safe to call again to refresh from disk. */
|
|
57
|
+
load() {
|
|
58
|
+
if (!existsSync(this.storePath)) {
|
|
59
|
+
this.identities = [];
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const raw = readFileSync(this.storePath, 'utf-8');
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
if (parsed.version !== 1) {
|
|
66
|
+
throw new Error(`Unsupported pool file version: ${parsed.version}`);
|
|
67
|
+
}
|
|
68
|
+
this.identities = parsed.identities;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
throw new Error(`IdentityPool: failed to load ${this.storePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Persist the in-memory state to the store file. Called automatically
|
|
75
|
+
* after every mutation. */
|
|
76
|
+
save() {
|
|
77
|
+
const dir = dirname(this.storePath);
|
|
78
|
+
if (!existsSync(dir))
|
|
79
|
+
mkdirSync(dir, { recursive: true });
|
|
80
|
+
const file = {
|
|
81
|
+
version: 1,
|
|
82
|
+
savedAt: new Date().toISOString(),
|
|
83
|
+
identities: this.identities,
|
|
84
|
+
};
|
|
85
|
+
writeFileSync(this.storePath, JSON.stringify(file, null, 2));
|
|
86
|
+
}
|
|
87
|
+
// ── CRUD ──
|
|
88
|
+
/**
|
|
89
|
+
* Create a new identity. Required: deviceProfile. Optional: everything
|
|
90
|
+
* else (sane defaults are filled in). If a `proxyPool` was supplied to
|
|
91
|
+
* the IdentityPool and `proxy` is omitted, the pool draws one.
|
|
92
|
+
*/
|
|
93
|
+
add(init) {
|
|
94
|
+
const id = randomUUID();
|
|
95
|
+
let proxy = init.proxy ?? null;
|
|
96
|
+
// If no proxy supplied and a pool exists, take the next round-robin pick
|
|
97
|
+
// from the pool. We use a placeholder domain key since identities
|
|
98
|
+
// are domain-agnostic at creation time.
|
|
99
|
+
if (proxy === null && this.proxyPool && this.proxyPool.size() > 0) {
|
|
100
|
+
proxy = this.proxyPool.selectForDomain('__identity-pool__');
|
|
101
|
+
}
|
|
102
|
+
const identity = {
|
|
103
|
+
id,
|
|
104
|
+
label: init.label,
|
|
105
|
+
createdAt: new Date().toISOString(),
|
|
106
|
+
lastUsedAt: null,
|
|
107
|
+
useCount: 0,
|
|
108
|
+
burnedAt: null,
|
|
109
|
+
burnedReason: null,
|
|
110
|
+
burnedDomains: [],
|
|
111
|
+
snapshot: null,
|
|
112
|
+
proxy,
|
|
113
|
+
behaviorProfile: init.behaviorProfile ?? 'human',
|
|
114
|
+
deviceProfile: init.deviceProfile,
|
|
115
|
+
locale: init.locale ?? 'en-US',
|
|
116
|
+
timezone: init.timezone ?? 'America/New_York',
|
|
117
|
+
};
|
|
118
|
+
this.identities.push(identity);
|
|
119
|
+
this.save();
|
|
120
|
+
return identity;
|
|
121
|
+
}
|
|
122
|
+
remove(id) {
|
|
123
|
+
const before = this.identities.length;
|
|
124
|
+
this.identities = this.identities.filter((i) => i.id !== id);
|
|
125
|
+
if (this.identities.length !== before) {
|
|
126
|
+
this.save();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
list() {
|
|
132
|
+
return [...this.identities];
|
|
133
|
+
}
|
|
134
|
+
size() {
|
|
135
|
+
return this.identities.length;
|
|
136
|
+
}
|
|
137
|
+
/** Identities not fully burned (i.e. eligible for at least some domains). */
|
|
138
|
+
available() {
|
|
139
|
+
return this.identities.filter((i) => i.burnedAt === null);
|
|
140
|
+
}
|
|
141
|
+
// ── Acquisition ──
|
|
142
|
+
/**
|
|
143
|
+
* Pick an identity for use. If `domain` is supplied, the result is
|
|
144
|
+
* guaranteed not to be in that identity's `burnedDomains` list. Applies
|
|
145
|
+
* the rotation policy: identities exceeding `maxUses` or `maxAgeMs`
|
|
146
|
+
* are auto-burned and skipped.
|
|
147
|
+
*
|
|
148
|
+
* Returns null if no eligible identity exists.
|
|
149
|
+
*/
|
|
150
|
+
acquire(domain) {
|
|
151
|
+
this.applyRotationPolicy();
|
|
152
|
+
const eligible = this.identities.filter((i) => {
|
|
153
|
+
if (i.burnedAt !== null)
|
|
154
|
+
return false;
|
|
155
|
+
if (domain && i.burnedDomains.includes(domain))
|
|
156
|
+
return false;
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
if (eligible.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
let chosen;
|
|
162
|
+
if (this.rotationPolicy.preferLeastRecentlyUsed) {
|
|
163
|
+
chosen = eligible.reduce((best, i) => {
|
|
164
|
+
const bestT = best.lastUsedAt ? Date.parse(best.lastUsedAt) : 0;
|
|
165
|
+
const iT = i.lastUsedAt ? Date.parse(i.lastUsedAt) : 0;
|
|
166
|
+
return iT < bestT ? i : best;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this.acquireCursor = (this.acquireCursor + 1) % eligible.length;
|
|
171
|
+
chosen = eligible[this.acquireCursor];
|
|
172
|
+
}
|
|
173
|
+
chosen.lastUsedAt = new Date().toISOString();
|
|
174
|
+
chosen.useCount++;
|
|
175
|
+
this.save();
|
|
176
|
+
return chosen;
|
|
177
|
+
}
|
|
178
|
+
// ── Burning ──
|
|
179
|
+
/**
|
|
180
|
+
* Mark an identity as burned, either fully (omit `domain`) or only on
|
|
181
|
+
* one specific domain. Burned identities are skipped by `acquire()`.
|
|
182
|
+
* Domain-specific burns also report the proxy ban back to the
|
|
183
|
+
* `ProxyPool` if one is wired up — that's the feedback loop that
|
|
184
|
+
* keeps the pool clean.
|
|
185
|
+
*/
|
|
186
|
+
markBurned(id, reason, domain) {
|
|
187
|
+
const identity = this.identities.find((i) => i.id === id);
|
|
188
|
+
if (!identity)
|
|
189
|
+
return false;
|
|
190
|
+
if (domain) {
|
|
191
|
+
if (!identity.burnedDomains.includes(domain))
|
|
192
|
+
identity.burnedDomains.push(domain);
|
|
193
|
+
// If the identity has a proxy and a ProxyPool, report the ban so
|
|
194
|
+
// the pool's reputation tracking learns from this failure.
|
|
195
|
+
if (identity.proxy && this.proxyPool) {
|
|
196
|
+
this.proxyPool.reportBan(identity.proxy.id, domain, reason);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
identity.burnedAt = new Date().toISOString();
|
|
201
|
+
identity.burnedReason = reason;
|
|
202
|
+
}
|
|
203
|
+
this.save();
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
/** Clear a burn (full or per-domain). Useful for manually unbanning. */
|
|
207
|
+
clearBurn(id, domain) {
|
|
208
|
+
const identity = this.identities.find((i) => i.id === id);
|
|
209
|
+
if (!identity)
|
|
210
|
+
return false;
|
|
211
|
+
if (domain) {
|
|
212
|
+
identity.burnedDomains = identity.burnedDomains.filter((d) => d !== domain);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
identity.burnedAt = null;
|
|
216
|
+
identity.burnedReason = null;
|
|
217
|
+
}
|
|
218
|
+
this.save();
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
applyRotationPolicy() {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
let mutated = false;
|
|
224
|
+
for (const i of this.identities) {
|
|
225
|
+
if (i.burnedAt !== null)
|
|
226
|
+
continue;
|
|
227
|
+
if (i.useCount >= this.rotationPolicy.maxUses) {
|
|
228
|
+
i.burnedAt = new Date().toISOString();
|
|
229
|
+
i.burnedReason = `auto-burn: useCount >= maxUses (${this.rotationPolicy.maxUses})`;
|
|
230
|
+
mutated = true;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (i.lastUsedAt) {
|
|
234
|
+
const ageMs = now - Date.parse(i.lastUsedAt);
|
|
235
|
+
if (ageMs > this.rotationPolicy.maxAgeMs) {
|
|
236
|
+
i.burnedAt = new Date().toISOString();
|
|
237
|
+
i.burnedReason = `auto-burn: age ${ageMs}ms > maxAgeMs ${this.rotationPolicy.maxAgeMs}`;
|
|
238
|
+
mutated = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (mutated)
|
|
243
|
+
this.save();
|
|
244
|
+
}
|
|
245
|
+
// ── BlackTip integration ──
|
|
246
|
+
/**
|
|
247
|
+
* Build a `BlackTipConfig` for the given identity. The caller passes
|
|
248
|
+
* this to `new BlackTip(config)`. The result includes:
|
|
249
|
+
* - deviceProfile, behaviorProfile, locale, timezone from the identity
|
|
250
|
+
* - proxy URL if the identity has one bound
|
|
251
|
+
*
|
|
252
|
+
* Cookies and localStorage from the identity's snapshot are NOT applied
|
|
253
|
+
* here (they need a launched browser). Call `restoreSnapshot(bt, identity)`
|
|
254
|
+
* after `bt.launch()` to apply them.
|
|
255
|
+
*/
|
|
256
|
+
applyToConfig(identity, baseConfig = {}) {
|
|
257
|
+
return {
|
|
258
|
+
...baseConfig,
|
|
259
|
+
deviceProfile: identity.deviceProfile,
|
|
260
|
+
behaviorProfile: identity.behaviorProfile,
|
|
261
|
+
locale: identity.locale,
|
|
262
|
+
timezone: identity.timezone,
|
|
263
|
+
proxy: identity.proxy ? proxyToUrl(identity.proxy) : baseConfig.proxy,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* After `bt.launch()`, apply the identity's snapshot (cookies +
|
|
268
|
+
* localStorage) to the running browser. No-op if the identity has
|
|
269
|
+
* no snapshot yet.
|
|
270
|
+
*/
|
|
271
|
+
async restoreSnapshot(bt, identity) {
|
|
272
|
+
if (!identity.snapshot)
|
|
273
|
+
return;
|
|
274
|
+
const mgr = new SnapshotManager(bt.core);
|
|
275
|
+
await mgr.restore(identity.snapshot);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Capture the current BlackTip session state into the identity. Call
|
|
279
|
+
* after a successful flow so the next acquire of this identity starts
|
|
280
|
+
* from a known-good logged-in state.
|
|
281
|
+
*/
|
|
282
|
+
async captureSnapshot(bt, identity) {
|
|
283
|
+
const mgr = new SnapshotManager(bt.core);
|
|
284
|
+
identity.snapshot = await mgr.capture(identity.label);
|
|
285
|
+
this.save();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=identity-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-pool.js","sourceRoot":"","sources":["../src/identity-pool.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,12 +8,19 @@ export { Logger } from './logging.js';
|
|
|
8
8
|
export { BrowserCore } from './browser-core.js';
|
|
9
9
|
export { fitDistribution, fitFittsLaw, fitMouseDynamics, fitTypingDynamics, fitFromSamples, deriveProfileConfig, } from './behavioral/calibration.js';
|
|
10
10
|
export type { MouseSample, MouseMovement, KeystrokeSample, TypingSession, DistributionFit, MouseFit, TypingFit, CalibratedProfile, } from './behavioral/calibration.js';
|
|
11
|
+
export { parseCmuKeystrokeCsv, parseBalabitMouseCsv, parseGenericTelemetryJson, CMU_PHRASE, } from './behavioral/parsers.js';
|
|
12
|
+
export { TlsSideChannel } from './tls-side-channel.js';
|
|
13
|
+
export type { TlsRequest, TlsResponse, ParsedCookie } from './tls-side-channel.js';
|
|
14
|
+
export { IdentityPool } from './identity-pool.js';
|
|
15
|
+
export type { Identity, IdentityPoolOptions, RotationPolicy, DeviceProfileName, } from './identity-pool.js';
|
|
11
16
|
export { ProxyPool, ProxyProviders, proxyToUrl } from './proxy-pool.js';
|
|
12
17
|
export type { ProxyDescriptor, ProxyProtocol, PoolOptions } from './proxy-pool.js';
|
|
13
18
|
export { SnapshotManager } from './snapshot.js';
|
|
14
19
|
export type { SessionSnapshot } from './snapshot.js';
|
|
15
20
|
export { attachObservability, JsonlFileExporter, ConsoleExporter, newTraceId, } from './observability.js';
|
|
16
21
|
export type { StructuredEvent, EventExporter } from './observability.js';
|
|
22
|
+
export { captureFingerprint, checkIpReputation, testAgainstAkamai, testAgainstAntiBot, } from './diagnostics.js';
|
|
23
|
+
export type { FingerprintSnapshot, IpReputationResult, AkamaiTestResult, AntiBotTestResult, AntiBotVendor, } from './diagnostics.js';
|
|
17
24
|
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
25
|
export { ErrorCodes } from './types.js';
|
|
19
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAGrC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,UAAU,GACX,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGnF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EACV,QAAQ,EACR,mBAAmB,EACnB,cAAc,EACd,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,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;AAGzE,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,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
CHANGED
|
@@ -7,8 +7,16 @@ export { Logger } from './logging.js';
|
|
|
7
7
|
export { BrowserCore } from './browser-core.js';
|
|
8
8
|
// v2 additions — Tier 2 calibration, Tier 3 infrastructure, observability.
|
|
9
9
|
export { fitDistribution, fitFittsLaw, fitMouseDynamics, fitTypingDynamics, fitFromSamples, deriveProfileConfig, } from './behavioral/calibration.js';
|
|
10
|
+
// v0.3.0 — dataset parsers for end-to-end calibration
|
|
11
|
+
export { parseCmuKeystrokeCsv, parseBalabitMouseCsv, parseGenericTelemetryJson, CMU_PHRASE, } from './behavioral/parsers.js';
|
|
12
|
+
// v0.3.0 — TLS side-channel via bogdanfinn/tls-client
|
|
13
|
+
export { TlsSideChannel } from './tls-side-channel.js';
|
|
14
|
+
// v0.4.0 — IdentityPool: long-running session and identity rotation
|
|
15
|
+
export { IdentityPool } from './identity-pool.js';
|
|
10
16
|
export { ProxyPool, ProxyProviders, proxyToUrl } from './proxy-pool.js';
|
|
11
17
|
export { SnapshotManager } from './snapshot.js';
|
|
12
18
|
export { attachObservability, JsonlFileExporter, ConsoleExporter, newTraceId, } from './observability.js';
|
|
19
|
+
// v0.2.0 — stealth diagnostics
|
|
20
|
+
export { captureFingerprint, checkIpReputation, testAgainstAkamai, testAgainstAntiBot, } from './diagnostics.js';
|
|
13
21
|
export { ErrorCodes } from './types.js';
|
|
14
22
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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;
|
|
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,sDAAsD;AACtD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,UAAU,GACX,MAAM,yBAAyB,CAAC;AAEjC,sDAAsD;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD,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;AAG5B,+BAA+B;AAC/B,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AA8C1B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLS side-channel — spawns the Go-based bogdanfinn/tls-client daemon
|
|
3
|
+
* and exposes a `fetchWithTls()` primitive that performs HTTP requests
|
|
4
|
+
* with a real Chrome TLS ClientHello, real H2 frame settings, and real
|
|
5
|
+
* H2 frame order. Used to make gating requests that the browser can't
|
|
6
|
+
* make through itself, then inject the resulting cookies into the
|
|
7
|
+
* BlackTip browser session.
|
|
8
|
+
*
|
|
9
|
+
* This is the v0.3.0 answer to "an edge gates the very first request
|
|
10
|
+
* before BlackTip's browser even has a session." Use it as follows:
|
|
11
|
+
*
|
|
12
|
+
* const channel = await TlsSideChannel.spawn();
|
|
13
|
+
* const resp = await channel.fetch('https://protected.example.com/');
|
|
14
|
+
* await bt.setCookies(resp.cookies.map(c => ({ name: c.name, value: c.value, domain: c.domain, path: c.path })));
|
|
15
|
+
* await bt.navigate('https://protected.example.com/');
|
|
16
|
+
* // ... browser session now has the cookies the gating request earned
|
|
17
|
+
* await channel.close();
|
|
18
|
+
*
|
|
19
|
+
* The daemon binary lives in `native/tls-client/blacktip-tls` (Linux/macOS)
|
|
20
|
+
* or `native/tls-client/blacktip-tls.exe` (Windows). Build it once with
|
|
21
|
+
* `cd native/tls-client && go build -o blacktip-tls .` — Go is the only
|
|
22
|
+
* build dependency, and you can grab it from https://go.dev/dl/.
|
|
23
|
+
*
|
|
24
|
+
* The daemon stays alive across many requests so we don't pay subprocess
|
|
25
|
+
* startup cost per call. It uses a newline-delimited JSON wire protocol
|
|
26
|
+
* with per-request IDs, so multiple `fetch()` calls can be in flight
|
|
27
|
+
* concurrently without interleaving issues.
|
|
28
|
+
*/
|
|
29
|
+
export interface TlsRequest {
|
|
30
|
+
url: string;
|
|
31
|
+
method?: string;
|
|
32
|
+
headers?: Record<string, string>;
|
|
33
|
+
/** Request body as a UTF-8 string (will be base64-encoded for the wire). */
|
|
34
|
+
body?: string;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
/** Chrome / Firefox / Safari profile name; defaults to chrome_133. */
|
|
37
|
+
profile?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface TlsResponse {
|
|
40
|
+
status: number;
|
|
41
|
+
/** Headers from the upstream response, lower-cased keys. */
|
|
42
|
+
headers: Record<string, string[]>;
|
|
43
|
+
/** Response body as a UTF-8 string (decoded from the wire's base64). */
|
|
44
|
+
body: string;
|
|
45
|
+
finalUrl: string;
|
|
46
|
+
durationMs: number;
|
|
47
|
+
/** Cookies parsed from `Set-Cookie` headers, ready to inject via `bt.setCookies()`. */
|
|
48
|
+
cookies: ParsedCookie[];
|
|
49
|
+
}
|
|
50
|
+
export interface ParsedCookie {
|
|
51
|
+
name: string;
|
|
52
|
+
value: string;
|
|
53
|
+
domain: string;
|
|
54
|
+
path: string;
|
|
55
|
+
httpOnly: boolean;
|
|
56
|
+
secure: boolean;
|
|
57
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
58
|
+
expires?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare class TlsSideChannel {
|
|
61
|
+
private proc;
|
|
62
|
+
private rl;
|
|
63
|
+
private pending;
|
|
64
|
+
private nextId;
|
|
65
|
+
private closed;
|
|
66
|
+
private constructor();
|
|
67
|
+
/**
|
|
68
|
+
* Spawn the daemon. Throws if the binary isn't built or can't start.
|
|
69
|
+
* The daemon stays alive until you call `close()`.
|
|
70
|
+
*/
|
|
71
|
+
static spawn(): Promise<TlsSideChannel>;
|
|
72
|
+
private handleLine;
|
|
73
|
+
/**
|
|
74
|
+
* Perform a single TLS-impersonated request. Multiple `fetch()` calls
|
|
75
|
+
* can be in flight concurrently — the daemon handles each in its own
|
|
76
|
+
* goroutine and matches responses by id.
|
|
77
|
+
*/
|
|
78
|
+
fetch(req: TlsRequest): Promise<TlsResponse>;
|
|
79
|
+
/** Shut down the daemon and clean up the subprocess. */
|
|
80
|
+
close(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=tls-side-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls-side-channel.d.ts","sourceRoot":"","sources":["../src/tls-side-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAUH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoGD,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO;IAYP;;;OAGG;WACU,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC;IAU7C,OAAO,CAAC,UAAU;IA8ClB;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAuBlD,wDAAwD;IAClD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAc7B"}
|