auto-api-discovery 1.0.1 → 1.0.5

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/dist/crawler.js CHANGED
@@ -51,7 +51,6 @@ async function startCrawler(targetUrl, maxDepth = 2, maxPages = 50) {
51
51
  try {
52
52
  browser = await playwright_1.chromium.launch({ headless: true });
53
53
  const context = await browser.newContext();
54
- // Load cookies if available to run fully authenticated
55
54
  if (fs.existsSync(SESSION_FILE)) {
56
55
  try {
57
56
  const cookies = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
@@ -63,7 +62,6 @@ async function startCrawler(targetUrl, maxDepth = 2, maxPages = 50) {
63
62
  }
64
63
  }
65
64
  const page = await context.newPage();
66
- // Attach the EXACT same interceptor from Milestone 1
67
65
  (0, interceptor_1.attachInterceptor)(page);
68
66
  let parsedTargetUrl;
69
67
  try {
@@ -84,7 +82,6 @@ async function startCrawler(targetUrl, maxDepth = 2, maxPages = 50) {
84
82
  if (!current)
85
83
  break;
86
84
  const { url: currentUrl, depth } = current;
87
- // Normalize URL (strip pure hash fragments to avoid duplicates)
88
85
  let normalizedUrl = currentUrl;
89
86
  try {
90
87
  const pureUrl = new URL(currentUrl);
@@ -99,28 +96,24 @@ async function startCrawler(targetUrl, maxDepth = 2, maxPages = 50) {
99
96
  try {
100
97
  await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
101
98
  pagesProcessed++;
102
- // Random delay to avoid aggressive WAF blocks
103
99
  await randomDelay(500, 1500);
104
100
  if (depth < maxDepth) {
105
- // Extract all <a> tags and map their absolute URLs
106
101
  const links = await page.$$eval('a', anchors => anchors.map(a => a.href));
107
102
  for (const link of links) {
108
103
  if (!link)
109
104
  continue;
110
105
  try {
111
106
  const parsedLink = new URL(link);
112
- // Filter out external links strictly based on target domain boundaries
113
107
  if (parsedLink.hostname === domain || parsedLink.hostname.endsWith(`.${domain}`)) {
114
108
  parsedLink.hash = '';
115
109
  const nextUrl = parsedLink.toString();
116
- // Add new links to queue
117
110
  if (!visited.has(nextUrl)) {
111
+ visited.add(nextUrl);
118
112
  queue.push({ url: nextUrl, depth: depth + 1 });
119
113
  }
120
114
  }
121
115
  }
122
116
  catch (err) {
123
- // Ignore invalid or weird internal hrefs (`javascript:`, etc)
124
117
  }
125
118
  }
126
119
  }
package/dist/db.js CHANGED
@@ -7,11 +7,9 @@ exports.insertEndpoint = insertEndpoint;
7
7
  exports.getAllEndpoints = getAllEndpoints;
8
8
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
9
  const path_1 = __importDefault(require("path"));
10
- // Initialize db
11
10
  const dbPath = path_1.default.resolve(process.cwd(), 'apigen.db');
12
11
  const db = new better_sqlite3_1.default(dbPath);
13
12
  db.pragma('journal_mode = WAL');
14
- // Create table
15
13
  db.exec(`
16
14
  CREATE TABLE IF NOT EXISTS endpoints (
17
15
  id TEXT PRIMARY KEY,
package/dist/index.js CHANGED
@@ -66,7 +66,6 @@ program
66
66
  await page.goto(url, { waitUntil: 'domcontentloaded' });
67
67
  console.log(chalk_1.default.green('Navigation complete. Intercepting API traffic...'));
68
68
  console.log(chalk_1.default.gray('Terminal output shows real-time capture. Close the browser window to exit.'));
69
- // Save cookies safely in a loop to guarantee they are captured before exit
70
69
  const sessionFile = path.resolve(process.cwd(), '.apigen-session.json');
71
70
  let isRunning = true;
72
71
  browser.on('disconnected', () => {
@@ -74,14 +73,12 @@ program
74
73
  console.log(chalk_1.default.yellow('\nBrowser closed. Session saved. Exiting apigen gracefully...'));
75
74
  process.exit(0);
76
75
  });
77
- // Periodically sync the cookies securely to avoid missing them if context is torn down quickly
78
76
  while (isRunning) {
79
77
  try {
80
78
  const cookies = await context.cookies();
81
79
  fs.writeFileSync(sessionFile, JSON.stringify(cookies, null, 2), 'utf-8');
82
80
  }
83
81
  catch (e) {
84
- // Might hit target closed error silently during exit
85
82
  }
86
83
  await new Promise(resolve => setTimeout(resolve, 2000));
87
84
  }
@@ -103,7 +100,6 @@ program
103
100
  process.exit(0);
104
101
  }
105
102
  console.log(chalk_1.default.blue(`Found ${endpoints.length} raw endpoints. Generating schema map...`));
106
- // Process URLs and inference schemas
107
103
  const schemaMap = (0, schema_engine_1.generateSchemaMap)(endpoints);
108
104
  const finalMap = Object.values(schemaMap);
109
105
  console.log(chalk_1.default.green(`Folded into ${finalMap.length} unique routes.`));
@@ -7,25 +7,21 @@ exports.attachInterceptor = attachInterceptor;
7
7
  const crypto_1 = require("crypto");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const db_1 = require("./db");
10
- const IGNORED_RESOURCE_TYPES = new Set(['image', 'stylesheet', 'font', 'media', 'script', 'document']);
11
10
  const TARGET_RESOURCE_TYPES = new Set(['xhr', 'fetch']);
12
11
  function attachInterceptor(page) {
13
12
  page.on('response', async (response) => {
14
13
  const request = response.request();
15
14
  const resourceType = request.resourceType();
16
- // 1. Crucial Filter: ONLY capture traffic if it is XHR, Fetch etc.
17
15
  if (!TARGET_RESOURCE_TYPES.has(resourceType)) {
18
16
  return;
19
17
  }
20
18
  const url = request.url();
21
- // Ignore tracking domains / static extensions
22
19
  if (url.includes('google-analytics.com') ||
23
20
  url.includes('googletagmanager.com') ||
24
21
  url.match(/\.(png|jpg|jpeg|gif|css|woff2?|js|ico|svg)$/i)) {
25
22
  return;
26
23
  }
27
24
  const method = request.method();
28
- // Ignore preflight requests
29
25
  if (method === 'OPTIONS')
30
26
  return;
31
27
  try {
@@ -33,17 +29,15 @@ function attachInterceptor(page) {
33
29
  const headers = request.headers();
34
30
  let reqBodyParsed = null;
35
31
  let resBodyParsed = null;
36
- // Parse request post body
37
32
  const postData = request.postData();
38
33
  if (postData) {
39
34
  try {
40
35
  reqBodyParsed = JSON.parse(postData);
41
36
  }
42
37
  catch {
43
- reqBodyParsed = postData; // Fallback to raw string
38
+ reqBodyParsed = postData;
44
39
  }
45
40
  }
46
- // Parse response body (JSON, text)
47
41
  const contentType = response.headers()['content-type'] || '';
48
42
  if (contentType.includes('application/json') || contentType.includes('text/')) {
49
43
  try {
@@ -63,16 +57,20 @@ function attachInterceptor(page) {
63
57
  else {
64
58
  resBodyParsed = "[Binary or Unsupported Content]";
65
59
  }
66
- // Extract basic path pattern
60
+ let finalUrl = url;
61
+ if (reqBodyParsed && reqBodyParsed.operationName && finalUrl.includes('/graphql')) {
62
+ const separator = finalUrl.includes('?') ? '&' : '?';
63
+ finalUrl = `${finalUrl}${separator}op=${reqBodyParsed.operationName}`;
64
+ }
67
65
  let pathPattern = '/';
68
66
  try {
69
- pathPattern = new URL(url).pathname;
67
+ pathPattern = new URL(finalUrl).pathname;
70
68
  }
71
69
  catch { }
72
70
  const data = {
73
71
  id: (0, crypto_1.randomUUID)(),
74
72
  method,
75
- url,
73
+ url: finalUrl,
76
74
  path_pattern: pathPattern,
77
75
  request_headers: headers,
78
76
  request_body: reqBodyParsed,
@@ -26,7 +26,7 @@ function mapSchemaToOpenAPI(customSchema) {
26
26
  }
27
27
  return { type: 'object', properties };
28
28
  }
29
- return {}; // fallback
29
+ return {};
30
30
  }
31
31
  function extractPathParams(path) {
32
32
  const matches = path.match(/\{([^}]+)\}/g);
@@ -77,7 +77,6 @@ function generateOpenAPI(customSchema, baseUrl) {
77
77
  }
78
78
  };
79
79
  }
80
- // Default response if empty
81
80
  if (Object.keys(operation.responses).length === 0) {
82
81
  operation.responses['200'] = { description: 'Success' };
83
82
  }
@@ -44,7 +44,6 @@ function inferSchema(data) {
44
44
  if (Array.isArray(data)) {
45
45
  if (data.length === 0)
46
46
  return ['any'];
47
- // Infer schema of the object inside the array
48
47
  return [inferSchema(data[0])];
49
48
  }
50
49
  if (typeof data === 'object') {
@@ -74,7 +73,7 @@ function generateSchemaMap(endpoints) {
74
73
  try {
75
74
  bodyData = JSON.parse(bodyData);
76
75
  }
77
- catch { } // Leave as string if parsing fails
76
+ catch { }
78
77
  }
79
78
  if (bodyData && typeof bodyData === 'object') {
80
79
  const schema = inferSchema(bodyData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-api-discovery",
3
- "version": "1.0.1",
3
+ "version": "1.0.5",
4
4
  "description": "API discovery automation CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,8 +12,9 @@
12
12
  "scripts": {
13
13
  "build": "tsc",
14
14
  "dev": "ts-node src/index.ts",
15
+ "test": "tsc --noEmit",
15
16
  "prepublishOnly": "npm run build",
16
- "postinstall": "playwright install"
17
+ "postinstall": "playwright install chromium"
17
18
  },
18
19
  "keywords": [
19
20
  "api",
@@ -36,4 +37,4 @@
36
37
  "ts-node": "^10.9.2",
37
38
  "typescript": "^5.3.3"
38
39
  }
39
- }
40
+ }