@shriyanss/js-recon 1.3.1-alpha.1 → 1.3.1-alpha.2

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/build/analyze/engine/astEngine.js +34 -6
  3. package/build/analyze/engine/astEngine.js.map +1 -1
  4. package/build/analyze/helpers/engineHelpers/taintFlow.js +218 -0
  5. package/build/analyze/helpers/engineHelpers/taintFlow.js.map +1 -0
  6. package/build/analyze/helpers/schemas.js +1 -0
  7. package/build/analyze/helpers/schemas.js.map +1 -1
  8. package/build/globalConfig.js +1 -1
  9. package/build/lazyLoad/downloadQueue.js +186 -0
  10. package/build/lazyLoad/downloadQueue.js.map +1 -0
  11. package/build/lazyLoad/index.js +57 -55
  12. package/build/lazyLoad/index.js.map +1 -1
  13. package/build/lazyLoad/next_js/NextJsCrawler.js +34 -11
  14. package/build/lazyLoad/next_js/NextJsCrawler.js.map +1 -1
  15. package/build/lazyLoad/next_js/next_GetJSScript.js +20 -3
  16. package/build/lazyLoad/next_js/next_GetJSScript.js.map +1 -1
  17. package/build/lazyLoad/next_js/next_SubsequentRequests.js +20 -2
  18. package/build/lazyLoad/next_js/next_SubsequentRequests.js.map +1 -1
  19. package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js +10 -1
  20. package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js.map +1 -1
  21. package/build/lazyLoad/techDetect/checkReact.js +13 -2
  22. package/build/lazyLoad/techDetect/checkReact.js.map +1 -1
  23. package/build/lazyLoad/techDetect/index.js +15 -4
  24. package/build/lazyLoad/techDetect/index.js.map +1 -1
  25. package/build/lazyLoad/vue/vue_discoverJsFiles.js +25 -11
  26. package/build/lazyLoad/vue/vue_discoverJsFiles.js.map +1 -1
  27. package/build/lazyLoad/vue/vue_getClientSidePaths.js +31 -4
  28. package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
  29. package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js +3 -3
  30. package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js.map +1 -1
  31. package/build/lazyLoad/vue/vue_stringJsFiles.js +142 -0
  32. package/build/lazyLoad/vue/vue_stringJsFiles.js.map +1 -0
  33. package/build/map/index.js +31 -0
  34. package/build/map/index.js.map +1 -1
  35. package/build/map/next_js/getExports.js +11 -5
  36. package/build/map/next_js/getExports.js.map +1 -1
  37. package/build/map/next_js/getFetchInstances.js +11 -5
  38. package/build/map/next_js/getFetchInstances.js.map +1 -1
  39. package/build/map/next_js/resolveAxios.js +24 -5
  40. package/build/map/next_js/resolveAxios.js.map +1 -1
  41. package/build/map/next_js/resolveAxiosHelpers/astNodeToJsonString.js +2 -2
  42. package/build/map/next_js/resolveAxiosHelpers/astNodeToJsonString.js.map +1 -1
  43. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js +11 -5
  44. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js.map +1 -1
  45. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js +206 -0
  46. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js.map +1 -0
  47. package/build/map/next_js/resolveAxiosHelpers/processAxiosCall.js +25 -8
  48. package/build/map/next_js/resolveAxiosHelpers/processAxiosCall.js.map +1 -1
  49. package/build/map/next_js/resolveAxiosHelpers/processDirectAxiosCall.js +14 -6
  50. package/build/map/next_js/resolveAxiosHelpers/processDirectAxiosCall.js.map +1 -1
  51. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js +22 -10
  52. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js.map +1 -1
  53. package/build/map/next_js/resolveAxiosHelpers/traceBody.js +913 -0
  54. package/build/map/next_js/resolveAxiosHelpers/traceBody.js.map +1 -0
  55. package/build/map/next_js/resolveFetch.js +115 -3
  56. package/build/map/next_js/resolveFetch.js.map +1 -1
  57. package/build/map/next_js/resolveNewRequest.js +749 -0
  58. package/build/map/next_js/resolveNewRequest.js.map +1 -0
  59. package/build/map/next_js/utils.js +311 -49
  60. package/build/map/next_js/utils.js.map +1 -1
  61. package/build/map/vue_js/vue_resolveFetch.js +155 -0
  62. package/build/map/vue_js/vue_resolveFetch.js.map +1 -0
  63. package/build/run/index.js +7 -2
  64. package/build/run/index.js.map +1 -1
  65. package/build/strings/index.js +5 -1
  66. package/build/strings/index.js.map +1 -1
  67. package/build/utility/makeReq.js +65 -8
  68. package/build/utility/makeReq.js.map +1 -1
  69. package/build/utility/openapiGenerator.js +46 -2
  70. package/build/utility/openapiGenerator.js.map +1 -1
  71. package/build/utility/postmanGenerator.js +163 -0
  72. package/build/utility/postmanGenerator.js.map +1 -0
  73. package/package.json +2 -2
