@apitap/core 1.0.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/LICENSE +60 -0
- package/README.md +362 -0
- package/SKILL.md +270 -0
- package/dist/auth/crypto.d.ts +31 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/handoff.d.ts +29 -0
- package/dist/auth/handoff.js +180 -0
- package/dist/auth/handoff.js.map +1 -0
- package/dist/auth/manager.d.ts +46 -0
- package/dist/auth/manager.js +127 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth-refresh.d.ts +16 -0
- package/dist/auth/oauth-refresh.js +91 -0
- package/dist/auth/oauth-refresh.js.map +1 -0
- package/dist/auth/refresh.d.ts +43 -0
- package/dist/auth/refresh.js +217 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/capture/anti-bot.d.ts +15 -0
- package/dist/capture/anti-bot.js +43 -0
- package/dist/capture/anti-bot.js.map +1 -0
- package/dist/capture/blocklist.d.ts +6 -0
- package/dist/capture/blocklist.js +70 -0
- package/dist/capture/blocklist.js.map +1 -0
- package/dist/capture/body-diff.d.ts +8 -0
- package/dist/capture/body-diff.js +102 -0
- package/dist/capture/body-diff.js.map +1 -0
- package/dist/capture/body-variables.d.ts +13 -0
- package/dist/capture/body-variables.js +142 -0
- package/dist/capture/body-variables.js.map +1 -0
- package/dist/capture/domain.d.ts +8 -0
- package/dist/capture/domain.js +34 -0
- package/dist/capture/domain.js.map +1 -0
- package/dist/capture/entropy.d.ts +33 -0
- package/dist/capture/entropy.js +100 -0
- package/dist/capture/entropy.js.map +1 -0
- package/dist/capture/filter.d.ts +11 -0
- package/dist/capture/filter.js +49 -0
- package/dist/capture/filter.js.map +1 -0
- package/dist/capture/graphql.d.ts +21 -0
- package/dist/capture/graphql.js +99 -0
- package/dist/capture/graphql.js.map +1 -0
- package/dist/capture/idle.d.ts +23 -0
- package/dist/capture/idle.js +44 -0
- package/dist/capture/idle.js.map +1 -0
- package/dist/capture/monitor.d.ts +26 -0
- package/dist/capture/monitor.js +183 -0
- package/dist/capture/monitor.js.map +1 -0
- package/dist/capture/oauth-detector.d.ts +18 -0
- package/dist/capture/oauth-detector.js +96 -0
- package/dist/capture/oauth-detector.js.map +1 -0
- package/dist/capture/pagination.d.ts +9 -0
- package/dist/capture/pagination.js +40 -0
- package/dist/capture/pagination.js.map +1 -0
- package/dist/capture/parameterize.d.ts +17 -0
- package/dist/capture/parameterize.js +63 -0
- package/dist/capture/parameterize.js.map +1 -0
- package/dist/capture/scrubber.d.ts +5 -0
- package/dist/capture/scrubber.js +38 -0
- package/dist/capture/scrubber.js.map +1 -0
- package/dist/capture/session.d.ts +46 -0
- package/dist/capture/session.js +445 -0
- package/dist/capture/session.js.map +1 -0
- package/dist/capture/token-detector.d.ts +16 -0
- package/dist/capture/token-detector.js +62 -0
- package/dist/capture/token-detector.js.map +1 -0
- package/dist/capture/verifier.d.ts +17 -0
- package/dist/capture/verifier.js +147 -0
- package/dist/capture/verifier.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +930 -0
- package/dist/cli.js.map +1 -0
- package/dist/discovery/auth.d.ts +17 -0
- package/dist/discovery/auth.js +81 -0
- package/dist/discovery/auth.js.map +1 -0
- package/dist/discovery/fetch.d.ts +17 -0
- package/dist/discovery/fetch.js +59 -0
- package/dist/discovery/fetch.js.map +1 -0
- package/dist/discovery/frameworks.d.ts +11 -0
- package/dist/discovery/frameworks.js +249 -0
- package/dist/discovery/frameworks.js.map +1 -0
- package/dist/discovery/index.d.ts +21 -0
- package/dist/discovery/index.js +219 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/openapi.d.ts +13 -0
- package/dist/discovery/openapi.js +175 -0
- package/dist/discovery/openapi.js.map +1 -0
- package/dist/discovery/probes.d.ts +9 -0
- package/dist/discovery/probes.js +70 -0
- package/dist/discovery/probes.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/report.d.ts +52 -0
- package/dist/inspect/report.js +191 -0
- package/dist/inspect/report.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.js +526 -0
- package/dist/mcp.js.map +1 -0
- package/dist/orchestration/browse.d.ts +38 -0
- package/dist/orchestration/browse.js +198 -0
- package/dist/orchestration/browse.js.map +1 -0
- package/dist/orchestration/cache.d.ts +15 -0
- package/dist/orchestration/cache.js +24 -0
- package/dist/orchestration/cache.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.js +158 -0
- package/dist/plugin.js.map +1 -0
- package/dist/read/decoders/deepwiki.d.ts +2 -0
- package/dist/read/decoders/deepwiki.js +148 -0
- package/dist/read/decoders/deepwiki.js.map +1 -0
- package/dist/read/decoders/grokipedia.d.ts +2 -0
- package/dist/read/decoders/grokipedia.js +210 -0
- package/dist/read/decoders/grokipedia.js.map +1 -0
- package/dist/read/decoders/hackernews.d.ts +2 -0
- package/dist/read/decoders/hackernews.js +168 -0
- package/dist/read/decoders/hackernews.js.map +1 -0
- package/dist/read/decoders/index.d.ts +2 -0
- package/dist/read/decoders/index.js +12 -0
- package/dist/read/decoders/index.js.map +1 -0
- package/dist/read/decoders/reddit.d.ts +2 -0
- package/dist/read/decoders/reddit.js +142 -0
- package/dist/read/decoders/reddit.js.map +1 -0
- package/dist/read/decoders/twitter.d.ts +12 -0
- package/dist/read/decoders/twitter.js +187 -0
- package/dist/read/decoders/twitter.js.map +1 -0
- package/dist/read/decoders/wikipedia.d.ts +2 -0
- package/dist/read/decoders/wikipedia.js +66 -0
- package/dist/read/decoders/wikipedia.js.map +1 -0
- package/dist/read/decoders/youtube.d.ts +2 -0
- package/dist/read/decoders/youtube.js +69 -0
- package/dist/read/decoders/youtube.js.map +1 -0
- package/dist/read/extract.d.ts +25 -0
- package/dist/read/extract.js +320 -0
- package/dist/read/extract.js.map +1 -0
- package/dist/read/index.d.ts +14 -0
- package/dist/read/index.js +66 -0
- package/dist/read/index.js.map +1 -0
- package/dist/read/peek.d.ts +9 -0
- package/dist/read/peek.js +137 -0
- package/dist/read/peek.js.map +1 -0
- package/dist/read/types.d.ts +44 -0
- package/dist/read/types.js +3 -0
- package/dist/read/types.js.map +1 -0
- package/dist/replay/engine.d.ts +53 -0
- package/dist/replay/engine.js +441 -0
- package/dist/replay/engine.js.map +1 -0
- package/dist/replay/truncate.d.ts +16 -0
- package/dist/replay/truncate.js +92 -0
- package/dist/replay/truncate.js.map +1 -0
- package/dist/serve.d.ts +31 -0
- package/dist/serve.js +149 -0
- package/dist/serve.js.map +1 -0
- package/dist/skill/generator.d.ts +44 -0
- package/dist/skill/generator.js +419 -0
- package/dist/skill/generator.js.map +1 -0
- package/dist/skill/importer.d.ts +26 -0
- package/dist/skill/importer.js +80 -0
- package/dist/skill/importer.js.map +1 -0
- package/dist/skill/search.d.ts +19 -0
- package/dist/skill/search.js +51 -0
- package/dist/skill/search.js.map +1 -0
- package/dist/skill/signing.d.ts +16 -0
- package/dist/skill/signing.js +34 -0
- package/dist/skill/signing.js.map +1 -0
- package/dist/skill/ssrf.d.ts +27 -0
- package/dist/skill/ssrf.js +210 -0
- package/dist/skill/ssrf.js.map +1 -0
- package/dist/skill/store.d.ts +7 -0
- package/dist/skill/store.js +93 -0
- package/dist/skill/store.js.map +1 -0
- package/dist/stats/report.d.ts +26 -0
- package/dist/stats/report.js +157 -0
- package/dist/stats/report.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/auth/crypto.ts +92 -0
- package/src/auth/handoff.ts +229 -0
- package/src/auth/manager.ts +140 -0
- package/src/auth/oauth-refresh.ts +120 -0
- package/src/auth/refresh.ts +300 -0
- package/src/capture/anti-bot.ts +63 -0
- package/src/capture/blocklist.ts +75 -0
- package/src/capture/body-diff.ts +109 -0
- package/src/capture/body-variables.ts +156 -0
- package/src/capture/domain.ts +34 -0
- package/src/capture/entropy.ts +121 -0
- package/src/capture/filter.ts +56 -0
- package/src/capture/graphql.ts +124 -0
- package/src/capture/idle.ts +45 -0
- package/src/capture/monitor.ts +224 -0
- package/src/capture/oauth-detector.ts +106 -0
- package/src/capture/pagination.ts +49 -0
- package/src/capture/parameterize.ts +68 -0
- package/src/capture/scrubber.ts +49 -0
- package/src/capture/session.ts +502 -0
- package/src/capture/token-detector.ts +76 -0
- package/src/capture/verifier.ts +171 -0
- package/src/cli.ts +1031 -0
- package/src/discovery/auth.ts +99 -0
- package/src/discovery/fetch.ts +85 -0
- package/src/discovery/frameworks.ts +231 -0
- package/src/discovery/index.ts +256 -0
- package/src/discovery/openapi.ts +230 -0
- package/src/discovery/probes.ts +76 -0
- package/src/index.ts +26 -0
- package/src/inspect/report.ts +247 -0
- package/src/mcp.ts +618 -0
- package/src/orchestration/browse.ts +250 -0
- package/src/orchestration/cache.ts +37 -0
- package/src/plugin.ts +188 -0
- package/src/read/decoders/deepwiki.ts +180 -0
- package/src/read/decoders/grokipedia.ts +246 -0
- package/src/read/decoders/hackernews.ts +198 -0
- package/src/read/decoders/index.ts +15 -0
- package/src/read/decoders/reddit.ts +158 -0
- package/src/read/decoders/twitter.ts +211 -0
- package/src/read/decoders/wikipedia.ts +75 -0
- package/src/read/decoders/youtube.ts +75 -0
- package/src/read/extract.ts +396 -0
- package/src/read/index.ts +78 -0
- package/src/read/peek.ts +175 -0
- package/src/read/types.ts +37 -0
- package/src/replay/engine.ts +559 -0
- package/src/replay/truncate.ts +116 -0
- package/src/serve.ts +189 -0
- package/src/skill/generator.ts +473 -0
- package/src/skill/importer.ts +107 -0
- package/src/skill/search.ts +76 -0
- package/src/skill/signing.ts +36 -0
- package/src/skill/ssrf.ts +238 -0
- package/src/skill/store.ts +107 -0
- package/src/stats/report.ts +208 -0
- package/src/types.ts +233 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/capture/body-variables.ts
|
|
2
|
+
// Strategy 2: Name-based heuristics — keys implying dynamic values
|
|
3
|
+
const DYNAMIC_KEY_PATTERNS = [
|
|
4
|
+
// Time
|
|
5
|
+
/timestamp/i, /\btime\b/i, /\bdate\b/i, /created[_-]?at/i, /updated[_-]?at/i,
|
|
6
|
+
/\bsince\b/i, /\buntil\b/i, /\bbefore\b/i, /\bafter\b/i, /\bexpires?\b/i,
|
|
7
|
+
// Pagination
|
|
8
|
+
/\bcursor\b/i, /\boffset\b/i, /\bpage\b/i, /page[_-]?number/i,
|
|
9
|
+
/next[_-]?token/i, /continuation/i,
|
|
10
|
+
// Identity
|
|
11
|
+
/request[_-]?id/i, /correlation[_-]?id/i, /trace[_-]?id/i,
|
|
12
|
+
/\bnonce\b/i, /idempotency[_-]?key/i,
|
|
13
|
+
// Session
|
|
14
|
+
/session[_-]?id/i, /\bcsrf\b/i, /\bxsrf\b/i,
|
|
15
|
+
// Geolocation
|
|
16
|
+
/\bgeo(code|loc(ation)?)\b/i, /\blat(itude)?\b/i, /\blo?ng(itude)?\b/i,
|
|
17
|
+
/\bcoord/i, /\bzip\b/i, /\bpostal/i,
|
|
18
|
+
// Search / user input
|
|
19
|
+
/\bquery\b/i, /\bsearch/i, /\bkeyword/i, /\bterm\b/i, /\bfilter\b/i,
|
|
20
|
+
];
|
|
21
|
+
function isDynamicKeyName(key) {
|
|
22
|
+
return DYNAMIC_KEY_PATTERNS.some(p => p.test(key));
|
|
23
|
+
}
|
|
24
|
+
// Strategy 3: Pattern-based detection — value patterns implying dynamic data
|
|
25
|
+
function isTimestampOrPattern(value) {
|
|
26
|
+
if (typeof value === 'number') {
|
|
27
|
+
// Unix epoch seconds (roughly 2001–2603)
|
|
28
|
+
if (Number.isInteger(value) && value >= 1e9 && value < 2e10)
|
|
29
|
+
return true;
|
|
30
|
+
// Unix epoch milliseconds
|
|
31
|
+
if (Number.isInteger(value) && value >= 1e12 && value < 2e13)
|
|
32
|
+
return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// ISO 8601 datetime
|
|
36
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value))
|
|
37
|
+
return true;
|
|
38
|
+
// ISO 8601 date only
|
|
39
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
40
|
+
return true;
|
|
41
|
+
// Prefixed IDs (req_xxx, id_xxx, txn_xxx, msg_xxx, evt_xxx)
|
|
42
|
+
if (/^(req|id|txn|msg|evt)_[a-zA-Z0-9]+$/.test(value))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect which fields in a JSON body are likely dynamic variables.
|
|
48
|
+
* Uses three strategies:
|
|
49
|
+
* Strategy 1 (cross-request diffing) is in body-diff.ts
|
|
50
|
+
* Strategy 2: Name-based key heuristics
|
|
51
|
+
* Strategy 3: Pattern-based value detection
|
|
52
|
+
* Plus existing: numeric values, UUIDs, base64 cursors, numeric strings
|
|
53
|
+
*/
|
|
54
|
+
export function detectBodyVariables(body, prefix = '') {
|
|
55
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const detected = [];
|
|
59
|
+
const obj = body;
|
|
60
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
61
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
62
|
+
// Strategy 2: key name implies dynamic value
|
|
63
|
+
if (isDynamicKeyName(key) && value != null) {
|
|
64
|
+
detected.push(path);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'number') {
|
|
68
|
+
// Strategy 3: epoch timestamp detection
|
|
69
|
+
if (isTimestampOrPattern(value)) {
|
|
70
|
+
detected.push(path);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Original: numeric values are often IDs
|
|
74
|
+
detected.push(path);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (typeof value === 'string') {
|
|
78
|
+
// Strategy 3: timestamp/pattern detection (checked first, catches more)
|
|
79
|
+
if (isTimestampOrPattern(value)) {
|
|
80
|
+
detected.push(path);
|
|
81
|
+
}
|
|
82
|
+
else if (isLikelyDynamic(value)) {
|
|
83
|
+
detected.push(path);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
87
|
+
// Recurse into nested objects
|
|
88
|
+
detected.push(...detectBodyVariables(value, path));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return detected;
|
|
92
|
+
}
|
|
93
|
+
function isLikelyDynamic(value) {
|
|
94
|
+
// UUID
|
|
95
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
// Base64-ish cursor (long alphanumeric with optional padding)
|
|
99
|
+
if (value.length > 15 && /^[a-zA-Z0-9+/=_-]+$/.test(value)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// Numeric string (ID)
|
|
103
|
+
if (/^\d{4,}$/.test(value)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Substitute variables in a body template.
|
|
110
|
+
*/
|
|
111
|
+
export function substituteBodyVariables(template, values) {
|
|
112
|
+
if (typeof template === 'string') {
|
|
113
|
+
// String template with :param placeholders
|
|
114
|
+
return template.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (match, name) => {
|
|
115
|
+
return values[name] ?? match;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Deep clone and substitute
|
|
119
|
+
const result = JSON.parse(JSON.stringify(template));
|
|
120
|
+
for (const [path, value] of Object.entries(values)) {
|
|
121
|
+
setNestedValue(result, path, value);
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
function setNestedValue(obj, path, value) {
|
|
126
|
+
const parts = path.split('.');
|
|
127
|
+
let current = obj;
|
|
128
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
129
|
+
const part = parts[i];
|
|
130
|
+
if (current[part] && typeof current[part] === 'object') {
|
|
131
|
+
current = current[part];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return; // Path doesn't exist
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const lastPart = parts[parts.length - 1];
|
|
138
|
+
if (lastPart in current) {
|
|
139
|
+
current[lastPart] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=body-variables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-variables.js","sourceRoot":"","sources":["../../src/capture/body-variables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a hostname matches the target domain.
|
|
3
|
+
* Uses dot-prefix matching to prevent evil-example.com matching example.com.
|
|
4
|
+
*
|
|
5
|
+
* @param hostname - The hostname to check (e.g. "api.example.com")
|
|
6
|
+
* @param target - The target domain or URL (e.g. "example.com" or "https://example.com/path")
|
|
7
|
+
*/
|
|
8
|
+
export declare function isDomainMatch(hostname: string, target: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/capture/domain.ts
|
|
2
|
+
/**
|
|
3
|
+
* Check if a hostname matches the target domain.
|
|
4
|
+
* Uses dot-prefix matching to prevent evil-example.com matching example.com.
|
|
5
|
+
*
|
|
6
|
+
* @param hostname - The hostname to check (e.g. "api.example.com")
|
|
7
|
+
* @param target - The target domain or URL (e.g. "example.com" or "https://example.com/path")
|
|
8
|
+
*/
|
|
9
|
+
export function isDomainMatch(hostname, target) {
|
|
10
|
+
// Extract hostname from URL if target looks like a URL
|
|
11
|
+
let targetHost;
|
|
12
|
+
try {
|
|
13
|
+
if (target.includes('://')) {
|
|
14
|
+
targetHost = new URL(target).hostname;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
targetHost = target;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
targetHost = target;
|
|
22
|
+
}
|
|
23
|
+
// Strip www. prefix from target for broader matching
|
|
24
|
+
if (targetHost.startsWith('www.')) {
|
|
25
|
+
targetHost = targetHost.slice(4);
|
|
26
|
+
}
|
|
27
|
+
// Exact match
|
|
28
|
+
if (hostname === targetHost)
|
|
29
|
+
return true;
|
|
30
|
+
// Dot-prefix suffix match: hostname must end with ".targetHost"
|
|
31
|
+
// This prevents evil-example.com from matching example.com
|
|
32
|
+
return hostname.endsWith('.' + targetHost);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../../src/capture/domain.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAc;IAC5D,uDAAuD;IACvD,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,MAAM,CAAC;QACtB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,qDAAqD;IACrD,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,cAAc;IACd,IAAI,QAAQ,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAEzC,gEAAgE;IAChE,2DAA2D;IAC3D,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface TokenClassification {
|
|
2
|
+
isToken: boolean;
|
|
3
|
+
confidence: 'high' | 'medium';
|
|
4
|
+
format: 'jwt' | 'opaque';
|
|
5
|
+
jwtClaims?: JwtClaims;
|
|
6
|
+
}
|
|
7
|
+
export interface JwtClaims {
|
|
8
|
+
exp?: number;
|
|
9
|
+
iat?: number;
|
|
10
|
+
iss?: string;
|
|
11
|
+
aud?: string;
|
|
12
|
+
scope?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Calculate Shannon entropy (bits per character) of a string.
|
|
16
|
+
* Higher values indicate more randomness.
|
|
17
|
+
*/
|
|
18
|
+
export declare function shannonEntropy(value: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* Parse JWT claims from a token string.
|
|
21
|
+
* Returns null if not a valid JWT structure.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseJwtClaims(token: string): JwtClaims | null;
|
|
24
|
+
/**
|
|
25
|
+
* Classify whether a header/cookie value is likely an auth token.
|
|
26
|
+
*
|
|
27
|
+
* Detection hierarchy:
|
|
28
|
+
* 1. JWT (eyJ prefix, 2 dots) → decode and classify with rich metadata
|
|
29
|
+
* 2. UUID → skip (entity ID, not token)
|
|
30
|
+
* 3. Short values (<16 chars) → skip
|
|
31
|
+
* 4. High-entropy opaque string → classify by entropy threshold
|
|
32
|
+
*/
|
|
33
|
+
export declare function isLikelyToken(name: string, value: string): TokenClassification;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/capture/entropy.ts
|
|
2
|
+
const MIN_TOKEN_LENGTH = 16;
|
|
3
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
/**
|
|
5
|
+
* Calculate Shannon entropy (bits per character) of a string.
|
|
6
|
+
* Higher values indicate more randomness.
|
|
7
|
+
*/
|
|
8
|
+
export function shannonEntropy(value) {
|
|
9
|
+
if (value.length === 0)
|
|
10
|
+
return 0;
|
|
11
|
+
const freq = new Map();
|
|
12
|
+
for (const ch of value) {
|
|
13
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
14
|
+
}
|
|
15
|
+
let entropy = 0;
|
|
16
|
+
const len = value.length;
|
|
17
|
+
for (const count of freq.values()) {
|
|
18
|
+
const p = count / len;
|
|
19
|
+
entropy -= p * Math.log2(p);
|
|
20
|
+
}
|
|
21
|
+
return entropy;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse JWT claims from a token string.
|
|
25
|
+
* Returns null if not a valid JWT structure.
|
|
26
|
+
*/
|
|
27
|
+
export function parseJwtClaims(token) {
|
|
28
|
+
// JWT: starts with eyJ, has exactly 2 dots
|
|
29
|
+
if (!token.startsWith('eyJ'))
|
|
30
|
+
return null;
|
|
31
|
+
const parts = token.split('.');
|
|
32
|
+
if (parts.length !== 3)
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
// Decode payload (second part), base64url → JSON
|
|
36
|
+
const payload = parts[1];
|
|
37
|
+
const padded = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
38
|
+
const json = Buffer.from(padded, 'base64').toString('utf-8');
|
|
39
|
+
const claims = JSON.parse(json);
|
|
40
|
+
if (typeof claims !== 'object' || claims === null)
|
|
41
|
+
return null;
|
|
42
|
+
const result = {};
|
|
43
|
+
if (typeof claims.exp === 'number')
|
|
44
|
+
result.exp = claims.exp;
|
|
45
|
+
if (typeof claims.iat === 'number')
|
|
46
|
+
result.iat = claims.iat;
|
|
47
|
+
if (typeof claims.iss === 'string')
|
|
48
|
+
result.iss = claims.iss;
|
|
49
|
+
if (typeof claims.aud === 'string')
|
|
50
|
+
result.aud = claims.aud;
|
|
51
|
+
if (typeof claims.scope === 'string')
|
|
52
|
+
result.scope = claims.scope;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Classify whether a header/cookie value is likely an auth token.
|
|
61
|
+
*
|
|
62
|
+
* Detection hierarchy:
|
|
63
|
+
* 1. JWT (eyJ prefix, 2 dots) → decode and classify with rich metadata
|
|
64
|
+
* 2. UUID → skip (entity ID, not token)
|
|
65
|
+
* 3. Short values (<16 chars) → skip
|
|
66
|
+
* 4. High-entropy opaque string → classify by entropy threshold
|
|
67
|
+
*/
|
|
68
|
+
export function isLikelyToken(name, value) {
|
|
69
|
+
// Strip "Bearer " prefix for analysis
|
|
70
|
+
const raw = value.startsWith('Bearer ') ? value.slice(7) : value;
|
|
71
|
+
// JWT detection — takes priority
|
|
72
|
+
const jwtClaims = parseJwtClaims(raw);
|
|
73
|
+
if (jwtClaims) {
|
|
74
|
+
return {
|
|
75
|
+
isToken: true,
|
|
76
|
+
confidence: 'high',
|
|
77
|
+
format: 'jwt',
|
|
78
|
+
jwtClaims,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// UUID exclusion — almost always entity IDs, not tokens
|
|
82
|
+
if (UUID_PATTERN.test(raw)) {
|
|
83
|
+
return { isToken: false, confidence: 'medium', format: 'opaque' };
|
|
84
|
+
}
|
|
85
|
+
// Minimum length gate
|
|
86
|
+
if (raw.length < MIN_TOKEN_LENGTH) {
|
|
87
|
+
return { isToken: false, confidence: 'medium', format: 'opaque' };
|
|
88
|
+
}
|
|
89
|
+
// Entropy-based classification
|
|
90
|
+
const entropy = shannonEntropy(raw);
|
|
91
|
+
if (entropy >= 4.5) {
|
|
92
|
+
return { isToken: true, confidence: 'high', format: 'opaque' };
|
|
93
|
+
}
|
|
94
|
+
if (entropy >= 3.5) {
|
|
95
|
+
return { isToken: true, confidence: 'medium', format: 'opaque' };
|
|
96
|
+
}
|
|
97
|
+
// Below threshold — not a token
|
|
98
|
+
return { isToken: false, confidence: 'medium', format: 'opaque' };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=entropy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../src/capture/entropy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FilterableResponse {
|
|
2
|
+
url: string;
|
|
3
|
+
status: number;
|
|
4
|
+
contentType: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Check if a URL path is framework or telemetry noise.
|
|
8
|
+
* Exported for testing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPathNoise(pathname: string): boolean;
|
|
11
|
+
export declare function shouldCapture(response: FilterableResponse): boolean;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/capture/filter.ts
|
|
2
|
+
import { isBlocklisted } from './blocklist.js';
|
|
3
|
+
const JSON_CONTENT_TYPES = [
|
|
4
|
+
'application/json',
|
|
5
|
+
'application/vnd.api+json',
|
|
6
|
+
'text/json',
|
|
7
|
+
];
|
|
8
|
+
/** Exact path matches that are telemetry/framework noise */
|
|
9
|
+
const NOISE_PATHS = new Set([
|
|
10
|
+
'/monitoring',
|
|
11
|
+
'/telemetry',
|
|
12
|
+
'/track',
|
|
13
|
+
'/manifest.json',
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Check if a URL path is framework or telemetry noise.
|
|
17
|
+
* Exported for testing.
|
|
18
|
+
*/
|
|
19
|
+
export function isPathNoise(pathname) {
|
|
20
|
+
// Exact match noise paths
|
|
21
|
+
if (NOISE_PATHS.has(pathname))
|
|
22
|
+
return true;
|
|
23
|
+
// Next.js static build assets (not data routes)
|
|
24
|
+
if (pathname.startsWith('/_next/static/'))
|
|
25
|
+
return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
export function shouldCapture(response) {
|
|
29
|
+
// Only keep 2xx success responses
|
|
30
|
+
if (response.status < 200 || response.status >= 300)
|
|
31
|
+
return false;
|
|
32
|
+
// Content-type must indicate JSON
|
|
33
|
+
const ct = response.contentType.toLowerCase().split(';')[0].trim();
|
|
34
|
+
if (!JSON_CONTENT_TYPES.some(t => ct === t))
|
|
35
|
+
return false;
|
|
36
|
+
// Check domain and path
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(response.url);
|
|
39
|
+
if (isBlocklisted(url.hostname))
|
|
40
|
+
return false;
|
|
41
|
+
if (isPathNoise(url.pathname))
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/capture/filter.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAQ/C,MAAM,kBAAkB,GAAG;IACzB,kBAAkB;IAClB,0BAA0B;IAC1B,WAAW;CACZ,CAAC;AAEF,4DAA4D;AAC5D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,aAAa;IACb,YAAY;IACZ,QAAQ;IACR,gBAAgB;CACjB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,0BAA0B;IAC1B,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,gDAAgD;IAChD,IAAI,QAAQ,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAA4B;IACxD,kCAAkC;IAClC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IAElE,kCAAkC;IAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1D,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface GraphQLParsed {
|
|
2
|
+
operationName: string | null;
|
|
3
|
+
query: string;
|
|
4
|
+
variables: Record<string, unknown> | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Detect if a request is to a GraphQL endpoint.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isGraphQLEndpoint(path: string, contentType: string, body: string | null): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a GraphQL request body.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseGraphQLBody(body: string): GraphQLParsed | null;
|
|
14
|
+
/**
|
|
15
|
+
* Extract operation name from query string or explicit operationName.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractOperationName(query: string, explicitName: string | null): string;
|
|
18
|
+
/**
|
|
19
|
+
* Detect which variables are likely dynamic (IDs, cursors, pagination).
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectGraphQLVariables(variables: Record<string, unknown> | null, prefix?: string): string[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/capture/graphql.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect if a request is to a GraphQL endpoint.
|
|
4
|
+
*/
|
|
5
|
+
export function isGraphQLEndpoint(path, contentType, body) {
|
|
6
|
+
// Path contains /graphql
|
|
7
|
+
if (path.includes('/graphql')) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
// Content-Type is application/graphql
|
|
11
|
+
if (contentType.includes('application/graphql')) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// Body contains a "query" field (GraphQL-style)
|
|
15
|
+
if (body) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(body);
|
|
18
|
+
if (typeof parsed.query === 'string') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Not JSON
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse a GraphQL request body.
|
|
30
|
+
*/
|
|
31
|
+
export function parseGraphQLBody(body) {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(body);
|
|
34
|
+
if (typeof parsed.query !== 'string') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
operationName: parsed.operationName ?? null,
|
|
39
|
+
query: parsed.query,
|
|
40
|
+
variables: parsed.variables ?? null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract operation name from query string or explicit operationName.
|
|
49
|
+
*/
|
|
50
|
+
export function extractOperationName(query, explicitName) {
|
|
51
|
+
if (explicitName) {
|
|
52
|
+
return explicitName;
|
|
53
|
+
}
|
|
54
|
+
// Match "query Name" or "mutation Name" at start
|
|
55
|
+
const match = query.match(/^\s*(query|mutation|subscription)\s+(\w+)/);
|
|
56
|
+
if (match) {
|
|
57
|
+
return match[2];
|
|
58
|
+
}
|
|
59
|
+
return 'Anonymous';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect which variables are likely dynamic (IDs, cursors, pagination).
|
|
63
|
+
*/
|
|
64
|
+
export function detectGraphQLVariables(variables, prefix = '') {
|
|
65
|
+
if (!variables || typeof variables !== 'object') {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const detected = [];
|
|
69
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
70
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
71
|
+
if (typeof value === 'number') {
|
|
72
|
+
// Numbers are often IDs or pagination values
|
|
73
|
+
detected.push(path);
|
|
74
|
+
}
|
|
75
|
+
else if (typeof value === 'string') {
|
|
76
|
+
// Cursor-like strings (base64, long alphanumeric)
|
|
77
|
+
if (isLikelyCursor(value)) {
|
|
78
|
+
detected.push(path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
82
|
+
// Recurse into nested objects
|
|
83
|
+
detected.push(...detectGraphQLVariables(value, path));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return detected;
|
|
87
|
+
}
|
|
88
|
+
function isLikelyCursor(value) {
|
|
89
|
+
// Base64-ish: long alphanumeric, possibly with = padding
|
|
90
|
+
if (value.length > 10 && /^[a-zA-Z0-9+/=_-]+$/.test(value)) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
// UUID-like
|
|
94
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=graphql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../src/capture/graphql.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks unique endpoint discoveries and detects idle periods.
|
|
3
|
+
* Used during interactive capture to nudge the user when no new
|
|
4
|
+
* endpoints have been found for a while.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IdleTracker {
|
|
7
|
+
private seen;
|
|
8
|
+
private lastNewTime;
|
|
9
|
+
private thresholdMs;
|
|
10
|
+
private fired;
|
|
11
|
+
private now;
|
|
12
|
+
constructor(thresholdMs?: number, now?: () => number);
|
|
13
|
+
/**
|
|
14
|
+
* Record an endpoint key (e.g. "GET /api/items").
|
|
15
|
+
* Returns true if it's genuinely new (not seen before).
|
|
16
|
+
*/
|
|
17
|
+
recordEndpoint(key: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check if the idle threshold has been exceeded.
|
|
20
|
+
* Returns true exactly once per idle period (until reset by a new endpoint).
|
|
21
|
+
*/
|
|
22
|
+
checkIdle(): boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/capture/idle.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tracks unique endpoint discoveries and detects idle periods.
|
|
4
|
+
* Used during interactive capture to nudge the user when no new
|
|
5
|
+
* endpoints have been found for a while.
|
|
6
|
+
*/
|
|
7
|
+
export class IdleTracker {
|
|
8
|
+
seen = new Set();
|
|
9
|
+
lastNewTime;
|
|
10
|
+
thresholdMs;
|
|
11
|
+
fired = false;
|
|
12
|
+
now;
|
|
13
|
+
constructor(thresholdMs = 15000, now = Date.now) {
|
|
14
|
+
this.thresholdMs = thresholdMs;
|
|
15
|
+
this.now = now;
|
|
16
|
+
this.lastNewTime = this.now();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Record an endpoint key (e.g. "GET /api/items").
|
|
20
|
+
* Returns true if it's genuinely new (not seen before).
|
|
21
|
+
*/
|
|
22
|
+
recordEndpoint(key) {
|
|
23
|
+
if (this.seen.has(key))
|
|
24
|
+
return false;
|
|
25
|
+
this.seen.add(key);
|
|
26
|
+
this.lastNewTime = this.now();
|
|
27
|
+
this.fired = false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if the idle threshold has been exceeded.
|
|
32
|
+
* Returns true exactly once per idle period (until reset by a new endpoint).
|
|
33
|
+
*/
|
|
34
|
+
checkIdle() {
|
|
35
|
+
if (this.fired)
|
|
36
|
+
return false;
|
|
37
|
+
if (this.now() - this.lastNewTime >= this.thresholdMs) {
|
|
38
|
+
this.fired = true;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=idle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idle.js","sourceRoot":"","sources":["../../src/capture/idle.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAEtB;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACd,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IACzB,WAAW,CAAS;IACpB,WAAW,CAAS;IACpB,KAAK,GAAG,KAAK,CAAC;IACd,GAAG,CAAe;IAE1B,YAAY,WAAW,GAAG,KAAK,EAAE,MAAoB,IAAI,CAAC,GAAG;QAC3D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,GAAW;QACxB,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,SAAS;QACP,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SkillGenerator } from '../skill/generator.js';
|
|
2
|
+
export interface CaptureOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
launch?: boolean;
|
|
6
|
+
attach?: boolean;
|
|
7
|
+
headless?: boolean;
|
|
8
|
+
duration?: number;
|
|
9
|
+
allDomains?: boolean;
|
|
10
|
+
enablePreview?: boolean;
|
|
11
|
+
scrub?: boolean;
|
|
12
|
+
onEndpoint?: (endpoint: {
|
|
13
|
+
id: string;
|
|
14
|
+
method: string;
|
|
15
|
+
path: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
onFiltered?: () => void;
|
|
18
|
+
onIdle?: () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface CaptureResult {
|
|
21
|
+
generators: Map<string, SkillGenerator>;
|
|
22
|
+
totalRequests: number;
|
|
23
|
+
filteredRequests: number;
|
|
24
|
+
domBytes?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function capture(options: CaptureOptions): Promise<CaptureResult>;
|