@@ -0,0 +1,749 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import chalk from "chalk";
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import parser from "@babel/parser";
14
+ import _traverse from "@babel/traverse";
15
+ import { resolveNodeValue, substituteVariablesInString } from "./utils.js";
16
+ import { astNodeToJsonString } from "./resolveAxiosHelpers/astNodeToJsonString.js";
17
+ import { getThirdArg } from "./resolveAxios.js";
18
+ import * as globals from "../../utility/globals.js";
19
+ const traverse = _traverse.default;
20
+ const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
21
+ /**
22
+ * Detect chunks that define an HTTP-request wrapper class. Heuristic:
23
+ * the chunk contains a `class` whose constructor assigns
24
+ * this.config = { url: ..., method: ..., ... }
25
+ * Returns map of chunkId -> Set of exported binding names that point to such a class.
26
+ */
27
+ const findWrapperClasses = (chunks) => {
28
+ const wrappers = new Map();
29
+ for (const chunk of Object.values(chunks)) {
30
+ if (!chunk.code)
31
+ continue;
32
+ let ast;
33
+ try {
34
+ ast = parser.parse(chunk.code, {
35
+ sourceType: "unambiguous",
36
+ plugins: ["jsx", "typescript"],
37
+ errorRecovery: true,
38
+ });
39
+ }
40
+ catch (_a) {
41
+ continue;
42
+ }
43
+ const wrapperClassNames = new Set();
44
+ traverse(ast, {
45
+ ClassDeclaration(path) {
46
+ var _a;
47
+ const className = (_a = path.node.id) === null || _a === void 0 ? void 0 : _a.name;
48
+ if (!className)
49
+ return;
50
+ if (isWrapperClassBody(path.node.body)) {
51
+ wrapperClassNames.add(className);
52
+ }
53
+ },
54
+ ClassExpression(path) {
55
+ // Class expressions assigned to a variable: let E = class { ... }
56
+ const parent = path.parent;
57
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier") {
58
+ if (isWrapperClassBody(path.node.body)) {
59
+ wrapperClassNames.add(parent.id.name);
60
+ }
61
+ }
62
+ else if (parent.type === "AssignmentExpression" && parent.left.type === "Identifier") {
63
+ if (isWrapperClassBody(path.node.body)) {
64
+ wrapperClassNames.add(parent.left.name);
65
+ }
66
+ }
67
+ },
68
+ });
69
+ if (wrapperClassNames.size === 0)
70
+ continue;
71
+ // Now find which exports point to these classes:
72
+ // o.d(t, { A: () => E, ... })
73
+ const exportedBindings = new Set();
74
+ traverse(ast, {
75
+ CallExpression(path) {
76
+ const callee = path.node.callee;
77
+ if (callee.type === "MemberExpression" &&
78
+ callee.property.type === "Identifier" &&
79
+ callee.property.name === "d" &&
80
+ path.node.arguments.length >= 2 &&
81
+ path.node.arguments[1].type === "ObjectExpression") {
82
+ for (const prop of path.node.arguments[1].properties) {
83
+ if (prop.type !== "ObjectProperty" || prop.key.type !== "Identifier")
84
+ continue;
85
+ const exportName = prop.key.name;
86
+ const value = prop.value;
87
+ // Pattern: A: () => E
88
+ if (value.type === "ArrowFunctionExpression" &&
89
+ value.body.type === "Identifier" &&
90
+ wrapperClassNames.has(value.body.name)) {
91
+ exportedBindings.add(exportName);
92
+ }
93
+ }
94
+ }
95
+ },
96
+ });
97
+ if (exportedBindings.size > 0) {
98
+ wrappers.set(chunk.id, exportedBindings);
99
+ }
100
+ }
101
+ return wrappers;
102
+ };
103
+ /**
104
+ * Returns true if a class body looks like an HTTP wrapper:
105
+ * its constructor assigns `this.config = { url: ..., method: ..., ... }`.
106
+ */
107
+ const isWrapperClassBody = (body) => {
108
+ if (!body || body.type !== "ClassBody")
109
+ return false;
110
+ for (const member of body.body) {
111
+ if (member.type !== "ClassMethod" ||
112
+ member.kind !== "constructor" ||
113
+ !member.body ||
114
+ member.body.type !== "BlockStatement") {
115
+ continue;
116
+ }
117
+ // Walk all statements/expressions in the constructor and look for
118
+ // this.config = { url: ..., method: ... }
119
+ let found = false;
120
+ const walkForConfigAssignment = (stmt) => {
121
+ if (found)
122
+ return;
123
+ if (!stmt || typeof stmt !== "object")
124
+ return;
125
+ if (stmt.type === "AssignmentExpression") {
126
+ const left = stmt.left;
127
+ const right = stmt.right;
128
+ if (left &&
129
+ left.type === "MemberExpression" &&
130
+ left.object.type === "ThisExpression" &&
131
+ left.property.type === "Identifier" &&
132
+ left.property.name === "config" &&
133
+ right &&
134
+ right.type === "ObjectExpression") {
135
+ const keys = right.properties
136
+ .filter((p) => p.type === "ObjectProperty")
137
+ .map((p) => p.key.type === "Identifier"
138
+ ? p.key.name
139
+ : p.key.type === "StringLiteral"
140
+ ? p.key.value
141
+ : null);
142
+ if (keys.includes("url") && keys.includes("method")) {
143
+ found = true;
144
+ return;
145
+ }
146
+ }
147
+ }
148
+ // Recurse into children
149
+ for (const key of Object.keys(stmt)) {
150
+ if (key === "loc" || key === "start" || key === "end")
151
+ continue;
152
+ const child = stmt[key];
153
+ if (Array.isArray(child)) {
154
+ for (const c of child)
155
+ walkForConfigAssignment(c);
156
+ }
157
+ else if (child && typeof child === "object" && child.type) {
158
+ walkForConfigAssignment(child);
159
+ }
160
+ }
161
+ };
162
+ for (const stmt of member.body.body)
163
+ walkForConfigAssignment(stmt);
164
+ if (found)
165
+ return true;
166
+ }
167
+ return false;
168
+ };
169
+ /**
170
+ * Within a chunk, find all `new X(...)` calls. For each, the caller of
171
+ * `findWrapperInstantiations` will decide whether the constructor is a wrapper
172
+ * class. We return the new-expression node plus the matched `X` text.
173
+ */
174
+ const findNewExpressionsWithUrl = (ast) => {
175
+ const results = [];
176
+ traverse(ast, {
177
+ NewExpression(path) {
178
+ const args = path.node.arguments;
179
+ if (args.length === 0)
180
+ return;
181
+ const first = args[0];
182
+ if (first.type !== "ObjectExpression")
183
+ return;
184
+ // Must have a `url` property (heuristic for HTTP config).
185
+ // Accept both identifier (`url: ...`) and string-literal (`"url": ...`) keys.
186
+ const hasUrl = first.properties.some((p) => {
187
+ if (p.type !== "ObjectProperty")
188
+ return false;
189
+ const keyName = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : null;
190
+ return keyName === "url";
191
+ });
192
+ if (!hasUrl)
193
+ return;
194
+ results.push(path);
195
+ },
196
+ });
197
+ return results;
198
+ };
199
+ /**
200
+ * Resolve which chunk + exported binding a `new X.Y(...)` callee refers to.
201
+ * If the callee is `X.Y`, find `X = require(N)` in the current chunk to get N,
202
+ * then verify N is a wrapper chunk and Y is one of its exported wrapper bindings.
203
+ * Returns the targetChunkId if so, else null.
204
+ */
205
+ const resolveCalleeToWrapperChunk = (calleeNode, ast, thirdArgName, wrappers) => {
206
+ if (!thirdArgName)
207
+ return null;
208
+ if (calleeNode.type !== "MemberExpression")
209
+ return null;
210
+ if (calleeNode.object.type !== "Identifier")
211
+ return null;
212
+ if (calleeNode.property.type !== "Identifier")
213
+ return null;
214
+ const objName = calleeNode.object.name;
215
+ const exportName = calleeNode.property.name;
216
+ let targetChunkId = null;
217
+ traverse(ast, {
218
+ VariableDeclarator(p) {
219
+ if (targetChunkId)
220
+ return;
221
+ const id = p.node.id;
222
+ const init = p.node.init;
223
+ if (id.type !== "Identifier" || id.name !== objName || !init)
224
+ return;
225
+ if (init.type === "CallExpression" &&
226
+ init.callee.type === "Identifier" &&
227
+ init.callee.name === thirdArgName &&
228
+ init.arguments.length > 0) {
229
+ const arg = init.arguments[0];
230
+ if (arg.type === "NumericLiteral")
231
+ targetChunkId = String(arg.value);
232
+ else if (arg.type === "StringLiteral")
233
+ targetChunkId = arg.value;
234
+ p.stop();
235
+ }
236
+ },
237
+ AssignmentExpression(p) {
238
+ if (targetChunkId)
239
+ return;
240
+ const left = p.node.left;
241
+ const right = p.node.right;
242
+ if (left.type !== "Identifier" || left.name !== objName)
243
+ return;
244
+ if (right.type === "CallExpression" &&
245
+ right.callee.type === "Identifier" &&
246
+ right.callee.name === thirdArgName &&
247
+ right.arguments.length > 0) {
248
+ const arg = right.arguments[0];
249
+ if (arg.type === "NumericLiteral")
250
+ targetChunkId = String(arg.value);
251
+ else if (arg.type === "StringLiteral")
252
+ targetChunkId = arg.value;
253
+ p.stop();
254
+ }
255
+ },
256
+ });
257
+ if (!targetChunkId)
258
+ return null;
259
+ const exports = wrappers.get(targetChunkId);
260
+ if (!exports || !exports.has(exportName))
261
+ return null;
262
+ return { chunkId: targetChunkId, exportName };
263
+ };
264
+ /**
265
+ * Extract a known string field (e.g., "method") from the first ObjectExpression
266
+ * argument of a `new` call. Returns the raw value (uppercased for methods) or null.
267
+ */
268
+ const extractObjectField = (objExpr, fieldName) => {
269
+ if (!objExpr || objExpr.type !== "ObjectExpression")
270
+ return null;
271
+ for (const prop of objExpr.properties) {
272
+ if (prop.type !== "ObjectProperty")
273
+ continue;
274
+ const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
275
+ if (keyName !== fieldName)
276
+ continue;
277
+ let val = null;
278
+ if (prop.value.type === "StringLiteral")
279
+ val = prop.value.value;
280
+ return { rawNode: prop.value, stringValue: val };
281
+ }
282
+ return null;
283
+ };
284
+ /**
285
+ * Walks upward from a `new X(...)` path to find the enclosing function
286
+ * declaration/expression. Returns its identifier name if it's named (or
287
+ * assigned to a named variable), else null.
288
+ */
289
+ const enclosingFunctionName = (path) => {
290
+ var _a, _b, _c, _d, _e, _f;
291
+ let cur = path;
292
+ while (cur) {
293
+ if ((_a = cur.isFunctionDeclaration) === null || _a === void 0 ? void 0 : _a.call(cur)) {
294
+ return ((_b = cur.node.id) === null || _b === void 0 ? void 0 : _b.name) || null;
295
+ }
296
+ if ((_c = cur.isVariableDeclarator) === null || _c === void 0 ? void 0 : _c.call(cur)) {
297
+ const init = cur.node.init;
298
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
299
+ return cur.node.id.type === "Identifier" ? cur.node.id.name : null;
300
+ }
301
+ }
302
+ if (((_d = cur.isArrowFunctionExpression) === null || _d === void 0 ? void 0 : _d.call(cur)) || ((_e = cur.isFunctionExpression) === null || _e === void 0 ? void 0 : _e.call(cur))) {
303
+ const parent = cur.parent;
304
+ if (parent && parent.type === "VariableDeclarator" && ((_f = parent.id) === null || _f === void 0 ? void 0 : _f.type) === "Identifier") {
305
+ return parent.id.name;
306
+ }
307
+ }
308
+ cur = cur.parentPath;
309
+ }
310
+ return null;
311
+ };
312
+ /**
313
+ * Within a chunk's AST, find calls to `factoryName(...)` followed by `.do(<arg>)`
314
+ * (possibly through an intermediate variable). Returns the first such arg AST
315
+ * node found, or null. Handles:
316
+ * factoryName().do(body)
317
+ * let x = factoryName(); x.do(body)
318
+ */
319
+ const findFactoryDoCallArg = (factoryName, ast) => {
320
+ let result = null;
321
+ // Variables that hold the instance from factoryName()
322
+ const instanceVars = new Set();
323
+ traverse(ast, {
324
+ // let x = factoryName()
325
+ VariableDeclarator(p) {
326
+ const init = p.node.init;
327
+ if (!init)
328
+ return;
329
+ if (init.type === "CallExpression" &&
330
+ init.callee.type === "Identifier" &&
331
+ init.callee.name === factoryName &&
332
+ p.node.id.type === "Identifier") {
333
+ instanceVars.add(p.node.id.name);
334
+ }
335
+ },
336
+ });
337
+ const DO_METHODS_LOCAL = new Set(["do", "doRawResponse"]);
338
+ traverse(ast, {
339
+ CallExpression(p) {
340
+ if (result)
341
+ return;
342
+ const callee = p.node.callee;
343
+ // Pattern: <X>.do(<arg>) or <X>.doRawResponse(<arg>)
344
+ if (callee.type === "MemberExpression" &&
345
+ callee.property.type === "Identifier" &&
346
+ DO_METHODS_LOCAL.has(callee.property.name) &&
347
+ p.node.arguments.length > 0) {
348
+ // Case A: factoryName().do(arg)
349
+ if (callee.object.type === "CallExpression" &&
350
+ callee.object.callee.type === "Identifier" &&
351
+ callee.object.callee.name === factoryName) {
352
+ result = p.node.arguments[0];
353
+ p.stop();
354
+ return;
355
+ }
356
+ // Case B: instance.do(arg) where instance came from factoryName()
357
+ if (callee.object.type === "Identifier" && instanceVars.has(callee.object.name)) {
358
+ result = p.node.arguments[0];
359
+ p.stop();
360
+ return;
361
+ }
362
+ }
363
+ },
364
+ });
365
+ return result;
366
+ };
367
+ /**
368
+ * For a given factory-export name on a wrapper-instantiation chunk, trace
369
+ * across all chunks that import this chunk, looking for `factory().do(arg)`
370
+ * call patterns. Returns the first `.do()` arg node found.
371
+ */
372
+ const traceDoCallAcrossChunks = (sourceChunkId, factoryExportName, chunks) => {
373
+ var _a;
374
+ for (const callerChunk of Object.values(chunks)) {
375
+ if (!((_a = callerChunk.imports) === null || _a === void 0 ? void 0 : _a.includes(sourceChunkId)))
376
+ continue;
377
+ if (!callerChunk.code)
378
+ continue;
379
+ let callerAst;
380
+ try {
381
+ callerAst = parser.parse(callerChunk.code, {
382
+ sourceType: "unambiguous",
383
+ plugins: ["jsx", "typescript"],
384
+ errorRecovery: true,
385
+ });
386
+ }
387
+ catch (_b) {
388
+ continue;
389
+ }
390
+ const thirdArgName = getThirdArg(callerAst);
391
+ if (!thirdArgName)
392
+ continue;
393
+ // Find which local var binds to require(sourceChunkId)
394
+ let localBinding = null;
395
+ traverse(callerAst, {
396
+ VariableDeclarator(p) {
397
+ if (localBinding)
398
+ return;
399
+ const init = p.node.init;
400
+ if (init &&
401
+ init.type === "CallExpression" &&
402
+ init.callee.type === "Identifier" &&
403
+ init.callee.name === thirdArgName &&
404
+ init.arguments.length > 0 &&
405
+ p.node.id.type === "Identifier") {
406
+ const arg = init.arguments[0];
407
+ const argVal = arg.type === "NumericLiteral"
408
+ ? String(arg.value)
409
+ : arg.type === "StringLiteral"
410
+ ? arg.value
411
+ : null;
412
+ if (argVal === sourceChunkId) {
413
+ localBinding = p.node.id.name;
414
+ }
415
+ }
416
+ },
417
+ });
418
+ if (!localBinding)
419
+ continue;
420
+ // Identify a CallExpression that invokes `localBinding.factoryExportName(...)`,
421
+ // including the `(0, b.factoryName)(...)` SequenceExpression form.
422
+ const isFactoryCall = (n) => {
423
+ if (!n || n.type !== "CallExpression")
424
+ return false;
425
+ const c = n.callee;
426
+ // localBinding.factoryExportName(...)
427
+ if (c.type === "MemberExpression" &&
428
+ c.object.type === "Identifier" &&
429
+ c.object.name === localBinding &&
430
+ c.property.type === "Identifier" &&
431
+ c.property.name === factoryExportName)
432
+ return true;
433
+ // (0, localBinding.factoryExportName)(...)
434
+ if (c.type === "SequenceExpression" && c.expressions.length === 2) {
435
+ const last = c.expressions[1];
436
+ if (last.type === "MemberExpression" &&
437
+ last.object.type === "Identifier" &&
438
+ last.object.name === localBinding &&
439
+ last.property.type === "Identifier" &&
440
+ last.property.name === factoryExportName)
441
+ return true;
442
+ }
443
+ return false;
444
+ };
445
+ // Step A: collect instance variable names. Patterns considered:
446
+ // let x = factory()
447
+ // let x = useRef(factory()) / useMemo(() => factory())
448
+ // let x = anyCall(factory()) // assume `.current.do` access for these
449
+ // Returns: Map<varName, {viaCurrent: boolean}>
450
+ const instanceVars = new Map();
451
+ const findFactoryInExpression = (expr) => {
452
+ if (!expr || typeof expr !== "object")
453
+ return { found: false, depth: 0 };
454
+ if (isFactoryCall(expr))
455
+ return { found: true, depth: 0 };
456
+ if (expr.type === "CallExpression" && expr.arguments) {
457
+ for (const a of expr.arguments) {
458
+ const r = findFactoryInExpression(a);
459
+ if (r.found)
460
+ return { found: true, depth: r.depth + 1 };
461
+ }
462
+ }
463
+ if (expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") {
464
+ if (expr.body) {
465
+ const r = findFactoryInExpression(expr.body);
466
+ if (r.found)
467
+ return { found: true, depth: r.depth + 1 };
468
+ }
469
+ }
470
+ if (expr.type === "BlockStatement" && expr.body) {
471
+ for (const s of expr.body) {
472
+ if (s.type === "ReturnStatement" && s.argument) {
473
+ const r = findFactoryInExpression(s.argument);
474
+ if (r.found)
475
+ return { found: true, depth: r.depth + 1 };
476
+ }
477
+ }
478
+ }
479
+ return { found: false, depth: 0 };
480
+ };
481
+ traverse(callerAst, {
482
+ VariableDeclarator(p) {
483
+ const init = p.node.init;
484
+ if (!init || p.node.id.type !== "Identifier")
485
+ return;
486
+ const r = findFactoryInExpression(init);
487
+ if (r.found) {
488
+ // depth>0 means it's wrapped (e.g., useRef) → access via .current
489
+ instanceVars.set(p.node.id.name, { viaCurrent: r.depth > 0 });
490
+ }
491
+ },
492
+ });
493
+ // Step B: find `.do(arg)` / `.doRawResponse(arg)` calls referencing an
494
+ // instance var or factory directly.
495
+ let argNode = null;
496
+ const DO_METHODS = new Set(["do", "doRawResponse"]);
497
+ traverse(callerAst, {
498
+ CallExpression(p) {
499
+ if (argNode)
500
+ return;
501
+ const callee = p.node.callee;
502
+ if (callee.type !== "MemberExpression" ||
503
+ callee.property.type !== "Identifier" ||
504
+ !DO_METHODS.has(callee.property.name) ||
505
+ p.node.arguments.length === 0)
506
+ return;
507
+ const recv = callee.object;
508
+ // Pattern 1: factory().do(arg)
509
+ if (isFactoryCall(recv)) {
510
+ argNode = p.node.arguments[0];
511
+ p.stop();
512
+ return;
513
+ }
514
+ // Pattern 2: instance.do(arg)
515
+ if (recv.type === "Identifier") {
516
+ const info = instanceVars.get(recv.name);
517
+ if (info && !info.viaCurrent) {
518
+ argNode = p.node.arguments[0];
519
+ p.stop();
520
+ return;
521
+ }
522
+ }
523
+ // Pattern 3: instance.current.do(arg)
524
+ if (recv.type === "MemberExpression" &&
525
+ recv.object.type === "Identifier" &&
526
+ recv.property.type === "Identifier" &&
527
+ recv.property.name === "current") {
528
+ const info = instanceVars.get(recv.object.name);
529
+ if (info) {
530
+ argNode = p.node.arguments[0];
531
+ p.stop();
532
+ return;
533
+ }
534
+ }
535
+ },
536
+ });
537
+ if (argNode) {
538
+ return { argNode, chunkCode: callerChunk.code };
539
+ }
540
+ }
541
+ return null;
542
+ };
543
+ /**
544
+ * Resolves wrapper-class HTTP requests: detects `new X.Y({url, method, ...}, ...)`
545
+ * patterns where X.Y is an exported HTTP-request wrapper class from another
546
+ * webpack chunk, then resolves the URL, method, and body schema.
547
+ */
548
+ const resolveNewRequest = (chunks, directory) => __awaiter(void 0, void 0, void 0, function* () {
549
+ console.log(chalk.cyan("[i] Resolving wrapper-class HTTP requests (new X({url, method, ...}))"));
550
+ const wrappers = findWrapperClasses(chunks);
551
+ if (wrappers.size === 0) {
552
+ console.log(chalk.yellow(" [!] No HTTP-wrapper classes detected"));
553
+ return;
554
+ }
555
+ for (const [chunkId, exports] of wrappers.entries()) {
556
+ console.log(chalk.green(` [✓] Wrapper class chunk ${chunkId} exports: ${Array.from(exports).join(", ")}`));
557
+ }
558
+ // Also collect, per wrapper chunk, the set of "factory" export bindings
559
+ // i.e. functions in OTHER chunks that return `new <wrapper>(...)` instances.
560
+ // Built lazily as we scan chunks below.
561
+ for (const chunk of Object.values(chunks)) {
562
+ if (!chunk.code || !chunk.file)
563
+ continue;
564
+ let ast;
565
+ try {
566
+ ast = parser.parse(chunk.code, {
567
+ sourceType: "unambiguous",
568
+ plugins: ["jsx", "typescript"],
569
+ errorRecovery: true,
570
+ });
571
+ }
572
+ catch (_a) {
573
+ continue;
574
+ }
575
+ const thirdArgName = getThirdArg(ast);
576
+ if (!thirdArgName)
577
+ continue;
578
+ const newExprPaths = findNewExpressionsWithUrl(ast);
579
+ if (newExprPaths.length === 0)
580
+ continue;
581
+ const filePath = path.join(directory, chunk.file);
582
+ let fileContent;
583
+ try {
584
+ fileContent = fs.readFileSync(filePath, "utf-8");
585
+ }
586
+ catch (_b) {
587
+ fileContent = chunk.code;
588
+ }
589
+ // Per-chunk: build a map of local factory-function names -> the export
590
+ // name (in the wrapper-instance chunk) under which they're exported.
591
+ const factoryExports = new Map();
592
+ traverse(ast, {
593
+ CallExpression(p) {
594
+ const callee = p.node.callee;
595
+ if (callee.type === "MemberExpression" &&
596
+ callee.property.type === "Identifier" &&
597
+ callee.property.name === "d" &&
598
+ p.node.arguments.length >= 2 &&
599
+ p.node.arguments[1].type === "ObjectExpression") {
600
+ for (const prop of p.node.arguments[1].properties) {
601
+ if (prop.type === "ObjectProperty" &&
602
+ prop.key.type === "Identifier" &&
603
+ prop.value.type === "ArrowFunctionExpression" &&
604
+ prop.value.body.type === "Identifier") {
605
+ factoryExports.set(prop.value.body.name, prop.key.name);
606
+ }
607
+ }
608
+ }
609
+ },
610
+ });
611
+ for (const newPath of newExprPaths) {
612
+ const calleeNode = newPath.node.callee;
613
+ const target = resolveCalleeToWrapperChunk(calleeNode, ast, thirdArgName, wrappers);
614
+ if (!target)
615
+ continue;
616
+ const args = newPath.node.arguments;
617
+ const configObj = args[0];
618
+ const optionsObj = args.length > 1 ? args[1] : null;
619
+ // Resolve URL
620
+ const urlField = extractObjectField(configObj, "url");
621
+ if (!urlField)
622
+ continue;
623
+ const urlSrc = chunk.code.slice(urlField.rawNode.start, urlField.rawNode.end);
624
+ let url = urlField.stringValue;
625
+ if (url === null) {
626
+ url = resolveNodeValue(urlField.rawNode, newPath.scope, urlSrc, "new", chunk.code, chunks, thirdArgName);
627
+ if (typeof url === "string" && (url.includes("[var ") || url.includes("[MemberExpression"))) {
628
+ const substituted = substituteVariablesInString(url, chunk.code, chunks, thirdArgName);
629
+ if (substituted)
630
+ url = substituted;
631
+ }
632
+ }
633
+ // Resolve method
634
+ const methodField = extractObjectField(configObj, "method");
635
+ let method = "GET";
636
+ if (methodField) {
637
+ if (methodField.stringValue) {
638
+ method = methodField.stringValue.toUpperCase();
639
+ }
640
+ else {
641
+ const resolved = resolveNodeValue(methodField.rawNode, newPath.scope, "", "new", chunk.code, chunks, thirdArgName);
642
+ if (typeof resolved === "string" && HTTP_METHODS.includes(resolved.toUpperCase())) {
643
+ method = resolved.toUpperCase();
644
+ }
645
+ }
646
+ }
647
+ // Determine dataType (body vs query). The wrapper class defaults to "query"
648
+ // if not specified, so absence ≡ "query".
649
+ let dataType = "query";
650
+ if (optionsObj && optionsObj.type === "ObjectExpression") {
651
+ const dt = extractObjectField(optionsObj, "dataType");
652
+ if ((dt === null || dt === void 0 ? void 0 : dt.stringValue) === "body")
653
+ dataType = "body";
654
+ }
655
+ // Find enclosing factory function name, then try to trace .do(<arg>)
656
+ // calls from chunks that import this chunk.
657
+ let bodyJson = "";
658
+ const factoryName = enclosingFunctionName(newPath);
659
+ if (factoryName) {
660
+ const exportName = factoryExports.get(factoryName);
661
+ if (exportName) {
662
+ // Also try local in-chunk call: factoryName().do(arg)
663
+ let argInfo = null;
664
+ const localArg = findFactoryDoCallArg(factoryName, ast);
665
+ if (localArg) {
666
+ argInfo = { argNode: localArg, chunkCode: chunk.code };
667
+ }
668
+ else {
669
+ argInfo = traceDoCallAcrossChunks(chunk.id, exportName, chunks);
670
+ }
671
+ if (argInfo) {
672
+ bodyJson = astNodeToJsonString(argInfo.argNode, argInfo.chunkCode);
673
+ }
674
+ }
675
+ }
676
+ const lineNo = findLineInFile(fileContent, chunk.code, newPath.node);
677
+ console.log(chalk.blue(`[+] Found wrapped HTTP request in chunk ${chunk.id} ("${filePath}":${lineNo})`));
678
+ // For dataType="query", attach the resolved arg keys as a query string
679
+ // to the URL so the OpenAPI generator surfaces them as `in: query` params.
680
+ let finalUrl = typeof url === "string" ? url : String(url !== null && url !== void 0 ? url : "");
681
+ if (dataType === "query" && bodyJson) {
682
+ const qs = jsonStringToQueryString(bodyJson);
683
+ if (qs)
684
+ finalUrl = finalUrl + (finalUrl.includes("?") ? "&" : "?") + qs;
685
+ }
686
+ console.log(chalk.green(` URL: ${finalUrl}`));
687
+ console.log(chalk.green(` Method: ${method}`));
688
+ console.log(chalk.gray(` dataType: ${dataType}`));
689
+ if (bodyJson) {
690
+ if (dataType === "query") {
691
+ console.log(chalk.green(` Query params: ${bodyJson}`));
692
+ }
693
+ else {
694
+ console.log(chalk.green(` Body: ${bodyJson}`));
695
+ }
696
+ }
697
+ globals.addOpenapiOutput({
698
+ url: finalUrl,
699
+ method,
700
+ path: finalUrl,
701
+ headers: {},
702
+ body: dataType === "body" ? bodyJson : "",
703
+ chunkId: chunk.id,
704
+ functionFile: filePath,
705
+ functionFileLine: lineNo,
706
+ });
707
+ }
708
+ }
709
+ });
710
+ /**
711
+ * Convert a JSON-ish string produced by astNodeToJsonString
712
+ * (which renders identifiers as bare strings and member exprs as code text)
713
+ * into a `key1=val1&key2=val2` query string for OpenAPI consumption.
714
+ * Best-effort: any unparseable values are emitted as `{var}` placeholders.
715
+ */
716
+ const jsonStringToQueryString = (s) => {
717
+ try {
718
+ // astNodeToJsonString emits valid-ish JSON; try parsing first
719
+ const obj = JSON.parse(s);
720
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj))
721
+ return "";
722
+ const parts = [];
723
+ for (const [k, v] of Object.entries(obj)) {
724
+ const valStr = typeof v === "string" ? v : JSON.stringify(v);
725
+ parts.push(`${encodeURIComponent(k)}={${encodeURIComponent(valStr)}}`);
726
+ }
727
+ return parts.join("&");
728
+ }
729
+ catch (_a) {
730
+ return "";
731
+ }
732
+ };
733
+ const findLineInFile = (fileContent, chunkCode, node) => {
734
+ try {
735
+ const snippet = chunkCode.slice(node.start, node.end).split("\n")[0].trim();
736
+ if (!snippet)
737
+ return -1;
738
+ const probe = snippet.slice(0, Math.min(snippet.length, 40));
739
+ const lines = fileContent.split("\n");
740
+ for (let i = 0; i < lines.length; i++) {
741
+ if (lines[i].includes(probe))
742
+ return i + 1;
743
+ }
744
+ }
745
+ catch (_a) { }
746
+ return -1;
747
+ };
748
+ export default resolveNewRequest;
749
+ //# sourceMappingURL=resolveNewRequest.js.map